Remove RestClient from sdk38 and cosmwasm
This commit is contained in:
parent
dc661dfe5c
commit
de510c98e5
@ -15,4 +15,4 @@ const faucetMnemonic =
|
||||
const faucetAddress = "cosmos1pkptre7fdkl6gfrzlesjjvhxhlc3r4gmmk8rs6";
|
||||
|
||||
const pen = await Secp256k1Pen.fromMnemonic(faucetMnemonic);
|
||||
const client = new RestClient(defaultHttpUrl);
|
||||
const client = new LcdClient(defaultHttpUrl);
|
||||
|
||||
@ -96,10 +96,10 @@ export function main(originalArgs: readonly string[]): void {
|
||||
"Msg",
|
||||
"MsgDelegate",
|
||||
"MsgSend",
|
||||
"LcdClient",
|
||||
"Pen",
|
||||
"PubKey",
|
||||
"pubkeyToAddress",
|
||||
"RestClient",
|
||||
"Secp256k1Pen",
|
||||
"SigningCosmosClient",
|
||||
"StdFee",
|
||||
|
||||
@ -2,7 +2,6 @@ import * as logs from "./logs";
|
||||
export { logs };
|
||||
|
||||
export { setupWasmExtension, WasmExtension } from "./lcdapi/wasm";
|
||||
export { RestClient } from "./restclient";
|
||||
export {
|
||||
Account,
|
||||
Block,
|
||||
|
||||
@ -1,494 +0,0 @@
|
||||
/* 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, 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 { RestClient } from "./restclient";
|
||||
import {
|
||||
alice,
|
||||
bech32AddressMatcher,
|
||||
ContractUploadInstructions,
|
||||
deployedErc20,
|
||||
fromOneElementArray,
|
||||
getHackatom,
|
||||
makeRandomAddress,
|
||||
makeSignedTx,
|
||||
pendingWithoutWasmd,
|
||||
wasmd,
|
||||
wasmdEnabled,
|
||||
} from "./testutils.spec";
|
||||
|
||||
async function uploadContract(
|
||||
client: RestClient,
|
||||
pen: Pen,
|
||||
contract: ContractUploadInstructions,
|
||||
): Promise<PostTxsResponse> {
|
||||
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: RestClient,
|
||||
pen: Pen,
|
||||
codeId: number,
|
||||
beneficiaryAddress: string,
|
||||
transferAmount?: readonly Coin[],
|
||||
): Promise<PostTxsResponse> {
|
||||
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: RestClient,
|
||||
pen: Pen,
|
||||
contractAddress: string,
|
||||
msg: object,
|
||||
): Promise<PostTxsResponse> {
|
||||
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("RestClient", () => {
|
||||
it("can be constructed", () => {
|
||||
const client = new RestClient(wasmd.endpoint);
|
||||
expect(client).toBeTruthy();
|
||||
});
|
||||
|
||||
describe("txsQuery", () => {
|
||||
it("can query by tags (module + code_id)", async () => {
|
||||
pendingWithoutWasmd();
|
||||
const client = new RestClient(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 = new RestClient(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 = new RestClient(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 = new RestClient(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 = new RestClient(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 = new RestClient(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"),
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@ -1,163 +0,0 @@
|
||||
import { fromBase64, fromUtf8, toHex, toUtf8 } from "@cosmjs/encoding";
|
||||
import { BroadcastMode, RestClient as BaseRestClient } from "@cosmjs/sdk38";
|
||||
|
||||
import { JsonObject, Model, parseWasmData, WasmData } from "./types";
|
||||
|
||||
// Currently all wasm query responses return json-encoded strings...
|
||||
// later deprecate this and use the specific types for result
|
||||
// (assuming it is inlined, no second parse needed)
|
||||
type WasmResponse<T = string> = WasmSuccess<T> | WasmError;
|
||||
|
||||
interface WasmSuccess<T = string> {
|
||||
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;
|
||||
}
|
||||
|
||||
/** Unfortunately, Cosmos SDK encodes empty arrays as null */
|
||||
type CosmosSdkArray<T> = readonly T[] | null;
|
||||
|
||||
function normalizeArray<T>(backend: CosmosSdkArray<T>): readonly T[] {
|
||||
return backend || [];
|
||||
}
|
||||
|
||||
function isWasmError<T>(resp: WasmResponse<T>): resp is WasmError {
|
||||
return (resp as WasmError).error !== undefined;
|
||||
}
|
||||
|
||||
function unwrapWasmResponse<T>(response: WasmResponse<T>): T {
|
||||
if (isWasmError(response)) {
|
||||
throw new Error(response.error);
|
||||
}
|
||||
return response.result;
|
||||
}
|
||||
|
||||
export class RestClient extends BaseRestClient {
|
||||
/**
|
||||
* Creates a new client to interact with a Cosmos SDK light client daemon.
|
||||
* This class tries to be a direct mapping onto the API. Some basic decoding and normalizatin is done
|
||||
* but things like caching are done at a higher level.
|
||||
*
|
||||
* When building apps, you should not need to use this class directly. If you do, this indicates a missing feature
|
||||
* in higher level components. Feel free to raise an issue in this case.
|
||||
*
|
||||
* @param apiUrl The URL of a Cosmos SDK light client daemon API (sometimes called REST server or REST API)
|
||||
* @param broadcastMode Defines at which point of the transaction processing the postTx method (i.e. transaction broadcasting) returns
|
||||
*/
|
||||
public constructor(apiUrl: string, broadcastMode = BroadcastMode.Block) {
|
||||
super(apiUrl, broadcastMode);
|
||||
}
|
||||
|
||||
// The /wasm endpoints
|
||||
|
||||
// wasm rest queries are listed here: https://github.com/cosmwasm/wasmd/blob/master/x/wasm/client/rest/query.go#L19-L27
|
||||
public async listCodeInfo(): Promise<readonly CodeInfo[]> {
|
||||
const path = `/wasm/code`;
|
||||
const responseData = (await this.get(path)) as WasmResponse<CosmosSdkArray<CodeInfo>>;
|
||||
return normalizeArray(unwrapWasmResponse(responseData));
|
||||
}
|
||||
|
||||
// this will download the original wasm bytecode by code id
|
||||
// throws error if no code with this id
|
||||
public async getCode(id: number): Promise<CodeDetails> {
|
||||
const path = `/wasm/code/${id}`;
|
||||
const responseData = (await this.get(path)) as WasmResponse<CodeDetails>;
|
||||
return unwrapWasmResponse(responseData);
|
||||
}
|
||||
|
||||
public async listContractsByCodeId(id: number): Promise<readonly ContractInfo[]> {
|
||||
const path = `/wasm/code/${id}/contracts`;
|
||||
const responseData = (await this.get(path)) as WasmResponse<CosmosSdkArray<ContractInfo>>;
|
||||
return normalizeArray(unwrapWasmResponse(responseData));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns null when contract was not found at this address.
|
||||
*/
|
||||
public async getContractInfo(address: string): Promise<ContractDetails | null> {
|
||||
const path = `/wasm/contract/${address}`;
|
||||
const response = (await this.get(path)) as WasmResponse<ContractDetails | null>;
|
||||
return unwrapWasmResponse(response);
|
||||
}
|
||||
|
||||
// Returns all contract state.
|
||||
// This is an empty array if no such contract, or contract has no data.
|
||||
public async getAllContractState(address: string): Promise<readonly Model[]> {
|
||||
const path = `/wasm/contract/${address}/state`;
|
||||
const responseData = (await this.get(path)) as WasmResponse<CosmosSdkArray<WasmData>>;
|
||||
return normalizeArray(unwrapWasmResponse(responseData)).map(parseWasmData);
|
||||
}
|
||||
|
||||
// Returns the data at the key if present (unknown decoded json),
|
||||
// or null if no data at this (contract address, key) pair
|
||||
public async queryContractRaw(address: string, key: Uint8Array): Promise<Uint8Array | null> {
|
||||
const hexKey = toHex(key);
|
||||
const path = `/wasm/contract/${address}/raw/${hexKey}?encoding=hex`;
|
||||
const responseData = (await this.get(path)) as WasmResponse<WasmData[]>;
|
||||
const data = unwrapWasmResponse(responseData);
|
||||
return data.length === 0 ? null : fromBase64(data[0].val);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
public async queryContractSmart(address: string, query: object): Promise<JsonObject> {
|
||||
const encoded = toHex(toUtf8(JSON.stringify(query)));
|
||||
const path = `/wasm/contract/${address}/smart/${encoded}?encoding=hex`;
|
||||
const responseData = (await this.get(path)) as WasmResponse<SmartQueryResponse>;
|
||||
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)));
|
||||
}
|
||||
}
|
||||
1
packages/cosmwasm/types/index.d.ts
vendored
1
packages/cosmwasm/types/index.d.ts
vendored
@ -1,7 +1,6 @@
|
||||
import * as logs from "./logs";
|
||||
export { logs };
|
||||
export { setupWasmExtension, WasmExtension } from "./lcdapi/wasm";
|
||||
export { RestClient } from "./restclient";
|
||||
export {
|
||||
Account,
|
||||
Block,
|
||||
|
||||
67
packages/cosmwasm/types/restclient.d.ts
vendored
67
packages/cosmwasm/types/restclient.d.ts
vendored
@ -1,67 +0,0 @@
|
||||
import { BroadcastMode, RestClient as BaseRestClient } 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;
|
||||
}
|
||||
export declare class RestClient extends BaseRestClient {
|
||||
/**
|
||||
* Creates a new client to interact with a Cosmos SDK light client daemon.
|
||||
* This class tries to be a direct mapping onto the API. Some basic decoding and normalizatin is done
|
||||
* but things like caching are done at a higher level.
|
||||
*
|
||||
* When building apps, you should not need to use this class directly. If you do, this indicates a missing feature
|
||||
* in higher level components. Feel free to raise an issue in this case.
|
||||
*
|
||||
* @param apiUrl The URL of a Cosmos SDK light client daemon API (sometimes called REST server or REST API)
|
||||
* @param broadcastMode Defines at which point of the transaction processing the postTx method (i.e. transaction broadcasting) returns
|
||||
*/
|
||||
constructor(apiUrl: string, broadcastMode?: BroadcastMode);
|
||||
listCodeInfo(): Promise<readonly CodeInfo[]>;
|
||||
getCode(id: number): Promise<CodeDetails>;
|
||||
listContractsByCodeId(id: number): Promise<readonly ContractInfo[]>;
|
||||
/**
|
||||
* Returns null when contract was not found at this address.
|
||||
*/
|
||||
getContractInfo(address: string): Promise<ContractDetails | null>;
|
||||
getAllContractState(address: string): Promise<readonly Model[]>;
|
||||
queryContractRaw(address: string, key: Uint8Array): Promise<Uint8Array | null>;
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
queryContractSmart(address: string, query: object): Promise<JsonObject>;
|
||||
}
|
||||
@ -37,7 +37,6 @@ export {
|
||||
SupplyExtension,
|
||||
TxsResponse,
|
||||
} from "./lcdapi";
|
||||
export { RestClient } from "./restclient";
|
||||
export { isMsgDelegate, isMsgSend, Msg, MsgDelegate, MsgSend } from "./msgs";
|
||||
export { Pen, Secp256k1Pen, makeCosmoshubPath } from "./pen";
|
||||
export { decodeBech32Pubkey, encodeBech32Pubkey, encodeSecp256k1Pubkey } from "./pubkey";
|
||||
|
||||
@ -1,885 +0,0 @@
|
||||
/* eslint-disable @typescript-eslint/camelcase */
|
||||
import { assert, sleep } from "@cosmjs/utils";
|
||||
import { ReadonlyDate } from "readonly-date";
|
||||
|
||||
import { rawSecp256k1PubkeyToAddress } from "./address";
|
||||
import { isPostTxFailure } from "./cosmosclient";
|
||||
import { makeSignBytes } from "./encoding";
|
||||
import { TxsResponse } from "./lcdapi";
|
||||
import { parseLogs } from "./logs";
|
||||
import { MsgSend } from "./msgs";
|
||||
import { makeCosmoshubPath, Secp256k1Pen } from "./pen";
|
||||
import { encodeBech32Pubkey } from "./pubkey";
|
||||
import { RestClient } from "./restclient";
|
||||
import { SigningCosmosClient } from "./signingcosmosclient";
|
||||
import cosmoshub from "./testdata/cosmoshub.json";
|
||||
import {
|
||||
faucet,
|
||||
makeRandomAddress,
|
||||
makeSignedTx,
|
||||
nonNegativeIntegerMatcher,
|
||||
pendingWithoutWasmd,
|
||||
semverMatcher,
|
||||
tendermintAddressMatcher,
|
||||
tendermintIdMatcher,
|
||||
tendermintOptionalIdMatcher,
|
||||
tendermintShortHashMatcher,
|
||||
unused,
|
||||
wasmd,
|
||||
wasmdEnabled,
|
||||
} from "./testutils.spec";
|
||||
import { StdFee } from "./types";
|
||||
|
||||
describe("RestClient", () => {
|
||||
const defaultRecipientAddress = makeRandomAddress();
|
||||
|
||||
it("can be constructed", () => {
|
||||
const client = new RestClient(wasmd.endpoint);
|
||||
expect(client).toBeTruthy();
|
||||
});
|
||||
|
||||
// The /auth endpoints
|
||||
|
||||
describe("authAccounts", () => {
|
||||
it("works for unused account without pubkey", async () => {
|
||||
pendingWithoutWasmd();
|
||||
const client = new RestClient(wasmd.endpoint);
|
||||
const { height, result } = await client.authAccounts(unused.address);
|
||||
expect(height).toMatch(nonNegativeIntegerMatcher);
|
||||
expect(result).toEqual({
|
||||
type: "cosmos-sdk/Account",
|
||||
value: {
|
||||
address: unused.address,
|
||||
public_key: "", // not known to the chain
|
||||
coins: [
|
||||
{
|
||||
amount: "1000000000",
|
||||
denom: "ucosm",
|
||||
},
|
||||
{
|
||||
amount: "1000000000",
|
||||
denom: "ustake",
|
||||
},
|
||||
],
|
||||
account_number: unused.accountNumber,
|
||||
sequence: 0,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
// This fails in the first test run if you forget to run `./scripts/wasmd/init.sh`
|
||||
it("has correct pubkey for faucet", async () => {
|
||||
pendingWithoutWasmd();
|
||||
const client = new RestClient(wasmd.endpoint);
|
||||
const { result } = await client.authAccounts(faucet.address);
|
||||
expect(result.value).toEqual(
|
||||
jasmine.objectContaining({
|
||||
public_key: encodeBech32Pubkey(faucet.pubkey, "cosmospub"),
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
// This property is used by CosmWasmClient.getAccount
|
||||
it("returns empty address for non-existent account", async () => {
|
||||
pendingWithoutWasmd();
|
||||
const client = new RestClient(wasmd.endpoint);
|
||||
const nonExistentAccount = makeRandomAddress();
|
||||
const { result } = await client.authAccounts(nonExistentAccount);
|
||||
expect(result).toEqual({
|
||||
type: "cosmos-sdk/Account",
|
||||
value: jasmine.objectContaining({ address: "" }),
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// The /blocks endpoints
|
||||
|
||||
describe("blocksLatest", () => {
|
||||
it("works", async () => {
|
||||
pendingWithoutWasmd();
|
||||
const client = new RestClient(wasmd.endpoint);
|
||||
const response = await client.blocksLatest();
|
||||
|
||||
// id
|
||||
expect(response.block_id.hash).toMatch(tendermintIdMatcher);
|
||||
|
||||
// header
|
||||
expect(response.block.header.version).toEqual({ block: "10", app: "0" });
|
||||
expect(parseInt(response.block.header.height, 10)).toBeGreaterThanOrEqual(1);
|
||||
expect(response.block.header.chain_id).toEqual(wasmd.chainId);
|
||||
expect(new ReadonlyDate(response.block.header.time).getTime()).toBeLessThan(ReadonlyDate.now());
|
||||
expect(new ReadonlyDate(response.block.header.time).getTime()).toBeGreaterThanOrEqual(
|
||||
ReadonlyDate.now() - 5_000,
|
||||
);
|
||||
expect(response.block.header.last_commit_hash).toMatch(tendermintIdMatcher);
|
||||
expect(response.block.header.last_block_id.hash).toMatch(tendermintIdMatcher);
|
||||
expect(response.block.header.data_hash).toMatch(tendermintOptionalIdMatcher);
|
||||
expect(response.block.header.validators_hash).toMatch(tendermintIdMatcher);
|
||||
expect(response.block.header.next_validators_hash).toMatch(tendermintIdMatcher);
|
||||
expect(response.block.header.consensus_hash).toMatch(tendermintIdMatcher);
|
||||
expect(response.block.header.app_hash).toMatch(tendermintIdMatcher);
|
||||
expect(response.block.header.last_results_hash).toMatch(tendermintOptionalIdMatcher);
|
||||
expect(response.block.header.evidence_hash).toMatch(tendermintOptionalIdMatcher);
|
||||
expect(response.block.header.proposer_address).toMatch(tendermintAddressMatcher);
|
||||
|
||||
// data
|
||||
expect(response.block.data.txs === null || Array.isArray(response.block.data.txs)).toEqual(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe("blocks", () => {
|
||||
it("works for block by height", async () => {
|
||||
pendingWithoutWasmd();
|
||||
const client = new RestClient(wasmd.endpoint);
|
||||
const height = parseInt((await client.blocksLatest()).block.header.height, 10);
|
||||
const response = await client.blocks(height - 1);
|
||||
|
||||
// id
|
||||
expect(response.block_id.hash).toMatch(tendermintIdMatcher);
|
||||
|
||||
// header
|
||||
expect(response.block.header.version).toEqual({ block: "10", app: "0" });
|
||||
expect(response.block.header.height).toEqual(`${height - 1}`);
|
||||
expect(response.block.header.chain_id).toEqual(wasmd.chainId);
|
||||
expect(new ReadonlyDate(response.block.header.time).getTime()).toBeLessThan(ReadonlyDate.now());
|
||||
expect(new ReadonlyDate(response.block.header.time).getTime()).toBeGreaterThanOrEqual(
|
||||
ReadonlyDate.now() - 5_000,
|
||||
);
|
||||
expect(response.block.header.last_commit_hash).toMatch(tendermintIdMatcher);
|
||||
expect(response.block.header.last_block_id.hash).toMatch(tendermintIdMatcher);
|
||||
expect(response.block.header.data_hash).toMatch(tendermintOptionalIdMatcher);
|
||||
expect(response.block.header.validators_hash).toMatch(tendermintIdMatcher);
|
||||
expect(response.block.header.next_validators_hash).toMatch(tendermintIdMatcher);
|
||||
expect(response.block.header.consensus_hash).toMatch(tendermintIdMatcher);
|
||||
expect(response.block.header.app_hash).toMatch(tendermintIdMatcher);
|
||||
expect(response.block.header.last_results_hash).toMatch(tendermintOptionalIdMatcher);
|
||||
expect(response.block.header.evidence_hash).toMatch(tendermintOptionalIdMatcher);
|
||||
expect(response.block.header.proposer_address).toMatch(tendermintAddressMatcher);
|
||||
|
||||
// data
|
||||
expect(response.block.data.txs === null || Array.isArray(response.block.data.txs)).toEqual(true);
|
||||
});
|
||||
});
|
||||
|
||||
// The /node_info endpoint
|
||||
|
||||
describe("nodeInfo", () => {
|
||||
it("works", async () => {
|
||||
pendingWithoutWasmd();
|
||||
const client = new RestClient(wasmd.endpoint);
|
||||
const { node_info, application_version } = await client.nodeInfo();
|
||||
|
||||
expect(node_info).toEqual({
|
||||
protocol_version: { p2p: "7", block: "10", app: "0" },
|
||||
id: jasmine.stringMatching(tendermintShortHashMatcher),
|
||||
listen_addr: "tcp://0.0.0.0:26656",
|
||||
network: wasmd.chainId,
|
||||
version: jasmine.stringMatching(/^0\.33\.[0-9]+$/),
|
||||
channels: "4020212223303800",
|
||||
moniker: wasmd.chainId,
|
||||
other: { tx_index: "on", rpc_address: "tcp://0.0.0.0:26657" },
|
||||
});
|
||||
expect(application_version).toEqual({
|
||||
name: "wasm",
|
||||
server_name: "wasmd",
|
||||
client_name: "wasmcli",
|
||||
version: jasmine.stringMatching(semverMatcher),
|
||||
commit: jasmine.stringMatching(tendermintShortHashMatcher),
|
||||
build_tags: "netgo,ledger,muslc",
|
||||
go: jasmine.stringMatching(/^go version go1\.[0-9]+\.[0-9]+ linux\/amd64$/),
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// The /txs endpoints
|
||||
|
||||
describe("txById", () => {
|
||||
let successful:
|
||||
| {
|
||||
readonly sender: string;
|
||||
readonly recipient: string;
|
||||
readonly hash: string;
|
||||
}
|
||||
| undefined;
|
||||
let unsuccessful:
|
||||
| {
|
||||
readonly sender: string;
|
||||
readonly recipient: string;
|
||||
readonly hash: string;
|
||||
}
|
||||
| undefined;
|
||||
|
||||
beforeAll(async () => {
|
||||
if (wasmdEnabled()) {
|
||||
const pen = await Secp256k1Pen.fromMnemonic(faucet.mnemonic);
|
||||
const client = new SigningCosmosClient(wasmd.endpoint, faucet.address, (signBytes) =>
|
||||
pen.sign(signBytes),
|
||||
);
|
||||
|
||||
{
|
||||
const recipient = makeRandomAddress();
|
||||
const transferAmount = {
|
||||
denom: "ucosm",
|
||||
amount: "1234567",
|
||||
};
|
||||
const result = await client.sendTokens(recipient, [transferAmount]);
|
||||
successful = {
|
||||
sender: faucet.address,
|
||||
recipient: recipient,
|
||||
hash: result.transactionHash,
|
||||
};
|
||||
}
|
||||
|
||||
{
|
||||
const memo = "Sending more than I can afford";
|
||||
const recipient = makeRandomAddress();
|
||||
const transferAmount = [
|
||||
{
|
||||
denom: "ucosm",
|
||||
amount: "123456700000000",
|
||||
},
|
||||
];
|
||||
const sendMsg: MsgSend = {
|
||||
type: "cosmos-sdk/MsgSend",
|
||||
value: {
|
||||
// eslint-disable-next-line @typescript-eslint/camelcase
|
||||
from_address: faucet.address,
|
||||
// eslint-disable-next-line @typescript-eslint/camelcase
|
||||
to_address: recipient,
|
||||
amount: transferAmount,
|
||||
},
|
||||
};
|
||||
const fee = {
|
||||
amount: [
|
||||
{
|
||||
denom: "ucosm",
|
||||
amount: "2000",
|
||||
},
|
||||
],
|
||||
gas: "80000", // 80k
|
||||
};
|
||||
const { accountNumber, sequence } = await client.getNonce();
|
||||
const chainId = await client.getChainId();
|
||||
const signBytes = makeSignBytes([sendMsg], fee, chainId, memo, accountNumber, sequence);
|
||||
const signature = await pen.sign(signBytes);
|
||||
const signedTx = {
|
||||
msg: [sendMsg],
|
||||
fee: fee,
|
||||
memo: memo,
|
||||
signatures: [signature],
|
||||
};
|
||||
const transactionId = await client.getIdentifier({ type: "cosmos-sdk/StdTx", value: signedTx });
|
||||
const result = await client.postTx(signedTx);
|
||||
assert(isPostTxFailure(result));
|
||||
unsuccessful = {
|
||||
sender: faucet.address,
|
||||
recipient: recipient,
|
||||
hash: transactionId,
|
||||
};
|
||||
}
|
||||
|
||||
await sleep(75); // wait until transactions are indexed
|
||||
}
|
||||
});
|
||||
|
||||
it("works for successful transaction", async () => {
|
||||
pendingWithoutWasmd();
|
||||
assert(successful);
|
||||
const client = new RestClient(wasmd.endpoint);
|
||||
const result = await client.txById(successful.hash);
|
||||
expect(result.height).toBeGreaterThanOrEqual(1);
|
||||
expect(result.txhash).toEqual(successful.hash);
|
||||
expect(result.codespace).toBeUndefined();
|
||||
expect(result.code).toBeUndefined();
|
||||
const logs = parseLogs(result.logs);
|
||||
expect(logs).toEqual([
|
||||
{
|
||||
msg_index: 0,
|
||||
log: "",
|
||||
events: [
|
||||
{
|
||||
type: "message",
|
||||
attributes: [
|
||||
{ key: "action", value: "send" },
|
||||
{ key: "sender", value: successful.sender },
|
||||
{ key: "module", value: "bank" },
|
||||
],
|
||||
},
|
||||
{
|
||||
type: "transfer",
|
||||
attributes: [
|
||||
{ key: "recipient", value: successful.recipient },
|
||||
{ key: "sender", value: successful.sender },
|
||||
{ key: "amount", value: "1234567ucosm" },
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it("works for unsuccessful transaction", async () => {
|
||||
pendingWithoutWasmd();
|
||||
assert(unsuccessful);
|
||||
const client = new RestClient(wasmd.endpoint);
|
||||
const result = await client.txById(unsuccessful.hash);
|
||||
expect(result.height).toBeGreaterThanOrEqual(1);
|
||||
expect(result.txhash).toEqual(unsuccessful.hash);
|
||||
expect(result.codespace).toEqual("sdk");
|
||||
expect(result.code).toEqual(5);
|
||||
expect(result.logs).toBeUndefined();
|
||||
expect(result.raw_log).toContain("insufficient funds");
|
||||
});
|
||||
});
|
||||
|
||||
describe("txsQuery", () => {
|
||||
let posted:
|
||||
| {
|
||||
readonly sender: string;
|
||||
readonly recipient: string;
|
||||
readonly hash: string;
|
||||
readonly height: number;
|
||||
readonly tx: TxsResponse;
|
||||
}
|
||||
| undefined;
|
||||
|
||||
beforeAll(async () => {
|
||||
if (wasmdEnabled()) {
|
||||
const pen = await Secp256k1Pen.fromMnemonic(faucet.mnemonic);
|
||||
const client = new SigningCosmosClient(wasmd.endpoint, faucet.address, (signBytes) =>
|
||||
pen.sign(signBytes),
|
||||
);
|
||||
|
||||
const recipient = makeRandomAddress();
|
||||
const transferAmount = [
|
||||
{
|
||||
denom: "ucosm",
|
||||
amount: "1234567",
|
||||
},
|
||||
];
|
||||
const result = await client.sendTokens(recipient, transferAmount);
|
||||
|
||||
await sleep(75); // wait until tx is indexed
|
||||
const txDetails = await new RestClient(wasmd.endpoint).txById(result.transactionHash);
|
||||
posted = {
|
||||
sender: faucet.address,
|
||||
recipient: recipient,
|
||||
hash: result.transactionHash,
|
||||
height: Number.parseInt(txDetails.height, 10),
|
||||
tx: txDetails,
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
it("can query transactions by height", async () => {
|
||||
pendingWithoutWasmd();
|
||||
assert(posted);
|
||||
const client = new RestClient(wasmd.endpoint);
|
||||
const result = await client.txsQuery(`tx.height=${posted.height}&limit=26`);
|
||||
expect(result).toEqual({
|
||||
count: jasmine.stringMatching(/^(1|2|3|4|5)$/), // 1-5 transactions as string
|
||||
limit: "26",
|
||||
page_number: "1",
|
||||
page_total: "1",
|
||||
total_count: jasmine.stringMatching(/^(1|2|3|4|5)$/), // 1-5 transactions as string
|
||||
txs: jasmine.arrayContaining([posted.tx]),
|
||||
});
|
||||
});
|
||||
|
||||
it("can query transactions by ID", async () => {
|
||||
pendingWithoutWasmd();
|
||||
assert(posted);
|
||||
const client = new RestClient(wasmd.endpoint);
|
||||
const result = await client.txsQuery(`tx.hash=${posted.hash}&limit=26`);
|
||||
expect(result).toEqual({
|
||||
count: "1",
|
||||
limit: "26",
|
||||
page_number: "1",
|
||||
page_total: "1",
|
||||
total_count: "1",
|
||||
txs: [posted.tx],
|
||||
});
|
||||
});
|
||||
|
||||
it("can query transactions by sender", async () => {
|
||||
pendingWithoutWasmd();
|
||||
assert(posted);
|
||||
const client = new RestClient(wasmd.endpoint);
|
||||
const result = await client.txsQuery(`message.sender=${posted.sender}&limit=200`);
|
||||
expect(parseInt(result.count, 10)).toBeGreaterThanOrEqual(1);
|
||||
expect(parseInt(result.limit, 10)).toEqual(200);
|
||||
expect(parseInt(result.page_number, 10)).toEqual(1);
|
||||
expect(parseInt(result.page_total, 10)).toEqual(1);
|
||||
expect(parseInt(result.total_count, 10)).toBeGreaterThanOrEqual(1);
|
||||
expect(result.txs.length).toBeGreaterThanOrEqual(1);
|
||||
expect(result.txs[result.txs.length - 1]).toEqual(posted.tx);
|
||||
});
|
||||
|
||||
it("can query transactions by recipient", async () => {
|
||||
pendingWithoutWasmd();
|
||||
assert(posted);
|
||||
const client = new RestClient(wasmd.endpoint);
|
||||
const result = await client.txsQuery(`transfer.recipient=${posted.recipient}&limit=200`);
|
||||
expect(parseInt(result.count, 10)).toEqual(1);
|
||||
expect(parseInt(result.limit, 10)).toEqual(200);
|
||||
expect(parseInt(result.page_number, 10)).toEqual(1);
|
||||
expect(parseInt(result.page_total, 10)).toEqual(1);
|
||||
expect(parseInt(result.total_count, 10)).toEqual(1);
|
||||
expect(result.txs.length).toBeGreaterThanOrEqual(1);
|
||||
expect(result.txs[result.txs.length - 1]).toEqual(posted.tx);
|
||||
});
|
||||
|
||||
it("can filter by tx.hash and tx.minheight", async () => {
|
||||
pending("This combination is broken 🤷♂️. Handle client-side at higher level.");
|
||||
pendingWithoutWasmd();
|
||||
assert(posted);
|
||||
const client = new RestClient(wasmd.endpoint);
|
||||
const hashQuery = `tx.hash=${posted.hash}`;
|
||||
|
||||
{
|
||||
const { count } = await client.txsQuery(`${hashQuery}&tx.minheight=0`);
|
||||
expect(count).toEqual("1");
|
||||
}
|
||||
|
||||
{
|
||||
const { count } = await client.txsQuery(`${hashQuery}&tx.minheight=${posted.height - 1}`);
|
||||
expect(count).toEqual("1");
|
||||
}
|
||||
|
||||
{
|
||||
const { count } = await client.txsQuery(`${hashQuery}&tx.minheight=${posted.height}`);
|
||||
expect(count).toEqual("1");
|
||||
}
|
||||
|
||||
{
|
||||
const { count } = await client.txsQuery(`${hashQuery}&tx.minheight=${posted.height + 1}`);
|
||||
expect(count).toEqual("0");
|
||||
}
|
||||
});
|
||||
|
||||
it("can filter by recipient and tx.minheight", async () => {
|
||||
pendingWithoutWasmd();
|
||||
assert(posted);
|
||||
const client = new RestClient(wasmd.endpoint);
|
||||
const recipientQuery = `transfer.recipient=${posted.recipient}`;
|
||||
|
||||
{
|
||||
const { count } = await client.txsQuery(`${recipientQuery}&tx.minheight=0`);
|
||||
expect(count).toEqual("1");
|
||||
}
|
||||
|
||||
{
|
||||
const { count } = await client.txsQuery(`${recipientQuery}&tx.minheight=${posted.height - 1}`);
|
||||
expect(count).toEqual("1");
|
||||
}
|
||||
|
||||
{
|
||||
const { count } = await client.txsQuery(`${recipientQuery}&tx.minheight=${posted.height}`);
|
||||
expect(count).toEqual("1");
|
||||
}
|
||||
|
||||
{
|
||||
const { count } = await client.txsQuery(`${recipientQuery}&tx.minheight=${posted.height + 1}`);
|
||||
expect(count).toEqual("0");
|
||||
}
|
||||
});
|
||||
|
||||
it("can filter by recipient and tx.maxheight", async () => {
|
||||
pendingWithoutWasmd();
|
||||
assert(posted);
|
||||
const client = new RestClient(wasmd.endpoint);
|
||||
const recipientQuery = `transfer.recipient=${posted.recipient}`;
|
||||
|
||||
{
|
||||
const { count } = await client.txsQuery(`${recipientQuery}&tx.maxheight=9999999999999`);
|
||||
expect(count).toEqual("1");
|
||||
}
|
||||
|
||||
{
|
||||
const { count } = await client.txsQuery(`${recipientQuery}&tx.maxheight=${posted.height + 1}`);
|
||||
expect(count).toEqual("1");
|
||||
}
|
||||
|
||||
{
|
||||
const { count } = await client.txsQuery(`${recipientQuery}&tx.maxheight=${posted.height}`);
|
||||
expect(count).toEqual("1");
|
||||
}
|
||||
|
||||
{
|
||||
const { count } = await client.txsQuery(`${recipientQuery}&tx.maxheight=${posted.height - 1}`);
|
||||
expect(count).toEqual("0");
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe("encodeTx", () => {
|
||||
it("works for cosmoshub example", async () => {
|
||||
pendingWithoutWasmd();
|
||||
const client = new RestClient(wasmd.endpoint);
|
||||
const response = await client.encodeTx(cosmoshub.tx);
|
||||
expect(response).toEqual(
|
||||
jasmine.objectContaining({
|
||||
tx: cosmoshub.tx_data,
|
||||
}),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("postTx", () => {
|
||||
it("can send tokens", async () => {
|
||||
pendingWithoutWasmd();
|
||||
const pen = await Secp256k1Pen.fromMnemonic(faucet.mnemonic);
|
||||
|
||||
const memo = "My first contract on chain";
|
||||
const theMsg: MsgSend = {
|
||||
type: "cosmos-sdk/MsgSend",
|
||||
value: {
|
||||
from_address: faucet.address,
|
||||
to_address: defaultRecipientAddress,
|
||||
amount: [
|
||||
{
|
||||
denom: "ucosm",
|
||||
amount: "1234567",
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
const fee: StdFee = {
|
||||
amount: [
|
||||
{
|
||||
amount: "5000",
|
||||
denom: "ucosm",
|
||||
},
|
||||
],
|
||||
gas: "890000",
|
||||
};
|
||||
|
||||
const client = new RestClient(wasmd.endpoint);
|
||||
const { account_number, sequence } = (await client.authAccounts(faucet.address)).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);
|
||||
const result = await client.postTx(signedTx);
|
||||
expect(result.code).toBeUndefined();
|
||||
expect(result).toEqual({
|
||||
height: jasmine.stringMatching(nonNegativeIntegerMatcher),
|
||||
txhash: jasmine.stringMatching(tendermintIdMatcher),
|
||||
// code is not set
|
||||
raw_log: jasmine.stringMatching(/^\[.+\]$/i),
|
||||
logs: jasmine.any(Array),
|
||||
gas_wanted: jasmine.stringMatching(nonNegativeIntegerMatcher),
|
||||
gas_used: jasmine.stringMatching(nonNegativeIntegerMatcher),
|
||||
});
|
||||
});
|
||||
|
||||
it("can't send transaction with additional signatures", async () => {
|
||||
pendingWithoutWasmd();
|
||||
const account1 = await Secp256k1Pen.fromMnemonic(faucet.mnemonic, makeCosmoshubPath(0));
|
||||
const account2 = await Secp256k1Pen.fromMnemonic(faucet.mnemonic, makeCosmoshubPath(1));
|
||||
const account3 = await Secp256k1Pen.fromMnemonic(faucet.mnemonic, makeCosmoshubPath(2));
|
||||
const address1 = rawSecp256k1PubkeyToAddress(account1.pubkey, "cosmos");
|
||||
const address2 = rawSecp256k1PubkeyToAddress(account2.pubkey, "cosmos");
|
||||
const address3 = rawSecp256k1PubkeyToAddress(account3.pubkey, "cosmos");
|
||||
|
||||
const memo = "My first contract on chain";
|
||||
const theMsg: MsgSend = {
|
||||
type: "cosmos-sdk/MsgSend",
|
||||
value: {
|
||||
from_address: address1,
|
||||
to_address: defaultRecipientAddress,
|
||||
amount: [
|
||||
{
|
||||
denom: "ucosm",
|
||||
amount: "1234567",
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
const fee: StdFee = {
|
||||
amount: [
|
||||
{
|
||||
amount: "5000",
|
||||
denom: "ucosm",
|
||||
},
|
||||
],
|
||||
gas: "890000",
|
||||
};
|
||||
|
||||
const client = new RestClient(wasmd.endpoint);
|
||||
const { account_number: an1, sequence: sequence1 } = (await client.authAccounts(address1)).result.value;
|
||||
const { account_number: an2, sequence: sequence2 } = (await client.authAccounts(address2)).result.value;
|
||||
const { account_number: an3, sequence: sequence3 } = (await client.authAccounts(address3)).result.value;
|
||||
|
||||
const signBytes1 = makeSignBytes([theMsg], fee, wasmd.chainId, memo, an1, sequence1);
|
||||
const signBytes2 = makeSignBytes([theMsg], fee, wasmd.chainId, memo, an2, sequence2);
|
||||
const signBytes3 = makeSignBytes([theMsg], fee, wasmd.chainId, memo, an3, sequence3);
|
||||
const signature1 = await account1.sign(signBytes1);
|
||||
const signature2 = await account2.sign(signBytes2);
|
||||
const signature3 = await account3.sign(signBytes3);
|
||||
const signedTx = {
|
||||
msg: [theMsg],
|
||||
fee: fee,
|
||||
memo: memo,
|
||||
signatures: [signature1, signature2, signature3],
|
||||
};
|
||||
const postResult = await client.postTx(signedTx);
|
||||
expect(postResult.code).toEqual(4);
|
||||
expect(postResult.raw_log).toContain("wrong number of signers");
|
||||
});
|
||||
|
||||
it("can send multiple messages with one signature", async () => {
|
||||
pendingWithoutWasmd();
|
||||
const account1 = await Secp256k1Pen.fromMnemonic(faucet.mnemonic, makeCosmoshubPath(0));
|
||||
const address1 = rawSecp256k1PubkeyToAddress(account1.pubkey, "cosmos");
|
||||
|
||||
const memo = "My first contract on chain";
|
||||
const msg1: MsgSend = {
|
||||
type: "cosmos-sdk/MsgSend",
|
||||
value: {
|
||||
from_address: address1,
|
||||
to_address: defaultRecipientAddress,
|
||||
amount: [
|
||||
{
|
||||
denom: "ucosm",
|
||||
amount: "1234567",
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
const msg2: MsgSend = {
|
||||
type: "cosmos-sdk/MsgSend",
|
||||
value: {
|
||||
from_address: address1,
|
||||
to_address: defaultRecipientAddress,
|
||||
amount: [
|
||||
{
|
||||
denom: "ucosm",
|
||||
amount: "7654321",
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
const fee: StdFee = {
|
||||
amount: [
|
||||
{
|
||||
amount: "5000",
|
||||
denom: "ucosm",
|
||||
},
|
||||
],
|
||||
gas: "890000",
|
||||
};
|
||||
|
||||
const client = new RestClient(wasmd.endpoint);
|
||||
const { account_number, sequence } = (await client.authAccounts(address1)).result.value;
|
||||
|
||||
const signBytes = makeSignBytes([msg1, msg2], fee, wasmd.chainId, memo, account_number, sequence);
|
||||
const signature1 = await account1.sign(signBytes);
|
||||
const signedTx = {
|
||||
msg: [msg1, msg2],
|
||||
fee: fee,
|
||||
memo: memo,
|
||||
signatures: [signature1],
|
||||
};
|
||||
const postResult = await client.postTx(signedTx);
|
||||
expect(postResult.code).toBeUndefined();
|
||||
});
|
||||
|
||||
it("can send multiple messages with multiple signatures", async () => {
|
||||
pendingWithoutWasmd();
|
||||
const account1 = await Secp256k1Pen.fromMnemonic(faucet.mnemonic, makeCosmoshubPath(0));
|
||||
const account2 = await Secp256k1Pen.fromMnemonic(faucet.mnemonic, makeCosmoshubPath(1));
|
||||
const address1 = rawSecp256k1PubkeyToAddress(account1.pubkey, "cosmos");
|
||||
const address2 = rawSecp256k1PubkeyToAddress(account2.pubkey, "cosmos");
|
||||
|
||||
const memo = "My first contract on chain";
|
||||
const msg1: MsgSend = {
|
||||
type: "cosmos-sdk/MsgSend",
|
||||
value: {
|
||||
from_address: address1,
|
||||
to_address: defaultRecipientAddress,
|
||||
amount: [
|
||||
{
|
||||
denom: "ucosm",
|
||||
amount: "1234567",
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
const msg2: MsgSend = {
|
||||
type: "cosmos-sdk/MsgSend",
|
||||
value: {
|
||||
from_address: address2,
|
||||
to_address: defaultRecipientAddress,
|
||||
amount: [
|
||||
{
|
||||
denom: "ucosm",
|
||||
amount: "7654321",
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
const fee: StdFee = {
|
||||
amount: [
|
||||
{
|
||||
amount: "5000",
|
||||
denom: "ucosm",
|
||||
},
|
||||
],
|
||||
gas: "890000",
|
||||
};
|
||||
|
||||
const client = new RestClient(wasmd.endpoint);
|
||||
const { account_number: an1, sequence: sequence1 } = (await client.authAccounts(address1)).result.value;
|
||||
const { account_number: an2, sequence: sequence2 } = (await client.authAccounts(address2)).result.value;
|
||||
|
||||
const signBytes1 = makeSignBytes([msg2, msg1], fee, wasmd.chainId, memo, an1, sequence1);
|
||||
const signBytes2 = makeSignBytes([msg2, msg1], fee, wasmd.chainId, memo, an2, sequence2);
|
||||
const signature1 = await account1.sign(signBytes1);
|
||||
const signature2 = await account2.sign(signBytes2);
|
||||
const signedTx = {
|
||||
msg: [msg2, msg1],
|
||||
fee: fee,
|
||||
memo: memo,
|
||||
signatures: [signature2, signature1],
|
||||
};
|
||||
const postResult = await client.postTx(signedTx);
|
||||
expect(postResult.code).toBeUndefined();
|
||||
|
||||
await sleep(500);
|
||||
const searched = await client.txsQuery(`tx.hash=${postResult.txhash}`);
|
||||
expect(searched.txs.length).toEqual(1);
|
||||
expect(searched.txs[0].tx.value.signatures).toEqual([signature2, signature1]);
|
||||
});
|
||||
|
||||
it("can't send transaction with wrong signature order (1)", async () => {
|
||||
pendingWithoutWasmd();
|
||||
const account1 = await Secp256k1Pen.fromMnemonic(faucet.mnemonic, makeCosmoshubPath(0));
|
||||
const account2 = await Secp256k1Pen.fromMnemonic(faucet.mnemonic, makeCosmoshubPath(1));
|
||||
const address1 = rawSecp256k1PubkeyToAddress(account1.pubkey, "cosmos");
|
||||
const address2 = rawSecp256k1PubkeyToAddress(account2.pubkey, "cosmos");
|
||||
|
||||
const memo = "My first contract on chain";
|
||||
const msg1: MsgSend = {
|
||||
type: "cosmos-sdk/MsgSend",
|
||||
value: {
|
||||
from_address: address1,
|
||||
to_address: defaultRecipientAddress,
|
||||
amount: [
|
||||
{
|
||||
denom: "ucosm",
|
||||
amount: "1234567",
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
const msg2: MsgSend = {
|
||||
type: "cosmos-sdk/MsgSend",
|
||||
value: {
|
||||
from_address: address2,
|
||||
to_address: defaultRecipientAddress,
|
||||
amount: [
|
||||
{
|
||||
denom: "ucosm",
|
||||
amount: "7654321",
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
const fee: StdFee = {
|
||||
amount: [
|
||||
{
|
||||
amount: "5000",
|
||||
denom: "ucosm",
|
||||
},
|
||||
],
|
||||
gas: "890000",
|
||||
};
|
||||
|
||||
const client = new RestClient(wasmd.endpoint);
|
||||
const { account_number: an1, sequence: sequence1 } = (await client.authAccounts(address1)).result.value;
|
||||
const { account_number: an2, sequence: sequence2 } = (await client.authAccounts(address2)).result.value;
|
||||
|
||||
const signBytes1 = makeSignBytes([msg1, msg2], fee, wasmd.chainId, memo, an1, sequence1);
|
||||
const signBytes2 = makeSignBytes([msg1, msg2], fee, wasmd.chainId, memo, an2, sequence2);
|
||||
const signature1 = await account1.sign(signBytes1);
|
||||
const signature2 = await account2.sign(signBytes2);
|
||||
const signedTx = {
|
||||
msg: [msg1, msg2],
|
||||
fee: fee,
|
||||
memo: memo,
|
||||
signatures: [signature2, signature1],
|
||||
};
|
||||
const postResult = await client.postTx(signedTx);
|
||||
expect(postResult.code).toEqual(8);
|
||||
});
|
||||
|
||||
it("can't send transaction with wrong signature order (2)", async () => {
|
||||
pendingWithoutWasmd();
|
||||
const account1 = await Secp256k1Pen.fromMnemonic(faucet.mnemonic, makeCosmoshubPath(0));
|
||||
const account2 = await Secp256k1Pen.fromMnemonic(faucet.mnemonic, makeCosmoshubPath(1));
|
||||
const address1 = rawSecp256k1PubkeyToAddress(account1.pubkey, "cosmos");
|
||||
const address2 = rawSecp256k1PubkeyToAddress(account2.pubkey, "cosmos");
|
||||
|
||||
const memo = "My first contract on chain";
|
||||
const msg1: MsgSend = {
|
||||
type: "cosmos-sdk/MsgSend",
|
||||
value: {
|
||||
from_address: address1,
|
||||
to_address: defaultRecipientAddress,
|
||||
amount: [
|
||||
{
|
||||
denom: "ucosm",
|
||||
amount: "1234567",
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
const msg2: MsgSend = {
|
||||
type: "cosmos-sdk/MsgSend",
|
||||
value: {
|
||||
from_address: address2,
|
||||
to_address: defaultRecipientAddress,
|
||||
amount: [
|
||||
{
|
||||
denom: "ucosm",
|
||||
amount: "7654321",
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
const fee: StdFee = {
|
||||
amount: [
|
||||
{
|
||||
amount: "5000",
|
||||
denom: "ucosm",
|
||||
},
|
||||
],
|
||||
gas: "890000",
|
||||
};
|
||||
|
||||
const client = new RestClient(wasmd.endpoint);
|
||||
const { account_number: an1, sequence: sequence1 } = (await client.authAccounts(address1)).result.value;
|
||||
const { account_number: an2, sequence: sequence2 } = (await client.authAccounts(address2)).result.value;
|
||||
|
||||
const signBytes1 = makeSignBytes([msg2, msg1], fee, wasmd.chainId, memo, an1, sequence1);
|
||||
const signBytes2 = makeSignBytes([msg2, msg1], fee, wasmd.chainId, memo, an2, sequence2);
|
||||
const signature1 = await account1.sign(signBytes1);
|
||||
const signature2 = await account2.sign(signBytes2);
|
||||
const signedTx = {
|
||||
msg: [msg2, msg1],
|
||||
fee: fee,
|
||||
memo: memo,
|
||||
signatures: [signature1, signature2],
|
||||
};
|
||||
const postResult = await client.postTx(signedTx);
|
||||
expect(postResult.code).toEqual(8);
|
||||
});
|
||||
});
|
||||
});
|
||||
@ -1,168 +0,0 @@
|
||||
import { isNonNullObject } from "@cosmjs/utils";
|
||||
import axios, { AxiosError, AxiosInstance } from "axios";
|
||||
|
||||
import {
|
||||
AuthAccountsResponse,
|
||||
BlockResponse,
|
||||
BroadcastMode,
|
||||
EncodeTxResponse,
|
||||
NodeInfoResponse,
|
||||
PostTxsResponse,
|
||||
SearchTxsResponse,
|
||||
TxsResponse,
|
||||
} from "./lcdapi";
|
||||
import { CosmosSdkTx, StdTx } from "./types";
|
||||
|
||||
// We want to get message data from 500 errors
|
||||
// https://stackoverflow.com/questions/56577124/how-to-handle-500-error-message-with-axios
|
||||
// this should be chained to catch one error and throw a more informative one
|
||||
function parseAxiosError(err: AxiosError): never {
|
||||
// use the error message sent from server, not default 500 msg
|
||||
if (err.response?.data) {
|
||||
let errorText: string;
|
||||
const data = err.response.data;
|
||||
// expect { error: string }, but otherwise dump
|
||||
if (data.error && typeof data.error === "string") {
|
||||
errorText = data.error;
|
||||
} else if (typeof data === "string") {
|
||||
errorText = data;
|
||||
} else {
|
||||
errorText = JSON.stringify(data);
|
||||
}
|
||||
throw new Error(`${errorText} (HTTP ${err.response.status})`);
|
||||
} else {
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated use LcdClient.
|
||||
*/
|
||||
export class RestClient {
|
||||
private readonly client: AxiosInstance;
|
||||
private readonly broadcastMode: BroadcastMode;
|
||||
|
||||
/**
|
||||
* Creates a new client to interact with a Cosmos SDK light client daemon.
|
||||
* This class tries to be a direct mapping onto the API. Some basic decoding and normalizatin is done
|
||||
* but things like caching are done at a higher level.
|
||||
*
|
||||
* When building apps, you should not need to use this class directly. If you do, this indicates a missing feature
|
||||
* in higher level components. Feel free to raise an issue in this case.
|
||||
*
|
||||
* @param apiUrl The URL of a Cosmos SDK light client daemon API (sometimes called REST server or REST API)
|
||||
* @param broadcastMode Defines at which point of the transaction processing the postTx method (i.e. transaction broadcasting) returns
|
||||
*/
|
||||
public constructor(apiUrl: string, broadcastMode = BroadcastMode.Block) {
|
||||
const headers = {
|
||||
post: { "Content-Type": "application/json" },
|
||||
};
|
||||
this.client = axios.create({
|
||||
baseURL: apiUrl,
|
||||
headers: headers,
|
||||
});
|
||||
this.broadcastMode = broadcastMode;
|
||||
}
|
||||
|
||||
public async get(path: string): Promise<any> {
|
||||
const { data } = await this.client.get(path).catch(parseAxiosError);
|
||||
if (data === null) {
|
||||
throw new Error("Received null response from server");
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
public async post(path: string, params: any): Promise<any> {
|
||||
if (!isNonNullObject(params)) throw new Error("Got unexpected type of params. Expected object.");
|
||||
const { data } = await this.client.post(path, params).catch(parseAxiosError);
|
||||
if (data === null) {
|
||||
throw new Error("Received null response from server");
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
// The /auth endpoints
|
||||
|
||||
public async authAccounts(address: string): Promise<AuthAccountsResponse> {
|
||||
const path = `/auth/accounts/${address}`;
|
||||
const responseData = await this.get(path);
|
||||
if (responseData.result.type !== "cosmos-sdk/Account") {
|
||||
throw new Error("Unexpected response data format");
|
||||
}
|
||||
return responseData as AuthAccountsResponse;
|
||||
}
|
||||
|
||||
// The /blocks endpoints
|
||||
|
||||
public async blocksLatest(): Promise<BlockResponse> {
|
||||
const responseData = await this.get("/blocks/latest");
|
||||
if (!responseData.block) {
|
||||
throw new Error("Unexpected response data format");
|
||||
}
|
||||
return responseData as BlockResponse;
|
||||
}
|
||||
|
||||
public async blocks(height: number): Promise<BlockResponse> {
|
||||
const responseData = await this.get(`/blocks/${height}`);
|
||||
if (!responseData.block) {
|
||||
throw new Error("Unexpected response data format");
|
||||
}
|
||||
return responseData as BlockResponse;
|
||||
}
|
||||
|
||||
// The /node_info endpoint
|
||||
|
||||
public async nodeInfo(): Promise<NodeInfoResponse> {
|
||||
const responseData = await this.get("/node_info");
|
||||
if (!responseData.node_info) {
|
||||
throw new Error("Unexpected response data format");
|
||||
}
|
||||
return responseData as NodeInfoResponse;
|
||||
}
|
||||
|
||||
// The /txs endpoints
|
||||
|
||||
public async txById(id: string): Promise<TxsResponse> {
|
||||
const responseData = await this.get(`/txs/${id}`);
|
||||
if (!responseData.tx) {
|
||||
throw new Error("Unexpected response data format");
|
||||
}
|
||||
return responseData as TxsResponse;
|
||||
}
|
||||
|
||||
public async txsQuery(query: string): Promise<SearchTxsResponse> {
|
||||
const responseData = await this.get(`/txs?${query}`);
|
||||
if (!responseData.txs) {
|
||||
throw new Error("Unexpected response data format");
|
||||
}
|
||||
return responseData as SearchTxsResponse;
|
||||
}
|
||||
|
||||
/** returns the amino-encoding of the transaction performed by the server */
|
||||
public async encodeTx(tx: CosmosSdkTx): Promise<EncodeTxResponse> {
|
||||
const responseData = await this.post("/txs/encode", tx);
|
||||
if (!responseData.tx) {
|
||||
throw new Error("Unexpected response data format");
|
||||
}
|
||||
return responseData as EncodeTxResponse;
|
||||
}
|
||||
|
||||
/**
|
||||
* Broadcasts a signed transaction to into the transaction pool.
|
||||
* Depending on the RestClient's broadcast mode, this might or might
|
||||
* wait for checkTx or deliverTx to be executed before returning.
|
||||
*
|
||||
* @param tx a signed transaction as StdTx (i.e. not wrapped in type/value container)
|
||||
*/
|
||||
public async postTx(tx: StdTx): Promise<PostTxsResponse> {
|
||||
const params = {
|
||||
tx: tx,
|
||||
mode: this.broadcastMode,
|
||||
};
|
||||
const responseData = await this.post("/txs", params);
|
||||
if (!responseData.txhash) {
|
||||
throw new Error("Unexpected response data format");
|
||||
}
|
||||
return responseData as PostTxsResponse;
|
||||
}
|
||||
}
|
||||
1
packages/sdk38/types/index.d.ts
vendored
1
packages/sdk38/types/index.d.ts
vendored
@ -35,7 +35,6 @@ export {
|
||||
SupplyExtension,
|
||||
TxsResponse,
|
||||
} from "./lcdapi";
|
||||
export { RestClient } from "./restclient";
|
||||
export { isMsgDelegate, isMsgSend, Msg, MsgDelegate, MsgSend } from "./msgs";
|
||||
export { Pen, Secp256k1Pen, makeCosmoshubPath } from "./pen";
|
||||
export { decodeBech32Pubkey, encodeBech32Pubkey, encodeSecp256k1Pubkey } from "./pubkey";
|
||||
|
||||
48
packages/sdk38/types/restclient.d.ts
vendored
48
packages/sdk38/types/restclient.d.ts
vendored
@ -1,48 +0,0 @@
|
||||
import {
|
||||
AuthAccountsResponse,
|
||||
BlockResponse,
|
||||
BroadcastMode,
|
||||
EncodeTxResponse,
|
||||
NodeInfoResponse,
|
||||
PostTxsResponse,
|
||||
SearchTxsResponse,
|
||||
TxsResponse,
|
||||
} from "./lcdapi";
|
||||
import { CosmosSdkTx, StdTx } from "./types";
|
||||
/**
|
||||
* @deprecated use LcdClient.
|
||||
*/
|
||||
export declare class RestClient {
|
||||
private readonly client;
|
||||
private readonly broadcastMode;
|
||||
/**
|
||||
* Creates a new client to interact with a Cosmos SDK light client daemon.
|
||||
* This class tries to be a direct mapping onto the API. Some basic decoding and normalizatin is done
|
||||
* but things like caching are done at a higher level.
|
||||
*
|
||||
* When building apps, you should not need to use this class directly. If you do, this indicates a missing feature
|
||||
* in higher level components. Feel free to raise an issue in this case.
|
||||
*
|
||||
* @param apiUrl The URL of a Cosmos SDK light client daemon API (sometimes called REST server or REST API)
|
||||
* @param broadcastMode Defines at which point of the transaction processing the postTx method (i.e. transaction broadcasting) returns
|
||||
*/
|
||||
constructor(apiUrl: string, broadcastMode?: BroadcastMode);
|
||||
get(path: string): Promise<any>;
|
||||
post(path: string, params: any): Promise<any>;
|
||||
authAccounts(address: string): Promise<AuthAccountsResponse>;
|
||||
blocksLatest(): Promise<BlockResponse>;
|
||||
blocks(height: number): Promise<BlockResponse>;
|
||||
nodeInfo(): Promise<NodeInfoResponse>;
|
||||
txById(id: string): Promise<TxsResponse>;
|
||||
txsQuery(query: string): Promise<SearchTxsResponse>;
|
||||
/** returns the amino-encoding of the transaction performed by the server */
|
||||
encodeTx(tx: CosmosSdkTx): Promise<EncodeTxResponse>;
|
||||
/**
|
||||
* Broadcasts a signed transaction to into the transaction pool.
|
||||
* Depending on the RestClient's broadcast mode, this might or might
|
||||
* wait for checkTx or deliverTx to be executed before returning.
|
||||
*
|
||||
* @param tx a signed transaction as StdTx (i.e. not wrapped in type/value container)
|
||||
*/
|
||||
postTx(tx: StdTx): Promise<PostTxsResponse>;
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user