Merge pull request #150 from CosmWasm/cleanup-cli

Update helpers to modern api, demo net
This commit is contained in:
Ethan Frey 2020-03-20 12:38:54 +01:00 committed by GitHub
commit 17916b07bb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 234 additions and 123 deletions

3
.gitignore vendored
View File

@ -14,3 +14,6 @@ node_modules/
# IDE-specific
.vscode/
# demo mnemonics from cli
*.key

View File

@ -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

View File

@ -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);
}