跳转到主要内容

Documentation Index

Fetch the complete documentation index at: https://injectivelabs-mintlify-jp-developers-first-half-1777019423.mintlify.app/llms.txt

Use this file to discover all available pages before exploring further.

在本页面中,我们将了解当用户通过 Keplr 钱包使用 Ledger 设备时 Injective 的实现方式。 如前所述,Injective 使用与其他 Cosmos 链不同的派生曲线,这意味着用户必须使用 Ethereum 应用程序(目前)与 Injective 交互。 要覆盖所有边缘情况并获得所有 Injective 支持钱包的完整开箱即用解决方案,建议你查看 MsgBroadcaster + WalletStrategy 抽象。如果你想自己实现,让我们一起看看代码示例。

概述

Keplr 暴露了一个 experimentalSignEIP712CosmosTx_v0 方法,可用于签名 EIP712 类型数据(通过将 Cosmos StdSignDoc 传递给上述方法在 Keplr 端自动生成),并允许 EVM 兼容链在通过 Keplr 连接 Ledger 设备时获得正确的签名。 以下是函数签名:
/**
 * 使用 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>;


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

示例实现

基于上述概述,现在让我们展示如何使用 Ledger + Keplr 在 Injective 上签名交易的完整示例。请记住,下面的示例考虑到你正在使用从 @injectivelabs/sdk-ts 包导出的 Msgs 接口。
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
}
Last modified on April 24, 2026