diff --git a/packages/fast-usdc/src/cli/cli.js b/packages/fast-usdc/src/cli/cli.js index c4504830833..42d63045f56 100644 --- a/packages/fast-usdc/src/cli/cli.js +++ b/packages/fast-usdc/src/cli/cli.js @@ -1,4 +1,9 @@ -import { Command } from 'commander'; +import { assertParsableNumber } from '@agoric/zoe/src/contractSupport/ratio.js'; +import { + Command, + InvalidArgumentError, + InvalidOptionArgumentError, +} from 'commander'; import { existsSync, mkdirSync, readFileSync } from 'fs'; import { fileURLToPath } from 'url'; import { dirname, resolve } from 'path'; @@ -69,19 +74,28 @@ export const initProgram = ( '--eth-seed ', 'Seed phrase for Ethereum account. CAUTION: Stored unencrypted in file system', ) + .requiredOption( + '--agoric-seed ', + 'Seed phrase for Agoric LP account. CAUTION: Stored unencrypted in file system', + ) .option( '--agoric-rpc [url]', 'Agoric RPC endpoint', + 'http://127.0.0.1:26656', + ) + .option( + '--agoric-api [url]', + 'Agoric RPC endpoint', 'http://127.0.0.1:1317', ) + .option('--noble-rpc [url]', 'Noble RPC endpoint', 'http://127.0.0.1:26657') .option('--noble-api [url]', 'Noble API endpoint', 'http://127.0.0.1:1318') + .option('--eth-rpc [url]', 'Ethereum RPC Endpoint', 'http://127.0.0.1:8545') .option( '--noble-to-agoric-channel [channel]', 'Channel ID on Noble for Agoric', 'channel-21', ) - .option('--noble-rpc [url]', 'Noble RPC endpoint', 'http://127.0.0.1:26657') - .option('--eth-rpc [url]', 'Ethereum RPC Endpoint', 'http://127.0.0.1:8545') .option( '--token-messenger-address [address]', 'Address of TokenMessenger contract', @@ -109,10 +123,15 @@ export const initProgram = ( '--eth-seed [string]', 'Seed phrase for Ethereum account. CAUTION: Stored unencrypted in file system', ) + .option( + '--agoric-seed ', + 'Seed phrase for Agoric LP account. CAUTION: Stored unencrypted in file system', + ) .option('--agoric-rpc [url]', 'Agoric RPC endpoint') + .option('--agoric-api [url]', 'Agoric API endpoint') .option('--noble-rpc [url]', 'Noble RPC endpoint') - .option('--eth-rpc [url]', 'Ethereum RPC Endpoint') .option('--noble-api [url]', 'Noble API endpoint') + .option('--eth-rpc [url]', 'Ethereum RPC Endpoint') .option( '--noble-to-agoric-channel [channel]', 'Channel ID on Noble for Agoric', @@ -129,9 +148,35 @@ export const initProgram = ( await configHelpers.update(makeConfigFile(), options); }); + /** @param {string} value */ + const parseDecimal = value => { + try { + assertParsableNumber(value); + } catch { + throw new InvalidArgumentError('Not a decimal number.'); + } + return value; + }; + + /** + * @param {string} str + * @returns {'auto' | number} + */ + const parseFee = str => { + if (str === 'auto') return 'auto'; + const num = parseFloat(str); + if (Number.isNaN(num)) { + throw new InvalidOptionArgumentError('Fee must be a number.'); + } + return num; + }; + program .command('deposit') .description('Offer assets to the liquidity pool') + .argument('', 'USDC to give', parseDecimal) + .option('--id [offer-id]', 'Offer ID') + .option('--fee [fee]', 'Cosmos fee', parseFee) .action(() => { console.error('TODO actually send deposit'); // TODO: Implement deposit logic @@ -140,6 +185,9 @@ export const initProgram = ( program .command('withdraw') .description('Withdraw assets from the liquidity pool') + .argument('', 'USDC to withdraw', parseDecimal) + .option('--id [offer-id]', 'Offer ID') + .option('--fee [fee]', 'Cosmos fee', parseFee) .action(() => { console.error('TODO actually send withdrawal'); // TODO: Implement withdraw logic diff --git a/packages/fast-usdc/test/cli/cli.test.ts b/packages/fast-usdc/test/cli/cli.test.ts index 6726da3d775..ae0d53216a5 100644 --- a/packages/fast-usdc/test/cli/cli.test.ts +++ b/packages/fast-usdc/test/cli/cli.test.ts @@ -105,12 +105,78 @@ test('shows help for config show command', async t => { t.snapshot(output); }); +test('shows help for deposit command', async t => { + const output = await collectStdOut([CLI_PATH, 'deposit', '-h']); + + t.snapshot(output); +}); + +test('shows help for withdraw command', async t => { + const output = await collectStdOut([CLI_PATH, 'withdraw', '-h']); + + t.snapshot(output); +}); + +test('shows error when deposit command is run without options', async t => { + const output = await collectStdErr([CLI_PATH, 'deposit']); + + t.snapshot(output); +}); + +test('shows error when deposit command is run with invalid amount', async t => { + const output = await collectStdErr([CLI_PATH, 'deposit', 'not-a-number']); + + t.snapshot(output); +}); + +test('shows error when deposit command is run with invalid fee', async t => { + const output = await collectStdErr([ + CLI_PATH, + 'deposit', + '50', + '--fee', + 'not-a-number', + ]); + + t.snapshot(output); +}); + +test('shows error when withdraw command is run without options', async t => { + const output = await collectStdErr([CLI_PATH, 'withdraw']); + + t.snapshot(output); +}); + +test('shows error when withdraw command is run with invalid amount', async t => { + const output = await collectStdErr([CLI_PATH, 'withdraw', 'not-a-number']); + + t.snapshot(output); +}); + +test('shows error when withdraw command is run with invalid fee', async t => { + const output = await collectStdErr([ + CLI_PATH, + 'withdraw', + '50', + '--fee', + 'not-a-number', + ]); + + t.snapshot(output); +}); + test('shows error when config init command is run without options', async t => { const output = await collectStdErr([CLI_PATH, 'config', 'init']); t.snapshot(output); }); +test('shows error when transfer command is run without options', async t => { + const output = await collectStdErr([CLI_PATH, 'transfer']); + + t.snapshot(output); +}); + test('shows error when config init command is run without eth seed', async t => { const output = await collectStdErr([ CLI_PATH, @@ -118,6 +184,36 @@ test('shows error when config init command is run without eth seed', async t => 'init', '--noble-seed', 'foo', + '--agoric-seed', + 'bar', + ]); + + t.snapshot(output); +}); + +test('shows error when config init command is run without agoric seed', async t => { + const output = await collectStdErr([ + CLI_PATH, + 'config', + 'init', + '--noble-seed', + 'foo', + '--eth-seed', + 'bar', + ]); + + t.snapshot(output); +}); + +test('shows error when config init command is run without noble seed', async t => { + const output = await collectStdErr([ + CLI_PATH, + 'config', + 'init', + '--agoric-seed', + 'foo', + '--eth-seed', + 'bar', ]); t.snapshot(output); @@ -139,13 +235,17 @@ test('calls config init with default args', t => { 'foo', '--eth-seed', 'bar', + '--agoric-seed', + 'bazinga', ]); const args = config.getInitArgs(); t.is(args.shift().path, `${homeDir}config.json`); t.deepEqual(args, [ { - agoricRpc: 'http://127.0.0.1:1317', + agoricSeed: 'bazinga', + agoricApi: 'http://127.0.0.1:1317', + agoricRpc: 'http://127.0.0.1:26656', ethRpc: 'http://127.0.0.1:8545', ethSeed: 'bar', nobleRpc: 'http://127.0.0.1:26657', @@ -174,6 +274,10 @@ test('calls config init with optional args', t => { 'foo', '--eth-seed', 'bar', + '--agoric-seed', + 'bazinga', + '--agoric-api', + '127.0.0.1:0000', '--agoric-rpc', '127.0.0.1:1111', '--eth-rpc', @@ -194,6 +298,8 @@ test('calls config init with optional args', t => { t.is(args.shift().path, `${homeDir}config.json`); t.deepEqual(args, [ { + agoricApi: '127.0.0.1:0000', + agoricSeed: 'bazinga', agoricRpc: '127.0.0.1:1111', ethRpc: '127.0.0.1:2222', ethSeed: 'bar', @@ -223,6 +329,10 @@ test('calls config update with args', t => { 'foo', '--eth-seed', 'bar', + '--agoric-seed', + 'bazinga', + '--agoric-api', + '127.0.0.1:0000', '--agoric-rpc', '127.0.0.1:1111', '--eth-rpc', @@ -243,6 +353,8 @@ test('calls config update with args', t => { t.is(args.shift().path, `${homeDir}config.json`); t.deepEqual(args, [ { + agoricSeed: 'bazinga', + agoricApi: '127.0.0.1:0000', agoricRpc: '127.0.0.1:1111', ethRpc: '127.0.0.1:2222', ethSeed: 'bar', diff --git a/packages/fast-usdc/test/cli/snapshots/cli.test.ts.md b/packages/fast-usdc/test/cli/snapshots/cli.test.ts.md index a96f75d613c..b044f515a6a 100644 --- a/packages/fast-usdc/test/cli/snapshots/cli.test.ts.md +++ b/packages/fast-usdc/test/cli/snapshots/cli.test.ts.md @@ -13,18 +13,18 @@ Generated by [AVA](https://avajs.dev). CLI to interact with Fast USDC liquidity pool␊ ␊ Options:␊ - -V, --version output the version number␊ - --home Home directory to use for config (default:␊ - "~/.fast-usdc")␊ - -h, --help display help for command␊ + -V, --version output the version number␊ + --home Home directory to use for config (default:␊ + "~/.fast-usdc")␊ + -h, --help display help for command␊ ␊ Commands:␊ - config Manage config␊ - deposit Offer assets to the liquidity pool␊ - withdraw Withdraw assets from the liquidity pool␊ - transfer Transfer USDC from Ethereum/L2 to Cosmos via Fast␊ - USDC␊ - help [command] display help for command␊ + config Manage config␊ + deposit [options] Offer assets to the liquidity pool␊ + withdraw [options] Withdraw assets from the liquidity pool␊ + transfer Transfer USDC from Ethereum/L2 to Cosmos via Fast␊ + USDC␊ + help [command] display help for command␊ ` ## shows help for transfer command @@ -75,16 +75,21 @@ Generated by [AVA](https://avajs.dev). --eth-seed Seed phrase for Ethereum account.␊ CAUTION: Stored unencrypted in file␊ system␊ + --agoric-seed Seed phrase for Agoric LP account.␊ + CAUTION: Stored unencrypted in file␊ + system␊ --agoric-rpc [url] Agoric RPC endpoint (default:␊ + "http://127.0.0.1:26656")␊ + --agoric-api [url] Agoric RPC endpoint (default:␊ "http://127.0.0.1:1317")␊ - --noble-api [url] Noble API endpoint (default:␊ - "http://127.0.0.1:1318")␊ - --noble-to-agoric-channel [channel] Channel ID on Noble for Agoric (default:␊ - "channel-21")␊ --noble-rpc [url] Noble RPC endpoint (default:␊ "http://127.0.0.1:26657")␊ + --noble-api [url] Noble API endpoint (default:␊ + "http://127.0.0.1:1318")␊ --eth-rpc [url] Ethereum RPC Endpoint (default:␊ "http://127.0.0.1:8545")␊ + --noble-to-agoric-channel [channel] Channel ID on Noble for Agoric (default:␊ + "channel-21")␊ --token-messenger-address [address] Address of TokenMessenger contract␊ (default:␊ "0xbd3fa81b58ba92a82136038b25adec7066af3155")␊ @@ -107,10 +112,14 @@ Generated by [AVA](https://avajs.dev). --eth-seed [string] Seed phrase for Ethereum account.␊ CAUTION: Stored unencrypted in file␊ system␊ + --agoric-seed Seed phrase for Agoric LP account.␊ + CAUTION: Stored unencrypted in file␊ + system␊ --agoric-rpc [url] Agoric RPC endpoint␊ + --agoric-api [url] Agoric API endpoint␊ --noble-rpc [url] Noble RPC endpoint␊ - --eth-rpc [url] Ethereum RPC Endpoint␊ --noble-api [url] Noble API endpoint␊ + --eth-rpc [url] Ethereum RPC Endpoint␊ --noble-to-agoric-channel [channel] Channel ID on Noble for Agoric␊ --token-messenger-address [address] Address of TokenMessenger contract␊ --token-contract-address [address] Address of USDC token contract␊ @@ -129,6 +138,82 @@ Generated by [AVA](https://avajs.dev). -h, --help display help for command␊ ` +## shows help for deposit command + +> Snapshot 1 + + `Usage: fast-usdc deposit [options] ␊ + ␊ + Offer assets to the liquidity pool␊ + ␊ + Arguments:␊ + give USDC to give␊ + ␊ + Options:␊ + --id [offer-id] Offer ID␊ + --fee [fee] Cosmos fee␊ + -h, --help display help for command␊ + ` + +## shows help for withdraw command + +> Snapshot 1 + + `Usage: fast-usdc withdraw [options] ␊ + ␊ + Withdraw assets from the liquidity pool␊ + ␊ + Arguments:␊ + want USDC to withdraw␊ + ␊ + Options:␊ + --id [offer-id] Offer ID␊ + --fee [fee] Cosmos fee␊ + -h, --help display help for command␊ + ` + +## shows error when deposit command is run without options + +> Snapshot 1 + + `error: missing required argument 'give'␊ + ` + +## shows error when deposit command is run with invalid amount + +> Snapshot 1 + + `error: command-argument value 'not-a-number' is invalid for argument 'give'. Not a decimal number.␊ + ` + +## shows error when deposit command is run with invalid fee + +> Snapshot 1 + + `error: option '--fee [fee]' argument 'not-a-number' is invalid. Fee must be a number.␊ + ` + +## shows error when withdraw command is run without options + +> Snapshot 1 + + `error: missing required argument 'want'␊ + ` + +## shows error when withdraw command is run with invalid amount + +> Snapshot 1 + + `error: command-argument value 'not-a-number' is invalid for argument 'want'. Not a decimal number.␊ + ` + +## shows error when withdraw command is run with invalid fee + +> Snapshot 1 + + `error: option '--fee [fee]' argument 'not-a-number' is invalid. Fee must be a number.␊ + ` + ## shows error when config init command is run without options > Snapshot 1 @@ -136,9 +221,30 @@ Generated by [AVA](https://avajs.dev). `error: required option '--noble-seed ' not specified␊ ` +## shows error when transfer command is run without options + +> Snapshot 1 + + `error: missing required argument 'amount'␊ + ` + ## shows error when config init command is run without eth seed > Snapshot 1 `error: required option '--eth-seed ' not specified␊ ` + +## shows error when config init command is run without agoric seed + +> Snapshot 1 + + `error: required option '--agoric-seed ' not specified␊ + ` + +## shows error when config init command is run without noble seed + +> Snapshot 1 + + `error: required option '--noble-seed ' not specified␊ + ` diff --git a/packages/fast-usdc/test/cli/snapshots/cli.test.ts.snap b/packages/fast-usdc/test/cli/snapshots/cli.test.ts.snap index c2e3eb0d1bd..ab682b37ff1 100644 Binary files a/packages/fast-usdc/test/cli/snapshots/cli.test.ts.snap and b/packages/fast-usdc/test/cli/snapshots/cli.test.ts.snap differ