このページでは、Keplrウォレット経由でLedgerデバイスを使用する場合のInjective実装について説明します。 前述したように、InjectiveはCosmosチェーンの他とは異なる導出曲線を使用するため、ユーザーは(現時点では)Ethereumアプリを使用してInjectiveとやり取りする必要があります。 すべてのエッジケースをカバーし、Injectiveでサポートされるウォレットに対して完全なアウトオブボックスソリューションを求める場合は、MsgBroadcaster + WalletStrategy抽象化を参照することを推奨します。独自の実装を行いたい場合は、コード例を一緒に見ていきましょう。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は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>;
eip712とsignDocを生成し、この関数に渡すことです。これにより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
}
