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