Merge pull request #66 from confio/erc20-balances
Add read support for multiple ERC20 tokens
This commit is contained in:
commit
e68db46b01
@ -43,6 +43,7 @@
|
||||
"@iov/crypto": "^2.0.0-alpha.7",
|
||||
"@iov/encoding": "^2.0.0-alpha.7",
|
||||
"@iov/stream": "^2.0.0-alpha.7",
|
||||
"@iov/utils": "^2.0.0-alpha.7",
|
||||
"fast-deep-equal": "^3.1.1",
|
||||
"readonly-date": "^1.0.0",
|
||||
"xstream": "^11.11.0"
|
||||
|
||||
@ -26,13 +26,13 @@ import { pubkeyToAddress } from "./address";
|
||||
import { Caip5 } from "./caip5";
|
||||
import { parseTx } from "./decode";
|
||||
import { buildSignedTx, buildUnsignedTx } from "./encode";
|
||||
import { nonceToAccountNumber, nonceToSequence, TokenInfos } from "./types";
|
||||
import { BankTokens, nonceToAccountNumber, nonceToSequence } from "./types";
|
||||
|
||||
export class CosmWasmCodec implements TxCodec {
|
||||
private readonly addressPrefix: CosmosAddressBech32Prefix;
|
||||
private readonly tokens: TokenInfos;
|
||||
private readonly tokens: BankTokens;
|
||||
|
||||
public constructor(addressPrefix: CosmosAddressBech32Prefix, tokens: TokenInfos) {
|
||||
public constructor(addressPrefix: CosmosAddressBech32Prefix, tokens: BankTokens) {
|
||||
this.addressPrefix = addressPrefix;
|
||||
this.tokens = tokens;
|
||||
}
|
||||
@ -93,7 +93,7 @@ export class CosmWasmCodec implements TxCodec {
|
||||
|
||||
const defaultPrefix = "cosmos" as CosmosAddressBech32Prefix;
|
||||
|
||||
const defaultTokens: TokenInfos = [
|
||||
const defaultTokens: BankTokens = [
|
||||
{
|
||||
fractionalDigits: 6,
|
||||
ticker: "ATOM",
|
||||
|
||||
@ -14,6 +14,7 @@ import {
|
||||
import { Secp256k1 } from "@iov/crypto";
|
||||
import { Encoding } from "@iov/encoding";
|
||||
import { HdPaths, Secp256k1HdWallet, UserProfile } from "@iov/keycontrol";
|
||||
import { assert } from "@iov/utils";
|
||||
|
||||
import { CosmWasmCodec, cosmWasmCodec } from "./cosmwasmcodec";
|
||||
import { CosmWasmConnection, TokenConfiguration } from "./cosmwasmconnection";
|
||||
@ -33,38 +34,63 @@ describe("CosmWasmConnection", () => {
|
||||
const httpUrl = "http://localhost:1317";
|
||||
const defaultChainId = "cosmos:testing" as ChainId;
|
||||
const defaultEmptyAddress = "cosmos1h806c7khnvmjlywdrkdgk2vrayy2mmvf9rxk2r" as Address;
|
||||
const defaultAddress = "cosmos1pkptre7fdkl6gfrzlesjjvhxhlc3r4gmmk8rs6" as Address;
|
||||
const defaultPubkey = {
|
||||
algo: Algorithm.Secp256k1,
|
||||
data: fromBase64("A08EGB7ro1ORuFhjOnZcSgwYlpe0DSFjVNUIkNNQxwKQ") as PubkeyBytes,
|
||||
};
|
||||
const faucetMnemonic =
|
||||
"economy stock theory fatal elder harbor betray wasp final emotion task crumble siren bottom lizard educate guess current outdoor pair theory focus wife stone";
|
||||
const faucetPath = HdPaths.cosmos(0);
|
||||
const defaultRecipient = "cosmos1t70qnpr0az8tf7py83m4ue5y89w58lkjmx0yq2" as Address;
|
||||
const faucetAccount = {
|
||||
pubkey: {
|
||||
algo: Algorithm.Secp256k1,
|
||||
data: fromBase64("A08EGB7ro1ORuFhjOnZcSgwYlpe0DSFjVNUIkNNQxwKQ") as PubkeyBytes,
|
||||
},
|
||||
address: "cosmos1pkptre7fdkl6gfrzlesjjvhxhlc3r4gmmk8rs6" as Address,
|
||||
};
|
||||
const unusedAccount = {
|
||||
pubkey: {
|
||||
algo: Algorithm.Secp256k1,
|
||||
data: fromBase64("ArkCaFUJ/IH+vKBmNRCdUVl3mCAhbopk9jjW4Ko4OfRQ") as PubkeyBytes,
|
||||
},
|
||||
address: "cosmos1cjsxept9rkggzxztslae9ndgpdyt2408lk850u" as Address,
|
||||
};
|
||||
|
||||
const defaultPrefix = "cosmos" as CosmosAddressBech32Prefix;
|
||||
|
||||
// this is for wasmd blockchain
|
||||
const defaultTokens: TokenConfiguration = [
|
||||
{
|
||||
fractionalDigits: 6,
|
||||
name: "Fee Token",
|
||||
ticker: "COSM",
|
||||
denom: "ucosm",
|
||||
},
|
||||
{
|
||||
fractionalDigits: 6,
|
||||
name: "Staking Token",
|
||||
ticker: "STAKE",
|
||||
denom: "ustake",
|
||||
},
|
||||
];
|
||||
const defaultConfig: TokenConfiguration = {
|
||||
bankTokens: [
|
||||
{
|
||||
fractionalDigits: 6,
|
||||
name: "Fee Token",
|
||||
ticker: "COSM",
|
||||
denom: "ucosm",
|
||||
},
|
||||
{
|
||||
fractionalDigits: 6,
|
||||
name: "Staking Token",
|
||||
ticker: "STAKE",
|
||||
denom: "ustake",
|
||||
},
|
||||
],
|
||||
erc20Tokens: [
|
||||
{
|
||||
contractAddress: "cosmos18vd8fpwxzck93qlwghaj6arh4p7c5n89uzcee5",
|
||||
fractionalDigits: 5,
|
||||
ticker: "ASH",
|
||||
name: "Ash Token",
|
||||
},
|
||||
{
|
||||
contractAddress: "cosmos1hqrdl6wstt8qzshwc6mrumpjk9338k0lr4dqxd",
|
||||
fractionalDigits: 0,
|
||||
ticker: "BASH",
|
||||
name: "Bash Token",
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
describe("establish", () => {
|
||||
it("can connect to Cosmos via http", async () => {
|
||||
pendingWithoutCosmos();
|
||||
const connection = await CosmWasmConnection.establish(httpUrl, defaultPrefix, defaultTokens);
|
||||
const connection = await CosmWasmConnection.establish(httpUrl, defaultPrefix, defaultConfig);
|
||||
expect(connection).toBeTruthy();
|
||||
connection.disconnect();
|
||||
});
|
||||
@ -73,7 +99,7 @@ describe("CosmWasmConnection", () => {
|
||||
describe("chainId", () => {
|
||||
it("displays the chain ID", async () => {
|
||||
pendingWithoutCosmos();
|
||||
const connection = await CosmWasmConnection.establish(httpUrl, defaultPrefix, defaultTokens);
|
||||
const connection = await CosmWasmConnection.establish(httpUrl, defaultPrefix, defaultConfig);
|
||||
const chainId = connection.chainId();
|
||||
expect(chainId).toEqual(defaultChainId);
|
||||
connection.disconnect();
|
||||
@ -83,7 +109,7 @@ describe("CosmWasmConnection", () => {
|
||||
describe("height", () => {
|
||||
it("displays the current height", async () => {
|
||||
pendingWithoutCosmos();
|
||||
const connection = await CosmWasmConnection.establish(httpUrl, defaultPrefix, defaultTokens);
|
||||
const connection = await CosmWasmConnection.establish(httpUrl, defaultPrefix, defaultConfig);
|
||||
const height = await connection.height();
|
||||
expect(height).toBeGreaterThan(0);
|
||||
connection.disconnect();
|
||||
@ -93,7 +119,7 @@ describe("CosmWasmConnection", () => {
|
||||
describe("getToken", () => {
|
||||
it("displays a given token", async () => {
|
||||
pendingWithoutCosmos();
|
||||
const connection = await CosmWasmConnection.establish(httpUrl, defaultPrefix, defaultTokens);
|
||||
const connection = await CosmWasmConnection.establish(httpUrl, defaultPrefix, defaultConfig);
|
||||
const token = await connection.getToken("COSM" as TokenTicker);
|
||||
expect(token).toEqual({
|
||||
fractionalDigits: 6,
|
||||
@ -105,7 +131,7 @@ describe("CosmWasmConnection", () => {
|
||||
|
||||
it("resolves to undefined if the token is not supported", async () => {
|
||||
pendingWithoutCosmos();
|
||||
const connection = await CosmWasmConnection.establish(httpUrl, defaultPrefix, defaultTokens);
|
||||
const connection = await CosmWasmConnection.establish(httpUrl, defaultPrefix, defaultConfig);
|
||||
const token = await connection.getToken("whatever" as TokenTicker);
|
||||
expect(token).toBeUndefined();
|
||||
connection.disconnect();
|
||||
@ -115,10 +141,19 @@ describe("CosmWasmConnection", () => {
|
||||
describe("getAllTokens", () => {
|
||||
it("resolves to a list of all supported tokens", async () => {
|
||||
pendingWithoutCosmos();
|
||||
const connection = await CosmWasmConnection.establish(httpUrl, defaultPrefix, defaultTokens);
|
||||
const connection = await CosmWasmConnection.establish(httpUrl, defaultPrefix, defaultConfig);
|
||||
const tokens = await connection.getAllTokens();
|
||||
// TODO: make this more flexible
|
||||
expect(tokens).toEqual([
|
||||
{
|
||||
fractionalDigits: 5,
|
||||
tokenName: "Ash Token",
|
||||
tokenTicker: "ASH" as TokenTicker,
|
||||
},
|
||||
{
|
||||
fractionalDigits: 0,
|
||||
tokenName: "Bash Token",
|
||||
tokenTicker: "BASH" as TokenTicker,
|
||||
},
|
||||
{
|
||||
fractionalDigits: 6,
|
||||
tokenName: "Fee Token",
|
||||
@ -137,7 +172,7 @@ describe("CosmWasmConnection", () => {
|
||||
describe("identifier", () => {
|
||||
it("calculates tx hash from PostableBytes", async () => {
|
||||
pendingWithoutCosmos();
|
||||
const connection = await CosmWasmConnection.establish(httpUrl, defaultPrefix, defaultTokens);
|
||||
const connection = await CosmWasmConnection.establish(httpUrl, defaultPrefix, defaultConfig);
|
||||
const postable = cosmWasmCodec.bytesToPost(signedTxJson);
|
||||
const id = await connection.identifier(postable);
|
||||
expect(id).toMatch(/^[0-9A-F]{64}$/);
|
||||
@ -148,7 +183,7 @@ describe("CosmWasmConnection", () => {
|
||||
describe("getAccount", () => {
|
||||
it("gets an empty account by address", async () => {
|
||||
pendingWithoutCosmos();
|
||||
const connection = await CosmWasmConnection.establish(httpUrl, defaultPrefix, defaultTokens);
|
||||
const connection = await CosmWasmConnection.establish(httpUrl, defaultPrefix, defaultConfig);
|
||||
const account = await connection.getAccount({ address: defaultEmptyAddress });
|
||||
expect(account).toBeUndefined();
|
||||
connection.disconnect();
|
||||
@ -156,35 +191,50 @@ describe("CosmWasmConnection", () => {
|
||||
|
||||
it("gets an account by address", async () => {
|
||||
pendingWithoutCosmos();
|
||||
const connection = await CosmWasmConnection.establish(httpUrl, defaultPrefix, defaultTokens);
|
||||
const account = await connection.getAccount({ address: defaultAddress });
|
||||
if (account === undefined) {
|
||||
throw new Error("Expected account not to be undefined");
|
||||
}
|
||||
expect(account.address).toEqual(defaultAddress);
|
||||
// Undefined until we sign a transaction (on multiple runs against one server this will be set), allow both
|
||||
if (account.pubkey !== undefined) {
|
||||
expect(account.pubkey).toEqual(defaultPubkey);
|
||||
}
|
||||
// Starts with two tokens
|
||||
expect(account.balance.length).toEqual(2);
|
||||
const connection = await CosmWasmConnection.establish(httpUrl, defaultPrefix, defaultConfig);
|
||||
const account = await connection.getAccount({ address: unusedAccount.address });
|
||||
assert(account, "Account must be defined");
|
||||
expect(account.address).toEqual(unusedAccount.address);
|
||||
expect(account.pubkey).toBeUndefined();
|
||||
expect(account.balance).toEqual([
|
||||
{
|
||||
tokenTicker: "ASH" as TokenTicker,
|
||||
quantity: "12812345",
|
||||
fractionalDigits: 5,
|
||||
},
|
||||
{
|
||||
tokenTicker: "BASH" as TokenTicker,
|
||||
quantity: "42",
|
||||
fractionalDigits: 0,
|
||||
},
|
||||
{
|
||||
tokenTicker: "COSM" as TokenTicker,
|
||||
quantity: "1000000000",
|
||||
fractionalDigits: 6,
|
||||
},
|
||||
{
|
||||
tokenTicker: "STAKE" as TokenTicker,
|
||||
quantity: "1000000000",
|
||||
fractionalDigits: 6,
|
||||
},
|
||||
]);
|
||||
connection.disconnect();
|
||||
});
|
||||
|
||||
it("gets an account by pubkey", async () => {
|
||||
pendingWithoutCosmos();
|
||||
const connection = await CosmWasmConnection.establish(httpUrl, defaultPrefix, defaultTokens);
|
||||
const account = await connection.getAccount({ pubkey: defaultPubkey });
|
||||
if (account === undefined) {
|
||||
throw new Error("Expected account not to be undefined");
|
||||
}
|
||||
expect(account.address).toEqual(defaultAddress);
|
||||
// Undefined until we sign a transaction (on multiple runs against one server this will be set), allow both
|
||||
if (account.pubkey !== undefined) {
|
||||
expect(account.pubkey).toEqual(defaultPubkey);
|
||||
}
|
||||
// Starts with two tokens
|
||||
expect(account.balance.length).toEqual(2);
|
||||
const connection = await CosmWasmConnection.establish(httpUrl, defaultPrefix, defaultConfig);
|
||||
const byAddress = await connection.getAccount({ address: unusedAccount.address });
|
||||
const byPubkey = await connection.getAccount({ pubkey: unusedAccount.pubkey });
|
||||
expect(byPubkey).toEqual(byAddress); // above we verified that by address works as expected
|
||||
connection.disconnect();
|
||||
});
|
||||
|
||||
it("has a pubkey when getting account with transactions", async () => {
|
||||
pendingWithoutCosmos();
|
||||
const connection = await CosmWasmConnection.establish(httpUrl, defaultPrefix, defaultConfig);
|
||||
const account = await connection.getAccount({ address: faucetAccount.address });
|
||||
expect(account?.pubkey).toEqual(faucetAccount.pubkey);
|
||||
connection.disconnect();
|
||||
});
|
||||
});
|
||||
@ -192,7 +242,7 @@ describe("CosmWasmConnection", () => {
|
||||
describe("integration tests", () => {
|
||||
it("can post and get a transaction", async () => {
|
||||
pendingWithoutCosmos();
|
||||
const connection = await CosmWasmConnection.establish(httpUrl, defaultPrefix, defaultTokens);
|
||||
const connection = await CosmWasmConnection.establish(httpUrl, defaultPrefix, defaultConfig);
|
||||
const profile = new UserProfile();
|
||||
const wallet = profile.addWallet(Secp256k1HdWallet.fromMnemonic(faucetMnemonic));
|
||||
const faucet = await profile.createIdentity(wallet.id, defaultChainId, faucetPath);
|
||||
@ -212,7 +262,7 @@ describe("CosmWasmConnection", () => {
|
||||
});
|
||||
const nonce = await connection.getNonce({ address: faucetAddress });
|
||||
// TODO: we need to use custom codecs everywhere
|
||||
const codec = new CosmWasmCodec(defaultPrefix, defaultTokens);
|
||||
const codec = new CosmWasmCodec(defaultPrefix, defaultConfig.bankTokens);
|
||||
const signed = await profile.signTransaction(faucet, unsigned, codec, nonce);
|
||||
const postableBytes = codec.bytesToPost(signed);
|
||||
const response = await connection.postTx(postableBytes);
|
||||
@ -257,7 +307,7 @@ describe("CosmWasmConnection", () => {
|
||||
|
||||
it("can post and search for a transaction", async () => {
|
||||
pendingWithoutCosmos();
|
||||
const connection = await CosmWasmConnection.establish(httpUrl, defaultPrefix, defaultTokens);
|
||||
const connection = await CosmWasmConnection.establish(httpUrl, defaultPrefix, defaultConfig);
|
||||
const profile = new UserProfile();
|
||||
const wallet = profile.addWallet(Secp256k1HdWallet.fromMnemonic(faucetMnemonic));
|
||||
const faucet = await profile.createIdentity(wallet.id, defaultChainId, faucetPath);
|
||||
@ -277,7 +327,7 @@ describe("CosmWasmConnection", () => {
|
||||
});
|
||||
const nonce = await connection.getNonce({ address: faucetAddress });
|
||||
// TODO: we need to use custom codecs everywhere
|
||||
const codec = new CosmWasmCodec(defaultPrefix, defaultTokens);
|
||||
const codec = new CosmWasmCodec(defaultPrefix, defaultConfig.bankTokens);
|
||||
const signed = await profile.signTransaction(faucet, unsigned, codec, nonce);
|
||||
const postableBytes = codec.bytesToPost(signed);
|
||||
const response = await connection.postTx(postableBytes);
|
||||
|
||||
@ -4,6 +4,7 @@ import {
|
||||
Account,
|
||||
AccountQuery,
|
||||
AddressQuery,
|
||||
Amount,
|
||||
BlockchainConnection,
|
||||
BlockHeader,
|
||||
BlockId,
|
||||
@ -37,7 +38,7 @@ import { Stream } from "xstream";
|
||||
import { decodeCosmosPubkey, pubkeyToAddress } from "./address";
|
||||
import { Caip5 } from "./caip5";
|
||||
import { decodeAmount, parseTxsResponse } from "./decode";
|
||||
import { accountToNonce, TokenInfo } from "./types";
|
||||
import { accountToNonce, BankToken, Erc20Token } from "./types";
|
||||
|
||||
const { toHex } = Encoding;
|
||||
|
||||
@ -69,7 +70,12 @@ function buildQueryString({
|
||||
return components.filter(Boolean).join("&");
|
||||
}
|
||||
|
||||
export type TokenConfiguration = ReadonlyArray<TokenInfo & { readonly name: string }>;
|
||||
export interface TokenConfiguration {
|
||||
/** Supported tokens of the Cosmos SDK bank module */
|
||||
readonly bankTokens: ReadonlyArray<BankToken & { readonly name: string }>;
|
||||
/** Smart contract based tokens (ERC20 compatible). Unset means empty array. */
|
||||
readonly erc20Tokens?: ReadonlyArray<Erc20Token & { readonly name: string }>;
|
||||
}
|
||||
|
||||
export class CosmWasmConnection implements BlockchainConnection {
|
||||
// we must know prefix and tokens a priori to understand the chain
|
||||
@ -91,10 +97,11 @@ export class CosmWasmConnection implements BlockchainConnection {
|
||||
private readonly restClient: RestClient;
|
||||
private readonly chainData: ChainData;
|
||||
private readonly addressPrefix: CosmosAddressBech32Prefix;
|
||||
private readonly tokenInfo: readonly TokenInfo[];
|
||||
private readonly bankTokens: readonly BankToken[];
|
||||
private readonly erc20Tokens: readonly Erc20Token[];
|
||||
|
||||
// these are derived from arguments (cached for use in multiple functions)
|
||||
private readonly primaryToken: Token;
|
||||
private readonly feeToken: BankToken | undefined;
|
||||
private readonly supportedTokens: readonly Token[];
|
||||
|
||||
private constructor(
|
||||
@ -106,14 +113,17 @@ export class CosmWasmConnection implements BlockchainConnection {
|
||||
this.restClient = restClient;
|
||||
this.chainData = chainData;
|
||||
this.addressPrefix = addressPrefix;
|
||||
this.tokenInfo = tokens;
|
||||
|
||||
this.supportedTokens = tokens.map(info => ({
|
||||
tokenTicker: info.ticker as TokenTicker,
|
||||
tokenName: info.name,
|
||||
fractionalDigits: info.fractionalDigits,
|
||||
}));
|
||||
this.primaryToken = this.supportedTokens[0];
|
||||
this.bankTokens = tokens.bankTokens;
|
||||
this.feeToken = this.bankTokens.find(() => true);
|
||||
const erc20Tokens = tokens.erc20Tokens || [];
|
||||
this.erc20Tokens = erc20Tokens;
|
||||
this.supportedTokens = [...tokens.bankTokens, ...erc20Tokens]
|
||||
.map(info => ({
|
||||
tokenTicker: info.ticker as TokenTicker,
|
||||
tokenName: info.name,
|
||||
fractionalDigits: info.fractionalDigits,
|
||||
}))
|
||||
.sort((a, b) => a.tokenTicker.localeCompare(b.tokenTicker));
|
||||
}
|
||||
|
||||
public disconnect(): void {
|
||||
@ -151,14 +161,35 @@ export class CosmWasmConnection implements BlockchainConnection {
|
||||
if (!account.address) {
|
||||
return undefined;
|
||||
}
|
||||
const supportedCoins = account.coins.filter(({ denom }) =>
|
||||
this.tokenInfo.find(token => token.denom === denom),
|
||||
|
||||
const supportedBankCoins = account.coins.filter(({ denom }) =>
|
||||
this.bankTokens.find(token => token.denom === denom),
|
||||
);
|
||||
const erc20Amounts = await Promise.all(
|
||||
this.erc20Tokens.map(
|
||||
async (erc20): Promise<Amount> => {
|
||||
const queryMsg = { balance: { address: address } };
|
||||
const response = JSON.parse(
|
||||
await this.restClient.queryContractSmart(erc20.contractAddress, queryMsg),
|
||||
);
|
||||
return {
|
||||
fractionalDigits: erc20.fractionalDigits,
|
||||
quantity: response.balance,
|
||||
tokenTicker: erc20.ticker as TokenTicker,
|
||||
};
|
||||
},
|
||||
),
|
||||
);
|
||||
|
||||
const balance = [
|
||||
...supportedBankCoins.map(coin => decodeAmount(this.bankTokens, coin)),
|
||||
...erc20Amounts,
|
||||
].sort((a, b) => a.tokenTicker.localeCompare(b.tokenTicker));
|
||||
|
||||
const pubkey = !account.public_key ? undefined : decodeCosmosPubkey(account.public_key);
|
||||
return {
|
||||
address: address,
|
||||
balance: supportedCoins.map(coin => decodeAmount(this.tokenInfo, coin)),
|
||||
balance: balance,
|
||||
pubkey: pubkey,
|
||||
};
|
||||
}
|
||||
@ -280,11 +311,12 @@ export class CosmWasmConnection implements BlockchainConnection {
|
||||
if (!isSendTransaction(tx)) {
|
||||
throw new Error("Received transaction of unsupported kind.");
|
||||
}
|
||||
if (!this.feeToken) throw new Error("This connection has no fee token configured.");
|
||||
return {
|
||||
tokens: {
|
||||
fractionalDigits: this.primaryToken.fractionalDigits,
|
||||
fractionalDigits: this.feeToken.fractionalDigits,
|
||||
quantity: "5000",
|
||||
tokenTicker: this.primaryToken.tokenTicker,
|
||||
tokenTicker: this.feeToken.ticker as TokenTicker,
|
||||
},
|
||||
gasLimit: "200000",
|
||||
};
|
||||
@ -321,6 +353,6 @@ export class CosmWasmConnection implements BlockchainConnection {
|
||||
// this is technically not the proper nonce. maybe this causes issues for sig validation?
|
||||
// leaving for now unless it causes issues
|
||||
const sequence = (accountForHeight.result.value.sequence - 1) as Nonce;
|
||||
return parseTxsResponse(chainId, parseInt(response.height, 10), sequence, response, this.tokenInfo);
|
||||
return parseTxsResponse(chainId, parseInt(response.height, 10), sequence, response, this.bankTokens);
|
||||
}
|
||||
}
|
||||
|
||||
@ -13,7 +13,7 @@ export function createCosmWasmConnector(
|
||||
tokens: TokenConfiguration,
|
||||
expectedChainId?: ChainId,
|
||||
): ChainConnector<CosmWasmConnection> {
|
||||
const codec = new CosmWasmCodec(addressPrefix, tokens);
|
||||
const codec = new CosmWasmCodec(addressPrefix, tokens.bankTokens);
|
||||
return {
|
||||
establishConnection: async () => CosmWasmConnection.establish(url, addressPrefix, tokens),
|
||||
codec: codec,
|
||||
|
||||
@ -15,7 +15,7 @@ import {
|
||||
} from "./decode";
|
||||
import { chainId, nonce, signedTxJson, txId } from "./testdata.spec";
|
||||
import data from "./testdata/cosmoshub.json";
|
||||
import { TokenInfos } from "./types";
|
||||
import { BankTokens } from "./types";
|
||||
|
||||
const { fromBase64, fromHex } = Encoding;
|
||||
|
||||
@ -52,7 +52,7 @@ describe("decode", () => {
|
||||
},
|
||||
gasLimit: "200000",
|
||||
};
|
||||
const defaultTokens: TokenInfos = [
|
||||
const defaultTokens: BankTokens = [
|
||||
{
|
||||
fractionalDigits: 6,
|
||||
ticker: "ATOM",
|
||||
|
||||
@ -19,7 +19,7 @@ import {
|
||||
} from "@iov/bcp";
|
||||
import { Decimal, Encoding } from "@iov/encoding";
|
||||
|
||||
import { TokenInfos } from "./types";
|
||||
import { BankTokens } from "./types";
|
||||
|
||||
const { fromBase64 } = Encoding;
|
||||
|
||||
@ -52,7 +52,7 @@ export function decodeFullSignature(signature: types.StdSignature, nonce: number
|
||||
};
|
||||
}
|
||||
|
||||
export function coinToDecimal(tokens: TokenInfos, coin: types.Coin): readonly [Decimal, string] {
|
||||
export function coinToDecimal(tokens: BankTokens, coin: types.Coin): readonly [Decimal, string] {
|
||||
const match = tokens.find(({ denom }) => denom === coin.denom);
|
||||
if (!match) {
|
||||
throw Error(`unknown denom: ${coin.denom}`);
|
||||
@ -61,7 +61,7 @@ export function coinToDecimal(tokens: TokenInfos, coin: types.Coin): readonly [D
|
||||
return [value, match.ticker];
|
||||
}
|
||||
|
||||
export function decodeAmount(tokens: TokenInfos, coin: types.Coin): Amount {
|
||||
export function decodeAmount(tokens: BankTokens, coin: types.Coin): Amount {
|
||||
const [value, ticker] = coinToDecimal(tokens, coin);
|
||||
return {
|
||||
quantity: value.atomics,
|
||||
@ -70,7 +70,7 @@ export function decodeAmount(tokens: TokenInfos, coin: types.Coin): Amount {
|
||||
};
|
||||
}
|
||||
|
||||
export function parseMsg(msg: types.Msg, chainId: ChainId, tokens: TokenInfos): UnsignedTransaction {
|
||||
export function parseMsg(msg: types.Msg, chainId: ChainId, tokens: BankTokens): UnsignedTransaction {
|
||||
if (types.isMsgSend(msg)) {
|
||||
if (msg.value.amount.length !== 1) {
|
||||
throw new Error("Only MsgSend with one amount is supported");
|
||||
@ -93,7 +93,7 @@ export function parseMsg(msg: types.Msg, chainId: ChainId, tokens: TokenInfos):
|
||||
}
|
||||
}
|
||||
|
||||
export function parseFee(fee: types.StdFee, tokens: TokenInfos): Fee {
|
||||
export function parseFee(fee: types.StdFee, tokens: BankTokens): Fee {
|
||||
if (fee.amount.length !== 1) {
|
||||
throw new Error("Only fee with one amount is supported");
|
||||
}
|
||||
@ -107,7 +107,7 @@ export function parseTx(
|
||||
txValue: types.StdTx,
|
||||
chainId: ChainId,
|
||||
nonce: Nonce,
|
||||
tokens: TokenInfos,
|
||||
tokens: BankTokens,
|
||||
): SignedTransaction {
|
||||
if (!types.isAminoStdTx(txValue)) {
|
||||
throw new Error("Only Amino StdTx is supported");
|
||||
@ -138,7 +138,7 @@ export function parseTxsResponse(
|
||||
currentHeight: number,
|
||||
nonce: Nonce,
|
||||
response: TxsResponse,
|
||||
tokens: TokenInfos,
|
||||
tokens: BankTokens,
|
||||
): ConfirmedAndSignedTransaction<UnsignedTransaction> {
|
||||
const height = parseInt(response.height, 10);
|
||||
return {
|
||||
|
||||
@ -21,7 +21,7 @@ import {
|
||||
encodeFullSignature,
|
||||
encodePubkey,
|
||||
} from "./encode";
|
||||
import { TokenInfos } from "./types";
|
||||
import { BankTokens } from "./types";
|
||||
|
||||
const { fromBase64 } = Encoding;
|
||||
|
||||
@ -41,7 +41,7 @@ describe("encode", () => {
|
||||
tokenTicker: atom,
|
||||
};
|
||||
const defaultMemo = "hello cosmos hub";
|
||||
const defaultTokens: TokenInfos = [
|
||||
const defaultTokens: BankTokens = [
|
||||
{
|
||||
fractionalDigits: 6,
|
||||
ticker: "ATOM",
|
||||
|
||||
@ -12,7 +12,7 @@ import {
|
||||
} from "@iov/bcp";
|
||||
import { Decimal, Encoding } from "@iov/encoding";
|
||||
|
||||
import { TokenInfos } from "./types";
|
||||
import { BankTokens } from "./types";
|
||||
|
||||
const { toBase64 } = Encoding;
|
||||
|
||||
@ -33,7 +33,7 @@ export function encodePubkey(pubkey: PubkeyBundle): types.PubKey {
|
||||
}
|
||||
}
|
||||
|
||||
export function decimalToCoin(lookup: TokenInfos, value: Decimal, ticker: string): types.Coin {
|
||||
export function decimalToCoin(lookup: BankTokens, value: Decimal, ticker: string): types.Coin {
|
||||
const match = lookup.find(token => token.ticker === ticker);
|
||||
if (!match) {
|
||||
throw Error(`unknown ticker: ${ticker}`);
|
||||
@ -49,7 +49,7 @@ export function decimalToCoin(lookup: TokenInfos, value: Decimal, ticker: string
|
||||
};
|
||||
}
|
||||
|
||||
export function encodeAmount(amount: Amount, tokens: TokenInfos): types.Coin {
|
||||
export function encodeAmount(amount: Amount, tokens: BankTokens): types.Coin {
|
||||
return decimalToCoin(
|
||||
tokens,
|
||||
Decimal.fromAtomics(amount.quantity, amount.fractionalDigits),
|
||||
@ -57,7 +57,7 @@ export function encodeAmount(amount: Amount, tokens: TokenInfos): types.Coin {
|
||||
);
|
||||
}
|
||||
|
||||
export function encodeFee(fee: Fee, tokens: TokenInfos): types.StdFee {
|
||||
export function encodeFee(fee: Fee, tokens: BankTokens): types.StdFee {
|
||||
if (fee.tokens === undefined) {
|
||||
throw new Error("Cannot encode fee without tokens");
|
||||
}
|
||||
@ -79,7 +79,7 @@ export function encodeFullSignature(fullSignature: FullSignature): types.StdSign
|
||||
}
|
||||
}
|
||||
|
||||
export function buildUnsignedTx(tx: UnsignedTransaction, tokens: TokenInfos): types.AminoTx {
|
||||
export function buildUnsignedTx(tx: UnsignedTransaction, tokens: BankTokens): types.AminoTx {
|
||||
if (!isSendTransaction(tx)) {
|
||||
throw new Error("Received transaction of unsupported kind");
|
||||
}
|
||||
@ -108,7 +108,7 @@ export function buildUnsignedTx(tx: UnsignedTransaction, tokens: TokenInfos): ty
|
||||
};
|
||||
}
|
||||
|
||||
export function buildSignedTx(tx: SignedTransaction, tokens: TokenInfos): types.AminoTx {
|
||||
export function buildSignedTx(tx: SignedTransaction, tokens: BankTokens): types.AminoTx {
|
||||
const built = buildUnsignedTx(tx.transaction, tokens);
|
||||
return {
|
||||
...built,
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import { types } from "@cosmwasm/sdk";
|
||||
import { Nonce } from "@iov/bcp";
|
||||
|
||||
export interface TokenInfo {
|
||||
export interface BankToken {
|
||||
readonly denom: string;
|
||||
readonly ticker: string;
|
||||
/**
|
||||
@ -16,7 +16,22 @@ export interface TokenInfo {
|
||||
readonly fractionalDigits: number;
|
||||
}
|
||||
|
||||
export type TokenInfos = ReadonlyArray<TokenInfo>;
|
||||
export type BankTokens = ReadonlyArray<BankToken>;
|
||||
|
||||
export interface Erc20Token {
|
||||
readonly contractAddress: string;
|
||||
readonly ticker: string;
|
||||
/**
|
||||
* The number of fractional digits the token supports.
|
||||
*
|
||||
* A quantity is expressed as atomic units. 10^fractionalDigits of those
|
||||
* atomic units make up 1 token.
|
||||
*
|
||||
* E.g. in Ethereum 10^18 wei are 1 ETH and from the quantity 123000000000000000000
|
||||
* the last 18 digits are the fractional part and the rest the wole part.
|
||||
*/
|
||||
readonly fractionalDigits: number;
|
||||
}
|
||||
|
||||
// tslint:disable-next-line:no-bitwise
|
||||
const maxAcct = 1 << 23;
|
||||
|
||||
4
packages/bcp/types/cosmwasmcodec.d.ts
vendored
4
packages/bcp/types/cosmwasmcodec.d.ts
vendored
@ -11,11 +11,11 @@ import {
|
||||
TxCodec,
|
||||
UnsignedTransaction,
|
||||
} from "@iov/bcp";
|
||||
import { TokenInfos } from "./types";
|
||||
import { BankTokens } from "./types";
|
||||
export declare class CosmWasmCodec implements TxCodec {
|
||||
private readonly addressPrefix;
|
||||
private readonly tokens;
|
||||
constructor(addressPrefix: CosmosAddressBech32Prefix, tokens: TokenInfos);
|
||||
constructor(addressPrefix: CosmosAddressBech32Prefix, tokens: BankTokens);
|
||||
bytesToSign(unsigned: UnsignedTransaction, nonce: Nonce): SigningJob;
|
||||
bytesToPost(signed: SignedTransaction): PostableBytes;
|
||||
identifier(_signed: SignedTransaction): TransactionId;
|
||||
|
||||
26
packages/bcp/types/cosmwasmconnection.d.ts
vendored
26
packages/bcp/types/cosmwasmconnection.d.ts
vendored
@ -21,12 +21,21 @@ import {
|
||||
UnsignedTransaction,
|
||||
} from "@iov/bcp";
|
||||
import { Stream } from "xstream";
|
||||
import { TokenInfo } from "./types";
|
||||
export declare type TokenConfiguration = ReadonlyArray<
|
||||
TokenInfo & {
|
||||
readonly name: string;
|
||||
}
|
||||
>;
|
||||
import { BankToken, Erc20Token } from "./types";
|
||||
export interface TokenConfiguration {
|
||||
/** Supported tokens of the Cosmos SDK bank module */
|
||||
readonly bankTokens: ReadonlyArray<
|
||||
BankToken & {
|
||||
readonly name: string;
|
||||
}
|
||||
>;
|
||||
/** Smart contract based tokens (ERC20 compatible). Unset means empty array. */
|
||||
readonly erc20Tokens?: ReadonlyArray<
|
||||
Erc20Token & {
|
||||
readonly name: string;
|
||||
}
|
||||
>;
|
||||
}
|
||||
export declare class CosmWasmConnection implements BlockchainConnection {
|
||||
static establish(
|
||||
url: string,
|
||||
@ -37,8 +46,9 @@ export declare class CosmWasmConnection implements BlockchainConnection {
|
||||
private readonly restClient;
|
||||
private readonly chainData;
|
||||
private readonly addressPrefix;
|
||||
private readonly tokenInfo;
|
||||
private readonly primaryToken;
|
||||
private readonly bankTokens;
|
||||
private readonly erc20Tokens;
|
||||
private readonly feeToken;
|
||||
private readonly supportedTokens;
|
||||
private constructor();
|
||||
disconnect(): void;
|
||||
|
||||
14
packages/bcp/types/decode.d.ts
vendored
14
packages/bcp/types/decode.d.ts
vendored
@ -12,24 +12,24 @@ import {
|
||||
UnsignedTransaction,
|
||||
} from "@iov/bcp";
|
||||
import { Decimal } from "@iov/encoding";
|
||||
import { TokenInfos } from "./types";
|
||||
import { BankTokens } from "./types";
|
||||
export declare function decodePubkey(pubkey: types.PubKey): PubkeyBundle;
|
||||
export declare function decodeSignature(signature: string): SignatureBytes;
|
||||
export declare function decodeFullSignature(signature: types.StdSignature, nonce: number): FullSignature;
|
||||
export declare function coinToDecimal(tokens: TokenInfos, coin: types.Coin): readonly [Decimal, string];
|
||||
export declare function decodeAmount(tokens: TokenInfos, coin: types.Coin): Amount;
|
||||
export declare function parseMsg(msg: types.Msg, chainId: ChainId, tokens: TokenInfos): UnsignedTransaction;
|
||||
export declare function parseFee(fee: types.StdFee, tokens: TokenInfos): Fee;
|
||||
export declare function coinToDecimal(tokens: BankTokens, coin: types.Coin): readonly [Decimal, string];
|
||||
export declare function decodeAmount(tokens: BankTokens, coin: types.Coin): Amount;
|
||||
export declare function parseMsg(msg: types.Msg, chainId: ChainId, tokens: BankTokens): UnsignedTransaction;
|
||||
export declare function parseFee(fee: types.StdFee, tokens: BankTokens): Fee;
|
||||
export declare function parseTx(
|
||||
txValue: types.StdTx,
|
||||
chainId: ChainId,
|
||||
nonce: Nonce,
|
||||
tokens: TokenInfos,
|
||||
tokens: BankTokens,
|
||||
): SignedTransaction;
|
||||
export declare function parseTxsResponse(
|
||||
chainId: ChainId,
|
||||
currentHeight: number,
|
||||
nonce: Nonce,
|
||||
response: TxsResponse,
|
||||
tokens: TokenInfos,
|
||||
tokens: BankTokens,
|
||||
): ConfirmedAndSignedTransaction<UnsignedTransaction>;
|
||||
|
||||
12
packages/bcp/types/encode.d.ts
vendored
12
packages/bcp/types/encode.d.ts
vendored
@ -1,11 +1,11 @@
|
||||
import { types } from "@cosmwasm/sdk";
|
||||
import { Amount, Fee, FullSignature, PubkeyBundle, SignedTransaction, UnsignedTransaction } from "@iov/bcp";
|
||||
import { Decimal } from "@iov/encoding";
|
||||
import { TokenInfos } from "./types";
|
||||
import { BankTokens } from "./types";
|
||||
export declare function encodePubkey(pubkey: PubkeyBundle): types.PubKey;
|
||||
export declare function decimalToCoin(lookup: TokenInfos, value: Decimal, ticker: string): types.Coin;
|
||||
export declare function encodeAmount(amount: Amount, tokens: TokenInfos): types.Coin;
|
||||
export declare function encodeFee(fee: Fee, tokens: TokenInfos): types.StdFee;
|
||||
export declare function decimalToCoin(lookup: BankTokens, value: Decimal, ticker: string): types.Coin;
|
||||
export declare function encodeAmount(amount: Amount, tokens: BankTokens): types.Coin;
|
||||
export declare function encodeFee(fee: Fee, tokens: BankTokens): types.StdFee;
|
||||
export declare function encodeFullSignature(fullSignature: FullSignature): types.StdSignature;
|
||||
export declare function buildUnsignedTx(tx: UnsignedTransaction, tokens: TokenInfos): types.AminoTx;
|
||||
export declare function buildSignedTx(tx: SignedTransaction, tokens: TokenInfos): types.AminoTx;
|
||||
export declare function buildUnsignedTx(tx: UnsignedTransaction, tokens: BankTokens): types.AminoTx;
|
||||
export declare function buildSignedTx(tx: SignedTransaction, tokens: BankTokens): types.AminoTx;
|
||||
|
||||
18
packages/bcp/types/types.d.ts
vendored
18
packages/bcp/types/types.d.ts
vendored
@ -1,6 +1,6 @@
|
||||
import { types } from "@cosmwasm/sdk";
|
||||
import { Nonce } from "@iov/bcp";
|
||||
export interface TokenInfo {
|
||||
export interface BankToken {
|
||||
readonly denom: string;
|
||||
readonly ticker: string;
|
||||
/**
|
||||
@ -14,7 +14,21 @@ export interface TokenInfo {
|
||||
*/
|
||||
readonly fractionalDigits: number;
|
||||
}
|
||||
export declare type TokenInfos = ReadonlyArray<TokenInfo>;
|
||||
export declare type BankTokens = ReadonlyArray<BankToken>;
|
||||
export interface Erc20Token {
|
||||
readonly contractAddress: string;
|
||||
readonly ticker: string;
|
||||
/**
|
||||
* The number of fractional digits the token supports.
|
||||
*
|
||||
* A quantity is expressed as atomic units. 10^fractionalDigits of those
|
||||
* atomic units make up 1 token.
|
||||
*
|
||||
* E.g. in Ethereum 10^18 wei are 1 ETH and from the quantity 123000000000000000000
|
||||
* the last 18 digits are the fractional part and the rest the wole part.
|
||||
*/
|
||||
readonly fractionalDigits: number;
|
||||
}
|
||||
export declare function accountToNonce({ account_number: account, sequence }: types.NonceInfo): Nonce;
|
||||
export declare function nonceToAccountNumber(nonce: Nonce): number;
|
||||
export declare function nonceToSequence(nonce: Nonce): number;
|
||||
|
||||
@ -2,27 +2,29 @@ import { CosmWasmCodec, CosmWasmConnection, TokenConfiguration } from "@cosmwasm
|
||||
import { TxCodec } from "@iov/bcp";
|
||||
|
||||
const prefix = "cosmos";
|
||||
const tokens: TokenConfiguration = [
|
||||
{
|
||||
fractionalDigits: 6,
|
||||
name: "Fee Token",
|
||||
ticker: "COSM",
|
||||
denom: "cosm",
|
||||
},
|
||||
{
|
||||
fractionalDigits: 6,
|
||||
name: "Staking Token",
|
||||
ticker: "STAKE",
|
||||
denom: "stake",
|
||||
},
|
||||
];
|
||||
const config: TokenConfiguration = {
|
||||
bankTokens: [
|
||||
{
|
||||
fractionalDigits: 6,
|
||||
name: "Fee Token",
|
||||
ticker: "COSM",
|
||||
denom: "cosm",
|
||||
},
|
||||
{
|
||||
fractionalDigits: 6,
|
||||
name: "Staking Token",
|
||||
ticker: "STAKE",
|
||||
denom: "stake",
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
export async function establishConnection(url: string): Promise<CosmWasmConnection> {
|
||||
return CosmWasmConnection.establish(url, prefix, tokens);
|
||||
return CosmWasmConnection.establish(url, prefix, config);
|
||||
}
|
||||
|
||||
export function codecImplementation(): TxCodec {
|
||||
return new CosmWasmCodec(prefix, tokens);
|
||||
return new CosmWasmCodec(prefix, config.bankTokens);
|
||||
}
|
||||
|
||||
export function codecDefaultFractionalDigits(): number {
|
||||
|
||||
@ -40,6 +40,7 @@
|
||||
"dependencies": {
|
||||
"@iov/crypto": "^2.0.0-alpha.7",
|
||||
"@iov/encoding": "^2.0.0-alpha.7",
|
||||
"@iov/utils": "^2.0.0-alpha.7",
|
||||
"axios": "^0.19.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
/* eslint-disable @typescript-eslint/camelcase */
|
||||
import { Random, Sha256 } from "@iov/crypto";
|
||||
import { Bech32, Encoding } from "@iov/encoding";
|
||||
import { assert } from "@iov/utils";
|
||||
|
||||
import { encodeSecp256k1Signature, makeSignBytes, marshalTx } from "./encoding";
|
||||
import { leb128Encode } from "./leb128.spec";
|
||||
@ -40,8 +41,12 @@ const unusedAccount = {
|
||||
address: "cosmos1cjsxept9rkggzxztslae9ndgpdyt2408lk850u",
|
||||
};
|
||||
|
||||
function cosmosEnabled(): boolean {
|
||||
return !!process.env.COSMOS_ENABLED;
|
||||
}
|
||||
|
||||
function pendingWithoutCosmos(): void {
|
||||
if (!process.env.COSMOS_ENABLED) {
|
||||
if (!cosmosEnabled()) {
|
||||
return pending("Set COSMOS_ENABLED to enable Cosmos node-based tests");
|
||||
}
|
||||
}
|
||||
@ -467,25 +472,28 @@ describe("RestClient", () => {
|
||||
const client = new RestClient(httpUrl);
|
||||
const noContract = makeRandomAddress();
|
||||
const expectedKey = toAscii("config");
|
||||
let contractAddress: string | undefined;
|
||||
|
||||
/**
|
||||
* Finds the most recent contract (created above)
|
||||
*
|
||||
* We assume the tests above ran, all instantiate the same contract and no other process squeezed in a different contract.
|
||||
*/
|
||||
async function getContractAddress(): Promise<string> {
|
||||
const contracts = Array.from(await client.listContractAddresses());
|
||||
const last = contracts.reverse().find(() => true);
|
||||
if (!last) throw new Error("No contract found");
|
||||
return last;
|
||||
}
|
||||
beforeAll(async () => {
|
||||
if (cosmosEnabled()) {
|
||||
const pen = await Secp256k1Pen.fromMnemonic(faucet.mnemonic);
|
||||
const uploadResult = await uploadContract(client, pen);
|
||||
assert(!uploadResult.code);
|
||||
const uploadLogs = parseLogs(uploadResult.logs);
|
||||
const codeId = Number.parseInt(findAttribute(uploadLogs, "message", "code_id").value, 10);
|
||||
const instantiateResult = await instantiateContract(client, pen, codeId, makeRandomAddress());
|
||||
assert(!instantiateResult.code);
|
||||
const instantiateLogs = parseLogs(instantiateResult.logs);
|
||||
const contractAddressAttr = findAttribute(instantiateLogs, "message", "contract_address");
|
||||
contractAddress = contractAddressAttr.value;
|
||||
}
|
||||
});
|
||||
|
||||
it("can get all state", async () => {
|
||||
pendingWithoutCosmos();
|
||||
const contractAddress = await getContractAddress();
|
||||
|
||||
// get contract state
|
||||
const state = await client.getAllContractState(contractAddress);
|
||||
const state = await client.getAllContractState(contractAddress!);
|
||||
expect(state.length).toEqual(1);
|
||||
const data = state[0];
|
||||
expect(data.key.toLowerCase()).toEqual(toHex(expectedKey));
|
||||
@ -499,16 +507,15 @@ describe("RestClient", () => {
|
||||
|
||||
it("can query by key", async () => {
|
||||
pendingWithoutCosmos();
|
||||
const contractAddress = await getContractAddress();
|
||||
|
||||
// query by one key
|
||||
const model = await client.queryContractRaw(contractAddress, expectedKey);
|
||||
const model = await client.queryContractRaw(contractAddress!, expectedKey);
|
||||
expect(model).not.toBeNull();
|
||||
expect((model as any).verifier).toBeDefined();
|
||||
expect((model as any).beneficiary).toBeDefined();
|
||||
|
||||
// missing key is null
|
||||
const missing = await client.queryContractRaw(contractAddress, fromHex("cafe0dad"));
|
||||
const missing = await client.queryContractRaw(contractAddress!, fromHex("cafe0dad"));
|
||||
expect(missing).toBeNull();
|
||||
|
||||
// bad address is null
|
||||
@ -518,14 +525,13 @@ describe("RestClient", () => {
|
||||
|
||||
it("can make smart queries", async () => {
|
||||
pendingWithoutCosmos();
|
||||
const contractAddress = await getContractAddress();
|
||||
|
||||
// we can query the verifier properly
|
||||
const verifier = await client.queryContractSmart(contractAddress, { verifier: {} });
|
||||
const verifier = await client.queryContractSmart(contractAddress!, { verifier: {} });
|
||||
expect(verifier).toEqual(faucet.address);
|
||||
|
||||
// invalid query syntax throws an error
|
||||
await client.queryContractSmart(contractAddress, { nosuchkey: {} }).then(
|
||||
await client.queryContractSmart(contractAddress!, { nosuchkey: {} }).then(
|
||||
() => fail("shouldn't succeed"),
|
||||
error => expect(error).toMatch("Error parsing QueryMsg"),
|
||||
);
|
||||
|
||||
@ -324,14 +324,14 @@ export class RestClient {
|
||||
|
||||
// Makes a "smart query" on the contract, returns response verbatim (json.RawMessage)
|
||||
// Throws error if no such contract or invalid query format
|
||||
public async queryContractSmart(address: string, query: object): Promise<unknown> {
|
||||
public async queryContractSmart(address: string, query: object): Promise<string> {
|
||||
const encoded = toHex(toUtf8(JSON.stringify(query)));
|
||||
const path = `/wasm/contract/${address}/smart/${encoded}?encoding=hex`;
|
||||
const responseData = (await this.get(path)) as WasmResponse;
|
||||
if (isWasmError(responseData)) {
|
||||
throw new Error(responseData.error);
|
||||
}
|
||||
// no extra parse here
|
||||
// no extra parse here for now, see https://github.com/confio/cosmwasm/issues/144
|
||||
return responseData.result;
|
||||
}
|
||||
}
|
||||
|
||||
2
packages/sdk/types/restclient.d.ts
vendored
2
packages/sdk/types/restclient.d.ts
vendored
@ -104,6 +104,6 @@ export declare class RestClient {
|
||||
getContractInfo(address: string): Promise<ContractInfo>;
|
||||
getAllContractState(address: string): Promise<readonly WasmData[]>;
|
||||
queryContractRaw(address: string, key: Uint8Array): Promise<unknown | null>;
|
||||
queryContractSmart(address: string, query: object): Promise<unknown>;
|
||||
queryContractSmart(address: string, query: object): Promise<string>;
|
||||
}
|
||||
export {};
|
||||
|
||||
@ -83,7 +83,9 @@ You should get output matching the following:
|
||||
|
||||
1. Faucet<br>
|
||||
economy stock theory fatal elder harbor betray wasp final emotion task crumble siren bottom lizard educate guess current outdoor pair theory focus wife stone<br>
|
||||
A08EGB7ro1ORuFhjOnZcSgwYlpe0DSFjVNUIkNNQxwKQ<br>
|
||||
cosmos1pkptre7fdkl6gfrzlesjjvhxhlc3r4gmmk8rs6
|
||||
2. Unused: for testing account state; this account never changes balances or nonces<br>
|
||||
oyster design unusual machine spread century engine gravity focus cave carry slot<br>
|
||||
ArkCaFUJ/IH+vKBmNRCdUVl3mCAhbopk9jjW4Ko4OfRQ<br>
|
||||
cosmos1cjsxept9rkggzxztslae9ndgpdyt2408lk850u
|
||||
|
||||
@ -88,7 +88,7 @@ async function main() {
|
||||
const codeId = Number.parseInt(codeIdAttr.value, 10);
|
||||
console.info(`Upload succeeded. Code ID is ${codeId}`);
|
||||
|
||||
const initMsg = {
|
||||
const initMsgAsh = {
|
||||
decimals: 5,
|
||||
name: "Ash token",
|
||||
symbol: "ASH",
|
||||
@ -103,16 +103,30 @@ async function main() {
|
||||
},
|
||||
],
|
||||
};
|
||||
const instantiationResult = await instantiateContract(client, pen, codeId, initMsg);
|
||||
if (instantiationResult.code) {
|
||||
throw new Error(
|
||||
`Instantiation failed with code: ${instantiationResult.code}; log: '${instantiationResult.raw_log}'`,
|
||||
);
|
||||
const initMsgBash = {
|
||||
decimals: 0,
|
||||
name: "Bash Token",
|
||||
symbol: "BASH",
|
||||
initial_balances: [
|
||||
{
|
||||
address: faucetAddress,
|
||||
amount: "999999999",
|
||||
},
|
||||
{
|
||||
address: unusedAccount,
|
||||
amount: "42",
|
||||
},
|
||||
],
|
||||
};
|
||||
for (const initMsg of [initMsgAsh, initMsgBash]) {
|
||||
const initResult = await instantiateContract(client, pen, codeId, initMsg);
|
||||
if (initResult.code) {
|
||||
throw new Error(`Instantiation failed with code: ${initResult.code}; log: '${initResult.raw_log}'`);
|
||||
}
|
||||
const instantiationLogs = logs.parseLogs(initResult.logs);
|
||||
const contractAddress = logs.findAttribute(instantiationLogs, "message", "contract_address").value;
|
||||
console.info(`Contract instantiated for ${initMsg.symbol} at ${contractAddress}`);
|
||||
}
|
||||
const instantiationLogs = logs.parseLogs(instantiationResult.logs);
|
||||
const contractAddress = logs.findAttribute(instantiationLogs, "message", "contract_address").value;
|
||||
|
||||
console.info(`Contract instantiated at ${contractAddress}`);
|
||||
}
|
||||
|
||||
main().then(
|
||||
|
||||
Loading…
Reference in New Issue
Block a user