跳转到主要内容
Injective 上的每笔交易都遵循相同的流程。该流程包含三个步骤:准备、签名和广播交易。让我们分别深入每个步骤,详细解释整个过程(包括示例),以便理解完整的交易流程。

准备交易

首先,我们需要准备交易以进行签名。要使用 Ethereum 原生钱包,我们必须将交易转换为 EIP712 类型数据,并使用钱包签名此类型数据。 使用我们对消息的自定义抽象,允许开发者直接从特定消息的 proto 文件获取 EIP712 TypedData。
import {
  MsgSend,
} from "@injectivelabs/sdk-ts/core/modules";
import {
  BaseAccount,
} from "@injectivelabs/sdk-ts/core/accounts";
import {
  ChainRestAuthApi,
  ChainRestTendermintApi,
} from "@injectivelabs/sdk-ts/client/chain";
import {
  getEip712TypedDataV2,
} from "@injectivelabs/sdk-ts/core/tx";
import {
  toBigNumber,
  toChainFormat,
  DEFAULT_BLOCK_TIMEOUT_HEIGHT,
} from "@injectivelabs/utils";
import { ChainId, EvmChainId } from "@injectivelabs/ts-types";
import { Network, getNetworkEndpoints } from "@injectivelabs/networks";

const injectiveAddress = "inj1";
const chainId = ChainId.Mainnet;
const evmChainId = EvmChainId.Mainnet;
const restEndpoint =
  "https://lcd.injective.network"; /* getNetworkEndpoints(Network.Mainnet).rest */
const amount = {
  denom: "inj",
  amount: toChainFormat(0.01).toFixed(),
};

/** 账户详情 **/
const chainRestAuthApi = new ChainRestAuthApi(restEndpoint);
const accountDetailsResponse = await chainRestAuthApi.fetchAccount(
  injectiveAddress
);
const baseAccount = BaseAccount.fromRestApi(accountDetailsResponse);
const accountDetails = baseAccount.toAccountDetails();

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

/** 准备交易 */
const msg = MsgSend.fromJSON({
  amount,
  srcInjectiveAddress: injectiveAddress,
  dstInjectiveAddress: injectiveAddress,
});

/** 用于在 Ethereum 钱包上签名的 EIP712 */
const eip712TypedData = getEip712TypedDataV2({
  msgs: [msg],
  tx: {
    accountNumber: accountDetails.accountNumber.toString(),
    sequence: accountDetails.sequence.toString(),
    timeoutHeight: timeoutHeight.toFixed(),
    chainId: chainId,
  },
  evmChainId,
});

签名交易

准备好 EIP712 类型数据后,我们继续进行签名。
/** 使用你喜欢的方法签名 EIP712 TypedData,以 Metamask 为例 */
const signature = await window.ethereum.request({
  method: "eth_signTypedData_v4",
  params: [
    ethereumAddress,
    JSON.stringify(eip712TypedData /* 来自上一步 */),
  ],
});

/** 获取签名者的公钥 */
const publicKeyHex = recoverTypedSignaturePubKey(eip712TypedData, signature);
const publicKeyBase64 = hexToBase64(publicKeyHex);
你也可以使用我们的 @injectivelabs/wallet-strategy 包来获取开箱即用的钱包提供者,它们提供了可用于签名交易的抽象方法。请参阅该包的文档,设置和使用都很简单。这是推荐的方式,因为你可以在 dApp 中使用多个钱包。WalletStrategy 提供的不仅仅是签名交易的抽象。

广播交易

签名准备好后,我们需要将交易广播到 Injective 链本身。从第二步获取签名后,我们需要将该签名包含在已签名的交易中并广播到链上。
import {
  Network,
  SIGN_AMINO,
  getNetworkEndpoints,
} from "@injectivelabs/networks";
import { getDefaultStdFee } from "@injectivelabs/utils";
import { ChainId, EvmChainId } from "@injectivelabs/ts-types";
import { createTransaction, TxRestApi } from "@injectivelabs/sdk-ts/core/tx";

const evmChainId = EvmChainId.Mainnet;

const { txRaw } = createTransaction({
  message: msgs,
  memo: memo,
  signMode: SIGN_AMINO,
  fee: getDefaultStdFee(),
  pubKey: publicKeyBase64 /* 来自上一步 */,
  sequence: baseAccount.sequence,
  timeoutHeight: timeoutHeight.toNumber(),
  accountNumber: baseAccount.accountNumber,
  chainId: chainId,
});
const web3Extension = createWeb3Extension({
  evmChainId,
});
const txRawEip712 = createTxRawEIP712(txRaw, web3Extension);

/** 附加签名 */
txRawEip712.signatures = [signatureBuff /* 来自上一步 */];

/** 广播交易 */
const restEndpoint =
  "https://lcd.injective.network"; /* getNetworkEndpoints(Network.Mainnet).rest */
const txRestApi = new TxRestApi(restEndpoint);

const txHash = await txRestApi.broadcast(txRawEip712);

/**
 * 获取 txHash 后,因为我们使用 Sync 模式,
 * 我们不确定交易是否已包含在区块中,
 * 它可能仍在 mempool 中,所以我们需要查询
 * 链以查看交易何时被包含
 */

/** 这将轮询查询交易并等待其包含在区块中 */
const response = await txRestApi.fetchTxPoll(txHash);

不使用 WalletStrategy 的示例(准备 + 签名 + 广播)

让我们看看完整的流程(使用 Metamask 作为签名钱包)
import {
  MsgSend,
} from "@injectivelabs/sdk-ts/core/modules";
import {
  BaseAccount,
} from "@injectivelabs/sdk-ts/core/accounts";
import {
  TxRestApi,
  SIGN_AMINO,
  hexToBase64,
  createTransaction,
  createTxRawEIP712,
  getEip712TypedData,
  createWeb3Extension,
  recoverTypedSignaturePubKey,
} from "@injectivelabs/sdk-ts/core/tx";
import {
  ChainRestAuthApi,
  ChainRestTendermintApi,
} from "@injectivelabs/sdk-ts/client/chain";
import {
  getEthereumAddress,
} from "@injectivelabs/sdk-ts/utils";
import {
  toBigNumber,
  toChainFormat,
  getDefaultStdFee,
  DEFAULT_BLOCK_TIMEOUT_HEIGHT,
} from "@injectivelabs/utils";
import { ChainId, EvmChainId } from "@injectivelabs/ts-types";
import { Network, getNetworkEndpoints } from "@injectivelabs/networks";

const injectiveAddress = "inj1";
const chainId = ChainId.Mainnet;
const evmChainId = EvmChainId.Mainnet;
const ethereumAddress = getEthereumAddress(injectiveAddress);
const restEndpoint = getNetworkEndpoints(Network.MainnetSentry).rest;
const amount = {
  denom: "inj",
  amount: toChainFormat(0.01).toFixed(),
};

/** 账户详情 **/
const chainRestAuthApi = new ChainRestAuthApi(restEndpoint);
const accountDetailsResponse = await chainRestAuthApi.fetchAccount(
  injectiveAddress
);
const baseAccount = BaseAccount.fromRestApi(accountDetailsResponse);
const accountDetails = baseAccount.toAccountDetails();

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

/** 准备交易 */
const msg = MsgSend.fromJSON({
  amount,
  srcInjectiveAddress: injectiveAddress,
  dstInjectiveAddress: injectiveAddress,
});

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

/** 使用你喜欢的方法签名 EIP712 TypedData,以 Metamask 为例 */
const signature = await window.ethereum.request({
  method: "eth_signTypedData_v4",
  params: [ethereumAddress, JSON.stringify(eip712TypedData)],
});

/** 获取签名者的公钥 */
const publicKeyHex = recoverTypedSignaturePubKey(eip712TypedData, signature);
const publicKeyBase64 = hexToBase64(publicKeyHex);
const signatureBuff = Buffer.from(signature.replace("0x", ""), "hex");

const { txRaw } = createTransaction({
  message: [msg],
  memo: "",
  signMode: SIGN_AMINO,
  fee: getDefaultStdFee(),
  pubKey: publicKeyBase64,
  sequence: baseAccount.sequence,
  timeoutHeight: timeoutHeight.toNumber(),
  accountNumber: baseAccount.accountNumber,
  chainId: chainId,
});
const web3Extension = createWeb3Extension({
  evmChainId,
});
const txRawEip712 = createTxRawEIP712(txRaw, web3Extension);

/** 附加签名 */
txRawEip712.signatures = [signatureBuff];

/** 广播交易 */
const txRestApi = new TxRestApi(restEndpoint);

const txResponse = await txRestApi.broadcast(txRawEip712);
const response = await txRestApi.fetchTxPoll(txResponse.txHash);

使用 WalletStrategy 的示例(准备 + 签名 + 广播)

示例可以在 wallet-core 包中找到。