stargate: Support Amino signing in signAndBroadcast

This commit is contained in:
willclarktech 2020-10-27 12:37:59 +01:00
parent 8bf0c97c08
commit 3d57bf9e29
No known key found for this signature in database
GPG Key ID: 551A86E2E398ADF7
2 changed files with 90 additions and 10 deletions

View File

@ -1,4 +1,4 @@
import { coin, coins, GasPrice } from "@cosmjs/launchpad";
import { coin, coins, GasPrice, Secp256k1HdWallet } from "@cosmjs/launchpad";
import { DirectSecp256k1Wallet, Registry } from "@cosmjs/proto-signing";
import { assert } from "@cosmjs/utils";
@ -126,7 +126,7 @@ describe("SigningStargateClient", () => {
});
describe("signAndBroadcast", () => {
it("works", async () => {
it("works with direct mode", async () => {
pendingWithoutSimapp();
const wallet = await DirectSecp256k1Wallet.fromMnemonic(faucet.mnemonic);
const msgDelegateTypeUrl = "/cosmos.staking.v1beta1.MsgDelegate";
@ -152,5 +152,32 @@ describe("SigningStargateClient", () => {
const result = await client.signAndBroadcast(faucet.address0, [msgAny], fee, memo);
assertIsBroadcastTxSuccess(result);
});
it("works with legacy Amino mode", async () => {
pendingWithoutSimapp();
const wallet = await Secp256k1HdWallet.fromMnemonic(faucet.mnemonic);
const msgDelegateTypeUrl = "/cosmos.staking.v1beta1.MsgDelegate";
const registry = new Registry();
registry.register(msgDelegateTypeUrl, cosmos.staking.v1beta1.MsgDelegate);
const options = { registry: registry };
const client = await SigningStargateClient.connectWithWallet(simapp.tendermintUrl, wallet, options);
const msg = cosmos.staking.v1beta1.MsgDelegate.create({
delegatorAddress: faucet.address0,
validatorAddress: validator.validatorAddress,
amount: coin(1234, "ustake"),
});
const msgAny = {
typeUrl: msgDelegateTypeUrl,
value: msg,
};
const fee = {
amount: coins(2000, "ucosm"),
gas: "200000",
};
const memo = "Use your power wisely";
const result = await client.signAndBroadcast(faucet.address0, [msgAny], fee, memo);
assertIsBroadcastTxSuccess(result);
});
});
});

View File

@ -1,13 +1,17 @@
/* eslint-disable @typescript-eslint/naming-convention */
import { fromBase64 } from "@cosmjs/encoding";
import {
AccountData,
buildFeeTable,
Coin,
CosmosFeeTable,
encodeSecp256k1Pubkey,
GasLimits,
GasPrice,
makeSignDoc as makeSignDocAmino,
Msg,
StdFee,
StdSignDoc,
} from "@cosmjs/launchpad";
import { Int53 } from "@cosmjs/math";
import {
@ -26,6 +30,40 @@ import { BroadcastTxResponse, StargateClient } from "./stargateclient";
const { TxRaw } = cosmos.tx.v1beta1;
function snakifyMsgValue(obj: Msg): Msg {
return {
...obj,
value: Object.entries(obj.value).reduce(
(snakified, [key, value]) => ({
...snakified,
[key
.split(/(?=[A-Z])/)
.join("_")
.toLowerCase()]: value,
}),
{},
),
};
}
function snakifyForAmino(signDoc: StdSignDoc): StdSignDoc {
return {
...signDoc,
msgs: signDoc.msgs.map(snakifyMsgValue),
};
}
function getMsgType(typeUrl: string): string {
const typeRegister: Record<string, string> = {
"/cosmos.staking.v1beta1.MsgDelegate": "cosmos-sdk/MsgDelegate",
};
const type = typeRegister[typeUrl];
if (!type) {
throw new Error("Type URL not known");
}
return type;
}
const defaultGasPrice = GasPrice.fromString("0.025ucosm");
const defaultGasLimits: GasLimits<CosmosFeeTable> = { send: 80000 };
@ -90,12 +128,8 @@ export class SigningStargateClient extends StargateClient {
fee: StdFee,
memo = "",
): Promise<BroadcastTxResponse> {
if (!isOfflineDirectSigner(this.signer)) {
throw new Error("Amino signer not yet supported");
}
const accountFromSigner = (await this.signer.getAccounts()).find(
(account) => account.address === address,
(account: AccountData) => account.address === address,
);
if (!accountFromSigner) {
throw new Error("Failed to retrieve account from signer");
@ -120,10 +154,29 @@ export class SigningStargateClient extends StargateClient {
value: txBody,
});
const gasLimit = Int53.fromString(fee.gas).toNumber();
const authInfoBytes = makeAuthInfoBytes([pubkeyAny], fee.amount, gasLimit, sequence);
const signDoc = makeSignDoc(txBodyBytes, authInfoBytes, chainId, accountNumber);
const signResponse = await this.signer.signDirect(address, signDoc);
if (isOfflineDirectSigner(this.signer)) {
const authInfoBytes = makeAuthInfoBytes([pubkeyAny], fee.amount, gasLimit, sequence);
const signDoc = makeSignDoc(txBodyBytes, authInfoBytes, chainId, accountNumber);
const signResponse = await this.signer.signDirect(address, signDoc);
const txRaw = TxRaw.create({
bodyBytes: txBodyBytes,
authInfoBytes: authInfoBytes,
signatures: [fromBase64(signResponse.signature.signature)],
});
const signedTx = Uint8Array.from(TxRaw.encode(txRaw).finish());
return this.broadcastTx(signedTx);
}
// Amino signer
const signMode = cosmos.tx.signing.v1beta1.SignMode.SIGN_MODE_LEGACY_AMINO_JSON;
const authInfoBytes = makeAuthInfoBytes([pubkeyAny], fee.amount, gasLimit, sequence, signMode);
const msgs = messages.map((msg) => ({
type: getMsgType(msg.typeUrl),
value: msg.value,
}));
const signDoc = makeSignDocAmino(msgs, fee, chainId, memo, accountNumber, sequence);
const signResponse = await this.signer.signAmino(address, snakifyForAmino(signDoc));
const txRaw = TxRaw.create({
bodyBytes: txBodyBytes,
authInfoBytes: authInfoBytes,