diff --git a/CHANGELOG.md b/CHANGELOG.md index 7a7e13fb..9b5aa82b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -49,6 +49,14 @@ - @cosmjs/tendermint-rpc: Remove types `BlockHash`, `TxBytes` and `TxHash`. Use `Uint8Array` instead. +### Added + +- @cosmjs/utils: Added `assertDefinedAndNotNull`. + +### Removed + +- @cosmjs/utils: `assertDefined` removed in favour of `assertDefinedAndNotNull`. + ## 0.23.2 (2021-01-06) ### Security diff --git a/packages/cosmwasm-stargate/src/aminotypes.spec.ts b/packages/cosmwasm-stargate/src/aminotypes.spec.ts new file mode 100644 index 00000000..38b7c563 --- /dev/null +++ b/packages/cosmwasm-stargate/src/aminotypes.spec.ts @@ -0,0 +1,337 @@ +/* eslint-disable @typescript-eslint/naming-convention */ +import { + MsgClearAdmin, + MsgExecuteContract, + MsgInstantiateContract, + MsgMigrateContract, + MsgStoreCode, + MsgUpdateAdmin, +} from "@cosmjs/cosmwasm-launchpad"; +import { fromBase64, toUtf8 } from "@cosmjs/encoding"; +import { coins } from "@cosmjs/launchpad"; +import { AminoTypes } from "@cosmjs/stargate"; +import Long from "long"; + +import { cosmWasmTypes } from "./aminotypes"; +import { cosmwasm } from "./codec"; + +type IMsgStoreCode = cosmwasm.wasm.v1beta1.IMsgStoreCode; +type IMsgInstantiateContract = cosmwasm.wasm.v1beta1.IMsgInstantiateContract; +type IMsgUpdateAdmin = cosmwasm.wasm.v1beta1.IMsgUpdateAdmin; +type IMsgClearAdmin = cosmwasm.wasm.v1beta1.IMsgClearAdmin; +type IMsgExecuteContract = cosmwasm.wasm.v1beta1.IMsgExecuteContract; +type IMsgMigrateContract = cosmwasm.wasm.v1beta1.IMsgMigrateContract; + +describe("AminoTypes", () => { + describe("toAmino", () => { + it("works for MsgStoreCode", () => { + const msg: IMsgStoreCode = { + sender: "cosmos1pkptre7fdkl6gfrzlesjjvhxhlc3r4gmmk8rs6", + wasmByteCode: fromBase64("WUVMTE9XIFNVQk1BUklORQ=="), + source: "Arrabiata", + builder: "Bob", + }; + const aminoMsg = new AminoTypes({ additions: cosmWasmTypes }).toAmino({ + typeUrl: "/cosmwasm.wasm.v1beta1.MsgStoreCode", + value: msg, + }); + const expected: MsgStoreCode = { + type: "wasm/MsgStoreCode", + value: { + sender: "cosmos1pkptre7fdkl6gfrzlesjjvhxhlc3r4gmmk8rs6", + wasm_byte_code: "WUVMTE9XIFNVQk1BUklORQ==", + source: "Arrabiata", + builder: "Bob", + }, + }; + expect(aminoMsg).toEqual(expected); + }); + + it("works for MsgInstantiateContract", () => { + const msg: IMsgInstantiateContract = { + sender: "cosmos1pkptre7fdkl6gfrzlesjjvhxhlc3r4gmmk8rs6", + codeId: Long.fromString("12345"), + label: "sticky", + initMsg: toUtf8( + JSON.stringify({ + foo: "bar", + }), + ), + initFunds: coins(1234, "ucosm"), + admin: "cosmos10dyr9899g6t0pelew4nvf4j5c3jcgv0r73qga5", + }; + const aminoMsg = new AminoTypes({ additions: cosmWasmTypes }).toAmino({ + typeUrl: "/cosmwasm.wasm.v1beta1.MsgInstantiateContract", + value: msg, + }); + const expected: MsgInstantiateContract = { + type: "wasm/MsgInstantiateContract", + value: { + sender: "cosmos1pkptre7fdkl6gfrzlesjjvhxhlc3r4gmmk8rs6", + code_id: "12345", + label: "sticky", + init_msg: { + foo: "bar", + }, + init_funds: coins(1234, "ucosm"), + admin: "cosmos10dyr9899g6t0pelew4nvf4j5c3jcgv0r73qga5", + }, + }; + expect(aminoMsg).toEqual(expected); + }); + + it("works for MsgUpdateAdmin", () => { + const msg: IMsgUpdateAdmin = { + sender: "cosmos1pkptre7fdkl6gfrzlesjjvhxhlc3r4gmmk8rs6", + newAdmin: "cosmos10dyr9899g6t0pelew4nvf4j5c3jcgv0r73qga5", + contract: "cosmos1xy4yqngt0nlkdcenxymg8tenrghmek4nmqm28k", + }; + const aminoMsg = new AminoTypes({ additions: cosmWasmTypes }).toAmino({ + typeUrl: "/cosmwasm.wasm.v1beta1.MsgUpdateAdmin", + value: msg, + }); + const expected: MsgUpdateAdmin = { + type: "wasm/MsgUpdateAdmin", + value: { + sender: "cosmos1pkptre7fdkl6gfrzlesjjvhxhlc3r4gmmk8rs6", + new_admin: "cosmos10dyr9899g6t0pelew4nvf4j5c3jcgv0r73qga5", + contract: "cosmos1xy4yqngt0nlkdcenxymg8tenrghmek4nmqm28k", + }, + }; + expect(aminoMsg).toEqual(expected); + }); + + it("works for MsgClearAdmin", () => { + const msg: IMsgClearAdmin = { + sender: "cosmos1pkptre7fdkl6gfrzlesjjvhxhlc3r4gmmk8rs6", + contract: "cosmos1xy4yqngt0nlkdcenxymg8tenrghmek4nmqm28k", + }; + const aminoMsg = new AminoTypes({ additions: cosmWasmTypes }).toAmino({ + typeUrl: "/cosmwasm.wasm.v1beta1.MsgClearAdmin", + value: msg, + }); + const expected: MsgClearAdmin = { + type: "wasm/MsgClearAdmin", + value: { + sender: "cosmos1pkptre7fdkl6gfrzlesjjvhxhlc3r4gmmk8rs6", + contract: "cosmos1xy4yqngt0nlkdcenxymg8tenrghmek4nmqm28k", + }, + }; + expect(aminoMsg).toEqual(expected); + }); + + it("works for MsgExecuteContract", () => { + const msg: IMsgExecuteContract = { + sender: "cosmos1pkptre7fdkl6gfrzlesjjvhxhlc3r4gmmk8rs6", + contract: "cosmos1xy4yqngt0nlkdcenxymg8tenrghmek4nmqm28k", + msg: toUtf8( + JSON.stringify({ + foo: "bar", + }), + ), + sentFunds: coins(1234, "ucosm"), + }; + const aminoMsg = new AminoTypes({ additions: cosmWasmTypes }).toAmino({ + typeUrl: "/cosmwasm.wasm.v1beta1.MsgExecuteContract", + value: msg, + }); + const expected: MsgExecuteContract = { + type: "wasm/MsgExecuteContract", + value: { + sender: "cosmos1pkptre7fdkl6gfrzlesjjvhxhlc3r4gmmk8rs6", + contract: "cosmos1xy4yqngt0nlkdcenxymg8tenrghmek4nmqm28k", + msg: { + foo: "bar", + }, + sent_funds: coins(1234, "ucosm"), + }, + }; + expect(aminoMsg).toEqual(expected); + }); + + it("works for MsgMigrateContract", () => { + const msg: IMsgMigrateContract = { + sender: "cosmos1pkptre7fdkl6gfrzlesjjvhxhlc3r4gmmk8rs6", + contract: "cosmos1xy4yqngt0nlkdcenxymg8tenrghmek4nmqm28k", + codeId: Long.fromString("98765"), + migrateMsg: toUtf8( + JSON.stringify({ + foo: "bar", + }), + ), + }; + const aminoMsg = new AminoTypes({ additions: cosmWasmTypes }).toAmino({ + typeUrl: "/cosmwasm.wasm.v1beta1.MsgMigrateContract", + value: msg, + }); + const expected: MsgMigrateContract = { + type: "wasm/MsgMigrateContract", + value: { + sender: "cosmos1pkptre7fdkl6gfrzlesjjvhxhlc3r4gmmk8rs6", + contract: "cosmos1xy4yqngt0nlkdcenxymg8tenrghmek4nmqm28k", + code_id: "98765", + msg: { + foo: "bar", + }, + }, + }; + expect(aminoMsg).toEqual(expected); + }); + }); + + describe("fromAmino", () => { + it("works for MsgStoreCode", () => { + const aminoMsg: MsgStoreCode = { + type: "wasm/MsgStoreCode", + value: { + sender: "cosmos1pkptre7fdkl6gfrzlesjjvhxhlc3r4gmmk8rs6", + wasm_byte_code: "WUVMTE9XIFNVQk1BUklORQ==", + source: "Arrabiata", + builder: "Bob", + }, + }; + const msg = new AminoTypes({ additions: cosmWasmTypes }).fromAmino(aminoMsg); + const expectedValue: IMsgStoreCode = { + sender: "cosmos1pkptre7fdkl6gfrzlesjjvhxhlc3r4gmmk8rs6", + wasmByteCode: fromBase64("WUVMTE9XIFNVQk1BUklORQ=="), + source: "Arrabiata", + builder: "Bob", + }; + expect(msg).toEqual({ + typeUrl: "/cosmwasm.wasm.v1beta1.MsgStoreCode", + value: expectedValue, + }); + }); + + it("works for MsgInstantiateContract", () => { + const aminoMsg: MsgInstantiateContract = { + type: "wasm/MsgInstantiateContract", + value: { + sender: "cosmos1pkptre7fdkl6gfrzlesjjvhxhlc3r4gmmk8rs6", + code_id: "12345", + label: "sticky", + init_msg: { + foo: "bar", + }, + init_funds: coins(1234, "ucosm"), + admin: "cosmos10dyr9899g6t0pelew4nvf4j5c3jcgv0r73qga5", + }, + }; + const msg = new AminoTypes({ additions: cosmWasmTypes }).fromAmino(aminoMsg); + const expectedValue: IMsgInstantiateContract = { + sender: "cosmos1pkptre7fdkl6gfrzlesjjvhxhlc3r4gmmk8rs6", + codeId: Long.fromString("12345"), + label: "sticky", + initMsg: toUtf8( + JSON.stringify({ + foo: "bar", + }), + ), + initFunds: coins(1234, "ucosm"), + admin: "cosmos10dyr9899g6t0pelew4nvf4j5c3jcgv0r73qga5", + }; + expect(msg).toEqual({ + typeUrl: "/cosmwasm.wasm.v1beta1.MsgInstantiateContract", + value: expectedValue, + }); + }); + + it("works for MsgUpdateAdmin", () => { + const aminoMsg: MsgUpdateAdmin = { + type: "wasm/MsgUpdateAdmin", + value: { + sender: "cosmos1pkptre7fdkl6gfrzlesjjvhxhlc3r4gmmk8rs6", + new_admin: "cosmos10dyr9899g6t0pelew4nvf4j5c3jcgv0r73qga5", + contract: "cosmos1xy4yqngt0nlkdcenxymg8tenrghmek4nmqm28k", + }, + }; + const msg = new AminoTypes({ additions: cosmWasmTypes }).fromAmino(aminoMsg); + const expectedValue: IMsgUpdateAdmin = { + sender: "cosmos1pkptre7fdkl6gfrzlesjjvhxhlc3r4gmmk8rs6", + newAdmin: "cosmos10dyr9899g6t0pelew4nvf4j5c3jcgv0r73qga5", + contract: "cosmos1xy4yqngt0nlkdcenxymg8tenrghmek4nmqm28k", + }; + expect(msg).toEqual({ + typeUrl: "/cosmwasm.wasm.v1beta1.MsgUpdateAdmin", + value: expectedValue, + }); + }); + + it("works for MsgClearAdmin", () => { + const aminoMsg: MsgClearAdmin = { + type: "wasm/MsgClearAdmin", + value: { + sender: "cosmos1pkptre7fdkl6gfrzlesjjvhxhlc3r4gmmk8rs6", + contract: "cosmos1xy4yqngt0nlkdcenxymg8tenrghmek4nmqm28k", + }, + }; + const msg = new AminoTypes({ additions: cosmWasmTypes }).fromAmino(aminoMsg); + const expectedValue: IMsgClearAdmin = { + sender: "cosmos1pkptre7fdkl6gfrzlesjjvhxhlc3r4gmmk8rs6", + contract: "cosmos1xy4yqngt0nlkdcenxymg8tenrghmek4nmqm28k", + }; + expect(msg).toEqual({ + typeUrl: "/cosmwasm.wasm.v1beta1.MsgClearAdmin", + value: expectedValue, + }); + }); + + it("works for MsgExecuteContract", () => { + const aminoMsg: MsgExecuteContract = { + type: "wasm/MsgExecuteContract", + value: { + sender: "cosmos1pkptre7fdkl6gfrzlesjjvhxhlc3r4gmmk8rs6", + contract: "cosmos1xy4yqngt0nlkdcenxymg8tenrghmek4nmqm28k", + msg: { + foo: "bar", + }, + sent_funds: coins(1234, "ucosm"), + }, + }; + const msg = new AminoTypes({ additions: cosmWasmTypes }).fromAmino(aminoMsg); + const expectedValue: IMsgExecuteContract = { + sender: "cosmos1pkptre7fdkl6gfrzlesjjvhxhlc3r4gmmk8rs6", + contract: "cosmos1xy4yqngt0nlkdcenxymg8tenrghmek4nmqm28k", + msg: toUtf8( + JSON.stringify({ + foo: "bar", + }), + ), + sentFunds: coins(1234, "ucosm"), + }; + expect(msg).toEqual({ + typeUrl: "/cosmwasm.wasm.v1beta1.MsgExecuteContract", + value: expectedValue, + }); + }); + + it("works for MsgMigrateContract", () => { + const aminoMsg: MsgMigrateContract = { + type: "wasm/MsgMigrateContract", + value: { + sender: "cosmos1pkptre7fdkl6gfrzlesjjvhxhlc3r4gmmk8rs6", + contract: "cosmos1xy4yqngt0nlkdcenxymg8tenrghmek4nmqm28k", + code_id: "98765", + msg: { + foo: "bar", + }, + }, + }; + const msg = new AminoTypes({ additions: cosmWasmTypes }).fromAmino(aminoMsg); + const expectedValue: IMsgMigrateContract = { + sender: "cosmos1pkptre7fdkl6gfrzlesjjvhxhlc3r4gmmk8rs6", + contract: "cosmos1xy4yqngt0nlkdcenxymg8tenrghmek4nmqm28k", + codeId: Long.fromString("98765"), + migrateMsg: toUtf8( + JSON.stringify({ + foo: "bar", + }), + ), + }; + expect(msg).toEqual({ + typeUrl: "/cosmwasm.wasm.v1beta1.MsgMigrateContract", + value: expectedValue, + }); + }); + }); +}); diff --git a/packages/cosmwasm-stargate/src/aminotypes.ts b/packages/cosmwasm-stargate/src/aminotypes.ts new file mode 100644 index 00000000..968d7260 --- /dev/null +++ b/packages/cosmwasm-stargate/src/aminotypes.ts @@ -0,0 +1,161 @@ +/* eslint-disable @typescript-eslint/naming-convention */ +import { + MsgClearAdmin, + MsgExecuteContract, + MsgInstantiateContract, + MsgMigrateContract, + MsgStoreCode, + MsgUpdateAdmin, +} from "@cosmjs/cosmwasm-launchpad"; +import { fromBase64, fromUtf8, toBase64, toUtf8 } from "@cosmjs/encoding"; +import { AminoConverter, coinFromProto } from "@cosmjs/stargate"; +import { assertDefinedAndNotNull } from "@cosmjs/utils"; +import Long from "long"; + +import { cosmwasm } from "./codec"; + +type IMsgStoreCode = cosmwasm.wasm.v1beta1.IMsgStoreCode; +type IMsgInstantiateContract = cosmwasm.wasm.v1beta1.IMsgInstantiateContract; +type IMsgUpdateAdmin = cosmwasm.wasm.v1beta1.IMsgUpdateAdmin; +type IMsgClearAdmin = cosmwasm.wasm.v1beta1.IMsgClearAdmin; +type IMsgExecuteContract = cosmwasm.wasm.v1beta1.IMsgExecuteContract; +type IMsgMigrateContract = cosmwasm.wasm.v1beta1.IMsgMigrateContract; + +export const cosmWasmTypes: Record = { + "/cosmwasm.wasm.v1beta1.MsgStoreCode": { + aminoType: "wasm/MsgStoreCode", + toAmino: ({ sender, wasmByteCode, source, builder }: IMsgStoreCode): MsgStoreCode["value"] => { + assertDefinedAndNotNull(sender, "missing sender"); + assertDefinedAndNotNull(wasmByteCode, "missing wasmByteCode"); + assertDefinedAndNotNull(source, "missing source"); + assertDefinedAndNotNull(builder, "missing builder"); + return { + sender: sender, + wasm_byte_code: toBase64(wasmByteCode), + source: source, + builder: builder, + }; + }, + fromAmino: ({ sender, wasm_byte_code, source, builder }: MsgStoreCode["value"]): IMsgStoreCode => ({ + sender: sender, + wasmByteCode: fromBase64(wasm_byte_code), + source: source, + builder: builder, + }), + }, + "/cosmwasm.wasm.v1beta1.MsgInstantiateContract": { + aminoType: "wasm/MsgInstantiateContract", + toAmino: ({ + sender, + codeId, + label, + initMsg, + initFunds, + admin, + }: IMsgInstantiateContract): MsgInstantiateContract["value"] => { + assertDefinedAndNotNull(sender, "missing sender"); + assertDefinedAndNotNull(codeId, "missing codeId"); + assertDefinedAndNotNull(label, "missing label"); + assertDefinedAndNotNull(initMsg, "missing initMsg"); + assertDefinedAndNotNull(initFunds, "missing initFunds"); + return { + sender: sender, + code_id: codeId.toString(), + label: label, + init_msg: JSON.parse(fromUtf8(initMsg)), + init_funds: initFunds.map(coinFromProto), + admin: admin ?? undefined, + }; + }, + fromAmino: ({ + sender, + code_id, + label, + init_msg, + init_funds, + admin, + }: MsgInstantiateContract["value"]): IMsgInstantiateContract => ({ + sender: sender, + codeId: Long.fromString(code_id), + label: label, + initMsg: toUtf8(JSON.stringify(init_msg)), + initFunds: [...init_funds], + admin: admin, + }), + }, + "/cosmwasm.wasm.v1beta1.MsgUpdateAdmin": { + aminoType: "wasm/MsgUpdateAdmin", + toAmino: ({ sender, newAdmin, contract }: IMsgUpdateAdmin): MsgUpdateAdmin["value"] => { + assertDefinedAndNotNull(sender, "missing sender"); + assertDefinedAndNotNull(newAdmin, "missing newAdmin"); + assertDefinedAndNotNull(contract, "missing contract"); + return { + sender: sender, + new_admin: newAdmin, + contract: contract, + }; + }, + fromAmino: ({ sender, new_admin, contract }: MsgUpdateAdmin["value"]): IMsgUpdateAdmin => ({ + sender: sender, + newAdmin: new_admin, + contract: contract, + }), + }, + "/cosmwasm.wasm.v1beta1.MsgClearAdmin": { + aminoType: "wasm/MsgClearAdmin", + toAmino: ({ sender, contract }: IMsgClearAdmin): MsgClearAdmin["value"] => { + assertDefinedAndNotNull(sender, "missing sender"); + assertDefinedAndNotNull(contract, "missing contract"); + return { + sender: sender, + contract: contract, + }; + }, + fromAmino: ({ sender, contract }: MsgClearAdmin["value"]): IMsgClearAdmin => ({ + sender: sender, + contract: contract, + }), + }, + "/cosmwasm.wasm.v1beta1.MsgExecuteContract": { + aminoType: "wasm/MsgExecuteContract", + toAmino: ({ sender, contract, msg, sentFunds }: IMsgExecuteContract): MsgExecuteContract["value"] => { + assertDefinedAndNotNull(sender, "missing sender"); + assertDefinedAndNotNull(contract, "missing contract"); + assertDefinedAndNotNull(msg, "missing msg"); + assertDefinedAndNotNull(sentFunds, "missing sentFunds"); + return { + sender: sender, + contract: contract, + msg: JSON.parse(fromUtf8(msg)), + sent_funds: sentFunds.map(coinFromProto), + }; + }, + fromAmino: ({ sender, contract, msg, sent_funds }: MsgExecuteContract["value"]): IMsgExecuteContract => ({ + sender: sender, + contract: contract, + msg: toUtf8(JSON.stringify(msg)), + sentFunds: [...sent_funds], + }), + }, + "/cosmwasm.wasm.v1beta1.MsgMigrateContract": { + aminoType: "wasm/MsgMigrateContract", + toAmino: ({ sender, contract, codeId, migrateMsg }: IMsgMigrateContract): MsgMigrateContract["value"] => { + assertDefinedAndNotNull(sender, "missing sender"); + assertDefinedAndNotNull(contract, "missing contract"); + assertDefinedAndNotNull(codeId, "missing codeId"); + assertDefinedAndNotNull(migrateMsg, "missing migrateMsg"); + return { + sender: sender, + contract: contract, + code_id: codeId.toString(), + msg: JSON.parse(fromUtf8(migrateMsg)), + }; + }, + fromAmino: ({ sender, contract, code_id, msg }: MsgMigrateContract["value"]): IMsgMigrateContract => ({ + sender: sender, + contract: contract, + codeId: Long.fromString(code_id), + migrateMsg: toUtf8(JSON.stringify(msg)), + }), + }, +}; diff --git a/packages/cosmwasm-stargate/src/queries/wasm.spec.ts b/packages/cosmwasm-stargate/src/queries/wasm.spec.ts index 78b7897f..48363469 100644 --- a/packages/cosmwasm-stargate/src/queries/wasm.spec.ts +++ b/packages/cosmwasm-stargate/src/queries/wasm.spec.ts @@ -9,7 +9,7 @@ import { parseRawLog, SigningStargateClient, } from "@cosmjs/stargate"; -import { assert, assertDefined } from "@cosmjs/utils"; +import { assert, assertDefinedAndNotNull } from "@cosmjs/utils"; import Long from "long"; import { cosmwasm } from "../codec"; @@ -395,7 +395,7 @@ describe("WasmExtension", () => { expect(codeId).toBeGreaterThanOrEqual(1); expect(codeId).toBeLessThanOrEqual(200); - assertDefined(result.data); + assertDefinedAndNotNull(result.data); const msgData = fromOneElementArray(result.data); expect(msgData.msgType).toEqual("store-code"); expect(MsgStoreCodeResponse.decode(msgData.data!)).toEqual( @@ -415,7 +415,7 @@ describe("WasmExtension", () => { const amountAttr = logs.findAttribute(parsedLogs, "transfer", "amount"); expect(amountAttr.value).toEqual("1234ucosm,321ustake"); - assertDefined(result.data); + assertDefinedAndNotNull(result.data); const msgData = fromOneElementArray(result.data); expect(msgData.msgType).toEqual("instantiate"); expect(MsgInstantiateContractResponse.decode(msgData.data!)).toEqual( @@ -441,7 +441,7 @@ describe("WasmExtension", () => { value: beneficiaryAddress, }); - assertDefined(result.data); + assertDefinedAndNotNull(result.data); const msgData = fromOneElementArray(result.data); expect(msgData.msgType).toEqual("execute"); expect(MsgExecuteContractResponse.decode(msgData.data!)).toEqual( diff --git a/packages/cosmwasm-stargate/src/signingcosmwasmclient.spec.ts b/packages/cosmwasm-stargate/src/signingcosmwasmclient.spec.ts index def69bad..4c4cb8a3 100644 --- a/packages/cosmwasm-stargate/src/signingcosmwasmclient.spec.ts +++ b/packages/cosmwasm-stargate/src/signingcosmwasmclient.spec.ts @@ -2,12 +2,21 @@ import { UploadMeta } from "@cosmjs/cosmwasm-launchpad"; import { sha256 } from "@cosmjs/crypto"; import { toHex } from "@cosmjs/encoding"; -import { coin, coins, GasPrice } from "@cosmjs/launchpad"; -import { DirectSecp256k1HdWallet, Registry } from "@cosmjs/proto-signing"; -import { assertIsBroadcastTxSuccess, codec } from "@cosmjs/stargate"; +import { + coin, + coins, + GasPrice, + MsgDelegate as LaunchpadMsgDelegate, + Secp256k1HdWallet, +} from "@cosmjs/launchpad"; +import { Coin, cosmosField, DirectSecp256k1HdWallet, registered, Registry } from "@cosmjs/proto-signing"; +import { AminoTypes, assertIsBroadcastTxSuccess, codec } from "@cosmjs/stargate"; import { assert, sleep } from "@cosmjs/utils"; import Long from "long"; +import pako from "pako"; +import { Message } from "protobufjs"; +import { cosmwasm } from "./codec"; import { PrivateSigningCosmWasmClient, SigningCosmWasmClient } from "./signingcosmwasmclient"; import { alice, @@ -15,12 +24,18 @@ import { makeRandomAddress, makeWasmClient, ModifyingDirectSecp256k1HdWallet, + ModifyingSecp256k1HdWallet, pendingWithoutWasmd, unused, validator, wasmd, } from "./testutils.spec"; +type IMsgSend = codec.cosmos.bank.v1beta1.IMsgSend; +type IMsgDelegate = codec.cosmos.staking.v1beta1.IMsgDelegate; +type IMsgStoreCode = cosmwasm.wasm.v1beta1.IMsgStoreCode; + +const { MsgSend } = codec.cosmos.bank.v1beta1; const { MsgDelegate } = codec.cosmos.staking.v1beta1; const { Tx } = codec.cosmos.tx.v1beta1; @@ -29,15 +44,30 @@ describe("SigningCosmWasmClient", () => { it("can be constructed", async () => { pendingWithoutWasmd(); const wallet = await DirectSecp256k1HdWallet.fromMnemonic(alice.mnemonic, undefined, wasmd.prefix); - const client = await SigningCosmWasmClient.connectWithSigner(wasmd.endpoint, wallet); + const options = { prefix: wasmd.prefix }; + const client = await SigningCosmWasmClient.connectWithSigner(wasmd.endpoint, wallet, options); expect(client).toBeTruthy(); }); + it("can be constructed with custom registry", async () => { + pendingWithoutWasmd(); + const wallet = await DirectSecp256k1HdWallet.fromMnemonic(alice.mnemonic); + const registry = new Registry(); + registry.register("/custom.MsgCustom", MsgSend); + const options = { prefix: wasmd.prefix, registry: registry }; + const client = await SigningCosmWasmClient.connectWithSigner(wasmd.endpoint, wallet, options); + const openedClient = (client as unknown) as PrivateSigningCosmWasmClient; + expect(openedClient.registry.lookupType("/custom.MsgCustom")).toEqual(MsgSend); + }); + it("can be constructed with custom gas price", async () => { pendingWithoutWasmd(); const wallet = await DirectSecp256k1HdWallet.fromMnemonic(alice.mnemonic, undefined, wasmd.prefix); - const gasPrice = GasPrice.fromString("3.14utest"); - const client = await SigningCosmWasmClient.connectWithSigner(wasmd.endpoint, wallet, { gasPrice }); + const options = { + prefix: wasmd.prefix, + gasPrice: GasPrice.fromString("3.14utest"), + }; + const client = await SigningCosmWasmClient.connectWithSigner(wasmd.endpoint, wallet, options); const openedClient = (client as unknown) as PrivateSigningCosmWasmClient; expect(openedClient.fees).toEqual({ upload: { @@ -70,10 +100,13 @@ describe("SigningCosmWasmClient", () => { it("can be constructed with custom gas limits", async () => { pendingWithoutWasmd(); const wallet = await DirectSecp256k1HdWallet.fromMnemonic(alice.mnemonic, undefined, wasmd.prefix); - const gasLimits = { - send: 160000, + const options = { + prefix: wasmd.prefix, + gasLimits: { + send: 160000, + }, }; - const client = await SigningCosmWasmClient.connectWithSigner(wasmd.endpoint, wallet, { gasLimits }); + const client = await SigningCosmWasmClient.connectWithSigner(wasmd.endpoint, wallet, options); const openedClient = (client as unknown) as PrivateSigningCosmWasmClient; expect(openedClient.fees).toEqual({ upload: { @@ -106,14 +139,14 @@ describe("SigningCosmWasmClient", () => { it("can be constructed with custom gas price and gas limits", async () => { pendingWithoutWasmd(); const wallet = await DirectSecp256k1HdWallet.fromMnemonic(alice.mnemonic, undefined, wasmd.prefix); - const gasPrice = GasPrice.fromString("3.14utest"); - const gasLimits = { - send: 160000, + const options = { + prefix: wasmd.prefix, + gasPrice: GasPrice.fromString("3.14utest"), + gasLimits: { + send: 160000, + }, }; - const client = await SigningCosmWasmClient.connectWithSigner(wasmd.endpoint, wallet, { - gasPrice, - gasLimits, - }); + const client = await SigningCosmWasmClient.connectWithSigner(wasmd.endpoint, wallet, options); const openedClient = (client as unknown) as PrivateSigningCosmWasmClient; expect(openedClient.fees).toEqual({ upload: { @@ -148,7 +181,8 @@ describe("SigningCosmWasmClient", () => { it("works", async () => { pendingWithoutWasmd(); const wallet = await DirectSecp256k1HdWallet.fromMnemonic(alice.mnemonic, undefined, wasmd.prefix); - const client = await SigningCosmWasmClient.connectWithSigner(wasmd.endpoint, wallet); + const options = { prefix: wasmd.prefix }; + const client = await SigningCosmWasmClient.connectWithSigner(wasmd.endpoint, wallet, options); const wasm = getHackatom().data; const { codeId, @@ -167,7 +201,8 @@ describe("SigningCosmWasmClient", () => { it("can set builder and source", async () => { pendingWithoutWasmd(); const wallet = await DirectSecp256k1HdWallet.fromMnemonic(alice.mnemonic, undefined, wasmd.prefix); - const client = await SigningCosmWasmClient.connectWithSigner(wasmd.endpoint, wallet); + const options = { prefix: wasmd.prefix }; + const client = await SigningCosmWasmClient.connectWithSigner(wasmd.endpoint, wallet, options); const hackatom = getHackatom(); const meta: UploadMeta = { source: "https://crates.io/api/v1/crates/cw-nameservice/0.1.0/download", @@ -184,7 +219,8 @@ describe("SigningCosmWasmClient", () => { it("works with transfer amount", async () => { pendingWithoutWasmd(); const wallet = await DirectSecp256k1HdWallet.fromMnemonic(alice.mnemonic, undefined, wasmd.prefix); - const client = await SigningCosmWasmClient.connectWithSigner(wasmd.endpoint, wallet); + const options = { prefix: wasmd.prefix }; + const client = await SigningCosmWasmClient.connectWithSigner(wasmd.endpoint, wallet, options); const { codeId } = await client.upload(alice.address0, getHackatom().data); const transferAmount = [coin(1234, "ucosm"), coin(321, "ustake")]; const beneficiaryAddress = makeRandomAddress(); @@ -211,7 +247,8 @@ describe("SigningCosmWasmClient", () => { it("works with admin", async () => { pendingWithoutWasmd(); const wallet = await DirectSecp256k1HdWallet.fromMnemonic(alice.mnemonic, undefined, wasmd.prefix); - const client = await SigningCosmWasmClient.connectWithSigner(wasmd.endpoint, wallet); + const options = { prefix: wasmd.prefix }; + const client = await SigningCosmWasmClient.connectWithSigner(wasmd.endpoint, wallet, options); const { codeId } = await client.upload(alice.address0, getHackatom().data); const beneficiaryAddress = makeRandomAddress(); const { contractAddress } = await client.instantiate( @@ -233,7 +270,8 @@ describe("SigningCosmWasmClient", () => { it("can instantiate one code multiple times", async () => { pendingWithoutWasmd(); const wallet = await DirectSecp256k1HdWallet.fromMnemonic(alice.mnemonic, undefined, wasmd.prefix); - const client = await SigningCosmWasmClient.connectWithSigner(wasmd.endpoint, wallet); + const options = { prefix: wasmd.prefix }; + const client = await SigningCosmWasmClient.connectWithSigner(wasmd.endpoint, wallet, options); const { codeId } = await client.upload(alice.address0, getHackatom().data); const contractAddress1 = await client.instantiate( alice.address0, @@ -261,7 +299,8 @@ describe("SigningCosmWasmClient", () => { it("can update an admin", async () => { pendingWithoutWasmd(); const wallet = await DirectSecp256k1HdWallet.fromMnemonic(alice.mnemonic, undefined, wasmd.prefix); - const client = await SigningCosmWasmClient.connectWithSigner(wasmd.endpoint, wallet); + const options = { prefix: wasmd.prefix }; + const client = await SigningCosmWasmClient.connectWithSigner(wasmd.endpoint, wallet, options); const { codeId } = await client.upload(alice.address0, getHackatom().data); const beneficiaryAddress = makeRandomAddress(); const { contractAddress } = await client.instantiate( @@ -296,7 +335,8 @@ describe("SigningCosmWasmClient", () => { it("can clear an admin", async () => { pendingWithoutWasmd(); const wallet = await DirectSecp256k1HdWallet.fromMnemonic(alice.mnemonic, undefined, wasmd.prefix); - const client = await SigningCosmWasmClient.connectWithSigner(wasmd.endpoint, wallet); + const options = { prefix: wasmd.prefix }; + const client = await SigningCosmWasmClient.connectWithSigner(wasmd.endpoint, wallet, options); const { codeId } = await client.upload(alice.address0, getHackatom().data); const beneficiaryAddress = makeRandomAddress(); const { contractAddress } = await client.instantiate( @@ -331,7 +371,8 @@ describe("SigningCosmWasmClient", () => { it("can can migrate from one code ID to another", async () => { pendingWithoutWasmd(); const wallet = await DirectSecp256k1HdWallet.fromMnemonic(alice.mnemonic, undefined, wasmd.prefix); - const client = await SigningCosmWasmClient.connectWithSigner(wasmd.endpoint, wallet); + const options = { prefix: wasmd.prefix }; + const client = await SigningCosmWasmClient.connectWithSigner(wasmd.endpoint, wallet, options); const { codeId: codeId1 } = await client.upload(alice.address0, getHackatom().data); const { codeId: codeId2 } = await client.upload(alice.address0, getHackatom().data); const beneficiaryAddress = makeRandomAddress(); @@ -371,7 +412,8 @@ describe("SigningCosmWasmClient", () => { it("works", async () => { pendingWithoutWasmd(); const wallet = await DirectSecp256k1HdWallet.fromMnemonic(alice.mnemonic, undefined, wasmd.prefix); - const client = await SigningCosmWasmClient.connectWithSigner(wasmd.endpoint, wallet); + const options = { prefix: wasmd.prefix }; + const client = await SigningCosmWasmClient.connectWithSigner(wasmd.endpoint, wallet, options); const { codeId } = await client.upload(alice.address0, getHackatom().data); // instantiate const transferAmount = [coin(233444, "ucosm"), coin(5454, "ustake")]; @@ -411,10 +453,36 @@ describe("SigningCosmWasmClient", () => { }); describe("sendTokens", () => { - it("works", async () => { + it("works with direct signer", async () => { pendingWithoutWasmd(); const wallet = await DirectSecp256k1HdWallet.fromMnemonic(alice.mnemonic, undefined, wasmd.prefix); - const client = await SigningCosmWasmClient.connectWithSigner(wasmd.endpoint, wallet); + const options = { prefix: wasmd.prefix }; + const client = await SigningCosmWasmClient.connectWithSigner(wasmd.endpoint, wallet, options); + + const transferAmount = coins(7890, "ucosm"); + const beneficiaryAddress = makeRandomAddress(); + const memo = "for dinner"; + + // no tokens here + const before = await client.getBalance(beneficiaryAddress, "ucosm"); + expect(before).toBeNull(); + + // send + const result = await client.sendTokens(alice.address0, beneficiaryAddress, transferAmount, memo); + assertIsBroadcastTxSuccess(result); + expect(result.rawLog).toBeTruthy(); + + // got tokens + const after = await client.getBalance(beneficiaryAddress, "ucosm"); + assert(after); + expect(after).toEqual(transferAmount[0]); + }); + + it("works with legacy Amino signer", async () => { + pendingWithoutWasmd(); + const wallet = await Secp256k1HdWallet.fromMnemonic(alice.mnemonic, undefined, wasmd.prefix); + const options = { prefix: wasmd.prefix }; + const client = await SigningCosmWasmClient.connectWithSigner(wasmd.endpoint, wallet, options); const transferAmount = coins(7890, "ucosm"); const beneficiaryAddress = makeRandomAddress(); @@ -444,7 +512,7 @@ describe("SigningCosmWasmClient", () => { const msgDelegateTypeUrl = "/cosmos.staking.v1beta1.MsgDelegate"; const registry = new Registry(); registry.register(msgDelegateTypeUrl, MsgDelegate); - const options = { registry: registry }; + const options = { prefix: wasmd.prefix, registry: registry }; const client = await SigningCosmWasmClient.connectWithSigner(wasmd.endpoint, wallet, options); const msg = MsgDelegate.create({ @@ -475,7 +543,7 @@ describe("SigningCosmWasmClient", () => { const msgDelegateTypeUrl = "/cosmos.staking.v1beta1.MsgDelegate"; const registry = new Registry(); registry.register(msgDelegateTypeUrl, MsgDelegate); - const options = { registry: registry }; + const options = { prefix: wasmd.prefix, registry: registry }; const client = await SigningCosmWasmClient.connectWithSigner(wasmd.endpoint, wallet, options); const msg = MsgDelegate.create({ @@ -506,5 +574,193 @@ describe("SigningCosmWasmClient", () => { expect(tx.authInfo!.fee!.gasLimit!.toNumber()).toEqual(333333); }); }); + + describe("legacy Amino mode", () => { + // NOTE: One custom registry shared between tests + // See https://github.com/protobufjs/protobuf.js#using-decorators + // > Decorated types reside in protobuf.roots["decorated"] using a flat structure, so no duplicate names. + const customRegistry = new Registry(); + const msgDelegateTypeUrl = "/cosmos.staking.v1beta1.MsgDelegate"; + + @registered(customRegistry, msgDelegateTypeUrl) + // eslint-disable-next-line @typescript-eslint/no-unused-vars + class CustomMsgDelegate extends Message { + @cosmosField.string(1) + public readonly custom_delegator_address?: string; + @cosmosField.string(2) + public readonly custom_validator_address?: string; + @cosmosField.message(3, Coin) + public readonly custom_amount?: Coin; + } + + it("works with bank MsgSend", async () => { + pendingWithoutWasmd(); + const wallet = await Secp256k1HdWallet.fromMnemonic(alice.mnemonic, undefined, wasmd.prefix); + const options = { prefix: wasmd.prefix }; + const client = await SigningCosmWasmClient.connectWithSigner(wasmd.endpoint, wallet, options); + + const msgSend: IMsgSend = { + fromAddress: alice.address0, + toAddress: makeRandomAddress(), + amount: coins(1234, "ucosm"), + }; + const msgAny = { + typeUrl: "/cosmos.bank.v1beta1.MsgSend", + value: msgSend, + }; + const fee = { + amount: coins(2000, "ucosm"), + gas: "200000", + }; + const memo = "Use your tokens wisely"; + const result = await client.signAndBroadcast(alice.address0, [msgAny], fee, memo); + assertIsBroadcastTxSuccess(result); + }); + + it("works with staking MsgDelegate", async () => { + pendingWithoutWasmd(); + const wallet = await Secp256k1HdWallet.fromMnemonic(alice.mnemonic, undefined, wasmd.prefix); + const options = { prefix: wasmd.prefix }; + const client = await SigningCosmWasmClient.connectWithSigner(wasmd.endpoint, wallet, options); + + const msgDelegate: IMsgDelegate = { + delegatorAddress: alice.address0, + validatorAddress: validator.validatorAddress, + amount: coin(1234, "ustake"), + }; + const msgAny = { + typeUrl: "/cosmos.staking.v1beta1.MsgDelegate", + value: msgDelegate, + }; + const fee = { + amount: coins(2000, "ustake"), + gas: "200000", + }; + const memo = "Use your tokens wisely"; + const result = await client.signAndBroadcast(alice.address0, [msgAny], fee, memo); + assertIsBroadcastTxSuccess(result); + }); + + it("works with wasm MsgStoreCode", async () => { + pendingWithoutWasmd(); + const wallet = await Secp256k1HdWallet.fromMnemonic(alice.mnemonic, undefined, wasmd.prefix); + const options = { prefix: wasmd.prefix }; + const client = await SigningCosmWasmClient.connectWithSigner(wasmd.endpoint, wallet, options); + const { data, builder, source } = getHackatom(); + + const msgStoreCode: IMsgStoreCode = { + sender: alice.address0, + wasmByteCode: pako.gzip(data), + source: source, + builder: builder, + }; + const msgAny = { + typeUrl: "/cosmwasm.wasm.v1beta1.MsgStoreCode", + value: msgStoreCode, + }; + const fee = { + amount: coins(2000, "ustake"), + gas: "1500000", + }; + const memo = "Use your tokens wisely"; + const result = await client.signAndBroadcast(alice.address0, [msgAny], fee, memo); + assertIsBroadcastTxSuccess(result); + }); + + it("works with a custom registry and custom message", async () => { + pendingWithoutWasmd(); + const wallet = await Secp256k1HdWallet.fromMnemonic(alice.mnemonic, undefined, wasmd.prefix); + const customAminoTypes = new AminoTypes({ + prefix: wasmd.prefix, + additions: { + "/cosmos.staking.v1beta1.MsgDelegate": { + aminoType: "cosmos-sdk/MsgDelegate", + toAmino: ({ + custom_delegator_address, + custom_validator_address, + custom_amount, + }: CustomMsgDelegate): LaunchpadMsgDelegate["value"] => { + assert(custom_delegator_address, "missing custom_delegator_address"); + assert(custom_validator_address, "missing validator_address"); + assert(custom_amount, "missing amount"); + assert(custom_amount.amount, "missing amount.amount"); + assert(custom_amount.denom, "missing amount.denom"); + return { + delegator_address: custom_delegator_address, + validator_address: custom_validator_address, + amount: { + amount: custom_amount.amount, + denom: custom_amount.denom, + }, + }; + }, + fromAmino: ({ + delegator_address, + validator_address, + amount, + }: LaunchpadMsgDelegate["value"]): CustomMsgDelegate => + CustomMsgDelegate.create({ + custom_delegator_address: delegator_address, + custom_validator_address: validator_address, + custom_amount: Coin.create(amount), + }), + }, + }, + }); + const options = { prefix: wasmd.prefix, registry: customRegistry, aminoTypes: customAminoTypes }; + const client = await SigningCosmWasmClient.connectWithSigner(wasmd.endpoint, wallet, options); + + const msg = { + custom_delegator_address: alice.address0, + custom_validator_address: validator.validatorAddress, + custom_amount: coin(1234, "ustake"), + }; + const msgAny = { + typeUrl: "/cosmos.staking.v1beta1.MsgDelegate", + value: msg, + }; + const fee = { + amount: coins(2000, "ucosm"), + gas: "200000", + }; + const memo = "Use your power wisely"; + const result = await client.signAndBroadcast(alice.address0, [msgAny], fee, memo); + assertIsBroadcastTxSuccess(result); + }); + + it("works with a modifying signer", async () => { + pendingWithoutWasmd(); + const wallet = await ModifyingSecp256k1HdWallet.fromMnemonic(alice.mnemonic, undefined, "wasm"); + const options = { prefix: wasmd.prefix }; + const client = await SigningCosmWasmClient.connectWithSigner(wasmd.endpoint, wallet, options); + + const msg = { + delegatorAddress: alice.address0, + validatorAddress: validator.validatorAddress, + amount: coin(1234, "ustake"), + }; + const msgAny = { + typeUrl: msgDelegateTypeUrl, + value: msg, + }; + const fee = { + amount: coins(2000, "ucosm"), + gas: "200000", + }; + const memo = "Use your power wisely"; + const result = await client.signAndBroadcast(alice.address0, [msgAny], fee, memo); + assertIsBroadcastTxSuccess(result); + + await sleep(1000); + + const searchResult = await client.getTx(result.transactionHash); + assert(searchResult, "Must find transaction"); + const tx = Tx.decode(searchResult.tx); + // From ModifyingSecp256k1HdWallet + expect(tx.body!.memo).toEqual("This was modified"); + expect({ ...tx.authInfo!.fee!.amount![0] }).toEqual(coin(3000, "ucosm")); + expect(tx.authInfo!.fee!.gasLimit!.toNumber()).toEqual(333333); + }); + }); }); }); diff --git a/packages/cosmwasm-stargate/src/signingcosmwasmclient.ts b/packages/cosmwasm-stargate/src/signingcosmwasmclient.ts index fff66281..1d23f12c 100644 --- a/packages/cosmwasm-stargate/src/signingcosmwasmclient.ts +++ b/packages/cosmwasm-stargate/src/signingcosmwasmclient.ts @@ -21,18 +21,21 @@ import { GasLimits, GasPrice, logs, + makeSignDoc as makeSignDocAmino, StdFee, } from "@cosmjs/launchpad"; import { Int53, Uint53 } from "@cosmjs/math"; import { EncodeObject, encodePubkey, + isOfflineDirectSigner, makeAuthInfoBytes, makeSignDoc, - OfflineDirectSigner, + OfflineSigner, Registry, } from "@cosmjs/proto-signing"; import { + AminoTypes, BroadcastTxFailure, BroadcastTxResponse, codec, @@ -43,10 +46,20 @@ import { adaptor34, Client as TendermintClient } from "@cosmjs/tendermint-rpc"; import Long from "long"; import pako from "pako"; +import { cosmWasmTypes } from "./aminotypes"; import { cosmwasm } from "./codec"; import { CosmWasmClient } from "./cosmwasmclient"; +const { SignMode } = codec.cosmos.tx.signing.v1beta1; const { TxRaw } = codec.cosmos.tx.v1beta1; +const { MsgMultiSend } = codec.cosmos.bank.v1beta1; +const { + MsgBeginRedelegate, + MsgCreateValidator, + MsgDelegate, + MsgEditValidator, + MsgUndelegate, +} = codec.cosmos.staking.v1beta1; const { MsgClearAdmin, MsgExecuteContract, @@ -81,6 +94,12 @@ function createBroadcastTxErrorMessage(result: BroadcastTxFailure): string { function createDefaultRegistry(): Registry { return new Registry([ + ["/cosmos.bank.v1beta1.MsgMultiSend", MsgMultiSend], + ["/cosmos.staking.v1beta1.MsgBeginRedelegate", MsgBeginRedelegate], + ["/cosmos.staking.v1beta1.MsgCreateValidator", MsgCreateValidator], + ["/cosmos.staking.v1beta1.MsgDelegate", MsgDelegate], + ["/cosmos.staking.v1beta1.MsgEditValidator", MsgEditValidator], + ["/cosmos.staking.v1beta1.MsgUndelegate", MsgUndelegate], ["/cosmwasm.wasm.v1beta1.MsgClearAdmin", MsgClearAdmin], ["/cosmwasm.wasm.v1beta1.MsgExecuteContract", MsgExecuteContract], ["/cosmwasm.wasm.v1beta1.MsgMigrateContract", MsgMigrateContract], @@ -92,6 +111,8 @@ function createDefaultRegistry(): Registry { export interface SigningCosmWasmClientOptions { readonly registry?: Registry; + readonly aminoTypes?: AminoTypes; + readonly prefix?: string; readonly gasPrice?: GasPrice; readonly gasLimits?: GasLimits; } @@ -99,16 +120,18 @@ export interface SigningCosmWasmClientOptions { /** Use for testing only */ export interface PrivateSigningCosmWasmClient { readonly fees: CosmWasmFeeTable; + readonly registry: Registry; } export class SigningCosmWasmClient extends CosmWasmClient { private readonly fees: CosmosFeeTable; private readonly registry: Registry; - private readonly signer: OfflineDirectSigner; + private readonly signer: OfflineSigner; + private readonly aminoTypes: AminoTypes; public static async connectWithSigner( endpoint: string, - signer: OfflineDirectSigner, + signer: OfflineSigner, options: SigningCosmWasmClientOptions = {}, ): Promise { const tmClient = await TendermintClient.connect(endpoint, adaptor34); @@ -117,17 +140,19 @@ export class SigningCosmWasmClient extends CosmWasmClient { private constructor( tmClient: TendermintClient, - signer: OfflineDirectSigner, + signer: OfflineSigner, options: SigningCosmWasmClientOptions, ) { super(tmClient); const { registry = createDefaultRegistry(), + aminoTypes = new AminoTypes({ additions: cosmWasmTypes, prefix: options.prefix }), gasPrice = defaultGasPrice, gasLimits = defaultGasLimits, } = options; this.fees = buildFeeTable(gasPrice, defaultGasLimits, gasLimits); this.registry = registry; + this.aminoTypes = aminoTypes; this.signer = signer; } @@ -355,12 +380,44 @@ export class SigningCosmWasmClient extends CosmWasmClient { }); const gasLimit = Int53.fromString(fee.gas).toNumber(); - const authInfoBytes = makeAuthInfoBytes([pubkeyAny], fee.amount, gasLimit, sequence); - const signDoc = makeSignDoc(txBodyBytes, authInfoBytes, chainId, accountNumber); - const { signature, signed } = await this.signer.signDirect(signerAddress, signDoc); + if (isOfflineDirectSigner(this.signer)) { + const authInfoBytes = makeAuthInfoBytes([pubkeyAny], fee.amount, gasLimit, sequence); + const signDoc = makeSignDoc(txBodyBytes, authInfoBytes, chainId, accountNumber); + const { signature, signed } = await this.signer.signDirect(signerAddress, signDoc); + const txRaw = TxRaw.create({ + bodyBytes: signed.bodyBytes, + authInfoBytes: signed.authInfoBytes, + signatures: [fromBase64(signature.signature)], + }); + const signedTx = Uint8Array.from(TxRaw.encode(txRaw).finish()); + return this.broadcastTx(signedTx); + } + + // Amino signer + const signMode = SignMode.SIGN_MODE_LEGACY_AMINO_JSON; + const msgs = messages.map((msg) => this.aminoTypes.toAmino(msg)); + const signDoc = makeSignDocAmino(msgs, fee, chainId, memo, accountNumber, sequence); + const { signature, signed } = await this.signer.signAmino(signerAddress, signDoc); + const signedTxBody = { + messages: signed.msgs.map((msg) => this.aminoTypes.fromAmino(msg)), + memo: signed.memo, + }; + const signedTxBodyBytes = this.registry.encode({ + typeUrl: "/cosmos.tx.v1beta1.TxBody", + value: signedTxBody, + }); + const signedGasLimit = Int53.fromString(signed.fee.gas).toNumber(); + const signedSequence = Int53.fromString(signed.sequence).toNumber(); + const signedAuthInfoBytes = makeAuthInfoBytes( + [pubkeyAny], + signed.fee.amount, + signedGasLimit, + signedSequence, + signMode, + ); const txRaw = TxRaw.create({ - bodyBytes: signed.bodyBytes, - authInfoBytes: signed.authInfoBytes, + bodyBytes: signedTxBodyBytes, + authInfoBytes: signedAuthInfoBytes, signatures: [fromBase64(signature.signature)], }); const signedTx = Uint8Array.from(TxRaw.encode(txRaw).finish()); diff --git a/packages/cosmwasm-stargate/src/testutils.spec.ts b/packages/cosmwasm-stargate/src/testutils.spec.ts index 5f778c73..e29980f8 100644 --- a/packages/cosmwasm-stargate/src/testutils.spec.ts +++ b/packages/cosmwasm-stargate/src/testutils.spec.ts @@ -1,7 +1,13 @@ /* eslint-disable @typescript-eslint/naming-convention */ import { Bip39, EnglishMnemonic, Random, Secp256k1, Slip10, Slip10Curve } from "@cosmjs/crypto"; import { Bech32, fromBase64 } from "@cosmjs/encoding"; -import { coins, makeCosmoshubPath } from "@cosmjs/launchpad"; +import { + AminoSignResponse, + coins, + makeCosmoshubPath, + Secp256k1HdWallet, + StdSignDoc, +} from "@cosmjs/launchpad"; import { DirectSecp256k1HdWallet, DirectSignResponse, makeAuthInfoBytes } from "@cosmjs/proto-signing"; import { AuthExtension, @@ -205,6 +211,41 @@ export async function makeWasmClient( return QueryClient.withExtensions(tmClient, setupAuthExtension, setupBankExtension, setupWasmExtension); } +/** + * A class for testing clients using an Amino signer which modifies the transaction it receives before signing + */ +export class ModifyingSecp256k1HdWallet extends Secp256k1HdWallet { + public static async fromMnemonic( + mnemonic: string, + hdPath = makeCosmoshubPath(0), + prefix = "cosmos", + ): Promise { + const mnemonicChecked = new EnglishMnemonic(mnemonic); + const seed = await Bip39.mnemonicToSeed(mnemonicChecked); + const { privkey } = Slip10.derivePath(Slip10Curve.Secp256k1, seed, hdPath); + const uncompressed = (await Secp256k1.makeKeypair(privkey)).pubkey; + return new ModifyingSecp256k1HdWallet( + mnemonicChecked, + hdPath, + privkey, + Secp256k1.compressPubkey(uncompressed), + prefix, + ); + } + + public async signAmino(signerAddress: string, signDoc: StdSignDoc): Promise { + const modifiedSignDoc = { + ...signDoc, + fee: { + amount: coins(3000, "ucosm"), + gas: "333333", + }, + memo: "This was modified", + }; + return super.signAmino(signerAddress, modifiedSignDoc); + } +} + /** * A class for testing clients using a direct signer which modifies the transaction it receives before signing */ diff --git a/packages/cosmwasm-stargate/types/aminotypes.d.ts b/packages/cosmwasm-stargate/types/aminotypes.d.ts new file mode 100644 index 00000000..413e64a2 --- /dev/null +++ b/packages/cosmwasm-stargate/types/aminotypes.d.ts @@ -0,0 +1,2 @@ +import { AminoConverter } from "@cosmjs/stargate"; +export declare const cosmWasmTypes: Record; diff --git a/packages/cosmwasm-stargate/types/signingcosmwasmclient.d.ts b/packages/cosmwasm-stargate/types/signingcosmwasmclient.d.ts index 41f160bc..c6d66c4e 100644 --- a/packages/cosmwasm-stargate/types/signingcosmwasmclient.d.ts +++ b/packages/cosmwasm-stargate/types/signingcosmwasmclient.d.ts @@ -9,25 +9,29 @@ import { UploadResult, } from "@cosmjs/cosmwasm-launchpad"; import { Coin, CosmosFeeTable, GasLimits, GasPrice, StdFee } from "@cosmjs/launchpad"; -import { EncodeObject, OfflineDirectSigner, Registry } from "@cosmjs/proto-signing"; -import { BroadcastTxResponse } from "@cosmjs/stargate"; +import { EncodeObject, OfflineSigner, Registry } from "@cosmjs/proto-signing"; +import { AminoTypes, BroadcastTxResponse } from "@cosmjs/stargate"; import { CosmWasmClient } from "./cosmwasmclient"; export interface SigningCosmWasmClientOptions { readonly registry?: Registry; + readonly aminoTypes?: AminoTypes; + readonly prefix?: string; readonly gasPrice?: GasPrice; readonly gasLimits?: GasLimits; } /** Use for testing only */ export interface PrivateSigningCosmWasmClient { readonly fees: CosmWasmFeeTable; + readonly registry: Registry; } export declare class SigningCosmWasmClient extends CosmWasmClient { private readonly fees; private readonly registry; private readonly signer; + private readonly aminoTypes; static connectWithSigner( endpoint: string, - signer: OfflineDirectSigner, + signer: OfflineSigner, options?: SigningCosmWasmClientOptions, ): Promise; private constructor(); diff --git a/packages/stargate/src/aminotypes.ts b/packages/stargate/src/aminotypes.ts index d4a7e194..8d424d72 100644 --- a/packages/stargate/src/aminotypes.ts +++ b/packages/stargate/src/aminotypes.ts @@ -1,7 +1,6 @@ /* eslint-disable @typescript-eslint/naming-convention */ import { fromBase64, toBase64 } from "@cosmjs/encoding"; import { - Coin, decodeBech32Pubkey, encodeBech32Pubkey, Msg, @@ -14,11 +13,11 @@ import { MsgUndelegate, } from "@cosmjs/launchpad"; import { EncodeObject } from "@cosmjs/proto-signing"; -import { assert } from "@cosmjs/utils"; +import { assertDefinedAndNotNull } from "@cosmjs/utils"; import { cosmos } from "./codec"; +import { coinFromProto } from "./stargateclient"; -type ICoin = cosmos.base.v1beta1.ICoin; type IMsgSend = cosmos.bank.v1beta1.IMsgSend; type IMsgMultiSend = cosmos.bank.v1beta1.IMsgMultiSend; type IMsgBeginRedelegate = cosmos.staking.v1beta1.IMsgBeginRedelegate; @@ -33,29 +32,18 @@ export interface AminoConverter { readonly fromAmino: (value: any) => any; } -function checkAmount(amount: readonly ICoin[] | undefined | null): readonly Coin[] { - assert(amount, "missing amount"); - return amount.map((a) => { - assert(a.amount, "missing amount"); - assert(a.denom, "missing denom"); - return { - amount: a.amount, - denom: a.denom, - }; - }); -} - function createDefaultTypes(prefix: string): Record { return { "/cosmos.bank.v1beta1.MsgSend": { aminoType: "cosmos-sdk/MsgSend", toAmino: ({ fromAddress, toAddress, amount }: IMsgSend): MsgSend["value"] => { - assert(fromAddress, "missing fromAddress"); - assert(toAddress, "missing toAddress"); + assertDefinedAndNotNull(fromAddress, "missing fromAddress"); + assertDefinedAndNotNull(toAddress, "missing toAddress"); + assertDefinedAndNotNull(amount, "missing amount"); return { from_address: fromAddress, to_address: toAddress, - amount: checkAmount(amount), + amount: amount.map(coinFromProto), }; }, fromAmino: ({ from_address, to_address, amount }: MsgSend["value"]): IMsgSend => ({ @@ -67,21 +55,23 @@ function createDefaultTypes(prefix: string): Record { "/cosmos.bank.v1beta1.MsgMultiSend": { aminoType: "cosmos-sdk/MsgMultiSend", toAmino: ({ inputs, outputs }: IMsgMultiSend): MsgMultiSend["value"] => { - assert(inputs, "missing inputs"); - assert(outputs, "missing outputs"); + assertDefinedAndNotNull(inputs, "missing inputs"); + assertDefinedAndNotNull(outputs, "missing outputs"); return { inputs: inputs.map((input) => { - assert(input.address, "missing input.address"); + assertDefinedAndNotNull(input.address, "missing input.address"); + assertDefinedAndNotNull(input.coins, "missing input.amount"); return { address: input.address, - coins: checkAmount(input.coins), + coins: input.coins.map(coinFromProto), }; }), outputs: outputs.map((output) => { - assert(output.address, "missing output.address"); + assertDefinedAndNotNull(output.address, "missing output.address"); + assertDefinedAndNotNull(output.coins, "missing output.coins"); return { address: output.address, - coins: checkAmount(output.coins), + coins: output.coins.map(coinFromProto), }; }), }; @@ -105,20 +95,15 @@ function createDefaultTypes(prefix: string): Record { validatorDstAddress, amount, }: IMsgBeginRedelegate): MsgBeginRedelegate["value"] => { - assert(delegatorAddress, "missing delegatorAddress"); - assert(validatorSrcAddress, "missing validatorSrcAddress"); - assert(validatorDstAddress, "missing validatorDstAddress"); - assert(amount, "missing amount"); - assert(amount.amount, "missing amount.amount"); - assert(amount.denom, "missing amount.denom"); + assertDefinedAndNotNull(delegatorAddress, "missing delegatorAddress"); + assertDefinedAndNotNull(validatorSrcAddress, "missing validatorSrcAddress"); + assertDefinedAndNotNull(validatorDstAddress, "missing validatorDstAddress"); + assertDefinedAndNotNull(amount, "missing amount"); return { delegator_address: delegatorAddress, validator_src_address: validatorSrcAddress, validator_dst_address: validatorDstAddress, - amount: { - amount: amount.amount, - denom: amount.denom, - }, + amount: coinFromProto(amount), }; }, fromAmino: ({ @@ -144,24 +129,22 @@ function createDefaultTypes(prefix: string): Record { pubkey, value, }: IMsgCreateValidator): MsgCreateValidator["value"] => { - assert(description, "missing description"); - assert(description.moniker, "missing description.moniker"); - assert(description.identity, "missing description.identity"); - assert(description.website, "missing description.website"); - assert(description.securityContact, "missing description.securityContact"); - assert(description.details, "missing description.details"); - assert(commission, "missing commission"); - assert(commission.rate, "missing commission.rate"); - assert(commission.maxRate, "missing commission.maxRate"); - assert(commission.maxChangeRate, "missing commission.maxChangeRate"); - assert(minSelfDelegation, "missing minSelfDelegation"); - assert(delegatorAddress, "missing delegatorAddress"); - assert(validatorAddress, "missing validatorAddress"); - assert(pubkey, "missing pubkey"); - assert(pubkey.value, "missing pubkey.value"); - assert(value, "missing value"); - assert(value.amount, "missing value.amount"); - assert(value.denom, "missing value.denom"); + assertDefinedAndNotNull(description, "missing description"); + assertDefinedAndNotNull(description.moniker, "missing description.moniker"); + assertDefinedAndNotNull(description.identity, "missing description.identity"); + assertDefinedAndNotNull(description.website, "missing description.website"); + assertDefinedAndNotNull(description.securityContact, "missing description.securityContact"); + assertDefinedAndNotNull(description.details, "missing description.details"); + assertDefinedAndNotNull(commission, "missing commission"); + assertDefinedAndNotNull(commission.rate, "missing commission.rate"); + assertDefinedAndNotNull(commission.maxRate, "missing commission.maxRate"); + assertDefinedAndNotNull(commission.maxChangeRate, "missing commission.maxChangeRate"); + assertDefinedAndNotNull(minSelfDelegation, "missing minSelfDelegation"); + assertDefinedAndNotNull(delegatorAddress, "missing delegatorAddress"); + assertDefinedAndNotNull(validatorAddress, "missing validatorAddress"); + assertDefinedAndNotNull(pubkey, "missing pubkey"); + assertDefinedAndNotNull(pubkey.value, "missing pubkey.value"); + assertDefinedAndNotNull(value, "missing value"); return { description: { moniker: description.moniker, @@ -185,10 +168,7 @@ function createDefaultTypes(prefix: string): Record { }, prefix, ), - value: { - amount: value.amount, - denom: value.denom, - }, + value: coinFromProto(value), }; }, fromAmino: ({ @@ -231,18 +211,13 @@ function createDefaultTypes(prefix: string): Record { "/cosmos.staking.v1beta1.MsgDelegate": { aminoType: "cosmos-sdk/MsgDelegate", toAmino: ({ delegatorAddress, validatorAddress, amount }: IMsgDelegate): MsgDelegate["value"] => { - assert(delegatorAddress, "missing delegatorAddress"); - assert(validatorAddress, "missing validatorAddress"); - assert(amount, "missing amount"); - assert(amount.amount, "missing amount.amount"); - assert(amount.denom, "missing amount.denom"); + assertDefinedAndNotNull(delegatorAddress, "missing delegatorAddress"); + assertDefinedAndNotNull(validatorAddress, "missing validatorAddress"); + assertDefinedAndNotNull(amount, "missing amount"); return { delegator_address: delegatorAddress, validator_address: validatorAddress, - amount: { - amount: amount.amount, - denom: amount.denom, - }, + amount: coinFromProto(amount), }; }, fromAmino: ({ delegator_address, validator_address, amount }: MsgDelegate["value"]): IMsgDelegate => ({ @@ -259,15 +234,15 @@ function createDefaultTypes(prefix: string): Record { minSelfDelegation, validatorAddress, }: IMsgEditValidator): MsgEditValidator["value"] => { - assert(description, "missing description"); - assert(description.moniker, "missing description.moniker"); - assert(description.identity, "missing description.identity"); - assert(description.website, "missing description.website"); - assert(description.securityContact, "missing description.securityContact"); - assert(description.details, "missing description.details"); - assert(commissionRate, "missing commissionRate"); - assert(minSelfDelegation, "missing minSelfDelegation"); - assert(validatorAddress, "missing validatorAddress"); + assertDefinedAndNotNull(description, "missing description"); + assertDefinedAndNotNull(description.moniker, "missing description.moniker"); + assertDefinedAndNotNull(description.identity, "missing description.identity"); + assertDefinedAndNotNull(description.website, "missing description.website"); + assertDefinedAndNotNull(description.securityContact, "missing description.securityContact"); + assertDefinedAndNotNull(description.details, "missing description.details"); + assertDefinedAndNotNull(commissionRate, "missing commissionRate"); + assertDefinedAndNotNull(minSelfDelegation, "missing minSelfDelegation"); + assertDefinedAndNotNull(validatorAddress, "missing validatorAddress"); return { description: { moniker: description.moniker, @@ -302,18 +277,13 @@ function createDefaultTypes(prefix: string): Record { "/cosmos.staking.v1beta1.MsgUndelegate": { aminoType: "cosmos-sdk/MsgUndelegate", toAmino: ({ delegatorAddress, validatorAddress, amount }: IMsgUndelegate): MsgUndelegate["value"] => { - assert(delegatorAddress, "missing delegatorAddress"); - assert(validatorAddress, "missing validatorAddress"); - assert(amount, "missing amount"); - assert(amount.amount, "missing amount.amount"); - assert(amount.denom, "missing amount.denom"); + assertDefinedAndNotNull(delegatorAddress, "missing delegatorAddress"); + assertDefinedAndNotNull(validatorAddress, "missing validatorAddress"); + assertDefinedAndNotNull(amount, "missing amount"); return { delegator_address: delegatorAddress, validator_address: validatorAddress, - amount: { - amount: amount.amount, - denom: amount.denom, - }, + amount: coinFromProto(amount), }; }, fromAmino: ({ diff --git a/packages/stargate/src/index.ts b/packages/stargate/src/index.ts index 4254360a..a278448a 100644 --- a/packages/stargate/src/index.ts +++ b/packages/stargate/src/index.ts @@ -1,5 +1,5 @@ export * as codec from "./codec"; -export { AminoTypes } from "./aminotypes"; +export { AminoConverter, AminoTypes } from "./aminotypes"; export { parseRawLog } from "./logs"; export { AuthExtension, diff --git a/packages/stargate/src/queries/queryclient.ts b/packages/stargate/src/queries/queryclient.ts index 31cf9ef1..f9d636cd 100644 --- a/packages/stargate/src/queries/queryclient.ts +++ b/packages/stargate/src/queries/queryclient.ts @@ -3,7 +3,7 @@ import { iavlSpec, ics23, tendermintSpec, verifyExistence, verifyNonExistence } import { toAscii, toHex } from "@cosmjs/encoding"; import { firstEvent } from "@cosmjs/stream"; import { Client as TendermintClient, Header, NewBlockHeaderEvent, ProofOp } from "@cosmjs/tendermint-rpc"; -import { arrayContentEquals, assert, assertDefined, isNonNullObject, sleep } from "@cosmjs/utils"; +import { arrayContentEquals, assert, assertDefinedAndNotNull, isNonNullObject, sleep } from "@cosmjs/utils"; import { Stream } from "xstream"; type QueryExtensionSetup

= (base: QueryClient) => P; @@ -233,7 +233,7 @@ export class QueryClient { // this must return the header for height+1 // throws an error if height is 0 or undefined private async getNextHeader(height?: number): Promise

{ - assertDefined(height); + assertDefinedAndNotNull(height); if (height === 0) { throw new Error("Query returned height 0, cannot prove it"); } diff --git a/packages/stargate/src/signingstargateclient.spec.ts b/packages/stargate/src/signingstargateclient.spec.ts index 921ea5cd..3753be49 100644 --- a/packages/stargate/src/signingstargateclient.spec.ts +++ b/packages/stargate/src/signingstargateclient.spec.ts @@ -24,6 +24,9 @@ import { validator, } from "./testutils.spec"; +type IMsgSend = cosmos.bank.v1beta1.IMsgSend; +type IMsgDelegate = cosmos.staking.v1beta1.IMsgDelegate; + const { MsgSend } = cosmos.bank.v1beta1; const { MsgDelegate } = cosmos.staking.v1beta1; const { Tx } = cosmos.tx.v1beta1; @@ -126,7 +129,7 @@ describe("SigningStargateClient", () => { }); describe("sendTokens", () => { - it("works", async () => { + it("works with direct signer", async () => { pendingWithoutSimapp(); const wallet = await DirectSecp256k1HdWallet.fromMnemonic(faucet.mnemonic); const client = await SigningStargateClient.connectWithSigner(simapp.tendermintUrl, wallet); @@ -149,6 +152,30 @@ describe("SigningStargateClient", () => { assert(after); expect(after).toEqual(transferAmount[0]); }); + + it("works with legacy Amino signer", async () => { + pendingWithoutSimapp(); + const wallet = await Secp256k1HdWallet.fromMnemonic(faucet.mnemonic); + const client = await SigningStargateClient.connectWithSigner(simapp.tendermintUrl, wallet); + + const transferAmount = coins(7890, "ucosm"); + const beneficiaryAddress = makeRandomAddress(); + const memo = "for dinner"; + + // no tokens here + const before = await client.getBalance(beneficiaryAddress, "ucosm"); + expect(before).toBeNull(); + + // send + const result = await client.sendTokens(faucet.address0, beneficiaryAddress, transferAmount, memo); + assertIsBroadcastTxSuccess(result); + expect(result.rawLog).toBeTruthy(); + + // got tokens + const after = await client.getBalance(beneficiaryAddress, "ucosm"); + assert(after); + expect(after).toEqual(transferAmount[0]); + }); }); describe("signAndBroadcast", () => { @@ -235,7 +262,7 @@ describe("SigningStargateClient", () => { const wallet = await Secp256k1HdWallet.fromMnemonic(faucet.mnemonic); const client = await SigningStargateClient.connectWithSigner(simapp.tendermintUrl, wallet); - const msgSend = { + const msgSend: IMsgSend = { fromAddress: faucet.address0, toAddress: makeRandomAddress(), amount: coins(1234, "ucosm"), @@ -258,7 +285,7 @@ describe("SigningStargateClient", () => { const wallet = await Secp256k1HdWallet.fromMnemonic(faucet.mnemonic); const client = await SigningStargateClient.connectWithSigner(simapp.tendermintUrl, wallet); - const msgDelegate = { + const msgDelegate: IMsgDelegate = { delegatorAddress: faucet.address0, validatorAddress: validator.validatorAddress, amount: coin(1234, "ustake"), diff --git a/packages/stargate/src/signingstargateclient.ts b/packages/stargate/src/signingstargateclient.ts index 263446f8..6a6d8f49 100644 --- a/packages/stargate/src/signingstargateclient.ts +++ b/packages/stargate/src/signingstargateclient.ts @@ -40,6 +40,17 @@ const { TxRaw } = cosmos.tx.v1beta1; const defaultGasPrice = GasPrice.fromString("0.025ucosm"); const defaultGasLimits: GasLimits = { send: 80000 }; +function createDefaultRegistry(): Registry { + return new Registry([ + ["/cosmos.bank.v1beta1.MsgMultiSend", MsgMultiSend], + ["/cosmos.staking.v1beta1.MsgBeginRedelegate", MsgBeginRedelegate], + ["/cosmos.staking.v1beta1.MsgCreateValidator", MsgCreateValidator], + ["/cosmos.staking.v1beta1.MsgDelegate", MsgDelegate], + ["/cosmos.staking.v1beta1.MsgEditValidator", MsgEditValidator], + ["/cosmos.staking.v1beta1.MsgUndelegate", MsgUndelegate], + ]); +} + /** Use for testing only */ export interface PrivateSigningStargateClient { readonly fees: CosmosFeeTable; @@ -49,6 +60,7 @@ export interface PrivateSigningStargateClient { export interface SigningStargateClientOptions { readonly registry?: Registry; readonly aminoTypes?: AminoTypes; + readonly prefix?: string; readonly gasPrice?: GasPrice; readonly gasLimits?: GasLimits; } @@ -57,7 +69,7 @@ export class SigningStargateClient extends StargateClient { private readonly fees: CosmosFeeTable; private readonly registry: Registry; private readonly signer: OfflineSigner; - private readonly aminoTypes; + private readonly aminoTypes: AminoTypes; public static async connectWithSigner( endpoint: string, @@ -75,15 +87,8 @@ export class SigningStargateClient extends StargateClient { ) { super(tmClient); const { - registry = new Registry([ - ["/cosmos.bank.v1beta1.MsgMultiSend", MsgMultiSend], - ["/cosmos.staking.v1beta1.MsgBeginRedelegate", MsgBeginRedelegate], - ["/cosmos.staking.v1beta1.MsgCreateValidator", MsgCreateValidator], - ["/cosmos.staking.v1beta1.MsgDelegate", MsgDelegate], - ["/cosmos.staking.v1beta1.MsgEditValidator", MsgEditValidator], - ["/cosmos.staking.v1beta1.MsgUndelegate", MsgUndelegate], - ]), - aminoTypes = new AminoTypes(), + registry = createDefaultRegistry(), + aminoTypes = new AminoTypes({ prefix: options.prefix }), gasPrice = defaultGasPrice, gasLimits = defaultGasLimits, } = options; @@ -111,19 +116,19 @@ export class SigningStargateClient extends StargateClient { } public async signAndBroadcast( - address: string, + signerAddress: string, messages: readonly EncodeObject[], fee: StdFee, memo = "", ): Promise { const accountFromSigner = (await this.signer.getAccounts()).find( - (account: AccountData) => account.address === address, + (account: AccountData) => account.address === signerAddress, ); if (!accountFromSigner) { throw new Error("Failed to retrieve account from signer"); } const pubkey = encodeSecp256k1Pubkey(accountFromSigner.pubkey); - const accountFromChain = await this.getAccount(address); + const accountFromChain = await this.getAccount(signerAddress); if (!accountFromChain) { throw new Error("Account not found"); } @@ -146,7 +151,7 @@ export class SigningStargateClient extends StargateClient { if (isOfflineDirectSigner(this.signer)) { const authInfoBytes = makeAuthInfoBytes([pubkeyAny], fee.amount, gasLimit, sequence); const signDoc = makeSignDoc(txBodyBytes, authInfoBytes, chainId, accountNumber); - const { signature, signed } = await this.signer.signDirect(address, signDoc); + const { signature, signed } = await this.signer.signDirect(signerAddress, signDoc); const txRaw = TxRaw.create({ bodyBytes: signed.bodyBytes, authInfoBytes: signed.authInfoBytes, @@ -160,7 +165,7 @@ export class SigningStargateClient extends StargateClient { const signMode = cosmos.tx.signing.v1beta1.SignMode.SIGN_MODE_LEGACY_AMINO_JSON; const msgs = messages.map((msg) => this.aminoTypes.toAmino(msg)); const signDoc = makeSignDocAmino(msgs, fee, chainId, memo, accountNumber, sequence); - const { signature, signed } = await this.signer.signAmino(address, signDoc); + const { signature, signed } = await this.signer.signAmino(signerAddress, signDoc); const signedTxBody = { messages: signed.msgs.map((msg) => this.aminoTypes.fromAmino(msg)), memo: signed.memo, diff --git a/packages/stargate/src/stargateclient.ts b/packages/stargate/src/stargateclient.ts index 9d2304bf..488ff285 100644 --- a/packages/stargate/src/stargateclient.ts +++ b/packages/stargate/src/stargateclient.ts @@ -18,7 +18,7 @@ import { Client as TendermintClient, DateTime, } from "@cosmjs/tendermint-rpc"; -import { assert, assertDefined } from "@cosmjs/utils"; +import { assert, assertDefinedAndNotNull } from "@cosmjs/utils"; import Long from "long"; import { cosmos } from "./codec"; @@ -110,10 +110,8 @@ export function accountFromProto(input: IBaseAccount): Account { } export function coinFromProto(input: ICoin): Coin { - assertDefined(input.amount); - assertDefined(input.denom); - assert(input.amount !== null); - assert(input.denom !== null); + assertDefinedAndNotNull(input.amount); + assertDefinedAndNotNull(input.denom); return { amount: input.amount, denom: input.denom, diff --git a/packages/stargate/types/index.d.ts b/packages/stargate/types/index.d.ts index 4254360a..a278448a 100644 --- a/packages/stargate/types/index.d.ts +++ b/packages/stargate/types/index.d.ts @@ -1,5 +1,5 @@ export * as codec from "./codec"; -export { AminoTypes } from "./aminotypes"; +export { AminoConverter, AminoTypes } from "./aminotypes"; export { parseRawLog } from "./logs"; export { AuthExtension, diff --git a/packages/stargate/types/signingstargateclient.d.ts b/packages/stargate/types/signingstargateclient.d.ts index ea901c42..58ae7ffa 100644 --- a/packages/stargate/types/signingstargateclient.d.ts +++ b/packages/stargate/types/signingstargateclient.d.ts @@ -10,6 +10,7 @@ export interface PrivateSigningStargateClient { export interface SigningStargateClientOptions { readonly registry?: Registry; readonly aminoTypes?: AminoTypes; + readonly prefix?: string; readonly gasPrice?: GasPrice; readonly gasLimits?: GasLimits; } @@ -31,7 +32,7 @@ export declare class SigningStargateClient extends StargateClient { memo?: string, ): Promise; signAndBroadcast( - address: string, + signerAddress: string, messages: readonly EncodeObject[], fee: StdFee, memo?: string, diff --git a/packages/utils/src/assert.spec.ts b/packages/utils/src/assert.spec.ts index 4b6374cf..d22b5c16 100644 --- a/packages/utils/src/assert.spec.ts +++ b/packages/utils/src/assert.spec.ts @@ -1,52 +1,54 @@ -import { assertDefined } from "./assert"; +import { assertDefinedAndNotNull } from "./assert"; describe("assert", () => { - describe("assertDefined", () => { + describe("assertDefinedAndNotNull", () => { it("passes for simple values", () => { { - const value: number | undefined = 123; - assertDefined(value); + const value: number | undefined | null = 123; + assertDefinedAndNotNull(value); expect(value).toEqual(123); } { - const value: string | undefined = "abc"; - assertDefined(value); + const value: string | undefined | null = "abc"; + assertDefinedAndNotNull(value); expect(value).toEqual("abc"); } }); it("passes for falsy values", () => { { - const value: number | undefined = 0; - assertDefined(value); + const value: number | undefined | null = 0; + assertDefinedAndNotNull(value); expect(value).toEqual(0); } { - const value: string | undefined = ""; - assertDefined(value); + const value: string | undefined | null = ""; + assertDefinedAndNotNull(value); expect(value).toEqual(""); } - { - const value: null | undefined = null; - assertDefined(value); - expect(value).toEqual(null); - } }); it("throws for undefined values", () => { { - const value: number | undefined = undefined; - expect(() => assertDefined(value)).toThrowError("value is undefined"); + const value: number | undefined | null = undefined; + expect(() => assertDefinedAndNotNull(value)).toThrowError("value is undefined or null"); } { - let value: string | undefined; - expect(() => assertDefined(value)).toThrowError("value is undefined"); + let value: string | undefined | null; + expect(() => assertDefinedAndNotNull(value)).toThrowError("value is undefined or null"); } }); + it("throws for null values", () => { + const value: number | undefined | null = null; + expect(() => assertDefinedAndNotNull(value)).toThrowError("value is undefined or null"); + }); + it("throws with custom message", () => { const value: number | undefined = undefined; - expect(() => assertDefined(value, "Bug in the data source")).toThrowError("Bug in the data source"); + expect(() => assertDefinedAndNotNull(value, "Bug in the data source")).toThrowError( + "Bug in the data source", + ); }); }); }); diff --git a/packages/utils/src/assert.ts b/packages/utils/src/assert.ts index 3c86b353..14f37517 100644 --- a/packages/utils/src/assert.ts +++ b/packages/utils/src/assert.ts @@ -5,8 +5,8 @@ export function assert(condition: any, msg?: string): asserts condition { } } -export function assertDefined(value: T | undefined, msg?: string): asserts value is T { - if (value === undefined) { - throw new Error(msg || "value is undefined"); +export function assertDefinedAndNotNull(value: T | undefined | null, msg?: string): asserts value is T { + if (value === undefined || value === null) { + throw new Error(msg ?? "value is undefined or null"); } } diff --git a/packages/utils/src/index.ts b/packages/utils/src/index.ts index 391b2471..596d63db 100644 --- a/packages/utils/src/index.ts +++ b/packages/utils/src/index.ts @@ -1,4 +1,4 @@ export { arrayContentEquals } from "./arrays"; -export { assert, assertDefined } from "./assert"; +export { assert, assertDefinedAndNotNull } from "./assert"; export { sleep } from "./sleep"; export { isNonNullObject, isUint8Array } from "./typechecks"; diff --git a/packages/utils/types/assert.d.ts b/packages/utils/types/assert.d.ts index 385fadf1..65f3dfdb 100644 --- a/packages/utils/types/assert.d.ts +++ b/packages/utils/types/assert.d.ts @@ -1,2 +1,5 @@ export declare function assert(condition: any, msg?: string): asserts condition; -export declare function assertDefined(value: T | undefined, msg?: string): asserts value is T; +export declare function assertDefinedAndNotNull( + value: T | undefined | null, + msg?: string, +): asserts value is T; diff --git a/packages/utils/types/index.d.ts b/packages/utils/types/index.d.ts index 391b2471..596d63db 100644 --- a/packages/utils/types/index.d.ts +++ b/packages/utils/types/index.d.ts @@ -1,4 +1,4 @@ export { arrayContentEquals } from "./arrays"; -export { assert, assertDefined } from "./assert"; +export { assert, assertDefinedAndNotNull } from "./assert"; export { sleep } from "./sleep"; export { isNonNullObject, isUint8Array } from "./typechecks";