メインコンテンツへスキップ

Documentation Index

Fetch the complete documentation index at: https://injectivelabs-mintlify-jp-native-developers-first-half.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 typed data(上記のメソッドにCosmos StdSignDocを渡すことでKeplr側で自動生成)に署名し、LedgerデバイスがKeplrへ接続されている場合でも、EVM互換チェーンとして適切な署名を取得できます。 関数のシグネチャは次のとおりです:
/**
 * Sign the sign doc with ethermint's EIP-712 format.
 * The difference from signEthereum(..., EthSignType.EIP712) is that this api returns a new sign doc changed by the user's fee setting and the signature for that sign doc.
 * Encoding tx to EIP-712 format should be done on the side using this api.
 * Not compatible with cosmjs.
 * The returned signature is (r | s | v) format which used in ethereum.
 * v should be 27 or 28 which is used in the ethereum mainnet regardless of chain.
 * @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 /* can be fetched from @injectivelabs/networks based on the Network */
}

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

  // In case we manually want to set gas options
  gas?: {
    gasPrice?: string
    gas?: number /** gas limit */
    feePayer?: string
    granter?: string
  }
}

/** Converting EIP712 tx details to 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 || '',
})

/**
 * We use this method only when we want to broadcast a transaction using Ledger on Keplr for Injective
 *
 * Note: Gas estimation not available
 * @param tx the transaction that needs to be broadcasted
 */
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

  /**
   * You choose to perform a check if
   * the user is indeed connected with Ledger + Keplr
   */
  if (/* your condition here */) {
    throw new GeneralException(
        new Error(
          'This method can only be used when Keplr is connected with Ledger',
        ),
      )
  }

  /** Account Details * */
  const chainRestAuthApi = new ChainRestAuthApi(endpoints.rest)
  const accountDetailsResponse = await chainRestAuthApi.fetchAccount(
    tx.injectiveAddress,
  )
  const baseAccount = BaseAccount.fromRestApi(accountDetailsResponse)
  const accountDetails = baseAccount.toAccountDetails()

  /** Block Details */
  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()

  /** EIP712 for signing on Ethereum wallets */
  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(),
    })
  )

  /**
   * Create TxRaw from the signed tx that we
   * get as a response in case the user changed the fee/memo
   * on the Keplr popup
   */
  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,
  })

  /** Preparing the transaction for client broadcasting */
  const web3Extension = createWeb3Extension({
    evmChainId,
  })
  const txRawEip712 = createTxRawEIP712(txRaw, web3Extension)

  /** Append Signatures */
  const signatureBuff = Buffer.from(
    aminoSignResponse.signature.signature,
    'base64',
  )
  txRawEip712.signatures = [signatureBuff]

  /** Broadcast the transaction */
  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 May 14, 2026