> ## Documentation Index
> Fetch the complete documentation index at: https://injectivelabs-docs-tokenization-section.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# 通过 Keplr 钱包使用 Ledger 交易

在本页面中，我们将了解当用户通过 Keplr 钱包使用 Ledger 设备时 Injective 的实现方式。

如前所述，Injective 使用与其他 Cosmos 链不同的派生曲线，这意味着用户必须使用 Ethereum 应用程序（目前）与 Injective 交互。

要覆盖所有边缘情况并获得所有 Injective 支持钱包的完整开箱即用解决方案，建议你查看 [MsgBroadcaster + WalletStrategy](./msgbroadcaster/#msgbroadcaster-+-wallet-strategy) 抽象。如果你想自己实现，让我们一起看看代码示例。

## 概述

Keplr 暴露了一个 `experimentalSignEIP712CosmosTx_v0` 方法，可用于签名 EIP712 类型数据（通过将 Cosmos StdSignDoc 传递给上述方法在 Keplr 端自动生成），并允许 EVM 兼容链在通过 Keplr 连接 Ledger 设备时获得正确的签名。

以下是函数签名：

```typescript theme={null}
/**
 * 使用 ethermint 的 EIP-712 格式签名 sign doc。
 * 与 signEthereum(..., EthSignType.EIP712) 的区别在于，此 api 返回由用户费用设置更改的新 sign doc 以及该 sign doc 的签名。
 * 将 tx 编码为 EIP-712 格式应在使用此 api 的一侧完成。
 * 与 cosmjs 不兼容。
 * 返回的签名是 (r | s | v) 格式，用于 ethereum。
 * v 应该是 27 或 28，用于 ethereum 主网，与链无关。
 * @param chainId
 * @param signer
 * @param eip712
 * @param signDoc
 * @param signOptions
 */
experimentalSignEIP712CosmosTx_v0(chainId: string, signer: string, eip712: {
    types: Record<string, {
        name: string;
        type: string;
    }[] | undefined>;
    domain: Record<string, any>;
    primaryType: string;
}, signDoc: StdSignDoc, signOptions?: KeplrSignOptions): Promise<AminoSignResponse>;


```

我们现在需要做的是生成 `eip712` 和 `signDoc`，将它们传递给这个函数，Keplr 将要求用户使用 Ledger 设备上的 Ethereum 应用程序签名交易。

## 示例实现

基于上述概述，现在让我们展示如何使用 Ledger + Keplr 在 Injective 上签名交易的完整示例。请记住，下面的示例考虑到你正在使用从 `@injectivelabs/sdk-ts` 包导出的 [Msgs](https://github.com/InjectiveLabs/injective-ts/blob/master/packages/sdk-ts/src/core/modules/msgs.ts#L60) 接口。

```typescript theme={null}
import {
 TxGrpcApi,
 SIGN_AMINO,
 createTransaction,
 createTxRawEIP712,
 getEip712TypedData
 createWeb3Extension,
 getGasPriceBasedOnMessage,
} from '@injectivelabs/sdk-ts/core/tx'
import {
 BaseAccount,
} from '@injectivelabs/sdk-ts/core/accounts'
import {
 ChainRestAuthApi,
 ChainRestTendermintApi,
} from '@injectivelabs/sdk-ts/client/chain'
import { EvmChainId, ChainId } from '@injectivelabs/ts-types'
import { getNetworkEndpoints, NetworkEndpoints, Network } from '@injectivelabs/networks'
import { GeneralException, TransactionException } from '@injectivelabs/exceptions'
import { toBigNumber, getStdFee } from '@injectivelabs/utils'

export interface Options {
  evmChainId: EvmChainId /* Evm chain id */
  chainId: ChainId; /* Injective chain id */
  endpoints: NetworkEndpoints /* 可以根据 Network 从 @injectivelabs/networks 获取 */
}

export interface Transaction {
  memo?: string
  injectiveAddress?: string
  msgs: Msgs | Msgs[]

  // 如果我们想手动设置 gas 选项
  gas?: {
    gasPrice?: string
    gas?: number /** gas limit */
    feePayer?: string
    granter?: string
  }
}

/** 将 EIP712 tx 详情转换为 Cosmos Std Sign Doc */
export const createEip712StdSignDoc = ({
  memo,
  chainId,
  accountNumber,
  timeoutHeight,
  sequence,
  gas,
  msgs,
}: {
  memo?: string
  chainId: ChainId
  timeoutHeight?: string
  accountNumber: number
  sequence: number
  gas?: string
  msgs: Msgs[]
}) => ({
  chain_id: chainId,
  timeout_height: timeoutHeight || '',
  account_number: accountNumber.toString(),
  sequence: sequence.toString(),
  fee: getStdFee({ gas }),
  msgs: msgs.map((m) => m.toEip712()),
  memo: memo || '',
})

/**
 * 我们仅在想要使用 Keplr 上的 Ledger 为 Injective 广播交易时使用此方法
 *
 * 注意：Gas 估算不可用
 * @param tx 需要广播的交易
 */
export const experimentalBroadcastKeplrWithLedger = async (
  tx: Transaction,
  options: Options
) => {
  const { endpoints, chainId, evmChainId } = options
  const msgs = Array.isArray(tx.msgs) ? tx.msgs : [tx.msgs]
  const DEFAULT_BLOCK_TIMEOUT_HEIGHT = 60

  /**
   * 你可以选择执行检查
   * 用户是否确实使用 Ledger + Keplr 连接
   */
  if (/* 你的条件在这里 */) {
    throw new GeneralException(
        new Error(
          'This method can only be used when Keplr is connected with Ledger',
        ),
      )
  }

  /** 账户详情 * */
  const chainRestAuthApi = new ChainRestAuthApi(endpoints.rest)
  const accountDetailsResponse = await chainRestAuthApi.fetchAccount(
    tx.injectiveAddress,
  )
  const baseAccount = BaseAccount.fromRestApi(accountDetailsResponse)
  const accountDetails = baseAccount.toAccountDetails()

  /** 区块详情 */
  const chainRestTendermintApi = new ChainRestTendermintApi(endpoints.rest)
  const latestBlock = await chainRestTendermintApi.fetchLatestBlock()
  const latestHeight = latestBlock.header.height
  const timeoutHeight = toBigNumber(latestHeight).plus(
    DEFAULT_BLOCK_TIMEOUT_HEIGHT,
  )

  const key = await window.keplr.getKey(chainId)
  const pubKey = Buffer.from(key.pubKey).toString('base64')
  const gas = (tx.gas?.gas || getGasPriceBasedOnMessage(msgs)).toString()

  /** 用于在 Ethereum 钱包上签名的 EIP712 */
  const eip712TypedData = getEip712TypedData({
    msgs,
    fee: getStdFee({ ...tx.gas, gas }),
    tx: {
      memo: tx.memo,
      accountNumber: accountDetails.accountNumber.toString(),
      sequence: accountDetails.sequence.toString(),
      timeoutHeight: timeoutHeight.toFixed(),
      chainId,
    },
    evmChainId,
  })

  const aminoSignResponse = await window.keplr.experimentalSignEIP712CosmosTx_v0(
    chainId,
    tx.injectiveAddress,
    eip712TypedData,
    createEip712StdSignDoc({
      ...tx,
      ...baseAccount,
      msgs,
      chainId,
      gas: gas || tx.gas?.gas?.toString(),
      timeoutHeight: timeoutHeight.toFixed(),
    })
  )

  /**
   * 从签名的 tx 创建 TxRaw，
   * 以防用户在 Keplr 弹窗中更改了费用/备注
   */
  const { txRaw } = createTransaction({
    pubKey,
    message: msgs,
    memo: aminoSignResponse.signed.memo,
    signMode: SIGN_AMINO,
    fee: aminoSignResponse.signed.fee,
    sequence: parseInt(aminoSignResponse.signed.sequence, 10),
    timeoutHeight: parseInt(
      (aminoSignResponse.signed as any).timeout_height,
      10,
    ),
    accountNumber: parseInt(aminoSignResponse.signed.account_number, 10),
    chainId,
  })

  /** 准备交易以进行客户端广播 */
  const web3Extension = createWeb3Extension({
    evmChainId,
  })
  const txRawEip712 = createTxRawEIP712(txRaw, web3Extension)

  /** 附加签名 */
  const signatureBuff = Buffer.from(
    aminoSignResponse.signature.signature,
    'base64',
  )
  txRawEip712.signatures = [signatureBuff]

  /** 广播交易 */
  const response = await new TxGrpcApi(endpoints.grpc).broadcast(txRawEip712)

  if (response.code !== 0) {
    throw new TransactionException(new Error(response.rawLog), {
      code: UnspecifiedErrorCode,
      contextCode: response.code,
      contextModule: response.codespace,
    })
  }

  return response
}
```
