stargate: Add conversion for bank/staking Amino Msg types

This commit is contained in:
willclarktech 2021-01-12 16:24:03 +00:00
parent 79cd8c0109
commit 95c292d83c
No known key found for this signature in database
GPG Key ID: 551A86E2E398ADF7
2 changed files with 372 additions and 35 deletions

View File

@ -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),
};
}
}

View File

@ -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 {};