cosmjs-util/src/cosmoscodec.ts

118 lines
3.1 KiB
TypeScript

/* eslint-disable @typescript-eslint/camelcase */
import {
Address,
ChainId,
Identity,
Nonce,
PostableBytes,
PrehashType,
SignableBytes,
SignedTransaction,
SigningJob,
TokenTicker,
TransactionId,
TxCodec,
UnsignedTransaction,
} from "@iov/bcp";
import { Sha256 } from "@iov/crypto";
import { Encoding } from "@iov/encoding";
import { marshalTx, unmarshalTx } from "@tendermint/amino-js";
import { CosmosBech32Prefix, isValidAddress, pubkeyToAddress } from "./address";
import { Caip5 } from "./caip5";
import { parseTx } from "./decode";
import { buildSignedTx, buildUnsignedTx } from "./encode";
import { TokenInfos } from "./types";
const { toHex, toUtf8 } = Encoding;
function sortJson(json: any): any {
if (typeof json !== "object" || json === null) {
return json;
}
if (Array.isArray(json)) {
return json.map(sortJson);
}
const sortedKeys = Object.keys(json).sort();
const result = sortedKeys.reduce(
(accumulator, key) => ({
...accumulator,
[key]: sortJson(json[key]),
}),
{},
);
return result;
}
export class CosmosCodec implements TxCodec {
private readonly prefix: CosmosBech32Prefix;
private readonly tokens: TokenInfos;
public constructor(prefix: CosmosBech32Prefix, tokens: TokenInfos) {
this.prefix = prefix;
this.tokens = tokens;
}
public bytesToSign(unsigned: UnsignedTransaction, nonce: Nonce): SigningJob {
const accountNumber = 0;
const memo = (unsigned as any).memo;
const built = buildUnsignedTx(unsigned, this.tokens);
const signMsg = sortJson({
account_number: accountNumber.toString(),
chain_id: Caip5.decode(unsigned.chainId),
fee: (built.value as any).fee,
memo: memo,
msgs: (built.value as any).msg,
sequence: nonce.toString(),
});
const signBytes = toUtf8(JSON.stringify(signMsg));
return {
bytes: signBytes as SignableBytes,
prehashType: PrehashType.Sha256,
};
}
public bytesToPost(signed: SignedTransaction): PostableBytes {
const built = buildSignedTx(signed, this.tokens);
const bytes = marshalTx(built, true);
return bytes as PostableBytes;
}
public identifier(signed: SignedTransaction): TransactionId {
const bytes = this.bytesToPost(signed);
const hash = new Sha256(bytes).digest();
return toHex(hash).toUpperCase() as TransactionId;
}
public parseBytes(bytes: PostableBytes, chainId: ChainId, nonce?: Nonce): SignedTransaction {
if (nonce === undefined) {
throw new Error("Nonce is required");
}
const parsed = unmarshalTx(bytes);
return parseTx(parsed, chainId, nonce, this.tokens);
}
public identityToAddress(identity: Identity): Address {
return pubkeyToAddress(identity.pubkey, this.prefix);
}
public isValidAddress(address: string): boolean {
return isValidAddress(address);
}
}
const defaultPrefix = "cosmos" as CosmosBech32Prefix;
const defaultTokens: TokenInfos = [
{
fractionalDigits: 6,
tokenName: "Atom (Cosmos Hub)",
tokenTicker: "ATOM" as TokenTicker,
denom: "uatom",
},
];
export const cosmosCodec = new CosmosCodec(defaultPrefix, defaultTokens);