Merge pull request #150 from CosmWasm/cleanup-cli
Update helpers to modern api, demo net
This commit is contained in:
commit
17916b07bb
3
.gitignore
vendored
3
.gitignore
vendored
@ -14,3 +14,6 @@ node_modules/
|
||||
|
||||
# IDE-specific
|
||||
.vscode/
|
||||
|
||||
# demo mnemonics from cli
|
||||
*.key
|
||||
|
||||
@ -81,55 +81,169 @@ The above code shows you the use of the API and various objects and is a great w
|
||||
how to embed cosmwasm-js into your project. However, if you just want a cli to perform some
|
||||
quick queries on a chain, you can use an extended set of helpers:
|
||||
|
||||
1. Start a local wasmd blockchain, for example running the setup from `../../scripts/wasmd/start.sh`
|
||||
2. Start with `./bin/cosmwasm-cli --init examples/helpers.ts` (note the new init file)
|
||||
3. Deploy some erc20 contracts: `../../scripts/wasmd/init.sh`
|
||||
4. Play around as in the following example code
|
||||
Start with `./bin/cosmwasm-cli --init examples/helpers.ts`
|
||||
|
||||
(This points to the Demo Net at https://lcd.demo-07.cosmwasm.com for ease of use. Other networks, look below)
|
||||
|
||||
Setup Account:
|
||||
|
||||
```ts
|
||||
const account = (await client.authAccounts(faucetAddress)).result.value;
|
||||
account
|
||||
// you can hand-copy a mnemonic here, but this is easiest for reuse between sessions
|
||||
// it creates a random one first time, then reads it in the future
|
||||
const mnemonic = loadOrCreateMnemonic("foo.key");
|
||||
const {address, client} = await connect(mnemonic, {});
|
||||
address
|
||||
|
||||
client.getAccount();
|
||||
// if empty - this only works with CosmWasm
|
||||
hitFaucet(defaultFaucetUrl, address, "COSM")
|
||||
client.getAccount();
|
||||
```
|
||||
|
||||
View contracts:
|
||||
|
||||
```ts
|
||||
// show all code and contracts
|
||||
client.listCodeInfo()
|
||||
client.listContractAddresses()
|
||||
client.getCodes()
|
||||
|
||||
// query the first contract
|
||||
const addr = (await client.listContractAddresses())[0]
|
||||
const info = await client.getContractInfo(addr)
|
||||
info.init_msg
|
||||
// query the first contract for first code
|
||||
const contracts = await client.getContracts(1);
|
||||
contracts
|
||||
const info = await client.getContract(contracts[0].address)
|
||||
info
|
||||
info.initMsg
|
||||
|
||||
// see your balance here
|
||||
smartQuery(client, addr, { balance: { address: faucetAddress } })
|
||||
|
||||
// make a new contract
|
||||
const initMsg = { name: "Foo Coin", symbol: "FOO", decimals: 2, initial_balances: [{address: faucetAddress, amount: "123456789"}]}
|
||||
const foo = await instantiateContract(client, pen, 1, initMsg);
|
||||
|
||||
smartQuery(client, foo, { balance: { address: faucetAddress } })
|
||||
|
||||
const rcpt = await randomAddress();
|
||||
rcpt
|
||||
smartQuery(client, foo, { balance: { address: rcpt } })
|
||||
|
||||
const execMsg = { transfer: {recipient: rcpt, amount: "808"}}
|
||||
const exec = await executeContract(client, pen, foo, execMsg);
|
||||
exec
|
||||
exec[0].events[0]
|
||||
smartQuery(client, foo, { balance: { address: rcpt } })
|
||||
smartQuery(client, addr, { balance: { address } })
|
||||
```
|
||||
|
||||
## Other example codes
|
||||
|
||||
### Create random mnemonic and Cosmos address
|
||||
Instantiate and use ERC20 contract:
|
||||
|
||||
```ts
|
||||
const mnemonic = Bip39.encode(Random.getBytes(16)).toString();
|
||||
const pen = await Secp256k1Pen.fromMnemonic(mnemonic);
|
||||
const pubkey = encodeSecp256k1Pubkey(pen.pubkey);
|
||||
const address = pubkeyToAddress(pubkey, "cosmos");
|
||||
// no money? no problem.
|
||||
// let's make our own s**coin - replace "FOO" with something else to avoid duplicates
|
||||
const initMsg = { name: "Foo Coin", symbol: "FOO", decimals: 2, initial_balances: [{address, amount: "123456789"}]}
|
||||
const foo = await client.instantiate(1, initMsg, "FOO");
|
||||
foo
|
||||
foo.logs[0].events[0]
|
||||
const fooAddr = foo.contractAddress;
|
||||
|
||||
// we can also find this another way...
|
||||
const fooAddr2 = await client.getContracts(1).then(contracts => contracts.filter(x => x.label == "FOO").map(x => x.address)[0])
|
||||
[fooAddr, fooAddr2]
|
||||
|
||||
// now we have some cash
|
||||
smartQuery(client, fooAddr, { balance: { address } })
|
||||
|
||||
const rcpt = await randomAddress("cosmos");
|
||||
rcpt
|
||||
smartQuery(client, fooAddr, { balance: { address: rcpt } })
|
||||
|
||||
const execMsg = { transfer: {recipient: rcpt, amount: "808"}}
|
||||
const exec = await client.execute(fooAddr, execMsg);
|
||||
exec
|
||||
exec.logs[0].events[0]
|
||||
smartQuery(client, fooAddr, { balance: { address: rcpt } })
|
||||
```
|
||||
|
||||
Or just send tokens:
|
||||
|
||||
```ts
|
||||
client.getAccount(rcpt)
|
||||
|
||||
const sent = await client.sendTokens(rcpt, [{amount: "1234", denom: "ucosm"}])
|
||||
sent
|
||||
foo.logs[0].events[0]
|
||||
```
|
||||
|
||||
### Use Custom Network
|
||||
|
||||
All the network info can be configured inside the last argument to connect.
|
||||
To see how to connect to the Regen Testnet, try this. (Note you need to use `.editor`
|
||||
in the repl to allow multi-line commands. Alternative is to place entire `regenOptions`
|
||||
on one line.
|
||||
|
||||
Run `./bin/cosmwasm-cli --init examples/helpers.ts`
|
||||
|
||||
```ts
|
||||
.editor
|
||||
const regenOptions = {
|
||||
httpUrl: "https://regen-lcd.vitwit.com/",
|
||||
networkId: "kontraua",
|
||||
feeToken: "utree",
|
||||
gasPrice: 0.025,
|
||||
bech32prefix: "xrn:",
|
||||
}
|
||||
^D
|
||||
|
||||
const mnemonic = loadOrCreateMnemonic("regen.key");
|
||||
const {address, client} = await connect(mnemonic, regenOptions);
|
||||
address
|
||||
|
||||
// check some random genesis account
|
||||
client.getAccount("xrn:1pdfr7xuckj6lhdphdde6peres9ufwgpsv87mag")
|
||||
|
||||
// your own account is undefined unless you did this before
|
||||
client.getAccount()
|
||||
```
|
||||
|
||||
Hit the faucet with your address (in browser): https://regen.vitwit.com/faucet then continue in node
|
||||
|
||||
```ts
|
||||
// should have tokens now
|
||||
client.getAccount()
|
||||
```
|
||||
|
||||
At this point you can continue all the other behaviors from above, looking at codes, etc.
|
||||
Do note that the ERC contract is code `5` on this network (instead of `1` above).
|
||||
|
||||
### Importing keys from `wasmcli`
|
||||
|
||||
If you are using the go commands and have tokens there, you may want to reuse the same account.
|
||||
(If you don't know what this is, just skip this section). You can reuse the mnemonic between the
|
||||
Go tooling and the Node.js tooling, but this violates all security protocols - only use this for
|
||||
testnets. In the future we will offer proper encrypted key management for CosmWasm-JS.
|
||||
|
||||
(You can replace `wasmcli` with `xrncli` and use `regenOptions` if you wish to use that testnet)
|
||||
|
||||
Create a new key - note mnemonic and address
|
||||
|
||||
```sh
|
||||
$ wasmcli keys add demo2
|
||||
|
||||
- name: demo2
|
||||
type: local
|
||||
address: cosmos1d4ut3z9c0kplgz5ma9t6ee637tagjqfyu4sxyl
|
||||
pubkey: cosmospub1addwnpepqtagg2smk2zvj77xaslej2wevwz7jft0q5hj5yuwvek3r6z0ufjtxnde4rq
|
||||
mnemonic: ""
|
||||
threshold: 0
|
||||
pubkeys: []
|
||||
|
||||
|
||||
**Important** write this mnemonic phrase in a safe place.
|
||||
It is the only way to recover your account if you ever forget your password.
|
||||
|
||||
cousin nephew vintage label empty sunny cargo mushroom photo side clarify sleep solid entire deal tattoo vehicle record discover arrive sting staff salt uncle
|
||||
```
|
||||
|
||||
Save mnemonic to a file
|
||||
|
||||
```sh
|
||||
echo "cousin nephew vintage label empty sunny cargo mushroom photo side clarify sleep solid entire deal tattoo vehicle record discover arrive sting staff salt uncle" > wasmcli.key
|
||||
```
|
||||
|
||||
Load it up in cosmwasm-js: `./bin/cosmwasm-cli --init examples/helpers.ts`
|
||||
|
||||
```ts
|
||||
const mnemonic = loadOrCreateMnemonic("wasmcli.key");
|
||||
const {address, client} = await connect(mnemonic, regenOptions);
|
||||
|
||||
// this should match what you got on the cli - showing compatibility
|
||||
address
|
||||
```
|
||||
|
||||
Once you have access to the same key as in the cli, you can use those tokens to play with contracts
|
||||
|
||||
## License
|
||||
|
||||
This package is part of the cosmwasm-js repository, licensed under the Apache
|
||||
|
||||
@ -1,101 +1,95 @@
|
||||
/* eslint-disable @typescript-eslint/camelcase */
|
||||
import {
|
||||
logs
|
||||
} from "@cosmwasm/sdk";
|
||||
|
||||
const defaultHttpUrl = "http://localhost:1317";
|
||||
const defaultNetworkId = "testing";
|
||||
const defaultFee: types.StdFee = {
|
||||
amount: [
|
||||
{
|
||||
amount: "5000",
|
||||
denom: "ucosm",
|
||||
},
|
||||
],
|
||||
gas: "890000",
|
||||
interface Options {
|
||||
httpUrl: string;
|
||||
networkId: string;
|
||||
feeToken: string;
|
||||
gasPrice: number;
|
||||
bech32prefix: string;
|
||||
};
|
||||
|
||||
const faucetMnemonic =
|
||||
"economy stock theory fatal elder harbor betray wasp final emotion task crumble siren bottom lizard educate guess current outdoor pair theory focus wife stone";
|
||||
const faucetAddress = "cosmos1pkptre7fdkl6gfrzlesjjvhxhlc3r4gmmk8rs6";
|
||||
|
||||
const pen = await Secp256k1Pen.fromMnemonic(faucetMnemonic);
|
||||
const client = new RestClient(defaultHttpUrl);
|
||||
|
||||
const networkId = "testing";
|
||||
|
||||
|
||||
// helper functions
|
||||
const instantiateContract = async (initClient: RestClient, initPen: Secp256k1Pen, codeId: number, msg: object, transferAmount?: types.Coin[]): Promise<string> => {
|
||||
const memo = "Create an ERC20 instance";
|
||||
const sender = pubkeyToAddress({ "type": types.pubkeyType.secp256k1, "value": toBase64(initPen.pubkey)}, "cosmos");
|
||||
const instantiateContractMsg = {
|
||||
type: "wasm/instantiate",
|
||||
value: {
|
||||
sender: sender,
|
||||
code_id: codeId.toString(),
|
||||
init_msg: msg,
|
||||
init_funds: transferAmount || [],
|
||||
},
|
||||
};
|
||||
const account = (await initClient.authAccounts(faucetAddress)).result.value;
|
||||
const signBytes = makeSignBytes([instantiateContractMsg], defaultFee, networkId, memo, account);
|
||||
const signature = await initPen.sign(signBytes);
|
||||
const signedTx = {
|
||||
msg: [instantiateContractMsg],
|
||||
fee: defaultFee,
|
||||
memo: memo,
|
||||
signatures: [signature],
|
||||
};
|
||||
const result = await initClient.postTx(signedTx);
|
||||
if (result.code) {
|
||||
throw new Error(`Failed tx: (${result.code}): ${result.raw_log}`)
|
||||
}
|
||||
const instantiationLogs = logs.parseLogs(result.logs);
|
||||
const contractAddress = logs.findAttribute(instantiationLogs, "message", "contract_address").value;
|
||||
return contractAddress;
|
||||
const defaultOptions: Options = {
|
||||
httpUrl: "https://lcd.demo-07.cosmwasm.com",
|
||||
networkId: "testing",
|
||||
feeToken: "ucosm",
|
||||
gasPrice: 0.025,
|
||||
bech32prefix: "cosmos",
|
||||
}
|
||||
|
||||
// helper functions
|
||||
const executeContract = async (execClient: RestClient, execPen: Secp256k1Pen, contractAddr: string, msg: object, transferAmount?: types.Coin[]): Promise<readonly logs.Log[]> => {
|
||||
const memo = "Create an ERC20 instance";
|
||||
const sender = pubkeyToAddress({ "type": types.pubkeyType.secp256k1, "value": toBase64(execPen.pubkey)}, "cosmos");
|
||||
const instantiateContractMsg = {
|
||||
type: "wasm/execute",
|
||||
value: {
|
||||
sender: sender,
|
||||
contract: contractAddr,
|
||||
msg: msg,
|
||||
sent_funds: transferAmount || [],
|
||||
},
|
||||
const defaultFaucetUrl = "https://faucet.demo-07.cosmwasm.com/credit";
|
||||
|
||||
const buildFeeTable = (feeToken: string, gasPrice: number): FeeTable => {
|
||||
const stdFee = (gas: number, denom: string, price: number) => {
|
||||
const amount = Math.floor(gas * price);
|
||||
return {
|
||||
amount: [{ amount: amount.toString(), denom: denom }],
|
||||
gas: gas.toString(),
|
||||
}
|
||||
};
|
||||
const account = (await execClient.authAccounts(faucetAddress)).result.value;
|
||||
const signBytes = makeSignBytes([instantiateContractMsg], defaultFee, networkId, memo, account);
|
||||
const signature = await execPen.sign(signBytes);
|
||||
const signedTx = {
|
||||
msg: [instantiateContractMsg],
|
||||
fee: defaultFee,
|
||||
memo: memo,
|
||||
signatures: [signature],
|
||||
};
|
||||
const result = await execClient.postTx(signedTx);
|
||||
if (result.code) {
|
||||
throw new Error(`Failed tx: (${result.code}): ${result.raw_log}`)
|
||||
|
||||
return {
|
||||
upload: stdFee(1000000, feeToken, gasPrice),
|
||||
init: stdFee(500000, feeToken, gasPrice),
|
||||
exec: stdFee(200000, feeToken, gasPrice),
|
||||
send: stdFee(80000, feeToken, gasPrice),
|
||||
}
|
||||
const execLogs = logs.parseLogs(result.logs);
|
||||
return execLogs;
|
||||
};
|
||||
|
||||
// TODO: hit faucet
|
||||
// if (config.faucetUrl) {
|
||||
// const acct = await client.getAccount();
|
||||
// if (!acct?.balance?.length) {
|
||||
// await ky.post(config.faucetUrl, { json: { ticker: "COSM", address } });
|
||||
// }
|
||||
// }
|
||||
|
||||
const penAddress = (pen: Secp256k1Pen, prefix: string): string => {
|
||||
const pubkey = encodeSecp256k1Pubkey(pen.pubkey);
|
||||
return pubkeyToAddress(pubkey, prefix);
|
||||
}
|
||||
|
||||
const connect = async (mnemonic: string, opts: Partial<Options>): Promise<{
|
||||
client: SigningCosmWasmClient,
|
||||
address: string,
|
||||
}> => {
|
||||
const options: Options = {...defaultOptions, ...opts};
|
||||
const feeTable = buildFeeTable(options.feeToken, options.gasPrice);
|
||||
const pen = await Secp256k1Pen.fromMnemonic(mnemonic);
|
||||
const address = penAddress(pen, options.bech32prefix);
|
||||
|
||||
const client = new SigningCosmWasmClient(options.httpUrl, address, signBytes => pen.sign(signBytes), feeTable);
|
||||
return {client, address};
|
||||
};
|
||||
|
||||
// smartQuery assumes the content is proper JSON data and parses before returning it
|
||||
const smartQuery = async (client: RestClient, addr: string, query: object): Promise<any> => {
|
||||
const smartQuery = async (client: CosmWasmClient, addr: string, query: object): Promise<any> => {
|
||||
const bin = await client.queryContractSmart(addr, query);
|
||||
return JSON.parse(fromUtf8(bin));
|
||||
}
|
||||
|
||||
|
||||
const randomAddress = async (): Promise<string> => {
|
||||
const mnemonic = Bip39.encode(Random.getBytes(16)).toString();
|
||||
const randomPen = await Secp256k1Pen.fromMnemonic(mnemonic);
|
||||
const pubkey = encodeSecp256k1Pubkey(randomPen.pubkey);
|
||||
return pubkeyToAddress(pubkey, "cosmos");
|
||||
// loadOrCreateMnemonic will try to load a mnemonic from the file.
|
||||
// If missing, it will generate a random one and save to the file.
|
||||
//
|
||||
// This is not secure, but does allow simple developer access to persist a
|
||||
// mnemonic between sessions
|
||||
const loadOrCreateMnemonic = (filename: string): string => {
|
||||
try {
|
||||
const mnemonic = fs.readFileSync(filename, "utf8");
|
||||
return mnemonic.trim();
|
||||
} catch (err) {
|
||||
const mnemonic = Bip39.encode(Random.getBytes(16)).toString();
|
||||
fs.writeFileSync(filename, mnemonic, "utf8");
|
||||
return mnemonic;
|
||||
}
|
||||
}
|
||||
|
||||
const hitFaucet = async (faucetUrl: string, address: string, ticker: string): Promise<void> => {
|
||||
const r = await axios.post(defaultFaucetUrl, { ticker, address });
|
||||
console.log(r.status);
|
||||
console.log(r.data);
|
||||
}
|
||||
|
||||
const randomAddress = async (prefix: string): Promise<string> => {
|
||||
const mnemonic = Bip39.encode(Random.getBytes(16)).toString();
|
||||
const pen = await Secp256k1Pen.fromMnemonic(mnemonic);
|
||||
const pubkey = encodeSecp256k1Pubkey(pen.pubkey);
|
||||
return pubkeyToAddress(pubkey, prefix);
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user