Create toBech32 and fromBech32 in @cosmjs/encoding (#1058)

* Create toBech32 and fromBech32

* Add changes to CHANGELOG.md

* Fix linting

* Update packages/encoding/src/bech32.ts

Co-authored-by: Simon Warta <2603011+webmaster128@users.noreply.github.com>

* Update CHANGELOG.md

Co-authored-by: Simon Warta <2603011+webmaster128@users.noreply.github.com>

* Update packages/encoding/src/bech32.ts

Co-authored-by: Simon Warta <2603011+webmaster128@users.noreply.github.com>

* Update bech32.ts

Co-authored-by: Simon Warta <2603011+webmaster128@users.noreply.github.com>
This commit is contained in:
Milan Steiner 2022-02-22 14:05:34 +01:00 committed by GitHub
parent 9b80dff493
commit 4c8b278c1d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
20 changed files with 84 additions and 61 deletions

View File

@ -37,6 +37,8 @@ and this project adheres to
identifier which should not be the case anyways.
- @cosmjs/stargate: Added support for slashing queries ([#927])
- @cosmjs/ledger-amino: Renamed `LaunchpadLedger` to `LedgerConnector` ([#955])
- @cosmjs/encoding: Created `toBech32()` and `fromBech32()`. Class Bech32 is now
deprecated and should not longer be used. ([#1053])
[#927]: https://github.com/cosmos/cosmjs/issues/927
[#955]: https://github.com/cosmos/cosmjs/issues/955
@ -44,6 +46,7 @@ and this project adheres to
[#994]: https://github.com/cosmos/cosmjs/issues/994
[#1011]: https://github.com/cosmos/cosmjs/issues/1011
[#1026]: https://github.com/cosmos/cosmjs/issues/1026
[#1053]: https://github.com/cosmos/cosmjs/issues/1053
### Removed

View File

@ -1,4 +1,4 @@
import { Bech32, fromHex, toBase64 } from "@cosmjs/encoding";
import { fromBech32, fromHex, toBase64 } from "@cosmjs/encoding";
import { pubkeyToAddress, pubkeyToRawAddress } from "./addresses";
import { decodeBech32Pubkey } from "./encoding";
@ -12,7 +12,7 @@ describe("addresses", () => {
value: "AtQaCqFnshaZQp6rIkvAPyzThvCvXSDO+9AzbxVErqJP",
};
expect(pubkeyToRawAddress(pubkey)).toEqual(
Bech32.decode("cosmos1h806c7khnvmjlywdrkdgk2vrayy2mmvf9rxk2r").data,
fromBech32("cosmos1h806c7khnvmjlywdrkdgk2vrayy2mmvf9rxk2r").data,
);
});
@ -22,7 +22,7 @@ describe("addresses", () => {
value: toBase64(fromHex("12ee6f581fe55673a1e9e1382a0829e32075a0aa4763c968bc526e1852e78c95")),
};
expect(pubkeyToRawAddress(pubkey)).toEqual(
Bech32.decode("cosmos1pfq05em6sfkls66ut4m2257p7qwlk448h8mysz").data,
fromBech32("cosmos1pfq05em6sfkls66ut4m2257p7qwlk448h8mysz").data,
);
});

View File

@ -1,7 +1,7 @@
// See https://github.com/tendermint/tendermint/blob/f2ada0a604b4c0763bda2f64fac53d506d3beca7/docs/spec/blockchain/encoding.md#public-key-cryptography
import { ripemd160, sha256 } from "@cosmjs/crypto";
import { Bech32, fromBase64 } from "@cosmjs/encoding";
import { fromBase64, toBech32 } from "@cosmjs/encoding";
import { encodeAminoPubkey } from "./encoding";
import { isEd25519Pubkey, isMultisigThresholdPubkey, isSecp256k1Pubkey, Pubkey } from "./pubkeys";
@ -38,5 +38,5 @@ export function pubkeyToRawAddress(pubkey: Pubkey): Uint8Array {
}
export function pubkeyToAddress(pubkey: Pubkey, prefix: string): string {
return Bech32.encode(prefix, pubkeyToRawAddress(pubkey));
return toBech32(prefix, pubkeyToRawAddress(pubkey));
}

View File

@ -1,4 +1,4 @@
import { Bech32, fromBase64, fromHex } from "@cosmjs/encoding";
import { fromBase64, fromBech32, fromHex } from "@cosmjs/encoding";
import {
decodeAminoPubkey,
@ -39,7 +39,7 @@ describe("encoding", () => {
describe("decodeAminoPubkey", () => {
it("works for secp256k1", () => {
const amino = Bech32.decode(
const amino = fromBech32(
"cosmospub1addwnpepqd8sgxq7aw348ydctp3n5ajufgxp395hksxjzc6565yfp56scupfqhlgyg5",
).data;
expect(decodeAminoPubkey(amino)).toEqual({
@ -51,7 +51,7 @@ describe("encoding", () => {
it("works for ed25519", () => {
// Encoded from `corald tendermint show-validator`
// Decoded from http://localhost:26657/validators
const amino = Bech32.decode(
const amino = fromBech32(
"coralvalconspub1zcjduepqvxg72ccnl9r65fv0wn3amlk4sfzqfe2k36l073kjx2qyaf6sk23qw7j8wq",
).data;
expect(decodeAminoPubkey(amino)).toEqual({
@ -65,7 +65,7 @@ describe("encoding", () => {
});
it("works for multisig", () => {
const pubkeyData = Bech32.decode(
const pubkeyData = fromBech32(
"cosmospub1addwnpepqd8sgxq7aw348ydctp3n5ajufgxp395hksxjzc6565yfp56scupfqhlgyg5",
).data;
const pubkey = {
@ -156,7 +156,7 @@ describe("encoding", () => {
type: "tendermint/PubKeySecp256k1",
value: "A08EGB7ro1ORuFhjOnZcSgwYlpe0DSFjVNUIkNNQxwKQ",
};
const expected = Bech32.decode(
const expected = fromBech32(
"cosmospub1addwnpepqd8sgxq7aw348ydctp3n5ajufgxp395hksxjzc6565yfp56scupfqhlgyg5",
).data;
expect(encodeAminoPubkey(pubkey)).toEqual(expected);
@ -169,7 +169,7 @@ describe("encoding", () => {
type: "tendermint/PubKeyEd25519",
value: "YZHlYxP5R6olj3Tj3f7VgkQE5VaOvv9G0jKATqdQsqI=",
};
const expected = Bech32.decode(
const expected = fromBech32(
"coralvalconspub1zcjduepqvxg72ccnl9r65fv0wn3amlk4sfzqfe2k36l073kjx2qyaf6sk23qw7j8wq",
).data;
expect(encodeAminoPubkey(pubkey)).toEqual(expected);
@ -200,16 +200,16 @@ describe("encoding", () => {
});
it("works for multisig", () => {
const expected1 = Bech32.decode(testgroup1PubkeyBech32).data;
const expected1 = fromBech32(testgroup1PubkeyBech32).data;
expect(encodeAminoPubkey(testgroup1)).toEqual(expected1);
const expected2 = Bech32.decode(testgroup2PubkeyBech32).data;
const expected2 = fromBech32(testgroup2PubkeyBech32).data;
expect(encodeAminoPubkey(testgroup2)).toEqual(expected2);
const expected3 = Bech32.decode(testgroup3PubkeyBech32).data;
const expected3 = fromBech32(testgroup3PubkeyBech32).data;
expect(encodeAminoPubkey(testgroup3)).toEqual(expected3);
const expected4 = Bech32.decode(testgroup4PubkeyBech32).data;
const expected4 = fromBech32(testgroup4PubkeyBech32).data;
expect(encodeAminoPubkey(testgroup4)).toEqual(expected4);
});
});

View File

@ -1,4 +1,4 @@
import { Bech32, fromBase64, fromHex, toBase64, toHex } from "@cosmjs/encoding";
import { fromBase64, fromBech32, fromHex, toBase64, toBech32, toHex } from "@cosmjs/encoding";
import { Uint53 } from "@cosmjs/math";
import { arrayContentStartsWith } from "@cosmjs/utils";
@ -77,7 +77,7 @@ export function decodeAminoPubkey(data: Uint8Array): Pubkey {
* @param bechEncoded the bech32 encoded pubkey
*/
export function decodeBech32Pubkey(bechEncoded: string): Pubkey {
const { data } = Bech32.decode(bechEncoded);
const { data } = fromBech32(bechEncoded);
return decodeAminoPubkey(data);
}
@ -200,5 +200,5 @@ export function encodeAminoPubkey(pubkey: Pubkey): Uint8Array {
* @param prefix the bech32 prefix (human readable part)
*/
export function encodeBech32Pubkey(pubkey: Pubkey, prefix: string): string {
return Bech32.encode(prefix, encodeAminoPubkey(pubkey));
return toBech32(prefix, encodeAminoPubkey(pubkey));
}

View File

@ -11,7 +11,7 @@ import {
Slip10Curve,
stringToPath,
} from "@cosmjs/crypto";
import { Bech32, fromBase64, fromUtf8, toBase64, toUtf8 } from "@cosmjs/encoding";
import { fromBase64, fromUtf8, toBase64, toBech32, toUtf8 } from "@cosmjs/encoding";
import { assert, isNonNullObject } from "@cosmjs/utils";
import { rawSecp256k1PubkeyToRawAddress } from "./addresses";
@ -346,7 +346,7 @@ export class Secp256k1HdWallet implements OfflineAminoSigner {
return Promise.all(
this.accounts.map(async ({ hdPath, prefix }) => {
const { privkey, pubkey } = await this.getKeyPair(hdPath);
const address = Bech32.encode(prefix, rawSecp256k1PubkeyToRawAddress(pubkey));
const address = toBech32(prefix, rawSecp256k1PubkeyToRawAddress(pubkey));
return {
algo: "secp256k1" as const,
privkey: privkey,

View File

@ -1,5 +1,5 @@
import { Secp256k1, Sha256 } from "@cosmjs/crypto";
import { Bech32 } from "@cosmjs/encoding";
import { toBech32 } from "@cosmjs/encoding";
import { rawSecp256k1PubkeyToRawAddress } from "./addresses";
import { encodeSecp256k1Signature } from "./signature";
@ -34,7 +34,7 @@ export class Secp256k1Wallet implements OfflineAminoSigner {
}
private get address(): string {
return Bech32.encode(this.prefix, rawSecp256k1PubkeyToRawAddress(this.pubkey));
return toBech32(this.prefix, rawSecp256k1PubkeyToRawAddress(this.pubkey));
}
public async getAccounts(): Promise<readonly AccountData[]> {

View File

@ -1,11 +1,11 @@
/* eslint-disable @typescript-eslint/naming-convention */
import { Random } from "@cosmjs/crypto";
import { Bech32 } from "@cosmjs/encoding";
import { toBech32 } from "@cosmjs/encoding";
import { AminoMsg, makeSignDoc, sortedJsonStringify } from "./signdoc";
function makeRandomAddress(): string {
return Bech32.encode("cosmos", Random.getBytes(20));
return toBech32("cosmos", Random.getBytes(20));
}
const testAddress = "cosmos1pkptre7fdkl6gfrzlesjjvhxhlc3r4gmmk8rs6";
const testValidatorAddress = "cosmosvaloper1yfkkk04ve8a0sugj4fe6q6zxuvmvza8r3arurr";

View File

@ -1,7 +1,7 @@
/* eslint-disable @typescript-eslint/naming-convention */
import { AminoSignResponse, Secp256k1HdWallet, Secp256k1HdWalletOptions, StdSignDoc } from "@cosmjs/amino";
import { Bip39, EnglishMnemonic, Random } from "@cosmjs/crypto";
import { Bech32, fromBase64 } from "@cosmjs/encoding";
import { fromBase64, toBech32 } from "@cosmjs/encoding";
import {
DirectSecp256k1HdWallet,
DirectSecp256k1HdWalletOptions,
@ -64,7 +64,7 @@ export function getHackatom(): ContractUploadInstructions {
}
export function makeRandomAddress(): string {
return Bech32.encode("wasm", Random.getBytes(20));
return toBech32("wasm", Random.getBytes(20));
}
export const tendermintIdMatcher = /^[0-9A-F]{64}$/;

View File

@ -10,9 +10,9 @@ on invalid input.
## Convert between bech32 and hex addresses
```
>> Bech32.encode("tiov", fromHex("1234ABCD0000AA0000FFFF0000AA00001234ABCD"))
>> toBech32("tiov", fromHex("1234ABCD0000AA0000FFFF0000AA00001234ABCD"))
'tiov1zg62hngqqz4qqq8lluqqp2sqqqfrf27dzrrmea'
>> toHex(Bech32.decode("tiov1zg62hngqqz4qqq8lluqqp2sqqqfrf27dzrrmea").data)
>> toHex(fromBech32("tiov1zg62hngqqz4qqq8lluqqp2sqqqfrf27dzrrmea").data)
'1234abcd0000aa0000ffff0000aa00001234abcd'
```

View File

@ -1,4 +1,4 @@
import { Bech32 } from "./bech32";
import { fromBech32, toBech32 } from "./bech32";
import { fromHex } from "./hex";
describe("Bech32", () => {
@ -8,30 +8,30 @@ describe("Bech32", () => {
describe("encode", () => {
it("works", () => {
expect(Bech32.encode("eth", ethAddressRaw)).toEqual("eth1n48g2mjh9ezz7zjtya37wtgg5r5emr0drkwlgw");
expect(toBech32("eth", ethAddressRaw)).toEqual("eth1n48g2mjh9ezz7zjtya37wtgg5r5emr0drkwlgw");
});
it("works for very short data", () => {
expect(() => Bech32.encode("eth", new Uint8Array(1))).not.toThrow();
expect(() => toBech32("eth", new Uint8Array(1))).not.toThrow();
});
it("works for very long prefixes", () => {
expect(() => Bech32.encode("p".repeat(70), new Uint8Array(20))).toThrowError(/exceeds length limit/i);
expect(() => toBech32("p".repeat(70), new Uint8Array(20))).toThrowError(/exceeds length limit/i);
});
// See https://github.com/bitcoin/bips/blob/master/bip-0173.mediawiki#Bech32
it("works if result is 90 characters", () => {
const result = Bech32.encode("eth", new Uint8Array(50));
const result = toBech32("eth", new Uint8Array(50));
expect(result.length).toEqual(90);
});
it("throws if result exceeds 90 characters", () => {
expect(() => Bech32.encode("eth", new Uint8Array(51))).toThrowError(/exceeds length limit/i);
expect(() => toBech32("eth", new Uint8Array(51))).toThrowError(/exceeds length limit/i);
});
it("works if a limit parameter is provided", () => {
const limit = 1024;
const result = Bech32.encode("eth", new Uint8Array(51), limit);
const result = toBech32("eth", new Uint8Array(51), limit);
expect(result).toEqual(
"eth1qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqug55er",
);
@ -40,13 +40,13 @@ describe("Bech32", () => {
it("throws if result exceeds the provided limit parameter", () => {
const limit = 10;
expect(() => Bech32.encode("eth", ethAddressRaw, limit)).toThrowError(/exceeds length limit/i);
expect(() => toBech32("eth", ethAddressRaw, limit)).toThrowError(/exceeds length limit/i);
});
});
describe("decode", () => {
it("works", () => {
expect(Bech32.decode("eth1n48g2mjh9ezz7zjtya37wtgg5r5emr0drkwlgw")).toEqual({
expect(fromBech32("eth1n48g2mjh9ezz7zjtya37wtgg5r5emr0drkwlgw")).toEqual({
prefix: "eth",
data: ethAddressRaw,
});
@ -55,7 +55,7 @@ describe("Bech32", () => {
it("works for addresses which exceed the specification limit of 90 characters", () => {
// Example from https://github.com/cosmos/cosmos-sdk/pull/6237#issuecomment-658116534
expect(() =>
Bech32.decode(
fromBech32(
"cosmospub1ytql0csgqvfzd666axrjzqmn5q2ucztcyxw8hvlzen94ay05tegaerkug5pn3xn8wqdymt598ufzd666axrjzqsxllmwacap3f6xyc4x30jl8ecrcs2tze3zzgxkmthcsqxnqxhwwgfzd666axrjzqs2rlu3wz5gnslgpprszjr8r65n0d6y39q657th77eyvengtk3z0y6h2pnk",
),
).not.toThrow();
@ -64,7 +64,7 @@ describe("Bech32", () => {
it("throws for addresses which exceed the specification limit of 90 characters if a limit is specified", () => {
// Example from https://github.com/cosmos/cosmos-sdk/pull/6237#issuecomment-658116534
expect(() =>
Bech32.decode(
fromBech32(
"cosmospub1ytql0csgqvfzd666axrjzqmn5q2ucztcyxw8hvlzen94ay05tegaerkug5pn3xn8wqdymt598ufzd666axrjzqsxllmwacap3f6xyc4x30jl8ecrcs2tze3zzgxkmthcsqxnqxhwwgfzd666axrjzqs2rlu3wz5gnslgpprszjr8r65n0d6y39q657th77eyvengtk3z0y6h2pnk",
90,
),

View File

@ -1,19 +1,39 @@
import * as bech32 from "bech32";
export function toBech32(prefix: string, data: Uint8Array, limit?: number): string {
const address = bech32.encode(prefix, bech32.toWords(data), limit);
return address;
}
export function fromBech32(
address: string,
limit = Infinity,
): { readonly prefix: string; readonly data: Uint8Array } {
const decodedAddress = bech32.decode(address, limit);
return {
prefix: decodedAddress.prefix,
data: new Uint8Array(bech32.fromWords(decodedAddress.words)),
};
}
/**
* @deprecated This class is deprecated and will be removed soon. Please use fromBech32() and toBech32() instead. For more details please refer to https://github.com/cosmos/cosmjs/issues/1053.
*/
export class Bech32 {
/**
* @deprecated This class is deprecated and will be removed soon. Please use fromBech32() and toBech32() instead. For more details please refer to https://github.com/cosmos/cosmjs/issues/1053.
*/
public static encode(prefix: string, data: Uint8Array, limit?: number): string {
const address = bech32.encode(prefix, bech32.toWords(data), limit);
return address;
return toBech32(prefix, data, limit);
}
/**
* @deprecated This class is deprecated and will be removed soon. Please use fromBech32() and toBech32() instead. For more details please refer to https://github.com/cosmos/cosmjs/issues/1053.
*/
public static decode(
address: string,
limit = Infinity,
): { readonly prefix: string; readonly data: Uint8Array } {
const decodedAddress = bech32.decode(address, limit);
return {
prefix: decodedAddress.prefix,
data: new Uint8Array(bech32.fromWords(decodedAddress.words)),
};
return fromBech32(address, limit);
}
}

View File

@ -1,6 +1,6 @@
export { fromAscii, toAscii } from "./ascii";
export { fromBase64, toBase64 } from "./base64";
export { Bech32 } from "./bech32";
export { Bech32, fromBech32, toBech32 } from "./bech32";
export { fromHex, toHex } from "./hex";
export { fromRfc3339, toRfc3339 } from "./rfc3339";
export { fromUtf8, toUtf8 } from "./utf8";

View File

@ -1,5 +1,5 @@
import { Random } from "@cosmjs/crypto";
import { Bech32 } from "@cosmjs/encoding";
import { toBech32 } from "@cosmjs/encoding";
import { makeCosmoshubPath, StargateClient } from "@cosmjs/stargate";
import { assert } from "@cosmjs/utils";
@ -18,7 +18,7 @@ const defaultTokenConfig: TokenConfiguration = {
const defaultAddressPrefix = "cosmos";
function makeRandomAddress(): string {
return Bech32.encode(defaultAddressPrefix, Random.getBytes(20));
return toBech32(defaultAddressPrefix, Random.getBytes(20));
}
const faucetMnemonic =

View File

@ -12,7 +12,7 @@ import {
Slip10Curve,
stringToPath,
} from "@cosmjs/crypto";
import { Bech32, fromBase64, fromUtf8, toBase64, toUtf8 } from "@cosmjs/encoding";
import { fromBase64, fromUtf8, toBase64, toBech32, toUtf8 } from "@cosmjs/encoding";
import { assert, isNonNullObject } from "@cosmjs/utils";
import { SignDoc } from "cosmjs-types/cosmos/tx/v1beta1/tx";
@ -346,7 +346,7 @@ export class DirectSecp256k1HdWallet implements OfflineDirectSigner {
return Promise.all(
this.accounts.map(async ({ hdPath, prefix }) => {
const { privkey, pubkey } = await this.getKeyPair(hdPath);
const address = Bech32.encode(prefix, rawSecp256k1PubkeyToRawAddress(pubkey));
const address = toBech32(prefix, rawSecp256k1PubkeyToRawAddress(pubkey));
return {
algo: "secp256k1" as const,
privkey: privkey,

View File

@ -1,6 +1,6 @@
import { encodeSecp256k1Signature, rawSecp256k1PubkeyToRawAddress } from "@cosmjs/amino";
import { Secp256k1, sha256 } from "@cosmjs/crypto";
import { Bech32 } from "@cosmjs/encoding";
import { toBech32 } from "@cosmjs/encoding";
import { SignDoc } from "cosmjs-types/cosmos/tx/v1beta1/tx";
import { AccountData, DirectSignResponse, OfflineDirectSigner } from "./signer";
@ -34,7 +34,7 @@ export class DirectSecp256k1Wallet implements OfflineDirectSigner {
}
private get address(): string {
return Bech32.encode(this.prefix, rawSecp256k1PubkeyToRawAddress(this.pubkey));
return toBech32(this.prefix, rawSecp256k1PubkeyToRawAddress(this.pubkey));
}
public async getAccounts(): Promise<readonly AccountData[]> {

View File

@ -1,5 +1,5 @@
import { MultisigThresholdPubkey, pubkeyToAddress, StdFee } from "@cosmjs/amino";
import { Bech32 } from "@cosmjs/encoding";
import { fromBech32 } from "@cosmjs/encoding";
import { encodePubkey } from "@cosmjs/proto-signing";
import { CompactBitArray, MultiSignature } from "cosmjs-types/cosmos/crypto/multisig/v1beta1/multisig";
import { SignMode } from "cosmjs-types/cosmos/tx/signing/v1beta1/signing";
@ -30,7 +30,7 @@ export function makeMultisignedTx(
signatures: Map<string, Uint8Array>,
): TxRaw {
const addresses = Array.from(signatures.keys());
const prefix = Bech32.decode(addresses[0]).prefix;
const prefix = fromBech32(addresses[0]).prefix;
const signers: boolean[] = Array(multisigPubkey.value.pubkeys.length).fill(false);
const signaturesList = new Array<Uint8Array>();

View File

@ -1,4 +1,4 @@
import { Bech32, fromAscii } from "@cosmjs/encoding";
import { fromAscii, fromBech32 } from "@cosmjs/encoding";
import { Decimal, Uint64 } from "@cosmjs/math";
import { PageRequest } from "cosmjs-types/cosmos/base/query/v1beta1/pagination";
import Long from "long";
@ -11,7 +11,7 @@ import { QueryClient } from "./queryclient";
* The result is typically 20 bytes long but not restricted to that.
*/
export function toAccAddress(address: string): Uint8Array {
return Bech32.decode(address).data;
return fromBech32(address).data;
}
/**

View File

@ -1,7 +1,7 @@
/* eslint-disable @typescript-eslint/naming-convention */
import { AminoSignResponse, Secp256k1HdWallet, Secp256k1HdWalletOptions, StdSignDoc } from "@cosmjs/amino";
import { Bip39, EnglishMnemonic, Random } from "@cosmjs/crypto";
import { Bech32 } from "@cosmjs/encoding";
import { toBech32 } from "@cosmjs/encoding";
import {
coins,
DirectSecp256k1HdWallet,
@ -54,7 +54,7 @@ export function makeRandomAddressBytes(): Uint8Array {
}
export function makeRandomAddress(): string {
return Bech32.encode("cosmos", makeRandomAddressBytes());
return toBech32("cosmos", makeRandomAddressBytes());
}
/** Returns first element. Throws if array has a different length than 1. */

View File

@ -3,7 +3,7 @@
/* eslint-disable @typescript-eslint/naming-convention */
const { coins } = require("@cosmjs/amino");
const { Random } = require("@cosmjs/crypto");
const { Bech32 } = require("@cosmjs/encoding");
const { toBech32 } = require("@cosmjs/encoding");
const { DirectSecp256k1HdWallet } = require("@cosmjs/proto-signing");
const {
assertIsDeliverTxSuccess,
@ -23,7 +23,7 @@ const faucet = {
async function main() {
const wallet = await DirectSecp256k1HdWallet.fromMnemonic(faucet.mnemonic, { prefix: prefix });
const client = await SigningStargateClient.connectWithSigner(rpcUrl, wallet, { prefix: prefix });
const recipient = Bech32.encode(prefix, Random.getBytes(20));
const recipient = toBech32(prefix, Random.getBytes(20));
const amount = coins(226644, "ucosm");
const fee = calculateFee(100_000, "0.025ucosm");
const memo = "Ensure chain has my pubkey";