Merge pull request #39 from confio/empower-sdk

Upload wasm code using plain @cosmwasm/sdk
This commit is contained in:
merge-when-green[bot] 2020-02-04 15:47:04 +00:00 committed by GitHub
commit f7d63b7037
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
22 changed files with 617 additions and 84 deletions

View File

@ -5,7 +5,7 @@ workflows:
jobs:
- build
- lint
- faucet_docker
# - faucet_docker
- test
jobs:

View File

@ -1,5 +1,5 @@
/* eslint-disable @typescript-eslint/camelcase */
import { marshalTx, unmarshalTx } from "@cosmwasm/sdk";
import { makeSignBytes, marshalTx, types, unmarshalTx } from "@cosmwasm/sdk";
import {
Address,
ChainId,
@ -14,7 +14,6 @@ import {
TxCodec,
UnsignedTransaction,
} from "@iov/bcp";
import { Encoding } from "@iov/encoding";
import { CosmosBech32Prefix, isValidAddress, pubkeyToAddress } from "./address";
import { Caip5 } from "./caip5";
@ -22,26 +21,6 @@ import { parseTx } from "./decode";
import { buildSignedTx, buildUnsignedTx } from "./encode";
import { nonceToAccountNumber, nonceToSequence, TokenInfos } from "./types";
const { toUtf8 } = Encoding;
function sortJson(json: any): any {
if (typeof json !== "object" || json === null) {
return json;
}
if (Array.isArray(json)) {
return json.map(sortJson);
}
const sortedKeys = Object.keys(json).sort();
const result = sortedKeys.reduce(
(accumulator, key) => ({
...accumulator,
[key]: sortJson(json[key]),
}),
{},
);
return result;
}
export class CosmWasmCodec implements TxCodec {
private readonly prefix: CosmosBech32Prefix;
private readonly tokens: TokenInfos;
@ -52,18 +31,19 @@ export class CosmWasmCodec implements TxCodec {
}
public bytesToSign(unsigned: UnsignedTransaction, nonce: Nonce): SigningJob {
const memo = (unsigned as any).memo;
const built = buildUnsignedTx(unsigned, this.tokens);
const signMsg = sortJson({
account_number: nonceToAccountNumber(nonce).toString(),
chain_id: Caip5.decode(unsigned.chainId),
fee: (built.value as any).fee,
memo: memo,
msgs: (built.value as any).msg,
sequence: nonceToSequence(nonce).toString(),
});
const signBytes = toUtf8(JSON.stringify(signMsg));
const nonceInfo: types.NonceInfo = {
account_number: nonceToAccountNumber(nonce),
sequence: nonceToSequence(nonce),
};
const signBytes = makeSignBytes(
built.value.msg,
built.value.fee,
Caip5.decode(unsigned.chainId),
built.value.memo || "",
nonceInfo,
);
return {
bytes: signBytes as SignableBytes,

View File

@ -1,5 +1,5 @@
/* eslint-disable @typescript-eslint/camelcase */
import { RestClient, TxsResponse, unmarshalTx } from "@cosmwasm/sdk";
import { RestClient, TxsResponse, types, unmarshalTx } from "@cosmwasm/sdk";
import {
Account,
AccountQuery,
@ -305,8 +305,19 @@ export class CosmWasmConnection implements BlockchainConnection {
response: TxsResponse,
chainId: ChainId,
): Promise<ConfirmedAndSignedTransaction<UnsignedTransaction> | FailedTransaction> {
const sender = (response.tx.value as any).msg[0].value.from_address;
const accountForHeight = await this.restClient.authAccounts(sender, response.height);
const firstMsg = response.tx.value.msg.find(() => true);
if (!firstMsg) throw new Error("Got transaction without a first message. What is going on here?");
let senderAddress: string;
if (types.isMsgSend(firstMsg)) {
senderAddress = firstMsg.value.from_address;
} else if (types.isMsgStoreCode(firstMsg)) {
senderAddress = firstMsg.value.sender;
} else {
throw new Error(`Got unsupported type of message: ${firstMsg.type}`);
}
const accountForHeight = await this.restClient.authAccounts(senderAddress, response.height);
// this is technically not the proper nonce. maybe this causes issues for sig validation?
// leaving for now unless it causes issues
const sequence = (accountForHeight.result.value.sequence - 1) as Nonce;

View File

@ -72,24 +72,27 @@ export function decodeAmount(tokens: TokenInfos, coin: types.Coin): Amount {
};
}
export function parseMsg(msg: types.Msg, chainId: ChainId, tokens: TokenInfos): SendTransaction {
if (msg.type !== "cosmos-sdk/MsgSend") {
throw new Error("Unknown message type in transaction");
export function parseMsg(msg: types.Msg, chainId: ChainId, tokens: TokenInfos): UnsignedTransaction {
if (types.isMsgSend(msg)) {
if (msg.value.amount.length !== 1) {
throw new Error("Only MsgSend with one amount is supported");
}
const send: SendTransaction = {
kind: "bcp/send",
chainId: chainId,
sender: msg.value.from_address as Address,
recipient: msg.value.to_address as Address,
amount: decodeAmount(tokens, msg.value.amount[0]),
};
return send;
} else {
// Unknown transaction type
const unknown = {
chainId: chainId,
kind: "bcp/unknown",
};
return unknown;
}
if (!(msg.value as types.MsgSend).from_address) {
throw new Error("Only MsgSend is supported");
}
const msgValue = msg.value as types.MsgSend;
if (msgValue.amount.length !== 1) {
throw new Error("Only MsgSend with one amount is supported");
}
return {
kind: "bcp/send",
chainId: chainId,
sender: msgValue.from_address as Address,
recipient: msgValue.to_address as Address,
amount: decodeAmount(tokens, msgValue.amount[0]),
};
}
export function parseFee(fee: types.StdFee, tokens: TokenInfos): Fee {

View File

@ -1,5 +1,5 @@
/* eslint-disable @typescript-eslint/camelcase */
import { types } from "@cosmwasm/sdk";
import { encodeSecp256k1Signature, types } from "@cosmwasm/sdk";
import {
Algorithm,
Amount,
@ -10,7 +10,6 @@ import {
SignedTransaction,
UnsignedTransaction,
} from "@iov/bcp";
import { Secp256k1 } from "@iov/crypto";
import { Decimal, Encoding } from "@iov/encoding";
import { TokenInfos } from "./types";
@ -72,14 +71,12 @@ export function encodeFee(fee: Fee, tokens: TokenInfos): types.StdFee {
}
export function encodeFullSignature(fullSignature: FullSignature): types.StdSignature {
return {
pub_key: {
type: "tendermint/PubKeySecp256k1",
value: toBase64(Secp256k1.compressPubkey(fullSignature.pubkey.data)),
},
// Recovery seems to be unused
signature: toBase64(Secp256k1.trimRecoveryByte(fullSignature.signature)),
};
switch (fullSignature.pubkey.algo) {
case Algorithm.Secp256k1:
return encodeSecp256k1Signature(fullSignature.pubkey.data, fullSignature.signature);
default:
throw new Error("Unsupported signing algorithm");
}
}
export function buildUnsignedTx(tx: UnsignedTransaction, tokens: TokenInfos): types.AminoTx {

View File

@ -23,13 +23,9 @@ const maxAcct = 1 << 23;
// tslint:disable-next-line:no-bitwise
const maxSeq = 1 << 20;
// NonceInfo is the data we need from account to create a nonce
// Use this so no confusion about order of arguments
export type NonceInfo = Pick<types.BaseAccount, "account_number" | "sequence">;
// this (lossily) encodes the two pieces of info (uint64) needed to sign into
// one (53-bit) number. Cross your fingers.
export function accountToNonce({ account_number: account, sequence }: NonceInfo): Nonce {
export function accountToNonce({ account_number: account, sequence }: types.NonceInfo): Nonce {
// we allow 23 bits (8 million) for accounts, and 20 bits (1 million) for tx/account
// let's fix this soon
if (account > maxAcct) {

View File

@ -7,7 +7,6 @@ import {
FullSignature,
Nonce,
PubkeyBundle,
SendTransaction,
SignatureBytes,
SignedTransaction,
UnsignedTransaction,
@ -19,7 +18,7 @@ export declare function decodeSignature(signature: string): SignatureBytes;
export declare function decodeFullSignature(signature: types.StdSignature, nonce: number): FullSignature;
export declare function coinToDecimal(tokens: TokenInfos, coin: types.Coin): readonly [Decimal, string];
export declare function decodeAmount(tokens: TokenInfos, coin: types.Coin): Amount;
export declare function parseMsg(msg: types.Msg, chainId: ChainId, tokens: TokenInfos): SendTransaction;
export declare function parseMsg(msg: types.Msg, chainId: ChainId, tokens: TokenInfos): UnsignedTransaction;
export declare function parseFee(fee: types.StdFee, tokens: TokenInfos): Fee;
export declare function parseTx(
txValue: types.StdTx,

View File

@ -15,7 +15,6 @@ export interface TokenInfo {
readonly fractionalDigits: number;
}
export declare type TokenInfos = ReadonlyArray<TokenInfo>;
export declare type NonceInfo = Pick<types.BaseAccount, "account_number" | "sequence">;
export declare function accountToNonce({ account_number: account, sequence }: NonceInfo): Nonce;
export declare function accountToNonce({ account_number: account, sequence }: types.NonceInfo): Nonce;
export declare function nonceToAccountNumber(nonce: Nonce): number;
export declare function nonceToSequence(nonce: Nonce): number;

View File

@ -38,9 +38,12 @@
"pack-web": "yarn build-or-skip && webpack --mode development --config webpack.web.config.js"
},
"dependencies": {
"@iov/crypto": "^2.0.0-alpha.7",
"@iov/encoding": "^2.0.0-alpha.7",
"axios": "^0.19.0"
},
"devDependencies": {
"@iov/bcp": "^2.0.0-alpha.7",
"@iov/keycontrol": "^2.0.0-alpha.7"
}
}

View File

@ -0,0 +1,59 @@
import { Encoding } from "@iov/encoding";
import { encodeSecp256k1Signature } from "./encoding";
const { fromBase64 } = Encoding;
describe("encoding", () => {
describe("encodeSecp256k1Signature", () => {
it("encodes a full signature", () => {
const pubkey = fromBase64("AtQaCqFnshaZQp6rIkvAPyzThvCvXSDO+9AzbxVErqJP");
const signature = fromBase64(
"1nUcIH0CLT0/nQ0mBTDrT6kMG20NY/PsH7P2gc4bpYNGLEYjBmdWevXUJouSE/9A/60QG9cYeqyTe5kFDeIPxQ==",
);
expect(encodeSecp256k1Signature(pubkey, signature)).toEqual({
// eslint-disable-next-line @typescript-eslint/camelcase
pub_key: {
type: "tendermint/PubKeySecp256k1",
value: "AtQaCqFnshaZQp6rIkvAPyzThvCvXSDO+9AzbxVErqJP",
},
signature: "1nUcIH0CLT0/nQ0mBTDrT6kMG20NY/PsH7P2gc4bpYNGLEYjBmdWevXUJouSE/9A/60QG9cYeqyTe5kFDeIPxQ==",
});
});
it("compresses uncompressed public keys", () => {
const pubkey = fromBase64(
"BE8EGB7ro1ORuFhjOnZcSgwYlpe0DSFjVNUIkNNQxwKQE7WHpoHoNswYeoFkuYpYSKK4mzFzMV/dB0DVAy4lnNU=",
);
const signature = fromBase64(
"1nUcIH0CLT0/nQ0mBTDrT6kMG20NY/PsH7P2gc4bpYNGLEYjBmdWevXUJouSE/9A/60QG9cYeqyTe5kFDeIPxQ==",
);
expect(encodeSecp256k1Signature(pubkey, signature)).toEqual({
// eslint-disable-next-line @typescript-eslint/camelcase
pub_key: {
type: "tendermint/PubKeySecp256k1",
value: "A08EGB7ro1ORuFhjOnZcSgwYlpe0DSFjVNUIkNNQxwKQ",
},
signature: "1nUcIH0CLT0/nQ0mBTDrT6kMG20NY/PsH7P2gc4bpYNGLEYjBmdWevXUJouSE/9A/60QG9cYeqyTe5kFDeIPxQ==",
});
});
it("removes recovery values from signature data", () => {
const pubkey = fromBase64("AtQaCqFnshaZQp6rIkvAPyzThvCvXSDO+9AzbxVErqJP");
const signature = Uint8Array.from([
...fromBase64(
"1nUcIH0CLT0/nQ0mBTDrT6kMG20NY/PsH7P2gc4bpYNGLEYjBmdWevXUJouSE/9A/60QG9cYeqyTe5kFDeIPxQ==",
),
99,
]);
expect(encodeSecp256k1Signature(pubkey, signature)).toEqual({
// eslint-disable-next-line @typescript-eslint/camelcase
pub_key: {
type: "tendermint/PubKeySecp256k1",
value: "AtQaCqFnshaZQp6rIkvAPyzThvCvXSDO+9AzbxVErqJP",
},
signature: "1nUcIH0CLT0/nQ0mBTDrT6kMG20NY/PsH7P2gc4bpYNGLEYjBmdWevXUJouSE/9A/60QG9cYeqyTe5kFDeIPxQ==",
});
});
});
});

View File

@ -1,8 +1,71 @@
import { Secp256k1 } from "@iov/crypto";
import { Encoding } from "@iov/encoding";
import { StdTx } from "./types";
import { Msg, NonceInfo, StdFee, StdSignature, StdTx } from "./types";
const { toBase64, toUtf8 } = Encoding;
function sortJson(json: any): any {
if (typeof json !== "object" || json === null) {
return json;
}
if (Array.isArray(json)) {
return json.map(sortJson);
}
const sortedKeys = Object.keys(json).sort();
const result = sortedKeys.reduce(
(accumulator, key) => ({
...accumulator,
[key]: sortJson(json[key]),
}),
{},
);
return result;
}
export function marshalTx(tx: StdTx): Uint8Array {
const json = JSON.stringify(tx);
return Encoding.toUtf8(json);
}
interface SignJson {
readonly account_number: string;
readonly chain_id: string;
readonly fee: StdFee;
readonly memo: string;
readonly msgs: readonly Msg[];
readonly sequence: string;
}
export function makeSignBytes(
msgs: readonly Msg[],
fee: StdFee,
chainId: string,
memo: string,
account: NonceInfo,
): Uint8Array {
const signJson: SignJson = {
// eslint-disable-next-line @typescript-eslint/camelcase
account_number: account.account_number.toString(),
// eslint-disable-next-line @typescript-eslint/camelcase
chain_id: chainId,
fee: fee,
memo: memo,
msgs: msgs,
sequence: account.sequence.toString(),
};
const signMsg = sortJson(signJson);
return toUtf8(JSON.stringify(signMsg));
}
export function encodeSecp256k1Signature(pubkey: Uint8Array, signature: Uint8Array): StdSignature {
return {
// eslint-disable-next-line @typescript-eslint/camelcase
pub_key: {
type: "tendermint/PubKeySecp256k1",
value: toBase64(Secp256k1.compressPubkey(pubkey)),
},
// Recovery seems to be unused
signature: toBase64(Secp256k1.trimRecoveryByte(signature)),
};
}

View File

@ -1,6 +1,6 @@
import * as types from "./types";
export { unmarshalTx } from "./decoding";
export { marshalTx } from "./encoding";
export { encodeSecp256k1Signature, makeSignBytes, marshalTx } from "./encoding";
export { RestClient, TxsResponse } from "./restclient";
export { types };

View File

@ -0,0 +1,124 @@
/* eslint-disable @typescript-eslint/camelcase */
import { parseAttribute, parseEvent, parseLog, parseLogs } from "./logs";
describe("logs", () => {
describe("parseAttribute", () => {
it("works", () => {
const attr = parseAttribute({ key: "a", value: "b" });
expect(attr).toEqual({ key: "a", value: "b" });
});
});
describe("parseEvent", () => {
it("works", () => {
const original = {
type: "message",
attributes: [
{
key: "action",
value: "store-code",
},
{
key: "module",
value: "wasm",
},
{
key: "action",
value: "store-code",
},
{
key: "sender",
value: "cosmos1pkptre7fdkl6gfrzlesjjvhxhlc3r4gmmk8rs6",
},
{
key: "code_id",
value: "1",
},
],
} as const;
const event = parseEvent(original);
expect(event).toEqual(original);
});
});
describe("parseLog", () => {
it("works", () => {
const original = {
msg_index: 0,
log: "",
events: [
{
type: "message",
attributes: [
{
key: "action",
value: "store-code",
},
{
key: "module",
value: "wasm",
},
{
key: "action",
value: "store-code",
},
{
key: "sender",
value: "cosmos1pkptre7fdkl6gfrzlesjjvhxhlc3r4gmmk8rs6",
},
{
key: "code_id",
value: "1",
},
],
},
],
} as const;
const log = parseLog(original);
expect(log).toEqual(original);
});
});
describe("parseLogs", () => {
it("works", () => {
const original = [
{
msg_index: 0,
log: "",
events: [
{
type: "message",
attributes: [
{
key: "action",
value: "store-code",
},
{
key: "module",
value: "wasm",
},
{
key: "action",
value: "store-code",
},
{
key: "sender",
value: "cosmos1pkptre7fdkl6gfrzlesjjvhxhlc3r4gmmk8rs6",
},
{
key: "code_id",
value: "1",
},
],
},
],
},
] as const;
const logs = parseLogs(original);
expect(logs).toEqual(original);
});
});
});

59
packages/sdk/src/logs.ts Normal file
View File

@ -0,0 +1,59 @@
/* eslint-disable @typescript-eslint/camelcase */
import { isNonNullObject } from "@iov/encoding";
export interface Attribute {
readonly key: string;
readonly value: string;
}
export interface Event {
readonly type: "message";
readonly attributes: readonly Attribute[];
}
export interface Log {
readonly msg_index: number;
readonly log: string;
readonly events: readonly Event[];
}
export function parseAttribute(input: unknown): Attribute {
if (!isNonNullObject(input)) throw new Error("Attribute must be a non-null object");
const { key, value } = input as any;
if (typeof key !== "string" || typeof value !== "string") {
throw new Error("Attribute is not a key/value pair");
}
return {
key: key,
value: value,
};
}
export function parseEvent(input: unknown): Event {
if (!isNonNullObject(input)) throw new Error("Event must be a non-null object");
const { type, attributes } = input as any;
if (type !== "message") throw new Error("Event must be of type message");
if (!Array.isArray(attributes)) throw new Error("Event's attributes must be an array");
return {
type: type,
attributes: attributes.map(parseAttribute),
};
}
export function parseLog(input: unknown): Log {
if (!isNonNullObject(input)) throw new Error("Log must be a non-null object");
const { msg_index, log, events } = input as any;
if (typeof msg_index !== "number") throw new Error("Log's msg_index must be a number");
if (typeof log !== "string") throw new Error("Log's log must be a string");
if (!Array.isArray(events)) throw new Error("Log's events must be an array");
return {
msg_index: msg_index,
log: log,
events: events.map(parseEvent),
};
}
export function parseLogs(input: unknown): readonly Log[] {
if (!Array.isArray(input)) throw new Error("Logs must be an array");
return input.map(parseLog);
}

View File

@ -1,15 +1,24 @@
/* eslint-disable @typescript-eslint/camelcase */
import { ChainId, PrehashType, SignableBytes } from "@iov/bcp";
import { Encoding } from "@iov/encoding";
import { HdPaths, Secp256k1HdWallet } from "@iov/keycontrol";
import { encodeSecp256k1Signature, makeSignBytes, marshalTx } from "./encoding";
import { Log, parseLogs } from "./logs";
import { RestClient } from "./restclient";
import contract from "./testdata/contract.json";
import data from "./testdata/cosmoshub.json";
import { StdTx } from "./types";
import { MsgSend, MsgStoreCode, StdFee, StdTx } from "./types";
const { fromBase64 } = Encoding;
const httpUrl = "http://localhost:1317";
const defaultNetworkId = "testing";
const faucetMnemonic =
"economy stock theory fatal elder harbor betray wasp final emotion task crumble siren bottom lizard educate guess current outdoor pair theory focus wife stone";
const faucetPath = HdPaths.cosmos(0);
const faucetAddress = "cosmos1pkptre7fdkl6gfrzlesjjvhxhlc3r4gmmk8rs6";
const emptyAddress = "cosmos1ltkhnmdcqemmd2tkhnx7qx66tq7e0wykw2j85k";
function pendingWithoutCosmos(): void {
if (!process.env.COSMOS_ENABLED) {
@ -17,6 +26,11 @@ function pendingWithoutCosmos(): void {
}
}
function parseSuccess(rawLog?: string): readonly Log[] {
if (!rawLog) throw new Error("Log missing");
return parseLogs(JSON.parse(rawLog));
}
describe("RestClient", () => {
it("can be constructed", () => {
const client = new RestClient(httpUrl);
@ -51,4 +65,103 @@ describe("RestClient", () => {
expect(await client.encodeTx(tx)).toEqual(fromBase64(data.tx_data));
});
});
describe("post", () => {
it("can send tokens", async () => {
pendingWithoutCosmos();
const wallet = Secp256k1HdWallet.fromMnemonic(faucetMnemonic);
const signer = await wallet.createIdentity("abc" as ChainId, faucetPath);
const memo = "My first contract on chain";
const theMsg: MsgSend = {
type: "cosmos-sdk/MsgSend",
value: {
from_address: faucetAddress,
to_address: emptyAddress,
amount: [
{
denom: "ucosm",
amount: "1234567",
},
],
},
};
const fee: StdFee = {
amount: [
{
amount: "5000",
denom: "ucosm",
},
],
gas: "890000",
};
const client = new RestClient(httpUrl);
const account = (await client.authAccounts(faucetAddress)).result.value;
const signBytes = makeSignBytes([theMsg], fee, defaultNetworkId, memo, account) as SignableBytes;
const rawSignature = await wallet.createTransactionSignature(signer, signBytes, PrehashType.Sha256);
const signature = encodeSecp256k1Signature(signer.pubkey.data, rawSignature);
const signedTx: StdTx = {
msg: [theMsg],
fee: fee,
memo: memo,
signatures: [signature],
};
const postableBytes = marshalTx(signedTx);
const result = await client.postTx(postableBytes);
// console.log("Raw log:", result.raw_log);
expect(result.code).toBeFalsy();
});
it("can upload wasm", async () => {
pendingWithoutCosmos();
const wallet = Secp256k1HdWallet.fromMnemonic(faucetMnemonic);
const signer = await wallet.createIdentity("abc" as ChainId, faucetPath);
const memo = "My first contract on chain";
const theMsg: MsgStoreCode = {
type: "wasm/store-code",
value: {
sender: faucetAddress,
wasm_byte_code: contract.data,
source: "https://github.com/confio/cosmwasm/raw/0.7/lib/vm/testdata/contract_0.6.wasm",
builder: "cosmwasm-opt:0.6.2",
},
};
const fee: StdFee = {
amount: [
{
amount: "5000000",
denom: "ucosm",
},
],
gas: "89000000",
};
const client = new RestClient(httpUrl);
const account = (await client.authAccounts(faucetAddress)).result.value;
const signBytes = makeSignBytes([theMsg], fee, defaultNetworkId, memo, account) as SignableBytes;
const rawSignature = await wallet.createTransactionSignature(signer, signBytes, PrehashType.Sha256);
const signature = encodeSecp256k1Signature(signer.pubkey.data, rawSignature);
const signedTx: StdTx = {
msg: [theMsg],
fee: fee,
memo: memo,
signatures: [signature],
};
const postableBytes = marshalTx(signedTx);
const result = await client.postTx(postableBytes);
// console.log("Raw log:", result.raw_log);
expect(result.code).toBeFalsy();
const [firstLog] = parseSuccess(result.raw_log);
const codeIdAttr = firstLog.events[0].attributes.find(attr => attr.key === "code_id");
expect(codeIdAttr).toEqual({ key: "code_id", value: "1" });
});
});
});

File diff suppressed because one or more lines are too long

View File

@ -22,13 +22,12 @@ export function isAminoStdTx(txValue: unknown): txValue is StdTx {
);
}
export interface Msg {
interface MsgTemplate {
readonly type: string;
// TODO: make better union type
readonly value: MsgSend | unknown;
readonly value: object;
}
export interface MsgSend {
export interface ValueSend {
/** Bech32 account address */
readonly from_address: string;
/** Bech32 account address */
@ -36,6 +35,37 @@ export interface MsgSend {
readonly amount: ReadonlyArray<Coin>;
}
export interface MsgSend extends MsgTemplate {
readonly type: "cosmos-sdk/MsgSend";
readonly value: ValueSend;
}
export interface ValueStoreCode {
/** Bech32 account address */
readonly sender: string;
/** Base64 encoded Wasm */
readonly wasm_byte_code: string;
/** A valid URI reference to the contract's source code, optional */
readonly source?: string;
/** A docker tag, optional */
readonly builder?: string;
}
export interface MsgStoreCode extends MsgTemplate {
readonly type: "wasm/store-code";
readonly value: ValueStoreCode;
}
export type Msg = MsgSend | MsgStoreCode | MsgTemplate;
export function isMsgSend(msg: Msg): msg is MsgSend {
return (msg as MsgSend).type === "cosmos-sdk/MsgSend";
}
export function isMsgStoreCode(msg: Msg): msg is MsgStoreCode {
return (msg as MsgStoreCode).type === "wasm/store-code";
}
export interface StdFee {
readonly amount: ReadonlyArray<Coin>;
readonly gas: string;
@ -67,3 +97,6 @@ export interface BaseAccount {
readonly account_number: number;
readonly sequence: number;
}
/** The data we need from BaseAccount to create a nonce */
export type NonceInfo = Pick<BaseAccount, "account_number" | "sequence">;

View File

@ -1,2 +1,10 @@
import { StdTx } from "./types";
import { Msg, NonceInfo, StdFee, StdSignature, StdTx } from "./types";
export declare function marshalTx(tx: StdTx): Uint8Array;
export declare function makeSignBytes(
msgs: readonly Msg[],
fee: StdFee,
chainId: string,
memo: string,
account: NonceInfo,
): Uint8Array;
export declare function encodeSecp256k1Signature(pubkey: Uint8Array, signature: Uint8Array): StdSignature;

View File

@ -1,5 +1,5 @@
import * as types from "./types";
export { unmarshalTx } from "./decoding";
export { marshalTx } from "./encoding";
export { encodeSecp256k1Signature, makeSignBytes, marshalTx } from "./encoding";
export { RestClient, TxsResponse } from "./restclient";
export { types };

17
packages/sdk/types/logs.d.ts vendored Normal file
View File

@ -0,0 +1,17 @@
export interface Attribute {
readonly key: string;
readonly value: string;
}
export interface Event {
readonly type: "message";
readonly attributes: readonly Attribute[];
}
export interface Log {
readonly msg_index: number;
readonly log: string;
readonly events: readonly Event[];
}
export declare function parseAttribute(input: unknown): Attribute;
export declare function parseEvent(input: unknown): Event;
export declare function parseLog(input: unknown): Log;
export declare function parseLogs(input: unknown): readonly Log[];

View File

@ -12,17 +12,38 @@ export declare type AminoTx = Tx & {
readonly value: StdTx;
};
export declare function isAminoStdTx(txValue: unknown): txValue is StdTx;
export interface Msg {
interface MsgTemplate {
readonly type: string;
readonly value: MsgSend | unknown;
readonly value: object;
}
export interface MsgSend {
export interface ValueSend {
/** Bech32 account address */
readonly from_address: string;
/** Bech32 account address */
readonly to_address: string;
readonly amount: ReadonlyArray<Coin>;
}
export interface MsgSend extends MsgTemplate {
readonly type: "cosmos-sdk/MsgSend";
readonly value: ValueSend;
}
export interface ValueStoreCode {
/** Bech32 account address */
readonly sender: string;
/** Base64 encoded Wasm */
readonly wasm_byte_code: string;
/** A valid URI reference to the contract's source code, optional */
readonly source?: string;
/** A docker tag, optional */
readonly builder?: string;
}
export interface MsgStoreCode extends MsgTemplate {
readonly type: "wasm/store-code";
readonly value: ValueStoreCode;
}
export declare type Msg = MsgSend | MsgStoreCode | MsgTemplate;
export declare function isMsgSend(msg: Msg): msg is MsgSend;
export declare function isMsgStoreCode(msg: Msg): msg is MsgStoreCode;
export interface StdFee {
readonly amount: ReadonlyArray<Coin>;
readonly gas: string;
@ -48,3 +69,6 @@ export interface BaseAccount {
readonly account_number: number;
readonly sequence: number;
}
/** The data we need from BaseAccount to create a nonce */
export declare type NonceInfo = Pick<BaseAccount, "account_number" | "sequence">;
export {};

41
scripts/cosm/manual_start.sh Executable file
View File

@ -0,0 +1,41 @@
#!/bin/bash
set -o errexit -o nounset -o pipefail
command -v shellcheck > /dev/null && shellcheck "$0"
## This is like start.sh but using local binaries, not docker images
SCRIPT_DIR="$(realpath "$(dirname "$0")")"
TMP_DIR=$(mktemp -d "${TMPDIR:-/tmp}/gaia.XXXXXXXXX")
chmod 777 "$TMP_DIR"
echo "Using temporary dir $TMP_DIR"
WASMD_LOGFILE="$TMP_DIR/wasmd.log"
REST_SERVER_LOGFILE="$TMP_DIR/rest-server.log"
# move the template into our temporary home
cp -r "$SCRIPT_DIR"/template/.wasm* "$TMP_DIR"
wasmd start \
--home "$TMP_DIR/.wasmd" \
--trace \
--rpc.laddr tcp://0.0.0.0:26657 \
> "$WASMD_LOGFILE" &
echo "wasmd running and logging into $WASMD_LOGFILE"
sleep 10
cat "$WASMD_LOGFILE"
wasmcli rest-server \
--home "$TMP_DIR/.wasmcli" \
--node tcp://localhost:26657 \
--trust-node \
--laddr tcp://0.0.0.0:1317 \
> "$REST_SERVER_LOGFILE" &
echo "rest server running on http://localhost:1317 and logging into $REST_SERVER_LOGFILE"
# Debug rest server start
sleep 3
cat "$REST_SERVER_LOGFILE"
tail -f "$WASMD_LOGFILE"