Merge pull request #614 from cosmos/594-amino-signing
Implement Amino signing for bank/staking in Stargate
This commit is contained in:
commit
3f996b9fc7
@ -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,
|
||||
|
||||
18
packages/launchpad/types/index.d.ts
vendored
18
packages/launchpad/types/index.d.ts
vendored
@ -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,
|
||||
|
||||
@ -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);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@ -1,61 +1,386 @@
|
||||
const defaultTypes: Record<string, string> = {
|
||||
"/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<string, AminoConverter> {
|
||||
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<string, AminoConverter>;
|
||||
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<string, string>;
|
||||
private readonly register: Record<string, AminoConverter>;
|
||||
|
||||
public constructor(additions: Record<string, string> = {}) {
|
||||
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),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@ -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 = {
|
||||
|
||||
@ -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<CosmosFeeTable>;
|
||||
}
|
||||
@ -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<CosmosFeeTable>(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({
|
||||
|
||||
18
packages/stargate/types/aminotypes.d.ts
vendored
18
packages/stargate/types/aminotypes.d.ts
vendored
@ -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<string, AminoConverter>;
|
||||
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<string, string>);
|
||||
toAmino(typeUrl: string): string;
|
||||
fromAmino(type: string): string;
|
||||
constructor({ additions, prefix }?: AminoTypesOptions);
|
||||
toAmino({ typeUrl, value }: EncodeObject): Msg;
|
||||
fromAmino({ type, value }: Msg): EncodeObject;
|
||||
}
|
||||
export {};
|
||||
|
||||
@ -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<CosmosFeeTable>;
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user