mirror of
https://github.com/cosmos/cosmjs.git
synced 2025-03-10 21:49:15 +00:00
Upgrade CosmWasm to 0.9 (#245)
* Upgrade chain to v0.9.0-alpha4 * Update expected build_tags * Update wasmd init contracts * Update hackatom test contract * Update CosmWasm message descriptions * Make some test code more compact * Pull out InstantiateOptions * Add admin field to ContractInfo * Allow instantiating with admin * Remove some noise * Add SigningCosmWasmClient.updateAdmin * Create return type ChangeAdminResult * Add SigningCosmWasmClient.clearAdmin * Add SigningCosmWasmClient.migrate * Move message type testers close to type * Export MsgUpdateAdmin/isMsgUpdateAdmin * Fix typo in privillage * Update some test code * Test hackatom result data * Add compatibility table * Update wasmd to v0.9.0-beta * Upgrade test contracts
This commit is contained in:
parent
9f7b126044
commit
77980b60f4
@ -4,6 +4,13 @@
|
||||
|
||||
An SDK to build CosmWasm clients.
|
||||
|
||||
## Compatibility
|
||||
|
||||
| CosmJS | CosmWasm | x/wasm |
|
||||
| ------ | -------- | ------ |
|
||||
| 0.21 | 0.9 | 0.9 |
|
||||
| 0.20 | 0.8 | 0.8 |
|
||||
|
||||
## License
|
||||
|
||||
This package is part of the cosmjs repository, licensed under the Apache License
|
||||
|
@ -23,17 +23,25 @@ export {
|
||||
export {
|
||||
ExecuteResult,
|
||||
FeeTable,
|
||||
InstantiateOptions,
|
||||
InstantiateResult,
|
||||
MigrateResult,
|
||||
SigningCallback,
|
||||
SigningCosmWasmClient,
|
||||
UploadMeta,
|
||||
UploadResult,
|
||||
} from "./signingcosmwasmclient";
|
||||
export {
|
||||
isMsgClearAdmin,
|
||||
isMsgExecuteContract,
|
||||
isMsgInstantiateContract,
|
||||
isMsgMigrateContract,
|
||||
isMsgUpdateAdmin,
|
||||
isMsgStoreCode,
|
||||
MsgStoreCode,
|
||||
MsgClearAdmin,
|
||||
MsgExecuteContract,
|
||||
MsgInstantiateContract,
|
||||
MsgMigrateContract,
|
||||
MsgUpdateAdmin,
|
||||
MsgStoreCode,
|
||||
} from "./msgs";
|
||||
|
@ -1,9 +1,10 @@
|
||||
import { Coin, Msg } from "@cosmjs/sdk38";
|
||||
|
||||
/**
|
||||
* Uploads Wam code to the chain
|
||||
* Uploads Wasm code to the chain.
|
||||
* A numeric, auto-incrementing code ID will be generated as a result of the execution of this message.
|
||||
*
|
||||
* @see https://github.com/cosmwasm/wasmd/blob/9842678d89/x/wasm/internal/types/msg.go#L17
|
||||
* @see https://github.com/CosmWasm/wasmd/blob/v0.9.0-alpha4/x/wasm/internal/types/msg.go#L34
|
||||
*/
|
||||
export interface MsgStoreCode extends Msg {
|
||||
readonly type: "wasm/store-code";
|
||||
@ -19,10 +20,15 @@ export interface MsgStoreCode extends Msg {
|
||||
};
|
||||
}
|
||||
|
||||
export function isMsgStoreCode(msg: Msg): msg is MsgStoreCode {
|
||||
return (msg as MsgStoreCode).type === "wasm/store-code";
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an instance of contract that was uploaded before.
|
||||
* This will trigger a call to the "init" export.
|
||||
*
|
||||
* @see https://github.com/cosmwasm/wasmd/blob/9842678d89/x/wasm/internal/types/msg.go#L73
|
||||
* @see https://github.com/CosmWasm/wasmd/blob/v0.9.0-alpha4/x/wasm/internal/types/msg.go#L104
|
||||
*/
|
||||
export interface MsgInstantiateContract extends Msg {
|
||||
readonly type: "wasm/instantiate";
|
||||
@ -36,13 +42,60 @@ export interface MsgInstantiateContract extends Msg {
|
||||
/** Init message as JavaScript object */
|
||||
readonly init_msg: any;
|
||||
readonly init_funds: ReadonlyArray<Coin>;
|
||||
/** Bech32-encoded admin address */
|
||||
readonly admin?: string;
|
||||
};
|
||||
}
|
||||
|
||||
export function isMsgInstantiateContract(msg: Msg): msg is MsgInstantiateContract {
|
||||
return (msg as MsgInstantiateContract).type === "wasm/instantiate";
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an instance of contract that was uploaded before.
|
||||
* Update the admin of a contract
|
||||
*
|
||||
* @see https://github.com/cosmwasm/wasmd/blob/9842678d89/x/wasm/internal/types/msg.go#L103
|
||||
* @see https://github.com/CosmWasm/wasmd/blob/v0.9.0-beta/x/wasm/internal/types/msg.go#L231
|
||||
*/
|
||||
export interface MsgUpdateAdmin extends Msg {
|
||||
readonly type: "wasm/update-contract-admin";
|
||||
readonly value: {
|
||||
/** Bech32-encoded sender address. This must be the old admin. */
|
||||
readonly sender: string;
|
||||
/** Bech32-encoded contract address to be updated */
|
||||
readonly contract: string;
|
||||
/** Bech32-encoded address of the new admin */
|
||||
readonly new_admin: string;
|
||||
};
|
||||
}
|
||||
|
||||
export function isMsgUpdateAdmin(msg: Msg): msg is MsgUpdateAdmin {
|
||||
return (msg as MsgUpdateAdmin).type === "wasm/update-contract-admin";
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears the admin of a contract, making it immutable.
|
||||
*
|
||||
* @see https://github.com/CosmWasm/wasmd/blob/v0.9.0-beta/x/wasm/internal/types/msg.go#L269
|
||||
*/
|
||||
export interface MsgClearAdmin extends Msg {
|
||||
readonly type: "wasm/clear-contract-admin";
|
||||
readonly value: {
|
||||
/** Bech32-encoded sender address. This must be the old admin. */
|
||||
readonly sender: string;
|
||||
/** Bech32-encoded contract address to be updated */
|
||||
readonly contract: string;
|
||||
};
|
||||
}
|
||||
|
||||
export function isMsgClearAdmin(msg: Msg): msg is MsgClearAdmin {
|
||||
return (msg as MsgClearAdmin).type === "wasm/clear-contract-admin";
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute a smart contract.
|
||||
* This will trigger a call to the "handle" export.
|
||||
*
|
||||
* @see https://github.com/CosmWasm/wasmd/blob/v0.9.0-alpha4/x/wasm/internal/types/msg.go#L158
|
||||
*/
|
||||
export interface MsgExecuteContract extends Msg {
|
||||
readonly type: "wasm/execute";
|
||||
@ -57,14 +110,29 @@ export interface MsgExecuteContract extends Msg {
|
||||
};
|
||||
}
|
||||
|
||||
export function isMsgStoreCode(msg: Msg): msg is MsgStoreCode {
|
||||
return (msg as MsgStoreCode).type === "wasm/store-code";
|
||||
}
|
||||
|
||||
export function isMsgInstantiateContract(msg: Msg): msg is MsgInstantiateContract {
|
||||
return (msg as MsgInstantiateContract).type === "wasm/instantiate";
|
||||
}
|
||||
|
||||
export function isMsgExecuteContract(msg: Msg): msg is MsgExecuteContract {
|
||||
return (msg as MsgExecuteContract).type === "wasm/execute";
|
||||
}
|
||||
|
||||
/**
|
||||
* Migrates a contract to a new Wasm code.
|
||||
*
|
||||
* @see https://github.com/CosmWasm/wasmd/blob/v0.9.0-alpha4/x/wasm/internal/types/msg.go#L195
|
||||
*/
|
||||
export interface MsgMigrateContract extends Msg {
|
||||
readonly type: "wasm/migrate";
|
||||
readonly value: {
|
||||
/** Bech32 account address */
|
||||
readonly sender: string;
|
||||
/** Bech32 account address */
|
||||
readonly contract: string;
|
||||
/** The new code */
|
||||
readonly code_id: string;
|
||||
/** Migrate message as JavaScript object */
|
||||
readonly msg: any;
|
||||
};
|
||||
}
|
||||
|
||||
export function isMsgMigrateContract(msg: Msg): msg is MsgMigrateContract {
|
||||
return (msg as MsgMigrateContract).type === "wasm/migrate";
|
||||
}
|
||||
|
@ -3,6 +3,8 @@ import { Sha256 } from "@cosmjs/crypto";
|
||||
import { Bech32, fromAscii, fromBase64, fromHex, toAscii, toBase64, toHex } from "@cosmjs/encoding";
|
||||
import {
|
||||
Coin,
|
||||
coin,
|
||||
coins,
|
||||
makeSignBytes,
|
||||
Msg,
|
||||
Pen,
|
||||
@ -119,6 +121,7 @@ async function executeContract(
|
||||
client: RestClient,
|
||||
pen: Pen,
|
||||
contractAddress: string,
|
||||
msg: object,
|
||||
): Promise<PostTxsResponse> {
|
||||
const memo = "Time for action";
|
||||
const theMsg: MsgExecuteContract = {
|
||||
@ -126,17 +129,12 @@ async function executeContract(
|
||||
value: {
|
||||
sender: alice.address0,
|
||||
contract: contractAddress,
|
||||
msg: { release: {} },
|
||||
msg: msg,
|
||||
sent_funds: [],
|
||||
},
|
||||
};
|
||||
const fee: StdFee = {
|
||||
amount: [
|
||||
{
|
||||
amount: "5000000",
|
||||
denom: "ucosm",
|
||||
},
|
||||
],
|
||||
amount: coins(5000000, "ucosm"),
|
||||
gas: "89000000",
|
||||
};
|
||||
|
||||
@ -261,16 +259,7 @@ describe("RestClient", () => {
|
||||
const pen = await Secp256k1Pen.fromMnemonic(alice.mnemonic);
|
||||
const client = new RestClient(wasmd.endpoint);
|
||||
|
||||
const transferAmount: readonly Coin[] = [
|
||||
{
|
||||
amount: "1234",
|
||||
denom: "ucosm",
|
||||
},
|
||||
{
|
||||
amount: "321",
|
||||
denom: "ustake",
|
||||
},
|
||||
];
|
||||
const transferAmount = [coin(1234, "ucosm"), coin(321, "ustake")];
|
||||
const beneficiaryAddress = makeRandomAddress();
|
||||
|
||||
let codeId: number;
|
||||
@ -308,7 +297,8 @@ describe("RestClient", () => {
|
||||
|
||||
// execute
|
||||
{
|
||||
const result = await executeContract(client, pen, contractAddress);
|
||||
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);
|
||||
@ -424,9 +414,16 @@ describe("RestClient", () => {
|
||||
// check out info
|
||||
const myInfo = await client.getContractInfo(myAddress);
|
||||
assert(myInfo);
|
||||
expect(myInfo.code_id).toEqual(codeId);
|
||||
expect(myInfo.creator).toEqual(alice.address0);
|
||||
expect((myInfo.init_msg as any).beneficiary).toEqual(beneficiaryAddress);
|
||||
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();
|
||||
|
@ -39,6 +39,8 @@ export interface ContractInfo {
|
||||
readonly code_id: number;
|
||||
/** Bech32 account address */
|
||||
readonly creator: string;
|
||||
/** Bech32-encoded admin address */
|
||||
readonly admin?: string;
|
||||
readonly label: string;
|
||||
}
|
||||
|
||||
|
@ -1,12 +1,12 @@
|
||||
import { Sha256 } from "@cosmjs/crypto";
|
||||
import { toHex } from "@cosmjs/encoding";
|
||||
import { Coin, Secp256k1Pen } from "@cosmjs/sdk38";
|
||||
import { coin, coins, Secp256k1Pen } from "@cosmjs/sdk38";
|
||||
import { assert } from "@cosmjs/utils";
|
||||
|
||||
import { isPostTxFailure, PrivateCosmWasmClient } from "./cosmwasmclient";
|
||||
import { RestClient } from "./restclient";
|
||||
import { SigningCosmWasmClient, UploadMeta } from "./signingcosmwasmclient";
|
||||
import { alice, getHackatom, makeRandomAddress, pendingWithoutWasmd } from "./testutils.spec";
|
||||
import { alice, getHackatom, makeRandomAddress, pendingWithoutWasmd, unused } from "./testutils.spec";
|
||||
|
||||
const httpUrl = "http://localhost:1317";
|
||||
|
||||
@ -82,16 +82,7 @@ describe("SigningCosmWasmClient", () => {
|
||||
const client = new SigningCosmWasmClient(httpUrl, alice.address0, (signBytes) => pen.sign(signBytes));
|
||||
const { codeId } = await client.upload(getHackatom().data);
|
||||
|
||||
const transferAmount: readonly Coin[] = [
|
||||
{
|
||||
amount: "1234",
|
||||
denom: "ucosm",
|
||||
},
|
||||
{
|
||||
amount: "321",
|
||||
denom: "ustake",
|
||||
},
|
||||
];
|
||||
const transferAmount = [coin(1234, "ucosm"), coin(321, "ustake")];
|
||||
const beneficiaryAddress = makeRandomAddress();
|
||||
const { contractAddress } = await client.instantiate(
|
||||
codeId,
|
||||
@ -100,8 +91,10 @@ describe("SigningCosmWasmClient", () => {
|
||||
beneficiary: beneficiaryAddress,
|
||||
},
|
||||
"My cool label",
|
||||
"Let's see if the memo is used",
|
||||
{
|
||||
memo: "Let's see if the memo is used",
|
||||
transferAmount,
|
||||
},
|
||||
);
|
||||
|
||||
const rest = new RestClient(httpUrl);
|
||||
@ -109,6 +102,29 @@ describe("SigningCosmWasmClient", () => {
|
||||
expect(balance).toEqual(transferAmount);
|
||||
});
|
||||
|
||||
it("works with admin", async () => {
|
||||
pendingWithoutWasmd();
|
||||
const pen = await Secp256k1Pen.fromMnemonic(alice.mnemonic);
|
||||
const client = new SigningCosmWasmClient(httpUrl, alice.address0, (signBytes) => pen.sign(signBytes));
|
||||
const { codeId } = await client.upload(getHackatom().data);
|
||||
|
||||
const beneficiaryAddress = makeRandomAddress();
|
||||
const { contractAddress } = await client.instantiate(
|
||||
codeId,
|
||||
{
|
||||
verifier: alice.address0,
|
||||
beneficiary: beneficiaryAddress,
|
||||
},
|
||||
"My cool label",
|
||||
{ admin: unused.address },
|
||||
);
|
||||
|
||||
const rest = new RestClient(httpUrl);
|
||||
const contract = await rest.getContractInfo(contractAddress);
|
||||
assert(contract);
|
||||
expect(contract.admin).toEqual(unused.address);
|
||||
});
|
||||
|
||||
it("can instantiate one code multiple times", async () => {
|
||||
pendingWithoutWasmd();
|
||||
const pen = await Secp256k1Pen.fromMnemonic(alice.mnemonic);
|
||||
@ -135,6 +151,111 @@ describe("SigningCosmWasmClient", () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe("updateAdmin", () => {
|
||||
it("can update an admin", async () => {
|
||||
pendingWithoutWasmd();
|
||||
const pen = await Secp256k1Pen.fromMnemonic(alice.mnemonic);
|
||||
const client = new SigningCosmWasmClient(httpUrl, alice.address0, (signBytes) => pen.sign(signBytes));
|
||||
const { codeId } = await client.upload(getHackatom().data);
|
||||
|
||||
const beneficiaryAddress = makeRandomAddress();
|
||||
const { contractAddress } = await client.instantiate(
|
||||
codeId,
|
||||
{
|
||||
verifier: alice.address0,
|
||||
beneficiary: beneficiaryAddress,
|
||||
},
|
||||
"My cool label",
|
||||
{
|
||||
admin: alice.address0,
|
||||
},
|
||||
);
|
||||
|
||||
const rest = new RestClient(httpUrl);
|
||||
const state1 = await rest.getContractInfo(contractAddress);
|
||||
assert(state1);
|
||||
expect(state1.admin).toEqual(alice.address0);
|
||||
|
||||
await client.updateAdmin(contractAddress, unused.address);
|
||||
|
||||
const state2 = await rest.getContractInfo(contractAddress);
|
||||
assert(state2);
|
||||
expect(state2.admin).toEqual(unused.address);
|
||||
});
|
||||
});
|
||||
|
||||
describe("clearAdmin", () => {
|
||||
it("can clear an admin", async () => {
|
||||
pendingWithoutWasmd();
|
||||
const pen = await Secp256k1Pen.fromMnemonic(alice.mnemonic);
|
||||
const client = new SigningCosmWasmClient(httpUrl, alice.address0, (signBytes) => pen.sign(signBytes));
|
||||
const { codeId } = await client.upload(getHackatom().data);
|
||||
|
||||
const beneficiaryAddress = makeRandomAddress();
|
||||
const { contractAddress } = await client.instantiate(
|
||||
codeId,
|
||||
{
|
||||
verifier: alice.address0,
|
||||
beneficiary: beneficiaryAddress,
|
||||
},
|
||||
"My cool label",
|
||||
{
|
||||
admin: alice.address0,
|
||||
},
|
||||
);
|
||||
|
||||
const rest = new RestClient(httpUrl);
|
||||
const state1 = await rest.getContractInfo(contractAddress);
|
||||
assert(state1);
|
||||
expect(state1.admin).toEqual(alice.address0);
|
||||
|
||||
await client.clearAdmin(contractAddress);
|
||||
|
||||
const state2 = await rest.getContractInfo(contractAddress);
|
||||
assert(state2);
|
||||
expect(state2.admin).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
describe("migrate", () => {
|
||||
it("can can migrate from one code ID to another", async () => {
|
||||
pendingWithoutWasmd();
|
||||
const pen = await Secp256k1Pen.fromMnemonic(alice.mnemonic);
|
||||
const client = new SigningCosmWasmClient(httpUrl, alice.address0, (signBytes) => pen.sign(signBytes));
|
||||
const { codeId: codeId1 } = await client.upload(getHackatom().data);
|
||||
const { codeId: codeId2 } = await client.upload(getHackatom().data);
|
||||
|
||||
const beneficiaryAddress = makeRandomAddress();
|
||||
const { contractAddress } = await client.instantiate(
|
||||
codeId1,
|
||||
{
|
||||
verifier: alice.address0,
|
||||
beneficiary: beneficiaryAddress,
|
||||
},
|
||||
"My cool label",
|
||||
{
|
||||
admin: alice.address0,
|
||||
},
|
||||
);
|
||||
|
||||
const rest = new RestClient(httpUrl);
|
||||
const state1 = await rest.getContractInfo(contractAddress);
|
||||
assert(state1);
|
||||
expect(state1.admin).toEqual(alice.address0);
|
||||
|
||||
const newVerifier = makeRandomAddress();
|
||||
await client.migrate(contractAddress, codeId2, { verifier: newVerifier });
|
||||
|
||||
const state2 = await rest.getContractInfo(contractAddress);
|
||||
assert(state2);
|
||||
expect(state2).toEqual({
|
||||
...state1,
|
||||
// eslint-disable-next-line @typescript-eslint/camelcase
|
||||
code_id: codeId2,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("execute", () => {
|
||||
it("works", async () => {
|
||||
pendingWithoutWasmd();
|
||||
@ -143,16 +264,7 @@ describe("SigningCosmWasmClient", () => {
|
||||
const { codeId } = await client.upload(getHackatom().data);
|
||||
|
||||
// instantiate
|
||||
const transferAmount: readonly Coin[] = [
|
||||
{
|
||||
amount: "233444",
|
||||
denom: "ucosm",
|
||||
},
|
||||
{
|
||||
amount: "5454",
|
||||
denom: "ustake",
|
||||
},
|
||||
];
|
||||
const transferAmount = [coin(233444, "ucosm"), coin(5454, "ustake")];
|
||||
const beneficiaryAddress = makeRandomAddress();
|
||||
const { contractAddress } = await client.instantiate(
|
||||
codeId,
|
||||
@ -161,8 +273,9 @@ describe("SigningCosmWasmClient", () => {
|
||||
beneficiary: beneficiaryAddress,
|
||||
},
|
||||
"amazing random contract",
|
||||
undefined,
|
||||
{
|
||||
transferAmount,
|
||||
},
|
||||
);
|
||||
|
||||
// execute
|
||||
@ -191,12 +304,7 @@ describe("SigningCosmWasmClient", () => {
|
||||
const client = new SigningCosmWasmClient(httpUrl, alice.address0, (signBytes) => pen.sign(signBytes));
|
||||
|
||||
// instantiate
|
||||
const transferAmount: readonly Coin[] = [
|
||||
{
|
||||
amount: "7890",
|
||||
denom: "ucosm",
|
||||
},
|
||||
];
|
||||
const transferAmount = coins(7890, "ucosm");
|
||||
const beneficiaryAddress = makeRandomAddress();
|
||||
|
||||
// no tokens here
|
||||
|
@ -23,7 +23,14 @@ import {
|
||||
PostTxResult,
|
||||
} from "./cosmwasmclient";
|
||||
import { findAttribute, Log } from "./logs";
|
||||
import { MsgExecuteContract, MsgInstantiateContract, MsgStoreCode } from "./msgs";
|
||||
import {
|
||||
MsgClearAdmin,
|
||||
MsgExecuteContract,
|
||||
MsgInstantiateContract,
|
||||
MsgMigrateContract,
|
||||
MsgStoreCode,
|
||||
MsgUpdateAdmin,
|
||||
} from "./msgs";
|
||||
|
||||
export interface SigningCallback {
|
||||
(signBytes: Uint8Array): Promise<StdSignature>;
|
||||
@ -33,7 +40,10 @@ export interface FeeTable {
|
||||
readonly upload: StdFee;
|
||||
readonly init: StdFee;
|
||||
readonly exec: StdFee;
|
||||
readonly migrate: StdFee;
|
||||
readonly send: StdFee;
|
||||
/** Paid when setting the contract admin to a new address or unsetting it */
|
||||
readonly changeAdmin: StdFee;
|
||||
}
|
||||
|
||||
function prepareBuilder(buider: string | undefined): string {
|
||||
@ -54,6 +64,10 @@ const defaultFees: FeeTable = {
|
||||
amount: coins(12500, "ucosm"),
|
||||
gas: "500000", // 500k
|
||||
},
|
||||
migrate: {
|
||||
amount: coins(5000, "ucosm"),
|
||||
gas: "200000", // 200k
|
||||
},
|
||||
exec: {
|
||||
amount: coins(5000, "ucosm"),
|
||||
gas: "200000", // 200k
|
||||
@ -62,6 +76,10 @@ const defaultFees: FeeTable = {
|
||||
amount: coins(2000, "ucosm"),
|
||||
gas: "80000", // 80k
|
||||
},
|
||||
changeAdmin: {
|
||||
amount: coins(2000, "ucosm"),
|
||||
gas: "80000", // 80k
|
||||
},
|
||||
};
|
||||
|
||||
export interface UploadMeta {
|
||||
@ -87,6 +105,20 @@ export interface UploadResult {
|
||||
readonly transactionHash: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* The options of an .instantiate() call.
|
||||
* All properties are optional.
|
||||
*/
|
||||
export interface InstantiateOptions {
|
||||
readonly memo?: string;
|
||||
readonly transferAmount?: readonly Coin[];
|
||||
/**
|
||||
* A bech32 encoded address of an admin account.
|
||||
* Caution: an admin has the privilege to upgrade a contract. If this is not desired, do not set this value.
|
||||
*/
|
||||
readonly admin?: string;
|
||||
}
|
||||
|
||||
export interface InstantiateResult {
|
||||
/** The address of the newly instantiated contract */
|
||||
readonly contractAddress: string;
|
||||
@ -95,6 +127,21 @@ export interface InstantiateResult {
|
||||
readonly transactionHash: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Result type of updateAdmin and clearAdmin
|
||||
*/
|
||||
export interface ChangeAdminResult {
|
||||
readonly logs: readonly Log[];
|
||||
/** Transaction hash (might be used as transaction ID). Guaranteed to be non-empty upper-case hex */
|
||||
readonly transactionHash: string;
|
||||
}
|
||||
|
||||
export interface MigrateResult {
|
||||
readonly logs: readonly Log[];
|
||||
/** Transaction hash (might be used as transaction ID). Guaranteed to be non-empty upper-case hex */
|
||||
readonly transactionHash: string;
|
||||
}
|
||||
|
||||
export interface ExecuteResult {
|
||||
readonly logs: readonly Log[];
|
||||
/** Transaction hash (might be used as transaction ID). Guaranteed to be non-empty upper-case hex */
|
||||
@ -194,8 +241,7 @@ export class SigningCosmWasmClient extends CosmWasmClient {
|
||||
codeId: number,
|
||||
initMsg: object,
|
||||
label: string,
|
||||
memo = "",
|
||||
transferAmount?: readonly Coin[],
|
||||
options: InstantiateOptions = {},
|
||||
): Promise<InstantiateResult> {
|
||||
const instantiateMsg: MsgInstantiateContract = {
|
||||
type: "wasm/instantiate",
|
||||
@ -207,9 +253,11 @@ export class SigningCosmWasmClient extends CosmWasmClient {
|
||||
// eslint-disable-next-line @typescript-eslint/camelcase
|
||||
init_msg: initMsg,
|
||||
// eslint-disable-next-line @typescript-eslint/camelcase
|
||||
init_funds: transferAmount || [],
|
||||
init_funds: options.transferAmount || [],
|
||||
admin: options.admin,
|
||||
},
|
||||
};
|
||||
const memo = options.memo || "";
|
||||
const fee = this.fees.init;
|
||||
const { accountNumber, sequence } = await this.getNonce();
|
||||
const chainId = await this.getChainId();
|
||||
@ -235,6 +283,106 @@ export class SigningCosmWasmClient extends CosmWasmClient {
|
||||
};
|
||||
}
|
||||
|
||||
public async updateAdmin(contractAddress: string, newAdmin: string, memo = ""): Promise<ChangeAdminResult> {
|
||||
const updateAdminMsg: MsgUpdateAdmin = {
|
||||
type: "wasm/update-contract-admin",
|
||||
value: {
|
||||
sender: this.senderAddress,
|
||||
contract: contractAddress,
|
||||
// eslint-disable-next-line @typescript-eslint/camelcase
|
||||
new_admin: newAdmin,
|
||||
},
|
||||
};
|
||||
const fee = this.fees.changeAdmin;
|
||||
const { accountNumber, sequence } = await this.getNonce();
|
||||
const chainId = await this.getChainId();
|
||||
const signBytes = makeSignBytes([updateAdminMsg], fee, chainId, memo, accountNumber, sequence);
|
||||
const signature = await this.signCallback(signBytes);
|
||||
const signedTx: StdTx = {
|
||||
msg: [updateAdminMsg],
|
||||
fee: fee,
|
||||
memo: memo,
|
||||
signatures: [signature],
|
||||
};
|
||||
|
||||
const result = await this.postTx(signedTx);
|
||||
if (isPostTxFailure(result)) {
|
||||
throw new Error(createPostTxErrorMessage(result));
|
||||
}
|
||||
return {
|
||||
logs: result.logs,
|
||||
transactionHash: result.transactionHash,
|
||||
};
|
||||
}
|
||||
|
||||
public async clearAdmin(contractAddress: string, memo = ""): Promise<ChangeAdminResult> {
|
||||
const clearAdminMsg: MsgClearAdmin = {
|
||||
type: "wasm/clear-contract-admin",
|
||||
value: {
|
||||
sender: this.senderAddress,
|
||||
contract: contractAddress,
|
||||
},
|
||||
};
|
||||
const fee = this.fees.changeAdmin;
|
||||
const { accountNumber, sequence } = await this.getNonce();
|
||||
const chainId = await this.getChainId();
|
||||
const signBytes = makeSignBytes([clearAdminMsg], fee, chainId, memo, accountNumber, sequence);
|
||||
const signature = await this.signCallback(signBytes);
|
||||
const signedTx: StdTx = {
|
||||
msg: [clearAdminMsg],
|
||||
fee: fee,
|
||||
memo: memo,
|
||||
signatures: [signature],
|
||||
};
|
||||
|
||||
const result = await this.postTx(signedTx);
|
||||
if (isPostTxFailure(result)) {
|
||||
throw new Error(createPostTxErrorMessage(result));
|
||||
}
|
||||
return {
|
||||
logs: result.logs,
|
||||
transactionHash: result.transactionHash,
|
||||
};
|
||||
}
|
||||
|
||||
public async migrate(
|
||||
contractAddress: string,
|
||||
codeId: number,
|
||||
migrateMsg: object,
|
||||
memo = "",
|
||||
): Promise<MigrateResult> {
|
||||
const msg: MsgMigrateContract = {
|
||||
type: "wasm/migrate",
|
||||
value: {
|
||||
sender: this.senderAddress,
|
||||
contract: contractAddress,
|
||||
// eslint-disable-next-line @typescript-eslint/camelcase
|
||||
code_id: new Uint53(codeId).toString(),
|
||||
msg: migrateMsg,
|
||||
},
|
||||
};
|
||||
const fee = this.fees.migrate;
|
||||
const { accountNumber, sequence } = await this.getNonce();
|
||||
const chainId = await this.getChainId();
|
||||
const signBytes = makeSignBytes([msg], fee, chainId, memo, accountNumber, sequence);
|
||||
const signature = await this.signCallback(signBytes);
|
||||
const signedTx: StdTx = {
|
||||
msg: [msg],
|
||||
fee: fee,
|
||||
memo: memo,
|
||||
signatures: [signature],
|
||||
};
|
||||
|
||||
const result = await this.postTx(signedTx);
|
||||
if (isPostTxFailure(result)) {
|
||||
throw new Error(createPostTxErrorMessage(result));
|
||||
}
|
||||
return {
|
||||
logs: result.logs,
|
||||
transactionHash: result.transactionHash,
|
||||
};
|
||||
}
|
||||
|
||||
public async execute(
|
||||
contractAddress: string,
|
||||
handleMsg: object,
|
||||
|
4
packages/cosmwasm/src/testdata/contract.json
vendored
4
packages/cosmwasm/src/testdata/contract.json
vendored
File diff suppressed because one or more lines are too long
@ -31,9 +31,9 @@ export const bech32AddressMatcher = /^[\x21-\x7e]{1,83}1[02-9ac-hj-np-z]{38}$/;
|
||||
/** Deployed as part of scripts/wasmd/init.sh */
|
||||
export const deployedErc20 = {
|
||||
codeId: 1,
|
||||
source: "https://crates.io/api/v1/crates/cw-erc20/0.4.0/download",
|
||||
source: "https://crates.io/api/v1/crates/cw-erc20/0.5.1/download",
|
||||
builder: "cosmwasm/rust-optimizer:0.8.0",
|
||||
checksum: "41b3bafd7f9a3870bbfb0a0620508df564c52499cdcdc67bf9df72262f3958a6",
|
||||
checksum: "3e97bf88bd960fee5e5959c77b972eb2927690bc10160792741b174f105ec0c5",
|
||||
instances: [
|
||||
"cosmos18vd8fpwxzck93qlwghaj6arh4p7c5n89uzcee5", // HASH
|
||||
"cosmos1hqrdl6wstt8qzshwc6mrumpjk9338k0lr4dqxd", // ISA
|
||||
|
10
packages/cosmwasm/types/index.d.ts
vendored
10
packages/cosmwasm/types/index.d.ts
vendored
@ -22,17 +22,25 @@ export {
|
||||
export {
|
||||
ExecuteResult,
|
||||
FeeTable,
|
||||
InstantiateOptions,
|
||||
InstantiateResult,
|
||||
MigrateResult,
|
||||
SigningCallback,
|
||||
SigningCosmWasmClient,
|
||||
UploadMeta,
|
||||
UploadResult,
|
||||
} from "./signingcosmwasmclient";
|
||||
export {
|
||||
isMsgClearAdmin,
|
||||
isMsgExecuteContract,
|
||||
isMsgInstantiateContract,
|
||||
isMsgMigrateContract,
|
||||
isMsgUpdateAdmin,
|
||||
isMsgStoreCode,
|
||||
MsgStoreCode,
|
||||
MsgClearAdmin,
|
||||
MsgExecuteContract,
|
||||
MsgInstantiateContract,
|
||||
MsgMigrateContract,
|
||||
MsgUpdateAdmin,
|
||||
MsgStoreCode,
|
||||
} from "./msgs";
|
||||
|
70
packages/cosmwasm/types/msgs.d.ts
vendored
70
packages/cosmwasm/types/msgs.d.ts
vendored
@ -1,8 +1,9 @@
|
||||
import { Coin, Msg } from "@cosmjs/sdk38";
|
||||
/**
|
||||
* Uploads Wam code to the chain
|
||||
* Uploads Wasm code to the chain.
|
||||
* A numeric, auto-incrementing code ID will be generated as a result of the execution of this message.
|
||||
*
|
||||
* @see https://github.com/cosmwasm/wasmd/blob/9842678d89/x/wasm/internal/types/msg.go#L17
|
||||
* @see https://github.com/CosmWasm/wasmd/blob/v0.9.0-alpha4/x/wasm/internal/types/msg.go#L34
|
||||
*/
|
||||
export interface MsgStoreCode extends Msg {
|
||||
readonly type: "wasm/store-code";
|
||||
@ -17,10 +18,12 @@ export interface MsgStoreCode extends Msg {
|
||||
readonly builder: string;
|
||||
};
|
||||
}
|
||||
export declare function isMsgStoreCode(msg: Msg): msg is MsgStoreCode;
|
||||
/**
|
||||
* Creates an instance of contract that was uploaded before.
|
||||
* This will trigger a call to the "init" export.
|
||||
*
|
||||
* @see https://github.com/cosmwasm/wasmd/blob/9842678d89/x/wasm/internal/types/msg.go#L73
|
||||
* @see https://github.com/CosmWasm/wasmd/blob/v0.9.0-alpha4/x/wasm/internal/types/msg.go#L104
|
||||
*/
|
||||
export interface MsgInstantiateContract extends Msg {
|
||||
readonly type: "wasm/instantiate";
|
||||
@ -34,12 +37,48 @@ export interface MsgInstantiateContract extends Msg {
|
||||
/** Init message as JavaScript object */
|
||||
readonly init_msg: any;
|
||||
readonly init_funds: ReadonlyArray<Coin>;
|
||||
/** Bech32-encoded admin address */
|
||||
readonly admin?: string;
|
||||
};
|
||||
}
|
||||
export declare function isMsgInstantiateContract(msg: Msg): msg is MsgInstantiateContract;
|
||||
/**
|
||||
* Creates an instance of contract that was uploaded before.
|
||||
* Update the admin of a contract
|
||||
*
|
||||
* @see https://github.com/cosmwasm/wasmd/blob/9842678d89/x/wasm/internal/types/msg.go#L103
|
||||
* @see https://github.com/CosmWasm/wasmd/blob/v0.9.0-beta/x/wasm/internal/types/msg.go#L231
|
||||
*/
|
||||
export interface MsgUpdateAdmin extends Msg {
|
||||
readonly type: "wasm/update-contract-admin";
|
||||
readonly value: {
|
||||
/** Bech32-encoded sender address. This must be the old admin. */
|
||||
readonly sender: string;
|
||||
/** Bech32-encoded contract address to be updated */
|
||||
readonly contract: string;
|
||||
/** Bech32-encoded address of the new admin */
|
||||
readonly new_admin: string;
|
||||
};
|
||||
}
|
||||
export declare function isMsgUpdateAdmin(msg: Msg): msg is MsgUpdateAdmin;
|
||||
/**
|
||||
* Clears the admin of a contract, making it immutable.
|
||||
*
|
||||
* @see https://github.com/CosmWasm/wasmd/blob/v0.9.0-beta/x/wasm/internal/types/msg.go#L269
|
||||
*/
|
||||
export interface MsgClearAdmin extends Msg {
|
||||
readonly type: "wasm/clear-contract-admin";
|
||||
readonly value: {
|
||||
/** Bech32-encoded sender address. This must be the old admin. */
|
||||
readonly sender: string;
|
||||
/** Bech32-encoded contract address to be updated */
|
||||
readonly contract: string;
|
||||
};
|
||||
}
|
||||
export declare function isMsgClearAdmin(msg: Msg): msg is MsgClearAdmin;
|
||||
/**
|
||||
* Execute a smart contract.
|
||||
* This will trigger a call to the "handle" export.
|
||||
*
|
||||
* @see https://github.com/CosmWasm/wasmd/blob/v0.9.0-alpha4/x/wasm/internal/types/msg.go#L158
|
||||
*/
|
||||
export interface MsgExecuteContract extends Msg {
|
||||
readonly type: "wasm/execute";
|
||||
@ -53,6 +92,23 @@ export interface MsgExecuteContract extends Msg {
|
||||
readonly sent_funds: ReadonlyArray<Coin>;
|
||||
};
|
||||
}
|
||||
export declare function isMsgStoreCode(msg: Msg): msg is MsgStoreCode;
|
||||
export declare function isMsgInstantiateContract(msg: Msg): msg is MsgInstantiateContract;
|
||||
export declare function isMsgExecuteContract(msg: Msg): msg is MsgExecuteContract;
|
||||
/**
|
||||
* Migrates a contract to a new Wasm code.
|
||||
*
|
||||
* @see https://github.com/CosmWasm/wasmd/blob/v0.9.0-alpha4/x/wasm/internal/types/msg.go#L195
|
||||
*/
|
||||
export interface MsgMigrateContract extends Msg {
|
||||
readonly type: "wasm/migrate";
|
||||
readonly value: {
|
||||
/** Bech32 account address */
|
||||
readonly sender: string;
|
||||
/** Bech32 account address */
|
||||
readonly contract: string;
|
||||
/** The new code */
|
||||
readonly code_id: string;
|
||||
/** Migrate message as JavaScript object */
|
||||
readonly msg: any;
|
||||
};
|
||||
}
|
||||
export declare function isMsgMigrateContract(msg: Msg): msg is MsgMigrateContract;
|
||||
|
2
packages/cosmwasm/types/restclient.d.ts
vendored
2
packages/cosmwasm/types/restclient.d.ts
vendored
@ -18,6 +18,8 @@ export interface ContractInfo {
|
||||
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 {
|
||||
|
@ -8,7 +8,10 @@ export interface FeeTable {
|
||||
readonly upload: StdFee;
|
||||
readonly init: StdFee;
|
||||
readonly exec: StdFee;
|
||||
readonly migrate: StdFee;
|
||||
readonly send: StdFee;
|
||||
/** Paid when setting the contract admin to a new address or unsetting it */
|
||||
readonly changeAdmin: StdFee;
|
||||
}
|
||||
export interface UploadMeta {
|
||||
/** The source URL */
|
||||
@ -31,6 +34,19 @@ export interface UploadResult {
|
||||
/** Transaction hash (might be used as transaction ID). Guaranteed to be non-empty upper-case hex */
|
||||
readonly transactionHash: string;
|
||||
}
|
||||
/**
|
||||
* The options of an .instantiate() call.
|
||||
* All properties are optional.
|
||||
*/
|
||||
export interface InstantiateOptions {
|
||||
readonly memo?: string;
|
||||
readonly transferAmount?: readonly Coin[];
|
||||
/**
|
||||
* A bech32 encoded address of an admin account.
|
||||
* Caution: an admin has the privilege to upgrade a contract. If this is not desired, do not set this value.
|
||||
*/
|
||||
readonly admin?: string;
|
||||
}
|
||||
export interface InstantiateResult {
|
||||
/** The address of the newly instantiated contract */
|
||||
readonly contractAddress: string;
|
||||
@ -38,6 +54,19 @@ export interface InstantiateResult {
|
||||
/** Transaction hash (might be used as transaction ID). Guaranteed to be non-empty upper-case hex */
|
||||
readonly transactionHash: string;
|
||||
}
|
||||
/**
|
||||
* Result type of updateAdmin and clearAdmin
|
||||
*/
|
||||
export interface ChangeAdminResult {
|
||||
readonly logs: readonly Log[];
|
||||
/** Transaction hash (might be used as transaction ID). Guaranteed to be non-empty upper-case hex */
|
||||
readonly transactionHash: string;
|
||||
}
|
||||
export interface MigrateResult {
|
||||
readonly logs: readonly Log[];
|
||||
/** Transaction hash (might be used as transaction ID). Guaranteed to be non-empty upper-case hex */
|
||||
readonly transactionHash: string;
|
||||
}
|
||||
export interface ExecuteResult {
|
||||
readonly logs: readonly Log[];
|
||||
/** Transaction hash (might be used as transaction ID). Guaranteed to be non-empty upper-case hex */
|
||||
@ -74,9 +103,11 @@ export declare class SigningCosmWasmClient extends CosmWasmClient {
|
||||
codeId: number,
|
||||
initMsg: object,
|
||||
label: string,
|
||||
memo?: string,
|
||||
transferAmount?: readonly Coin[],
|
||||
options?: InstantiateOptions,
|
||||
): Promise<InstantiateResult>;
|
||||
updateAdmin(contractAddress: string, newAdmin: string, memo?: string): Promise<ChangeAdminResult>;
|
||||
clearAdmin(contractAddress: string, memo?: string): Promise<ChangeAdminResult>;
|
||||
migrate(contractAddress: string, codeId: number, migrateMsg: object, memo?: string): Promise<MigrateResult>;
|
||||
execute(
|
||||
contractAddress: string,
|
||||
handleMsg: object,
|
||||
|
@ -192,7 +192,7 @@ describe("RestClient", () => {
|
||||
client_name: "wasmcli",
|
||||
version: jasmine.stringMatching(semverMatcher),
|
||||
commit: jasmine.stringMatching(tendermintShortHashMatcher),
|
||||
build_tags: "netgo,ledger",
|
||||
build_tags: "netgo,ledger,muslc",
|
||||
go: jasmine.stringMatching(/^go version go1\.[0-9]+\.[0-9]+ linux\/amd64$/),
|
||||
});
|
||||
});
|
||||
|
@ -1,3 +1,3 @@
|
||||
41b3bafd7f9a3870bbfb0a0620508df564c52499cdcdc67bf9df72262f3958a6 cw-erc20.wasm
|
||||
f4bba4289d150c0348ec42444fa142edd22ce5a024ad3314405c70314f3eb0fc cw-nameservice.wasm
|
||||
0f08a890443dbf644f61a7dc3aa7b2a03e9d142dd1b718aa8b7f8a80b886bff1 staking.wasm
|
||||
3e97bf88bd960fee5e5959c77b972eb2927690bc10160792741b174f105ec0c5 cw-erc20.wasm
|
||||
851aa8bbc76bc2326a38b99e1432bb06a8ed36442a68e9e676d10ed8beedd1d1 cw-nameservice.wasm
|
||||
44397b14c9ec35b3188d16b5ed46de2fb6397d7bf2d1f2755a9970054aa7abb0 staking.wasm
|
||||
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -22,7 +22,7 @@ const guest = {
|
||||
};
|
||||
|
||||
const codeMeta = {
|
||||
source: "https://crates.io/api/v1/crates/cw-erc20/0.4.0/download",
|
||||
source: "https://crates.io/api/v1/crates/cw-erc20/0.5.1/download",
|
||||
builder: "cosmwasm/rust-optimizer:0.8.0",
|
||||
};
|
||||
|
||||
@ -133,8 +133,9 @@ async function main() {
|
||||
console.info(`Upload succeeded. Receipt: ${JSON.stringify(uploadReceipt)}`);
|
||||
|
||||
for (const initMsg of [initMsgHash, initMsgIsa, initMsgJade]) {
|
||||
const memo = `Create an ERC20 instance for ${initMsg.symbol}`;
|
||||
const { contractAddress } = await client.instantiate(uploadReceipt.codeId, initMsg, initMsg.symbol, memo);
|
||||
const { contractAddress } = await client.instantiate(uploadReceipt.codeId, initMsg, initMsg.symbol, {
|
||||
memo: `Create an ERC20 instance for ${initMsg.symbol}`,
|
||||
});
|
||||
console.info(`Contract instantiated for ${initMsg.symbol} at ${contractAddress}`);
|
||||
}
|
||||
}
|
||||
|
@ -12,7 +12,7 @@ const alice = {
|
||||
};
|
||||
|
||||
const codeMeta = {
|
||||
source: "https://crates.io/api/v1/crates/cw-nameservice/0.4.0/download",
|
||||
source: "https://crates.io/api/v1/crates/cw-nameservice/0.5.1/download",
|
||||
builder: "cosmwasm/rust-optimizer:0.8.0",
|
||||
};
|
||||
|
||||
@ -44,8 +44,9 @@ async function main() {
|
||||
console.info(`Upload succeeded. Receipt: ${JSON.stringify(uploadReceipt)}`);
|
||||
|
||||
for (const { label, initMsg } of [free, luxury]) {
|
||||
const memo = `Create an nameservice instance "${label}"`;
|
||||
const { contractAddress } = await client.instantiate(uploadReceipt.codeId, initMsg, label, memo);
|
||||
const { contractAddress } = await client.instantiate(uploadReceipt.codeId, initMsg, label, {
|
||||
memo: `Create an nameservice instance "${label}"`,
|
||||
});
|
||||
console.info(`Contract "${label}" instantiated at ${contractAddress}`);
|
||||
}
|
||||
}
|
||||
|
@ -2,7 +2,7 @@
|
||||
|
||||
/* eslint-disable @typescript-eslint/camelcase */
|
||||
const { SigningCosmWasmClient } = require("@cosmjs/cosmwasm");
|
||||
const { Secp256k1Pen } = require("@cosmjs/sdk38");
|
||||
const { coins, Secp256k1Pen } = require("@cosmjs/sdk38");
|
||||
const fs = require("fs");
|
||||
|
||||
const httpUrl = "http://localhost:1317";
|
||||
@ -30,17 +30,25 @@ const bounty = {
|
||||
},
|
||||
};
|
||||
|
||||
const fees = {
|
||||
upload: {
|
||||
amount: coins(25000, "ucosm"),
|
||||
gas: "1500000", // 1.5 million
|
||||
},
|
||||
};
|
||||
|
||||
async function main() {
|
||||
const pen = await Secp256k1Pen.fromMnemonic(alice.mnemonic);
|
||||
const client = new SigningCosmWasmClient(httpUrl, alice.address0, (signBytes) => pen.sign(signBytes));
|
||||
const client = new SigningCosmWasmClient(httpUrl, alice.address0, (signBytes) => pen.sign(signBytes), fees);
|
||||
|
||||
const wasm = fs.readFileSync(__dirname + "/contracts/staking.wasm");
|
||||
const uploadReceipt = await client.upload(wasm, codeMeta, "Upload Staking code");
|
||||
console.info(`Upload succeeded. Receipt: ${JSON.stringify(uploadReceipt)}`);
|
||||
|
||||
for (const { label, initMsg } of [bounty]) {
|
||||
const memo = `Create an staking instance "${label}"`;
|
||||
const { contractAddress } = await client.instantiate(uploadReceipt.codeId, initMsg, label, memo);
|
||||
const { contractAddress } = await client.instantiate(uploadReceipt.codeId, initMsg, label, {
|
||||
memo: `Create an staking instance "${label}"`,
|
||||
});
|
||||
console.info(`Contract "${label}" instantiated at ${contractAddress}`);
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
# Choose from https://hub.docker.com/r/cosmwasm/wasmd-demo/tags
|
||||
REPOSITORY="cosmwasm/wasmd-demo"
|
||||
VERSION="v0.8.0"
|
||||
# Choose from https://hub.docker.com/r/cosmwasm/wasmd/tags
|
||||
REPOSITORY="cosmwasm/wasmd"
|
||||
VERSION="v0.9.0-beta"
|
||||
|
||||
CONTAINER_NAME="wasmd"
|
||||
|
Loading…
x
Reference in New Issue
Block a user