메인 콘텐츠로 건너뛰기
Injective의 모든 트랜잭션은 동일한 흐름을 따릅니다. 이 흐름은 트랜잭션 준비, 서명, 브로드캐스트의 세 단계로 구성됩니다. 각 단계를 개별적으로 살펴보고 전체 트랜잭션 흐름을 이해할 수 있도록 프로세스를 심층적으로 설명합니다(예제 포함).

트랜잭션 준비

먼저 서명을 위해 트랜잭션을 준비해야 합니다. 이 시점에서는 제공된 메시지와 서명자를 기반으로 트랜잭션을 빠르게 준비할 수 있는 일부 온라인 추상화(예: @cosmjs/stargate 패키지 사용)를 사용할 수 없습니다. 그 이유는 이러한 패키지가 Injective의 publicKey typeUrl을 지원하지 않으므로 클라이언트 측에서 주소 준비를 해야 하기 때문입니다. 이를 해결하기 위해 @injectivelabs/sdk-ts 패키지 내에서 txRaw 트랜잭션을 준비할 수 있는 함수를 제공했습니다. txRaw는 트랜잭션 및 서명자 자체에 대한 세부 정보를 포함하는 Cosmos에서 사용되는 트랜잭션 인터페이스입니다. cosmos 지갑에서 개인키를 가져오는 것은 일반적으로 chainId에 대한 현재 키를 가져와서 거기에서 pubKey에 접근하는 방식으로 수행됩니다(예: const key = await window.keplr.getKey(chainId) => const pubKey = key.publicKey).
import {
  MsgSend,
} from "@injectivelabs/sdk-ts/core/modules";
import {
  BaseAccount,
} from "@injectivelabs/sdk-ts/core/accounts";
import {
  createTransaction,
} from "@injectivelabs/sdk-ts/core/tx";
import { toBigNumber, toChainFormat } from "@injectivelabs/utils";
import {
  ChainRestAuthApi,
  ChainRestTendermintApi,
} from "@injectivelabs/sdk-ts/client/chain";
import { getStdFee, DEFAULT_BLOCK_TIMEOUT_HEIGHT } from "@injectivelabs/utils";

(async () => {
  const injectiveAddress = "inj1";
  const chainId = "injective-1"; /* ChainId.Mainnet */
  const restEndpoint =
    "https://sentry.lcd.injective.network"; /* 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 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,
  });

  /** 지갑/개인키에서 서명자의 PubKey 가져오기 */
  const pubKey = await getPubKey();

  /** 트랜잭션 준비 **/
  const { txRaw, signDoc } = createTransaction({
    pubKey,
    chainId,
    fee: getStdFee({}),
    message: msg,
    sequence: baseAccount.sequence,
    timeoutHeight: timeoutHeight.toNumber(),
    accountNumber: baseAccount.accountNumber,
  });
})();

트랜잭션 서명

트랜잭션을 준비한 후 서명을 진행합니다. 이전 단계에서 txRaw 트랜잭션을 얻은 후 Cosmos 네이티브 지갑(예: Keplr)을 사용하여 서명합니다.
import { ChainId } from '@injectivelabs/ts-types'
import { SignDoc } from '@keplr-wallet/types'

const getKeplr = async (chainId: string) => {
  await window.keplr.enable(chainId);

  const offlineSigner = window.keplr.getOfflineSigner(chainId);
  const accounts = await offlineSigner.getAccounts();
  const key = await window.keplr.getKey(chainId);

  return { offlineSigner, accounts, key }
}

const { offlineSigner } = await getKeplr(ChainId.Mainnet)

/* 트랜잭션 서명 */
const address = 'inj1...'
const signDoc = /* 이전 단계에서 */
const directSignResponse = await offlineSigner.signDirect(address, signDoc as SignDoc)
@injectivelabs/wallet-strategy 패키지를 사용하여 트랜잭션에 서명하는 데 사용할 수 있는 추상화된 메서드를 제공하는 바로 사용 가능한 지갑 프로바이더를 얻을 수도 있습니다. 패키지의 문서를 참조하세요. 설정 및 사용이 간단합니다. dApp에서 여러 지갑을 사용할 수 있으므로 이 방법을 권장합니다. WalletStrategy는 트랜잭션 서명 추상화 이상의 기능을 제공합니다.

트랜잭션 브로드캐스트

서명이 준비되면 트랜잭션을 Injective 체인 자체에 브로드캐스트해야 합니다. 두 번째 단계에서 서명을 받은 후 서명된 트랜잭션에 해당 서명을 포함하고 체인에 브로드캐스트해야 합니다.
import { ChainId } from '@injectivelabs/ts-types'
import {
  TxRestApi,
  CosmosTxV1Beta1Tx,
  BroadcastModeKeplr,
  getTxRawFromTxRawOrDirectSignResponse,
  TxRaw,
} from '@injectivelabs/sdk-ts/core/tx'
import { TransactionException } from '@injectivelabs/exceptions'

/**
 * 중요 참고:
 * Keplr/Leap 지갑을 사용하는 경우
 * 트랜잭션에 서명한 후 `directSignResponse`를 받게 되며,
 * `createTransaction` 함수를 사용하여 생성한 `txRaw`에 서명을 추가하는 대신
 * 실제로 서명된 트랜잭션(즉, `directSignResponse.signed`)에서
 * `directSignResponse`의 서명을 첨부해야 합니다.
 * 그 이유는 사용자가 원래 트랜잭션을 변경할 수 있기 때문입니다
 * (예: 가스 한도 또는 가스 가격 변경) 그래서 서명되는 트랜잭션과
 * 브로드캐스트되는 트랜잭션이 동일하지 않습니다.
 */
const directSignResponse = /* 위의 두 번째 단계에서 */;
const txRaw = getTxRawFromTxRawOrDirectSignResponse(directSignResponse)

const broadcastTx = async (chainId: String, txRaw: TxRaw) => {
  const getKeplr = async (chainId: string) => {
    await window.keplr.enable(chainId);

    return window.keplr
  }

  const keplr = await getKeplr(ChainId.Mainnet)
  const result = await keplr.sendTx(
    chainId,
    CosmosTxV1Beta1Tx.TxRaw.encode(txRaw).finish(),
    BroadcastModeKeplr.Sync,
  )

  if (!result || result.length === 0) {
    throw new TransactionException(
      new Error('트랜잭션 브로드캐스트 실패'),
      { contextModule: 'Keplr' },
    )
  }

  return Buffer.from(result).toString('hex')
}

const txHash = await broadcastTx(ChainId.Mainnet, txRaw)

/**
 * txHash를 받은 후 Sync 모드를 사용하기 때문에
 * 트랜잭션이 블록에 포함되었는지 확실하지 않습니다.
 * 아직 mempool에 있을 수 있으므로 체인을 쿼리하여
 * 트랜잭션이 언제 블록에 포함되는지 확인해야 합니다.
 */
const restEndpoint = 'https://sentry.lcd.injective.network' /* getNetworkEndpoints(Network.MainnetSentry).rest */
const txRestApi = new TxRestApi(restEndpoint)

 /** 트랜잭션을 폴링하고 블록에 포함될 때까지 대기합니다 */
const response = await txRestApi.fetchTxPoll(txHash)

예제 (준비 + 서명 + 브로드캐스트)

전체 흐름을 살펴보겠습니다 (Keplr을 서명 지갑으로 사용)
import {
  MsgSend,
} from "@injectivelabs/sdk-ts/core/modules";
import {
  BaseAccount,
} from "@injectivelabs/sdk-ts/core/accounts";
import { ChainId } from "@injectivelabs/ts-types";
import { SignDoc } from "@keplr-wallet/types";
import { toBigNumber, toChainFormat } from "@injectivelabs/utils";
import { TransactionException } from "@injectivelabs/exceptions";
import {
  ChainRestAuthApi,
  ChainRestTendermintApi,
} from "@injectivelabs/sdk-ts/client/chain";
import { getStdFee, DEFAULT_BLOCK_TIMEOUT_HEIGHT } from "@injectivelabs/utils";
import {
  TxRaw,
  TxRestApi,
  createTransaction,
  CosmosTxV1Beta1Tx,
  BroadcastModeKeplr,
  getTxRawFromTxRawOrDirectSignResponse,
} from "@injectivelabs/sdk-ts/core/tx";

const getKeplr = async (chainId: string) => {
  await window.keplr.enable(chainId);

  const offlineSigner = window.keplr.getOfflineSigner(chainId);
  const accounts = await offlineSigner.getAccounts();
  const key = await window.keplr.getKey(chainId);

  return { offlineSigner, accounts, key };
};

const broadcastTx = async (chainId: string, txRaw: TxRaw) => {
  const keplr = await getKeplr(ChainId.Mainnet);
  const result = await keplr.sendTx(
    chainId,
    CosmosTxV1Beta1Tx.TxRaw.encode(txRaw).finish(),
    BroadcastModeKeplr.Sync
  );

  if (!result || result.length === 0) {
    throw new TransactionException(
      new Error("트랜잭션 브로드캐스트 실패"),
      { contextModule: "Keplr" }
    );
  }

  return Buffer.from(result).toString("hex");
};

(async () => {
  const chainId = "injective-1"; /* ChainId.Mainnet */
  const { key, offlineSigner } = await getKeplr(chainId);
  const pubKey = Buffer.from(key.pubKey).toString("base64");
  const injectiveAddress = key.bech32Address;
  const restEndpoint =
    "https://sentry.lcd.injective.network"; /* 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 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,
  });

  /** 트랜잭션 준비 **/
  const { signDoc } = createTransaction({
    pubKey,
    chainId,
    fee: getStdFee({}),
    message: msg,
    sequence: baseAccount.sequence,
    timeoutHeight: timeoutHeight.toNumber(),
    accountNumber: baseAccount.accountNumber,
  });

  const directSignResponse = await offlineSigner.signDirect(
    injectiveAddress,
    signDoc as SignDoc
  );
  const txRaw = getTxRawFromTxRawOrDirectSignResponse(directSignResponse);
  const txHash = await broadcastTx(ChainId.Mainnet, txRaw);
  const response = await new TxRestApi(restEndpoint).fetchTxPoll(txHash);

  console.log(response);
})();

WalletStrategy를 사용한 예제 (준비 + 서명 + 브로드캐스트)

예제는 wallet-core 패키지에서 찾을 수 있습니다.