diff --git a/packages/cosmwasm/src/lcdapi/wasm.spec.ts b/packages/cosmwasm/src/lcdapi/wasm.spec.ts new file mode 100644 index 00000000..aa408871 --- /dev/null +++ b/packages/cosmwasm/src/lcdapi/wasm.spec.ts @@ -0,0 +1,510 @@ +/* eslint-disable @typescript-eslint/camelcase */ +import { Sha256 } from "@cosmjs/crypto"; +import { Bech32, fromAscii, fromBase64, fromHex, toAscii, toBase64, toHex } from "@cosmjs/encoding"; +import { + Coin, + coin, + coins, + LcdClient, + makeSignBytes, + Pen, + PostTxsResponse, + Secp256k1Pen, + StdFee, +} from "@cosmjs/sdk38"; +import { assert } from "@cosmjs/utils"; + +import { findAttribute, parseLogs } from "../logs"; +import { + isMsgInstantiateContract, + isMsgStoreCode, + MsgExecuteContract, + MsgInstantiateContract, + MsgStoreCode, +} from "../msgs"; +import { + alice, + bech32AddressMatcher, + ContractUploadInstructions, + deployedErc20, + fromOneElementArray, + getHackatom, + makeRandomAddress, + makeSignedTx, + pendingWithoutWasmd, + wasmd, + wasmdEnabled, +} from "../testutils.spec"; +import { setupWasmModule, WasmModule } from "./wasm"; + +type WasmClient = LcdClient & WasmModule; + +function makeWasmClient(apiUrl: string): WasmClient { + return LcdClient.withModules({ apiUrl }, setupWasmModule); +} + +async function uploadContract( + client: WasmClient, + pen: Pen, + contract: ContractUploadInstructions, +): Promise { + const memo = "My first contract on chain"; + const theMsg: MsgStoreCode = { + type: "wasm/store-code", + value: { + sender: alice.address0, + wasm_byte_code: toBase64(contract.data), + source: contract.source || "", + builder: contract.builder || "", + }, + }; + const fee: StdFee = { + amount: [ + { + amount: "5000000", + denom: "ucosm", + }, + ], + gas: "89000000", + }; + + const { account_number, sequence } = (await client.authAccounts(alice.address0)).result.value; + const signBytes = makeSignBytes([theMsg], fee, wasmd.chainId, memo, account_number, sequence); + const signature = await pen.sign(signBytes); + const signedTx = makeSignedTx(theMsg, fee, memo, signature); + return client.postTx(signedTx); +} + +async function instantiateContract( + client: WasmClient, + pen: Pen, + codeId: number, + beneficiaryAddress: string, + transferAmount?: readonly Coin[], +): Promise { + const memo = "Create an escrow instance"; + const theMsg: MsgInstantiateContract = { + type: "wasm/instantiate", + value: { + sender: alice.address0, + code_id: codeId.toString(), + label: "my escrow", + init_msg: { + verifier: alice.address0, + beneficiary: beneficiaryAddress, + }, + init_funds: transferAmount || [], + }, + }; + const fee: StdFee = { + amount: [ + { + amount: "5000000", + denom: "ucosm", + }, + ], + gas: "89000000", + }; + + const { account_number, sequence } = (await client.authAccounts(alice.address0)).result.value; + const signBytes = makeSignBytes([theMsg], fee, wasmd.chainId, memo, account_number, sequence); + const signature = await pen.sign(signBytes); + const signedTx = makeSignedTx(theMsg, fee, memo, signature); + return client.postTx(signedTx); +} + +async function executeContract( + client: WasmClient, + pen: Pen, + contractAddress: string, + msg: object, +): Promise { + const memo = "Time for action"; + const theMsg: MsgExecuteContract = { + type: "wasm/execute", + value: { + sender: alice.address0, + contract: contractAddress, + msg: msg, + sent_funds: [], + }, + }; + const fee: StdFee = { + amount: coins(5000000, "ucosm"), + gas: "89000000", + }; + + const { account_number, sequence } = (await client.authAccounts(alice.address0)).result.value; + const signBytes = makeSignBytes([theMsg], fee, wasmd.chainId, memo, account_number, sequence); + const signature = await pen.sign(signBytes); + const signedTx = makeSignedTx(theMsg, fee, memo, signature); + return client.postTx(signedTx); +} + +describe("wasm", () => { + it("can be constructed", () => { + const client = makeWasmClient(wasmd.endpoint); + expect(client).toBeTruthy(); + }); + + describe("txsQuery", () => { + it("can query by tags (module + code_id)", async () => { + pendingWithoutWasmd(); + const client = makeWasmClient(wasmd.endpoint); + const result = await client.txsQuery(`message.module=wasm&message.code_id=${deployedErc20.codeId}`); + expect(parseInt(result.count, 10)).toBeGreaterThanOrEqual(4); + + // Check first 4 results + const [store, hash, isa, jade] = result.txs.map((tx) => fromOneElementArray(tx.tx.value.msg)); + assert(isMsgStoreCode(store)); + assert(isMsgInstantiateContract(hash)); + assert(isMsgInstantiateContract(isa)); + assert(isMsgInstantiateContract(jade)); + expect(store.value).toEqual( + jasmine.objectContaining({ + sender: alice.address0, + source: deployedErc20.source, + builder: deployedErc20.builder, + }), + ); + expect(hash.value).toEqual({ + code_id: deployedErc20.codeId.toString(), + init_funds: [], + init_msg: jasmine.objectContaining({ + symbol: "HASH", + }), + label: "HASH", + sender: alice.address0, + }); + expect(isa.value).toEqual({ + code_id: deployedErc20.codeId.toString(), + init_funds: [], + init_msg: jasmine.objectContaining({ symbol: "ISA" }), + label: "ISA", + sender: alice.address0, + }); + expect(jade.value).toEqual({ + code_id: deployedErc20.codeId.toString(), + init_funds: [], + init_msg: jasmine.objectContaining({ symbol: "JADE" }), + label: "JADE", + sender: alice.address0, + admin: alice.address1, + }); + }); + + // Like previous test but filtered by message.action=store-code and message.action=instantiate + it("can query by tags (module + code_id + action)", async () => { + pendingWithoutWasmd(); + const client = makeWasmClient(wasmd.endpoint); + + { + const uploads = await client.txsQuery( + `message.module=wasm&message.code_id=${deployedErc20.codeId}&message.action=store-code`, + ); + expect(parseInt(uploads.count, 10)).toEqual(1); + const store = fromOneElementArray(uploads.txs[0].tx.value.msg); + assert(isMsgStoreCode(store)); + expect(store.value).toEqual( + jasmine.objectContaining({ + sender: alice.address0, + source: deployedErc20.source, + builder: deployedErc20.builder, + }), + ); + } + + { + const instantiations = await client.txsQuery( + `message.module=wasm&message.code_id=${deployedErc20.codeId}&message.action=instantiate`, + ); + expect(parseInt(instantiations.count, 10)).toBeGreaterThanOrEqual(3); + const [hash, isa, jade] = instantiations.txs.map((tx) => fromOneElementArray(tx.tx.value.msg)); + assert(isMsgInstantiateContract(hash)); + assert(isMsgInstantiateContract(isa)); + assert(isMsgInstantiateContract(jade)); + expect(hash.value).toEqual({ + code_id: deployedErc20.codeId.toString(), + init_funds: [], + init_msg: jasmine.objectContaining({ + symbol: "HASH", + }), + label: "HASH", + sender: alice.address0, + }); + expect(isa.value).toEqual({ + code_id: deployedErc20.codeId.toString(), + init_funds: [], + init_msg: jasmine.objectContaining({ symbol: "ISA" }), + label: "ISA", + sender: alice.address0, + }); + expect(jade.value).toEqual({ + code_id: deployedErc20.codeId.toString(), + init_funds: [], + init_msg: jasmine.objectContaining({ symbol: "JADE" }), + label: "JADE", + sender: alice.address0, + admin: alice.address1, + }); + } + }); + }); + + describe("postTx", () => { + it("can upload, instantiate and execute wasm", async () => { + pendingWithoutWasmd(); + const pen = await Secp256k1Pen.fromMnemonic(alice.mnemonic); + const client = makeWasmClient(wasmd.endpoint); + + const transferAmount = [coin(1234, "ucosm"), coin(321, "ustake")]; + const beneficiaryAddress = makeRandomAddress(); + + let codeId: number; + + // upload + { + // console.log("Raw log:", result.raw_log); + const result = await uploadContract(client, pen, getHackatom()); + expect(result.code).toBeFalsy(); + const logs = parseLogs(result.logs); + const codeIdAttr = findAttribute(logs, "message", "code_id"); + codeId = Number.parseInt(codeIdAttr.value, 10); + expect(codeId).toBeGreaterThanOrEqual(1); + expect(codeId).toBeLessThanOrEqual(200); + expect(result.data).toEqual(toHex(toAscii(`${codeId}`)).toUpperCase()); + } + + let contractAddress: string; + + // instantiate + { + const result = await instantiateContract(client, pen, codeId, beneficiaryAddress, transferAmount); + expect(result.code).toBeFalsy(); + // console.log("Raw log:", result.raw_log); + const logs = parseLogs(result.logs); + const contractAddressAttr = findAttribute(logs, "message", "contract_address"); + contractAddress = contractAddressAttr.value; + const amountAttr = findAttribute(logs, "transfer", "amount"); + expect(amountAttr.value).toEqual("1234ucosm,321ustake"); + expect(result.data).toEqual(toHex(Bech32.decode(contractAddress).data).toUpperCase()); + + const balance = (await client.authAccounts(contractAddress)).result.value.coins; + expect(balance).toEqual(transferAmount); + } + + // execute + { + const result = await executeContract(client, pen, contractAddress, { release: {} }); + expect(result.data).toEqual("F00BAA"); + expect(result.code).toBeFalsy(); + // console.log("Raw log:", result.logs); + const logs = parseLogs(result.logs); + const wasmEvent = logs.find(() => true)?.events.find((e) => e.type === "wasm"); + assert(wasmEvent, "Event of type wasm expected"); + expect(wasmEvent.attributes).toContain({ key: "action", value: "release" }); + expect(wasmEvent.attributes).toContain({ + key: "destination", + value: beneficiaryAddress, + }); + + // Verify token transfer from contract to beneficiary + const beneficiaryBalance = (await client.authAccounts(beneficiaryAddress)).result.value.coins; + expect(beneficiaryBalance).toEqual(transferAmount); + const contractBalance = (await client.authAccounts(contractAddress)).result.value.coins; + expect(contractBalance).toEqual([]); + } + }); + }); + + // The /wasm endpoints + + describe("query", () => { + it("can list upload code", async () => { + pendingWithoutWasmd(); + const pen = await Secp256k1Pen.fromMnemonic(alice.mnemonic); + const client = makeWasmClient(wasmd.endpoint); + + // check with contracts were here first to compare + const existingInfos = await client.listCodeInfo(); + existingInfos.forEach((val, idx) => expect(val.id).toEqual(idx + 1)); + const numExisting = existingInfos.length; + + // upload data + const hackatom = getHackatom(); + const result = await uploadContract(client, pen, hackatom); + expect(result.code).toBeFalsy(); + const logs = parseLogs(result.logs); + const codeIdAttr = findAttribute(logs, "message", "code_id"); + const codeId = Number.parseInt(codeIdAttr.value, 10); + + // ensure we were added to the end of the list + const newInfos = await client.listCodeInfo(); + expect(newInfos.length).toEqual(numExisting + 1); + const lastInfo = newInfos[newInfos.length - 1]; + expect(lastInfo.id).toEqual(codeId); + expect(lastInfo.creator).toEqual(alice.address0); + + // ensure metadata is present + expect(lastInfo.source).toEqual(hackatom.source); + expect(lastInfo.builder).toEqual(hackatom.builder); + + // check code hash matches expectation + const wasmHash = new Sha256(hackatom.data).digest(); + expect(lastInfo.data_hash.toLowerCase()).toEqual(toHex(wasmHash)); + + // download code and check against auto-gen + const { data } = await client.getCode(codeId); + expect(fromBase64(data)).toEqual(hackatom.data); + }); + + it("can list contracts and get info", async () => { + pendingWithoutWasmd(); + const pen = await Secp256k1Pen.fromMnemonic(alice.mnemonic); + const client = makeWasmClient(wasmd.endpoint); + const beneficiaryAddress = makeRandomAddress(); + const transferAmount: readonly Coin[] = [ + { + amount: "707707", + denom: "ucosm", + }, + ]; + + // reuse an existing contract, or upload if needed + let codeId: number; + const existingInfos = await client.listCodeInfo(); + if (existingInfos.length > 0) { + codeId = existingInfos[existingInfos.length - 1].id; + } else { + const uploadResult = await uploadContract(client, pen, getHackatom()); + expect(uploadResult.code).toBeFalsy(); + const uploadLogs = parseLogs(uploadResult.logs); + const codeIdAttr = findAttribute(uploadLogs, "message", "code_id"); + codeId = Number.parseInt(codeIdAttr.value, 10); + } + + // create new instance and compare before and after + const existingContractsByCode = await client.listContractsByCodeId(codeId); + for (const contract of existingContractsByCode) { + expect(contract.address).toMatch(bech32AddressMatcher); + expect(contract.code_id).toEqual(codeId); + expect(contract.creator).toMatch(bech32AddressMatcher); + expect(contract.label).toMatch(/^.+$/); + } + + const result = await instantiateContract(client, pen, codeId, beneficiaryAddress, transferAmount); + expect(result.code).toBeFalsy(); + const logs = parseLogs(result.logs); + const contractAddressAttr = findAttribute(logs, "message", "contract_address"); + const myAddress = contractAddressAttr.value; + + const newContractsByCode = await client.listContractsByCodeId(codeId); + expect(newContractsByCode.length).toEqual(existingContractsByCode.length + 1); + const newContract = newContractsByCode[newContractsByCode.length - 1]; + expect(newContract).toEqual( + jasmine.objectContaining({ + code_id: codeId, + creator: alice.address0, + label: "my escrow", + }), + ); + + // check out info + const myInfo = await client.getContractInfo(myAddress); + assert(myInfo); + expect(myInfo).toEqual( + jasmine.objectContaining({ + code_id: codeId, + creator: alice.address0, + init_msg: jasmine.objectContaining({ + beneficiary: beneficiaryAddress, + }), + }), + ); + expect(myInfo.admin).toBeUndefined(); + + // make sure random addresses don't give useful info + const nonExistentAddress = makeRandomAddress(); + expect(await client.getContractInfo(nonExistentAddress)).toBeNull(); + }); + + describe("contract state", () => { + const client = makeWasmClient(wasmd.endpoint); + const noContract = makeRandomAddress(); + const expectedKey = toAscii("config"); + let contractAddress: string | undefined; + + beforeAll(async () => { + if (wasmdEnabled()) { + const pen = await Secp256k1Pen.fromMnemonic(alice.mnemonic); + const uploadResult = await uploadContract(client, pen, getHackatom()); + 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 () => { + pendingWithoutWasmd(); + + // get contract state + const state = await client.getAllContractState(contractAddress!); + expect(state.length).toEqual(1); + const data = state[0]; + expect(data.key).toEqual(expectedKey); + const value = JSON.parse(fromAscii(data.val)); + expect(value.verifier).toBeDefined(); + expect(value.beneficiary).toBeDefined(); + + // bad address is empty array + const noContractState = await client.getAllContractState(noContract); + expect(noContractState).toEqual([]); + }); + + it("can query by key", async () => { + pendingWithoutWasmd(); + + // query by one key + const raw = await client.queryContractRaw(contractAddress!, expectedKey); + assert(raw, "must get result"); + const model = JSON.parse(fromAscii(raw)); + expect(model.verifier).toBeDefined(); + expect(model.beneficiary).toBeDefined(); + + // missing key is null + const missing = await client.queryContractRaw(contractAddress!, fromHex("cafe0dad")); + expect(missing).toBeNull(); + + // bad address is null + const noContractModel = await client.queryContractRaw(noContract, expectedKey); + expect(noContractModel).toBeNull(); + }); + + it("can make smart queries", async () => { + pendingWithoutWasmd(); + + // we can query the verifier properly + const resultDocument = await client.queryContractSmart(contractAddress!, { verifier: {} }); + expect(resultDocument).toEqual({ verifier: alice.address0 }); + + // invalid query syntax throws an error + await client.queryContractSmart(contractAddress!, { nosuchkey: {} }).then( + () => fail("shouldn't succeed"), + (error) => + expect(error).toMatch(/query wasm contract failed: parsing hackatom::contract::QueryMsg/), + ); + + // invalid address throws an error + await client.queryContractSmart(noContract, { verifier: {} }).then( + () => fail("shouldn't succeed"), + (error) => expect(error).toMatch("not found"), + ); + }); + }); + }); +}); diff --git a/packages/cosmwasm/src/lcdapi/wasm.ts b/packages/cosmwasm/src/lcdapi/wasm.ts new file mode 100644 index 00000000..538f4baa --- /dev/null +++ b/packages/cosmwasm/src/lcdapi/wasm.ts @@ -0,0 +1,157 @@ +import { fromBase64, fromUtf8, toHex, toUtf8 } from "@cosmjs/encoding"; +import { LcdApiArray, LcdClient, LcdModule, normalizeLcdApiArray } from "@cosmjs/sdk38"; + +import { JsonObject, Model, parseWasmData, WasmData } from "../types"; + +type WasmResponse = WasmSuccess | WasmError; + +interface WasmSuccess { + readonly height: string; + readonly result: T; +} + +interface WasmError { + readonly error: string; +} + +export interface CodeInfo { + readonly id: number; + /** Bech32 account address */ + readonly creator: string; + /** Hex-encoded sha256 hash of the code stored here */ + readonly data_hash: string; + /** + * An URL to a .tar.gz archive of the source code of the contract, which can be used to reproducibly build the Wasm bytecode. + * + * @see https://github.com/CosmWasm/cosmwasm-verify + */ + readonly source?: string; + /** + * A docker image (including version) to reproducibly build the Wasm bytecode from the source code. + * + * @example ```cosmwasm/rust-optimizer:0.8.0``` + * @see https://github.com/CosmWasm/cosmwasm-verify + */ + readonly builder?: string; +} + +export interface CodeDetails extends CodeInfo { + /** Base64 encoded raw wasm data */ + readonly data: string; +} + +// This is list view, without contract info +export interface ContractInfo { + readonly address: string; + readonly code_id: number; + /** Bech32 account address */ + readonly creator: string; + /** Bech32-encoded admin address */ + readonly admin?: string; + readonly label: string; +} + +export interface ContractDetails extends ContractInfo { + /** Argument passed on initialization of the contract */ + readonly init_msg: object; +} + +interface SmartQueryResponse { + // base64 encoded response + readonly smart: string; +} + +function isWasmError(resp: WasmResponse): resp is WasmError { + return (resp as WasmError).error !== undefined; +} + +function unwrapWasmResponse(response: WasmResponse): T { + if (isWasmError(response)) { + throw new Error(response.error); + } + return response.result; +} + +/** + * @see https://github.com/cosmwasm/wasmd/blob/master/x/wasm/client/rest/query.go#L19-L27 + */ +export interface WasmModule extends LcdModule { + readonly listCodeInfo: () => Promise; + + /** + * Downloads the original wasm bytecode by code ID. + * + * Throws an error if no code with this id + */ + readonly getCode: (id: number) => Promise; + + readonly listContractsByCodeId: (id: number) => Promise; + + /** + * Returns null when contract was not found at this address. + */ + readonly getContractInfo: (address: string) => Promise; + + /** + * Returns all contract state. + * This is an empty array if no such contract, or contract has no data. + */ + readonly getAllContractState: (address: string) => Promise; + + /** + * Returns the data at the key if present (unknown decoded json), + * or null if no data at this (contract address, key) pair + */ + readonly queryContractRaw: (address: string, key: Uint8Array) => Promise; + + /** + * Makes a smart query on the contract and parses the reponse as JSON. + * Throws error if no such contract exists, the query format is invalid or the response is invalid. + */ + readonly queryContractSmart: (address: string, query: object) => Promise; +} + +export function setupWasmModule(base: LcdClient): WasmModule { + return { + listCodeInfo: async () => { + const path = `/wasm/code`; + const responseData = (await base.get(path)) as WasmResponse>; + return normalizeLcdApiArray(unwrapWasmResponse(responseData)); + }, + getCode: async (id: number) => { + const path = `/wasm/code/${id}`; + const responseData = (await base.get(path)) as WasmResponse; + return unwrapWasmResponse(responseData); + }, + listContractsByCodeId: async (id: number) => { + const path = `/wasm/code/${id}/contracts`; + const responseData = (await base.get(path)) as WasmResponse>; + return normalizeLcdApiArray(unwrapWasmResponse(responseData)); + }, + getContractInfo: async (address: string) => { + const path = `/wasm/contract/${address}`; + const response = (await base.get(path)) as WasmResponse; + return unwrapWasmResponse(response); + }, + getAllContractState: async (address: string) => { + const path = `/wasm/contract/${address}/state`; + const responseData = (await base.get(path)) as WasmResponse>; + return normalizeLcdApiArray(unwrapWasmResponse(responseData)).map(parseWasmData); + }, + queryContractRaw: async (address: string, key: Uint8Array) => { + const hexKey = toHex(key); + const path = `/wasm/contract/${address}/raw/${hexKey}?encoding=hex`; + const responseData = (await base.get(path)) as WasmResponse; + const data = unwrapWasmResponse(responseData); + return data.length === 0 ? null : fromBase64(data[0].val); + }, + queryContractSmart: async (address: string, query: object) => { + const encoded = toHex(toUtf8(JSON.stringify(query))); + const path = `/wasm/contract/${address}/smart/${encoded}?encoding=hex`; + const responseData = (await base.get(path)) as WasmResponse; + const result = unwrapWasmResponse(responseData); + // By convention, smart queries must return a valid JSON document (see https://github.com/CosmWasm/cosmwasm/issues/144) + return JSON.parse(fromUtf8(fromBase64(result.smart))); + }, + }; +} diff --git a/packages/cosmwasm/types/lcdapi/wasm.d.ts b/packages/cosmwasm/types/lcdapi/wasm.d.ts new file mode 100644 index 00000000..3e4d67b8 --- /dev/null +++ b/packages/cosmwasm/types/lcdapi/wasm.d.ts @@ -0,0 +1,72 @@ +import { LcdClient, LcdModule } from "@cosmjs/sdk38"; +import { JsonObject, Model } from "../types"; +export interface CodeInfo { + readonly id: number; + /** Bech32 account address */ + readonly creator: string; + /** Hex-encoded sha256 hash of the code stored here */ + readonly data_hash: string; + /** + * An URL to a .tar.gz archive of the source code of the contract, which can be used to reproducibly build the Wasm bytecode. + * + * @see https://github.com/CosmWasm/cosmwasm-verify + */ + readonly source?: string; + /** + * A docker image (including version) to reproducibly build the Wasm bytecode from the source code. + * + * @example ```cosmwasm/rust-optimizer:0.8.0``` + * @see https://github.com/CosmWasm/cosmwasm-verify + */ + readonly builder?: string; +} +export interface CodeDetails extends CodeInfo { + /** Base64 encoded raw wasm data */ + readonly data: string; +} +export interface ContractInfo { + readonly address: string; + readonly code_id: number; + /** Bech32 account address */ + readonly creator: string; + /** Bech32-encoded admin address */ + readonly admin?: string; + readonly label: string; +} +export interface ContractDetails extends ContractInfo { + /** Argument passed on initialization of the contract */ + readonly init_msg: object; +} +/** + * @see https://github.com/cosmwasm/wasmd/blob/master/x/wasm/client/rest/query.go#L19-L27 + */ +export interface WasmModule extends LcdModule { + readonly listCodeInfo: () => Promise; + /** + * Downloads the original wasm bytecode by code ID. + * + * Throws an error if no code with this id + */ + readonly getCode: (id: number) => Promise; + readonly listContractsByCodeId: (id: number) => Promise; + /** + * Returns null when contract was not found at this address. + */ + readonly getContractInfo: (address: string) => Promise; + /** + * Returns all contract state. + * This is an empty array if no such contract, or contract has no data. + */ + readonly getAllContractState: (address: string) => Promise; + /** + * Returns the data at the key if present (unknown decoded json), + * or null if no data at this (contract address, key) pair + */ + readonly queryContractRaw: (address: string, key: Uint8Array) => Promise; + /** + * Makes a smart query on the contract and parses the reponse as JSON. + * Throws error if no such contract exists, the query format is invalid or the response is invalid. + */ + readonly queryContractSmart: (address: string, query: object) => Promise; +} +export declare function setupWasmModule(base: LcdClient): WasmModule; diff --git a/packages/cosmwasm/types/lcdapi/wasm.spec.d.ts b/packages/cosmwasm/types/lcdapi/wasm.spec.d.ts new file mode 100644 index 00000000..cb0ff5c3 --- /dev/null +++ b/packages/cosmwasm/types/lcdapi/wasm.spec.d.ts @@ -0,0 +1 @@ +export {};