diff --git a/README.md b/README.md index fe11878e..ce368044 100644 --- a/README.md +++ b/README.md @@ -64,12 +64,13 @@ const lcd = LCDClient.fromDefaultConfig('mainnet'); // To use LocalTerra or a custom endpoint const lcd = new LCDClient({ - localterra: { // key must be the chainID + localterra: { + // key must be the chainID lcd: 'http://localhost:1317', chainID: 'localterra', gasAdjustment: 1.75, gasPrices: { uluna: 0.15 }, - prefix: 'terra', // bech32 prefix, used by the LCD to understand which is the right chain to query + prefix: 'terra', // bech32 prefix, used by the LCD to understand which is the right chain to query }, }); @@ -117,9 +118,44 @@ wallet .createAndSignTx({ msgs: [send], memo: 'test from feather.js!', - chainID: 'pisco-1' // now here a chainID must be specified + chainID: 'pisco-1', // now here a chainID must be specified }) - .then(tx => lcd.tx.broadcast(tx, 'pisco-1')) // same here + .then(tx => lcd.tx.broadcast(tx, 'pisco-1')) // same here + .then(result => { + console.log(`TX hash: ${result.txhash}`); + }); +``` + +### Tx with messages from custom modules + +```ts +wallet + // feather js detect that the tx contains a MsgAminoCustom and will use SIGN_MODE_AMINO_JSON instead of SIGN_MODE_DIRECT + .createAndSignTx({ + msgs: [ + new MsgAminoCustom({ + type: 'osmosis/gamm/swap-exact-amount-in', + value: { + sender: 'osmo1...', + routes: [ + { + pool_id: '2', + token_out_denom: 'uion', + }, + ], + token_in: { + denom: 'uosmo', + amount: '10000000', + }, + token_out_min_amount: '8538', + }, + }), + // you can add more messages here if needed + ], + memo: 'test from feather.js!', + chainID: 'osmosis-1', + }) + .then(tx => lcd.tx.broadcast(tx, 'osmosis-1')) .then(result => { console.log(`TX hash: ${result.txhash}`); }); diff --git a/src/client/lcd/Wallet.ts b/src/client/lcd/Wallet.ts index ba048b45..56bbec96 100644 --- a/src/client/lcd/Wallet.ts +++ b/src/client/lcd/Wallet.ts @@ -4,6 +4,7 @@ import { CreateTxOptions } from '../lcd/api/TxAPI'; import { Tx } from '../../core/Tx'; import { SignMode as SignModeV1 } from '@terra-money/legacy.proto/cosmos/tx/signing/v1beta1/signing'; import { SignMode as SignModeV2 } from '@terra-money/terra.proto/cosmos/tx/signing/v1beta1/signing'; +import { MsgAminoCustom } from 'core'; export class Wallet { constructor(public lcd: LCDClient, public key: Key) {} @@ -89,7 +90,9 @@ export class Wallet { chainID: options.chainID, signMode: options.signMode || - (this.lcd.config[options.chainID].isClassic + (options.msgs.find(m => m instanceof MsgAminoCustom) + ? SignModeV2.SIGN_MODE_LEGACY_AMINO_JSON + : this.lcd.config[options.chainID].isClassic ? SignModeV1.SIGN_MODE_DIRECT : SignModeV2.SIGN_MODE_DIRECT), }, diff --git a/src/core/Msg.ts b/src/core/Msg.ts index 6e552787..e383f29f 100644 --- a/src/core/Msg.ts +++ b/src/core/Msg.ts @@ -46,6 +46,7 @@ import { MsgRedelegate as AMsgRedelegate, MsgUndelegate as AMsgUndelegate, } from './alliance/msgs'; +import { CustomMsg, MsgAminoCustom } from './custom/msgs'; import { MsgStoreCode, MsgMigrateCode, @@ -103,6 +104,7 @@ export type Msg = | IbcConnectionMsg | IbcChannelMsg | AllianceMsg + | CustomMsg | MsgLiquidStake | MsgRedeemStake | CrisisMsg; @@ -119,6 +121,7 @@ export namespace Msg { | VestingMsg.Amino | WasmMsg.Amino | IbcTransferMsg.Amino + | CustomMsg.Amino | CrisisMsg.Amino; export type Data = @@ -139,6 +142,7 @@ export namespace Msg { | AMsgDelegate.Data | AMsgRedelegate.Data | AMsgUndelegate.Data + | CustomMsg.Data | MsgLiquidStake.Data | MsgRedeemStake.Data | CrisisMsg.Data; @@ -170,111 +174,187 @@ export namespace Msg { // bank case 'bank/MsgSend': case 'cosmos-sdk/MsgSend': - return MsgSend.fromAmino(data, isClassic); + return MsgSend.fromAmino(data as MsgSend.Amino, isClassic); case 'bank/MsgMultiSend': case 'cosmos-sdk/MsgMultiSend': - return MsgMultiSend.fromAmino(data, isClassic); + return MsgMultiSend.fromAmino(data as MsgMultiSend.Amino, isClassic); // distribution case 'distribution/MsgModifyWithdrawAddress': case 'cosmos-sdk/MsgModifyWithdrawAddress': - return MsgSetWithdrawAddress.fromAmino(data, isClassic); + return MsgSetWithdrawAddress.fromAmino( + data as MsgSetWithdrawAddress.Amino, + isClassic + ); case 'distribution/MsgWithdrawDelegationReward': case 'cosmos-sdk/MsgWithdrawDelegationReward': - return MsgWithdrawDelegatorReward.fromAmino(data, isClassic); + return MsgWithdrawDelegatorReward.fromAmino( + data as MsgWithdrawDelegatorReward.Amino, + isClassic + ); case 'distribution/MsgWithdrawValidatorCommission': case 'cosmos-sdk/MsgWithdrawValidatorCommission': - return MsgWithdrawValidatorCommission.fromAmino(data, isClassic); + return MsgWithdrawValidatorCommission.fromAmino( + data as MsgWithdrawValidatorCommission.Amino, + isClassic + ); case 'distribution/MsgFundCommunityPool': case 'cosmos-sdk/MsgFundCommunityPool': - return MsgFundCommunityPool.fromAmino(data, isClassic); + return MsgFundCommunityPool.fromAmino( + data as MsgFundCommunityPool.Amino, + isClassic + ); // feegrant case 'feegrant/MsgGrantAllowance': case 'cosmos-sdk/MsgGrantAllowance': - return MsgGrantAllowance.fromAmino(data, isClassic); + return MsgGrantAllowance.fromAmino( + data as MsgGrantAllowance.Amino, + isClassic + ); case 'feegrant/MsgRevokeAllowance': case 'cosmos-sdk/MsgRevokeAllowance': - return MsgRevokeAllowance.fromAmino(data, isClassic); + return MsgRevokeAllowance.fromAmino( + data as MsgRevokeAllowance.Amino, + isClassic + ); // gov case 'gov/MsgDeposit': case 'cosmos-sdk/MsgDeposit': - return MsgDeposit.fromAmino(data, isClassic); + return MsgDeposit.fromAmino(data as MsgDeposit.Amino, isClassic); case 'gov/MsgSubmitProposal': case 'cosmos-sdk/MsgSubmitProposal': - return MsgSubmitProposal.fromAmino(data, isClassic); + return MsgSubmitProposal.fromAmino( + data as MsgSubmitProposal.Amino, + isClassic + ); case 'gov/MsgVote': case 'cosmos-sdk/MsgVote': - return MsgVote.fromAmino(data, isClassic); + return MsgVote.fromAmino(data as MsgVote.Amino, isClassic); case 'gov/MsgVoteWeighted': case 'cosmos-sdk/MsgVoteWeighted': - return MsgVoteWeighted.fromAmino(data, isClassic); + return MsgVoteWeighted.fromAmino( + data as MsgVoteWeighted.Amino, + isClassic + ); // msgauth case 'msgauth/MsgGrantAuthorization': case 'cosmos-sdk/MsgGrant': - return MsgGrantAuthorization.fromAmino(data, isClassic); + return MsgGrantAuthorization.fromAmino( + data as MsgGrantAuthorization.Amino, + isClassic + ); case 'msgauth/MsgRevokeAuthorization': case 'cosmos-sdk/MsgRevoke': - return MsgRevokeAuthorization.fromAmino(data, isClassic); + return MsgRevokeAuthorization.fromAmino( + data as MsgRevokeAuthorization.Amino, + isClassic + ); case 'msgauth/MsgExecAuthorized': case 'cosmos-sdk/MsgExec': - return MsgExecAuthorized.fromAmino(data, isClassic); + return MsgExecAuthorized.fromAmino( + data as MsgExecAuthorized.Amino, + isClassic + ); // slashing case 'slashing/MsgUnjail': case 'cosmos-sdk/MsgUnjail': - return MsgUnjail.fromAmino(data, isClassic); + return MsgUnjail.fromAmino(data as MsgUnjail.Amino, isClassic); // staking case 'staking/MsgDelegate': case 'cosmos-sdk/MsgDelegate': - return MsgDelegate.fromAmino(data, isClassic); + return MsgDelegate.fromAmino(data as MsgDelegate.Amino, isClassic); case 'staking/MsgUndelegate': case 'cosmos-sdk/MsgUndelegate': - return MsgUndelegate.fromAmino(data, isClassic); + return MsgUndelegate.fromAmino(data as MsgUndelegate.Amino, isClassic); case 'staking/MsgBeginRedelegate': case 'cosmos-sdk/MsgBeginRedelegate': - return MsgBeginRedelegate.fromAmino(data, isClassic); + return MsgBeginRedelegate.fromAmino( + data as MsgBeginRedelegate.Amino, + isClassic + ); case 'staking/MsgCreateValidator': case 'cosmos-sdk/MsgCreateValidator': - return MsgCreateValidator.fromAmino(data, isClassic); + return MsgCreateValidator.fromAmino( + data as MsgCreateValidator.Amino, + isClassic + ); case 'staking/MsgEditValidator': case 'cosmos-sdk/MsgEditValidator': - return MsgEditValidator.fromAmino(data, isClassic); + return MsgEditValidator.fromAmino( + data as MsgEditValidator.Amino, + isClassic + ); // vesting case 'cosmos-sdk/MsgCreatePeriodicVestingAccount': - return MsgCreatePeriodicVestingAccount.fromAmino(data, isClassic); + return MsgCreatePeriodicVestingAccount.fromAmino( + data as MsgCreatePeriodicVestingAccount.Amino, + isClassic + ); case 'cosmos-sdk/MsgCreateVestingAccount': - return MsgCreateVestingAccount.fromAmino(data, isClassic); + return MsgCreateVestingAccount.fromAmino( + data as MsgCreateVestingAccount.Amino, + isClassic + ); case 'cosmos-sdk/MsgDonateAllVestingTokens': - return MsgDonateAllVestingTokens.fromAmino(data, isClassic); + return MsgDonateAllVestingTokens.fromAmino( + data as MsgDonateAllVestingTokens.Amino, + isClassic + ); // wasm case 'wasm/MsgStoreCode': - return MsgStoreCode.fromAmino(data, isClassic); + return MsgStoreCode.fromAmino(data as MsgStoreCode.Amino, isClassic); case 'wasm/MsgMigrateCode': - return MsgMigrateCode.fromAmino(data, isClassic); + return MsgMigrateCode.fromAmino( + data as MsgMigrateCode.Amino, + isClassic + ); case 'wasm/MsgInstantiateContract': - return MsgInstantiateContract.fromAmino(data, isClassic); + return MsgInstantiateContract.fromAmino( + data as MsgInstantiateContract.Amino, + isClassic + ); case 'wasm/MsgExecuteContract': - return MsgExecuteContract.fromAmino(data, isClassic); + return MsgExecuteContract.fromAmino( + data as MsgExecuteContract.Amino, + isClassic + ); case 'wasm/MsgMigrateContract': - return MsgMigrateContract.fromAmino(data, isClassic); + return MsgMigrateContract.fromAmino( + data as MsgMigrateContract.Amino, + isClassic + ); case 'wasm/MsgUpdateContractAdmin': case 'wasm/MsgUpdateAdmin': - return MsgUpdateContractAdmin.fromAmino(data, isClassic); + return MsgUpdateContractAdmin.fromAmino( + data as MsgUpdateContractAdmin.Amino, + isClassic + ); case 'wasm/MsgClearContractAdmin': case 'wasm/MsgClearAdmin': - return MsgClearContractAdmin.fromAmino(data, isClassic); + return MsgClearContractAdmin.fromAmino( + data as MsgClearContractAdmin.Amino, + isClassic + ); // ibc-transfer case 'cosmos-sdk/MsgTransfer': - return MsgTransfer.fromAmino(data, isClassic); + return MsgTransfer.fromAmino(data as MsgTransfer.Amino, isClassic); // crisis case 'crisis/MsgVerifyInvariant': case 'cosmos-sdk/MsgVerifyInvariant': - return MsgVerifyInvariant.fromAmino(data, isClassic); + return MsgVerifyInvariant.fromAmino( + data as MsgVerifyInvariant.Amino, + isClassic + ); + + // custom + default: + return MsgAminoCustom.fromAmino(data, isClassic); } } export function fromData(data: Msg.Data, isClassic?: boolean): Msg { @@ -428,8 +508,10 @@ export namespace Msg { // crisis case '/cosmos.crisis.v1beta1.MsgVerifyInvariant': return MsgVerifyInvariant.fromData(data, isClassic); + + // custom default: - throw Error(`not supported msg ${data['@type']}`); + return MsgAminoCustom.fromData(data, isClassic); } } diff --git a/src/core/custom/msgs/MsgAminoCustom.ts b/src/core/custom/msgs/MsgAminoCustom.ts new file mode 100644 index 00000000..057c0752 --- /dev/null +++ b/src/core/custom/msgs/MsgAminoCustom.ts @@ -0,0 +1,79 @@ +import { JSONSerializable } from '../../../util/json'; + +/** + * A delegator can submit this message to send more alliance assets + * to be staked through the alliance module in a validator. + */ +export class MsgAminoCustom extends JSONSerializable< + MsgAminoCustom.Amino, + MsgAminoCustom.Data, + {} +> { + /** + * + * @param delegatorAddress delegator's account address + * @param validatorAddress validator's operator address + * @param amount amount of alliance assets to be sent for delegation + */ + constructor(public aminoMsg: MsgAminoCustom.Amino) { + super(); + } + + public toAmino(_?: boolean): MsgAminoCustom.Amino { + _; + return this.aminoMsg; + } + + public static fromAmino( + data: MsgAminoCustom.Amino, + _?: boolean + ): MsgAminoCustom { + _; + return new MsgAminoCustom(data); + } + + public toProto(_?: boolean): {} { + _; + throw new Error('Protobuf not supported for MsgAminoCustom'); + } + + public packAny(_?: boolean): {} { + _; + throw new Error('Protobuf not supported for MsgAminoCustom'); + } + + public static unpackAny(msgAny: {}, _?: boolean): {} { + msgAny; + _; + throw new Error('Protobuf not supported for MsgAminoCustom'); + } + + public static fromData( + data: MsgAminoCustom.Data, + _?: boolean + ): MsgAminoCustom { + _; + const { msg } = data; + return new MsgAminoCustom(msg); + } + + public toData(_?: boolean): MsgAminoCustom.Data { + _; + return { + '@type': 'MsgCustomAmino', + msg: this.aminoMsg, + }; + } +} + +export namespace MsgAminoCustom { + export interface Data { + '@type': 'MsgCustomAmino'; + msg: MsgAminoCustom.Amino; + } + + export interface Amino { + type: string; + value: Object; + } +} diff --git a/src/core/custom/msgs/index.ts b/src/core/custom/msgs/index.ts new file mode 100644 index 00000000..ad9e639b --- /dev/null +++ b/src/core/custom/msgs/index.ts @@ -0,0 +1,9 @@ +import { MsgAminoCustom } from './MsgAminoCustom'; + +export * from './MsgAminoCustom'; + +export type CustomMsg = MsgAminoCustom; +export namespace CustomMsg { + export type Amino = MsgAminoCustom.Amino; + export type Data = MsgAminoCustom.Data; +} diff --git a/src/core/index.ts b/src/core/index.ts index 6c7fe2bb..ef3d2cf1 100644 --- a/src/core/index.ts +++ b/src/core/index.ts @@ -14,6 +14,8 @@ export * from './Deposit'; export * from './SignatureV2'; export * from './MultiSignature'; +// Custom +export * from './custom/msgs'; // Stride export { MsgLiquidStake } from './stride/msgs'; export { MsgRedeemStake } from './stride/msgs';