From 79cd8c0109af51f27bb065c47679f11355e75c52 Mon Sep 17 00:00:00 2001 From: willclarktech Date: Tue, 12 Jan 2021 13:02:52 +0000 Subject: [PATCH 1/5] launchpad: Export bank and staking Msg types/checkers --- packages/launchpad/src/index.ts | 18 +++++++++++++++++- packages/launchpad/types/index.d.ts | 18 +++++++++++++++++- 2 files changed, 34 insertions(+), 2 deletions(-) diff --git a/packages/launchpad/src/index.ts b/packages/launchpad/src/index.ts index 644eb610..52990a9d 100644 --- a/packages/launchpad/src/index.ts +++ b/packages/launchpad/src/index.ts @@ -99,7 +99,23 @@ export { uint64ToNumber, uint64ToString, } from "./lcdapi"; -export { isMsgDelegate, isMsgSend, Msg, MsgDelegate, MsgSend } from "./msgs"; +export { + isMsgBeginRedelegate, + isMsgCreateValidator, + isMsgDelegate, + isMsgEditValidator, + isMsgMultiSend, + isMsgSend, + isMsgUndelegate, + Msg, + MsgBeginRedelegate, + MsgCreateValidator, + MsgDelegate, + MsgEditValidator, + MsgMultiSend, + MsgSend, + MsgUndelegate, +} from "./msgs"; export { decodeAminoPubkey, decodeBech32Pubkey, diff --git a/packages/launchpad/types/index.d.ts b/packages/launchpad/types/index.d.ts index 1c8bc2ea..e0a1863f 100644 --- a/packages/launchpad/types/index.d.ts +++ b/packages/launchpad/types/index.d.ts @@ -97,7 +97,23 @@ export { uint64ToNumber, uint64ToString, } from "./lcdapi"; -export { isMsgDelegate, isMsgSend, Msg, MsgDelegate, MsgSend } from "./msgs"; +export { + isMsgBeginRedelegate, + isMsgCreateValidator, + isMsgDelegate, + isMsgEditValidator, + isMsgMultiSend, + isMsgSend, + isMsgUndelegate, + Msg, + MsgBeginRedelegate, + MsgCreateValidator, + MsgDelegate, + MsgEditValidator, + MsgMultiSend, + MsgSend, + MsgUndelegate, +} from "./msgs"; export { decodeAminoPubkey, decodeBech32Pubkey, From 95c292d83cd4db6fe48901a2dd099244795f18b8 Mon Sep 17 00:00:00 2001 From: willclarktech Date: Tue, 12 Jan 2021 16:24:03 +0000 Subject: [PATCH 2/5] stargate: Add conversion for bank/staking Amino Msg types --- packages/stargate/src/aminotypes.ts | 389 ++++++++++++++++++++++-- packages/stargate/types/aminotypes.d.ts | 18 +- 2 files changed, 372 insertions(+), 35 deletions(-) diff --git a/packages/stargate/src/aminotypes.ts b/packages/stargate/src/aminotypes.ts index d8d20707..d4a7e194 100644 --- a/packages/stargate/src/aminotypes.ts +++ b/packages/stargate/src/aminotypes.ts @@ -1,61 +1,386 @@ -const defaultTypes: Record = { - "/cosmos.bank.v1beta1.MsgSend": "cosmos-sdk/MsgSend", - "/cosmos.bank.v1beta1.MsgMultiSend": "cosmos-sdk/MsgMultiSend", - "/cosmos.crisis.v1beta1.MsgVerifyInvariant": "cosmos-sdk/MsgVerifyInvariant", - "/cosmos.distribution.v1beta1.MsgSetWithdrawAddress": "cosmos-sdk/MsgSetWithdrawAddress", - "/cosmos.distribution.v1beta1.MsgWithdrawDelegatorReward": "cosmos-sdk/MsgWithdrawDelegatorReward", - "/cosmos.distribution.v1beta1.MsgWithdrawValidatorComission": "cosmos-sdk/MsgWithdrawValidatorComission", - "/cosmos.distribution.v1beta1.MsgFundCommunityPool": "cosmos-sdk/MsgFundCommunityPool", - "/cosmos.evidence.v1beta1.MsgSubmitEvidence": "cosmos-sdk/MsgSubmitEvidence", - "/cosmos.gov.v1beta1.MsgSubmitProposal": "cosmos-sdk/MsgSubmitProposal", - "/cosmos.gov.v1beta1.MsgVote": "cosmos-sdk/MsgVote", - "/cosmos.gov.v1beta1.MsgDeposit": "cosmos-sdk/MsgDeposit", - "/cosmos.slashing.v1beta1.MsgUnjail": "cosmos-sdk/MsgUnjail", - "/cosmos.staking.v1beta1.MsgCreateValidator": "cosmos-sdk/MsgCreateValidator", - "/cosmos.staking.v1beta1.MsgEditValidator": "cosmos-sdk/MsgEditValidator", - "/cosmos.staking.v1beta1.MsgDelegate": "cosmos-sdk/MsgDelegate", - "/cosmos.staking.v1beta1.MsgBeginRedelegate": "cosmos-sdk/MsgBeginRedelegate", - "/cosmos.staking.v1beta1.MsgUndelegate": "cosmos-sdk/MsgUndelegate", - "/cosmos.vesting.v1beta1.MsgCreateVestingAccount": "cosmos-sdk/MsgCreateVestingAccount", -}; +/* eslint-disable @typescript-eslint/naming-convention */ +import { fromBase64, toBase64 } from "@cosmjs/encoding"; +import { + Coin, + decodeBech32Pubkey, + encodeBech32Pubkey, + Msg, + MsgBeginRedelegate, + MsgCreateValidator, + MsgDelegate, + MsgEditValidator, + MsgMultiSend, + MsgSend, + MsgUndelegate, +} from "@cosmjs/launchpad"; +import { EncodeObject } from "@cosmjs/proto-signing"; +import { assert } from "@cosmjs/utils"; + +import { cosmos } from "./codec"; + +type ICoin = cosmos.base.v1beta1.ICoin; +type IMsgSend = cosmos.bank.v1beta1.IMsgSend; +type IMsgMultiSend = cosmos.bank.v1beta1.IMsgMultiSend; +type IMsgBeginRedelegate = cosmos.staking.v1beta1.IMsgBeginRedelegate; +type IMsgCreateValidator = cosmos.staking.v1beta1.IMsgCreateValidator; +type IMsgDelegate = cosmos.staking.v1beta1.IMsgDelegate; +type IMsgEditValidator = cosmos.staking.v1beta1.IMsgEditValidator; +type IMsgUndelegate = cosmos.staking.v1beta1.IMsgUndelegate; + +export interface AminoConverter { + readonly aminoType: string; + readonly toAmino: (value: any) => any; + 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"); + return { + from_address: fromAddress, + to_address: toAddress, + amount: checkAmount(amount), + }; + }, + fromAmino: ({ from_address, to_address, amount }: MsgSend["value"]): IMsgSend => ({ + fromAddress: from_address, + toAddress: to_address, + amount: [...amount], + }), + }, + "/cosmos.bank.v1beta1.MsgMultiSend": { + aminoType: "cosmos-sdk/MsgMultiSend", + toAmino: ({ inputs, outputs }: IMsgMultiSend): MsgMultiSend["value"] => { + assert(inputs, "missing inputs"); + assert(outputs, "missing outputs"); + return { + inputs: inputs.map((input) => { + assert(input.address, "missing input.address"); + return { + address: input.address, + coins: checkAmount(input.coins), + }; + }), + outputs: outputs.map((output) => { + assert(output.address, "missing output.address"); + return { + address: output.address, + coins: checkAmount(output.coins), + }; + }), + }; + }, + fromAmino: ({ inputs, outputs }: MsgMultiSend["value"]): IMsgMultiSend => ({ + inputs: inputs.map((input) => ({ + address: input.address, + coins: [...input.coins], + })), + outputs: outputs.map((output) => ({ + address: output.address, + coins: [...output.coins], + })), + }), + }, + "/cosmos.staking.v1beta1.MsgBeginRedelegate": { + aminoType: "cosmos-sdk/MsgBeginRedelegate", + toAmino: ({ + delegatorAddress, + validatorSrcAddress, + 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"); + return { + delegator_address: delegatorAddress, + validator_src_address: validatorSrcAddress, + validator_dst_address: validatorDstAddress, + amount: { + amount: amount.amount, + denom: amount.denom, + }, + }; + }, + fromAmino: ({ + delegator_address, + validator_src_address, + validator_dst_address, + amount, + }: MsgBeginRedelegate["value"]): IMsgBeginRedelegate => ({ + delegatorAddress: delegator_address, + validatorSrcAddress: validator_src_address, + validatorDstAddress: validator_dst_address, + amount: amount, + }), + }, + "/cosmos.staking.v1beta1.MsgCreateValidator": { + aminoType: "cosmos-sdk/MsgCreateValidator", + toAmino: ({ + description, + commission, + minSelfDelegation, + delegatorAddress, + validatorAddress, + 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"); + return { + description: { + moniker: description.moniker, + identity: description.identity, + website: description.website, + security_contact: description.securityContact, + details: description.details, + }, + commission: { + rate: commission.rate, + max_rate: commission.maxRate, + max_change_rate: commission.maxChangeRate, + }, + min_self_delegation: minSelfDelegation, + delegator_address: delegatorAddress, + validator_address: validatorAddress, + pubkey: encodeBech32Pubkey( + { + type: "tendermint/PubKeySecp256k1", + value: toBase64(pubkey.value), + }, + prefix, + ), + value: { + amount: value.amount, + denom: value.denom, + }, + }; + }, + fromAmino: ({ + description, + commission, + min_self_delegation, + delegator_address, + validator_address, + pubkey, + value, + }: MsgCreateValidator["value"]): IMsgCreateValidator => { + const decodedPubkey = decodeBech32Pubkey(pubkey); + if (decodedPubkey.type !== "tendermint/PubKeySecp256k1") { + throw new Error("Only Secp256k1 public keys are supported"); + } + return { + description: { + moniker: description.moniker, + identity: description.identity, + website: description.website, + securityContact: description.security_contact, + details: description.details, + }, + commission: { + rate: commission.rate, + maxRate: commission.max_rate, + maxChangeRate: commission.max_change_rate, + }, + minSelfDelegation: min_self_delegation, + delegatorAddress: delegator_address, + validatorAddress: validator_address, + pubkey: { + type_url: "/cosmos.crypto.secp256k1.PubKey", + value: fromBase64(decodedPubkey.value), + }, + value: value, + }; + }, + }, + "/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"); + return { + delegator_address: delegatorAddress, + validator_address: validatorAddress, + amount: { + amount: amount.amount, + denom: amount.denom, + }, + }; + }, + fromAmino: ({ delegator_address, validator_address, amount }: MsgDelegate["value"]): IMsgDelegate => ({ + delegatorAddress: delegator_address, + validatorAddress: validator_address, + amount: amount, + }), + }, + "/cosmos.staking.v1beta1.MsgEditValidator": { + aminoType: "cosmos-sdk/MsgEditValidator", + toAmino: ({ + description, + commissionRate, + 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"); + return { + description: { + moniker: description.moniker, + identity: description.identity, + website: description.website, + security_contact: description.securityContact, + details: description.details, + }, + commission_rate: commissionRate, + min_self_delegation: minSelfDelegation, + validator_address: validatorAddress, + }; + }, + fromAmino: ({ + description, + commission_rate, + min_self_delegation, + validator_address, + }: MsgEditValidator["value"]): IMsgEditValidator => ({ + description: { + moniker: description.moniker, + identity: description.identity, + website: description.website, + securityContact: description.security_contact, + details: description.details, + }, + commissionRate: commission_rate, + minSelfDelegation: min_self_delegation, + validatorAddress: validator_address, + }), + }, + "/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"); + return { + delegator_address: delegatorAddress, + validator_address: validatorAddress, + amount: { + amount: amount.amount, + denom: amount.denom, + }, + }; + }, + fromAmino: ({ + delegator_address, + validator_address, + amount, + }: MsgUndelegate["value"]): IMsgUndelegate => ({ + delegatorAddress: delegator_address, + validatorAddress: validator_address, + amount: amount, + }), + }, + }; +} + +interface AminoTypesOptions { + readonly additions?: Record; + readonly prefix?: string; +} /** * A map from Stargate message types as used in the messages's `Any` type * to Amino types. */ export class AminoTypes { - private readonly register: Record; + private readonly register: Record; - public constructor(additions: Record = {}) { + public constructor({ additions = {}, prefix = "cosmos" }: AminoTypesOptions = {}) { const additionalAminoTypes = Object.values(additions); - const filteredDefaultTypes = Object.entries(defaultTypes).reduce( - (acc, [key, value]) => (additionalAminoTypes.includes(value) ? acc : { ...acc, [key]: value }), + const filteredDefaultTypes = Object.entries(createDefaultTypes(prefix)).reduce( + (acc, [key, value]) => + additionalAminoTypes.find(({ aminoType }) => value.aminoType === aminoType) + ? acc + : { ...acc, [key]: value }, {}, ); this.register = { ...filteredDefaultTypes, ...additions }; } - public toAmino(typeUrl: string): string { - const type = this.register[typeUrl]; - if (!type) { + public toAmino({ typeUrl, value }: EncodeObject): Msg { + const converter = this.register[typeUrl]; + if (!converter) { throw new Error( "Type URL does not exist in the Amino message type register. " + "If you need support for this message type, you can pass in additional entries to the AminoTypes constructor. " + "If you think this message type should be included by default, please open an issue at https://github.com/cosmos/cosmjs/issues.", ); } - return type; + return { + type: converter.aminoType, + value: converter.toAmino(value), + }; } - public fromAmino(type: string): string { - const [typeUrl] = Object.entries(this.register).find(([_typeUrl, value]) => value === type) ?? []; - if (!typeUrl) { + public fromAmino({ type, value }: Msg): EncodeObject { + const result = Object.entries(this.register).find(([_typeUrl, { aminoType }]) => aminoType === type); + if (!result) { throw new Error( "Type does not exist in the Amino message type register. " + "If you need support for this message type, you can pass in additional entries to the AminoTypes constructor. " + "If you think this message type should be included by default, please open an issue at https://github.com/cosmos/cosmjs/issues.", ); } - return typeUrl; + const [typeUrl, converter] = result; + return { + typeUrl: typeUrl, + value: converter.fromAmino(value), + }; } } diff --git a/packages/stargate/types/aminotypes.d.ts b/packages/stargate/types/aminotypes.d.ts index 473b3797..9654066b 100644 --- a/packages/stargate/types/aminotypes.d.ts +++ b/packages/stargate/types/aminotypes.d.ts @@ -1,10 +1,22 @@ +import { Msg } from "@cosmjs/launchpad"; +import { EncodeObject } from "@cosmjs/proto-signing"; +export interface AminoConverter { + readonly aminoType: string; + readonly toAmino: (value: any) => any; + readonly fromAmino: (value: any) => any; +} +interface AminoTypesOptions { + readonly additions?: Record; + readonly prefix?: string; +} /** * A map from Stargate message types as used in the messages's `Any` type * to Amino types. */ export declare class AminoTypes { private readonly register; - constructor(additions?: Record); - toAmino(typeUrl: string): string; - fromAmino(type: string): string; + constructor({ additions, prefix }?: AminoTypesOptions); + toAmino({ typeUrl, value }: EncodeObject): Msg; + fromAmino({ type, value }: Msg): EncodeObject; } +export {}; From 7c8a3af4e194cade832982489c5ef5de607a4ba8 Mon Sep 17 00:00:00 2001 From: willclarktech Date: Tue, 12 Jan 2021 16:24:26 +0000 Subject: [PATCH 3/5] stargate: Update Amino types tests --- packages/stargate/src/aminotypes.spec.ts | 567 ++++++++++++++++++++++- 1 file changed, 542 insertions(+), 25 deletions(-) diff --git a/packages/stargate/src/aminotypes.spec.ts b/packages/stargate/src/aminotypes.spec.ts index a678aa2d..8aef1d19 100644 --- a/packages/stargate/src/aminotypes.spec.ts +++ b/packages/stargate/src/aminotypes.spec.ts @@ -1,56 +1,573 @@ /* eslint-disable @typescript-eslint/naming-convention */ +import { fromBase64 } from "@cosmjs/encoding"; +import { + coin, + coins, + encodeBech32Pubkey, + MsgBeginRedelegate, + MsgCreateValidator, + MsgDelegate, + MsgEditValidator, + MsgMultiSend, + MsgSend, + MsgUndelegate, +} from "@cosmjs/launchpad"; + import { AminoTypes } from "./aminotypes"; +import { cosmos } from "./codec"; + +type IMsgSend = cosmos.bank.v1beta1.IMsgSend; +type IMsgMultiSend = cosmos.bank.v1beta1.IMsgMultiSend; +type IMsgBeginRedelegate = cosmos.staking.v1beta1.IMsgBeginRedelegate; +type IMsgCreateValidator = cosmos.staking.v1beta1.IMsgCreateValidator; +type IMsgDelegate = cosmos.staking.v1beta1.IMsgDelegate; +type IMsgEditValidator = cosmos.staking.v1beta1.IMsgEditValidator; +type IMsgUndelegate = cosmos.staking.v1beta1.IMsgUndelegate; describe("AminoTypes", () => { describe("toAmino", () => { - it("works for known type url", () => { - const msgType = new AminoTypes().toAmino("/cosmos.staking.v1beta1.MsgDelegate"); - expect(msgType).toEqual("cosmos-sdk/MsgDelegate"); + it("works for MsgSend", () => { + const msg: IMsgSend = { + fromAddress: "cosmos1pkptre7fdkl6gfrzlesjjvhxhlc3r4gmmk8rs6", + toAddress: "cosmos10dyr9899g6t0pelew4nvf4j5c3jcgv0r73qga5", + amount: coins(1234, "ucosm"), + }; + const aminoMsg = new AminoTypes().toAmino({ + typeUrl: "/cosmos.bank.v1beta1.MsgSend", + value: msg, + }); + const expected: MsgSend = { + type: "cosmos-sdk/MsgSend", + value: { + from_address: "cosmos1pkptre7fdkl6gfrzlesjjvhxhlc3r4gmmk8rs6", + to_address: "cosmos10dyr9899g6t0pelew4nvf4j5c3jcgv0r73qga5", + amount: coins(1234, "ucosm"), + }, + }; + expect(aminoMsg).toEqual(expected); + }); + + it("works for MsgMultiSend", () => { + const msg: IMsgMultiSend = { + inputs: [ + { address: "cosmos1pkptre7fdkl6gfrzlesjjvhxhlc3r4gmmk8rs6", coins: coins(1234, "ucosm") }, + { address: "cosmos10dyr9899g6t0pelew4nvf4j5c3jcgv0r73qga5", coins: coins(5678, "ucosm") }, + ], + outputs: [ + { address: "cosmos1xy4yqngt0nlkdcenxymg8tenrghmek4nmqm28k", coins: coins(6000, "ucosm") }, + { address: "cosmos142u9fgcjdlycfcez3lw8x6x5h7rfjlnfhpw2lx", coins: coins(912, "ucosm") }, + ], + }; + const aminoMsg = new AminoTypes().toAmino({ + typeUrl: "/cosmos.bank.v1beta1.MsgMultiSend", + value: msg, + }); + const expected: MsgMultiSend = { + type: "cosmos-sdk/MsgMultiSend", + value: { + inputs: [ + { address: "cosmos1pkptre7fdkl6gfrzlesjjvhxhlc3r4gmmk8rs6", coins: coins(1234, "ucosm") }, + { address: "cosmos10dyr9899g6t0pelew4nvf4j5c3jcgv0r73qga5", coins: coins(5678, "ucosm") }, + ], + outputs: [ + { address: "cosmos1xy4yqngt0nlkdcenxymg8tenrghmek4nmqm28k", coins: coins(6000, "ucosm") }, + { address: "cosmos142u9fgcjdlycfcez3lw8x6x5h7rfjlnfhpw2lx", coins: coins(912, "ucosm") }, + ], + }, + }; + expect(aminoMsg).toEqual(expected); + }); + + it("works for MsgBeginRedelegate", () => { + const msg: IMsgBeginRedelegate = { + delegatorAddress: "cosmos1pkptre7fdkl6gfrzlesjjvhxhlc3r4gmmk8rs6", + validatorSrcAddress: "cosmos10dyr9899g6t0pelew4nvf4j5c3jcgv0r73qga5", + validatorDstAddress: "cosmos1xy4yqngt0nlkdcenxymg8tenrghmek4nmqm28k", + amount: coin(1234, "ucosm"), + }; + const aminoMsg = new AminoTypes().toAmino({ + typeUrl: "/cosmos.staking.v1beta1.MsgBeginRedelegate", + value: msg, + }); + const expected: MsgBeginRedelegate = { + type: "cosmos-sdk/MsgBeginRedelegate", + value: { + delegator_address: "cosmos1pkptre7fdkl6gfrzlesjjvhxhlc3r4gmmk8rs6", + validator_src_address: "cosmos10dyr9899g6t0pelew4nvf4j5c3jcgv0r73qga5", + validator_dst_address: "cosmos1xy4yqngt0nlkdcenxymg8tenrghmek4nmqm28k", + amount: coin(1234, "ucosm"), + }, + }; + expect(aminoMsg).toEqual(expected); + }); + + it("works for MsgCreateValidator", () => { + const msg: IMsgCreateValidator = { + description: { + moniker: "validator", + identity: "me", + website: "valid.com", + securityContact: "Hamburglar", + details: "...", + }, + commission: { + rate: "0.2", + maxRate: "0.3", + maxChangeRate: "0.1", + }, + minSelfDelegation: "123", + delegatorAddress: "cosmos1pkptre7fdkl6gfrzlesjjvhxhlc3r4gmmk8rs6", + validatorAddress: "cosmos10dyr9899g6t0pelew4nvf4j5c3jcgv0r73qga5", + pubkey: { + type_url: "/cosmos.crypto.secp256k1.PubKey", + value: fromBase64("A08EGB7ro1ORuFhjOnZcSgwYlpe0DSFjVNUIkNNQxwKQ"), + }, + value: coin(1234, "ucosm"), + }; + const aminoMsg = new AminoTypes().toAmino({ + typeUrl: "/cosmos.staking.v1beta1.MsgCreateValidator", + value: msg, + }); + const expected: MsgCreateValidator = { + type: "cosmos-sdk/MsgCreateValidator", + value: { + description: { + moniker: "validator", + identity: "me", + website: "valid.com", + security_contact: "Hamburglar", + details: "...", + }, + commission: { + rate: "0.2", + max_rate: "0.3", + max_change_rate: "0.1", + }, + min_self_delegation: "123", + delegator_address: "cosmos1pkptre7fdkl6gfrzlesjjvhxhlc3r4gmmk8rs6", + validator_address: "cosmos10dyr9899g6t0pelew4nvf4j5c3jcgv0r73qga5", + pubkey: encodeBech32Pubkey( + { type: "tendermint/PubKeySecp256k1", value: "A08EGB7ro1ORuFhjOnZcSgwYlpe0DSFjVNUIkNNQxwKQ" }, + "cosmos", + ), + value: coin(1234, "ucosm"), + }, + }; + expect(aminoMsg).toEqual(expected); + }); + + it("works for MsgDelegate", () => { + const msg: IMsgDelegate = { + delegatorAddress: "cosmos1pkptre7fdkl6gfrzlesjjvhxhlc3r4gmmk8rs6", + validatorAddress: "cosmos10dyr9899g6t0pelew4nvf4j5c3jcgv0r73qga5", + amount: coin(1234, "ucosm"), + }; + const aminoMsg = new AminoTypes().toAmino({ + typeUrl: "/cosmos.staking.v1beta1.MsgDelegate", + value: msg, + }); + const expected: MsgDelegate = { + type: "cosmos-sdk/MsgDelegate", + value: { + delegator_address: "cosmos1pkptre7fdkl6gfrzlesjjvhxhlc3r4gmmk8rs6", + validator_address: "cosmos10dyr9899g6t0pelew4nvf4j5c3jcgv0r73qga5", + amount: coin(1234, "ucosm"), + }, + }; + expect(aminoMsg).toEqual(expected); + }); + + it("works for MsgEditValidator", () => { + const msg: IMsgEditValidator = { + description: { + moniker: "validator", + identity: "me", + website: "valid.com", + securityContact: "Hamburglar", + details: "...", + }, + commissionRate: "0.2", + minSelfDelegation: "123", + validatorAddress: "cosmos10dyr9899g6t0pelew4nvf4j5c3jcgv0r73qga5", + }; + const aminoMsg = new AminoTypes().toAmino({ + typeUrl: "/cosmos.staking.v1beta1.MsgEditValidator", + value: msg, + }); + const expected: MsgEditValidator = { + type: "cosmos-sdk/MsgEditValidator", + value: { + description: { + moniker: "validator", + identity: "me", + website: "valid.com", + security_contact: "Hamburglar", + details: "...", + }, + commission_rate: "0.2", + min_self_delegation: "123", + validator_address: "cosmos10dyr9899g6t0pelew4nvf4j5c3jcgv0r73qga5", + }, + }; + expect(aminoMsg).toEqual(expected); + }); + + it("works for MsgUndelegate", () => { + const msg: IMsgUndelegate = { + delegatorAddress: "cosmos1pkptre7fdkl6gfrzlesjjvhxhlc3r4gmmk8rs6", + validatorAddress: "cosmos10dyr9899g6t0pelew4nvf4j5c3jcgv0r73qga5", + amount: coin(1234, "ucosm"), + }; + const aminoMsg = new AminoTypes().toAmino({ + typeUrl: "/cosmos.staking.v1beta1.MsgUndelegate", + value: msg, + }); + const expected: MsgUndelegate = { + type: "cosmos-sdk/MsgUndelegate", + value: { + delegator_address: "cosmos1pkptre7fdkl6gfrzlesjjvhxhlc3r4gmmk8rs6", + validator_address: "cosmos10dyr9899g6t0pelew4nvf4j5c3jcgv0r73qga5", + amount: coin(1234, "ucosm"), + }, + }; + expect(aminoMsg).toEqual(expected); }); it("works with custom type url", () => { - const msgType = new AminoTypes({ "/my.CustomType": "my-sdk/CustomType" }).toAmino("/my.CustomType"); - expect(msgType).toEqual("my-sdk/CustomType"); + const msg = { + foo: "bar", + }; + const aminoMsg = new AminoTypes({ + additions: { + "/my.CustomType": { + aminoType: "my-sdk/CustomType", + toAmino: ({ + foo, + }: { + readonly foo: string; + }): { readonly foo: string; readonly constant: string } => ({ + foo: `amino-prefix-${foo}`, + constant: "something-for-amino", + }), + fromAmino: () => {}, + }, + }, + }).toAmino({ typeUrl: "/my.CustomType", value: msg }); + expect(aminoMsg).toEqual({ + type: "my-sdk/CustomType", + value: { + foo: "amino-prefix-bar", + constant: "something-for-amino", + }, + }); }); it("works with overridden type url", () => { - const msgType = new AminoTypes({ - "/cosmos.staking.v1beta1.MsgDelegate": "my-override/MsgDelegate", - }).toAmino("/cosmos.staking.v1beta1.MsgDelegate"); - expect(msgType).toEqual("my-override/MsgDelegate"); + const msg: IMsgDelegate = { + delegatorAddress: "cosmos1pkptre7fdkl6gfrzlesjjvhxhlc3r4gmmk8rs6", + validatorAddress: "cosmos10dyr9899g6t0pelew4nvf4j5c3jcgv0r73qga5", + amount: coin(1234, "ucosm"), + }; + const aminoMsg = new AminoTypes({ + additions: { + "/cosmos.staking.v1beta1.MsgDelegate": { + aminoType: "my-override/MsgDelegate", + toAmino: (m: IMsgDelegate): { readonly foo: string } => ({ + foo: m.delegatorAddress ?? "", + }), + fromAmino: () => {}, + }, + }, + }).toAmino({ + typeUrl: "/cosmos.staking.v1beta1.MsgDelegate", + value: msg, + }); + const expected = { + type: "my-override/MsgDelegate", + value: { + foo: "cosmos1pkptre7fdkl6gfrzlesjjvhxhlc3r4gmmk8rs6", + }, + }; + expect(aminoMsg).toEqual(expected); }); it("throws for unknown type url", () => { - expect(() => new AminoTypes().toAmino("/xxx.Unknown")).toThrowError( + expect(() => new AminoTypes().toAmino({ typeUrl: "/xxx.Unknown", value: { foo: "bar" } })).toThrowError( /Type URL does not exist in the Amino message type register./i, ); }); }); describe("fromAmino", () => { - it("works for known type url", () => { - const msgUrl = new AminoTypes().fromAmino("cosmos-sdk/MsgDelegate"); - expect(msgUrl).toEqual("/cosmos.staking.v1beta1.MsgDelegate"); + it("works for MsgSend", () => { + const aminoMsg: MsgSend = { + type: "cosmos-sdk/MsgSend", + value: { + from_address: "cosmos1pkptre7fdkl6gfrzlesjjvhxhlc3r4gmmk8rs6", + to_address: "cosmos10dyr9899g6t0pelew4nvf4j5c3jcgv0r73qga5", + amount: coins(1234, "ucosm"), + }, + }; + const msg = new AminoTypes().fromAmino(aminoMsg); + const expectedValue: IMsgSend = { + fromAddress: "cosmos1pkptre7fdkl6gfrzlesjjvhxhlc3r4gmmk8rs6", + toAddress: "cosmos10dyr9899g6t0pelew4nvf4j5c3jcgv0r73qga5", + amount: coins(1234, "ucosm"), + }; + expect(msg).toEqual({ + typeUrl: "/cosmos.bank.v1beta1.MsgSend", + value: expectedValue, + }); }); - it("works with custom type url", () => { - const msgType = new AminoTypes({ "/my.CustomType": "my-sdk/CustomType" }).fromAmino( - "my-sdk/CustomType", - ); - expect(msgType).toEqual("/my.CustomType"); + it("works for MsgMultiSend", () => { + const aminoMsg: MsgMultiSend = { + type: "cosmos-sdk/MsgMultiSend", + value: { + inputs: [ + { address: "cosmos1pkptre7fdkl6gfrzlesjjvhxhlc3r4gmmk8rs6", coins: coins(1234, "ucosm") }, + { address: "cosmos10dyr9899g6t0pelew4nvf4j5c3jcgv0r73qga5", coins: coins(5678, "ucosm") }, + ], + outputs: [ + { address: "cosmos1xy4yqngt0nlkdcenxymg8tenrghmek4nmqm28k", coins: coins(6000, "ucosm") }, + { address: "cosmos142u9fgcjdlycfcez3lw8x6x5h7rfjlnfhpw2lx", coins: coins(912, "ucosm") }, + ], + }, + }; + const msg = new AminoTypes().fromAmino(aminoMsg); + const expectedValue: IMsgMultiSend = { + inputs: [ + { address: "cosmos1pkptre7fdkl6gfrzlesjjvhxhlc3r4gmmk8rs6", coins: coins(1234, "ucosm") }, + { address: "cosmos10dyr9899g6t0pelew4nvf4j5c3jcgv0r73qga5", coins: coins(5678, "ucosm") }, + ], + outputs: [ + { address: "cosmos1xy4yqngt0nlkdcenxymg8tenrghmek4nmqm28k", coins: coins(6000, "ucosm") }, + { address: "cosmos142u9fgcjdlycfcez3lw8x6x5h7rfjlnfhpw2lx", coins: coins(912, "ucosm") }, + ], + }; + expect(msg).toEqual({ + typeUrl: "/cosmos.bank.v1beta1.MsgMultiSend", + value: expectedValue, + }); + }); + + it("works for MsgBeginRedelegate", () => { + const aminoMsg: MsgBeginRedelegate = { + type: "cosmos-sdk/MsgBeginRedelegate", + value: { + delegator_address: "cosmos1pkptre7fdkl6gfrzlesjjvhxhlc3r4gmmk8rs6", + validator_src_address: "cosmos10dyr9899g6t0pelew4nvf4j5c3jcgv0r73qga5", + validator_dst_address: "cosmos1xy4yqngt0nlkdcenxymg8tenrghmek4nmqm28k", + amount: coin(1234, "ucosm"), + }, + }; + const msg = new AminoTypes().fromAmino(aminoMsg); + const expectedValue: IMsgBeginRedelegate = { + delegatorAddress: "cosmos1pkptre7fdkl6gfrzlesjjvhxhlc3r4gmmk8rs6", + validatorSrcAddress: "cosmos10dyr9899g6t0pelew4nvf4j5c3jcgv0r73qga5", + validatorDstAddress: "cosmos1xy4yqngt0nlkdcenxymg8tenrghmek4nmqm28k", + amount: coin(1234, "ucosm"), + }; + expect(msg).toEqual({ + typeUrl: "/cosmos.staking.v1beta1.MsgBeginRedelegate", + value: expectedValue, + }); + }); + + it("works for MsgCreateValidator", () => { + const aminoMsg: MsgCreateValidator = { + type: "cosmos-sdk/MsgCreateValidator", + value: { + description: { + moniker: "validator", + identity: "me", + website: "valid.com", + security_contact: "Hamburglar", + details: "...", + }, + commission: { + rate: "0.2", + max_rate: "0.3", + max_change_rate: "0.1", + }, + min_self_delegation: "123", + delegator_address: "cosmos1pkptre7fdkl6gfrzlesjjvhxhlc3r4gmmk8rs6", + validator_address: "cosmos10dyr9899g6t0pelew4nvf4j5c3jcgv0r73qga5", + pubkey: encodeBech32Pubkey( + { type: "tendermint/PubKeySecp256k1", value: "A08EGB7ro1ORuFhjOnZcSgwYlpe0DSFjVNUIkNNQxwKQ" }, + "cosmos", + ), + value: coin(1234, "ucosm"), + }, + }; + const msg = new AminoTypes().fromAmino(aminoMsg); + const expectedValue: IMsgCreateValidator = { + description: { + moniker: "validator", + identity: "me", + website: "valid.com", + securityContact: "Hamburglar", + details: "...", + }, + commission: { + rate: "0.2", + maxRate: "0.3", + maxChangeRate: "0.1", + }, + minSelfDelegation: "123", + delegatorAddress: "cosmos1pkptre7fdkl6gfrzlesjjvhxhlc3r4gmmk8rs6", + validatorAddress: "cosmos10dyr9899g6t0pelew4nvf4j5c3jcgv0r73qga5", + pubkey: { + type_url: "/cosmos.crypto.secp256k1.PubKey", + value: fromBase64("A08EGB7ro1ORuFhjOnZcSgwYlpe0DSFjVNUIkNNQxwKQ"), + }, + value: coin(1234, "ucosm"), + }; + expect(msg).toEqual({ + typeUrl: "/cosmos.staking.v1beta1.MsgCreateValidator", + value: expectedValue, + }); + }); + + it("works for MsgDelegate", () => { + const aminoMsg: MsgDelegate = { + type: "cosmos-sdk/MsgDelegate", + value: { + delegator_address: "cosmos1pkptre7fdkl6gfrzlesjjvhxhlc3r4gmmk8rs6", + validator_address: "cosmos10dyr9899g6t0pelew4nvf4j5c3jcgv0r73qga5", + amount: coin(1234, "ucosm"), + }, + }; + const msg = new AminoTypes().fromAmino(aminoMsg); + const expectedValue: IMsgDelegate = { + delegatorAddress: "cosmos1pkptre7fdkl6gfrzlesjjvhxhlc3r4gmmk8rs6", + validatorAddress: "cosmos10dyr9899g6t0pelew4nvf4j5c3jcgv0r73qga5", + amount: coin(1234, "ucosm"), + }; + expect(msg).toEqual({ + typeUrl: "/cosmos.staking.v1beta1.MsgDelegate", + value: expectedValue, + }); + }); + + it("works for MsgEditValidator", () => { + const aminoMsg: MsgEditValidator = { + type: "cosmos-sdk/MsgEditValidator", + value: { + description: { + moniker: "validator", + identity: "me", + website: "valid.com", + security_contact: "Hamburglar", + details: "...", + }, + commission_rate: "0.2", + min_self_delegation: "123", + validator_address: "cosmos10dyr9899g6t0pelew4nvf4j5c3jcgv0r73qga5", + }, + }; + const msg = new AminoTypes().fromAmino(aminoMsg); + const expectedValue: IMsgEditValidator = { + description: { + moniker: "validator", + identity: "me", + website: "valid.com", + securityContact: "Hamburglar", + details: "...", + }, + commissionRate: "0.2", + minSelfDelegation: "123", + validatorAddress: "cosmos10dyr9899g6t0pelew4nvf4j5c3jcgv0r73qga5", + }; + expect(msg).toEqual({ + typeUrl: "/cosmos.staking.v1beta1.MsgEditValidator", + value: expectedValue, + }); + }); + + it("works for MsgUndelegate", () => { + const aminoMsg: MsgUndelegate = { + type: "cosmos-sdk/MsgUndelegate", + value: { + delegator_address: "cosmos1pkptre7fdkl6gfrzlesjjvhxhlc3r4gmmk8rs6", + validator_address: "cosmos10dyr9899g6t0pelew4nvf4j5c3jcgv0r73qga5", + amount: coin(1234, "ucosm"), + }, + }; + const msg = new AminoTypes().fromAmino(aminoMsg); + const expectedValue: IMsgUndelegate = { + delegatorAddress: "cosmos1pkptre7fdkl6gfrzlesjjvhxhlc3r4gmmk8rs6", + validatorAddress: "cosmos10dyr9899g6t0pelew4nvf4j5c3jcgv0r73qga5", + amount: coin(1234, "ucosm"), + }; + expect(msg).toEqual({ + typeUrl: "/cosmos.staking.v1beta1.MsgUndelegate", + value: expectedValue, + }); + }); + + it("works for custom type url", () => { + const aminoMsg = { + type: "my-sdk/CustomType", + value: { + foo: "amino-prefix-bar", + constant: "something-for-amino", + }, + }; + const msg = new AminoTypes({ + additions: { + "/my.CustomType": { + aminoType: "my-sdk/CustomType", + toAmino: () => {}, + fromAmino: ({ foo }: { readonly foo: string; readonly constant: string }): any => ({ + foo: foo.slice(13), + }), + }, + }, + }).fromAmino(aminoMsg); + const expectedValue = { + foo: "bar", + }; + expect(msg).toEqual({ + typeUrl: "/my.CustomType", + value: expectedValue, + }); }); it("works with overridden type url", () => { - const msgType = new AminoTypes({ - "/my.OverrideType": "cosmos-sdk/MsgDelegate", - }).fromAmino("cosmos-sdk/MsgDelegate"); - expect(msgType).toEqual("/my.OverrideType"); + const msg = new AminoTypes({ + additions: { + "/my.OverrideType": { + aminoType: "cosmos-sdk/MsgDelegate", + toAmino: () => {}, + fromAmino: ({ foo }: { readonly foo: string }): IMsgDelegate => ({ + delegatorAddress: foo, + validatorAddress: "cosmos10dyr9899g6t0pelew4nvf4j5c3jcgv0r73qga5", + amount: coin(1234, "ucosm"), + }), + }, + }, + }).fromAmino({ + type: "cosmos-sdk/MsgDelegate", + value: { + foo: "cosmos1pkptre7fdkl6gfrzlesjjvhxhlc3r4gmmk8rs6", + }, + }); + const expected: { readonly typeUrl: "/my.OverrideType"; readonly value: IMsgDelegate } = { + typeUrl: "/my.OverrideType", + value: { + delegatorAddress: "cosmos1pkptre7fdkl6gfrzlesjjvhxhlc3r4gmmk8rs6", + validatorAddress: "cosmos10dyr9899g6t0pelew4nvf4j5c3jcgv0r73qga5", + amount: coin(1234, "ucosm"), + }, + }; + expect(msg).toEqual(expected); }); it("throws for unknown type url", () => { - expect(() => new AminoTypes().fromAmino("cosmos-sdk/MsgUnknown")).toThrowError( - /Type does not exist in the Amino message type register./i, - ); + expect(() => + new AminoTypes().fromAmino({ type: "cosmos-sdk/MsgUnknown", value: { foo: "bar" } }), + ).toThrowError(/Type does not exist in the Amino message type register./i); }); }); }); From 4fbc3d9070caf124842440141154f0cf9781c986 Mon Sep 17 00:00:00 2001 From: willclarktech Date: Tue, 12 Jan 2021 17:18:45 +0000 Subject: [PATCH 4/5] stargate: Update SigningStargateClient for Amino signing --- .../stargate/src/signingstargateclient.ts | 36 +++++++++++++------ .../stargate/types/signingstargateclient.d.ts | 2 ++ 2 files changed, 28 insertions(+), 10 deletions(-) diff --git a/packages/stargate/src/signingstargateclient.ts b/packages/stargate/src/signingstargateclient.ts index 2cff750e..263446f8 100644 --- a/packages/stargate/src/signingstargateclient.ts +++ b/packages/stargate/src/signingstargateclient.ts @@ -27,6 +27,14 @@ import { AminoTypes } from "./aminotypes"; import { cosmos } from "./codec"; import { BroadcastTxResponse, StargateClient } from "./stargateclient"; +const { MsgMultiSend } = cosmos.bank.v1beta1; +const { + MsgBeginRedelegate, + MsgCreateValidator, + MsgDelegate, + MsgEditValidator, + MsgUndelegate, +} = cosmos.staking.v1beta1; const { TxRaw } = cosmos.tx.v1beta1; const defaultGasPrice = GasPrice.fromString("0.025ucosm"); @@ -40,6 +48,7 @@ export interface PrivateSigningStargateClient { export interface SigningStargateClientOptions { readonly registry?: Registry; + readonly aminoTypes?: AminoTypes; readonly gasPrice?: GasPrice; readonly gasLimits?: GasLimits; } @@ -48,7 +57,7 @@ export class SigningStargateClient extends StargateClient { private readonly fees: CosmosFeeTable; private readonly registry: Registry; private readonly signer: OfflineSigner; - private readonly aminoTypes = new AminoTypes(); + private readonly aminoTypes; public static async connectWithSigner( endpoint: string, @@ -65,9 +74,22 @@ export class SigningStargateClient extends StargateClient { options: SigningStargateClientOptions, ) { super(tmClient); - const { registry = new Registry(), gasPrice = defaultGasPrice, gasLimits = defaultGasLimits } = options; + 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(), + gasPrice = defaultGasPrice, + gasLimits = defaultGasLimits, + } = options; this.fees = buildFeeTable(gasPrice, defaultGasLimits, gasLimits); this.registry = registry; + this.aminoTypes = aminoTypes; this.signer = signer; } @@ -136,17 +158,11 @@ export class SigningStargateClient extends StargateClient { // Amino signer const signMode = cosmos.tx.signing.v1beta1.SignMode.SIGN_MODE_LEGACY_AMINO_JSON; - const msgs = messages.map((msg) => ({ - type: this.aminoTypes.toAmino(msg.typeUrl), - value: msg.value, - })); + 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 signedTxBody = { - messages: signed.msgs.map((msg) => ({ - typeUrl: this.aminoTypes.fromAmino(msg.type), - value: msg.value, - })), + messages: signed.msgs.map((msg) => this.aminoTypes.fromAmino(msg)), memo: signed.memo, }; const signedTxBodyBytes = this.registry.encode({ diff --git a/packages/stargate/types/signingstargateclient.d.ts b/packages/stargate/types/signingstargateclient.d.ts index b217d13d..ea901c42 100644 --- a/packages/stargate/types/signingstargateclient.d.ts +++ b/packages/stargate/types/signingstargateclient.d.ts @@ -1,5 +1,6 @@ import { Coin, CosmosFeeTable, GasLimits, GasPrice, StdFee } from "@cosmjs/launchpad"; import { EncodeObject, OfflineSigner, Registry } from "@cosmjs/proto-signing"; +import { AminoTypes } from "./aminotypes"; import { BroadcastTxResponse, StargateClient } from "./stargateclient"; /** Use for testing only */ export interface PrivateSigningStargateClient { @@ -8,6 +9,7 @@ export interface PrivateSigningStargateClient { } export interface SigningStargateClientOptions { readonly registry?: Registry; + readonly aminoTypes?: AminoTypes; readonly gasPrice?: GasPrice; readonly gasLimits?: GasLimits; } From cb616c9e152f96521ca7099127e4b23d10760011 Mon Sep 17 00:00:00 2001 From: willclarktech Date: Tue, 12 Jan 2021 17:19:34 +0000 Subject: [PATCH 5/5] stargate: Update SigningStargateClient tests for Amino signing --- .../src/signingstargateclient.spec.ts | 134 ++++++++++++++---- 1 file changed, 108 insertions(+), 26 deletions(-) diff --git a/packages/stargate/src/signingstargateclient.spec.ts b/packages/stargate/src/signingstargateclient.spec.ts index 7a409cbb..921ea5cd 100644 --- a/packages/stargate/src/signingstargateclient.spec.ts +++ b/packages/stargate/src/signingstargateclient.spec.ts @@ -1,9 +1,16 @@ /* eslint-disable @typescript-eslint/naming-convention */ -import { coin, coins, GasPrice, Secp256k1HdWallet } from "@cosmjs/launchpad"; +import { + coin, + coins, + GasPrice, + MsgDelegate as LaunchpadMsgDelegate, + Secp256k1HdWallet, +} from "@cosmjs/launchpad"; import { Coin, cosmosField, DirectSecp256k1HdWallet, registered, Registry } from "@cosmjs/proto-signing"; import { assert, sleep } from "@cosmjs/utils"; import { Message } from "protobufjs"; +import { AminoTypes } from "./aminotypes"; import { cosmos } from "./codec"; import { PrivateSigningStargateClient, SigningStargateClient } from "./signingstargateclient"; import { assertIsBroadcastTxSuccess } from "./stargateclient"; @@ -150,10 +157,7 @@ describe("SigningStargateClient", () => { pendingWithoutSimapp(); const wallet = await DirectSecp256k1HdWallet.fromMnemonic(faucet.mnemonic); const msgDelegateTypeUrl = "/cosmos.staking.v1beta1.MsgDelegate"; - const registry = new Registry(); - registry.register(msgDelegateTypeUrl, MsgDelegate); - const options = { registry: registry }; - const client = await SigningStargateClient.connectWithSigner(simapp.tendermintUrl, wallet, options); + const client = await SigningStargateClient.connectWithSigner(simapp.tendermintUrl, wallet); const msg = MsgDelegate.create({ delegatorAddress: faucet.address0, @@ -177,10 +181,7 @@ describe("SigningStargateClient", () => { pendingWithoutSimapp(); const wallet = await ModifyingDirectSecp256k1HdWallet.fromMnemonic(faucet.mnemonic); const msgDelegateTypeUrl = "/cosmos.staking.v1beta1.MsgDelegate"; - const registry = new Registry(); - registry.register(msgDelegateTypeUrl, MsgDelegate); - const options = { registry: registry }; - const client = await SigningStargateClient.connectWithSigner(simapp.tendermintUrl, wallet, options); + const client = await SigningStargateClient.connectWithSigner(simapp.tendermintUrl, wallet); const msg = MsgDelegate.create({ delegatorAddress: faucet.address0, @@ -212,36 +213,118 @@ describe("SigningStargateClient", () => { }); describe("legacy Amino mode", () => { - // NOTE: One registry shared between tests + // 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 registry = new Registry(); + const customRegistry = new Registry(); const msgDelegateTypeUrl = "/cosmos.staking.v1beta1.MsgDelegate"; - @registered(registry, msgDelegateTypeUrl) + @registered(customRegistry, msgDelegateTypeUrl) // eslint-disable-next-line @typescript-eslint/no-unused-vars class CustomMsgDelegate extends Message { @cosmosField.string(1) - public readonly delegator_address?: string; + public readonly custom_delegator_address?: string; @cosmosField.string(2) - public readonly validator_address?: string; + public readonly custom_validator_address?: string; @cosmosField.message(3, Coin) - public readonly amount?: Coin; + public readonly custom_amount?: Coin; } - it("works", async () => { + it("works with bank MsgSend", async () => { pendingWithoutSimapp(); const wallet = await Secp256k1HdWallet.fromMnemonic(faucet.mnemonic); - const options = { registry: registry }; - const client = await SigningStargateClient.connectWithSigner(simapp.tendermintUrl, wallet, options); + const client = await SigningStargateClient.connectWithSigner(simapp.tendermintUrl, wallet); - const msg = { - delegator_address: faucet.address0, - validator_address: validator.validatorAddress, + const msgSend = { + fromAddress: faucet.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(faucet.address0, [msgAny], fee, memo); + assertIsBroadcastTxSuccess(result); + }); + + it("works with staking MsgDelegate", async () => { + pendingWithoutSimapp(); + const wallet = await Secp256k1HdWallet.fromMnemonic(faucet.mnemonic); + const client = await SigningStargateClient.connectWithSigner(simapp.tendermintUrl, wallet); + + const msgDelegate = { + delegatorAddress: faucet.address0, + validatorAddress: validator.validatorAddress, amount: coin(1234, "ustake"), }; const msgAny = { - typeUrl: msgDelegateTypeUrl, + 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(faucet.address0, [msgAny], fee, memo); + assertIsBroadcastTxSuccess(result); + }); + + it("works with a custom registry and custom message", async () => { + pendingWithoutSimapp(); + const wallet = await Secp256k1HdWallet.fromMnemonic(faucet.mnemonic); + const customAminoTypes = new AminoTypes({ + 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 = { registry: customRegistry, aminoTypes: customAminoTypes }; + const client = await SigningStargateClient.connectWithSigner(simapp.tendermintUrl, wallet, options); + + const msg = { + custom_delegator_address: faucet.address0, + custom_validator_address: validator.validatorAddress, + custom_amount: coin(1234, "ustake"), + }; + const msgAny = { + typeUrl: "/cosmos.staking.v1beta1.MsgDelegate", value: msg, }; const fee = { @@ -256,12 +339,11 @@ describe("SigningStargateClient", () => { it("works with a modifying signer", async () => { pendingWithoutSimapp(); const wallet = await ModifyingSecp256k1HdWallet.fromMnemonic(faucet.mnemonic); - const options = { registry: registry }; - const client = await SigningStargateClient.connectWithSigner(simapp.tendermintUrl, wallet, options); + const client = await SigningStargateClient.connectWithSigner(simapp.tendermintUrl, wallet); const msg = { - delegator_address: faucet.address0, - validator_address: validator.validatorAddress, + delegatorAddress: faucet.address0, + validatorAddress: validator.validatorAddress, amount: coin(1234, "ustake"), }; const msgAny = {