diff --git a/packages/sdk/src/cosmwasmclient.spec.ts b/packages/sdk/src/cosmwasmclient.spec.ts index 319610a8..0c8292b3 100644 --- a/packages/sdk/src/cosmwasmclient.spec.ts +++ b/packages/sdk/src/cosmwasmclient.spec.ts @@ -222,50 +222,22 @@ describe("CosmWasmClient", () => { beforeAll(async () => { if (cosmosEnabled()) { const pen = await Secp256k1Pen.fromMnemonic(faucet.mnemonic); - const client = CosmWasmClient.makeReadOnly(httpUrl); + const client = CosmWasmClient.makeWritable(httpUrl, faucet.address, signBytes => pen.sign(signBytes)); - const memo = "My first contract on chain"; - const sendMsg: MsgSend = { - type: "cosmos-sdk/MsgSend", - value: { - from_address: faucet.address, - to_address: makeRandomAddress(), - amount: [ - { - denom: "ucosm", - amount: "1234567", - }, - ], + const recipient = makeRandomAddress(); + const transferAmount = [ + { + denom: "ucosm", + amount: "1234567", }, - }; + ]; + const result = await client.sendTokens(recipient, transferAmount); - const fee: StdFee = { - amount: [ - { - amount: "5000", - denom: "ucosm", - }, - ], - gas: "890000", - }; - - const chainId = await client.chainId(); - const { accountNumber, sequence } = await client.getNonce(faucet.address); - const signBytes = makeSignBytes([sendMsg], fee, chainId, memo, accountNumber, sequence); - const signature = await pen.sign(signBytes); - const signedTx = { - msg: [sendMsg], - fee: fee, - memo: memo, - signatures: [signature], - }; - - const result = await client.postTx(marshalTx(signedTx)); await sleep(50); // wait until tx is indexed const txDetails = await new RestClient(httpUrl).txsById(result.transactionHash); posted = { - sender: sendMsg.value.from_address, - recipient: sendMsg.value.to_address, + sender: faucet.address, + recipient: recipient, hash: result.transactionHash, height: Number.parseInt(txDetails.height, 10), tx: txDetails.tx, @@ -446,6 +418,37 @@ describe("CosmWasmClient", () => { }); }); + describe("sendTokens", () => { + it("works", async () => { + pendingWithoutCosmos(); + const pen = await Secp256k1Pen.fromMnemonic(faucet.mnemonic); + const client = CosmWasmClient.makeWritable(httpUrl, faucet.address, signBytes => pen.sign(signBytes)); + + // instantiate + const transferAmount: readonly Coin[] = [ + { + amount: "7890", + denom: "ucosm", + }, + ]; + const beneficiaryAddress = makeRandomAddress(); + + // no tokens here + const before = await client.getAccount(beneficiaryAddress); + expect(before).toBeUndefined(); + + // send + const result = await client.sendTokens(beneficiaryAddress, transferAmount, "for dinner"); + const [firstLog] = result.logs; + expect(firstLog).toBeTruthy(); + + // got tokens + const after = await client.getAccount(beneficiaryAddress); + assert(after); + expect(after.coins).toEqual(transferAmount); + }); + }); + describe("queryContractRaw", () => { const configKey = toAscii("config"); const otherKey = toAscii("this_does_not_exist"); diff --git a/packages/sdk/src/cosmwasmclient.ts b/packages/sdk/src/cosmwasmclient.ts index be66846d..18b5319d 100644 --- a/packages/sdk/src/cosmwasmclient.ts +++ b/packages/sdk/src/cosmwasmclient.ts @@ -10,39 +10,40 @@ import { CosmosSdkTx, MsgExecuteContract, MsgInstantiateContract, + MsgSend, MsgStoreCode, StdFee, StdSignature, } from "./types"; -const defaultUploadFee: StdFee = { - amount: [ - { - amount: "5000", - denom: "ucosm", - }, - ], - gas: "1000000", // one million -}; +export interface FeeTable { + readonly upload: StdFee; + readonly init: StdFee; + readonly exec: StdFee; + readonly send: StdFee; +} -const defaultInitFee: StdFee = { - amount: [ - { - amount: "5000", - denom: "ucosm", - }, - ], - gas: "500000", // 500k -}; +function singleAmount(amount: number, denom: string): readonly Coin[] { + return [{ amount: amount.toString(), denom: denom }]; +} -const defaultExecFee: StdFee = { - amount: [ - { - amount: "5000", - denom: "ucosm", - }, - ], - gas: "200000", // 200k +const defaultFees: FeeTable = { + upload: { + amount: singleAmount(25000, "ucosm"), + gas: "1000000", // one million + }, + init: { + amount: singleAmount(12500, "ucosm"), + gas: "500000", // 500k + }, + exec: { + amount: singleAmount(5000, "ucosm"), + gas: "200000", // 200k + }, + send: { + amount: singleAmount(2000, "ucosm"), + gas: "80000", // 80k + }, }; export interface SigningCallback { @@ -98,22 +99,28 @@ export interface ExecuteResult { export class CosmWasmClient { public static makeReadOnly(url: string): CosmWasmClient { - return new CosmWasmClient(url); + return new CosmWasmClient(url, undefined, {}); } public static makeWritable( url: string, senderAddress: string, signCallback: SigningCallback, + feeTable?: Partial, ): CosmWasmClient { - return new CosmWasmClient(url, { - senderAddress: senderAddress, - signCallback: signCallback, - }); + return new CosmWasmClient( + url, + { + senderAddress: senderAddress, + signCallback: signCallback, + }, + feeTable || {}, + ); } private readonly restClient: RestClient; private readonly signingData: SigningData | undefined; + private readonly fees: FeeTable; private get senderAddress(): string { if (!this.signingData) throw new Error("Signing data not set in this client"); @@ -125,9 +132,10 @@ export class CosmWasmClient { return this.signingData.signCallback; } - private constructor(url: string, signingData?: SigningData) { + private constructor(url: string, signingData: SigningData | undefined, customFees: Partial) { this.restClient = new RestClient(url); this.signingData = signingData; + this.fees = { ...defaultFees, ...customFees }; } public async chainId(): Promise { @@ -234,7 +242,7 @@ export class CosmWasmClient { builder: "", }, }; - const fee = defaultUploadFee; + const fee = this.fees.upload; const { accountNumber, sequence } = await this.getNonce(); const chainId = await this.chainId(); const signBytes = makeSignBytes([storeCodeMsg], fee, chainId, memo, accountNumber, sequence); @@ -270,7 +278,7 @@ export class CosmWasmClient { init_funds: transferAmount || [], }, }; - const fee = defaultInitFee; + const fee = this.fees.init; const { accountNumber, sequence } = await this.getNonce(); const chainId = await this.chainId(); const signBytes = makeSignBytes([instantiateMsg], fee, chainId, memo, accountNumber, sequence); @@ -304,7 +312,7 @@ export class CosmWasmClient { sent_funds: transferAmount || [], }, }; - const fee = defaultExecFee; + const fee = this.fees.exec; const { accountNumber, sequence } = await this.getNonce(); const chainId = await this.chainId(); const signBytes = makeSignBytes([executeMsg], fee, chainId, memo, accountNumber, sequence); @@ -322,6 +330,36 @@ export class CosmWasmClient { }; } + public async sendTokens( + recipientAddress: string, + transferAmount: readonly Coin[], + memo = "", + ): Promise { + const sendMsg: MsgSend = { + type: "cosmos-sdk/MsgSend", + value: { + // eslint-disable-next-line @typescript-eslint/camelcase + from_address: this.senderAddress, + // eslint-disable-next-line @typescript-eslint/camelcase + to_address: recipientAddress, + amount: transferAmount, + }, + }; + const fee = this.fees.send; + const { accountNumber, sequence } = await this.getNonce(); + const chainId = await this.chainId(); + const signBytes = makeSignBytes([sendMsg], fee, chainId, memo, accountNumber, sequence); + const signature = await this.signCallback(signBytes); + const signedTx = { + msg: [sendMsg], + fee: fee, + memo: memo, + signatures: [signature], + }; + + return this.postTx(marshalTx(signedTx)); + } + /** * Returns the data at the key if present (raw contract dependent storage data) * or null if no data at this key. diff --git a/packages/sdk/types/cosmwasmclient.d.ts b/packages/sdk/types/cosmwasmclient.d.ts index 93fd5216..2a5208bb 100644 --- a/packages/sdk/types/cosmwasmclient.d.ts +++ b/packages/sdk/types/cosmwasmclient.d.ts @@ -1,6 +1,12 @@ import { Log } from "./logs"; import { BlockResponse, TxsResponse } from "./restclient"; -import { Coin, CosmosSdkAccount, CosmosSdkTx, StdSignature } from "./types"; +import { Coin, CosmosSdkAccount, CosmosSdkTx, StdFee, StdSignature } from "./types"; +export interface FeeTable { + readonly upload: StdFee; + readonly init: StdFee; + readonly exec: StdFee; + readonly send: StdFee; +} export interface SigningCallback { (signBytes: Uint8Array): Promise; } @@ -29,9 +35,15 @@ export interface ExecuteResult { } export declare class CosmWasmClient { static makeReadOnly(url: string): CosmWasmClient; - static makeWritable(url: string, senderAddress: string, signCallback: SigningCallback): CosmWasmClient; + static makeWritable( + url: string, + senderAddress: string, + signCallback: SigningCallback, + feeTable?: Partial, + ): CosmWasmClient; private readonly restClient; private readonly signingData; + private readonly fees; private get senderAddress(); private get signCallback(); private constructor(); @@ -71,6 +83,7 @@ export declare class CosmWasmClient { memo?: string, transferAmount?: readonly Coin[], ): Promise; + sendTokens(recipientAddress: string, transferAmount: readonly Coin[], memo?: string): Promise; /** * Returns the data at the key if present (raw contract dependent storage data) * or null if no data at this key.