diff --git a/.gitignore b/.gitignore index 9c97bbd..f246192 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,5 @@ node_modules dist .env +*.tgz +.reviews/ diff --git a/README.md b/README.md index 58f5a01..46fb982 100644 --- a/README.md +++ b/README.md @@ -20,12 +20,26 @@ A command-line interface that wraps [@mento-protocol/mento-sdk](https://github.c ```bash # Install globally -npm install -g mento-cli +npm install -g @mento-protocol/cli # Or run directly without installing -npx mento-cli +npx @mento-protocol/cli ``` +The package name is `@mento-protocol/cli`; the installed binary is `mento`. + +### Installing from source + +```bash +git clone https://github.com/mento-protocol/mento-cli.git +cd mento-cli +npm install +npm pack +npm install -g ./mento-protocol-cli-*.tgz +``` + +Use `npm pack` rather than `npm install -g .` — Volta and some npm setups produce a broken symlink when linking a local package directly. + Requires **Node.js >= 18**. ## Quick Start @@ -37,8 +51,8 @@ mento tokens # Discover available trading routes mento routes -# Get a quote for swapping 100 USDm to CELO -mento quote USDm CELO 100 +# Get a quote for swapping 100 USDm to USDC +mento quote USDm USDC 100 # Show protocol overview mento info @@ -70,7 +84,7 @@ Discover trading routes between token pairs. ```bash mento routes # List all available routes -mento routes --from USDm --to CELO # Filter routes by token pair +mento routes --from USDm --to USDC # Filter routes by token pair mento routes --direct # Show only direct (single-hop) routes mento routes --fresh # Bypass cache, fetch live from chain mento routes --graph # Show route graph visualization @@ -109,10 +123,10 @@ mento pools --json # Output as JSON Get swap quotes for a token pair and amount. ```bash -mento quote USDm CELO 100 # Quote 100 USDm → CELO -mento quote CELO USDm 50 # Quote 50 CELO → USDm -mento quote USDm CELO 100 --all-routes # Compare quotes across all routes -mento quote USDm CELO 100 --json # Output as JSON +mento quote USDm USDC 100 # Quote 100 USDm → USDC +mento quote USDC USDm 50 # Quote 50 USDC → USDm +mento quote USDm USDC 100 --all-routes # Compare quotes across all routes +mento quote USDm USDC 100 --json # Output as JSON ``` **Positional arguments:** ` ` @@ -128,10 +142,10 @@ mento quote USDm CELO 100 --json # Output as JSON Execute a token swap on-chain. ```bash -mento swap USDm CELO 100 --private-key 0x... # Swap with inline key -mento swap USDm CELO 100 --keyfile ./key.txt # Swap with keyfile -mento swap USDm CELO 100 --dry-run # Preview without sending tx -mento swap USDm CELO 100 --slippage 1 --yes # 1% slippage, skip prompt +mento swap USDm USDC 100 --private-key 0x... # Swap with inline key +mento swap USDm USDC 100 --keyfile ./key.txt # Swap with keyfile +mento swap USDm USDC 100 --dry-run # Preview without sending tx +mento swap USDm USDC 100 --slippage 1 --yes # 1% slippage, skip prompt ``` **Positional arguments:** ` ` @@ -153,7 +167,7 @@ Check trading status and limits. ```bash mento trading status # Check trading status for all routes -mento trading status USDm CELO # Check if a specific pair is tradable +mento trading status USDm USDC # Check if a specific pair is tradable mento trading limits # Show trading limits for all pools mento trading limits --json # Output limits as JSON ``` @@ -227,16 +241,16 @@ The chain registry maps chain names to their chain IDs and default RPC endpoints ```bash # Full workflow: inspect → quote → swap mento tokens --all -mento routes --from USDm --to CELO -mento quote USDm CELO 100 -mento swap USDm CELO 100 --private-key 0x... --slippage 0.5 +mento routes --from USDm --to USDC +mento quote USDm USDC 100 +mento swap USDm USDC 100 --private-key 0x... --slippage 0.5 # Pipe JSON output to jq for scripting mento tokens --json | jq '.[] | .symbol' -mento quote USDm CELO 100 --json | jq '.amountOut' +mento quote USDm USDC 100 --json | jq '.amountOut' # Check if trading is active before swapping -mento trading status USDm CELO +mento trading status USDm USDC # Refresh cached data if results seem stale mento cache routes @@ -247,7 +261,6 @@ mento cache tokens - **SDK**: [github.com/mento-protocol/mento-sdk](https://github.com/mento-protocol/mento-sdk) - **Protocol**: [mento.org](https://mento.org) -- **PRD**: [docs/PRD.md](docs/PRD.md) ## License diff --git a/bin/mento.js b/bin/mento.js old mode 100644 new mode 100755 diff --git a/package-lock.json b/package-lock.json index 6607f74..dc019e6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { - "name": "mento-cli", - "version": "0.1.0", + "name": "@mento-protocol/cli", + "version": "3.0.0", "lockfileVersion": 3, "requires": true, "packages": { "": { - "name": "mento-cli", - "version": "0.1.0", + "name": "@mento-protocol/cli", + "version": "3.0.0", "license": "MIT", "dependencies": { "@mento-protocol/mento-sdk": "^3.0.0", @@ -44,9 +44,9 @@ } }, "node_modules/@esbuild/aix-ppc64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.3.tgz", - "integrity": "sha512-9fJMTNFTWZMh5qwrBItuziu834eOCUcEqymSH7pY+zoMVEZg3gcPuBNxH1EvfVYe9h0x/Ptw8KBzv7qxb7l8dg==", + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.7.tgz", + "integrity": "sha512-EKX3Qwmhz1eMdEJokhALr0YiD0lhQNwDqkPYyPhiSwKrh7/4KRjQc04sZ8db+5DVVnZ1LmbNDI1uAMPEUBnQPg==", "cpu": [ "ppc64" ], @@ -61,9 +61,9 @@ } }, "node_modules/@esbuild/android-arm": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.3.tgz", - "integrity": "sha512-i5D1hPY7GIQmXlXhs2w8AWHhenb00+GxjxRncS2ZM7YNVGNfaMxgzSGuO8o8SJzRc/oZwU2bcScvVERk03QhzA==", + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.7.tgz", + "integrity": "sha512-jbPXvB4Yj2yBV7HUfE2KHe4GJX51QplCN1pGbYjvsyCZbQmies29EoJbkEc+vYuU5o45AfQn37vZlyXy4YJ8RQ==", "cpu": [ "arm" ], @@ -78,9 +78,9 @@ } }, "node_modules/@esbuild/android-arm64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.3.tgz", - "integrity": "sha512-YdghPYUmj/FX2SYKJ0OZxf+iaKgMsKHVPF1MAq/P8WirnSpCStzKJFjOjzsW0QQ7oIAiccHdcqjbHmJxRb/dmg==", + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.7.tgz", + "integrity": "sha512-62dPZHpIXzvChfvfLJow3q5dDtiNMkwiRzPylSCfriLvZeq0a1bWChrGx/BbUbPwOrsWKMn8idSllklzBy+dgQ==", "cpu": [ "arm64" ], @@ -95,9 +95,9 @@ } }, "node_modules/@esbuild/android-x64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.3.tgz", - "integrity": "sha512-IN/0BNTkHtk8lkOM8JWAYFg4ORxBkZQf9zXiEOfERX/CzxW3Vg1ewAhU7QSWQpVIzTW+b8Xy+lGzdYXV6UZObQ==", + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.7.tgz", + "integrity": "sha512-x5VpMODneVDb70PYV2VQOmIUUiBtY3D3mPBG8NxVk5CogneYhkR7MmM3yR/uMdITLrC1ml/NV1rj4bMJuy9MCg==", "cpu": [ "x64" ], @@ -112,9 +112,9 @@ } }, "node_modules/@esbuild/darwin-arm64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.3.tgz", - "integrity": "sha512-Re491k7ByTVRy0t3EKWajdLIr0gz2kKKfzafkth4Q8A5n1xTHrkqZgLLjFEHVD+AXdUGgQMq+Godfq45mGpCKg==", + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.7.tgz", + "integrity": "sha512-5lckdqeuBPlKUwvoCXIgI2D9/ABmPq3Rdp7IfL70393YgaASt7tbju3Ac+ePVi3KDH6N2RqePfHnXkaDtY9fkw==", "cpu": [ "arm64" ], @@ -129,9 +129,9 @@ } }, "node_modules/@esbuild/darwin-x64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.3.tgz", - "integrity": "sha512-vHk/hA7/1AckjGzRqi6wbo+jaShzRowYip6rt6q7VYEDX4LEy1pZfDpdxCBnGtl+A5zq8iXDcyuxwtv3hNtHFg==", + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.7.tgz", + "integrity": "sha512-rYnXrKcXuT7Z+WL5K980jVFdvVKhCHhUwid+dDYQpH+qu+TefcomiMAJpIiC2EM3Rjtq0sO3StMV/+3w3MyyqQ==", "cpu": [ "x64" ], @@ -146,9 +146,9 @@ } }, "node_modules/@esbuild/freebsd-arm64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.3.tgz", - "integrity": "sha512-ipTYM2fjt3kQAYOvo6vcxJx3nBYAzPjgTCk7QEgZG8AUO3ydUhvelmhrbOheMnGOlaSFUoHXB6un+A7q4ygY9w==", + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.7.tgz", + "integrity": "sha512-B48PqeCsEgOtzME2GbNM2roU29AMTuOIN91dsMO30t+Ydis3z/3Ngoj5hhnsOSSwNzS+6JppqWsuhTp6E82l2w==", "cpu": [ "arm64" ], @@ -163,9 +163,9 @@ } }, "node_modules/@esbuild/freebsd-x64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.3.tgz", - "integrity": "sha512-dDk0X87T7mI6U3K9VjWtHOXqwAMJBNN2r7bejDsc+j03SEjtD9HrOl8gVFByeM0aJksoUuUVU9TBaZa2rgj0oA==", + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.7.tgz", + "integrity": "sha512-jOBDK5XEjA4m5IJK3bpAQF9/Lelu/Z9ZcdhTRLf4cajlB+8VEhFFRjWgfy3M1O4rO2GQ/b2dLwCUGpiF/eATNQ==", "cpu": [ "x64" ], @@ -180,9 +180,9 @@ } }, "node_modules/@esbuild/linux-arm": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.3.tgz", - "integrity": "sha512-s6nPv2QkSupJwLYyfS+gwdirm0ukyTFNl3KTgZEAiJDd+iHZcbTPPcWCcRYH+WlNbwChgH2QkE9NSlNrMT8Gfw==", + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.7.tgz", + "integrity": "sha512-RkT/YXYBTSULo3+af8Ib0ykH8u2MBh57o7q/DAs3lTJlyVQkgQvlrPTnjIzzRPQyavxtPtfg0EopvDyIt0j1rA==", "cpu": [ "arm" ], @@ -197,9 +197,9 @@ } }, "node_modules/@esbuild/linux-arm64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.3.tgz", - "integrity": "sha512-sZOuFz/xWnZ4KH3YfFrKCf1WyPZHakVzTiqji3WDc0BCl2kBwiJLCXpzLzUBLgmp4veFZdvN5ChW4Eq/8Fc2Fg==", + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.7.tgz", + "integrity": "sha512-RZPHBoxXuNnPQO9rvjh5jdkRmVizktkT7TCDkDmQ0W2SwHInKCAV95GRuvdSvA7w4VMwfCjUiPwDi0ZO6Nfe9A==", "cpu": [ "arm64" ], @@ -214,9 +214,9 @@ } }, "node_modules/@esbuild/linux-ia32": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.3.tgz", - "integrity": "sha512-yGlQYjdxtLdh0a3jHjuwOrxQjOZYD/C9PfdbgJJF3TIZWnm/tMd/RcNiLngiu4iwcBAOezdnSLAwQDPqTmtTYg==", + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.7.tgz", + "integrity": "sha512-GA48aKNkyQDbd3KtkplYWT102C5sn/EZTY4XROkxONgruHPU72l+gW+FfF8tf2cFjeHaRbWpOYa/uRBz/Xq1Pg==", "cpu": [ "ia32" ], @@ -231,9 +231,9 @@ } }, "node_modules/@esbuild/linux-loong64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.3.tgz", - "integrity": "sha512-WO60Sn8ly3gtzhyjATDgieJNet/KqsDlX5nRC5Y3oTFcS1l0KWba+SEa9Ja1GfDqSF1z6hif/SkpQJbL63cgOA==", + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.7.tgz", + "integrity": "sha512-a4POruNM2oWsD4WKvBSEKGIiWQF8fZOAsycHOt6JBpZ+JN2n2JH9WAv56SOyu9X5IqAjqSIPTaJkqN8F7XOQ5Q==", "cpu": [ "loong64" ], @@ -248,9 +248,9 @@ } }, "node_modules/@esbuild/linux-mips64el": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.3.tgz", - "integrity": "sha512-APsymYA6sGcZ4pD6k+UxbDjOFSvPWyZhjaiPyl/f79xKxwTnrn5QUnXR5prvetuaSMsb4jgeHewIDCIWljrSxw==", + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.7.tgz", + "integrity": "sha512-KabT5I6StirGfIz0FMgl1I+R1H73Gp0ofL9A3nG3i/cYFJzKHhouBV5VWK1CSgKvVaG4q1RNpCTR2LuTVB3fIw==", "cpu": [ "mips64el" ], @@ -265,9 +265,9 @@ } }, "node_modules/@esbuild/linux-ppc64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.3.tgz", - "integrity": "sha512-eizBnTeBefojtDb9nSh4vvVQ3V9Qf9Df01PfawPcRzJH4gFSgrObw+LveUyDoKU3kxi5+9RJTCWlj4FjYXVPEA==", + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.7.tgz", + "integrity": "sha512-gRsL4x6wsGHGRqhtI+ifpN/vpOFTQtnbsupUF5R5YTAg+y/lKelYR1hXbnBdzDjGbMYjVJLJTd2OFmMewAgwlQ==", "cpu": [ "ppc64" ], @@ -282,9 +282,9 @@ } }, "node_modules/@esbuild/linux-riscv64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.3.tgz", - "integrity": "sha512-3Emwh0r5wmfm3ssTWRQSyVhbOHvqegUDRd0WhmXKX2mkHJe1SFCMJhagUleMq+Uci34wLSipf8Lagt4LlpRFWQ==", + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.7.tgz", + "integrity": "sha512-hL25LbxO1QOngGzu2U5xeXtxXcW+/GvMN3ejANqXkxZ/opySAZMrc+9LY/WyjAan41unrR3YrmtTsUpwT66InQ==", "cpu": [ "riscv64" ], @@ -299,9 +299,9 @@ } }, "node_modules/@esbuild/linux-s390x": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.3.tgz", - "integrity": "sha512-pBHUx9LzXWBc7MFIEEL0yD/ZVtNgLytvx60gES28GcWMqil8ElCYR4kvbV2BDqsHOvVDRrOxGySBM9Fcv744hw==", + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.7.tgz", + "integrity": "sha512-2k8go8Ycu1Kb46vEelhu1vqEP+UeRVj2zY1pSuPdgvbd5ykAw82Lrro28vXUrRmzEsUV0NzCf54yARIK8r0fdw==", "cpu": [ "s390x" ], @@ -316,9 +316,9 @@ } }, "node_modules/@esbuild/linux-x64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.3.tgz", - "integrity": "sha512-Czi8yzXUWIQYAtL/2y6vogER8pvcsOsk5cpwL4Gk5nJqH5UZiVByIY8Eorm5R13gq+DQKYg0+JyQoytLQas4dA==", + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.7.tgz", + "integrity": "sha512-hzznmADPt+OmsYzw1EE33ccA+HPdIqiCRq7cQeL1Jlq2gb1+OyWBkMCrYGBJ+sxVzve2ZJEVeePbLM2iEIZSxA==", "cpu": [ "x64" ], @@ -333,9 +333,9 @@ } }, "node_modules/@esbuild/netbsd-arm64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.3.tgz", - "integrity": "sha512-sDpk0RgmTCR/5HguIZa9n9u+HVKf40fbEUt+iTzSnCaGvY9kFP0YKBWZtJaraonFnqef5SlJ8/TiPAxzyS+UoA==", + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.7.tgz", + "integrity": "sha512-b6pqtrQdigZBwZxAn1UpazEisvwaIDvdbMbmrly7cDTMFnw/+3lVxxCTGOrkPVnsYIosJJXAsILG9XcQS+Yu6w==", "cpu": [ "arm64" ], @@ -350,9 +350,9 @@ } }, "node_modules/@esbuild/netbsd-x64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.3.tgz", - "integrity": "sha512-P14lFKJl/DdaE00LItAukUdZO5iqNH7+PjoBm+fLQjtxfcfFE20Xf5CrLsmZdq5LFFZzb5JMZ9grUwvtVYzjiA==", + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.7.tgz", + "integrity": "sha512-OfatkLojr6U+WN5EDYuoQhtM+1xco+/6FSzJJnuWiUw5eVcicbyK3dq5EeV/QHT1uy6GoDhGbFpprUiHUYggrw==", "cpu": [ "x64" ], @@ -367,9 +367,9 @@ } }, "node_modules/@esbuild/openbsd-arm64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.3.tgz", - "integrity": "sha512-AIcMP77AvirGbRl/UZFTq5hjXK+2wC7qFRGoHSDrZ5v5b8DK/GYpXW3CPRL53NkvDqb9D+alBiC/dV0Fb7eJcw==", + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.7.tgz", + "integrity": "sha512-AFuojMQTxAz75Fo8idVcqoQWEHIXFRbOc1TrVcFSgCZtQfSdc1RXgB3tjOn/krRHENUB4j00bfGjyl2mJrU37A==", "cpu": [ "arm64" ], @@ -384,9 +384,9 @@ } }, "node_modules/@esbuild/openbsd-x64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.3.tgz", - "integrity": "sha512-DnW2sRrBzA+YnE70LKqnM3P+z8vehfJWHXECbwBmH/CU51z6FiqTQTHFenPlHmo3a8UgpLyH3PT+87OViOh1AQ==", + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.7.tgz", + "integrity": "sha512-+A1NJmfM8WNDv5CLVQYJ5PshuRm/4cI6WMZRg1by1GwPIQPCTs1GLEUHwiiQGT5zDdyLiRM/l1G0Pv54gvtKIg==", "cpu": [ "x64" ], @@ -401,9 +401,9 @@ } }, "node_modules/@esbuild/openharmony-arm64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.3.tgz", - "integrity": "sha512-NinAEgr/etERPTsZJ7aEZQvvg/A6IsZG/LgZy+81wON2huV7SrK3e63dU0XhyZP4RKGyTm7aOgmQk0bGp0fy2g==", + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.7.tgz", + "integrity": "sha512-+KrvYb/C8zA9CU/g0sR6w2RBw7IGc5J2BPnc3dYc5VJxHCSF1yNMxTV5LQ7GuKteQXZtspjFbiuW5/dOj7H4Yw==", "cpu": [ "arm64" ], @@ -418,9 +418,9 @@ } }, "node_modules/@esbuild/sunos-x64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.3.tgz", - "integrity": "sha512-PanZ+nEz+eWoBJ8/f8HKxTTD172SKwdXebZ0ndd953gt1HRBbhMsaNqjTyYLGLPdoWHy4zLU7bDVJztF5f3BHA==", + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.7.tgz", + "integrity": "sha512-ikktIhFBzQNt/QDyOL580ti9+5mL/YZeUPKU2ivGtGjdTYoqz6jObj6nOMfhASpS4GU4Q/Clh1QtxWAvcYKamA==", "cpu": [ "x64" ], @@ -435,9 +435,9 @@ } }, "node_modules/@esbuild/win32-arm64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.3.tgz", - "integrity": "sha512-B2t59lWWYrbRDw/tjiWOuzSsFh1Y/E95ofKz7rIVYSQkUYBjfSgf6oeYPNWHToFRr2zx52JKApIcAS/D5TUBnA==", + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.7.tgz", + "integrity": "sha512-7yRhbHvPqSpRUV7Q20VuDwbjW5kIMwTHpptuUzV+AA46kiPze5Z7qgt6CLCK3pWFrHeNfDd1VKgyP4O+ng17CA==", "cpu": [ "arm64" ], @@ -452,9 +452,9 @@ } }, "node_modules/@esbuild/win32-ia32": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.3.tgz", - "integrity": "sha512-QLKSFeXNS8+tHW7tZpMtjlNb7HKau0QDpwm49u0vUp9y1WOF+PEzkU84y9GqYaAVW8aH8f3GcBck26jh54cX4Q==", + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.7.tgz", + "integrity": "sha512-SmwKXe6VHIyZYbBLJrhOoCJRB/Z1tckzmgTLfFYOfpMAx63BJEaL9ExI8x7v0oAO3Zh6D/Oi1gVxEYr5oUCFhw==", "cpu": [ "ia32" ], @@ -469,9 +469,9 @@ } }, "node_modules/@esbuild/win32-x64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.3.tgz", - "integrity": "sha512-4uJGhsxuptu3OcpVAzli+/gWusVGwZZHTlS63hh++ehExkVT8SgiEf7/uC/PclrPPkLhZqGgCTjd0VWLo6xMqA==", + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.7.tgz", + "integrity": "sha512-56hiAJPhwQ1R4i+21FVF7V8kSD5zZTdHcVuRFMW0hn753vVfQN8xlx4uOPT4xoGH0Z/oVATuR82AiqSTDIpaHg==", "cpu": [ "x64" ], @@ -486,9 +486,9 @@ } }, "node_modules/@mento-protocol/mento-sdk": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@mento-protocol/mento-sdk/-/mento-sdk-3.0.0.tgz", - "integrity": "sha512-kvdbu9kH0UHP3T+64DNPuPaS5Wa+UwgJZbG4L0jdlmB5KlNu+mavBUXKQYX2+ZZBVsy9eY7bpqbv8Iu0R21j6A==", + "version": "3.2.5", + "resolved": "https://registry.npmjs.org/@mento-protocol/mento-sdk/-/mento-sdk-3.2.5.tgz", + "integrity": "sha512-ckScM4iHuqb6EG8eOR/FjUR896z4GOqr2Z6EjRi5PBIpeG24Typz4rminMlMOcSWxybxjCuczrYnauO0F2TDMw==", "license": "MIT", "dependencies": { "viem": "^2.21.44" @@ -574,9 +574,9 @@ } }, "node_modules/@types/node": { - "version": "22.19.13", - "resolved": "https://registry.npmjs.org/@types/node/-/node-22.19.13.tgz", - "integrity": "sha512-akNQMv0wW5uyRpD2v2IEyRSZiR+BeGuoB6L310EgGObO44HSMNT8z1xzio28V8qOrgYaopIDNA18YgdXd+qTiw==", + "version": "22.19.17", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.19.17.tgz", + "integrity": "sha512-wGdMcf+vPYM6jikpS/qhg6WiqSV/OhG+jeeHT/KlVqxYfD40iYJf9/AE1uQxVWFvU7MipKRkRv8NSHiCGgPr8Q==", "dev": true, "license": "MIT", "dependencies": { @@ -693,9 +693,9 @@ "license": "MIT" }, "node_modules/esbuild": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.3.tgz", - "integrity": "sha512-8VwMnyGCONIs6cWue2IdpHxHnAjzxnw2Zr7MkVxB2vjmQ2ivqGFb4LEG3SMnv0Gb2F/G/2yA8zUaiL1gywDCCg==", + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.7.tgz", + "integrity": "sha512-IxpibTjyVnmrIQo5aqNpCgoACA/dTKLTlhMHihVHhdkxKyPO1uBBthumT0rdHmcsk9uMonIWS0m4FljWzILh3w==", "dev": true, "hasInstallScript": true, "license": "MIT", @@ -706,32 +706,32 @@ "node": ">=18" }, "optionalDependencies": { - "@esbuild/aix-ppc64": "0.27.3", - "@esbuild/android-arm": "0.27.3", - "@esbuild/android-arm64": "0.27.3", - "@esbuild/android-x64": "0.27.3", - "@esbuild/darwin-arm64": "0.27.3", - "@esbuild/darwin-x64": "0.27.3", - "@esbuild/freebsd-arm64": "0.27.3", - "@esbuild/freebsd-x64": "0.27.3", - "@esbuild/linux-arm": "0.27.3", - "@esbuild/linux-arm64": "0.27.3", - "@esbuild/linux-ia32": "0.27.3", - "@esbuild/linux-loong64": "0.27.3", - "@esbuild/linux-mips64el": "0.27.3", - "@esbuild/linux-ppc64": "0.27.3", - "@esbuild/linux-riscv64": "0.27.3", - "@esbuild/linux-s390x": "0.27.3", - "@esbuild/linux-x64": "0.27.3", - "@esbuild/netbsd-arm64": "0.27.3", - "@esbuild/netbsd-x64": "0.27.3", - "@esbuild/openbsd-arm64": "0.27.3", - "@esbuild/openbsd-x64": "0.27.3", - "@esbuild/openharmony-arm64": "0.27.3", - "@esbuild/sunos-x64": "0.27.3", - "@esbuild/win32-arm64": "0.27.3", - "@esbuild/win32-ia32": "0.27.3", - "@esbuild/win32-x64": "0.27.3" + "@esbuild/aix-ppc64": "0.27.7", + "@esbuild/android-arm": "0.27.7", + "@esbuild/android-arm64": "0.27.7", + "@esbuild/android-x64": "0.27.7", + "@esbuild/darwin-arm64": "0.27.7", + "@esbuild/darwin-x64": "0.27.7", + "@esbuild/freebsd-arm64": "0.27.7", + "@esbuild/freebsd-x64": "0.27.7", + "@esbuild/linux-arm": "0.27.7", + "@esbuild/linux-arm64": "0.27.7", + "@esbuild/linux-ia32": "0.27.7", + "@esbuild/linux-loong64": "0.27.7", + "@esbuild/linux-mips64el": "0.27.7", + "@esbuild/linux-ppc64": "0.27.7", + "@esbuild/linux-riscv64": "0.27.7", + "@esbuild/linux-s390x": "0.27.7", + "@esbuild/linux-x64": "0.27.7", + "@esbuild/netbsd-arm64": "0.27.7", + "@esbuild/netbsd-x64": "0.27.7", + "@esbuild/openbsd-arm64": "0.27.7", + "@esbuild/openbsd-x64": "0.27.7", + "@esbuild/openharmony-arm64": "0.27.7", + "@esbuild/sunos-x64": "0.27.7", + "@esbuild/win32-arm64": "0.27.7", + "@esbuild/win32-ia32": "0.27.7", + "@esbuild/win32-x64": "0.27.7" } }, "node_modules/eventemitter3": { @@ -756,9 +756,9 @@ } }, "node_modules/get-tsconfig": { - "version": "4.13.6", - "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.13.6.tgz", - "integrity": "sha512-shZT/QMiSHc/YBLxxOkMtgSid5HFoauqCE3/exfsEcwg1WkeqjG+V40yBbBrsD+jW2HDXcs28xOfcbm2jI8Ddw==", + "version": "4.14.0", + "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.14.0.tgz", + "integrity": "sha512-yTb+8DXzDREzgvYmh6s9vHsSVCHeC0G3PI5bEXNBHtmshPnO+S5O7qgLEOn0I5QvMy6kpZN8K1NKGyilLb93wA==", "dev": true, "license": "MIT", "dependencies": { @@ -802,9 +802,9 @@ } }, "node_modules/ox": { - "version": "0.14.0", - "resolved": "https://registry.npmjs.org/ox/-/ox-0.14.0.tgz", - "integrity": "sha512-WLOB7IKnmI3Ol6RAqY7CJdZKl8QaI44LN91OGF1061YIeN6bL5IsFcdp7+oQShRyamE/8fW/CBRWhJAOzI35Dw==", + "version": "0.14.20", + "resolved": "https://registry.npmjs.org/ox/-/ox-0.14.20.tgz", + "integrity": "sha512-rby38C3nDn8eQkf29Zgw4hkCZJ64Qqi0zRPWL8ENUQ7JVuoITqrVtwWQgM/He19SCMUEc7hS/Sjw0jIOSLJhOw==", "funding": [ { "type": "github", @@ -921,9 +921,9 @@ "license": "MIT" }, "node_modules/viem": { - "version": "2.47.0", - "resolved": "https://registry.npmjs.org/viem/-/viem-2.47.0.tgz", - "integrity": "sha512-jU5e1E1s5E5M1y+YrELDnNar/34U8NXfVcRfxtVETigs2gS1vvW2ngnBoQUGBwLnNr0kNv+NUu4m10OqHByoFw==", + "version": "2.48.4", + "resolved": "https://registry.npmjs.org/viem/-/viem-2.48.4.tgz", + "integrity": "sha512-mReP/rgY2P+WeeRSG4sUvccCLKfyAW1C73Y3KkobAqgzYmVna9qyUMNE44xIUkDtfvRuC33r24UhF4baBYovsg==", "funding": [ { "type": "github", @@ -938,7 +938,7 @@ "@scure/bip39": "1.6.0", "abitype": "1.2.3", "isows": "1.0.7", - "ox": "0.14.0", + "ox": "0.14.20", "ws": "8.18.3" }, "peerDependencies": { diff --git a/package.json b/package.json index d4ce986..39ed003 100644 --- a/package.json +++ b/package.json @@ -1,14 +1,19 @@ { - "name": "mento-cli", - "version": "0.1.0", + "name": "@mento-protocol/cli", + "version": "3.0.0", "description": "CLI tool for the Mento Protocol — inspect tokens, routes, pools, get quotes, and execute swaps", "type": "commonjs", "bin": { "mento": "./bin/mento.js" }, + "files": [ + "bin", + "dist" + ], "scripts": { "build": "tsc", - "dev": "tsx src/index.ts" + "dev": "tsx src/index.ts", + "prepack": "tsc" }, "dependencies": { "@mento-protocol/mento-sdk": "^3.0.0", @@ -25,5 +30,8 @@ "engines": { "node": ">=18" }, + "publishConfig": { + "access": "public" + }, "license": "MIT" } diff --git a/src/commands/pools.ts b/src/commands/pools.ts index 4037f64..205e556 100644 --- a/src/commands/pools.ts +++ b/src/commands/pools.ts @@ -2,7 +2,7 @@ import { Command } from 'commander'; import chalk from 'chalk'; import { getMento, resolveChainId, type GlobalOptions } from '../lib/client.js'; import { output, formatAddress } from '../lib/format.js'; -import { resolveToken } from '../lib/utils.js'; +import { prefetchTokens, tokenSymbolOrAddress } from '../lib/utils.js'; import { handleError } from '../lib/errors.js'; import type { Pool, PoolDetails, FPMMPoolDetails, VirtualPoolDetails } from '@mento-protocol/mento-sdk'; @@ -11,16 +11,11 @@ interface PoolsOptions { details?: string; } -function getTokenSymbol(chainId: number, address: string): string { - const token = resolveToken(chainId, address); - return token?.symbol ?? formatAddress(address); -} - function poolToRow(pool: Pool, chainId: number): string[] { return [ formatAddress(pool.poolAddr), - getTokenSymbol(chainId, pool.token0), - getTokenSymbol(chainId, pool.token1), + tokenSymbolOrAddress(chainId, pool.token0), + tokenSymbolOrAddress(chainId, pool.token1), pool.poolType, ]; } @@ -29,9 +24,9 @@ function poolToData(pool: Pool, chainId: number): Record { return { poolAddr: pool.poolAddr, token0: pool.token0, - token0Symbol: getTokenSymbol(chainId, pool.token0), + token0Symbol: tokenSymbolOrAddress(chainId, pool.token0), token1: pool.token1, - token1Symbol: getTokenSymbol(chainId, pool.token1), + token1Symbol: tokenSymbolOrAddress(chainId, pool.token1), poolType: pool.poolType, factoryAddr: pool.factoryAddr, ...(pool.exchangeId ? { exchangeId: pool.exchangeId } : {}), @@ -54,7 +49,7 @@ function formatFPMMDetails(details: FPMMPoolDetails): void { console.log(` Price Diff: ${details.pricing.priceDifferencePercent.toFixed(4)}%`); console.log(` In Band: ${details.rebalancing.inBand ?? 'N/A'}`); } else { - console.log(chalk.yellow(`Pricing unavailable: ${details.pricingUnavailableReason ?? 'unknown'}`)); + console.log(chalk.yellow('Pricing unavailable (FX market may be closed)')); } console.log(''); @@ -108,7 +103,6 @@ function poolDetailsToData(details: PoolDetails): Record { priceDifferencePercent: d.pricing.priceDifferencePercent, } : null, - pricingUnavailableReason: d.pricingUnavailableReason, fees: { lpFeePercent: d.fees.lpFeePercent, protocolFeePercent: d.fees.protocolFeePercent, @@ -161,7 +155,6 @@ Examples: const chainId = resolveChainId(globalOpts.chain); if (options.details) { - // Show detailed info for a single pool const details = await mento.pools.getPoolDetails(options.details); if (jsonMode) { @@ -178,7 +171,11 @@ Examples: } // List all pools (with optional type filter) - let pools = await mento.pools.getPools(); + const [, allPools] = await Promise.all([ + prefetchTokens(mento, chainId), + mento.pools.getPools(), + ]); + let pools = allPools; if (options.type) { const filter = options.type.toLowerCase(); diff --git a/src/commands/quote.ts b/src/commands/quote.ts index e504d5c..b48ae1a 100644 --- a/src/commands/quote.ts +++ b/src/commands/quote.ts @@ -1,7 +1,7 @@ import { Command } from 'commander'; import chalk from 'chalk'; import { getMento, resolveChainId, type GlobalOptions } from '../lib/client.js'; -import { formatTokenAmount, output } from '../lib/format.js'; +import { formatTokenAmount, output, parseAmount } from '../lib/format.js'; import { resolveToken } from '../lib/utils.js'; import { handleError } from '../lib/errors.js'; import type { Route } from '@mento-protocol/mento-sdk'; @@ -10,13 +10,6 @@ interface QuoteOptions { allRoutes?: boolean; } -function parseAmount(amountStr: string, decimals: number): bigint { - // Support decimal input like "1.5" - const [intPart, fracPart = ''] = amountStr.split('.'); - const paddedFrac = fracPart.slice(0, decimals).padEnd(decimals, '0'); - return BigInt(intPart) * BigInt(10 ** decimals) + BigInt(paddedFrac); -} - function calcEffectivePrice(amountIn: bigint, amountOut: bigint, decimalsIn: number, decimalsOut: number): string { if (amountOut === BigInt(0)) return 'N/A'; // price = amountOut / amountIn (in human units) @@ -48,8 +41,10 @@ Examples: const jsonMode = globalOpts.json ?? false; const chainId = resolveChainId(globalOpts.chain); - const tokenIn = resolveToken(chainId, tokenInSymbol); - const tokenOut = resolveToken(chainId, tokenOutSymbol); + const [tokenIn, tokenOut] = await Promise.all([ + resolveToken(mento, chainId, tokenInSymbol), + resolveToken(mento, chainId, tokenOutSymbol), + ]); if (!tokenIn) { console.error(chalk.red(`Error: Token not found: ${tokenInSymbol}`)); diff --git a/src/commands/routes.ts b/src/commands/routes.ts index 346b3b1..c0ef2ab 100644 --- a/src/commands/routes.ts +++ b/src/commands/routes.ts @@ -2,7 +2,7 @@ import { Command } from 'commander'; import chalk from 'chalk'; import { getMento, resolveChainId, type GlobalOptions } from '../lib/client.js'; import { output } from '../lib/format.js'; -import { resolveToken } from '../lib/utils.js'; +import { prefetchTokens, resolveToken, tokenSymbolOrAddress } from '../lib/utils.js'; import { handleError } from '../lib/errors.js'; import type { Route, RouteWithCost } from '@mento-protocol/mento-sdk'; @@ -36,15 +36,6 @@ function routeToData(route: Route | RouteWithCost): Record { }; } -/** - * Resolve the symbol for a token address using the chain's token registry. - * Falls back to an abbreviated address if the token is not found. - */ -function symbolForAddress(chainId: number, address: string): string { - const token = resolveToken(chainId, address); - return token ? token.symbol : `${address.slice(0, 6)}...`; -} - /** * Build a list of edges (symbol pairs) for a route. * Direct routes produce one edge; multi-hop routes produce edges through @@ -91,7 +82,7 @@ function routeToEdges( // The next token in the hop is the one in this pool that isn't the current token const nextAddr = poolToken0 === currentAddr ? poolToken1 : poolToken0; - const nextSymbol = symbolForAddress(chainId, nextAddr); + const nextSymbol = tokenSymbolOrAddress(chainId, nextAddr); edges.push([currentSymbol, nextSymbol]); @@ -156,7 +147,10 @@ Examples: // --graph: output Mermaid flowchart and exit if (options.graph) { - const routes = await mento.routes.getRoutes({ cached: !options.fresh }); + const [, routes] = await Promise.all([ + prefetchTokens(mento, chainId), + mento.routes.getRoutes({ cached: !options.fresh }), + ]); const mermaid = buildMermaidGraph([...routes], chainId); console.log(mermaid); return; @@ -171,8 +165,10 @@ Examples: process.exit(1); } - const tokenIn = resolveToken(chainId, options.from); - const tokenOut = resolveToken(chainId, options.to); + const [tokenIn, tokenOut] = await Promise.all([ + resolveToken(mento, chainId, options.from), + resolveToken(mento, chainId, options.to), + ]); if (!tokenIn) { console.error(chalk.red(`Error: Token not found: ${options.from}`)); diff --git a/src/commands/swap.ts b/src/commands/swap.ts index 081403a..a8cb712 100644 --- a/src/commands/swap.ts +++ b/src/commands/swap.ts @@ -1,7 +1,7 @@ import { Command } from 'commander'; import chalk from 'chalk'; import { getMento, resolveChainId, type GlobalOptions } from '../lib/client.js'; -import { formatTokenAmount } from '../lib/format.js'; +import { formatTokenAmount, parseAmount } from '../lib/format.js'; import { resolveToken } from '../lib/utils.js'; import { handleError } from '../lib/errors.js'; import { createWallet, type WalletOptions } from '../lib/wallet.js'; @@ -14,12 +14,6 @@ interface SwapCommandOptions extends WalletOptions { yes?: boolean; } -function parseAmount(amountStr: string, decimals: number): bigint { - const [intPart, fracPart = ''] = amountStr.split('.'); - const paddedFrac = fracPart.slice(0, decimals).padEnd(decimals, '0'); - return BigInt(intPart) * BigInt(10 ** decimals) + BigInt(paddedFrac); -} - function callParamsToJson(params: CallParams): Record { return { to: params.to, @@ -70,8 +64,10 @@ Examples: const jsonMode = globalOpts.json ?? false; // Resolve tokens - const tokenIn = resolveToken(chainId, tokenInSymbol); - const tokenOut = resolveToken(chainId, tokenOutSymbol); + const [tokenIn, tokenOut] = await Promise.all([ + resolveToken(mento, chainId, tokenInSymbol), + resolveToken(mento, chainId, tokenOutSymbol), + ]); if (!tokenIn) { console.error(chalk.red(`Error: Token not found: ${tokenInSymbol}`)); diff --git a/src/commands/trading.ts b/src/commands/trading.ts index a26e8a9..babee25 100644 --- a/src/commands/trading.ts +++ b/src/commands/trading.ts @@ -2,15 +2,10 @@ import { Command } from 'commander'; import chalk from 'chalk'; import { getMento, resolveChainId, type GlobalOptions } from '../lib/client.js'; import { formatAddress, output } from '../lib/format.js'; -import { resolveToken } from '../lib/utils.js'; +import { prefetchTokens, resolveTokenSync, tokenSymbolOrAddress } from '../lib/utils.js'; import { handleError } from '../lib/errors.js'; import type { Pool, TradingLimit, PoolTradabilityStatus } from '@mento-protocol/mento-sdk'; -function getTokenSymbol(chainId: number, address: string): string { - const token = resolveToken(chainId, address); - return token?.symbol ?? formatAddress(address); -} - function formatLimitValue(value: bigint, decimals: number): string { if (value === 0n) return '0'; const divisor = BigInt(10 ** decimals); @@ -35,9 +30,9 @@ function limitsToData( return { poolAddr: pool.poolAddr, token0: pool.token0, - token0Symbol: getTokenSymbol(chainId, pool.token0), + token0Symbol: tokenSymbolOrAddress(chainId, pool.token0), token1: pool.token1, - token1Symbol: getTokenSymbol(chainId, pool.token1), + token1Symbol: tokenSymbolOrAddress(chainId, pool.token1), poolType: pool.poolType, tradable: status.tradable, circuitBreakerOk: status.circuitBreakerOk, @@ -45,7 +40,7 @@ function limitsToData( limitsOk: status.limitsOk, limits: status.limits.map((l) => ({ asset: l.asset, - assetSymbol: getTokenSymbol(chainId, l.asset), + assetSymbol: tokenSymbolOrAddress(chainId, l.asset), maxIn: l.maxIn.toString(), maxOut: l.maxOut.toString(), until: l.until, @@ -63,7 +58,7 @@ function formatLimitsStatus(ok: boolean): string { } function printLimitRow(limit: TradingLimit, chainId: number): void { - const symbol = getTokenSymbol(chainId, limit.asset); + const symbol = tokenSymbolOrAddress(chainId, limit.asset); const maxIn = formatLimitValue(limit.maxIn, limit.decimals); const maxOut = formatLimitValue(limit.maxOut, limit.decimals); const reset = formatResetTime(limit.until); @@ -97,9 +92,9 @@ Examples: const chainId = resolveChainId(globalOpts.chain); if (tokenA && tokenB) { - // Check specific pair - const resolvedA = resolveToken(chainId, tokenA); - const resolvedB = resolveToken(chainId, tokenB); + await prefetchTokens(mento, chainId); + const resolvedA = resolveTokenSync(chainId, tokenA); + const resolvedB = resolveTokenSync(chainId, tokenB); if (!resolvedA) { console.error(chalk.red(`Error: Token not found: ${tokenA}`)); @@ -184,7 +179,10 @@ Examples: const jsonMode = globalOpts.json ?? false; const chainId = resolveChainId(globalOpts.chain); - const pools = await mento.pools.getPools(); + const [, pools] = await Promise.all([ + prefetchTokens(mento, chainId), + mento.pools.getPools(), + ]); if (!jsonMode) { console.log(chalk.bold('Trading Limits — All Pools')); @@ -195,8 +193,8 @@ Examples: for (const pool of pools) { const status = await mento.trading.getPoolTradabilityStatus(pool); - const sym0 = getTokenSymbol(chainId, pool.token0); - const sym1 = getTokenSymbol(chainId, pool.token1); + const sym0 = tokenSymbolOrAddress(chainId, pool.token0); + const sym1 = tokenSymbolOrAddress(chainId, pool.token1); allData.push(limitsToData(pool, status, chainId)); diff --git a/src/lib/format.ts b/src/lib/format.ts index 9b6f57f..8352b77 100644 --- a/src/lib/format.ts +++ b/src/lib/format.ts @@ -10,6 +10,16 @@ export function formatAddress(address: string, chars: number = 6): string { return `${address.slice(0, chars + 2)}...${address.slice(-chars)}`; } +/** + * Parse a human-readable decimal amount (e.g. "1.5") into a bigint scaled to + * the token's decimals. Excess fractional digits are truncated. + */ +export function parseAmount(amountStr: string, decimals: number): bigint { + const [intPart, fracPart = ''] = amountStr.split('.'); + const paddedFrac = fracPart.slice(0, decimals).padEnd(decimals, '0'); + return BigInt(intPart) * BigInt(10 ** decimals) + BigInt(paddedFrac); +} + /** * Format a token amount from a raw bigint/string value with the given decimals. * Returns a human-readable string with commas and specified decimal places. diff --git a/src/lib/utils.ts b/src/lib/utils.ts index 6a4925f..85cdc06 100644 --- a/src/lib/utils.ts +++ b/src/lib/utils.ts @@ -1,33 +1,74 @@ -import { getCachedTokens, type Token } from '@mento-protocol/mento-sdk'; +import { getCachedTokens, type Mento, type Token } from '@mento-protocol/mento-sdk'; +import { formatAddress } from './format.js'; + +const resolvedCollateral = new Map(); +const inFlight = new Map>(); /** - * Resolve a token symbol to a Token object using the SDK's cached token data. - * Case-insensitive matching. + * Fetch and cache the collateral asset list for a chain. Concurrent first + * calls share a single in-flight RPC. After the first await resolves, the + * collateral list is available for `resolveTokenSync` on this chain. */ -export function resolveTokenBySymbol( - chainId: number, - symbol: string -): Token | undefined { - const tokens = getCachedTokens(chainId); - return tokens.find( - (t) => t.symbol.toLowerCase() === symbol.toLowerCase() +export function prefetchTokens(mento: Mento, chainId: number): Promise { + const cached = resolvedCollateral.get(chainId); + if (cached) return Promise.resolve(cached); + + const pending = inFlight.get(chainId); + if (pending) return pending; + + const fetch = mento.tokens + .getCollateralAssets() + .then((tokens) => { + resolvedCollateral.set(chainId, tokens); + return tokens; + }) + .finally(() => { + inFlight.delete(chainId); + }); + inFlight.set(chainId, fetch); + return fetch; +} + +function findToken(tokens: readonly Token[], input: string): Token | undefined { + const needle = input.toLowerCase(); + if (input.startsWith('0x')) { + return tokens.find((t) => t.address.toLowerCase() === needle); + } + return tokens.find((t) => t.symbol.toLowerCase() === needle); +} + +/** + * Resolve by symbol or address against stable tokens (SDK cache) and + * collateral assets. Returns `undefined` if `prefetchTokens` has not been + * awaited for this chain or the token is genuinely unknown. + */ +export function resolveTokenSync(chainId: number, input: string): Token | undefined { + return ( + findToken(getCachedTokens(chainId), input) ?? + findToken(resolvedCollateral.get(chainId) ?? [], input) ); } /** - * Resolve a token by address or symbol. - * If the input looks like an address (starts with 0x), match by address. - * Otherwise, match by symbol. + * Async resolver — only prefetches collateral if the token isn't already + * known via stables or a previously-resolved collateral cache. */ -export function resolveToken( +export async function resolveToken( + mento: Mento, chainId: number, - input: string -): Token | undefined { - const tokens = getCachedTokens(chainId); - if (input.startsWith('0x')) { - return tokens.find( - (t) => t.address.toLowerCase() === input.toLowerCase() - ); - } - return resolveTokenBySymbol(chainId, input); + input: string, +): Promise { + const cached = resolveTokenSync(chainId, input); + if (cached) return cached; + await prefetchTokens(mento, chainId); + return resolveTokenSync(chainId, input); +} + +/** + * Render-time helper: returns the token symbol for an address, falling back to + * an abbreviated address when the token isn't known. Requires `prefetchTokens` + * to have completed for this chain. + */ +export function tokenSymbolOrAddress(chainId: number, address: string): string { + return resolveTokenSync(chainId, address)?.symbol ?? formatAddress(address); }