-
Notifications
You must be signed in to change notification settings - Fork 353
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
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
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 | ||
* | ||
* 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 | ||
|
@@ -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, | ||
|
@@ -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> | ||
} | ||
|
||
|
@@ -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; | ||
|
@@ -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(); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 There was a problem hiding this comment. Choose a reason for hiding this commentThe 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. |
||
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 | ||
|
@@ -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 | ||
|
@@ -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; | ||
}; | ||
|
||
|
@@ -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, | ||
|
@@ -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); | ||
} | ||
|
||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍