From 0334ccc30befcfc9b5f52e85788780284b60b9f0 Mon Sep 17 00:00:00 2001 From: jinoosss <112360739+jinoosss@users.noreply.github.com> Date: Wed, 22 Jan 2025 17:12:15 +0900 Subject: [PATCH] feat: implements transaction builder (#22) --- packages/sdk/package.json | 5 +- .../sdk/src/core/types/transaction.types.ts | 26 ++++- packages/sdk/src/core/utils/index.ts | 1 + .../core/utils/transaction-message.utils.ts | 100 ++++++++++++++++++ .../sdk/src/core/utils/transaction.utils.ts | 71 ++++++------- .../providers/adena-wallet/adena-wallet.ts | 10 +- .../providers/adena-wallet/mapper.utils.ts | 34 +++++- .../adena-wallet/types/transactions.ts | 14 +-- .../src/providers/gno-wallet/gno-wallet.ts | 6 +- yarn.lock | 70 ++++++------ 10 files changed, 238 insertions(+), 99 deletions(-) create mode 100644 packages/sdk/src/core/utils/transaction-message.utils.ts diff --git a/packages/sdk/package.json b/packages/sdk/package.json index 9dd3132..c57d891 100644 --- a/packages/sdk/package.json +++ b/packages/sdk/package.json @@ -35,14 +35,15 @@ "test:ci": "jest --coverage --passWithNoTests " }, "dependencies": { - "@gnolang/gno-js-client": "^1.3.0", - "@gnolang/tm2-js-client": "^1.2.1", + "@gnolang/gno-js-client": "1.3.2", + "@gnolang/tm2-js-client": "1.2.4", "@web3auth/base": "^8.12.4", "@web3auth/base-provider": "^8.12.4", "@web3auth/no-modal": "^8.12.4", "@web3auth/openlogin-adapter": "^8.12.4" }, "devDependencies": { + "@bufbuild/protobuf": "^2.2.3", "@types/eslint": "^9", "@types/jest": "^29.5.12", "@types/node": "^22.0.0", diff --git a/packages/sdk/src/core/types/transaction.types.ts b/packages/sdk/src/core/types/transaction.types.ts index 156998d..8f9e886 100644 --- a/packages/sdk/src/core/types/transaction.types.ts +++ b/packages/sdk/src/core/types/transaction.types.ts @@ -1,4 +1,4 @@ -import { MsgAddPackage, MsgCall, MsgSend } from '@gnolang/gno-js-client'; +import { MsgAddPackage, MsgCall, MsgEndpoint, MsgSend } from '@gnolang/gno-js-client'; import { MsgRun } from '@gnolang/gno-js-client/bin/proto/gno/vm'; export type TransactionMessageType = '/bank.MsgSend' | '/vm.m_call' | '/vm.m_addpkg' | '/vm.m_run'; @@ -9,11 +9,27 @@ export enum BroadcastType { SYNC = 'SYNC', COMMIT = 'COMMIT', } +export interface AddPackageMessage { + type: MsgEndpoint.MSG_ADD_PKG; + value: MsgAddPackage; +} + +export interface MsgCallMessage { + type: MsgEndpoint.MSG_CALL; + value: MsgCall; +} + +export interface MsgSendMessage { + type: MsgEndpoint.MSG_SEND; + value: MsgSend; +} + +export interface MsgRunMessage { + type: MsgEndpoint.MSG_RUN; + value: MsgRun; +} -export type TransactionMessage = { - type: TransactionMessageType; - value: TransactionMessageValue; -}; +export type TransactionMessage = AddPackageMessage | MsgCallMessage | MsgSendMessage | MsgRunMessage; export interface TransactionData { messages: TransactionMessage[]; diff --git a/packages/sdk/src/core/utils/index.ts b/packages/sdk/src/core/utils/index.ts index e51ddd2..2ea8cbd 100644 --- a/packages/sdk/src/core/utils/index.ts +++ b/packages/sdk/src/core/utils/index.ts @@ -1,4 +1,5 @@ export * from './encode.utils'; export * from './message.utils'; export * from './storage.utils'; +export * from './transaction-message.utils'; export * from './transaction.utils'; diff --git a/packages/sdk/src/core/utils/transaction-message.utils.ts b/packages/sdk/src/core/utils/transaction-message.utils.ts new file mode 100644 index 0000000..4440c49 --- /dev/null +++ b/packages/sdk/src/core/utils/transaction-message.utils.ts @@ -0,0 +1,100 @@ +import { Any, MsgAddPackage, MsgCall, MsgEndpoint, MsgSend } from '@gnolang/gno-js-client'; +import { MsgRun } from '@gnolang/gno-js-client/bin/proto/gno/vm'; + +import { AddPackageMessage, MsgCallMessage, MsgRunMessage, MsgSendMessage, TransactionMessage } from '../types'; + +export function makeAddPackageMessage(value: MsgAddPackage): AddPackageMessage { + return { + type: MsgEndpoint.MSG_ADD_PKG, + value, + }; +} + +export function makeMsgCallMessage(value: MsgCall): MsgCallMessage { + return { + type: MsgEndpoint.MSG_CALL, + value, + }; +} + +export function makeMsgSendMessage(value: MsgSend): MsgSendMessage { + return { + type: MsgEndpoint.MSG_SEND, + value, + }; +} + +export function makeMsgRunMessage(value: MsgRun): MsgRunMessage { + return { + type: MsgEndpoint.MSG_RUN, + value, + }; +} + +export function encodeTransactionMessage(message: TransactionMessage): Uint8Array { + if (isAddPackageMessage(message)) { + return MsgAddPackage.encode(message.value).finish(); + } + + if (isMsgCallMessage(message)) { + return MsgCall.encode(message.value).finish(); + } + + if (isMsgSendMessage(message)) { + return MsgSend.encode(message.value).finish(); + } + + if (isMsgRunMessage(message)) { + return MsgRun.encode(message.value).finish(); + } + + throw new Error('Unknown message type'); +} + +export function decodeTransactionMessage(message: Any): TransactionMessage { + if (message.typeUrl === MsgEndpoint.MSG_ADD_PKG) { + return { + type: MsgEndpoint.MSG_ADD_PKG, + value: MsgAddPackage.decode(message.value), + }; + } + + if (message.typeUrl === MsgEndpoint.MSG_CALL) { + return { + type: MsgEndpoint.MSG_CALL, + value: MsgCall.decode(message.value), + }; + } + + if (message.typeUrl === MsgEndpoint.MSG_SEND) { + return { + type: MsgEndpoint.MSG_SEND, + value: MsgSend.decode(message.value), + }; + } + + if (message.typeUrl === MsgEndpoint.MSG_RUN) { + return { + type: MsgEndpoint.MSG_RUN, + value: MsgRun.decode(message.value), + }; + } + + throw new Error('Unknown message type'); +} + +function isAddPackageMessage(message: TransactionMessage): message is AddPackageMessage { + return message.type === MsgEndpoint.MSG_ADD_PKG; +} + +function isMsgCallMessage(message: TransactionMessage): message is MsgCallMessage { + return message.type === MsgEndpoint.MSG_CALL; +} + +function isMsgSendMessage(message: TransactionMessage): message is MsgSendMessage { + return message.type === MsgEndpoint.MSG_SEND; +} + +function isMsgRunMessage(message: TransactionMessage): message is MsgRunMessage { + return message.type === MsgEndpoint.MSG_RUN; +} diff --git a/packages/sdk/src/core/utils/transaction.utils.ts b/packages/sdk/src/core/utils/transaction.utils.ts index 5fd3d6e..11b67c1 100644 --- a/packages/sdk/src/core/utils/transaction.utils.ts +++ b/packages/sdk/src/core/utils/transaction.utils.ts @@ -1,74 +1,65 @@ -import { Tx } from '@gnolang/tm2-js-client'; -import { TransactionMessage } from '../types'; +import { Any, defaultTxFee } from '@gnolang/gno-js-client'; +import { Tx, TxFee } from '@gnolang/tm2-js-client'; -export const defaultGasFee = { - amount: 1000000, - denom: 'ugnot', -}; +import { TransactionMessage } from '../types'; +import { encodeTransactionMessage } from './transaction-message.utils'; -export const defaultGasWanted = 1_000_000; +export const defaultGasWanted = 10_000_000; export class TransactionBuilder { private _messages: TransactionMessage[] = []; - private _fees: { amount: string; denom: string }[] = []; - private _chainId: string = ''; private _memo: string = ''; - private _accountNumber: string = ''; - private _sequence: string = ''; - private _gasWanted: string = ''; + private _gasWanted: number = defaultGasWanted; + private _gasFee: string = defaultTxFee; messages(...messages: TransactionMessage[]): TransactionBuilder { this._messages = messages; return this; } - fees(...fees: { amount: number; denom: string }[]): TransactionBuilder { - this._fees = fees.map((fee) => ({ amount: fee.amount.toString(), denom: fee.denom })); + fee(amount: number, denom: string): TransactionBuilder { + this._gasFee = `${amount}${denom}`; return this; } gasWanted(amount: number): TransactionBuilder { - this._gasWanted = amount.toString(); + this._gasWanted = amount; return this; } - chainId(chainId: string): TransactionBuilder { - this._chainId = chainId; + memo(memo: string): TransactionBuilder { + this._memo = memo; return this; } - accountNumber(accountNumber: number): TransactionBuilder { - this._accountNumber = accountNumber.toString(); - return this; + private get txMessages(): Any[] { + return this._messages.map((message) => { + return Any.create({ + typeUrl: message.type, + value: encodeTransactionMessage(message), + }); + }); } - sequence(sequence: number): TransactionBuilder { - this._sequence = sequence.toString(); - return this; - } - - memo(memo: string): TransactionBuilder { - this._memo = memo; - return this; + private get txFee(): TxFee { + return TxFee.create({ + gasFee: this._gasFee, + gasWanted: this._gasWanted, + }); } build(): Tx { - const txDocument = { - msgs: this._messages, - fee: { - amount: this._fees, - gas: this._gasWanted, - }, - chain_id: this._chainId, - account_number: this._accountNumber, - sequence: this._sequence, + return { + messages: this.txMessages, + fee: this.txFee, memo: this._memo, + signatures: [], }; - - return Tx.fromJSON(txDocument); } public static create(): TransactionBuilder { - return new TransactionBuilder().gasWanted(defaultGasWanted).fees(defaultGasFee); + const builder = new TransactionBuilder(); + + return builder.gasWanted(defaultGasWanted); } } diff --git a/packages/sdk/src/providers/adena-wallet/adena-wallet.ts b/packages/sdk/src/providers/adena-wallet/adena-wallet.ts index 05c9acf..6c4dde0 100644 --- a/packages/sdk/src/providers/adena-wallet/adena-wallet.ts +++ b/packages/sdk/src/providers/adena-wallet/adena-wallet.ts @@ -1,5 +1,3 @@ -import { Tx } from '@gnolang/tm2-js-client'; - import { makeResponseMessage } from '../../core'; import { WalletProvider } from '../../core/providers'; import { AccountInfo, NetworkInfo, WalletResponseFailureType, WalletResponseSuccessType } from '../../core/types'; @@ -22,8 +20,8 @@ import { SwitchNetworkOptions, SwitchNetworkResponse, } from '../../core/types/methods'; -import { isSuccessType, mapResponseByAdenaResponse } from './mapper.utils'; -import { AdenaWallet, TransactionParams } from './types'; +import { isSuccessType, mapResponseByAdenaResponse, mapTxToTransactionParams } from './mapper.utils'; +import { AdenaWallet } from './types'; export class AdenaWalletProvider implements WalletProvider { private getAdena(): AdenaWallet { @@ -91,14 +89,14 @@ export class AdenaWalletProvider implements WalletProvider { async signTransaction(options: SignTransactionOptions): Promise { const adena = this.getAdena(); - const response = await adena.SignTx(Tx.toJSON(options.tx) as TransactionParams); + const response = await adena.SignTx(mapTxToTransactionParams(options.tx)); return mapResponseByAdenaResponse(response, response.data); } async broadcastTransaction(options: BroadcastTransactionOptions): Promise { const adena = this.getAdena(); - const response = await adena.DoContract(Tx.toJSON(options.tx) as TransactionParams); + const response = await adena.DoContract(mapTxToTransactionParams(options.tx)); const transactionResult = response.data; return mapResponseByAdenaResponse(response, transactionResult); diff --git a/packages/sdk/src/providers/adena-wallet/mapper.utils.ts b/packages/sdk/src/providers/adena-wallet/mapper.utils.ts index ed88491..c728b64 100644 --- a/packages/sdk/src/providers/adena-wallet/mapper.utils.ts +++ b/packages/sdk/src/providers/adena-wallet/mapper.utils.ts @@ -1,3 +1,6 @@ +import { defaultTxFee } from '@gnolang/gno-js-client'; +import { Tx } from '@gnolang/tm2-js-client'; +import { decodeTransactionMessage, defaultGasWanted } from '../../core'; import { WalletResponse, WalletResponseFailureType, @@ -6,7 +9,7 @@ import { WalletResponseSuccessType, WalletResponseType, } from '../../core/types'; -import { AdenaResponse, AdenaResponseStatus } from './types'; +import { AdenaResponse, AdenaResponseStatus, TransactionParams } from './types'; export function isSuccessType(type: WalletResponseType | string): type is WalletResponseSuccessType { const typeValue = type.toString(); @@ -50,3 +53,32 @@ export function mapResponseByAdenaResponse( data: data as ProviderResponseData, }; } + +export function mapTxToTransactionParams(tx: Tx): TransactionParams { + const gasWanted = tx.fee?.gasWanted.toNumber() || defaultGasWanted; + const gasFee = tx.fee?.gasFee || defaultTxFee; + const gasFeeAmount = parseTokenAmount(gasFee); + const messages = tx.messages.map(decodeTransactionMessage); + + return { + messages, + gasFee: gasFeeAmount, + gasWanted, + memo: tx.memo, + }; +} + +function parseTokenAmount(value: string): number { + const match = value.match(/^(\d+)/); + if (!match || match.length < 2) { + return 0; + } + + try { + return parseInt(match[1]); + } catch (error) { + console.error('Error parsing token amount', error); + } + + return 0; +} diff --git a/packages/sdk/src/providers/adena-wallet/types/transactions.ts b/packages/sdk/src/providers/adena-wallet/types/transactions.ts index e0f014d..3606271 100644 --- a/packages/sdk/src/providers/adena-wallet/types/transactions.ts +++ b/packages/sdk/src/providers/adena-wallet/types/transactions.ts @@ -1,20 +1,10 @@ -import { MsgAddPackage, MsgCall, MsgSend } from '@gnolang/gno-js-client'; -import { MsgRun } from '@gnolang/gno-js-client/bin/proto/gno/vm'; import { BroadcastTxCommitResult } from '@gnolang/tm2-js-client'; import { AdenaResponse } from '.'; - -type EMessageType = '/bank.MsgSend' | '/vm.m_call' | '/vm.m_addpkg' | '/vm.m_run'; - -type TMessage = MsgAddPackage | MsgCall | MsgSend | MsgRun; - -export type ContractMessage = { - type: EMessageType; - value: TMessage; -}; +import { TransactionMessage } from '../../../core'; export type TransactionParams = { - messages: ContractMessage[]; + messages: TransactionMessage[]; gasFee: number; gasWanted: number; memo?: string; diff --git a/packages/sdk/src/providers/gno-wallet/gno-wallet.ts b/packages/sdk/src/providers/gno-wallet/gno-wallet.ts index 83df825..9b1f413 100644 --- a/packages/sdk/src/providers/gno-wallet/gno-wallet.ts +++ b/packages/sdk/src/providers/gno-wallet/gno-wallet.ts @@ -8,6 +8,7 @@ import { } from '@gnolang/tm2-js-client'; import { BroadcastType, NetworkInfo, WalletResponseFailureType, WalletResponseSuccessType } from '../../core'; +import { DEFAULT_RPC_URL, GNO_ADDRESS_PREFIX } from '../../core/constants/chains.constant'; import { TM2WalletProvider } from '../../core/providers/tm2-wallet'; import { AddEstablishResponse, @@ -28,7 +29,6 @@ import { } from '../../core/types/methods'; import { encodeTransaction } from '../../core/utils/encode.utils'; import { makeResponseMessage } from '../../core/utils/message.utils'; -import { DEFAULT_RPC_URL, GNO_ADDRESS_PREFIX } from '../../core/constants/chains.constant'; import { normalizeRpcUrl, validateNetworkInput } from '../../core/utils/network.utils'; import { GetSocialUserProfileResponse } from '../../core/types/methods/get-social-user-profile.types'; @@ -188,12 +188,14 @@ export class GnoWalletProvider implements TM2WalletProvider { return makeResponseMessage(WalletResponseFailureType.NOT_CONNECTED); } + const signedTransaction = await this.wallet!.signTransaction(options.tx, decodeTxMessages); + const transactionEndpoint = options.broadcastType === BroadcastType.COMMIT ? TransactionEndpoint.BROADCAST_TX_COMMIT : TransactionEndpoint.BROADCAST_TX_SYNC; - const transactionResult = await this.wallet!.sendTransaction(options.tx, transactionEndpoint); + const transactionResult = await this.wallet!.sendTransaction(signedTransaction, transactionEndpoint); return makeResponseMessage(WalletResponseSuccessType.TRANSACTION_SUCCESS, transactionResult); } diff --git a/yarn.lock b/yarn.lock index a076871..e8baa04 100644 --- a/yarn.lock +++ b/yarn.lock @@ -9,8 +9,9 @@ __metadata: version: 0.0.0-use.local resolution: "@adena-wallet/sdk@workspace:packages/sdk" dependencies: - "@gnolang/gno-js-client": "npm:^1.3.0" - "@gnolang/tm2-js-client": "npm:^1.2.1" + "@bufbuild/protobuf": "npm:^2.2.3" + "@gnolang/gno-js-client": "npm:1.3.2" + "@gnolang/tm2-js-client": "npm:1.2.4" "@types/eslint": "npm:^9" "@types/jest": "npm:^29.5.12" "@types/node": "npm:^22.0.0" @@ -441,6 +442,13 @@ __metadata: languageName: node linkType: hard +"@bufbuild/protobuf@npm:^2.2.3": + version: 2.2.3 + resolution: "@bufbuild/protobuf@npm:2.2.3" + checksum: 10c0/546c38b924c4a8dd79ec457928cfb99a5aab2945d11f15f4f06894bdc148ea37a1ae8b78cf17de9bb5354cbb896f6af4099073690b5b2089e1b196cec963d6ec + languageName: node + linkType: hard + "@cosmjs/amino@npm:^0.32.4": version: 0.32.4 resolution: "@cosmjs/amino@npm:0.32.4" @@ -766,32 +774,32 @@ __metadata: languageName: node linkType: hard -"@gnolang/gno-js-client@npm:^1.3.0": - version: 1.3.0 - resolution: "@gnolang/gno-js-client@npm:1.3.0" +"@gnolang/gno-js-client@npm:1.3.2": + version: 1.3.2 + resolution: "@gnolang/gno-js-client@npm:1.3.2" dependencies: "@cosmjs/ledger-amino": "npm:^0.32.4" - "@gnolang/tm2-js-client": "npm:^1.2.1" + "@gnolang/tm2-js-client": "npm:^1.2.4" long: "npm:^5.2.3" - protobufjs: "npm:^7.2.3" - checksum: 10c0/71a539c6c666636cca4db869853b501328f2b95598232d73f87b0f8080525c3a418c36f310a62387c4cba61e75cc8092ddc7dacf51758c9d24ea1629ce6daf4c + protobufjs: "npm:^7.4.0" + checksum: 10c0/172bcf77d6ceac98dffdacba8315755b0f879e95c3eaa663f9943dbf9be4ead6d7d8a86bf319d18f7d8a03003092922828fb5c7cc08c1c9a4896d189609d7243 languageName: node linkType: hard -"@gnolang/tm2-js-client@npm:^1.2.1": - version: 1.2.1 - resolution: "@gnolang/tm2-js-client@npm:1.2.1" +"@gnolang/tm2-js-client@npm:1.2.4, @gnolang/tm2-js-client@npm:^1.2.4": + version: 1.2.4 + resolution: "@gnolang/tm2-js-client@npm:1.2.4" dependencies: "@cosmjs/amino": "npm:^0.32.4" "@cosmjs/crypto": "npm:^0.32.4" "@cosmjs/ledger-amino": "npm:^0.32.4" - "@types/uuid": "npm:^9.0.4" - axios: "npm:^1.4.0" + "@types/uuid": "npm:^10.0.0" + axios: "npm:^1.7.2" long: "npm:^5.2.3" - protobufjs: "npm:^7.2.3" - uuid: "npm:^9.0.1" - ws: "npm:^8.16.0" - checksum: 10c0/b327f6f65c3b961f4f364bed77b82670fd1a81493cf3523b0b149a0636879608371bec0885ce3de4079ded0b600541e2470d1e47db358756a40adad52a56162d + protobufjs: "npm:^7.4.0" + uuid: "npm:^10.0.0" + ws: "npm:^8.18.0" + checksum: 10c0/72148e952c17e977246db816c7778ba80379f6d36ef2bbc1184c62a11ccb0428814a9c0357593cfeb84976696b8bde09ae3b5c9a6ea858e5c930f4332ae331a3 languageName: node linkType: hard @@ -1811,10 +1819,10 @@ __metadata: languageName: node linkType: hard -"@types/uuid@npm:^9.0.4": - version: 9.0.8 - resolution: "@types/uuid@npm:9.0.8" - checksum: 10c0/b411b93054cb1d4361919579ef3508a1f12bf15b5fdd97337d3d351bece6c921b52b6daeef89b62340fd73fd60da407878432a1af777f40648cbe53a01723489 +"@types/uuid@npm:^10.0.0": + version: 10.0.0 + resolution: "@types/uuid@npm:10.0.0" + checksum: 10c0/9a1404bf287164481cb9b97f6bb638f78f955be57c40c6513b7655160beb29df6f84c915aaf4089a1559c216557dc4d2f79b48d978742d3ae10b937420ddac60 languageName: node linkType: hard @@ -2226,14 +2234,14 @@ __metadata: languageName: node linkType: hard -"axios@npm:^1.4.0": - version: 1.7.7 - resolution: "axios@npm:1.7.7" +"axios@npm:^1.7.2": + version: 1.7.9 + resolution: "axios@npm:1.7.9" dependencies: follow-redirects: "npm:^1.15.6" form-data: "npm:^4.0.0" proxy-from-env: "npm:^1.1.0" - checksum: 10c0/4499efc89e86b0b49ffddc018798de05fab26e3bf57913818266be73279a6418c3ce8f9e934c7d2d707ab8c095e837fc6c90608fb7715b94d357720b5f568af7 + checksum: 10c0/b7a41e24b59fee5f0f26c1fc844b45b17442832eb3a0fb42dd4f1430eb4abc571fe168e67913e8a1d91c993232bd1d1ab03e20e4d1fee8c6147649b576fc1b0b languageName: node linkType: hard @@ -5392,7 +5400,7 @@ __metadata: languageName: node linkType: hard -"protobufjs@npm:^7.2.3": +"protobufjs@npm:^7.4.0": version: 7.4.0 resolution: "protobufjs@npm:7.4.0" dependencies: @@ -6360,12 +6368,12 @@ __metadata: languageName: node linkType: hard -"uuid@npm:^9.0.1": - version: 9.0.1 - resolution: "uuid@npm:9.0.1" +"uuid@npm:^10.0.0": + version: 10.0.0 + resolution: "uuid@npm:10.0.0" bin: uuid: dist/bin/uuid - checksum: 10c0/1607dd32ac7fc22f2d8f77051e6a64845c9bce5cd3dd8aa0070c074ec73e666a1f63c7b4e0f4bf2bc8b9d59dc85a15e17807446d9d2b17c8485fbc2147b27f9b + checksum: 10c0/eab18c27fe4ab9fb9709a5d5f40119b45f2ec8314f8d4cf12ce27e4c6f4ffa4a6321dc7db6c515068fa373c075b49691ba969f0010bf37f44c37ca40cd6bf7fe languageName: node linkType: hard @@ -6482,7 +6490,7 @@ __metadata: languageName: node linkType: hard -"ws@npm:^8.16.0": +"ws@npm:^8.18.0": version: 8.18.0 resolution: "ws@npm:8.18.0" peerDependencies: