트랜잭션 준비
먼저 서명을 위해 트랜잭션을 준비해야 합니다. 이 시점에서는 제공된 메시지와 서명자를 기반으로 트랜잭션을 빠르게 준비할 수 있는 일부 온라인 추상화(예:@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).
복사
AI에게 묻기
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)을 사용하여 서명합니다.
복사
AI에게 묻기
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 체인 자체에 브로드캐스트해야 합니다. 두 번째 단계에서 서명을 받은 후 서명된 트랜잭션에 해당 서명을 포함하고 체인에 브로드캐스트해야 합니다.복사
AI에게 묻기
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을 서명 지갑으로 사용)복사
AI에게 묻기
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);
})();
