diff --git a/packages/cosmwasm/src/cw1cosmwasmclient.spec.ts b/packages/cosmwasm/src/cw1cosmwasmclient.spec.ts new file mode 100644 index 00000000..a8997fd1 --- /dev/null +++ b/packages/cosmwasm/src/cw1cosmwasmclient.spec.ts @@ -0,0 +1,118 @@ +/* eslint-disable @typescript-eslint/naming-convention */ +import { coins, Secp256k1HdWallet } from "@cosmjs/launchpad"; + +import { Cw1CosmWasmClient } from "./cw1cosmwasmclient"; +import { + alice, + deployedCw1, + launchpad, + makeRandomAddress, + pendingWithoutCw1, + pendingWithoutLaunchpad, +} from "./testutils.spec"; + +describe("Cw1CosmWasmClient", () => { + const contractAddress = deployedCw1.instances[0]; + const defaultToAddress = makeRandomAddress(); + const defaultMsg = { + bank: { + send: { + from_address: contractAddress, + to_address: defaultToAddress, + amount: coins(1, "ucosm"), + }, + }, + }; + + describe("constructor", () => { + it("can be constructed", async () => { + const wallet = await Secp256k1HdWallet.fromMnemonic(alice.mnemonic); + const client = new Cw1CosmWasmClient(launchpad.endpoint, alice.address0, wallet, contractAddress); + expect(client).toBeTruthy(); + }); + }); + + describe("canSend", () => { + it("returns true if client signer can send", async () => { + pendingWithoutLaunchpad(); + pendingWithoutCw1(); + + const wallet = await Secp256k1HdWallet.fromMnemonic(alice.mnemonic); + const client = new Cw1CosmWasmClient( + launchpad.endpoint, + alice.address0, + wallet, + deployedCw1.instances[0], + ); + const result = await client.canSend(defaultMsg); + + expect(result).toEqual(true); + }); + + it("returns false if client signer cannot send", async () => { + pendingWithoutLaunchpad(); + pendingWithoutCw1(); + + const wallet = await Secp256k1HdWallet.fromMnemonic(alice.mnemonic); + const client = new Cw1CosmWasmClient( + launchpad.endpoint, + alice.address1, + wallet, + deployedCw1.instances[0], + ); + const result = await client.canSend(defaultMsg); + + expect(result).toEqual(false); + }); + + it("returns true if supplied signer can send", async () => { + pendingWithoutLaunchpad(); + pendingWithoutCw1(); + + const wallet = await Secp256k1HdWallet.fromMnemonic(alice.mnemonic); + const client = new Cw1CosmWasmClient( + launchpad.endpoint, + alice.address1, + wallet, + deployedCw1.instances[0], + ); + const result = await client.canSend(defaultMsg, alice.address0); + + expect(result).toEqual(true); + }); + + it("returns false if supplied signer cannot send", async () => { + pendingWithoutLaunchpad(); + pendingWithoutCw1(); + + const wallet = await Secp256k1HdWallet.fromMnemonic(alice.mnemonic); + const client = new Cw1CosmWasmClient( + launchpad.endpoint, + alice.address0, + wallet, + deployedCw1.instances[0], + ); + const result = await client.canSend(defaultMsg, alice.address1); + + expect(result).toEqual(false); + }); + }); + + describe("executeCw1", () => { + it("works", async () => { + pendingWithoutLaunchpad(); + pendingWithoutCw1(); + + const wallet = await Secp256k1HdWallet.fromMnemonic(alice.mnemonic); + const client = new Cw1CosmWasmClient( + launchpad.endpoint, + alice.address0, + wallet, + deployedCw1.instances[0], + ); + const result = await client.executeCw1([defaultMsg]); + + expect(result.transactionHash).toBeTruthy(); + }); + }); +}); diff --git a/packages/cosmwasm/src/cw1cosmwasmclient.ts b/packages/cosmwasm/src/cw1cosmwasmclient.ts new file mode 100644 index 00000000..4905fd92 --- /dev/null +++ b/packages/cosmwasm/src/cw1cosmwasmclient.ts @@ -0,0 +1,45 @@ +/* eslint-disable @typescript-eslint/naming-convention */ +import { Account, BroadcastMode, GasLimits, GasPrice, OfflineSigner } from "@cosmjs/launchpad"; + +import { CosmosMsg } from "./cosmosmsg"; +import { CosmWasmFeeTable, ExecuteResult, SigningCosmWasmClient } from "./signingcosmwasmclient"; + +export class Cw1CosmWasmClient extends SigningCosmWasmClient { + public readonly cw1ContractAddress: string; + + public constructor( + apiUrl: string, + signerAddress: string, + signer: OfflineSigner, + cw1ContractAddress: string, + gasPrice?: GasPrice, + gasLimits?: Partial>, + broadcastMode?: BroadcastMode, + ) { + super(apiUrl, signerAddress, signer, gasPrice, gasLimits, broadcastMode); + this.cw1ContractAddress = cw1ContractAddress; + } + + public async getAccount(address?: string): Promise { + return super.getAccount(address || this.cw1ContractAddress); + } + + public async canSend(msg: CosmosMsg, address = this.senderAddress): Promise { + const result = await this.queryContractSmart(this.cw1ContractAddress, { + can_send: { + sender: address, + msg: msg, + }, + }); + return result.can_send; + } + + public async executeCw1(msgs: readonly CosmosMsg[], memo = ""): Promise { + const handleMsg = { + execute: { + msgs: msgs, + }, + }; + return this.execute(this.cw1ContractAddress, handleMsg, memo); + } +} diff --git a/packages/cosmwasm/src/cw1subkeycosmwasmclient.spec.ts b/packages/cosmwasm/src/cw1subkeycosmwasmclient.spec.ts new file mode 100644 index 00000000..2054fbf6 --- /dev/null +++ b/packages/cosmwasm/src/cw1subkeycosmwasmclient.spec.ts @@ -0,0 +1,323 @@ +/* eslint-disable @typescript-eslint/naming-convention */ +import { coin, coins, Secp256k1HdWallet } from "@cosmjs/launchpad"; + +import { Cw1SubkeyCosmWasmClient } from "./cw1subkeycosmwasmclient"; +import { + alice, + deployedCw1, + launchpad, + makeRandomAddress, + pendingWithoutCw1, + pendingWithoutLaunchpad, +} from "./testutils.spec"; + +describe("Cw1SubkeyCosmWasmClient", () => { + describe("getAdmins", () => { + it("works", async () => { + pendingWithoutLaunchpad(); + pendingWithoutCw1(); + + const wallet = await Secp256k1HdWallet.fromMnemonic(alice.mnemonic); + const client = new Cw1SubkeyCosmWasmClient( + launchpad.endpoint, + alice.address0, + wallet, + deployedCw1.instances[0], + ); + const result = await client.getAdmins(); + + expect(result).toEqual([alice.address0]); + }); + }); + + describe("isAdmin", () => { + it("returns true if client signer is admin", async () => { + pendingWithoutLaunchpad(); + pendingWithoutCw1(); + + const wallet = await Secp256k1HdWallet.fromMnemonic(alice.mnemonic); + const client = new Cw1SubkeyCosmWasmClient( + launchpad.endpoint, + alice.address0, + wallet, + deployedCw1.instances[0], + ); + const result = await client.isAdmin(); + + expect(result).toEqual(true); + }); + + it("returns false if client signer is not admin", async () => { + pendingWithoutLaunchpad(); + pendingWithoutCw1(); + + const wallet = await Secp256k1HdWallet.fromMnemonic(alice.mnemonic); + const client = new Cw1SubkeyCosmWasmClient( + launchpad.endpoint, + alice.address1, + wallet, + deployedCw1.instances[0], + ); + const result = await client.isAdmin(); + + expect(result).toEqual(false); + }); + + it("returns true if supplied signer is admin", async () => { + pendingWithoutLaunchpad(); + pendingWithoutCw1(); + + const wallet = await Secp256k1HdWallet.fromMnemonic(alice.mnemonic); + const client = new Cw1SubkeyCosmWasmClient( + launchpad.endpoint, + alice.address1, + wallet, + deployedCw1.instances[0], + ); + const result = await client.isAdmin(alice.address0); + + expect(result).toEqual(true); + }); + + it("returns false if supplied signer is not admin", async () => { + pendingWithoutLaunchpad(); + pendingWithoutCw1(); + + const wallet = await Secp256k1HdWallet.fromMnemonic(alice.mnemonic); + const client = new Cw1SubkeyCosmWasmClient( + launchpad.endpoint, + alice.address0, + wallet, + deployedCw1.instances[0], + ); + const result = await client.isAdmin(alice.address1); + + expect(result).toEqual(false); + }); + }); + + describe("getAllAllowances", () => { + it("works", async () => { + pendingWithoutLaunchpad(); + pendingWithoutCw1(); + + const wallet = await Secp256k1HdWallet.fromMnemonic(alice.mnemonic); + const client = new Cw1SubkeyCosmWasmClient( + launchpad.endpoint, + alice.address0, + wallet, + deployedCw1.instances[0], + ); + const result = await client.getAllAllowances(); + expect(result).toBeTruthy(); + }); + }); + + describe("getAllowance", () => { + it("works for client signer", async () => { + pendingWithoutLaunchpad(); + pendingWithoutCw1(); + + const wallet = await Secp256k1HdWallet.fromMnemonic(alice.mnemonic); + const client = new Cw1SubkeyCosmWasmClient( + launchpad.endpoint, + alice.address0, + wallet, + deployedCw1.instances[0], + ); + const result = await client.getAllowance(); + + expect(result).toEqual({ balance: [], expires: { never: {} } }); + }); + }); + + describe("getAllPermissions", () => { + it("works", async () => { + pendingWithoutLaunchpad(); + pendingWithoutCw1(); + + const wallet = await Secp256k1HdWallet.fromMnemonic(alice.mnemonic); + const client = new Cw1SubkeyCosmWasmClient( + launchpad.endpoint, + alice.address0, + wallet, + deployedCw1.instances[0], + ); + const result = await client.getAllPermissions(); + expect(result.length).toEqual(1); + // TODO: test content of permissions + }); + }); + + describe("getPermissions", () => { + it("works for client signer", async () => { + pendingWithoutLaunchpad(); + pendingWithoutCw1(); + + const wallet = await Secp256k1HdWallet.fromMnemonic(alice.mnemonic); + const client = new Cw1SubkeyCosmWasmClient( + launchpad.endpoint, + alice.address0, + wallet, + deployedCw1.instances[0], + ); + const result = await client.getPermissions(); + + expect(result).toEqual({ + delegate: false, + redelegate: false, + undelegate: false, + withdraw: false, + }); + }); + + it("works for supplied signer", async () => { + pendingWithoutLaunchpad(); + pendingWithoutCw1(); + + const wallet = await Secp256k1HdWallet.fromMnemonic(alice.mnemonic); + const client = new Cw1SubkeyCosmWasmClient( + launchpad.endpoint, + alice.address0, + wallet, + deployedCw1.instances[0], + ); + const result = await client.getPermissions(alice.address1); + + expect(result).toEqual({ + delegate: false, + redelegate: false, + undelegate: false, + withdraw: false, + }); + }); + }); + + describe("addAdmin and removeAdmin", () => { + it("works", async () => { + pendingWithoutLaunchpad(); + pendingWithoutCw1(); + + const wallet = await Secp256k1HdWallet.fromMnemonic(alice.mnemonic); + const client = new Cw1SubkeyCosmWasmClient( + launchpad.endpoint, + alice.address0, + wallet, + deployedCw1.instances[0], + ); + const newAdmin = makeRandomAddress(); + expect(await client.isAdmin(newAdmin)).toBeFalse(); + + const addResult = await client.addAdmin(newAdmin); + expect(addResult.transactionHash).toBeTruthy(); + expect(await client.isAdmin(newAdmin)).toBeTrue(); + + const removeResult = await client.removeAdmin(newAdmin); + expect(removeResult.transactionHash).toBeTruthy(); + expect(await client.isAdmin(newAdmin)).toBeFalse(); + }); + }); + + describe("increaseAllowance and decreaseAllowance", () => { + it("works", async () => { + pendingWithoutLaunchpad(); + pendingWithoutCw1(); + + const wallet = await Secp256k1HdWallet.fromMnemonic(alice.mnemonic); + const client = new Cw1SubkeyCosmWasmClient( + launchpad.endpoint, + alice.address0, + wallet, + deployedCw1.instances[0], + ); + const spender = makeRandomAddress(); + expect(await client.getAllowance(spender)).toEqual({ balance: [], expires: { never: {} } }); + + const increaseAmount = coin(100, "ucosm"); + const increaseResult = await client.increaseAllowance(spender, increaseAmount); + expect(increaseResult.transactionHash).toBeTruthy(); + expect(await client.getAllowance(spender)).toEqual({ + balance: [increaseAmount], + expires: { never: {} }, + }); + + const decreaseAmount = coin(20, "ucosm"); + const decreaseResult = await client.decreaseAllowance(spender, decreaseAmount); + expect(decreaseResult.transactionHash).toBeTruthy(); + expect(await client.getAllowance(spender)).toEqual({ + balance: coins(80, "ucosm"), + expires: { never: {} }, + }); + }); + + it("works with expiration", async () => { + pendingWithoutLaunchpad(); + pendingWithoutCw1(); + + const wallet = await Secp256k1HdWallet.fromMnemonic(alice.mnemonic); + const client = new Cw1SubkeyCosmWasmClient( + launchpad.endpoint, + alice.address0, + wallet, + deployedCw1.instances[0], + ); + const spender = makeRandomAddress(); + expect(await client.getAllowance(spender)).toEqual({ balance: [], expires: { never: {} } }); + + const increaseAmount = coin(100, "ucosm"); + const increaseExpiration = { at_height: 88888888888 }; + const increaseResult = await client.increaseAllowance(spender, increaseAmount, increaseExpiration); + expect(increaseResult.transactionHash).toBeTruthy(); + expect(await client.getAllowance(spender)).toEqual({ + balance: [increaseAmount], + expires: increaseExpiration, + }); + + const decreaseAmount = coin(20, "ucosm"); + const decreaseExpiration = { at_height: 99999999999 }; + const decreaseResult = await client.decreaseAllowance(spender, decreaseAmount, decreaseExpiration); + expect(decreaseResult.transactionHash).toBeTruthy(); + expect(await client.getAllowance(spender)).toEqual({ + balance: coins(80, "ucosm"), + expires: decreaseExpiration, + }); + }); + }); + + describe("setPermissions", () => { + it("works", async () => { + pendingWithoutLaunchpad(); + pendingWithoutCw1(); + + const wallet = await Secp256k1HdWallet.fromMnemonic(alice.mnemonic); + const client = new Cw1SubkeyCosmWasmClient( + launchpad.endpoint, + alice.address0, + wallet, + deployedCw1.instances[0], + ); + const spender = makeRandomAddress(); + const defaultPermissions = { + delegate: false, + redelegate: false, + undelegate: false, + withdraw: false, + }; + expect(await client.getPermissions(spender)).toEqual(defaultPermissions); + + const newPermissions = { + delegate: true, + redelegate: true, + undelegate: true, + withdraw: false, + }; + const setPermissionsResult = await client.setPermissions(spender, newPermissions); + expect(setPermissionsResult.transactionHash).toBeTruthy(); + expect(await client.getPermissions(spender)).toEqual(newPermissions); + + const resetPermissionsResult = await client.setPermissions(spender, defaultPermissions); + expect(resetPermissionsResult.transactionHash).toBeTruthy(); + expect(await client.getPermissions(spender)).toEqual(defaultPermissions); + }); + }); +}); diff --git a/packages/cosmwasm/src/cw1subkeycosmwasmclient.ts b/packages/cosmwasm/src/cw1subkeycosmwasmclient.ts new file mode 100644 index 00000000..87aa92de --- /dev/null +++ b/packages/cosmwasm/src/cw1subkeycosmwasmclient.ts @@ -0,0 +1,152 @@ +/* eslint-disable @typescript-eslint/naming-convention */ +import { Coin } from "@cosmjs/launchpad"; + +import { Cw1CosmWasmClient } from "./cw1cosmwasmclient"; +import { Expiration } from "./interfaces"; +import { ExecuteResult } from "./signingcosmwasmclient"; + +/** + * @see https://github.com/CosmWasm/cosmwasm-plus/blob/v0.3.2/contracts/cw1-subkeys/src/msg.rs#L88 + */ +export interface Cw1SubkeyAllowanceInfo { + readonly balance: readonly Coin[]; + readonly expires: Expiration; +} + +/** + * @see https://github.com/CosmWasm/cosmwasm-plus/blob/v0.3.2/contracts/cw1-subkeys/src/msg.rs#L83 + */ +interface AllAllowancesResponse { + readonly allowances: readonly Cw1SubkeyAllowanceInfo[]; +} + +/** + * @see https://github.com/CosmWasm/cosmwasm-plus/blob/v0.3.2/contracts/cw1-subkeys/src/state.rs#L15 + */ +export interface Cw1SubkeyPermissions { + readonly delegate: boolean; + readonly redelegate: boolean; + readonly undelegate: boolean; + readonly withdraw: boolean; +} + +/** + * @see https://github.com/CosmWasm/cosmwasm-plus/blob/v0.3.2/contracts/cw1-subkeys/src/msg.rs#L95 + */ +export interface Cw1SubkeyPermissionsInfo { + /** Spender address */ + readonly spender: string; + readonly permissions: readonly Cw1SubkeyPermissions[]; +} + +/** + * @see https://github.com/CosmWasm/cosmwasm-plus/blob/v0.3.2/contracts/cw1-subkeys/src/msg.rs#L101 + */ +interface AllPermissionsResponse { + readonly permissions: readonly Cw1SubkeyPermissionsInfo[]; +} + +export class Cw1SubkeyCosmWasmClient extends Cw1CosmWasmClient { + private async setAdmins(admins: readonly string[], memo = ""): Promise { + const handleMsg = { + update_admins: { + admins: admins, + }, + }; + return this.execute(this.cw1ContractAddress, handleMsg, memo); + } + + public async getAdmins(): Promise { + const { admins } = await this.queryContractSmart(this.cw1ContractAddress, { admin_list: {} }); + return admins; + } + + public async isAdmin(address = this.senderAddress): Promise { + const admins = await this.getAdmins(); + return admins.includes(address); + } + + public async getAllAllowances(): Promise { + const response: AllAllowancesResponse = await this.queryContractSmart(this.cw1ContractAddress, { + all_allowances: {}, + }); + return response.allowances; + } + + public async getAllowance(address = this.senderAddress): Promise { + return this.queryContractSmart(this.cw1ContractAddress, { + allowance: { spender: address }, + }); + } + + public async getAllPermissions(): Promise { + const response: AllPermissionsResponse = await this.queryContractSmart(this.cw1ContractAddress, { + all_permissions: {}, + }); + return response.permissions; + } + + public async getPermissions(address = this.senderAddress): Promise { + return this.queryContractSmart(this.cw1ContractAddress, { + permissions: { spender: address }, + }); + } + + public async addAdmin(address: string, memo = ""): Promise { + const admins = await this.getAdmins(); + const newAdmins = admins.includes(address) ? admins : [...admins, address]; + return this.setAdmins(newAdmins, memo); + } + + public async removeAdmin(address: string, memo = ""): Promise { + const admins = await this.getAdmins(); + const newAdmins = admins.filter((admin) => admin !== address); + return this.setAdmins(newAdmins, memo); + } + + public async increaseAllowance( + address: string, + amount: Coin, + expires?: Expiration, + memo = "", + ): Promise { + const handleMsg = { + increase_allowance: { + spender: address, + amount: amount, + expires: expires, + }, + }; + return this.execute(this.cw1ContractAddress, handleMsg, memo); + } + + public async decreaseAllowance( + address: string, + amount: Coin, + expires?: Expiration, + memo = "", + ): Promise { + const handleMsg = { + decrease_allowance: { + spender: address, + amount: amount, + expires: expires, + }, + }; + return this.execute(this.cw1ContractAddress, handleMsg, memo); + } + + public async setPermissions( + address: string, + permissions: Cw1SubkeyPermissions, + memo = "", + ): Promise { + const handleMsg = { + set_permissions: { + spender: address, + permissions: permissions, + }, + }; + return this.execute(this.cw1ContractAddress, handleMsg, memo); + } +} diff --git a/packages/cosmwasm/src/cw3cosmwasmclient.ts b/packages/cosmwasm/src/cw3cosmwasmclient.ts index 933c9574..ef8efb9d 100644 --- a/packages/cosmwasm/src/cw3cosmwasmclient.ts +++ b/packages/cosmwasm/src/cw3cosmwasmclient.ts @@ -3,16 +3,12 @@ import { BroadcastMode, GasLimits, GasPrice, OfflineSigner } from "@cosmjs/launc import { CosmosMsg } from "./cosmosmsg"; import { Account } from "./cosmwasmclient"; +import { Expiration } from "./interfaces"; import { CosmWasmFeeTable, ExecuteResult, SigningCosmWasmClient } from "./signingcosmwasmclient"; -export type Expiration = - | { - readonly at_height: number; - } - | { - readonly at_time: number; - }; - +/** + * @see https://github.com/CosmWasm/cosmwasm-plus/blob/v0.3.2/packages/cw3/src/msg.rs#L35 + */ export enum Vote { Yes = "yes", No = "no", diff --git a/packages/cosmwasm/src/index.ts b/packages/cosmwasm/src/index.ts index b90bf610..692d9bbc 100644 --- a/packages/cosmwasm/src/index.ts +++ b/packages/cosmwasm/src/index.ts @@ -1,4 +1,6 @@ +export { Expiration } from "./interfaces"; export { setupWasmExtension, WasmExtension } from "./lcdapi/wasm"; + export { BankMsg, CosmosMsg, CustomMsg, StakingMsg, WasmMsg } from "./cosmosmsg"; export { Account, @@ -17,9 +19,9 @@ export { SearchTxQuery, SearchTxFilter, } from "./cosmwasmclient"; +export { Cw1CosmWasmClient } from "./cw1cosmwasmclient"; export { Cw3CosmWasmClient, - Expiration, ProposalResult, ProposalsResult, ThresholdResult, diff --git a/packages/cosmwasm/src/interfaces/cw0.ts b/packages/cosmwasm/src/interfaces/cw0.ts new file mode 100644 index 00000000..17d098dd --- /dev/null +++ b/packages/cosmwasm/src/interfaces/cw0.ts @@ -0,0 +1,15 @@ +/* eslint-disable @typescript-eslint/naming-convention */ + +/** + * @see https://github.com/CosmWasm/cosmwasm-plus/blob/v0.3.2/packages/cw0/src/expiration.rs#L14 + */ +export type Expiration = + | { + readonly at_height: number; + } + | { + readonly at_time: number; + } + | { + readonly never: Record; + }; diff --git a/packages/cosmwasm/src/interfaces/index.ts b/packages/cosmwasm/src/interfaces/index.ts new file mode 100644 index 00000000..b91ff756 --- /dev/null +++ b/packages/cosmwasm/src/interfaces/index.ts @@ -0,0 +1 @@ +export { Expiration } from "./cw0"; diff --git a/packages/cosmwasm/src/testutils.spec.ts b/packages/cosmwasm/src/testutils.spec.ts index f9f12ef6..5b2202b8 100644 --- a/packages/cosmwasm/src/testutils.spec.ts +++ b/packages/cosmwasm/src/testutils.spec.ts @@ -103,6 +103,14 @@ export const deployedCw3 = { ], }; +/** Deployed as part of scripts/launchpad/init.sh */ +export const deployedCw1 = { + codeId: 4, + source: "https://crates.io/api/v1/crates/cw1-subkeys/0.3.1/download", + builder: "cosmwasm/rust-optimizer:0.10.4", + instances: ["cosmos1vs2vuks65rq7xj78mwtvn7vvnm2gn7ad5me0d2"], +}; + export const launchpad = { endpoint: "http://localhost:1317", chainId: "testing", @@ -141,6 +149,16 @@ export function pendingWithoutCw3(): void { } } +export function cw1Enabled(): boolean { + return !!process.env.CW1_ENABLED; +} + +export function pendingWithoutCw1(): void { + if (!cw1Enabled()) { + return pending("Set CW1_ENABLED to enable CW1-based tests"); + } +} + /** Returns first element. Throws if array has a different length than 1. */ export function fromOneElementArray(elements: ArrayLike): T { if (elements.length !== 1) throw new Error(`Expected exactly one element but got ${elements.length}`); diff --git a/packages/cosmwasm/types/cw1cosmwasmclient.d.ts b/packages/cosmwasm/types/cw1cosmwasmclient.d.ts new file mode 100644 index 00000000..b3da0c32 --- /dev/null +++ b/packages/cosmwasm/types/cw1cosmwasmclient.d.ts @@ -0,0 +1,18 @@ +import { Account, BroadcastMode, GasLimits, GasPrice, OfflineSigner } from "@cosmjs/launchpad"; +import { CosmosMsg } from "./cosmosmsg"; +import { CosmWasmFeeTable, ExecuteResult, SigningCosmWasmClient } from "./signingcosmwasmclient"; +export declare class Cw1CosmWasmClient extends SigningCosmWasmClient { + readonly cw1ContractAddress: string; + constructor( + apiUrl: string, + signerAddress: string, + signer: OfflineSigner, + cw1ContractAddress: string, + gasPrice?: GasPrice, + gasLimits?: Partial>, + broadcastMode?: BroadcastMode, + ); + getAccount(address?: string): Promise; + canSend(msg: CosmosMsg, address?: string): Promise; + executeCw1(msgs: readonly CosmosMsg[], memo?: string): Promise; +} diff --git a/packages/cosmwasm/types/cw1subkeycosmwasmclient.d.ts b/packages/cosmwasm/types/cw1subkeycosmwasmclient.d.ts new file mode 100644 index 00000000..da665871 --- /dev/null +++ b/packages/cosmwasm/types/cw1subkeycosmwasmclient.d.ts @@ -0,0 +1,52 @@ +import { Coin } from "@cosmjs/launchpad"; +import { Cw1CosmWasmClient } from "./cw1cosmwasmclient"; +import { Expiration } from "./interfaces"; +import { ExecuteResult } from "./signingcosmwasmclient"; +/** + * @see https://github.com/CosmWasm/cosmwasm-plus/blob/v0.3.2/contracts/cw1-subkeys/src/msg.rs#L88 + */ +export interface Cw1SubkeyAllowanceInfo { + readonly balance: readonly Coin[]; + readonly expires: Expiration; +} +/** + * @see https://github.com/CosmWasm/cosmwasm-plus/blob/v0.3.2/contracts/cw1-subkeys/src/state.rs#L15 + */ +export interface Cw1SubkeyPermissions { + readonly delegate: boolean; + readonly redelegate: boolean; + readonly undelegate: boolean; + readonly withdraw: boolean; +} +/** + * @see https://github.com/CosmWasm/cosmwasm-plus/blob/v0.3.2/contracts/cw1-subkeys/src/msg.rs#L95 + */ +export interface Cw1SubkeyPermissionsInfo { + /** Spender address */ + readonly spender: string; + readonly permissions: readonly Cw1SubkeyPermissions[]; +} +export declare class Cw1SubkeyCosmWasmClient extends Cw1CosmWasmClient { + private setAdmins; + getAdmins(): Promise; + isAdmin(address?: string): Promise; + getAllAllowances(): Promise; + getAllowance(address?: string): Promise; + getAllPermissions(): Promise; + getPermissions(address?: string): Promise; + addAdmin(address: string, memo?: string): Promise; + removeAdmin(address: string, memo?: string): Promise; + increaseAllowance( + address: string, + amount: Coin, + expires?: Expiration, + memo?: string, + ): Promise; + decreaseAllowance( + address: string, + amount: Coin, + expires?: Expiration, + memo?: string, + ): Promise; + setPermissions(address: string, permissions: Cw1SubkeyPermissions, memo?: string): Promise; +} diff --git a/packages/cosmwasm/types/cw3cosmwasmclient.d.ts b/packages/cosmwasm/types/cw3cosmwasmclient.d.ts index 80ff14fc..b0fe4220 100644 --- a/packages/cosmwasm/types/cw3cosmwasmclient.d.ts +++ b/packages/cosmwasm/types/cw3cosmwasmclient.d.ts @@ -1,14 +1,11 @@ import { BroadcastMode, GasLimits, GasPrice, OfflineSigner } from "@cosmjs/launchpad"; import { CosmosMsg } from "./cosmosmsg"; import { Account } from "./cosmwasmclient"; +import { Expiration } from "./interfaces"; import { CosmWasmFeeTable, ExecuteResult, SigningCosmWasmClient } from "./signingcosmwasmclient"; -export declare type Expiration = - | { - readonly at_height: number; - } - | { - readonly at_time: number; - }; +/** + * @see https://github.com/CosmWasm/cosmwasm-plus/blob/v0.3.2/packages/cw3/src/msg.rs#L35 + */ export declare enum Vote { Yes = "yes", No = "no", diff --git a/packages/cosmwasm/types/index.d.ts b/packages/cosmwasm/types/index.d.ts index b90bf610..071e9351 100644 --- a/packages/cosmwasm/types/index.d.ts +++ b/packages/cosmwasm/types/index.d.ts @@ -1,3 +1,4 @@ +export { Expiration } from "./interfaces"; export { setupWasmExtension, WasmExtension } from "./lcdapi/wasm"; export { BankMsg, CosmosMsg, CustomMsg, StakingMsg, WasmMsg } from "./cosmosmsg"; export { @@ -17,9 +18,9 @@ export { SearchTxQuery, SearchTxFilter, } from "./cosmwasmclient"; +export { Cw1CosmWasmClient } from "./cw1cosmwasmclient"; export { Cw3CosmWasmClient, - Expiration, ProposalResult, ProposalsResult, ThresholdResult, diff --git a/packages/cosmwasm/types/interfaces/cw0.d.ts b/packages/cosmwasm/types/interfaces/cw0.d.ts new file mode 100644 index 00000000..1a9bae43 --- /dev/null +++ b/packages/cosmwasm/types/interfaces/cw0.d.ts @@ -0,0 +1,13 @@ +/** + * @see https://github.com/CosmWasm/cosmwasm-plus/blob/v0.3.2/packages/cw0/src/expiration.rs#L14 + */ +export declare type Expiration = + | { + readonly at_height: number; + } + | { + readonly at_time: number; + } + | { + readonly never: Record; + }; diff --git a/packages/cosmwasm/types/interfaces/index.d.ts b/packages/cosmwasm/types/interfaces/index.d.ts new file mode 100644 index 00000000..b91ff756 --- /dev/null +++ b/packages/cosmwasm/types/interfaces/index.d.ts @@ -0,0 +1 @@ +export { Expiration } from "./cw0"; diff --git a/scripts/launchpad/contracts/checksums.sha256 b/scripts/launchpad/contracts/checksums.sha256 index e9600aba..b441fcd8 100644 --- a/scripts/launchpad/contracts/checksums.sha256 +++ b/scripts/launchpad/contracts/checksums.sha256 @@ -1,3 +1,4 @@ +c478a6d9d6303e67f28d7863ea6e07426e9f0082744557d503e723bc1c46ccf8 cw1_subkeys.wasm 1a4a376ef1099ad3edc33aa1d3105e4621bc49e44b1ac0a449d7b6912e40fb0a cw3_fixed_multisig.wasm ebc2b11e2afa50d5dcd4234840cd581e948a59d888bb8d651598bba3732cd8ee cw-nameservice.wasm d04368320ad55089384adb171aaea39e43d710d7608829adba0300ed30aa2988 cw_erc20.wasm diff --git a/scripts/launchpad/contracts/cw1_subkeys.wasm b/scripts/launchpad/contracts/cw1_subkeys.wasm new file mode 100644 index 00000000..beeddda4 --- /dev/null +++ b/scripts/launchpad/contracts/cw1_subkeys.wasm @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c478a6d9d6303e67f28d7863ea6e07426e9f0082744557d503e723bc1c46ccf8 +size 267814 diff --git a/scripts/launchpad/deploy_cw1.js b/scripts/launchpad/deploy_cw1.js new file mode 100755 index 00000000..5a29333d --- /dev/null +++ b/scripts/launchpad/deploy_cw1.js @@ -0,0 +1,58 @@ +#!/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/cw1-subkeys/0.3.1/download", + builder: "cosmwasm/rust-optimizer:0.10.4", +}; + +async function main() { + const wallet = await Secp256k1HdWallet.fromMnemonic(alice.mnemonic); + const client = new SigningCosmWasmClient(httpUrl, alice.address0, wallet); + + const wasm = fs.readFileSync(__dirname + "/contracts/cw1_subkeys.wasm"); + const uploadReceipt = await client.upload(wasm, codeMeta, "Upload CW1 subkeys contract"); + console.info(`Upload succeeded. Receipt: ${JSON.stringify(uploadReceipt)}`); + + const initMsg = { + admins: [alice.address0], + mutable: true, + }; + const label = "Subkey test"; + const { contractAddress } = await client.instantiate(uploadReceipt.codeId, initMsg, label, { + memo: `Create a CW1 instance for ${alice.address0}`, + admin: alice.address0, + }); + await client.sendTokens(contractAddress, [ + { + amount: "1000", + denom: "ucosm", + }, + ]); + console.info(`Contract instantiated for ${alice.address0} subkey at ${contractAddress}`); +} + +main().then( + () => { + console.info("All done, let the coins flow."); + process.exit(0); + }, + (error) => { + console.error(error); + process.exit(1); + }, +); diff --git a/scripts/launchpad/init.sh b/scripts/launchpad/init.sh index 7c49cb3e..11476250 100755 --- a/scripts/launchpad/init.sh +++ b/scripts/launchpad/init.sh @@ -27,4 +27,5 @@ SCRIPT_DIR="$(realpath "$(dirname "$0")")" "$SCRIPT_DIR/deploy_hackatom.js" "$SCRIPT_DIR/deploy_erc20.js" "$SCRIPT_DIR/deploy_cw3.js" +"$SCRIPT_DIR/deploy_cw1.js" # "$SCRIPT_DIR/deploy_nameservice.js"