Merge pull request #535 from cosmos/416-multisig
Add CW3 multisig support
This commit is contained in:
commit
fb79fd5e75
323
packages/cosmwasm/src/cw3cosmwasmclient.spec.ts
Normal file
323
packages/cosmwasm/src/cw3cosmwasmclient.spec.ts
Normal file
@ -0,0 +1,323 @@
|
||||
/* eslint-disable @typescript-eslint/naming-convention */
|
||||
import { makeCosmoshubPath, Secp256k1HdWallet } from "@cosmjs/launchpad";
|
||||
import { assert, sleep } from "@cosmjs/utils";
|
||||
|
||||
import { Cw3CosmWasmClient, Vote } from "./cw3cosmwasmclient";
|
||||
import {
|
||||
alice,
|
||||
deployedCw3,
|
||||
launchpad,
|
||||
makeRandomAddress,
|
||||
pendingWithoutCw3,
|
||||
pendingWithoutLaunchpad,
|
||||
} from "./testutils.spec";
|
||||
|
||||
describe("Cw3CosmWasmClient", () => {
|
||||
describe("constructor", () => {
|
||||
it("can be constructed", async () => {
|
||||
const wallet = await Secp256k1HdWallet.fromMnemonic(alice.mnemonic);
|
||||
const client = new Cw3CosmWasmClient(
|
||||
launchpad.endpoint,
|
||||
alice.address0,
|
||||
wallet,
|
||||
deployedCw3.instances[0],
|
||||
);
|
||||
expect(client).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
||||
describe("queries", () => {
|
||||
const contractAddress = deployedCw3.instances[0];
|
||||
const toAddress = makeRandomAddress();
|
||||
const msg = {
|
||||
bank: {
|
||||
send: {
|
||||
from_address: contractAddress,
|
||||
to_address: toAddress,
|
||||
amount: [
|
||||
{
|
||||
amount: "1",
|
||||
denom: "ucosm",
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
};
|
||||
let proposalId: number;
|
||||
let expirationHeight: number;
|
||||
|
||||
beforeAll(async () => {
|
||||
const wallet = await Secp256k1HdWallet.fromMnemonic(alice.mnemonic);
|
||||
const client = new Cw3CosmWasmClient(launchpad.endpoint, alice.address0, wallet, contractAddress);
|
||||
const currentHeight = await client.getHeight();
|
||||
expirationHeight = currentHeight + 1;
|
||||
const { logs } = await client.createMultisigProposal(
|
||||
"My proposal",
|
||||
"A proposal to propose proposing proposals",
|
||||
[msg],
|
||||
undefined,
|
||||
{ at_height: expirationHeight },
|
||||
);
|
||||
const wasmEvents = logs[0].events.find((event) => event.type === "wasm");
|
||||
assert(wasmEvents, "Wasm events not found in logs");
|
||||
const proposalIdAttribute = wasmEvents.attributes.find((log) => log.key === "proposal_id");
|
||||
assert(proposalIdAttribute, "Proposal ID not found in logs");
|
||||
proposalId = parseInt(proposalIdAttribute.value, 10);
|
||||
});
|
||||
|
||||
it("getThreshold", async () => {
|
||||
pendingWithoutLaunchpad();
|
||||
pendingWithoutCw3();
|
||||
|
||||
const wallet = await Secp256k1HdWallet.fromMnemonic(alice.mnemonic);
|
||||
const client = new Cw3CosmWasmClient(
|
||||
launchpad.endpoint,
|
||||
alice.address0,
|
||||
wallet,
|
||||
deployedCw3.instances[0],
|
||||
);
|
||||
const result = await client.getThreshold();
|
||||
|
||||
expect(result).toEqual({ absolute_count: { weight_needed: 1, total_weight: 3 } });
|
||||
});
|
||||
|
||||
it("getProposal", async () => {
|
||||
pendingWithoutLaunchpad();
|
||||
pendingWithoutCw3();
|
||||
|
||||
const wallet = await Secp256k1HdWallet.fromMnemonic(alice.mnemonic);
|
||||
const client = new Cw3CosmWasmClient(launchpad.endpoint, alice.address0, wallet, contractAddress);
|
||||
const result = await client.getProposal(proposalId);
|
||||
|
||||
expect(result).toEqual({
|
||||
id: proposalId,
|
||||
title: "My proposal",
|
||||
description: "A proposal to propose proposing proposals",
|
||||
msgs: [msg],
|
||||
expires: { at_height: expirationHeight },
|
||||
status: "passed",
|
||||
});
|
||||
});
|
||||
|
||||
it("listProposals", async () => {
|
||||
pendingWithoutLaunchpad();
|
||||
pendingWithoutCw3();
|
||||
|
||||
const wallet = await Secp256k1HdWallet.fromMnemonic(alice.mnemonic);
|
||||
const client = new Cw3CosmWasmClient(launchpad.endpoint, alice.address0, wallet, contractAddress);
|
||||
const result = await client.listProposals({ startAfter: proposalId - 1, limit: 1 });
|
||||
|
||||
expect(result).toEqual({
|
||||
proposals: [
|
||||
{
|
||||
id: proposalId,
|
||||
title: "My proposal",
|
||||
description: "A proposal to propose proposing proposals",
|
||||
msgs: [msg],
|
||||
expires: { at_height: expirationHeight },
|
||||
status: "passed",
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
it("reverseProposals", async () => {
|
||||
pendingWithoutLaunchpad();
|
||||
pendingWithoutCw3();
|
||||
|
||||
const wallet = await Secp256k1HdWallet.fromMnemonic(alice.mnemonic);
|
||||
const client = new Cw3CosmWasmClient(launchpad.endpoint, alice.address0, wallet, contractAddress);
|
||||
const result = await client.reverseProposals({ limit: 1 });
|
||||
|
||||
expect(result).toEqual({
|
||||
proposals: [
|
||||
{
|
||||
id: proposalId,
|
||||
title: "My proposal",
|
||||
description: "A proposal to propose proposing proposals",
|
||||
msgs: [msg],
|
||||
expires: { at_height: expirationHeight },
|
||||
status: "passed",
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
it("getVote", async () => {
|
||||
pendingWithoutLaunchpad();
|
||||
pendingWithoutCw3();
|
||||
|
||||
const wallet = await Secp256k1HdWallet.fromMnemonic(alice.mnemonic);
|
||||
const client = new Cw3CosmWasmClient(launchpad.endpoint, alice.address0, wallet, contractAddress);
|
||||
const result = await client.getVote(proposalId, alice.address0);
|
||||
|
||||
expect(result).toEqual({ vote: Vote.Yes });
|
||||
});
|
||||
|
||||
it("listVotes", async () => {
|
||||
pendingWithoutLaunchpad();
|
||||
pendingWithoutCw3();
|
||||
|
||||
const wallet = await Secp256k1HdWallet.fromMnemonic(alice.mnemonic);
|
||||
const client = new Cw3CosmWasmClient(launchpad.endpoint, alice.address0, wallet, contractAddress);
|
||||
const result = await client.listVotes(proposalId);
|
||||
|
||||
expect(result).toEqual({ votes: [{ voter: alice.address0, vote: Vote.Yes, weight: 1 }] });
|
||||
});
|
||||
|
||||
it("getVoter", async () => {
|
||||
pendingWithoutLaunchpad();
|
||||
pendingWithoutCw3();
|
||||
|
||||
const wallet = await Secp256k1HdWallet.fromMnemonic(alice.mnemonic);
|
||||
const client = new Cw3CosmWasmClient(launchpad.endpoint, alice.address0, wallet, contractAddress);
|
||||
const result = await client.getVoter(alice.address0);
|
||||
|
||||
expect(result).toEqual({ addr: alice.address0, weight: 1 });
|
||||
});
|
||||
|
||||
it("listVoters", async () => {
|
||||
pendingWithoutLaunchpad();
|
||||
pendingWithoutCw3();
|
||||
|
||||
const wallet = await Secp256k1HdWallet.fromMnemonic(alice.mnemonic);
|
||||
const client = new Cw3CosmWasmClient(launchpad.endpoint, alice.address0, wallet, contractAddress);
|
||||
const result = await client.listVoters();
|
||||
|
||||
expect(result.voters.length).toEqual(3);
|
||||
expect(result.voters).toEqual(
|
||||
jasmine.arrayContaining([
|
||||
{ addr: alice.address0, weight: 1 },
|
||||
{ addr: alice.address1, weight: 1 },
|
||||
{ addr: alice.address2, weight: 1 },
|
||||
]),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("Proposal lifecycle", () => {
|
||||
it("proposal is accepted (proposer has enough weight alone)", async () => {
|
||||
pendingWithoutLaunchpad();
|
||||
pendingWithoutCw3();
|
||||
const contractAddress = deployedCw3.instances[0];
|
||||
const wallet = await Secp256k1HdWallet.fromMnemonic(alice.mnemonic);
|
||||
const client = new Cw3CosmWasmClient(launchpad.endpoint, alice.address0, wallet, contractAddress);
|
||||
const toAddress = makeRandomAddress();
|
||||
const msg = {
|
||||
bank: {
|
||||
send: {
|
||||
from_address: contractAddress,
|
||||
to_address: toAddress,
|
||||
amount: [
|
||||
{
|
||||
amount: "1",
|
||||
denom: "ucosm",
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
};
|
||||
await client.createMultisigProposal("My proposal", "A proposal to propose proposing proposals", [msg]);
|
||||
const { proposals } = await client.reverseProposals({ limit: 1 });
|
||||
const proposalId = proposals[0].id;
|
||||
const executeResult = await client.executeMultisigProposal(proposalId);
|
||||
expect(executeResult).toBeTruthy();
|
||||
});
|
||||
|
||||
it("proposal is accepted (proposer does not have enough weight alone)", async () => {
|
||||
pendingWithoutLaunchpad();
|
||||
pendingWithoutCw3();
|
||||
const contractAddress = deployedCw3.instances[1];
|
||||
const proposerWallet = await Secp256k1HdWallet.fromMnemonic(alice.mnemonic);
|
||||
const proposer = new Cw3CosmWasmClient(
|
||||
launchpad.endpoint,
|
||||
alice.address0,
|
||||
proposerWallet,
|
||||
contractAddress,
|
||||
);
|
||||
const voterWallet = await Secp256k1HdWallet.fromMnemonic(alice.mnemonic, makeCosmoshubPath(1));
|
||||
const voter = new Cw3CosmWasmClient(launchpad.endpoint, alice.address1, voterWallet, contractAddress);
|
||||
const toAddress = makeRandomAddress();
|
||||
const msg = {
|
||||
bank: {
|
||||
send: {
|
||||
from_address: contractAddress,
|
||||
to_address: toAddress,
|
||||
amount: [
|
||||
{
|
||||
amount: "1",
|
||||
denom: "ucosm",
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
};
|
||||
await proposer.createMultisigProposal("My proposal", "A proposal to propose proposing proposals", [
|
||||
msg,
|
||||
]);
|
||||
const { proposals } = await voter.reverseProposals({ limit: 1 });
|
||||
const proposalId = proposals[0].id;
|
||||
const voteResult = await voter.voteMultisigProposal(proposalId, Vote.Yes);
|
||||
expect(voteResult).toBeTruthy();
|
||||
|
||||
const executeResult = await proposer.executeMultisigProposal(proposalId);
|
||||
expect(executeResult).toBeTruthy();
|
||||
});
|
||||
|
||||
it("proposal is rejected", async () => {
|
||||
pendingWithoutLaunchpad();
|
||||
pendingWithoutCw3();
|
||||
const contractAddress = deployedCw3.instances[1];
|
||||
const proposerWallet = await Secp256k1HdWallet.fromMnemonic(alice.mnemonic);
|
||||
const proposer = new Cw3CosmWasmClient(
|
||||
launchpad.endpoint,
|
||||
alice.address0,
|
||||
proposerWallet,
|
||||
contractAddress,
|
||||
);
|
||||
const voter1Wallet = await Secp256k1HdWallet.fromMnemonic(alice.mnemonic, makeCosmoshubPath(1));
|
||||
const voter1 = new Cw3CosmWasmClient(launchpad.endpoint, alice.address1, voter1Wallet, contractAddress);
|
||||
const voter2Wallet = await Secp256k1HdWallet.fromMnemonic(alice.mnemonic, makeCosmoshubPath(2));
|
||||
const voter2 = new Cw3CosmWasmClient(launchpad.endpoint, alice.address2, voter2Wallet, contractAddress);
|
||||
const toAddress = makeRandomAddress();
|
||||
const msg = {
|
||||
bank: {
|
||||
send: {
|
||||
from_address: contractAddress,
|
||||
to_address: toAddress,
|
||||
amount: [
|
||||
{
|
||||
amount: "1",
|
||||
denom: "ucosm",
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
};
|
||||
const currentHeight = await proposer.getHeight();
|
||||
await proposer.createMultisigProposal(
|
||||
"My proposal",
|
||||
"A proposal to propose proposing proposals",
|
||||
[msg],
|
||||
{
|
||||
at_height: currentHeight,
|
||||
},
|
||||
{
|
||||
at_height: currentHeight + 5,
|
||||
},
|
||||
);
|
||||
const { proposals } = await voter1.reverseProposals({ limit: 1 });
|
||||
const proposalId = proposals[0].id;
|
||||
|
||||
const vote1Result = await voter1.voteMultisigProposal(proposalId, Vote.Abstain);
|
||||
expect(vote1Result).toBeTruthy();
|
||||
const vote2Result = await voter2.voteMultisigProposal(proposalId, Vote.No);
|
||||
expect(vote2Result).toBeTruthy();
|
||||
|
||||
await sleep(2000);
|
||||
|
||||
const closeResult = await proposer.closeMultisigProposal(proposalId);
|
||||
expect(closeResult).toBeTruthy();
|
||||
});
|
||||
});
|
||||
});
|
||||
197
packages/cosmwasm/src/cw3cosmwasmclient.ts
Normal file
197
packages/cosmwasm/src/cw3cosmwasmclient.ts
Normal file
@ -0,0 +1,197 @@
|
||||
/* eslint-disable @typescript-eslint/naming-convention */
|
||||
import { BroadcastMode, GasLimits, GasPrice, OfflineSigner } from "@cosmjs/launchpad";
|
||||
|
||||
import { CosmWasmFeeTable, ExecuteResult, SigningCosmWasmClient } from "./signingcosmwasmclient";
|
||||
|
||||
export type Expiration =
|
||||
| {
|
||||
readonly at_height: number;
|
||||
}
|
||||
| {
|
||||
readonly at_time: number;
|
||||
};
|
||||
|
||||
export enum Vote {
|
||||
Yes = "yes",
|
||||
No = "no",
|
||||
Abstain = "abstain",
|
||||
Veto = "veto",
|
||||
}
|
||||
|
||||
export interface ThresholdResult {
|
||||
readonly absolute_count: {
|
||||
readonly weight_needed: number;
|
||||
readonly total_weight: number;
|
||||
};
|
||||
}
|
||||
|
||||
export interface ProposalResult {
|
||||
readonly id: number;
|
||||
readonly title: string;
|
||||
readonly description: string;
|
||||
readonly msgs: ReadonlyArray<Record<string, unknown>>;
|
||||
readonly expires: Expiration;
|
||||
readonly status: string;
|
||||
}
|
||||
|
||||
export interface ProposalsResult {
|
||||
readonly proposals: readonly ProposalResult[];
|
||||
}
|
||||
|
||||
export interface VoteResult {
|
||||
readonly vote: Vote;
|
||||
}
|
||||
|
||||
export interface VotesResult {
|
||||
readonly votes: ReadonlyArray<{ readonly vote: Vote; readonly voter: string; readonly weight: number }>;
|
||||
}
|
||||
|
||||
export interface VoterResult {
|
||||
readonly addr: string;
|
||||
readonly weight: number;
|
||||
}
|
||||
|
||||
export interface VotersResult {
|
||||
readonly voters: readonly VoterResult[];
|
||||
}
|
||||
|
||||
interface StartBeforeNumberPaginationOptions {
|
||||
readonly startBefore?: number;
|
||||
readonly limit?: number;
|
||||
}
|
||||
|
||||
interface StartAfterNumberPaginationOptions {
|
||||
readonly startAfter?: number;
|
||||
readonly limit?: number;
|
||||
}
|
||||
|
||||
interface StartAfterStringPaginationOptions {
|
||||
readonly startAfter?: string;
|
||||
readonly limit?: number;
|
||||
}
|
||||
|
||||
export class Cw3CosmWasmClient extends SigningCosmWasmClient {
|
||||
private readonly cw3ContractAddress: string;
|
||||
|
||||
public constructor(
|
||||
apiUrl: string,
|
||||
senderAddress: string,
|
||||
signer: OfflineSigner,
|
||||
cw3ContractAddress: string,
|
||||
gasPrice?: GasPrice,
|
||||
gasLimits?: Partial<GasLimits<CosmWasmFeeTable>>,
|
||||
broadcastMode?: BroadcastMode,
|
||||
) {
|
||||
super(apiUrl, senderAddress, signer, gasPrice, gasLimits, broadcastMode);
|
||||
this.cw3ContractAddress = cw3ContractAddress;
|
||||
}
|
||||
|
||||
public getThreshold(): Promise<ThresholdResult> {
|
||||
return this.queryContractSmart(this.cw3ContractAddress, { threshold: {} });
|
||||
}
|
||||
|
||||
public getProposal(proposalId: number): Promise<ProposalResult> {
|
||||
return this.queryContractSmart(this.cw3ContractAddress, { proposal: { proposal_id: proposalId } });
|
||||
}
|
||||
|
||||
public listProposals({ startAfter, limit }: StartAfterNumberPaginationOptions = {}): Promise<
|
||||
ProposalsResult
|
||||
> {
|
||||
return this.queryContractSmart(this.cw3ContractAddress, {
|
||||
list_proposals: {
|
||||
start_after: startAfter,
|
||||
limit: limit,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
public reverseProposals({ startBefore, limit }: StartBeforeNumberPaginationOptions = {}): Promise<
|
||||
ProposalsResult
|
||||
> {
|
||||
return this.queryContractSmart(this.cw3ContractAddress, {
|
||||
reverse_proposals: {
|
||||
start_before: startBefore,
|
||||
limit: limit,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
public getVote(proposalId: number, voter: string): Promise<VoteResult> {
|
||||
return this.queryContractSmart(this.cw3ContractAddress, {
|
||||
vote: {
|
||||
proposal_id: proposalId,
|
||||
voter: voter,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
public listVotes(
|
||||
proposalId: number,
|
||||
{ startAfter, limit }: StartAfterStringPaginationOptions = {},
|
||||
): Promise<VotesResult> {
|
||||
return this.queryContractSmart(this.cw3ContractAddress, {
|
||||
list_votes: {
|
||||
proposal_id: proposalId,
|
||||
start_after: startAfter,
|
||||
limit: limit,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
public getVoter(address: string): Promise<VoterResult> {
|
||||
return this.queryContractSmart(this.cw3ContractAddress, {
|
||||
voter: {
|
||||
address: address,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
public listVoters({ startAfter, limit }: StartAfterStringPaginationOptions = {}): Promise<VotersResult> {
|
||||
return this.queryContractSmart(this.cw3ContractAddress, {
|
||||
list_voters: {
|
||||
start_after: startAfter,
|
||||
limit: limit,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
public createMultisigProposal(
|
||||
title: string,
|
||||
description: string,
|
||||
msgs: ReadonlyArray<Record<string, unknown>>,
|
||||
earliest?: Expiration,
|
||||
latest?: Expiration,
|
||||
memo = "",
|
||||
): Promise<ExecuteResult> {
|
||||
const handleMsg = {
|
||||
propose: {
|
||||
title: title,
|
||||
description: description,
|
||||
msgs: msgs,
|
||||
earliest: earliest,
|
||||
latest: latest,
|
||||
},
|
||||
};
|
||||
return this.execute(this.cw3ContractAddress, handleMsg, memo);
|
||||
}
|
||||
|
||||
public voteMultisigProposal(proposalId: number, vote: Vote, memo = ""): Promise<ExecuteResult> {
|
||||
const handleMsg = {
|
||||
vote: {
|
||||
proposal_id: proposalId,
|
||||
vote: vote,
|
||||
},
|
||||
};
|
||||
return this.execute(this.cw3ContractAddress, handleMsg, memo);
|
||||
}
|
||||
|
||||
public executeMultisigProposal(proposalId: number, memo = ""): Promise<ExecuteResult> {
|
||||
const handleMsg = { execute: { proposal_id: proposalId } };
|
||||
return this.execute(this.cw3ContractAddress, handleMsg, memo);
|
||||
}
|
||||
|
||||
public closeMultisigProposal(proposalId: number, memo = ""): Promise<ExecuteResult> {
|
||||
const handleMsg = { close: { proposal_id: proposalId } };
|
||||
return this.execute(this.cw3ContractAddress, handleMsg, memo);
|
||||
}
|
||||
}
|
||||
@ -16,6 +16,18 @@ export {
|
||||
SearchTxQuery,
|
||||
SearchTxFilter,
|
||||
} from "./cosmwasmclient";
|
||||
export {
|
||||
Cw3CosmWasmClient,
|
||||
Expiration,
|
||||
ProposalResult,
|
||||
ProposalsResult,
|
||||
ThresholdResult,
|
||||
Vote,
|
||||
VoteResult,
|
||||
VotesResult,
|
||||
VoterResult,
|
||||
VotersResult,
|
||||
} from "./cw3cosmwasmclient";
|
||||
export {
|
||||
ExecuteResult,
|
||||
CosmWasmFeeTable,
|
||||
|
||||
@ -91,6 +91,18 @@ export const deployedErc20 = {
|
||||
],
|
||||
};
|
||||
|
||||
/** Deployed as part of scripts/launchpad/init.sh */
|
||||
export const deployedCw3 = {
|
||||
codeId: 3,
|
||||
source: "https://crates.io/api/v1/crates/cw3-fixed-multisig/0.3.1/download",
|
||||
builder: "cosmwasm/rust-optimizer:0.10.4",
|
||||
instances: [
|
||||
"cosmos1xqeym28j9xgv0p93pwwt6qcxf9tdvf9zddufdw", // Multisig (1/3)
|
||||
"cosmos1jka38ckju8cpjap00jf9xdvdyttz9caujtd6t5", // Multisig (2/3)
|
||||
"cosmos12dnl585uxzddjw9hw4ca694f054shgpgr4zg80", // Multisig (uneven weights)
|
||||
],
|
||||
};
|
||||
|
||||
export const launchpad = {
|
||||
endpoint: "http://localhost:1317",
|
||||
chainId: "testing",
|
||||
@ -119,6 +131,16 @@ export function pendingWithoutErc20(): void {
|
||||
}
|
||||
}
|
||||
|
||||
export function cw3Enabled(): boolean {
|
||||
return !!process.env.CW3_ENABLED;
|
||||
}
|
||||
|
||||
export function pendingWithoutCw3(): void {
|
||||
if (!cw3Enabled()) {
|
||||
return pending("Set CW3_ENABLED to enable CW3-based tests");
|
||||
}
|
||||
}
|
||||
|
||||
/** Returns first element. Throws if array has a different length than 1. */
|
||||
export function fromOneElementArray<T>(elements: ArrayLike<T>): T {
|
||||
if (elements.length !== 1) throw new Error(`Expected exactly one element but got ${elements.length}`);
|
||||
|
||||
96
packages/cosmwasm/types/cw3cosmwasmclient.d.ts
vendored
Normal file
96
packages/cosmwasm/types/cw3cosmwasmclient.d.ts
vendored
Normal file
@ -0,0 +1,96 @@
|
||||
import { BroadcastMode, GasLimits, GasPrice, OfflineSigner } from "@cosmjs/launchpad";
|
||||
import { CosmWasmFeeTable, ExecuteResult, SigningCosmWasmClient } from "./signingcosmwasmclient";
|
||||
export declare type Expiration =
|
||||
| {
|
||||
readonly at_height: number;
|
||||
}
|
||||
| {
|
||||
readonly at_time: number;
|
||||
};
|
||||
export declare enum Vote {
|
||||
Yes = "yes",
|
||||
No = "no",
|
||||
Abstain = "abstain",
|
||||
Veto = "veto",
|
||||
}
|
||||
export interface ThresholdResult {
|
||||
readonly absolute_count: {
|
||||
readonly weight_needed: number;
|
||||
readonly total_weight: number;
|
||||
};
|
||||
}
|
||||
export interface ProposalResult {
|
||||
readonly id: number;
|
||||
readonly title: string;
|
||||
readonly description: string;
|
||||
readonly msgs: ReadonlyArray<Record<string, unknown>>;
|
||||
readonly expires: Expiration;
|
||||
readonly status: string;
|
||||
}
|
||||
export interface ProposalsResult {
|
||||
readonly proposals: readonly ProposalResult[];
|
||||
}
|
||||
export interface VoteResult {
|
||||
readonly vote: Vote;
|
||||
}
|
||||
export interface VotesResult {
|
||||
readonly votes: ReadonlyArray<{
|
||||
readonly vote: Vote;
|
||||
readonly voter: string;
|
||||
readonly weight: number;
|
||||
}>;
|
||||
}
|
||||
export interface VoterResult {
|
||||
readonly addr: string;
|
||||
readonly weight: number;
|
||||
}
|
||||
export interface VotersResult {
|
||||
readonly voters: readonly VoterResult[];
|
||||
}
|
||||
interface StartBeforeNumberPaginationOptions {
|
||||
readonly startBefore?: number;
|
||||
readonly limit?: number;
|
||||
}
|
||||
interface StartAfterNumberPaginationOptions {
|
||||
readonly startAfter?: number;
|
||||
readonly limit?: number;
|
||||
}
|
||||
interface StartAfterStringPaginationOptions {
|
||||
readonly startAfter?: string;
|
||||
readonly limit?: number;
|
||||
}
|
||||
export declare class Cw3CosmWasmClient extends SigningCosmWasmClient {
|
||||
private readonly cw3ContractAddress;
|
||||
constructor(
|
||||
apiUrl: string,
|
||||
senderAddress: string,
|
||||
signer: OfflineSigner,
|
||||
cw3ContractAddress: string,
|
||||
gasPrice?: GasPrice,
|
||||
gasLimits?: Partial<GasLimits<CosmWasmFeeTable>>,
|
||||
broadcastMode?: BroadcastMode,
|
||||
);
|
||||
getThreshold(): Promise<ThresholdResult>;
|
||||
getProposal(proposalId: number): Promise<ProposalResult>;
|
||||
listProposals({ startAfter, limit }?: StartAfterNumberPaginationOptions): Promise<ProposalsResult>;
|
||||
reverseProposals({ startBefore, limit }?: StartBeforeNumberPaginationOptions): Promise<ProposalsResult>;
|
||||
getVote(proposalId: number, voter: string): Promise<VoteResult>;
|
||||
listVotes(
|
||||
proposalId: number,
|
||||
{ startAfter, limit }?: StartAfterStringPaginationOptions,
|
||||
): Promise<VotesResult>;
|
||||
getVoter(address: string): Promise<VoterResult>;
|
||||
listVoters({ startAfter, limit }?: StartAfterStringPaginationOptions): Promise<VotersResult>;
|
||||
createMultisigProposal(
|
||||
title: string,
|
||||
description: string,
|
||||
msgs: ReadonlyArray<Record<string, unknown>>,
|
||||
earliest?: Expiration,
|
||||
latest?: Expiration,
|
||||
memo?: string,
|
||||
): Promise<ExecuteResult>;
|
||||
voteMultisigProposal(proposalId: number, vote: Vote, memo?: string): Promise<ExecuteResult>;
|
||||
executeMultisigProposal(proposalId: number, memo?: string): Promise<ExecuteResult>;
|
||||
closeMultisigProposal(proposalId: number, memo?: string): Promise<ExecuteResult>;
|
||||
}
|
||||
export {};
|
||||
12
packages/cosmwasm/types/index.d.ts
vendored
12
packages/cosmwasm/types/index.d.ts
vendored
@ -16,6 +16,18 @@ export {
|
||||
SearchTxQuery,
|
||||
SearchTxFilter,
|
||||
} from "./cosmwasmclient";
|
||||
export {
|
||||
Cw3CosmWasmClient,
|
||||
Expiration,
|
||||
ProposalResult,
|
||||
ProposalsResult,
|
||||
ThresholdResult,
|
||||
Vote,
|
||||
VoteResult,
|
||||
VotesResult,
|
||||
VoterResult,
|
||||
VotersResult,
|
||||
} from "./cw3cosmwasmclient";
|
||||
export {
|
||||
ExecuteResult,
|
||||
CosmWasmFeeTable,
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
1a4a376ef1099ad3edc33aa1d3105e4621bc49e44b1ac0a449d7b6912e40fb0a cw3_fixed_multisig.wasm
|
||||
ebc2b11e2afa50d5dcd4234840cd581e948a59d888bb8d651598bba3732cd8ee cw-nameservice.wasm
|
||||
d04368320ad55089384adb171aaea39e43d710d7608829adba0300ed30aa2988 cw_erc20.wasm
|
||||
3defc33a41f58c71d38b176d521c411d8e74d26403fde7660486930c7579a016 hackatom.wasm
|
||||
|
||||
BIN
scripts/launchpad/contracts/cw3_fixed_multisig.wasm
(Stored with Git LFS)
Normal file
BIN
scripts/launchpad/contracts/cw3_fixed_multisig.wasm
(Stored with Git LFS)
Normal file
Binary file not shown.
97
scripts/launchpad/deploy_cw3.js
Executable file
97
scripts/launchpad/deploy_cw3.js
Executable file
@ -0,0 +1,97 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
/* eslint-disable @typescript-eslint/naming-convention */
|
||||
const { SigningCosmWasmClient } = require("@cosmjs/cosmwasm");
|
||||
const { Secp256k1HdWallet } = require("@cosmjs/launchpad");
|
||||
const fs = require("fs");
|
||||
|
||||
const httpUrl = "http://localhost:1317";
|
||||
const alice = {
|
||||
mnemonic: "enlist hip relief stomach skate base shallow young switch frequent cry park",
|
||||
address0: "cosmos14qemq0vw6y3gc3u3e0aty2e764u4gs5le3hada",
|
||||
address1: "cosmos1hhg2rlu9jscacku2wwckws7932qqqu8x3gfgw0",
|
||||
address2: "cosmos1xv9tklw7d82sezh9haa573wufgy59vmwe6xxe5",
|
||||
address3: "cosmos17yg9mssjenmc3jkqth6ulcwj9cxujrxxzezwta",
|
||||
address4: "cosmos1f7j7ryulwjfe9ljplvhtcaxa6wqgula3etktce",
|
||||
};
|
||||
|
||||
const codeMeta = {
|
||||
source: "https://crates.io/api/v1/crates/cw3-fixed-multisig/0.3.1/download",
|
||||
builder: "cosmwasm/rust-optimizer:0.10.4",
|
||||
};
|
||||
|
||||
const initData = [
|
||||
{
|
||||
admin: alice.address0,
|
||||
label: "Multisig (1/3)",
|
||||
initMsg: {
|
||||
voters: [
|
||||
{ addr: alice.address0, weight: 1 },
|
||||
{ addr: alice.address1, weight: 1 },
|
||||
{ addr: alice.address2, weight: 1 },
|
||||
],
|
||||
required_weight: 1,
|
||||
max_voting_period: { height: 12345 },
|
||||
},
|
||||
},
|
||||
{
|
||||
admin: alice.address0,
|
||||
label: "Multisig (2/3)",
|
||||
initMsg: {
|
||||
voters: [
|
||||
{ addr: alice.address0, weight: 1 },
|
||||
{ addr: alice.address1, weight: 1 },
|
||||
{ addr: alice.address2, weight: 1 },
|
||||
],
|
||||
required_weight: 2,
|
||||
max_voting_period: { height: 12345 },
|
||||
},
|
||||
},
|
||||
{
|
||||
admin: alice.address0,
|
||||
label: "Multisig (uneven weights)",
|
||||
initMsg: {
|
||||
voters: [
|
||||
{ addr: alice.address0, weight: 1 },
|
||||
{ addr: alice.address1, weight: 2 },
|
||||
{ addr: alice.address2, weight: 3 },
|
||||
],
|
||||
required_weight: 3,
|
||||
max_voting_period: { height: 12345 },
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
async function main() {
|
||||
const wallet = await Secp256k1HdWallet.fromMnemonic(alice.mnemonic);
|
||||
const client = new SigningCosmWasmClient(httpUrl, alice.address0, wallet);
|
||||
|
||||
const wasm = fs.readFileSync(__dirname + "/contracts/cw3_fixed_multisig.wasm");
|
||||
const uploadReceipt = await client.upload(wasm, codeMeta, "Upload CW3 fixed multisig contract");
|
||||
console.info(`Upload succeeded. Receipt: ${JSON.stringify(uploadReceipt)}`);
|
||||
|
||||
for (const { admin, initMsg, label } of initData) {
|
||||
const { contractAddress } = await client.instantiate(uploadReceipt.codeId, initMsg, label, {
|
||||
memo: `Create a CW3 instance for ${initMsg.symbol}`,
|
||||
admin: admin,
|
||||
});
|
||||
await client.sendTokens(contractAddress, [
|
||||
{
|
||||
amount: "1000",
|
||||
denom: "ucosm",
|
||||
},
|
||||
]);
|
||||
console.info(`Contract instantiated for ${label} at ${contractAddress}`);
|
||||
}
|
||||
}
|
||||
|
||||
main().then(
|
||||
() => {
|
||||
console.info("All done, let the coins flow.");
|
||||
process.exit(0);
|
||||
},
|
||||
(error) => {
|
||||
console.error(error);
|
||||
process.exit(1);
|
||||
},
|
||||
);
|
||||
@ -26,4 +26,5 @@ SCRIPT_DIR="$(realpath "$(dirname "$0")")"
|
||||
)
|
||||
"$SCRIPT_DIR/deploy_hackatom.js"
|
||||
"$SCRIPT_DIR/deploy_erc20.js"
|
||||
"$SCRIPT_DIR/deploy_cw3.js"
|
||||
# "$SCRIPT_DIR/deploy_nameservice.js"
|
||||
|
||||
Loading…
Reference in New Issue
Block a user