diff --git a/clients/validator/src/index.ts b/clients/validator/src/index.ts index 3b24d23209..f0ea3ee2c9 100644 --- a/clients/validator/src/index.ts +++ b/clients/validator/src/index.ts @@ -1,9 +1,9 @@ import NetClient, { INetClient } from "./net-client"; -import { Gateway, GatewayBond, MixNode, MixNodeBond } from "./types"; +import { Gateway, GatewayBond, MixNode, MixNodeBond, SendRequest } from "./types"; import { Bip39, Random } from "@cosmjs/crypto"; import { DirectSecp256k1HdWallet } from "@cosmjs/proto-signing"; import MixnodesCache from "./caches/mixnodes"; -import { coin, Coin, coins } from "@cosmjs/launchpad"; +import { buildFeeTable, coin, Coin, coins, StdFee } from "@cosmjs/launchpad"; import { ExecuteResult, InstantiateOptions, @@ -24,6 +24,8 @@ import { import GatewaysCache from "./caches/gateways"; import QueryClient, { IQueryClient } from "./query-client"; import { BroadcastTxSuccess, isBroadcastTxFailure } from "@cosmjs/stargate"; +import { makeBankMsgSend } from "./utils"; +import { nymGasLimits, nymGasPrice } from "./stargate-helper"; export { coins, coin }; export { Coin }; @@ -383,6 +385,30 @@ export default class ValidatorClient { } } + /** + * Send funds multiple times from one address to another in a single block. + */ + async sendMultiple(senderAddress: string, data: SendRequest[], memo?: string): Promise { + if (this.client instanceof NetClient) { + if (data.length === 1) { + return this.send(data[0].senderAddress, data[0].recipientAddress, data[0].transferAmount, memo) + } + + const encoded = data.map(req => makeBankMsgSend(req.senderAddress, req.recipientAddress, req.transferAmount)); + + // the function to calculate fee for a single entry is not exposed... + const table = buildFeeTable(nymGasPrice(this.stakeDenom), {sendMultiple: nymGasLimits.send * data.length}, {sendMultiple: nymGasLimits.send * data.length}) + const fee = table.sendMultiple + const result = await this.client.signAndBroadcast(senderAddress, encoded, fee, memo) + if (isBroadcastTxFailure(result)) { + throw new Error(`Error when broadcasting tx ${result.transactionHash} at height ${result.height}. Code: ${result.code}; Raw log: ${result.rawLog}`) + } + return result + } else { + throw new Error("Tried to use sendMultiple with a query client"); + } + } + async upload(senderAddress: string, wasmCode: Uint8Array, meta?: UploadMeta, memo?: string): Promise { if (this.client instanceof NetClient) { return this.client.upload(senderAddress, wasmCode, meta, memo).catch((err) => this.handleRequestFailure(err)); diff --git a/clients/validator/src/net-client.ts b/clients/validator/src/net-client.ts index 5be1e22c0c..99b7e64612 100644 --- a/clients/validator/src/net-client.ts +++ b/clients/validator/src/net-client.ts @@ -6,10 +6,10 @@ import { PagedResponse, StateParams } from "./index"; -import { DirectSecp256k1HdWallet } from "@cosmjs/proto-signing"; -import { Coin, GasPrice } from "@cosmjs/launchpad"; +import { DirectSecp256k1HdWallet, EncodeObject } from "@cosmjs/proto-signing"; +import { Coin, GasPrice, StdFee } from "@cosmjs/launchpad"; import { BroadcastTxResponse } from "@cosmjs/stargate" -import { nymGasLimits } from "./stargate-helper" +import { nymGasLimits, nymGasPrice } from "./stargate-helper" import { ExecuteResult, InstantiateOptions, InstantiateResult, MigrateResult, UploadMeta, UploadResult } from "@cosmjs/cosmwasm"; export interface INetClient { @@ -21,6 +21,7 @@ export interface INetClient { ownsMixNode(contractAddress: string, address: string): Promise; ownsGateway(contractAddress: string, address: string): Promise; getStateParams(contractAddress: string): Promise; + signAndBroadcast(signerAddress: string, messages: readonly EncodeObject[], fee: StdFee, memo?: string): Promise; executeContract(senderAddress: string, contractAddress: string, handleMsg: Record, memo?: string, transferAmount?: readonly Coin[]): Promise; instantiate(senderAddress: string, codeId: number, initMsg: Record, label: string, options?: InstantiateOptions): Promise; sendTokens(senderAddress: string, recipientAddress: string, transferAmount: readonly Coin[], memo?: string): Promise; @@ -54,7 +55,7 @@ export default class NetClient implements INetClient { public static async connect(wallet: DirectSecp256k1HdWallet, url: string, stakeDenom: string): Promise { const [{ address }] = await wallet.getAccounts(); const signerOptions: SigningCosmWasmClientOptions = { - gasPrice: GasPrice.fromString(`0.025${stakeDenom}`), + gasPrice: nymGasPrice(stakeDenom), gasLimits: nymGasLimits, }; const client = await SigningCosmWasmClient.connectWithSigner(url, wallet, signerOptions); @@ -101,6 +102,10 @@ export default class NetClient implements INetClient { return this.cosmClient.execute(senderAddress, contractAddress, handleMsg, memo, transferAmount); } + public signAndBroadcast(signerAddress: string, messages: readonly EncodeObject[], fee: StdFee, memo?: string): Promise { + return this.cosmClient.signAndBroadcast(signerAddress, messages, fee, memo) + } + public sendTokens(senderAddress: string, recipientAddress: string, transferAmount: readonly Coin[], memo?: string): Promise { return this.cosmClient.sendTokens(senderAddress, recipientAddress, transferAmount, memo); } diff --git a/clients/validator/src/stargate-helper.ts b/clients/validator/src/stargate-helper.ts index 059dd2306f..1494a7b289 100644 --- a/clients/validator/src/stargate-helper.ts +++ b/clients/validator/src/stargate-helper.ts @@ -1,5 +1,5 @@ import axios from "axios"; -import { GasLimits } from "@cosmjs/launchpad"; +import { GasLimits, GasPrice } from "@cosmjs/launchpad"; import { CosmWasmFeeTable } from "@cosmjs/cosmwasm"; @@ -19,6 +19,8 @@ export const nymGasLimits: GasLimits = { changeAdmin: 80_000, }; +export const nymGasPrice: (stakeDenom: string) => GasPrice = (stakeDenom: string) => GasPrice.fromString(`0.025${stakeDenom}`); + export const defaultOptions: Options = { httpUrl: "http://localhost:26657", networkId: "nymnet", diff --git a/clients/validator/src/types.ts b/clients/validator/src/types.ts index d753e047cc..e2f5f75a23 100644 --- a/clients/validator/src/types.ts +++ b/clients/validator/src/types.ts @@ -30,3 +30,9 @@ export type Gateway = { identity_key: string, version: string } + +export type SendRequest = { + senderAddress: string, + recipientAddress: string, + transferAmount: readonly Coin[] +} \ No newline at end of file diff --git a/clients/validator/src/utils.ts b/clients/validator/src/utils.ts index 15fb1253c3..fe6329409e 100644 --- a/clients/validator/src/utils.ts +++ b/clients/validator/src/utils.ts @@ -1,5 +1,14 @@ // eslint-disable-next-line @typescript-eslint/no-explicit-any,@typescript-eslint/explicit-module-boundary-types -export default function log(text: string, thing: any): void { - const msg = JSON.stringify(thing); - console.log(`${text}: ${msg}`); +import { Coin } from "@cosmjs/launchpad/"; +import { EncodeObject } from "@cosmjs/proto-signing"; + +export function makeBankMsgSend(senderAddress: string, recipientAddress: string, transferAmount: readonly Coin[]): EncodeObject { + return { + typeUrl: "/cosmos.bank.v1beta1.MsgSend", + value: { + fromAddress: senderAddress, + toAddress: recipientAddress, + amount: transferAmount, + }, + }; } \ No newline at end of file