Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

cw20-base: upgrade helper.ts to cosmjs 0.25 #248

Merged
merged 4 commits into from
Jul 13, 2021
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
155 changes: 77 additions & 78 deletions contracts/cw20-base/helpers.ts
Original file line number Diff line number Diff line change
@@ -1,20 +1,24 @@
import axios from "axios"
import fs from "fs"
import { SigningCosmWasmClient, CosmWasmFeeTable} from "@cosmjs/cosmwasm-stargate"
import { GasPrice, Secp256k1HdWallet, GasLimits, makeCosmoshubPath } from "@cosmjs/launchpad"
import { Slip10RawIndex } from "@cosmjs/crypto"
import path from "path"
/*
* This is a set of helpers meant for use with @cosmjs/cli
* With these you can easily use the cw20 contract without worrying about forming messages and parsing queries.
*
* Usage: npx @cosmjs/cli@^0.23 --init https://raw.githubusercontent.com/CosmWasm/cosmwasm-plus/master/contracts/cw20-base/helpers.ts
*
*
* Usage: npx @cosmjs/cli@^0.25 --init https://raw.githubusercontent.com/CosmWasm/cosmwasm-plus/master/contracts/cw20-base/helpers.ts
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

*
* Create a client:
* const client = await useOptions(hackatomOptions).setup(password);
* await client.getAccount()
*
* const [addr, client] = await useOptions(oysternetOptions).setup('password');
*
* Get the mnemonic:
* await useOptions(hackatomOptions).recoverMnemonic(password)
*
* await useOptions(oysternetOptions).recoverMnemonic(password)
*
* If you want to use this code inside an app, you will need several imports from https://github.com/CosmWasm/cosmjs
*/
*/

const path = require("path");

interface Options {
readonly httpUrl: string
Expand All @@ -28,15 +32,15 @@ interface Options {
readonly gasLimits: Partial<GasLimits<CosmWasmFeeTable>> // only set the ones you want to override
}

const hackatomOptions: Options = {
httpUrl: 'https://lcd.heldernet.cosmwasm.com',
networkId: 'hackatom-wasm',
gasPrice: GasPrice.fromString("0.025ucosm"),
bech32prefix: 'cosmos',
feeToken: 'ucosm',
faucetUrl: 'https://faucet.heldernet.cosmwasm.com/credit',
const oysternetOptions: Options = {
httpUrl: 'http://rpc.oysternet.cosmwasm.com',
networkId: 'oysternet-1',
gasPrice: GasPrice.fromString("0.01usponge"),
bech32prefix: 'wasm',
feeToken: 'usponge',
faucetUrl: 'https://faucet.oysternet.cosmwasm.com/credit',
hdPath: makeCosmoshubPath(0),
defaultKeyFile: path.join(process.env.HOME, ".heldernet.key"),
defaultKeyFile: path.join(process.env.HOME, ".oysternet.key"),
gasLimits: {
upload: 1500000,
init: 600000,
Expand All @@ -46,7 +50,7 @@ const hackatomOptions: Options = {
}

interface Network {
setup: (password: string, filename?: string) => Promise<SigningCosmWasmClient>
setup: (password: string, filename?: string) => Promise<[string, SigningCosmWasmClient]>
recoverMnemonic: (password: string, filename?: string) => Promise<string>
}

Expand All @@ -58,7 +62,7 @@ const useOptions = (options: Options): Network => {
encrypted = fs.readFileSync(filename, 'utf8');
} catch (err) {
// generate if no file exists
const wallet = await Secp256k1HdWallet.generate(12, options.hdPath, options.bech32prefix);
const wallet = await Secp256k1HdWallet.generate(12, {hdPaths: [options.hdPath], prefix: options.bech32prefix});
const encrypted = await wallet.serialize(password);
fs.writeFileSync(filename, encrypted, 'utf8');
return wallet;
Expand All @@ -67,59 +71,56 @@ const useOptions = (options: Options): Network => {
const wallet = await Secp256k1HdWallet.deserialize(encrypted, password);
return wallet;
};

const connect = async (
wallet: Secp256k1HdWallet,
options: Options
): Promise<SigningCosmWasmClient> => {
const [{ address }] = await wallet.getAccounts();

const client = new SigningCosmWasmClient(
options.httpUrl,
address,
wallet,
hackatomOptions.gasPrice,
hackatomOptions.gasLimits,
);
return client;
const clientOptions = {
prefix: options.bech32prefix,
gasPrice: options.gasPrice,
gasLimits: options.gasLimits
}
return await SigningCosmWasmClient.connectWithSigner(options.httpUrl, wallet, clientOptions)
};


const hitFaucet = async (
faucetUrl: string,
address: string,
ticker: string
denom: string
): Promise<void> => {
await axios.post(faucetUrl, { ticker, address });
await axios.post(faucetUrl, {denom, address});
}
const setup = async (password: string, filename?: string): Promise<SigningCosmWasmClient> => {

const setup = async (password: string, filename?: string): Promise<[string, SigningCosmWasmClient]> => {
const keyfile = filename || options.defaultKeyFile;
const wallet = await loadOrCreateWallet(hackatomOptions, keyfile, password);
const client = await connect(wallet, hackatomOptions);
const wallet = await loadOrCreateWallet(oysternetOptions, keyfile, password);
const client = await connect(wallet, oysternetOptions);

const [account] = await wallet.getAccounts();
// ensure we have some tokens
if (options.faucetUrl) {
const account = await client.getAccount();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This tells you whether or not the account exists on chain. You probably want to keep it

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Checking if the account is empty (if they had tokens earlier but spent them all) is a fair enough check.
It's on the client side, so being stricter doesn't add security

if (!account) {
const tokens = await client.getBalance(account.address, options.feeToken)
if (tokens.amount === '0') {
console.log(`Getting ${options.feeToken} from faucet`);
await hitFaucet(options.faucetUrl, client.senderAddress, options.feeToken);
await hitFaucet(options.faucetUrl, account.address, options.feeToken);
}
}

return client;
return [account.address, client];
}


const recoverMnemonic = async (password: string, filename?: string): Promise<string> => {
const keyfile = filename || options.defaultKeyFile;
const wallet = await loadOrCreateWallet(hackatomOptions, keyfile, password);
const wallet = await loadOrCreateWallet(oysternetOptions, keyfile, password);
return wallet.mnemonic;
}

return {setup, recoverMnemonic};
}


// TODO: Extract to separate folder: interfaces and funcs
interface Balances {
readonly address: string
readonly amount: string // decimal as string
Expand All @@ -130,7 +131,7 @@ interface MintInfo {
readonly cap?: string // decimal as string
}

type Expiration = {readonly at_height: number} | {readonly at_time: number} | {readonly never: {}};
type Expiration = {readonly at_height: number} | {readonly at_time: number} | {readonly never: {}};

interface AllowanceResponse {
readonly allowance: string; // integer as string
Expand Down Expand Up @@ -165,33 +166,31 @@ interface CW20Instance {
minter: () => Promise<any>

// actions
mint: (recipient: string, amount: string) => Promise<string>
transfer: (recipient: string, amount: string) => Promise<string>
burn: (amount: string) => Promise<string>
increaseAllowance: (recipient: string, amount: string) => Promise<string>
decreaseAllowance: (recipient: string, amount: string) => Promise<string>
transferFrom: (owner: string, recipient: string, amount: string) => Promise<string>
mint: (txSigner: string, recipient: string, amount: string) => Promise<string>
transfer: (txSigner: string, recipient: string, amount: string) => Promise<string>
burn: (txSigner: string, amount: string) => Promise<string>
increaseAllowance: (txSigner: string, recipient: string, amount: string) => Promise<string>
decreaseAllowance: (txSigner: string, recipient: string, amount: string) => Promise<string>
transferFrom: (txSigner: string, owner: string, recipient: string, amount: string) => Promise<string>
}

interface CW20Contract {
// upload a code blob and returns a codeId
upload: () => Promise<number>
upload: (txSigner: string) => Promise<number>

// instantiates a cw20 contract
// codeId must come from a previous deploy
// label is the public name of the contract in listing
// if you set admin, you can run migrations on this contract (likely client.senderAddress)
instantiate: (codeId: number, initMsg: Record<string, unknown>, label: string, admin?: string) => Promise<CW20Instance>
instantiate: (txSigner: string, codeId: number, initMsg: Record<string, unknown>, label: string, admin?: string) => Promise<CW20Instance>

use: (contractAddress: string) => CW20Instance
}


const CW20 = (client: SigningCosmWasmClient): CW20Contract => {
export const CW20 = (client: SigningCosmWasmClient): CW20Contract => {
const use = (contractAddress: string): CW20Instance => {
const balance = async (account?: string): Promise<string> => {
const address = account || client.senderAddress;
const result = await client.queryContractSmart(contractAddress, {balance: { address }});
const balance = async (account: string): Promise<string> => {
const result = await client.queryContractSmart(contractAddress, {balance: { account }});
return result.balance;
};

Expand All @@ -217,38 +216,38 @@ const CW20 = (client: SigningCosmWasmClient): CW20Contract => {
};

// mints tokens, returns transactionHash
const mint = async (recipient: string, amount: string): Promise<string> => {
const result = await client.execute(contractAddress, {mint: {recipient, amount}});
const mint = async (senderAddress: string, recipient: string, amount: string): Promise<string> => {
const result = await client.execute(senderAddress, contractAddress, {mint: {recipient, amount}});
return result.transactionHash;
}

// transfers tokens, returns transactionHash
const transfer = async (recipient: string, amount: string): Promise<string> => {
const result = await client.execute(contractAddress, {transfer: {recipient, amount}});
const transfer = async (senderAddress: string, recipient: string, amount: string): Promise<string> => {
const result = await client.execute(senderAddress, contractAddress, {transfer: {recipient, amount}});
return result.transactionHash;
}

// burns tokens, returns transactionHash
const burn = async (amount: string): Promise<string> => {
const result = await client.execute(contractAddress, {burn: {amount}});
const burn = async (senderAddress: string, amount: string): Promise<string> => {
const result = await client.execute(senderAddress, contractAddress, {burn: {amount}});
return result.transactionHash;
}

const increaseAllowance = async (spender: string, amount: string): Promise<string> => {
const result = await client.execute(contractAddress, {increase_allowance: {spender, amount}});
const increaseAllowance = async (senderAddress: string, spender: string, amount: string): Promise<string> => {
const result = await client.execute(senderAddress, contractAddress, {increase_allowance: {spender, amount}});
return result.transactionHash;
}

const decreaseAllowance = async (spender: string, amount: string): Promise<string> => {
const result = await client.execute(contractAddress, {decrease_allowance: {spender, amount}});
const decreaseAllowance = async (senderAddress: string, spender: string, amount: string): Promise<string> => {
const result = await client.execute(senderAddress, contractAddress, {decrease_allowance: {spender, amount}});
return result.transactionHash;
}

const transferFrom = async (owner: string, recipient: string, amount: string): Promise<string> => {
const result = await client.execute(contractAddress, {transfer_from: {owner, recipient, amount}});
const transferFrom = async (senderAddress: string, owner: string, recipient: string, amount: string): Promise<string> => {
const result = await client.execute(senderAddress, contractAddress, {transfer_from: {owner, recipient, amount}});
return result.transactionHash;
}

return {
contractAddress,
balance,
Expand All @@ -273,20 +272,20 @@ const CW20 = (client: SigningCosmWasmClient): CW20Contract => {
}
return r.data
}
const upload = async (): Promise<number> => {

const upload = async (senderAddress: string): Promise<number> => {
const meta = {
source: "https://github.com/CosmWasm/cosmwasm-plus/tree/v0.4.0/contracts/cw20-base",
source: "https://github.com/CosmWasm/cosmwasm-plus/tree/v0.6.2/contracts/cw20-base",
builder: "cosmwasm/workspace-optimizer:0.10.7"
};
const sourceUrl = "https://github.com/CosmWasm/cosmwasm-plus/releases/download/v0.4.0/cw20_base.wasm";
const sourceUrl = "https://github.com/CosmWasm/cosmwasm-plus/releases/download/v0.6.2/cw20_base.wasm";
const wasm = await downloadWasm(sourceUrl);
const result = await client.upload(wasm, meta);
const result = await client.upload(senderAddress, wasm, meta);
return result.codeId;
}

const instantiate = async (codeId: number, initMsg: Record<string, unknown>, label: string, admin?: string): Promise<CW20Instance> => {
const result = await client.instantiate(codeId, initMsg, label, { memo: `Init ${label}`, admin});
const instantiate = async (senderAddress: string, codeId: number, initMsg: Record<string, unknown>, label: string, admin?: string): Promise<CW20Instance> => {
const result = await client.instantiate(senderAddress, codeId, initMsg, label, { memo: `Init ${label}`, admin});
return use(result.contractAddress);
}

Expand Down