본문으로 건너뛰기

Cosmos

November 11, 2022
Nahee Park
Software Engineer, DSRV

Prerequisites

Cosmos 파트에서는 @cosmjs를 추가로 이용하여 실습을 진행합니다. 해당 패키지를 미리 설치해주세요.

Signed transaction 생성하기

signed transaction을 만들기 위해선 크게 세 가지 절차를 거쳐야 합니다.

  1. raw transaction 생성
  2. raw transaction에 대한 signature 생성
  3. raw transaction과 signature을 합쳐 signed transaction 생성

1. getCosmosTx

signed transaction을 생성하기 위해서는 raw transaction과 signature가 필요합니다. kms를 통해 signature를 얻기 위해선 raw transaction이 필요하기 때문에 먼저 raw transaction을 생성하기 위한 getCosmosTx 함수를 만듭니다.

getCosmosTx.ts
import { Account, CHAIN } from '@dsrv/kms/lib/types';
import { Cosmos } from '@dsrv/kms/lib/blockchains/cosmos';
import {
Registry,
makeAuthInfoBytes,
makeSignDoc,
encodePubkey,
makeSignBytes,
DirectSecp256k1HdWallet,
TxBodyEncodeObject,
} from '@cosmjs/proto-signing';
import { encodeSecp256k1Pubkey } from '@cosmjs/amino';
import { StargateClient, defaultRegistryTypes } from '@cosmjs/stargate';
import { Int53 } from '@cosmjs/math';
import { SignDoc } from 'cosmjs-types/cosmos/tx/v1beta1/tx';

interface RawTransaction {
unSignedTx: SignDoc;
serializedTx: string;
}

const getTxBodyBytes = (transaction) => {
const registry = new Registry(defaultRegistryTypes);

const txBodyEncodeObjectTxBodyEncodeObject = {
typeUrl: '/cosmos.tx.v1beta1.TxBody',
value: {
messages: transaction.msgs,
memo: transaction.memo,
},
};

const txBodyBytes = registry.encode(txBodyEncodeObject);
return txBodyBytes;
};

const getAuthInfoBytes = (transaction, pubkey) => {
const gasLimit = Int53.fromString(transaction.fee.gas).toNumber();
const authInfoBytes = makeAuthInfoBytes(
[
{
pubkey: encodePubkey(encodeSecp256k1Pubkey(pubkey)),
sequence: transaction.signerData.sequence,
},
],
transaction.fee.amount,
gasLimit,
undefined,
undefined,
// 1,
);

return authInfoBytes;
};

export const getCosmosTx = async (mnemonic: string): Promise<RawTransaction> => {
/* 1. get Account */
const wallet = await DirectSecp256k1HdWallet.fromMnemonic(mnemonic);
const [{ address, pubkey }] = await wallet.getAccounts();

/* 2. make raw transaction */
const rpcUrl = 'https://cosmos-testnet-rpc.allthatnode.com:26657';
const client = await StargateClient.connect(rpcUrl);
const sequence = await client.getSequence(address);
const chainId = await client.getChainId();

const transaction = {
signerData: {
accountNumber: sequence.accountNumber,
sequence: sequence.sequence,
chainId,
},
fee: {
amount: [
{
denom: 'uatom',
amount: '10000',
},
],
gas: '180000', // 180k
},
memo: 'dsrv/kms',
msgs: [
{
typeUrl: '/cosmos.bank.v1beta1.MsgSend',
value: {
fromAddress: address,
toAddress: address, // send to yourself
amount: [{ denom: 'uatom', amount: '10000' }],
},
},
],
sequence: sequence.sequence,
};

/* create signDoc */
const txBodyBytes = getTxBodyBytes(transaction);
const authInfoBytes = getAuthInfoBytes(transaction, pubkey);

const signDoc = makeSignDoc(
txBodyBytes,
authInfoBytes,
transaction.signerData.chainId,
Number(transaction.signerData.accountNumber),
);

/* serialized singDoc */

const uint8SignDoc = makeSignBytes(signDoc);
const serializedTx = `0x${Buffer.from(uint8SignDoc).toString('hex')}`;

return {
unSignedTx: signDoc,
serializedTx,
};
};

2. getCosmosSignature

다음으로 transaction에 대한 signature를 얻기 위해, serializedTx를 인자로 받아서 signature를 생성하는 getCosmosSignature 함수를 만듭니다.

getCosmosSignature.ts
import { CHAIN } from '@dsrv/kms/lib/types';
import { Cosmos } from '@dsrv/kms/lib/blockchains/cosmos';

export const getCosmosSignature = (serializedTx: string): string => {
const { signature } = Cosmos.signTx(
{
mnemonic,
path: { type: CHAIN.COSMOS, account: 0, index: 0 },
},
serializedTx,
);

return signature;
};

3. createCosmosSignedTx

마지막으로 위에서 생성한 unSignedTxsignature를 통해 signed transaction을 리턴해주는 createCosmosSignedTx 함수를 만듭니다.

createCosmosSignedTx.ts
import { TxRaw, SignDoc } from 'cosmjs-types/cosmos/tx/v1beta1/tx';

interface createCosmosSignedTxProps {
unSignedTx: SignDoc;
signature: string;
}

/* create singedTx by combining tx and signature */
export const createCosmosSignedTx = ({
unSignedTx,
signature,
}: createCosmosSignedTxProps): string => {
const txRaw = TxRaw.fromPartial({
bodyBytes: unSignedTx.bodyBytes,
authInfoBytes: unSignedTx.authInfoBytes,
signatures: [new Uint8Array(Buffer.from(signature.replace('0x', ''), 'hex'))],
});

const txByte = TxRaw.encode(txRaw).finish();
const signedTx = `0x${Buffer.from(txByte).toString('hex')}`;

return signedTx;
};

최종적으로 위에서 만든 함수, getCosmosTx, getCosmosSignature, createCosmosSignedTx 를 이용해 signed transaction을 리턴하는 getCosmosSignedTx를 만들 수 있습니다.

getCosmosSignedTx.ts
export const getCosmosSignedTx = async (mnemonic: string) => {
/* 1. get rawTransaction */
const { serializedTx, unSignedTx } = await getCosmosTx(mnemonic);
/* 2. get signature */
const cosmosSignature = getCosmosSignature(serializedTx);
/* 3. create singedTx by combining rawTransaction and signature */
const cosmosSignedTx = createCosmosSignedTx({
unSignedTx,
signature: cosmosSignature,
});

return cosmosSignedTx;
};

Signed transaction 전송하기

Signed transaction을 생성했다면, 그것을 이용하여 트랜잭션을 전송할 수 있습니다.

sendCosmosTransaction.ts
import { StargateClient, DeliverTxResponse } from '@cosmjs/stargate';

export const sendCosmosTransaction = async (serializedTx: string): Promise<DeliverTxResponse> => {
const rpcUrl = 'https://cosmos-testnet-rpc.allthatnode.com:26657';
const client = await StargateClient.connect(rpcUrl);
const txResult = await client.broadcastTx(
Uint8Array.from(Buffer.from(serializedTx.replace('0x', ''), 'hex')),
);
return txResult;
};
main.ts
const mnemonic = 'sample mnemonic';
const main = async () => {
const cosmosSignedTx = await getCosmosSignedTx(mnemonic);
const cosmosTxResult = await sendCosmosTransaction(cosmosSignedTx);
console.log('Cosmos Tx Result : ', cosmosTxResult);
};
main();

Examples

아래의 예제를 통해 실제로 트랜잭션을 전송해 볼 수 있습니다. 트랜잭션을 보내기 위해선 테스트넷 토큰이 필요합니다. 지갑 내의 FAUCET 탭에서 faucet을 요청할 수 있습니다.

위험

니모닉이 유출될 경우, 암호화폐 자산을 모두 잃을 수 있습니다. 아래의 예제를 실행시킬 때에는 테스트용 혹은 개발용 니모닉을 사용해주세요.

라이브 에디터
결과
Loading...