From 2616b39482a649517de380a44e312b32c1b6d5a0 Mon Sep 17 00:00:00 2001 From: Felix Date: Fri, 9 Oct 2026 11:29:14 +0800 Subject: [PATCH 01/12] feat: migrate sun-cli to public SUN SDK --- .github/workflows/ci.yml | 6 +- SUN_SDK_MIGRATION_DESIGN.md | 177 +++ package-lock.json | 1384 ++++++++++++++--- package.json | 15 +- src/commands/contract.ts | 44 +- src/commands/liquidity.ts | 412 ++--- src/commands/pool.ts | 2 +- src/commands/sunpump.ts | 76 +- src/commands/swap.ts | 52 +- src/commands/wallet.ts | 30 +- src/lib/command.ts | 19 +- src/lib/context.ts | 27 +- src/lib/output.ts | 2 +- src/lib/sdk/compat.ts | 28 + src/lib/sdk/constants.ts | 6 + src/lib/sdk/errors.ts | 21 + src/lib/sdk/factory.ts | 81 + src/lib/sdk/index.ts | 6 + src/lib/sdk/types.ts | 16 + src/lib/sdk/wallet.ts | 37 + src/lib/sunpump.ts | 280 ---- src/lib/tokens.ts | 2 +- src/lib/wallet.ts | 99 +- test/commands/liquidity-sdk.test.ts | 235 +++ test/commands/sdk-write-migration.test.ts | 127 ++ test/commands/sunpump-sdk.test.ts | 60 + test/contract/command-manifest.test.ts | 22 + test/contract/fixtures.ts | 103 ++ test/contract/golden-output.test.ts | 20 + test/contract/sdk-dependency-boundary.test.ts | 40 + test/helpers/run-cli.ts | 79 + test/lib/context.test.ts | 52 +- test/lib/sdk/compat.test.ts | 29 + test/lib/sdk/constants.test.ts | 9 + test/lib/sdk/errors.test.ts | 18 + test/lib/sdk/factory.test.ts | 32 + test/lib/sdk/wallet.test.ts | 34 + test/lib/sunpump.test.ts | 34 - test/lib/wallet.test.ts | 82 +- 39 files changed, 2751 insertions(+), 1047 deletions(-) create mode 100644 SUN_SDK_MIGRATION_DESIGN.md create mode 100644 src/lib/sdk/compat.ts create mode 100644 src/lib/sdk/constants.ts create mode 100644 src/lib/sdk/errors.ts create mode 100644 src/lib/sdk/factory.ts create mode 100644 src/lib/sdk/index.ts create mode 100644 src/lib/sdk/types.ts create mode 100644 src/lib/sdk/wallet.ts delete mode 100644 src/lib/sunpump.ts create mode 100644 test/commands/liquidity-sdk.test.ts create mode 100644 test/commands/sdk-write-migration.test.ts create mode 100644 test/commands/sunpump-sdk.test.ts create mode 100644 test/contract/command-manifest.test.ts create mode 100644 test/contract/fixtures.ts create mode 100644 test/contract/golden-output.test.ts create mode 100644 test/contract/sdk-dependency-boundary.test.ts create mode 100644 test/helpers/run-cli.ts create mode 100644 test/lib/sdk/compat.test.ts create mode 100644 test/lib/sdk/constants.test.ts create mode 100644 test/lib/sdk/errors.test.ts create mode 100644 test/lib/sdk/factory.test.ts create mode 100644 test/lib/sdk/wallet.test.ts delete mode 100644 test/lib/sunpump.test.ts diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 2e6f927..05780b3 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -12,7 +12,8 @@ jobs: strategy: matrix: - node-version: [18.x, 20.x, 22.x] + # The public SUN SDK requires Node.js 20 or newer. + node-version: [20.x, 22.x] steps: - name: Checkout repository @@ -51,3 +52,6 @@ jobs: - name: TypeScript type check run: npx tsc --noEmit + + - name: Lint + run: npm run lint diff --git a/SUN_SDK_MIGRATION_DESIGN.md b/SUN_SDK_MIGRATION_DESIGN.md new file mode 100644 index 0000000..02896a4 --- /dev/null +++ b/SUN_SDK_MIGRATION_DESIGN.md @@ -0,0 +1,177 @@ +# sun-cli 接入公网 SUN SDK 方案 + +## 1. 背景与目标 + +本次改造沿用 SUN.io 接入 SUN SDK 的原则:保留 `sun-cli` 已有命令、参数、输出和确认流程,命令底层改用公网 npm 上的 SUN SDK 实现。 + +目标: + +- 保持现有 CLI API 兼容,避免调用方迁移。 +- 删除 `@sun-protocol/sun-kit`、旧私有 `@sun-sdk/*` 和私有 npm registry 依赖。 +- 统一通过 SUN SDK 完成 API 查询、链上读取、交易构建和执行。 +- 复用 `agent-wallet`,通过适配器满足 SDK 的 `WalletAdapter` 契约。 +- 支持 mainnet 和 Nile;公网 SDK 当前不支持 Shasta,SDK 命令在 Shasta 下应返回明确错误。 + +非目标: + +- 不调整命令名称、参数语义或默认输出格式。 +- 不把带真实私钥的 E2E 脚本、报告或测试账户提交到仓库。 +- 不在本次需求中重做 SUN SDK 内部协议算法。 + +## 2. 依赖策略 + +正式依赖最终固定到通过验证的公网版本。当前 manifest 暂时保留 `0.1.0`,本地联调同时安装 SUN SDK 功能分支产出的 protocols 与 sunswap-v2 tarball;Steven 发布 beta 后,再把以下依赖统一更新并锁定到该 beta 版本: + +| 包 | 用途 | +| --------------------------------- | ------------------------------------------ | +| `@sun-protocol/sun-sdk-api` | SUN.IO 和 SunPump HTTP API | +| `@sun-protocol/sun-sdk-chains` | 网络配置、协议合约地址、WTRX 地址 | +| `@sun-protocol/sun-sdk-core` | Network、TRON 地址和通用类型 | +| `@sun-protocol/sun-sdk-protocols` | Swap、V2/V3/V4 流动性、Position、SunPump | +| `@sun-protocol/sun-sdk-runtime` | TronWeb client、合约读写、余额和钱包运行时 | + +SDK 要求 Node.js 20 及以上,因此 CLI 的 `engines.node` 和 CI 矩阵同步调整为 Node 20/22。版本先精确锁定,后续 SDK 升级单独验证并更新锁文件,避免协议行为随安装时间漂移。 + +## 3. 分层设计 + +### 3.1 SDK 工厂 + +`src/lib/sdk/factory.ts` 负责: + +1. 校验 SDK 网络。 +2. 根据 `TRON_RPC_URL`、TronGrid API Key 和网络创建只读 `TronWeb`。 +3. 使用公网 runtime 的 Node 入口创建 `TronClient`。 +4. 注入 `WalletAdapter` 创建 `Runtime`。 +5. 创建一个进程内复用的 `SunSDK`。 + +CLI 创建的 SDK 始终带 runtime,因此使用 `CliSunSDK` 将 SDK 中可选的 `runtime` 收紧为必选,命令层不再依赖 `any`。 + +### 3.2 钱包适配 + +`AgentWalletSdkAdapter` 把现有 `agent-wallet` 映射到 SUN SDK: + +- `getAddress()`:返回当前 TRON 地址。 +- `signTransaction()`:签名 SDK 构建的 TRON 交易。 +- `signMessage()` / `signTypedData()`:保留消息和 Permit2 typed-data 能力。 +- 不在 SDK 层读取或持久化私钥。 + +读命令可以在没有钱包时执行;写命令仍由 `writeAction` 在确认前检查钱包。 + +### 3.3 命令映射 + +| CLI 能力 | 公网 SDK 调用 | +| ------------------------ | --------------------------------------- | +| SUN.IO 查询 | `SunApiClient` | +| SunPump 查询 | `SunPumpApiClient` | +| 钱包余额 | `readBalances(runtime.tronClient, ...)` | +| 通用合约读取/发送 | `runtime.contract.read/write` | +| 高层 Swap | `sdk.swap.router.quote/execute` | +| V2 增减流动性 | `sdk.liquidity.v2.add/remove` | +| V3 Mint/Increase | `sdk.liquidity.v3.add/increase` | +| V3 Decrease/Collect | `sdk.positions.v3.remove/collect` | +| V4 Mint/Increase | `sdk.liquidity.v4.add/increase` | +| V4 Decrease/Collect/Info | `sdk.positions.v4.remove/collect/read` | +| SunPump 买卖 | `sdk.pump.buy/sell.quote/execute` | + +CLI 的小数滑点继续接受 `0.005` 形式,在适配边界转换成 SDK 的百分比形式 `0.5%`。TRX/WTRX、交易结果和 SDK 错误也集中在适配层转换,命令输出继续使用 CLI 原有结构。 + +## 4. 兼容性约束 + +- 命令清单、参数名称、帮助文本和退出码通过契约测试锁定。 +- 写操作保留摘要、二次确认、`--yes`、`--dry-run` 和 Tronscan 链接。 +- JSON 模式继续输出机器可解析对象;交易结果规范化为 `txid`。 +- 原生 TRX 在需要 ERC20/TRC20 地址的 V2/V3/V4 场景转换成对应网络 WTRX。 +- V4 mint 在旧 CLI 未提供 fee/tick 时使用 `fee=3000`、`tickSpacing=60` 和 `[-120, 120]`;`--recipient` 映射到公网 SDK 的 `owner`。V4 increase/remove/collect 由 SDK 根据 `tokenId` 读取并校验 pool key,CLI 不拼装不完整的 pool key。 +- 未配置钱包时,读命令正常工作;写命令统一返回 `NO_WALLET` 类错误。 +- `--network shasta` 参数为兼容性继续保留,但 SDK 支持范围仅 mainnet/Nile。 + +### 4.1 公网 0.1.0 的兼容补充 + +公网协议层 `0.1.0` 尚未把旧 CLI 的所有“显式最小到账量”暴露为高层入参: + +- V2 remove 的 `--min-a/--min-b`。 +- V3 increase 的 `--min0/--min1`。 +- V3 decrease 的 `--min0/--min1`。 + +高层 SDK 会按 slippage 自行计算 minimum,直接透传这些额外字段不能视为已经支持。已采用方案 A:在 SUN SDK 的 `feature/sun-cli-compat-minimums` 分支补充显式 minimum 字段及单元测试,并通过本地 pack 供 sun-cli 联调。待 beta 验证通过后,再将 CLI 的精确依赖版本升级到公网发布版。 + +本地 pack 的 Nile 真实链上 E2E 已验证 V2 remove、V3 increase 和 V3 decrease 的显式 minimum;正式发布验收仍需等 Steven 发布 beta 后,在干净安装环境重新执行同一组用例。 + +### 4.2 本地 pack 联调结果 + +- V2 add/remove:真实交易成功,remove 后 LP 余额精确减少指定的 `100000000`,并收到两种底层资产。 +- V3 mint:在 TRX/USDT 0.05% 池创建仓位 `#705`;同时确认自动 tick 参数由 CLI 按旧实现的 `100 × tickSpacing` 透传给 SDK。 +- V3 单边金额:补齐 SDK 的区间内自动配比逻辑,只传 `amount0` 或 `amount1` 时仍能生成非零流动性;CLI 在 token 输入顺序与池顺序相反时同步交换 amount/minimum,保持旧参数语义。 +- V3 increase:显式 `amount0Min=1`、`amount1Min=1`,仓位流动性由 `177636846` 增至 `195400530`。 +- V3 decrease:显式 `amount0Min=1`、`amount1Min=1`,移除 `10000000` 后仓位流动性精确变为 `185400530`。 +- 所有写交易均在 Nile 成功上链;密钥、临时执行脚本和原始签名交易均未写入仓库。 + +## 5. 自动化测试 + +### 5.1 单元测试 + +- SDK 网络选择和非法网络拒绝。 +- `agent-wallet` 到 `WalletAdapter` 的地址、交易、消息和 typed-data 映射。 +- SDK 错误到 CLI 错误码映射。 +- SDK 多步交易结果到 CLI `txid` 的映射。 +- TRX/WTRX 常量和网络地址。 +- Wallet、Contract、Swap 和 SunPump 命令是否调用正确的公网 SDK namespace。 + +### 5.2 契约测试 + +- 根命令及子命令 help 清单不变。 +- JSON 输出、错误输出和关键 golden output 不变。 +- 生产代码和 `package.json` 不允许重新引入 `@sun-sdk/*` 或 `sun-kit`。 +- 五个公网 SDK 直接依赖必须固定到已验证版本。 + +### 5.3 CI 门禁 + +Node 20 和 22 均执行: + +1. `npm ci` +2. `npm run build` +3. `npm test` + +另外执行 TypeScript 类型检查和 ESLint。`build` 必须先清理 `dist`,防止已删除的私有 SDK/E2E 产物进入 npm 包。 + +## 6. 人工自测用例 + +### 6.1 无钱包、无资金用例 + +| 场景 | 示例 | 期望 | +| ---------- | ------------------------------------------------------- | ------------------------------------- | +| CLI 契约 | `sun --help`、各命令 `--help` | 命令和参数保持兼容 | +| API 查询 | `sun price TRX --network nile --json` | 返回合法 JSON | +| Pool 查询 | `sun pool list --network nile --json` | API 正常、分页正常 | +| Swap 报价 | `sun swap:quote TRX USDT 1000000 --network nile --json` | 返回可解析路由 | +| 合约读取 | 带 ABI 执行 `contract read` | 使用 runtime contract client 返回结果 | +| 钱包缺失 | 执行任一写命令 | 确认前给出明确钱包错误,不广播交易 | +| Dry run | 任一写命令加 `--dry-run --json` | 只输出动作与参数,不创建 SDK 交易 | +| 非支持网络 | SDK 命令加 `--network shasta` | 返回明确的 unsupported network 错误 | + +### 6.2 Nile 小额写入用例 + +在隔离测试钱包、限额资金和显式 `--yes` 下执行: + +1. Contract send:最小可验证合约写入。 +2. Swap:TRX/WTRX 与常用 TRC20 的小额交换。 +3. V2:add 后 remove,校验 LP 和余额变化。 +4. V3:mint、increase、collect、decrease,校验 position 状态。 +5. V4:已有池 mint/increase/collect/decrease;新池创建单独验证。 +6. SunPump:quote-buy/quote-sell,再做允许范围内的小额 buy/sell。 + +每条写入验证:退出码为 0、输出包含 `txid`、Tronscan 可查询、交易 receipt 成功、前后余额或 position 状态符合预期。失败用例至少覆盖余额不足、滑点过低、无路由、无授权和过期 deadline。 + +真实网络 E2E 只用于发布前临时验证,脚本、密钥和运行产物保留在仓库外。 + +## 7. 发布与回滚 + +1. 在干净环境执行 `npm ci`、build、lint、test 和 `npm pack --dry-run`。 +2. 检查 tarball 中不存在旧 `sun-kit`、私有 `@sun-sdk/*`、E2E 命令或测试报告。 +3. Nile 完成小额冒烟后先发布 beta,观察 API/交易错误。 +4. mainnet 只做受控小额冒烟,通过后再提升 latest。 +5. 回滚以 npm dist-tag 指回上一稳定版为主;依赖已精确锁定,可复现本次构建。 + +### 7.1 当前依赖审计备注 + +截至 2026-10-08,已将 TronWeb 从锁文件中的 6.2.2 升级到 6.5.1,清除了旧版 `ws`、`form-data` 等高危链路。生产依赖审计仍报告 2 个 high,来源是 TronWeb 6.5.1 精确依赖的 Axios 1.18.0;`npm audit --force` 给出的方案会反向降级 TronWeb,不应采用。另有 SUN SDK V3 的 ethers v5/elliptic 低危无上游修复项。发布前需要跟踪 TronWeb 和 SUN SDK 的上游版本,而不是在 CLI 中强制覆盖不兼容的传递依赖。 diff --git a/package-lock.json b/package-lock.json index 54a6fd5..2dbc69a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,13 +12,17 @@ "@bankofai/agent-wallet": "^2.3.0", "@scure/bip32": "^2.0.1", "@scure/bip39": "^2.0.1", - "@sun-protocol/sun-kit": "^1.2.0", + "@sun-protocol/sun-sdk-api": "0.1.0", + "@sun-protocol/sun-sdk-chains": "0.1.0", + "@sun-protocol/sun-sdk-core": "0.1.0", + "@sun-protocol/sun-sdk-protocols": "0.1.0", + "@sun-protocol/sun-sdk-runtime": "0.1.0", "chalk": "^4.1.2", "cli-table3": "^0.6.5", "commander": "^12.1.0", "dotenv": "^16.4.5", "ora": "^5.4.1", - "tronweb": "^6.2.1" + "tronweb": "^6.5.1" }, "bin": { "sun": "bin/sun" @@ -36,22 +40,25 @@ "ts-node": "^10.9.2", "typescript": "^5.0.0", "typescript-eslint": "^8.56.1" + }, + "engines": { + "node": ">=20" } }, "node_modules/@adraffy/ens-normalize": { - "version": "1.10.1", - "resolved": "https://registry.npmjs.org/@adraffy/ens-normalize/-/ens-normalize-1.10.1.tgz", - "integrity": "sha512-96Z2IP3mYmF1Xg2cDm8f1gWGf/HUVedQ3FMifV4kG/PQ4yEP51xDtRAEfhVNt5f/uzpNkZHwWQuUcu6D6K+Ekw==", + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@adraffy/ens-normalize/-/ens-normalize-1.11.1.tgz", + "integrity": "sha512-nhCBV3quEgesuf7c7KYfperqSS14T8bYuvJ8PcLJp6znkZpFc0AuW4qBtr8eKVyPPe/8RSr7sglCWPU5eaxwKQ==", "license": "MIT" }, "node_modules/@babel/code-frame": { - "version": "7.29.0", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.0.tgz", - "integrity": "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.7.tgz", + "integrity": "sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-validator-identifier": "^7.28.5", + "@babel/helper-validator-identifier": "^7.29.7", "js-tokens": "^4.0.0", "picocolors": "^1.1.1" }, @@ -60,9 +67,9 @@ } }, "node_modules/@babel/compat-data": { - "version": "7.29.0", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.0.tgz", - "integrity": "sha512-T1NCJqT/j9+cn8fvkt7jtwbLBfLC/1y1c7NtCeXFRgzGTsafi68MRv8yzkYSapBnFA6L3U2VSc02ciDzoAJhJg==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.7.tgz", + "integrity": "sha512-locTkQyKvwIEgBzVrn8693ebc97F2U8ZHjbXwDXJ5Fn2TCpNwTlKcaKLkdHop5c/icOFE7qt7Q9JC5hnKNa6Gg==", "dev": true, "license": "MIT", "engines": { @@ -70,21 +77,21 @@ } }, "node_modules/@babel/core": { - "version": "7.29.0", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.0.tgz", - "integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.7.tgz", + "integrity": "sha512-RgHBCvtjbOK2gXSNBNIkNoEc9qoVEtau3hj8gEqKQuL3HZAibKarWFEI3Lfm6EYKkLalOh8eSrj9b+ch9H/VBA==", "dev": true, "license": "MIT", "dependencies": { - "@babel/code-frame": "^7.29.0", - "@babel/generator": "^7.29.0", - "@babel/helper-compilation-targets": "^7.28.6", - "@babel/helper-module-transforms": "^7.28.6", - "@babel/helpers": "^7.28.6", - "@babel/parser": "^7.29.0", - "@babel/template": "^7.28.6", - "@babel/traverse": "^7.29.0", - "@babel/types": "^7.29.0", + "@babel/code-frame": "^7.29.7", + "@babel/generator": "^7.29.7", + "@babel/helper-compilation-targets": "^7.29.7", + "@babel/helper-module-transforms": "^7.29.7", + "@babel/helpers": "^7.29.7", + "@babel/parser": "^7.29.7", + "@babel/template": "^7.29.7", + "@babel/traverse": "^7.29.7", + "@babel/types": "^7.29.7", "@jridgewell/remapping": "^2.3.5", "convert-source-map": "^2.0.0", "debug": "^4.1.0", @@ -101,14 +108,14 @@ } }, "node_modules/@babel/generator": { - "version": "7.29.1", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.1.tgz", - "integrity": "sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw==", + "version": "7.29.8", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.8.tgz", + "integrity": "sha512-gZbepsdh3WDtgZKWL+vTPh71LSBrm/Y4/QDZBVCcYfmeTEEuoOYwlSy+G1StfJg+/Zy550u/3TATbm7qDbbMtg==", "dev": true, "license": "MIT", "dependencies": { - "@babel/parser": "^7.29.0", - "@babel/types": "^7.29.0", + "@babel/parser": "^7.29.8", + "@babel/types": "^7.29.8", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" @@ -118,14 +125,14 @@ } }, "node_modules/@babel/helper-compilation-targets": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.28.6.tgz", - "integrity": "sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.29.7.tgz", + "integrity": "sha512-wem6WaBj4NaVYVdNhLPPVacES6ZJ+KBBfSkTMD3YZxbP3rm3Di85tJU5ljaUNhaOynt+Aj0xruhYuzQBt8n71g==", "dev": true, "license": "MIT", "dependencies": { - "@babel/compat-data": "^7.28.6", - "@babel/helper-validator-option": "^7.27.1", + "@babel/compat-data": "^7.29.7", + "@babel/helper-validator-option": "^7.29.7", "browserslist": "^4.24.0", "lru-cache": "^5.1.1", "semver": "^6.3.1" @@ -135,9 +142,9 @@ } }, "node_modules/@babel/helper-globals": { - "version": "7.28.0", - "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz", - "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.29.7.tgz", + "integrity": "sha512-3nQVUAtvkKH9zahfWgw96Jc/uFOmjACE1kQz82E2lqWmHBgjzbNlsC22nuQTfahmWeQtTq5nQ/4Nnd2A1wj4zA==", "dev": true, "license": "MIT", "engines": { @@ -145,29 +152,29 @@ } }, "node_modules/@babel/helper-module-imports": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.28.6.tgz", - "integrity": "sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.29.7.tgz", + "integrity": "sha512-ejHwrQQYcm9xnTivShn2IDOlIzInN34AXskvq9QicvCtEzq1Vzclu/tKF8Jq1Cg8JG2GL6/EmjgsCT7lXepE3g==", "dev": true, "license": "MIT", "dependencies": { - "@babel/traverse": "^7.28.6", - "@babel/types": "^7.28.6" + "@babel/traverse": "^7.29.7", + "@babel/types": "^7.29.7" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-module-transforms": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.6.tgz", - "integrity": "sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.29.7.tgz", + "integrity": "sha512-UPUVSyXbOh627KiCIGQSgwWzGeBKLkaJ9PJEdrngIwMSzxLR4jS4+f1f1jb7VzBbg8nFLaYotvVPFCTqdrmTAg==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-module-imports": "^7.28.6", - "@babel/helper-validator-identifier": "^7.28.5", - "@babel/traverse": "^7.28.6" + "@babel/helper-module-imports": "^7.29.7", + "@babel/helper-validator-identifier": "^7.29.7", + "@babel/traverse": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -187,9 +194,9 @@ } }, "node_modules/@babel/helper-string-parser": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", - "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.29.7.tgz", + "integrity": "sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==", "dev": true, "license": "MIT", "engines": { @@ -197,9 +204,9 @@ } }, "node_modules/@babel/helper-validator-identifier": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", - "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.29.7.tgz", + "integrity": "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==", "dev": true, "license": "MIT", "engines": { @@ -207,9 +214,9 @@ } }, "node_modules/@babel/helper-validator-option": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz", - "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.29.7.tgz", + "integrity": "sha512-N9ZErrD+yW5geCDtBqnOoxmR8+tNKiGuxKlDpuJxfsqpa2dFcexaziGAE/qoHLiDDreVNMupxGmSoNlyvsA3gw==", "dev": true, "license": "MIT", "engines": { @@ -217,27 +224,27 @@ } }, "node_modules/@babel/helpers": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.28.6.tgz", - "integrity": "sha512-xOBvwq86HHdB7WUDTfKfT/Vuxh7gElQ+Sfti2Cy6yIWNW05P8iUslOVcZ4/sKbE+/jQaukQAdz/gf3724kYdqw==", + "version": "7.29.10", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.29.10.tgz", + "integrity": "sha512-EMCx3AGZ3WfAl6ht7AyQoPm2Lh+BGYEMdc4Q6Hf7uSCDOZFuLq44qNwL+iaim8VlyYhRAo4X43+weadK8ZB4Yg==", "dev": true, "license": "MIT", "dependencies": { - "@babel/template": "^7.28.6", - "@babel/types": "^7.28.6" + "@babel/template": "^7.29.7", + "@babel/types": "^7.29.8" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/parser": { - "version": "7.29.0", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.0.tgz", - "integrity": "sha512-IyDgFV5GeDUVX4YdF/3CPULtVGSXXMLh1xVIgdCgxApktqnQV0r7/8Nqthg+8YLGaAtdyIlo2qIdZrbCv4+7ww==", + "version": "7.29.9", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.9.tgz", + "integrity": "sha512-CjXrNHTnvqBVqHgdBysY3vk2T8tpJHb5/RMeHJBTyVa9xgugCB0CJTx/3oO8RV2QRQP391RWpB7D6hLjm8V9uA==", "dev": true, "license": "MIT", "dependencies": { - "@babel/types": "^7.29.0" + "@babel/types": "^7.29.8" }, "bin": { "parser": "bin/babel-parser.js" @@ -498,33 +505,33 @@ } }, "node_modules/@babel/template": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.28.6.tgz", - "integrity": "sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.29.7.tgz", + "integrity": "sha512-puq+Gf35oI24FeN11LkoUQFqv9uwNeWpxXZi/Ji3rRIoKAzKnxRaZ+Gkj0vKS9ZCiTESfng1N9LyOyXvo+m+Gg==", "dev": true, "license": "MIT", "dependencies": { - "@babel/code-frame": "^7.28.6", - "@babel/parser": "^7.28.6", - "@babel/types": "^7.28.6" + "@babel/code-frame": "^7.29.7", + "@babel/parser": "^7.29.7", + "@babel/types": "^7.29.7" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/traverse": { - "version": "7.29.0", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.0.tgz", - "integrity": "sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA==", + "version": "7.29.10", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.10.tgz", + "integrity": "sha512-B3lYAs2nkKskbgQ1nfy8Ma9ao9Q1rntTAQHrzzyDuj+TSO+X3le6PfWwqDvodsb+OumE10eMFfuMCTQSQ5iuIQ==", "dev": true, "license": "MIT", "dependencies": { - "@babel/code-frame": "^7.29.0", - "@babel/generator": "^7.29.0", - "@babel/helper-globals": "^7.28.0", - "@babel/parser": "^7.29.0", - "@babel/template": "^7.28.6", - "@babel/types": "^7.29.0", + "@babel/code-frame": "^7.29.7", + "@babel/generator": "^7.29.8", + "@babel/helper-globals": "^7.29.7", + "@babel/parser": "^7.29.9", + "@babel/template": "^7.29.7", + "@babel/types": "^7.29.8", "debug": "^4.3.1" }, "engines": { @@ -532,14 +539,14 @@ } }, "node_modules/@babel/types": { - "version": "7.29.0", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.0.tgz", - "integrity": "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==", + "version": "7.29.8", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.8.tgz", + "integrity": "sha512-Vj1jF3cPfxg7OAfoI7QnVKLoILlm2JF9pnVHrX8qx7AHMiYWT+NDAA7jChlNgRS4WTLc/fD1lXLmPixluj+3Gg==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-string-parser": "^7.27.1", - "@babel/helper-validator-identifier": "^7.28.5" + "@babel/helper-string-parser": "^7.29.7", + "@babel/helper-validator-identifier": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -707,16 +714,16 @@ } }, "node_modules/@eslint/config-array/node_modules/brace-expansion": { - "version": "5.0.4", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.4.tgz", - "integrity": "sha512-h+DEnpVvxmfVefa4jFbCf5HdH5YMDXRsmKflpf1pILZWRFlTbJpxeU55nJl4Smt5HQaGzg1o6RHFPJaOqnmBDg==", + "version": "5.0.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.12.tgz", + "integrity": "sha512-YovQ3rzhaLMIrDjNDMkNS01tea93qhEhG5xy8f6+R0l+dw3Ki+5sCoIoI942iuLZTHWogWktgwVDhU09iNEimQ==", "dev": true, "license": "MIT", "dependencies": { "balanced-match": "^4.0.2" }, "engines": { - "node": "18 || 20 || >=22" + "node": "20 || >=22" } }, "node_modules/@eslint/config-array/node_modules/minimatch": { @@ -806,30 +813,583 @@ "node": "^20.19.0 || ^22.13.0 || >=24" } }, + "node_modules/@ethersproject/abi": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/@ethersproject/abi/-/abi-5.7.0.tgz", + "integrity": "sha512-351ktp42TiRcYB3H1OP8yajPeAQstMW/yCFokj/AthP9bLHzQFPlOrxOcwYEDkUAICmOHljvN4K39OMTMUa9RA==", + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "license": "MIT", + "dependencies": { + "@ethersproject/address": "^5.7.0", + "@ethersproject/bignumber": "^5.7.0", + "@ethersproject/bytes": "^5.7.0", + "@ethersproject/constants": "^5.7.0", + "@ethersproject/hash": "^5.7.0", + "@ethersproject/keccak256": "^5.7.0", + "@ethersproject/logger": "^5.7.0", + "@ethersproject/properties": "^5.7.0", + "@ethersproject/strings": "^5.7.0" + } + }, + "node_modules/@ethersproject/abstract-provider": { + "version": "5.8.0", + "resolved": "https://registry.npmjs.org/@ethersproject/abstract-provider/-/abstract-provider-5.8.0.tgz", + "integrity": "sha512-wC9SFcmh4UK0oKuLJQItoQdzS/qZ51EJegK6EmAWlh+OptpQ/npECOR3QqECd8iGHC0RJb4WKbVdSfif4ammrg==", + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "license": "MIT", + "dependencies": { + "@ethersproject/bignumber": "^5.8.0", + "@ethersproject/bytes": "^5.8.0", + "@ethersproject/logger": "^5.8.0", + "@ethersproject/networks": "^5.8.0", + "@ethersproject/properties": "^5.8.0", + "@ethersproject/transactions": "^5.8.0", + "@ethersproject/web": "^5.8.0" + } + }, + "node_modules/@ethersproject/abstract-signer": { + "version": "5.8.0", + "resolved": "https://registry.npmjs.org/@ethersproject/abstract-signer/-/abstract-signer-5.8.0.tgz", + "integrity": "sha512-N0XhZTswXcmIZQdYtUnd79VJzvEwXQw6PK0dTl9VoYrEBxxCPXqS0Eod7q5TNKRxe1/5WUMuR0u0nqTF/avdCA==", + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "license": "MIT", + "dependencies": { + "@ethersproject/abstract-provider": "^5.8.0", + "@ethersproject/bignumber": "^5.8.0", + "@ethersproject/bytes": "^5.8.0", + "@ethersproject/logger": "^5.8.0", + "@ethersproject/properties": "^5.8.0" + } + }, + "node_modules/@ethersproject/address": { + "version": "5.8.0", + "resolved": "https://registry.npmjs.org/@ethersproject/address/-/address-5.8.0.tgz", + "integrity": "sha512-GhH/abcC46LJwshoN+uBNoKVFPxUuZm6dA257z0vZkKmU1+t8xTn8oK7B9qrj8W2rFRMch4gbJl6PmVxjxBEBA==", + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "license": "MIT", + "dependencies": { + "@ethersproject/bignumber": "^5.8.0", + "@ethersproject/bytes": "^5.8.0", + "@ethersproject/keccak256": "^5.8.0", + "@ethersproject/logger": "^5.8.0", + "@ethersproject/rlp": "^5.8.0" + } + }, + "node_modules/@ethersproject/address/node_modules/@ethersproject/keccak256": { + "version": "5.8.0", + "resolved": "https://registry.npmjs.org/@ethersproject/keccak256/-/keccak256-5.8.0.tgz", + "integrity": "sha512-A1pkKLZSz8pDaQ1ftutZoaN46I6+jvuqugx5KYNeQOPqq+JZ0Txm7dlWesCHB5cndJSu5vP2VKptKf7cksERng==", + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "license": "MIT", + "dependencies": { + "@ethersproject/bytes": "^5.8.0", + "js-sha3": "0.8.0" + } + }, + "node_modules/@ethersproject/base64": { + "version": "5.8.0", + "resolved": "https://registry.npmjs.org/@ethersproject/base64/-/base64-5.8.0.tgz", + "integrity": "sha512-lN0oIwfkYj9LbPx4xEkie6rAMJtySbpOAFXSDVQaBnAzYfB4X2Qr+FXJGxMoc3Bxp2Sm8OwvzMrywxyw0gLjIQ==", + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "license": "MIT", + "dependencies": { + "@ethersproject/bytes": "^5.8.0" + } + }, + "node_modules/@ethersproject/bignumber": { + "version": "5.8.0", + "resolved": "https://registry.npmjs.org/@ethersproject/bignumber/-/bignumber-5.8.0.tgz", + "integrity": "sha512-ZyaT24bHaSeJon2tGPKIiHszWjD/54Sz8t57Toch475lCLljC6MgPmxk7Gtzz+ddNN5LuHea9qhAe0x3D+uYPA==", + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "license": "MIT", + "dependencies": { + "@ethersproject/bytes": "^5.8.0", + "@ethersproject/logger": "^5.8.0", + "bn.js": "^5.2.1" + } + }, + "node_modules/@ethersproject/bytes": { + "version": "5.8.0", + "resolved": "https://registry.npmjs.org/@ethersproject/bytes/-/bytes-5.8.0.tgz", + "integrity": "sha512-vTkeohgJVCPVHu5c25XWaWQOZ4v+DkGoC42/TS2ond+PARCxTJvgTFUNDZovyQ/uAQ4EcpqqowKydcdmRKjg7A==", + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "license": "MIT", + "dependencies": { + "@ethersproject/logger": "^5.8.0" + } + }, + "node_modules/@ethersproject/constants": { + "version": "5.8.0", + "resolved": "https://registry.npmjs.org/@ethersproject/constants/-/constants-5.8.0.tgz", + "integrity": "sha512-wigX4lrf5Vu+axVTIvNsuL6YrV4O5AXl5ubcURKMEME5TnWBouUh0CDTWxZ2GpnRn1kcCgE7l8O5+VbV9QTTcg==", + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "license": "MIT", + "dependencies": { + "@ethersproject/bignumber": "^5.8.0" + } + }, + "node_modules/@ethersproject/hash": { + "version": "5.8.0", + "resolved": "https://registry.npmjs.org/@ethersproject/hash/-/hash-5.8.0.tgz", + "integrity": "sha512-ac/lBcTbEWW/VGJij0CNSw/wPcw9bSRgCB0AIBz8CvED/jfvDoV9hsIIiWfvWmFEi8RcXtlNwp2jv6ozWOsooA==", + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "license": "MIT", + "dependencies": { + "@ethersproject/abstract-signer": "^5.8.0", + "@ethersproject/address": "^5.8.0", + "@ethersproject/base64": "^5.8.0", + "@ethersproject/bignumber": "^5.8.0", + "@ethersproject/bytes": "^5.8.0", + "@ethersproject/keccak256": "^5.8.0", + "@ethersproject/logger": "^5.8.0", + "@ethersproject/properties": "^5.8.0", + "@ethersproject/strings": "^5.8.0" + } + }, + "node_modules/@ethersproject/hash/node_modules/@ethersproject/keccak256": { + "version": "5.8.0", + "resolved": "https://registry.npmjs.org/@ethersproject/keccak256/-/keccak256-5.8.0.tgz", + "integrity": "sha512-A1pkKLZSz8pDaQ1ftutZoaN46I6+jvuqugx5KYNeQOPqq+JZ0Txm7dlWesCHB5cndJSu5vP2VKptKf7cksERng==", + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "license": "MIT", + "dependencies": { + "@ethersproject/bytes": "^5.8.0", + "js-sha3": "0.8.0" + } + }, + "node_modules/@ethersproject/hash/node_modules/@ethersproject/strings": { + "version": "5.8.0", + "resolved": "https://registry.npmjs.org/@ethersproject/strings/-/strings-5.8.0.tgz", + "integrity": "sha512-qWEAk0MAvl0LszjdfnZ2uC8xbR2wdv4cDabyHiBh3Cldq/T8dPH3V4BbBsAYJUeonwD+8afVXld274Ls+Y1xXg==", + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "license": "MIT", + "dependencies": { + "@ethersproject/bytes": "^5.8.0", + "@ethersproject/constants": "^5.8.0", + "@ethersproject/logger": "^5.8.0" + } + }, + "node_modules/@ethersproject/keccak256": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/@ethersproject/keccak256/-/keccak256-5.7.0.tgz", + "integrity": "sha512-2UcPboeL/iW+pSg6vZ6ydF8tCnv3Iu/8tUmLLzWWGzxWKFFqOBQFLo6uLUv6BDrLgCDfN28RJ/wtByx+jZ4KBg==", + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "license": "MIT", + "dependencies": { + "@ethersproject/bytes": "^5.7.0", + "js-sha3": "0.8.0" + } + }, + "node_modules/@ethersproject/logger": { + "version": "5.8.0", + "resolved": "https://registry.npmjs.org/@ethersproject/logger/-/logger-5.8.0.tgz", + "integrity": "sha512-Qe6knGmY+zPPWTC+wQrpitodgBfH7XoceCGL5bJVejmH+yCS3R8jJm8iiWuvWbG76RUmyEG53oqv6GMVWqunjA==", + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "license": "MIT" + }, + "node_modules/@ethersproject/networks": { + "version": "5.8.0", + "resolved": "https://registry.npmjs.org/@ethersproject/networks/-/networks-5.8.0.tgz", + "integrity": "sha512-egPJh3aPVAzbHwq8DD7Po53J4OUSsA1MjQp8Vf/OZPav5rlmWUaFLiq8cvQiGK0Z5K6LYzm29+VA/p4RL1FzNg==", + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "license": "MIT", + "dependencies": { + "@ethersproject/logger": "^5.8.0" + } + }, + "node_modules/@ethersproject/properties": { + "version": "5.8.0", + "resolved": "https://registry.npmjs.org/@ethersproject/properties/-/properties-5.8.0.tgz", + "integrity": "sha512-PYuiEoQ+FMaZZNGrStmN7+lWjlsoufGIHdww7454FIaGdbe/p5rnaCXTr5MtBYl3NkeoVhHZuyzChPeGeKIpQw==", + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "license": "MIT", + "dependencies": { + "@ethersproject/logger": "^5.8.0" + } + }, + "node_modules/@ethersproject/rlp": { + "version": "5.8.0", + "resolved": "https://registry.npmjs.org/@ethersproject/rlp/-/rlp-5.8.0.tgz", + "integrity": "sha512-LqZgAznqDbiEunaUvykH2JAoXTT9NV0Atqk8rQN9nx9SEgThA/WMx5DnW8a9FOufo//6FZOCHZ+XiClzgbqV9Q==", + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "license": "MIT", + "dependencies": { + "@ethersproject/bytes": "^5.8.0", + "@ethersproject/logger": "^5.8.0" + } + }, + "node_modules/@ethersproject/sha2": { + "version": "5.8.0", + "resolved": "https://registry.npmjs.org/@ethersproject/sha2/-/sha2-5.8.0.tgz", + "integrity": "sha512-dDOUrXr9wF/YFltgTBYS0tKslPEKr6AekjqDW2dbn1L1xmjGR+9GiKu4ajxovnrDbwxAKdHjW8jNcwfz8PAz4A==", + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "license": "MIT", + "dependencies": { + "@ethersproject/bytes": "^5.8.0", + "@ethersproject/logger": "^5.8.0", + "hash.js": "1.1.7" + } + }, + "node_modules/@ethersproject/signing-key": { + "version": "5.8.0", + "resolved": "https://registry.npmjs.org/@ethersproject/signing-key/-/signing-key-5.8.0.tgz", + "integrity": "sha512-LrPW2ZxoigFi6U6aVkFN/fa9Yx/+4AtIUe4/HACTvKJdhm0eeb107EVCIQcrLZkxaSIgc/eCrX8Q1GtbH+9n3w==", + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "license": "MIT", + "dependencies": { + "@ethersproject/bytes": "^5.8.0", + "@ethersproject/logger": "^5.8.0", + "@ethersproject/properties": "^5.8.0", + "bn.js": "^5.2.1", + "elliptic": "6.6.1", + "hash.js": "1.1.7" + } + }, + "node_modules/@ethersproject/solidity": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/@ethersproject/solidity/-/solidity-5.7.0.tgz", + "integrity": "sha512-HmabMd2Dt/raavyaGukF4XxizWKhKQ24DoLtdNbBmNKUOPqwjsKQSdV9GQtj9CBEea9DlzETlVER1gYeXXBGaA==", + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "license": "MIT", + "dependencies": { + "@ethersproject/bignumber": "^5.7.0", + "@ethersproject/bytes": "^5.7.0", + "@ethersproject/keccak256": "^5.7.0", + "@ethersproject/logger": "^5.7.0", + "@ethersproject/sha2": "^5.7.0", + "@ethersproject/strings": "^5.7.0" + } + }, + "node_modules/@ethersproject/strings": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/@ethersproject/strings/-/strings-5.7.0.tgz", + "integrity": "sha512-/9nu+lj0YswRNSH0NXYqrh8775XNyEdUQAuf3f+SmOrnVewcJ5SBNAjF7lpgehKi4abvNNXyf+HX86czCdJ8Mg==", + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "license": "MIT", + "dependencies": { + "@ethersproject/bytes": "^5.7.0", + "@ethersproject/constants": "^5.7.0", + "@ethersproject/logger": "^5.7.0" + } + }, + "node_modules/@ethersproject/transactions": { + "version": "5.8.0", + "resolved": "https://registry.npmjs.org/@ethersproject/transactions/-/transactions-5.8.0.tgz", + "integrity": "sha512-UglxSDjByHG0TuU17bDfCemZ3AnKO2vYrL5/2n2oXvKzvb7Cz+W9gOWXKARjp2URVwcWlQlPOEQyAviKwT4AHg==", + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "license": "MIT", + "dependencies": { + "@ethersproject/address": "^5.8.0", + "@ethersproject/bignumber": "^5.8.0", + "@ethersproject/bytes": "^5.8.0", + "@ethersproject/constants": "^5.8.0", + "@ethersproject/keccak256": "^5.8.0", + "@ethersproject/logger": "^5.8.0", + "@ethersproject/properties": "^5.8.0", + "@ethersproject/rlp": "^5.8.0", + "@ethersproject/signing-key": "^5.8.0" + } + }, + "node_modules/@ethersproject/transactions/node_modules/@ethersproject/keccak256": { + "version": "5.8.0", + "resolved": "https://registry.npmjs.org/@ethersproject/keccak256/-/keccak256-5.8.0.tgz", + "integrity": "sha512-A1pkKLZSz8pDaQ1ftutZoaN46I6+jvuqugx5KYNeQOPqq+JZ0Txm7dlWesCHB5cndJSu5vP2VKptKf7cksERng==", + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "license": "MIT", + "dependencies": { + "@ethersproject/bytes": "^5.8.0", + "js-sha3": "0.8.0" + } + }, + "node_modules/@ethersproject/web": { + "version": "5.8.0", + "resolved": "https://registry.npmjs.org/@ethersproject/web/-/web-5.8.0.tgz", + "integrity": "sha512-j7+Ksi/9KfGviws6Qtf9Q7KCqRhpwrYKQPs+JBA/rKVFF/yaWLHJEH3zfVP2plVu+eys0d2DlFmhoQJayFewcw==", + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "license": "MIT", + "dependencies": { + "@ethersproject/base64": "^5.8.0", + "@ethersproject/bytes": "^5.8.0", + "@ethersproject/logger": "^5.8.0", + "@ethersproject/properties": "^5.8.0", + "@ethersproject/strings": "^5.8.0" + } + }, + "node_modules/@ethersproject/web/node_modules/@ethersproject/strings": { + "version": "5.8.0", + "resolved": "https://registry.npmjs.org/@ethersproject/strings/-/strings-5.8.0.tgz", + "integrity": "sha512-qWEAk0MAvl0LszjdfnZ2uC8xbR2wdv4cDabyHiBh3Cldq/T8dPH3V4BbBsAYJUeonwD+8afVXld274Ls+Y1xXg==", + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "license": "MIT", + "dependencies": { + "@ethersproject/bytes": "^5.8.0", + "@ethersproject/constants": "^5.8.0", + "@ethersproject/logger": "^5.8.0" + } + }, "node_modules/@humanfs/core": { - "version": "0.19.1", - "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", - "integrity": "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==", + "version": "0.19.2", + "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.2.tgz", + "integrity": "sha512-UhXNm+CFMWcbChXywFwkmhqjs3PRCmcSa/hfBgLIb7oQ5HNb1wS0icWsGtSAUNgefHeI+eBrA8I1fxmbHsGdvA==", "dev": true, "license": "Apache-2.0", + "dependencies": { + "@humanfs/types": "^0.15.0" + }, "engines": { "node": ">=18.18.0" } }, "node_modules/@humanfs/node": { - "version": "0.16.7", - "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.7.tgz", - "integrity": "sha512-/zUx+yOsIrG4Y43Eh2peDeKCxlRt/gET6aHfaKpuq267qXdYDFViVHfMaLyygZOnl0kGWxFIgsBy8QFuTLUXEQ==", + "version": "0.16.8", + "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.8.tgz", + "integrity": "sha512-gE1eQNZ3R++kTzFUpdGlpmy8kDZD/MLyHqDwqjkVQI0JMdI1D51sy1H958PNXYkM2rAac7e5/CnIKZrHtPh3BQ==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@humanfs/core": "^0.19.1", + "@humanfs/core": "^0.19.2", + "@humanfs/types": "^0.15.0", "@humanwhocodes/retry": "^0.4.0" }, "engines": { "node": ">=18.18.0" } }, + "node_modules/@humanfs/types": { + "version": "0.15.0", + "resolved": "https://registry.npmjs.org/@humanfs/types/-/types-0.15.0.tgz", + "integrity": "sha512-ZZ1w0aoQkwuUuC7Yf+7sdeaNfqQiiLcSRbfI08oAxqLtpXQr9AIVX7Ay7HLDuiLYAaFPu8oBYNq/QIi9URHJ3Q==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18.0" + } + }, "node_modules/@humanwhocodes/module-importer": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", @@ -2027,21 +2587,109 @@ "node": ">=20" } }, - "node_modules/@sun-protocol/sun-kit": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@sun-protocol/sun-kit/-/sun-kit-1.2.0.tgz", - "integrity": "sha512-UH5LIxC6WRno+OYVHSyCACqyujh8de0PQYRRzQV/IUxXxpsQADIVnO8w1Awn7ENbkN4imZh+9O2IkT+l4AP6qA==", - "license": "MIT", + "node_modules/@sun-protocol/sun-sdk-api": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/@sun-protocol/sun-sdk-api/-/sun-sdk-api-0.1.0.tgz", + "integrity": "sha512-ilaj6fH38pBiQ7bxzz1TtYmU8HGWr5J+YXGwGTlMuES/NTfoUIVzLjqA7N7h1gt2m1VCb+7rBf7+5VORRumVuQ==", + "dependencies": { + "@sun-protocol/sun-sdk-chains": "0.1.0", + "@sun-protocol/sun-sdk-core": "0.1.0" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/@sun-protocol/sun-sdk-chains": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/@sun-protocol/sun-sdk-chains/-/sun-sdk-chains-0.1.0.tgz", + "integrity": "sha512-MOYksVAW70mKlY/SGspOO6VqRAcPB4Zf/lq3AopZ9pw79CEKWZAL9VOvb2LgWkStZkk5fvZGi5S8bQE5YlSnRA==", + "dependencies": { + "@sun-protocol/sun-sdk-core": "0.1.0" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/@sun-protocol/sun-sdk-core": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/@sun-protocol/sun-sdk-core/-/sun-sdk-core-0.1.0.tgz", + "integrity": "sha512-ZFk9ZS3cf2A09HLYbnHDX30eS0aIN/Aq6s2xunvSWNxP3569/5AVNMLMRVIPydnAjbqWZlHpD7h6RzxFccMD1w==", + "dependencies": { + "@ethersproject/address": "^5.0.2", + "@ethersproject/bignumber": "^5.5.0", + "@ethersproject/bytes": "^5.7.0", + "@ethersproject/keccak256": "5.7.0", + "@ethersproject/strings": "5.7.0", + "big.js": "^5.2.2", + "decimal.js-light": "^2.5.0", + "jsbi": "^3.1.4", + "tiny-invariant": "^1.1.0", + "toformat": "^2.0.0", + "tslib": "^2.3.0" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/@sun-protocol/sun-sdk-launchpad": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/@sun-protocol/sun-sdk-launchpad/-/sun-sdk-launchpad-0.1.0.tgz", + "integrity": "sha512-0flPr/T1SqHK0/ZPEim0mr9vOQR4Lc6aEcZx9OaZ5o9j4zOUk6xbMNxkIzm5wy9LUlZisIg5VHerm5eWuCm1uA==", + "dependencies": { + "@sun-protocol/sun-sdk-chains": "0.1.0", + "@sun-protocol/sun-sdk-core": "0.1.0", + "@sun-protocol/sun-sdk-runtime": "0.1.0" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/@sun-protocol/sun-sdk-protocols": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/@sun-protocol/sun-sdk-protocols/-/sun-sdk-protocols-0.1.0.tgz", + "integrity": "sha512-ifIkZKrYeVaJeD9mVjdfvUQNvR7OMti8BD/Ox5N4pixBQU8uZ6s2ZZMFRPXdNevx7HDwZ1r+uJBv30yW2qfTeA==", "dependencies": { "@sun-protocol/permit2-sdk": "^1.0.0", - "@sun-protocol/universal-router-sdk": "^1.0.1", - "viem": "^2.0.0" + "@sun-protocol/sun-sdk-api": "0.1.0", + "@sun-protocol/sun-sdk-chains": "0.1.0", + "@sun-protocol/sun-sdk-core": "0.1.0", + "@sun-protocol/sun-sdk-launchpad": "0.1.0", + "@sun-protocol/sun-sdk-psm": "0.1.0", + "@sun-protocol/sun-sdk-runtime": "0.1.0", + "@sun-protocol/sun-sdk-sunswap-v2": "0.1.0", + "@sun-protocol/sun-sdk-sunswap-v3": "0.1.0", + "@sun-protocol/sun-sdk-sunswap-v4": "0.1.0", + "@sun-protocol/universal-router-sdk": "^1.1.1" }, "engines": { - "node": ">=18.0.0" + "node": ">=20" + } + }, + "node_modules/@sun-protocol/sun-sdk-psm": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/@sun-protocol/sun-sdk-psm/-/sun-sdk-psm-0.1.0.tgz", + "integrity": "sha512-o75VKy+XP613dhHmtmVVWTQvrEQgEikDFEFiwg9wd57Ogmhz/sDrNwZE1KLZcmUbNLbSjP0/BYQPwnob6YWKkw==", + "dependencies": { + "@sun-protocol/sun-sdk-chains": "0.1.0", + "@sun-protocol/sun-sdk-core": "0.1.0" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/@sun-protocol/sun-sdk-runtime": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/@sun-protocol/sun-sdk-runtime/-/sun-sdk-runtime-0.1.0.tgz", + "integrity": "sha512-vporzxWRxoOX2fqJNTRgn1CJTqN/SZI1ukw8gn8OzY4Ytbad/D0c+Y7V3NE3y8ikrDh4PrA0NBAuHbhtLjkRzw==", + "dependencies": { + "@sun-protocol/sun-sdk-chains": "0.1.0", + "@sun-protocol/sun-sdk-core": "0.1.0" + }, + "engines": { + "node": ">=20" }, "peerDependencies": { - "tronweb": "^6.2.1" + "tronweb": "*" }, "peerDependenciesMeta": { "tronweb": { @@ -2049,16 +2697,182 @@ } } }, + "node_modules/@sun-protocol/sun-sdk-sunswap-v2": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/@sun-protocol/sun-sdk-sunswap-v2/-/sun-sdk-sunswap-v2-0.1.0.tgz", + "integrity": "sha512-GMGqceLCWvRTAVm3pYDEOK87sRHLFVqrQY55nkIQP0iwj/wIXznMY4ffiJ/cy/Ou8wL9V1diKQuefvAit/v8fA==", + "dependencies": { + "@ethersproject/bignumber": "5.7.0", + "@ethersproject/keccak256": "5.7.0", + "@ethersproject/solidity": "5.7.0", + "@noble/hashes": "1.4.0", + "@sun-protocol/sun-sdk-chains": "0.1.0", + "@sun-protocol/sun-sdk-core": "0.1.0", + "jsbi": "^3.1.4", + "tiny-invariant": "^1.1.0" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/@sun-protocol/sun-sdk-sunswap-v2/node_modules/@ethersproject/bignumber": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/@ethersproject/bignumber/-/bignumber-5.7.0.tgz", + "integrity": "sha512-n1CAdIHRWjSucQO3MC1zPSVgV/6dy/fjL9pMrPP9peL+QxEg9wOsVqwD4+818B6LUEtaXzVHQiuivzRoxPxUGw==", + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "license": "MIT", + "dependencies": { + "@ethersproject/bytes": "^5.7.0", + "@ethersproject/logger": "^5.7.0", + "bn.js": "^5.2.1" + } + }, + "node_modules/@sun-protocol/sun-sdk-sunswap-v2/node_modules/@noble/hashes": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.4.0.tgz", + "integrity": "sha512-V1JJ1WTRUqHHrOSh597hURcMqVKVGL/ea3kv0gSnEdsEZ0/+VyPghM1lMNGc00z7CIQorSvbKpuJkxvuHbvdbg==", + "license": "MIT", + "engines": { + "node": ">= 16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@sun-protocol/sun-sdk-sunswap-v3": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/@sun-protocol/sun-sdk-sunswap-v3/-/sun-sdk-sunswap-v3-0.1.0.tgz", + "integrity": "sha512-kuQ1IfpwfxOrMGmY4JIF6+0Q7hX1B/2iXqHq6lTZovl/kYERlGMfoIcsBRE1NczOqC1Tz+n7QVh4nHUOdaelNg==", + "dependencies": { + "@ethersproject/abi": "5.7.0", + "@ethersproject/address": "5.7.0", + "@ethersproject/bytes": "5.7.0", + "@ethersproject/keccak256": "5.7.0", + "@ethersproject/logger": "5.7.0", + "@ethersproject/solidity": "5.7.0", + "@noble/hashes": "1.4.0", + "@sun-protocol/sun-sdk-chains": "0.1.0", + "@sun-protocol/sun-sdk-core": "0.1.0", + "jsbi": "^3.1.4", + "tiny-invariant": "^1.1.0" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/@sun-protocol/sun-sdk-sunswap-v3/node_modules/@ethersproject/address": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/@ethersproject/address/-/address-5.7.0.tgz", + "integrity": "sha512-9wYhYt7aghVGo758POM5nqcOMaE168Q6aRLJZwUmiqSrAungkG74gSSeKEIR7ukixesdRZGPgVqme6vmxs1fkA==", + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "license": "MIT", + "dependencies": { + "@ethersproject/bignumber": "^5.7.0", + "@ethersproject/bytes": "^5.7.0", + "@ethersproject/keccak256": "^5.7.0", + "@ethersproject/logger": "^5.7.0", + "@ethersproject/rlp": "^5.7.0" + } + }, + "node_modules/@sun-protocol/sun-sdk-sunswap-v3/node_modules/@ethersproject/bytes": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/@ethersproject/bytes/-/bytes-5.7.0.tgz", + "integrity": "sha512-nsbxwgFXWh9NyYWo+U8atvmMsSdKJprTcICAkvbBffT75qDocbuggBU0SJiVK2MuTrp0q+xvLkTnGMPK1+uA9A==", + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "license": "MIT", + "dependencies": { + "@ethersproject/logger": "^5.7.0" + } + }, + "node_modules/@sun-protocol/sun-sdk-sunswap-v3/node_modules/@ethersproject/logger": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/@ethersproject/logger/-/logger-5.7.0.tgz", + "integrity": "sha512-0odtFdXu/XHtjQXJYA3u9G0G8btm0ND5Cu8M7i5vhEcE8/HmF4Lbdqanwyv4uQTr2tx6b7fQRmgLrsnpQlmnig==", + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "license": "MIT" + }, + "node_modules/@sun-protocol/sun-sdk-sunswap-v3/node_modules/@noble/hashes": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.4.0.tgz", + "integrity": "sha512-V1JJ1WTRUqHHrOSh597hURcMqVKVGL/ea3kv0gSnEdsEZ0/+VyPghM1lMNGc00z7CIQorSvbKpuJkxvuHbvdbg==", + "license": "MIT", + "engines": { + "node": ">= 16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@sun-protocol/sun-sdk-sunswap-v4": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/@sun-protocol/sun-sdk-sunswap-v4/-/sun-sdk-sunswap-v4-0.1.0.tgz", + "integrity": "sha512-FRL2nqjmDxdeOVeRe0r/oATBekwcohHXoZ9SpA5bD6fWD9MIMW+jVQ0cmYn2HheCwNlp8yfCvM/SV4JXleBRoQ==", + "dependencies": { + "@ethersproject/keccak256": "5.7.0", + "@noble/hashes": "1.4.0", + "@sun-protocol/sun-sdk-chains": "0.1.0", + "@sun-protocol/sun-sdk-core": "0.1.0" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/@sun-protocol/sun-sdk-sunswap-v4/node_modules/@noble/hashes": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.4.0.tgz", + "integrity": "sha512-V1JJ1WTRUqHHrOSh597hURcMqVKVGL/ea3kv0gSnEdsEZ0/+VyPghM1lMNGc00z7CIQorSvbKpuJkxvuHbvdbg==", + "license": "MIT", + "engines": { + "node": ">= 16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, "node_modules/@sun-protocol/universal-router-sdk": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@sun-protocol/universal-router-sdk/-/universal-router-sdk-1.0.1.tgz", - "integrity": "sha512-DdwRXUZjzYQctjoxmnogjdSeyJhCo0X9aO39ntS14nR8xuqrJcQPg4DYrcaM+RlHDRXyWA/XGnr2jahV91wDnw==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@sun-protocol/universal-router-sdk/-/universal-router-sdk-1.1.1.tgz", + "integrity": "sha512-9OSxI7UG17KL81xs8XNObQ7WRKjM/DSf7hoIrtr+lxAMVTr+FxSWTHang3q58nmEZB2yEAf9Cb5kfGkRx0bJoQ==", "license": "MIT", "dependencies": { "@pancakeswap/swap-sdk-core": "^1.1.0", "@sun-protocol/permit2-sdk": "^1.0.0", - "decimal.js": "^10.6.0", - "tronweb": "^6.0.4", "viem": "^2.37.5" }, "engines": { @@ -2479,16 +3293,16 @@ } }, "node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": { - "version": "5.0.4", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.4.tgz", - "integrity": "sha512-h+DEnpVvxmfVefa4jFbCf5HdH5YMDXRsmKflpf1pILZWRFlTbJpxeU55nJl4Smt5HQaGzg1o6RHFPJaOqnmBDg==", + "version": "5.0.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.12.tgz", + "integrity": "sha512-YovQ3rzhaLMIrDjNDMkNS01tea93qhEhG5xy8f6+R0l+dw3Ki+5sCoIoI942iuLZTHWogWktgwVDhU09iNEimQ==", "dev": true, "license": "MIT", "dependencies": { "balanced-match": "^4.0.2" }, "engines": { - "node": "18 || 20 || >=22" + "node": "20 || >=22" } }, "node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": { @@ -2901,6 +3715,18 @@ "integrity": "sha512-G965FqalsNyrPqgEGON7nIx1e/OVENSgiEIzyC63haUMuvNnwIgIjMs52hlTCKhkBny7A2ORNlfY9Zu+jmGk1Q==", "license": "MIT" }, + "node_modules/agent-base": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", + "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "license": "MIT", + "dependencies": { + "debug": "4" + }, + "engines": { + "node": ">= 6.0.0" + } + }, "node_modules/agentkeepalive": { "version": "4.6.0", "resolved": "https://registry.npmjs.org/agentkeepalive/-/agentkeepalive-4.6.0.tgz", @@ -2989,9 +3815,9 @@ } }, "node_modules/anymatch/node_modules/picomatch": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz", + "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", "dev": true, "license": "MIT", "engines": { @@ -3025,14 +3851,15 @@ "license": "MIT" }, "node_modules/axios": { - "version": "1.13.5", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.13.5.tgz", - "integrity": "sha512-cz4ur7Vb0xS4/KUN0tPWe44eqxrIu31me+fbang3ijiNscE129POzipJJA6zniq2C/Z6sJCjMimjS8Lc/GAs8Q==", + "version": "1.18.0", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.18.0.tgz", + "integrity": "sha512-E32NzpYKp++W7XRe52rHiXV2ehxmh3wbdgO7MHeFM+vqxLBYHzt0ElkiImtOBxtOmyp0yoC8C6uESVV84Y2/hw==", "license": "MIT", "dependencies": { - "follow-redirects": "^1.15.11", + "follow-redirects": "^1.16.0", "form-data": "^4.0.5", - "proxy-from-env": "^1.1.0" + "https-proxy-agent": "^5.0.1", + "proxy-from-env": "^2.1.0" } }, "node_modules/babel-jest": { @@ -3168,9 +3995,9 @@ "license": "MIT" }, "node_modules/baseline-browser-mapping": { - "version": "2.10.7", - "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.7.tgz", - "integrity": "sha512-1ghYO3HnxGec0TCGBXiDLVns4eCSx4zJpxnHrlqFQajmhfKMQBzUGDdkMK7fUW7PTHTeLf+j87aTuKuuwWzMGw==", + "version": "2.11.27", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.11.27.tgz", + "integrity": "sha512-ElY12DaROGuan+lMmZ8Cvo/ZUbXPe7Enc/9VU/b1T3Kp4dwytRcNdR8DoSJN5SNJT/CuvcCA0DHDVmMOCePdRQ==", "dev": true, "license": "Apache-2.0", "bin": { @@ -3245,19 +4072,25 @@ } }, "node_modules/brace-expansion": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", - "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "version": "2.1.7", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.7.tgz", + "integrity": "sha512-uZbew1NqdmPDTMJ8ah1y+b+9QEJrfkXFk3RcTQw3X0jW/xRUvFKsg1CfQdSYGdTbXZWExtU3J3ccxtnfw1Fi0g==", "dev": true, "license": "MIT", "dependencies": { "balanced-match": "^1.0.0" } }, + "node_modules/brorand": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/brorand/-/brorand-1.1.0.tgz", + "integrity": "sha512-cKV8tMCEpQs4hK/ik71d6LrPOnpkpGBR0wzxqr68g2m/LB2GxVYQroAjMJZRVM1Y4BCjCKc3vAamxSzOY2RP+w==", + "license": "MIT" + }, "node_modules/browserslist": { - "version": "4.28.1", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.1.tgz", - "integrity": "sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA==", + "version": "4.29.3", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.29.3.tgz", + "integrity": "sha512-1R4kiYKXGViqEN0CnoDrXc1StD9niAwu+j2dukWzrD4bJgsD4lDmEp0CRbc6E/vYJIfTHwPmwyaKtVSudICdPA==", "dev": true, "funding": [ { @@ -3275,11 +4108,11 @@ ], "license": "MIT", "dependencies": { - "baseline-browser-mapping": "^2.9.0", - "caniuse-lite": "^1.0.30001759", - "electron-to-chromium": "^1.5.263", - "node-releases": "^2.0.27", - "update-browserslist-db": "^1.2.0" + "baseline-browser-mapping": "^2.11.26", + "caniuse-lite": "^1.0.30001813", + "electron-to-chromium": "^1.5.439", + "node-releases": "^2.0.57", + "update-browserslist-db": "^1.3.3" }, "bin": { "browserslist": "cli.js" @@ -3409,9 +4242,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001778", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001778.tgz", - "integrity": "sha512-PN7uxFL+ExFJO61aVmP1aIEG4i9whQd4eoSCebav62UwDyp5OHh06zN4jqKSMePVgxHifCw1QJxdRkA1Pisekg==", + "version": "1.0.30001815", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001815.tgz", + "integrity": "sha512-uDybUEyfgbyJJ93J1vikTylBX31AkdVYeVPeffMQpf8XN7hSfr0oZOn4VFOIMNvtU5+rHvC5Udg2TgtJKHm23Q==", "dev": true, "funding": [ { @@ -3694,7 +4527,6 @@ "version": "4.4.3", "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", - "dev": true, "license": "MIT", "dependencies": { "ms": "^2.1.3" @@ -3708,12 +4540,6 @@ } } }, - "node_modules/decimal.js": { - "version": "10.6.0", - "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.6.0.tgz", - "integrity": "sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg==", - "license": "MIT" - }, "node_modules/decimal.js-light": { "version": "2.5.1", "resolved": "https://registry.npmjs.org/decimal.js-light/-/decimal.js-light-2.5.1.tgz", @@ -3839,12 +4665,33 @@ "license": "MIT" }, "node_modules/electron-to-chromium": { - "version": "1.5.313", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.313.tgz", - "integrity": "sha512-QBMrTWEf00GXZmJyx2lbYD45jpI3TUFnNIzJ5BBc8piGUDwMPa1GV6HJWTZVvY/eiN3fSopl7NRbgGp9sZ9LTA==", + "version": "1.5.451", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.451.tgz", + "integrity": "sha512-vUNKPrUzw8ByEMOxloq0a71iD/mPuPxVkbDm3G5YhnxTzOw/I6qAg2MUS0cYj/c267lWmw26yW4Vv7foxTxPLg==", "dev": true, "license": "ISC" }, + "node_modules/elliptic": { + "version": "6.6.1", + "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.6.1.tgz", + "integrity": "sha512-RaddvvMatK2LJHqFJ+YA4WysVN5Ita9E35botqIYspQ4TkRAlCicdzKOjlyv/1Za5RyTNn7di//eEV0uTAfe3g==", + "license": "MIT", + "dependencies": { + "bn.js": "^4.11.9", + "brorand": "^1.1.0", + "hash.js": "^1.0.0", + "hmac-drbg": "^1.0.1", + "inherits": "^2.0.4", + "minimalistic-assert": "^1.0.1", + "minimalistic-crypto-utils": "^1.0.1" + } + }, + "node_modules/elliptic/node_modules/bn.js": { + "version": "4.12.5", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.5.tgz", + "integrity": "sha512-3aRg6/JxfffFD+OlOjOFR3Vo79l39ooBTFucxx+MT3dhCtzn3EmiUPQo+6/OZuI2jbXi3YKgmiTFBgChQMwIRQ==", + "license": "MIT" + }, "node_modules/emittery": { "version": "0.13.1", "resolved": "https://registry.npmjs.org/emittery/-/emittery-0.13.1.tgz", @@ -3893,9 +4740,9 @@ } }, "node_modules/es-object-atoms": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", - "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.2.tgz", + "integrity": "sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw==", "license": "MIT", "dependencies": { "es-errors": "^1.3.0" @@ -4100,16 +4947,16 @@ } }, "node_modules/eslint/node_modules/brace-expansion": { - "version": "5.0.4", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.4.tgz", - "integrity": "sha512-h+DEnpVvxmfVefa4jFbCf5HdH5YMDXRsmKflpf1pILZWRFlTbJpxeU55nJl4Smt5HQaGzg1o6RHFPJaOqnmBDg==", + "version": "5.0.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.12.tgz", + "integrity": "sha512-YovQ3rzhaLMIrDjNDMkNS01tea93qhEhG5xy8f6+R0l+dw3Ki+5sCoIoI942iuLZTHWogWktgwVDhU09iNEimQ==", "dev": true, "license": "MIT", "dependencies": { "balanced-match": "^4.0.2" }, "engines": { - "node": "18 || 20 || >=22" + "node": "20 || >=22" } }, "node_modules/eslint/node_modules/escape-string-regexp": { @@ -4341,9 +5188,9 @@ } }, "node_modules/ethers": { - "version": "6.13.5", - "resolved": "https://registry.npmjs.org/ethers/-/ethers-6.13.5.tgz", - "integrity": "sha512-+knKNieu5EKRThQJWwqaJ10a6HE9sSehGeqWN65//wE7j47ZpFhKAnHB/JJFibwwg61I/koxaPsXbXpD/skNOQ==", + "version": "6.17.0", + "resolved": "https://registry.npmjs.org/ethers/-/ethers-6.17.0.tgz", + "integrity": "sha512-BpyrpIPJ3ydEVow8zGaz1DuPS7YU8DcWxuBnY9a0UA/lvAPwrMr+EPXsfrul628SRaekPNeIM4UFh/91GWZang==", "funding": [ { "type": "individual", @@ -4356,13 +5203,13 @@ ], "license": "MIT", "dependencies": { - "@adraffy/ens-normalize": "1.10.1", + "@adraffy/ens-normalize": "1.11.1", "@noble/curves": "1.2.0", "@noble/hashes": "1.3.2", "@types/node": "22.7.5", "aes-js": "4.0.0-beta.5", "tslib": "2.7.0", - "ws": "8.17.1" + "ws": "8.21.0" }, "engines": { "node": ">=14.0.0" @@ -4608,16 +5455,16 @@ } }, "node_modules/flatted": { - "version": "3.4.1", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.1.tgz", - "integrity": "sha512-IxfVbRFVlV8V/yRaGzk0UVIcsKKHMSfYw66T/u4nTwlWteQePsxe//LjudR1AMX4tZW3WFCh3Zqa/sjlqpbURQ==", + "version": "3.4.4", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.4.tgz", + "integrity": "sha512-5+ybhBZANEJxaH3X5evAFatUxLfEHSr7n6kYJ+1Qd0mUqr4eu9gIf6GDbWHf8RJijHrjjO8G+la14SlL2SeS1Q==", "dev": true, "license": "ISC" }, "node_modules/follow-redirects": { - "version": "1.15.11", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.11.tgz", - "integrity": "sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==", + "version": "1.16.1", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.16.1.tgz", + "integrity": "sha512-FNvFGzoMLWmE6Yj9spb/zjd7yiNCHiAW9/Tg9CXrQ8wuu32HtlJOwWO11OJafl5FfY3DxTdQ0vj42zU1kvv5jg==", "funding": [ { "type": "individual", @@ -4652,16 +5499,16 @@ } }, "node_modules/form-data": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz", - "integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==", + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.6.tgz", + "integrity": "sha512-vKatAh4SlVfgbv+YtmhiRjhEMJsYpsG1Y2rMQtR+SVSbytsSD1YGzDIcrAJmdFec88u/+VoGmxnl+80gL1tRCQ==", "license": "MIT", "dependencies": { "asynckit": "^0.4.0", "combined-stream": "^1.0.8", "es-set-tostringtag": "^2.1.0", - "hasown": "^2.0.2", - "mime-types": "^2.1.12" + "hasown": "^2.0.4", + "mime-types": "^2.1.35" }, "engines": { "node": ">= 6" @@ -4839,13 +5686,13 @@ "license": "ISC" }, "node_modules/handlebars": { - "version": "4.7.8", - "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.8.tgz", - "integrity": "sha512-vafaFqs8MZkRrSX7sFVUdo3ap/eNiLnb4IakshzvP56X5Nr1iGKAIqdX6tMlm6HcNRIkr6AxO5jFEoJzzpT8aQ==", + "version": "4.7.10", + "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.10.tgz", + "integrity": "sha512-P5VJMVM7qgBn6vjXMw8WG9uVI+ncf2pi72j4de4yz5ZULLj2RGqLYaKOYGsgyrViQ0tePOVlN1tDCCXXtFqXKg==", "dev": true, "license": "MIT", "dependencies": { - "minimist": "^1.2.5", + "minimist": "^1.2.8", "neo-async": "^2.6.2", "source-map": "^0.6.1", "wordwrap": "^1.0.0" @@ -4896,10 +5743,20 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/hash.js": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/hash.js/-/hash.js-1.1.7.tgz", + "integrity": "sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA==", + "license": "MIT", + "dependencies": { + "inherits": "^2.0.3", + "minimalistic-assert": "^1.0.1" + } + }, "node_modules/hasown": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", - "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.4.tgz", + "integrity": "sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==", "license": "MIT", "dependencies": { "function-bind": "^1.1.2" @@ -4908,6 +5765,17 @@ "node": ">= 0.4" } }, + "node_modules/hmac-drbg": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz", + "integrity": "sha512-Tti3gMqLdZfhOQY1Mzf/AanLiqh1WTiJgEj26ZuYQ9fbkLomzGchCws4FyrSd4VkpBfiNhaE1On+lOz894jvXg==", + "license": "MIT", + "dependencies": { + "hash.js": "^1.0.3", + "minimalistic-assert": "^1.0.0", + "minimalistic-crypto-utils": "^1.0.1" + } + }, "node_modules/html-escaper": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", @@ -4915,6 +5783,19 @@ "dev": true, "license": "MIT" }, + "node_modules/https-proxy-agent": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", + "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", + "license": "MIT", + "dependencies": { + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/human-signals": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", @@ -5281,9 +6162,9 @@ "license": "MIT" }, "node_modules/jayson/node_modules/ws": { - "version": "7.5.10", - "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.10.tgz", - "integrity": "sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ==", + "version": "7.5.13", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.13.tgz", + "integrity": "sha512-rsKI6xDBFVf4r/x8XyChGK04QR/XHroxs/jUcoWvtEZM8TPU/X/uIY9B1CsSzYws9ZJb/6bbBu7dPhFW00CAoA==", "license": "MIT", "engines": { "node": ">=8.3.0" @@ -5893,6 +6774,12 @@ "url": "https://github.com/chalk/supports-color?sponsor=1" } }, + "node_modules/js-sha3": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/js-sha3/-/js-sha3-0.8.0.tgz", + "integrity": "sha512-gF1cRrHhIzNfToc802P800N8PpXS+evLLXfsVpowqmAFR9uwbi89WvXg2QspOmXL8QL86J4T1EpFu+yUkwJY3Q==", + "license": "MIT" + }, "node_modules/js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", @@ -5901,9 +6788,9 @@ "license": "MIT" }, "node_modules/js-yaml": { - "version": "3.14.2", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.2.tgz", - "integrity": "sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg==", + "version": "3.15.2", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.15.2.tgz", + "integrity": "sha512-6EuL879VkRA+1Cz578mKMiKvjPNEuk6+r1JaFzoSWejZmtf7xWbIyw1e3KkxlkzTIt9Taw6JBhEppG7utc1P+w==", "dev": true, "license": "MIT", "dependencies": { @@ -5914,6 +6801,12 @@ "js-yaml": "bin/js-yaml.js" } }, + "node_modules/jsbi": { + "version": "3.2.5", + "resolved": "https://registry.npmjs.org/jsbi/-/jsbi-3.2.5.tgz", + "integrity": "sha512-aBE4n43IPvjaddScbvWRA2YlTzKEynHzu7MqOyTipdHucf/VxS63ViCjxYRg86M8Rxwbt/GfzHl1kKERkt45fQ==", + "license": "Apache-2.0" + }, "node_modules/jsesc": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", @@ -6153,6 +7046,18 @@ "node": ">=6" } }, + "node_modules/minimalistic-assert": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz", + "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==", + "license": "ISC" + }, + "node_modules/minimalistic-crypto-utils": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz", + "integrity": "sha512-JIYlbt6g8i5jKfJ3xz7rF0LXmv2TkDxBLUkiBeZ7bAx4GnnNMr8xFpGnOxn6GhTEHx3SjRrZEoU+j04prX1ktg==", + "license": "MIT" + }, "node_modules/minimatch": { "version": "9.0.9", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz", @@ -6274,11 +7179,14 @@ "license": "MIT" }, "node_modules/node-releases": { - "version": "2.0.36", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.36.tgz", - "integrity": "sha512-TdC8FSgHz8Mwtw9g5L4gR/Sh9XhSP/0DEkQxfEFXOpiul5IiHgHan2VhYYb6agDSfp4KuvltmGApc8HMgUrIkA==", + "version": "2.0.58", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.58.tgz", + "integrity": "sha512-BdiKAgbfRlvfMq/XzO/AF9NPmaA8a+YpLAyAFTVFBj3gARrAIfWvNq7Q7NE4se3KgFu0KkrvQMxTS0XVnsGzHg==", "dev": true, - "license": "MIT" + "license": "MIT", + "engines": { + "node": ">=18" + } }, "node_modules/normalize-path": { "version": "3.0.0", @@ -6391,9 +7299,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.54", + "resolved": "https://registry.npmjs.org/ox/-/ox-0.14.54.tgz", + "integrity": "sha512-52PGH6ldJmWY9+iy/TBSJUsCSy5NmLvkVBYTINmO8srsjRrCmCdO575kGizmZ1jpNQ1gnlpVwiciIpU37fcULA==", "funding": [ { "type": "github", @@ -6420,12 +7328,6 @@ } } }, - "node_modules/ox/node_modules/@adraffy/ens-normalize": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@adraffy/ens-normalize/-/ens-normalize-1.11.1.tgz", - "integrity": "sha512-nhCBV3quEgesuf7c7KYfperqSS14T8bYuvJ8PcLJp6znkZpFc0AuW4qBtr8eKVyPPe/8RSr7sglCWPU5eaxwKQ==", - "license": "MIT" - }, "node_modules/ox/node_modules/@noble/curves": { "version": "1.9.1", "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.9.1.tgz", @@ -6620,9 +7522,9 @@ "license": "ISC" }, "node_modules/picomatch": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", - "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.7.tgz", + "integrity": "sha512-qcJu88Q2IWqJsDD529JKMdwGm/dvInW4HvQnRwiH9JtihJvzGOscDtHE3x1pBKeUOTysQ8kVmLnJ2kJu7yhcGA==", "dev": true, "license": "MIT", "engines": { @@ -6723,10 +7625,13 @@ } }, "node_modules/proxy-from-env": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", - "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", - "license": "MIT" + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-2.1.0.tgz", + "integrity": "sha512-cJ+oHTW1VAEa8cJslgmUZrc+sjRKgAKl3Zyse6+PV38hZe/V6Z14TbCuXcan9F9ghlz4QrFr2c92TNF82UkYHA==", + "license": "MIT", + "engines": { + "node": ">=10" + } }, "node_modules/punycode": { "version": "2.3.1", @@ -6891,9 +7796,9 @@ } }, "node_modules/rpc-websockets/node_modules/uuid": { - "version": "11.1.0", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-11.1.0.tgz", - "integrity": "sha512-0/A9rDy9P7cJ+8w1c9WD9V//9Wj15Ce2MPz8Ri6032usz+NfePxx5AcN3bN+r6ZL6jEo066/yNYB3tn4pQEx+A==", + "version": "11.1.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-11.1.1.tgz", + "integrity": "sha512-vIYxrBCC/N/K+Js3qSN88go7kIfNPssr/hHCesKCQNAjmgvYS2oqr69kIufEG+O4+PfezOH4EbIeHCfFov8ZgQ==", "funding": [ "https://github.com/sponsors/broofa", "https://github.com/sponsors/ctavan" @@ -7286,9 +8191,9 @@ } }, "node_modules/test-exclude/node_modules/brace-expansion": { - "version": "1.1.12", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", - "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "version": "1.1.21", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.21.tgz", + "integrity": "sha512-9zeA+KLZNNzglF2TPKRQEDyx6Yby7daAkuy8MiPzpXPsYDWi/DRM8jmwUDxokQjYqBpv5DgPiwD4h4ZZSy1Ujw==", "dev": true, "license": "MIT", "dependencies": { @@ -7385,19 +8290,19 @@ "license": "MIT" }, "node_modules/tronweb": { - "version": "6.2.2", - "resolved": "https://registry.npmjs.org/tronweb/-/tronweb-6.2.2.tgz", - "integrity": "sha512-jRBf4+7fJ0HUVzveBi0tE21r3EygCNtbYE92T38Sxlwr/x320W2vz+dvGLOIpp4kW/CvJ4HLvtnb6U30A0V2eA==", + "version": "6.5.1", + "resolved": "https://registry.npmjs.org/tronweb/-/tronweb-6.5.1.tgz", + "integrity": "sha512-mqa3WIBq7sYtkkIywa2Jop3UwJX6EgIrBkFpm3sSjppStTOTtYeMJyeIHJVvFFCaYN4vjU1FDjQPSvHFTcuzEw==", "license": "MIT", "dependencies": { - "@babel/runtime": "7.26.10", - "axios": "1.13.5", + "axios": "1.18.0", "bignumber.js": "9.1.2", "ethereum-cryptography": "2.2.1", - "ethers": "6.13.5", + "ethers": "6.17.0", "eventemitter3": "5.0.1", "google-protobuf": "3.21.4", "semver": "7.7.1", + "tslib": "2.7.0", "validator": "13.15.23" } }, @@ -7684,9 +8589,9 @@ } }, "node_modules/update-browserslist-db": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz", - "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==", + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.3.4.tgz", + "integrity": "sha512-t8wonIOdQyOjtPd2VktlBu6QjTVTks6v8bSi7kd0r2Vll8TK1JYDZtRRdbmCHOAMphkZgrzK+L+vXK4pyZ8t4A==", "dev": true, "funding": [ { @@ -7785,9 +8690,9 @@ } }, "node_modules/viem": { - "version": "2.47.2", - "resolved": "https://registry.npmjs.org/viem/-/viem-2.47.2.tgz", - "integrity": "sha512-etDIwDgmDiGaPg8rUbJtUFuC3/nAJCbhMYyfh5dOcqNNkzBWTNcS2VluPSM5JVo+9U3b2hle2RkBEq3+xyvlvg==", + "version": "2.57.4", + "resolved": "https://registry.npmjs.org/viem/-/viem-2.57.4.tgz", + "integrity": "sha512-ro+8AKrcGU6tPfou0gBRYv6bxnU3tPxxJGY4qpJwjiR40eHCWzeGhrAgYMlIncFtlnry2/yGjz57WOC67oshCQ==", "funding": [ { "type": "github", @@ -7802,8 +8707,8 @@ "@scure/bip39": "1.6.0", "abitype": "1.2.3", "isows": "1.0.7", - "ox": "0.14.0", - "ws": "8.18.3" + "ox": "0.14.54", + "ws": "8.21.0" }, "peerDependencies": { "typescript": ">=5.0.4" @@ -7865,27 +8770,6 @@ "url": "https://paulmillr.com/funding/" } }, - "node_modules/viem/node_modules/ws": { - "version": "8.18.3", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.3.tgz", - "integrity": "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==", - "license": "MIT", - "engines": { - "node": ">=10.0.0" - }, - "peerDependencies": { - "bufferutil": "^4.0.1", - "utf-8-validate": ">=5.0.2" - }, - "peerDependenciesMeta": { - "bufferutil": { - "optional": true - }, - "utf-8-validate": { - "optional": true - } - } - }, "node_modules/walker": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/walker/-/walker-1.0.8.tgz", @@ -8074,9 +8958,9 @@ } }, "node_modules/ws": { - "version": "8.17.1", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz", - "integrity": "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==", + "version": "8.21.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.21.0.tgz", + "integrity": "sha512-Vsp28b7DRcimFQvrqu2Wek3z1iYxDCWqHYB8Qsnk/S4RfaCQzPGPyBNuVjJV3cd6UiKtUtp6sNM77gWvzcCH+g==", "license": "MIT", "engines": { "node": ">=10.0.0" diff --git a/package.json b/package.json index 94be3e2..55518fa 100644 --- a/package.json +++ b/package.json @@ -8,7 +8,7 @@ "sun": "./bin/sun" }, "scripts": { - "build": "tsc", + "build": "npm run clean && tsc", "dev": "ts-node src/bin.ts", "start": "node dist/bin.js", "clean": "rm -rf dist", @@ -17,7 +17,7 @@ "lint:fix": "eslint . --fix", "format": "prettier --write .", "format:check": "prettier --check .", - "prepublishOnly": "npm run clean && npm run build", + "prepublishOnly": "npm run build", "publish:beta": "npm publish --tag beta --access public", "publish:latest": "npm publish --access public" }, @@ -37,6 +37,9 @@ "ai-agent" ], "license": "MIT", + "engines": { + "node": ">=20" + }, "overrides": { "utf-8-validate": "5.0.10" }, @@ -44,13 +47,17 @@ "@bankofai/agent-wallet": "^2.3.0", "@scure/bip32": "^2.0.1", "@scure/bip39": "^2.0.1", - "@sun-protocol/sun-kit": "^1.2.0", + "@sun-protocol/sun-sdk-api": "0.1.0", + "@sun-protocol/sun-sdk-chains": "0.1.0", + "@sun-protocol/sun-sdk-core": "0.1.0", + "@sun-protocol/sun-sdk-protocols": "0.1.0", + "@sun-protocol/sun-sdk-runtime": "0.1.0", "chalk": "^4.1.2", "cli-table3": "^0.6.5", "commander": "^12.1.0", "dotenv": "^16.4.5", "ora": "^5.4.1", - "tronweb": "^6.2.1" + "tronweb": "^6.5.1" }, "devDependencies": { "@eslint/js": "^10.0.1", diff --git a/src/commands/contract.ts b/src/commands/contract.ts index 05c9548..e0d6fd1 100644 --- a/src/commands/contract.ts +++ b/src/commands/contract.ts @@ -1,6 +1,16 @@ import { Command } from 'commander' import { getNetwork } from '../lib/context' import { writeAction, readAction } from '../lib/command' +import { toCliTxResult } from '../lib/sdk/compat' + +function parseAbi(value: string | undefined): any[] { + if (!value) throw new Error('--abi is required for SDK contract calls') + return JSON.parse(value) +} + +function parseValue(value: string | undefined): bigint | undefined { + return value === undefined ? undefined : BigInt(value) +} export function registerContractCommands(program: Command) { const contract = program @@ -16,16 +26,13 @@ export function registerContractCommands(program: Command) { await readAction({ spinnerLabel: `Reading ${functionName}...`, errorLabel: 'Contract read failed', - execute: (kit) => - kit.readContract( - { - address, - functionName, - args: JSON.parse(opts.args), - abi: opts.abi ? JSON.parse(opts.abi) : undefined, - }, - getNetwork(), - ), + execute: (sdk) => + sdk.runtime.contract.read({ + address, + functionName, + args: JSON.parse(opts.args), + abi: parseAbi(opts.abi), + }), transform: (result) => ({ result }), }) }) @@ -50,14 +57,15 @@ export function registerContractCommands(program: Command) { spinnerLabel: `Sending ${functionName}...`, errorLabel: 'Contract send failed', execute: (kit) => - kit.sendContractTx({ - address, - functionName, - args: JSON.parse(opts.args), - value: opts.value, - abi: opts.abi ? JSON.parse(opts.abi) : undefined, - network: getNetwork(), - }), + kit.runtime.contract + .write({ + address, + functionName, + args: JSON.parse(opts.args), + callValue: parseValue(opts.value), + abi: parseAbi(opts.abi), + }) + .then(toCliTxResult), }) }) } diff --git a/src/commands/liquidity.ts b/src/commands/liquidity.ts index cd14636..3b37653 100644 --- a/src/commands/liquidity.ts +++ b/src/commands/liquidity.ts @@ -3,54 +3,96 @@ import { getNetwork } from '../lib/context' import { writeAction, readAction } from '../lib/command' import { withSpinner } from '../lib/output' import { resolveTokenAddress, getSymbolOrAddress } from '../lib/tokens' -import { - SUNSWAP_V2_MAINNET_ROUTER, - SUNSWAP_V2_NILE_ROUTER, - SUNSWAP_V2_MAINNET_FACTORY, - SUNSWAP_V2_NILE_FACTORY, - SUNSWAP_V2_FACTORY_MIN_ABI, - SUNSWAP_V2_PAIR_MIN_ABI, - SUNSWAP_V3_MAINNET_POSITION_MANAGER, - SUNSWAP_V3_NILE_POSITION_MANAGER, - TRX_ADDRESS, - WTRX_MAINNET, - WTRX_NILE, - createReadonlyTronWeb, -} from '@sun-protocol/sun-kit' - -const V2_ROUTERS: Record = { - mainnet: SUNSWAP_V2_MAINNET_ROUTER, - nile: SUNSWAP_V2_NILE_ROUTER, +import { getContractAddress } from '@sun-protocol/sun-sdk-chains' +import type { Network } from '@sun-protocol/sun-sdk-core' +import { TronWeb } from 'tronweb' +import { TRX_ADDRESS, WTRX_MAINNET, WTRX_NILE } from '../lib/sdk/constants' +import { createReadonlyTronWeb } from '../lib/sdk/factory' +import { toCliTxResult } from '../lib/sdk/compat' + +const SUNSWAP_V2_FACTORY_MIN_ABI = [ + { + constant: true, + inputs: [ + { name: 'tokenA', type: 'address' }, + { name: 'tokenB', type: 'address' }, + ], + name: 'getPair', + outputs: [{ name: 'pair', type: 'address' }], + type: 'function', + }, +] + +const SUNSWAP_V2_PAIR_MIN_ABI = [ + { + constant: true, + inputs: [], + name: 'getReserves', + outputs: [ + { name: '_reserve0', type: 'uint112' }, + { name: '_reserve1', type: 'uint112' }, + { name: '_blockTimestampLast', type: 'uint32' }, + ], + type: 'function', + }, + { + constant: true, + inputs: [], + name: 'token0', + outputs: [{ name: '', type: 'address' }], + type: 'function', + }, +] + +const TICK_SPACING_BY_FEE: Record = { + 100: 1, + 500: 10, + 3000: 60, + 10000: 200, } -const V2_FACTORIES: Record = { - mainnet: SUNSWAP_V2_MAINNET_FACTORY, - nile: SUNSWAP_V2_NILE_FACTORY, -} - -const V3_POSITION_MANAGERS: Record = { - mainnet: SUNSWAP_V3_MAINNET_POSITION_MANAGER, - nile: SUNSWAP_V3_NILE_POSITION_MANAGER, +function assertSdkNetwork(network: string): Network { + if (network === 'mainnet' || network === 'nile') return network + throw new Error(`Unsupported SDK network: ${network}`) } function getV2Router(network: string, override?: string): string { if (override) return override - const router = V2_ROUTERS[network] - if (!router) { - throw new Error(`V2 router not configured for network: ${network}. Use --router to specify.`) - } - return router + return getContractAddress(assertSdkNetwork(network), 'sunswapV2Router') } function getV3PositionManager(network: string, override?: string): string { if (override) return override - const pm = V3_POSITION_MANAGERS[network] - if (!pm) { - throw new Error( - `V3 position manager not configured for network: ${network}. Use --pm to specify.`, - ) - } - return pm + return getContractAddress(assertSdkNetwork(network), 'sunswapV3PositionManager') +} + +function getV2Factory(network: string): string { + return getContractAddress(assertSdkNetwork(network), 'sunswapV2Factory') +} + +function percentFromDecimal(value: string | undefined): `${number}%` | undefined { + return value === undefined ? undefined : (`${parseFloat(value) * 100}%` as `${number}%`) +} + +function tickSpacingForFee(fee: number): number { + return TICK_SPACING_BY_FEE[fee] ?? 60 +} + +function toV3PoolOrder(token0: string, token1: string, value0: T, value1: T): [T, T] { + const token0Hex = TronWeb.address.toHex(token0).toLowerCase() + const token1Hex = TronWeb.address.toHex(token1).toLowerCase() + return token0Hex < token1Hex ? [value0, value1] : [value1, value0] +} + +function withContracts( + overrides: Record, +): Record | undefined { + const entries = Object.entries(overrides).filter((entry): entry is [string, string] => !!entry[1]) + return entries.length ? Object.fromEntries(entries) : undefined +} + +function mapTx(result: unknown): unknown { + return toCliTxResult(result) } function toWTRXIfNative(tokenAddress: string, network: string): string { @@ -79,11 +121,8 @@ async function getV2PairReserves( tokenA: string, tokenB: string, ): Promise { - const tronWeb = await createReadonlyTronWeb(network) - const factoryAddress = V2_FACTORIES[network] - if (!factoryAddress) { - throw new Error(`V2 factory not configured for network: ${network}`) - } + const tronWeb = createReadonlyTronWeb({ network }) + const factoryAddress = getV2Factory(network) const lookupA = getLookupToken(tokenA, network) const lookupB = getLookupToken(tokenB, network) @@ -212,20 +251,20 @@ export function registerLiquidityCommands(program: Command) { confirmMsg: 'Add V2 liquidity?', spinnerLabel: 'Adding V2 liquidity...', errorLabel: 'V2 add liquidity failed', - execute: (kit) => - kit.addLiquidityV2({ - network, - routerAddress: router, - abi: opts.abi ? JSON.parse(opts.abi) : undefined, - tokenA, - tokenB, - amountADesired: amountA, - amountBDesired: amountB, - amountAMin: opts.minA, - amountBMin: opts.minB, - to: opts.to, - deadline: opts.deadline, - }), + execute: (sdk) => + sdk.liquidity.v2.add + .execute({ + contracts: withContracts({ sunswapV2Router: router }), + tokenA, + tokenB, + amountADesired: amountA, + amountBDesired: amountB, + amountAMin: opts.minA, + amountBMin: opts.minB, + recipient: opts.to, + deadline: opts.deadline, + }) + .then(mapTx), }) }) @@ -270,19 +309,19 @@ export function registerLiquidityCommands(program: Command) { confirmMsg: 'Remove V2 liquidity?', spinnerLabel: 'Removing V2 liquidity...', errorLabel: 'V2 remove liquidity failed', - execute: (kit) => - kit.removeLiquidityV2({ - network, - routerAddress: router, - abi: opts.abi ? JSON.parse(opts.abi) : undefined, - tokenA, - tokenB, - liquidity: opts.liquidity, - amountAMin: opts.minA, - amountBMin: opts.minB, - to: opts.to, - deadline: opts.deadline, - }), + execute: (sdk) => + sdk.liquidity.v2.remove + .execute({ + contracts: withContracts({ sunswapV2Router: router }), + tokenA, + tokenB, + liquidity: opts.liquidity, + amountAMin: opts.minA, + amountBMin: opts.minB, + recipient: opts.to, + deadline: opts.deadline, + }) + .then(mapTx), }) }) @@ -336,6 +375,8 @@ export function registerLiquidityCommands(program: Command) { const tickRange = opts.tickLower && opts.tickUpper ? `[${opts.tickLower}, ${opts.tickUpper}]` : '(auto)' + const [amount0, amount1] = toV3PoolOrder(token0, token1, opts.amount0, opts.amount1) + const [amount0Min, amount1Min] = toV3PoolOrder(token0, token1, opts.min0, opts.min1) await writeAction({ title: 'V3 Mint Position', @@ -352,23 +393,25 @@ export function registerLiquidityCommands(program: Command) { confirmMsg: 'Mint V3 position?', spinnerLabel: 'Minting V3 position...', errorLabel: 'V3 mint failed', - execute: (kit) => - kit.mintPositionV3({ - network, - positionManagerAddress: pm, - abi: opts.abi ? JSON.parse(opts.abi) : undefined, - token0, - token1, - fee: opts.fee ? parseInt(opts.fee) : undefined, - tickLower: opts.tickLower ? parseInt(opts.tickLower) : undefined, - tickUpper: opts.tickUpper ? parseInt(opts.tickUpper) : undefined, - amount0Desired: opts.amount0, - amount1Desired: opts.amount1, - amount0Min: opts.min0, - amount1Min: opts.min1, - recipient: opts.recipient, - deadline: opts.deadline, - }), + execute: (sdk) => + sdk.liquidity.v3.add + .execute({ + contracts: withContracts({ sunswapV3PositionManager: pm }), + tokenA: token0, + tokenB: token1, + fee: opts.fee ? parseInt(opts.fee) : 3000, + tickLower: opts.tickLower ? parseInt(opts.tickLower) : undefined, + tickUpper: opts.tickUpper ? parseInt(opts.tickUpper) : undefined, + tickRangeWidth: + opts.tickLower === undefined && opts.tickUpper === undefined ? 100 : undefined, + amount0, + amount1, + amount0Min, + amount1Min, + recipient: opts.recipient, + deadline: opts.deadline, + }) + .then(mapTx), summarizeResult: (result: any) => { const out: Record = {} if (result?.computedTicks) { @@ -418,18 +461,18 @@ export function registerLiquidityCommands(program: Command) { confirmMsg: 'Increase V3 liquidity?', spinnerLabel: 'Increasing V3 liquidity...', errorLabel: 'V3 increase liquidity failed', - execute: (kit) => - kit.increaseLiquidityV3({ - network, - positionManagerAddress: pm, - abi: opts.abi ? JSON.parse(opts.abi) : undefined, - tokenId: opts.tokenId, - amount0Desired: opts.amount0, - amount1Desired: opts.amount1, - amount0Min: opts.min0, - amount1Min: opts.min1, - deadline: opts.deadline, - }), + execute: (sdk) => + sdk.liquidity.v3.increase + .execute({ + contracts: withContracts({ sunswapV3PositionManager: pm }), + tokenId: opts.tokenId, + amount0: opts.amount0, + amount1: opts.amount1, + amount0Min: opts.min0, + amount1Min: opts.min1, + deadline: opts.deadline, + }) + .then(mapTx), summarizeResult: (result: any) => { const out: Record = {} if (result?.computedAmounts) { @@ -467,17 +510,17 @@ export function registerLiquidityCommands(program: Command) { confirmMsg: 'Decrease V3 liquidity?', spinnerLabel: 'Decreasing V3 liquidity...', errorLabel: 'V3 decrease liquidity failed', - execute: (kit) => - kit.decreaseLiquidityV3({ - network, - positionManagerAddress: pm, - abi: opts.abi ? JSON.parse(opts.abi) : undefined, - tokenId: opts.tokenId, - liquidity: opts.liquidity, - amount0Min: opts.min0, - amount1Min: opts.min1, - deadline: opts.deadline, - }), + execute: (sdk) => + sdk.positions.v3.remove + .execute({ + contracts: withContracts({ sunswapV3PositionManager: pm }), + tokenId: opts.tokenId, + liquidity: opts.liquidity, + amount0Min: opts.min0, + amount1Min: opts.min1, + deadline: opts.deadline, + }) + .then(mapTx), }) }) @@ -503,14 +546,14 @@ export function registerLiquidityCommands(program: Command) { confirmMsg: 'Collect V3 fees?', spinnerLabel: 'Collecting V3 fees...', errorLabel: 'V3 collect failed', - execute: (kit) => - kit.collectPositionV3({ - network, - positionManagerAddress: pm, - abi: opts.abi ? JSON.parse(opts.abi) : undefined, - tokenId: opts.tokenId, - recipient: opts.recipient, - }), + execute: (sdk) => + sdk.positions.v3.collect + .execute({ + contracts: withContracts({ sunswapV3PositionManager: pm }), + tokenId: opts.tokenId, + recipient: opts.recipient, + }) + .then(mapTx), summarizeResult: (result: any) => { const out: Record = {} if (result?.estimatedFees) { @@ -562,15 +605,24 @@ export function registerLiquidityCommands(program: Command) { const token1Display = getSymbolOrAddress(token1Input, network) const token0Note = token0 !== token0Input ? ` → WTRX` : '' const token1Note = token1 !== token1Input ? ` → WTRX` : '' + const fee = opts.fee ? parseInt(opts.fee) : 3000 + const tickSpacing = tickSpacingForFee(fee) + const tickLower = opts.tickLower ? parseInt(opts.tickLower) : -2 * tickSpacing + const tickUpper = opts.tickUpper ? parseInt(opts.tickUpper) : 2 * tickSpacing + + if (!opts.amount0 && !opts.amount1) { + console.error('Error: At least one of --amount0 or --amount1 is required') + process.exitCode = 1 + return + } await writeAction({ title: 'V4 Mint Position', summary: { Token0: `${token0Display}${token0Note} (${token0})`, Token1: `${token1Display}${token1Note} (${token1})`, - Fee: opts.fee || '(auto)', - 'Tick Range': - opts.tickLower && opts.tickUpper ? `[${opts.tickLower}, ${opts.tickUpper}]` : '(auto)', + Fee: fee, + 'Tick Range': `[${tickLower}, ${tickUpper}]`, Amount0: opts.amount0 || '(auto)', Amount1: opts.amount1 || '(auto)', Slippage: opts.slippage @@ -582,22 +634,24 @@ export function registerLiquidityCommands(program: Command) { confirmMsg: 'Mint V4 position?', spinnerLabel: 'Minting V4 position...', errorLabel: 'V4 mint failed', - execute: (kit) => - kit.mintPositionV4({ - network, - token0, - token1, - fee: opts.fee ? parseInt(opts.fee) : undefined, - tickLower: opts.tickLower ? parseInt(opts.tickLower) : undefined, - tickUpper: opts.tickUpper ? parseInt(opts.tickUpper) : undefined, - amount0Desired: opts.amount0, - amount1Desired: opts.amount1, - slippage: opts.slippage ? parseFloat(opts.slippage) : undefined, - recipient: opts.recipient, - deadline: opts.deadline, - sqrtPriceX96: opts.sqrtPrice, - createPoolIfNeeded: opts.createPool, - }), + execute: (sdk) => + sdk.liquidity.v4.add + .execute({ + token0, + token1, + fee, + tickSpacing, + tickLower, + tickUpper, + amount0: opts.amount0, + amount1: opts.amount1, + slippage: percentFromDecimal(opts.slippage), + owner: opts.recipient, + deadline: opts.deadline, + sqrtPriceX96: opts.sqrtPrice, + createPool: opts.createPool, + }) + .then(mapTx), summarizeResult: (result: any) => { const out: Record = {} if (result?.poolCreated !== undefined) out['Pool Created'] = result.poolCreated @@ -645,6 +699,12 @@ export function registerLiquidityCommands(program: Command) { const token0Display = getSymbolOrAddress(token0Input, network) const token1Display = getSymbolOrAddress(token1Input, network) + if (!opts.amount0 && !opts.amount1) { + console.error('Error: At least one of --amount0 or --amount1 is required') + process.exitCode = 1 + return + } + await writeAction({ title: 'V4 Increase Liquidity', summary: { @@ -658,18 +718,16 @@ export function registerLiquidityCommands(program: Command) { confirmMsg: 'Increase V4 liquidity?', spinnerLabel: 'Increasing V4 liquidity...', errorLabel: 'V4 increase liquidity failed', - execute: (kit) => - kit.increaseLiquidityV4({ - network, - tokenId: opts.tokenId, - token0, - token1, - fee: opts.fee ? parseInt(opts.fee) : undefined, - amount0Desired: opts.amount0, - amount1Desired: opts.amount1, - slippage: opts.slippage ? parseFloat(opts.slippage) : undefined, - deadline: opts.deadline, - }), + execute: (sdk) => + sdk.liquidity.v4.increase + .execute({ + tokenId: opts.tokenId, + amount0: opts.amount0, + amount1: opts.amount1, + slippage: percentFromDecimal(opts.slippage), + deadline: opts.deadline, + }) + .then(mapTx), summarizeResult: (result: any) => { const out: Record = {} if (result?.computedAmounts) { @@ -725,19 +783,17 @@ export function registerLiquidityCommands(program: Command) { confirmMsg: 'Decrease V4 liquidity?', spinnerLabel: 'Decreasing V4 liquidity...', errorLabel: 'V4 decrease liquidity failed', - execute: (kit) => - kit.decreaseLiquidityV4({ - network, - tokenId: opts.tokenId, - liquidity: opts.liquidity, - token0, - token1, - fee: opts.fee ? parseInt(opts.fee) : undefined, - amount0Min: opts.min0, - amount1Min: opts.min1, - slippage: opts.slippage ? parseFloat(opts.slippage) : undefined, - deadline: opts.deadline, - }), + execute: (sdk) => + sdk.positions.v4.remove + .execute({ + tokenId: opts.tokenId, + liquidity: opts.liquidity, + amount0Min: opts.min0, + amount1Min: opts.min1, + slippage: percentFromDecimal(opts.slippage), + deadline: opts.deadline, + }) + .then(mapTx), summarizeResult: (result: any) => { const out: Record = {} if (result?.computedAmountMin) { @@ -794,15 +850,13 @@ export function registerLiquidityCommands(program: Command) { confirmMsg: 'Collect V4 fees?', spinnerLabel: 'Collecting V4 fees...', errorLabel: 'V4 collect failed', - execute: (kit) => - kit.collectPositionV4({ - network, - tokenId: opts.tokenId, - token0, - token1, - fee: opts.fee ? parseInt(opts.fee) : undefined, - deadline: opts.deadline, - }), + execute: (sdk) => + sdk.positions.v4.collect + .execute({ + tokenId: opts.tokenId, + deadline: opts.deadline, + }) + .then(mapTx), }) }) @@ -815,14 +869,18 @@ export function registerLiquidityCommands(program: Command) { await readAction({ spinnerLabel: 'Fetching V4 position info...', errorLabel: 'V4 position info failed', - execute: (kit) => kit.getV4PositionInfo(opts.pm, opts.tokenId, getNetwork()), - transform: (result) => { + execute: (sdk) => + sdk.positions.v4.read({ + tokenId: opts.tokenId, + contracts: withContracts({ sunswapV4PositionManager: opts.pm }), + }), + transform: (result: any) => { if (!result) return { error: 'Position not found' } return { - currency0: result.poolKey.currency0, - currency1: result.poolKey.currency1, - fee: result.poolKey.fee, - tickSpacing: result.poolKey.tickSpacing, + currency0: result.poolKey?.currency0, + currency1: result.poolKey?.currency1, + fee: result.poolKey?.fee, + tickSpacing: result.poolKey?.tickSpacing, tickLower: result.tickLower, tickUpper: result.tickUpper, liquidity: result.liquidity, diff --git a/src/commands/pool.ts b/src/commands/pool.ts index 491d4de..56752bf 100644 --- a/src/commands/pool.ts +++ b/src/commands/pool.ts @@ -3,7 +3,7 @@ import { getNetwork } from '../lib/context' import { readApiAction } from '../lib/command' import { formatUsd, formatPct, formatTime } from '../lib/output' import { tryResolveTokenAddress } from '../lib/tokens' -import { TRX_ADDRESS, WTRX_MAINNET, WTRX_NILE } from '@sun-protocol/sun-kit' +import { TRX_ADDRESS, WTRX_MAINNET, WTRX_NILE } from '../lib/sdk/constants' function resolveTokenForPoolQuery(input: string | undefined, network: string): string | undefined { if (!input) return undefined diff --git a/src/commands/sunpump.ts b/src/commands/sunpump.ts index 1516c79..31fc6b4 100644 --- a/src/commands/sunpump.ts +++ b/src/commands/sunpump.ts @@ -2,6 +2,8 @@ import { Command } from 'commander' import { isDryRun, parseApiResponse, writeAction } from '../lib/command' import { confirm, printSummary } from '../lib/confirm' import { getNetwork, getKit } from '../lib/context' +import { SunPumpApiClient } from '@sun-protocol/sun-sdk-api' +import { toCliTxResult } from '../lib/sdk/compat' import { output, outputError, @@ -15,7 +17,6 @@ import { formatAmount, formatPct, } from '../lib/output' -import { getSunPump, SunPump } from '../lib/sunpump' // --------------------------------------------------------------------------- // pumpAction — local mirror of readApiAction but for the SunPump client. @@ -26,26 +27,24 @@ import { getSunPump, SunPump } from '../lib/sunpump' interface PumpActionOpts { spinnerLabel: string errorLabel: string - execute: (client: SunPump) => Promise + execute: (client: SunPumpApiClient) => Promise tableConfig?: { headers: string[]; toRow: (item: any) => string[] } transform?: (data: any) => unknown parseEnvelope?: boolean detailFor?: (data: any) => Record | null } -function assertMainnet(): void { - const n = getNetwork() - if (n && n !== 'mainnet') { - throw new Error( - `SunPump is only available on mainnet (got "${n}"). Drop --network or pass --network mainnet.`, - ) +function getSunPumpApi(): SunPumpApiClient { + const network = getNetwork() + if (network !== 'mainnet' && network !== 'nile') { + throw new Error(`Unsupported SDK network: ${network}`) } + return new SunPumpApiClient({ network }) } async function pumpAction(opts: PumpActionOpts): Promise { try { - assertMainnet() - const client = getSunPump() + const client = getSunPumpApi() const raw = await withSpinner(opts.spinnerLabel, () => opts.execute(client)) let data: unknown = raw @@ -237,7 +236,7 @@ function baseUnitsToDecimal(raw: string | bigint, decimals: number): string { return negative ? `-${out}` : out } -// SunKit's enum lists 0/1/2 but the contract uses extra states (e.g. 3 = launched-on-DEX). +// The contract uses extra states (e.g. 3 = launched-on-DEX). // Map what we know; surface raw values verbatim otherwise. const SUNPUMP_STATE_NAME: Record = { 0: 'NOT_EXIST', @@ -280,12 +279,7 @@ const portfolioTable = { // --------------------------------------------------------------------------- export function registerSunpumpCommands(program: Command) { - const sp = program - .command('sunpump') - .description('SunPump endpoints (mainnet only: api-v2.sunpump.meme).') - .hook('preAction', () => { - assertMainnet() - }) + const sp = program.command('sunpump').description('SunPump endpoints (mainnet and nile).') // -------------------------- token group ---------------------------------- const token = sp.command('token').description('Token info, search, holders, ranking') @@ -669,8 +663,7 @@ export function registerSunpumpCommands(program: Command) { } try { - assertMainnet() - const client = getSunPump() + const client = getSunPumpApi() const raw = await withSpinner('Launching token...', () => client.agentTokenLaunch(params)) const { data } = parseApiResponse(raw) if (isJsonMode()) { @@ -706,11 +699,10 @@ export function registerSunpumpCommands(program: Command) { .description('Show SunPump token state (0 NOT_EXIST, 1 TRADING, 2 READY_TO_LAUNCH, 3 LAUNCHED)') .action(async (tokenAddress: string) => { try { - const network = getNetwork() const result = await withSpinner('Fetching token state...', async () => { const kit = await getKit() - const state = await kit.getSunPumpTokenState(tokenAddress, network) - const info = await kit.getSunPumpTokenInfo(tokenAddress, network).catch(() => null) + const state = await kit.pump.state({ token: tokenAddress }) + const info = await kit.pump.info({ token: tokenAddress }).catch(() => null) return { state, info } }) if (isJsonMode()) { @@ -747,10 +739,9 @@ export function registerSunpumpCommands(program: Command) { return } try { - const network = getNetwork() const quote: any = await withSpinner('Fetching buy quote...', async () => { const kit = await getKit() - return kit.sunpumpQuoteBuy(tokenAddress, trxSun, network) + return kit.pump.buy.quote({ token: tokenAddress, trxAmount: trxSun }) }) if (isJsonMode()) { output({ ...quote, tokenAddress, trxSun, trxIn: opts.trx }) @@ -781,10 +772,9 @@ export function registerSunpumpCommands(program: Command) { return } try { - const network = getNetwork() const quote: any = await withSpinner('Fetching sell quote...', async () => { const kit = await getKit() - return kit.sunpumpQuoteSell(tokenAddress, tokenRaw, network) + return kit.pump.sell.quote({ token: tokenAddress, tokenAmount: tokenRaw }) }) if (isJsonMode()) { output({ ...quote, tokenAddress, tokenRaw, amountIn: opts.amount }) @@ -826,7 +816,7 @@ export function registerSunpumpCommands(program: Command) { try { const quote = await withSpinner('Fetching buy quote...', async () => { const kit = await getKit() - return kit.sunpumpQuoteBuy(tokenAddress, trxSun, network) + return kit.pump.buy.quote({ token: tokenAddress, trxAmount: trxSun }) }) summary['Tokens Out (expected)'] = baseUnitsToDecimal((quote as any).tokenAmount, 18) summary['Fee'] = `${baseUnitsToDecimal((quote as any).fee, 6)} TRX` @@ -842,13 +832,14 @@ export function registerSunpumpCommands(program: Command) { spinnerLabel: 'Submitting buy...', errorLabel: 'Buy failed', execute: (kit) => - kit.sunpumpBuy({ - tokenAddress, - trxAmount: trxSun, - minTokenOut: opts.minOut, - slippage, - network, - }), + kit.pump.buy + .execute({ + token: tokenAddress, + trxAmount: trxSun, + minTokenOut: opts.minOut, + slippage: `${slippage * 100}%`, + } as any) + .then(toCliTxResult), onSuccess: async (result: any) => { if (isJsonMode()) return const chalk = (await import('chalk')).default @@ -897,7 +888,7 @@ export function registerSunpumpCommands(program: Command) { try { const quote = await withSpinner('Fetching sell quote...', async () => { const kit = await getKit() - return kit.sunpumpQuoteSell(tokenAddress, tokenRaw, network) + return kit.pump.sell.quote({ token: tokenAddress, tokenAmount: tokenRaw }) }) summary['TRX Out (expected)'] = `${baseUnitsToDecimal((quote as any).trxAmount, 6)} TRX` summary['Fee'] = `${baseUnitsToDecimal((quote as any).fee, 6)} TRX` @@ -913,13 +904,14 @@ export function registerSunpumpCommands(program: Command) { spinnerLabel: 'Submitting sell...', errorLabel: 'Sell failed', execute: (kit) => - kit.sunpumpSell({ - tokenAddress, - tokenAmount: tokenRaw, - minTrxOut: opts.minOut, - slippage, - network, - }), + kit.pump.sell + .execute({ + token: tokenAddress, + tokenAmount: tokenRaw, + minTrxOut: opts.minOut, + slippage: `${slippage * 100}%`, + } as any) + .then(toCliTxResult), onSuccess: async (result: any) => { if (isJsonMode()) return const chalk = (await import('chalk')).default diff --git a/src/commands/swap.ts b/src/commands/swap.ts index 49a79c7..42b2357 100644 --- a/src/commands/swap.ts +++ b/src/commands/swap.ts @@ -3,6 +3,7 @@ import { getNetwork } from '../lib/context' import { writeAction, readAction } from '../lib/command' import { isJsonMode, output, outputError, withSpinner } from '../lib/output' import { resolveTokenAddress, getSymbolOrAddress } from '../lib/tokens' +import { toCliTxResult } from '../lib/sdk/compat' const ROUTER_API: Record = { mainnet: 'https://rot.endjgfsv.link', @@ -17,6 +18,19 @@ interface RouteData { impact: string } +function selectSwapRoute(quote: any): any { + return quote.bestRoute ?? quote.route ?? quote.routes?.[0] +} + +function cliSlippageToSdk(value: number): `${number}%` { + return `${value * 100}%` as `${number}%` +} + +function parseAbi(value: string | undefined): any[] { + if (!value) throw new Error('--abi is required for SDK contract calls') + return JSON.parse(value) +} + async function fetchSwapQuote( tokenIn: string, tokenOut: string, @@ -87,7 +101,17 @@ export function registerSwapCommands(program: Command) { confirmMsg: 'Execute this swap?', spinnerLabel: 'Executing swap...', errorLabel: 'Swap failed', - execute: (kit) => kit.swap({ tokenIn, tokenOut, amountIn, slippage, network }), + execute: async (sdk) => { + const quote = await sdk.swap.router.quote({ tokenIn, tokenOut, amountIn }) + const route = selectSwapRoute(quote) + if (!route) throw new Error('No route available for this token pair') + const result = await sdk.swap.router.execute({ + quote, + route, + slippage: cliSlippageToSdk(slippage), + }) + return toCliTxResult(result) + }, onSuccess: async (result: any) => { if (!isJsonMode()) { const chalk = (await import('chalk')).default @@ -179,13 +203,12 @@ export function registerSwapCommands(program: Command) { await readAction({ spinnerLabel: 'Quoting swap...', errorLabel: 'Quote failed', - execute: (kit) => - kit.quoteExactInput({ - network: getNetwork(), - routerAddress: opts.router, + execute: (sdk) => + sdk.runtime.contract.read({ + address: opts.router, functionName: opts.fn, args: JSON.parse(opts.args), - abi: opts.abi ? JSON.parse(opts.abi) : undefined, + abi: parseAbi(opts.abi), }), transform: (result) => ({ result }), }) @@ -212,14 +235,15 @@ export function registerSwapCommands(program: Command) { spinnerLabel: 'Executing swap...', errorLabel: 'SwapExactInput failed', execute: (kit) => - kit.swapExactInput({ - network: getNetwork(), - routerAddress: opts.router, - functionName: opts.fn, - args: JSON.parse(opts.args), - value: opts.value, - abi: opts.abi ? JSON.parse(opts.abi) : undefined, - }), + kit.runtime.contract + .write({ + address: opts.router, + functionName: opts.fn, + args: JSON.parse(opts.args), + callValue: opts.value === undefined ? undefined : BigInt(opts.value), + abi: parseAbi(opts.abi), + }) + .then(toCliTxResult), }) }) } diff --git a/src/commands/wallet.ts b/src/commands/wallet.ts index 5ee57a4..55c1528 100644 --- a/src/commands/wallet.ts +++ b/src/commands/wallet.ts @@ -3,6 +3,7 @@ import { getNetwork } from '../lib/context' import { readAction } from '../lib/command' import { getWalletAddress, initWallet } from '../lib/wallet' import { output, outputError } from '../lib/output' +import { readBalances } from '@sun-protocol/sun-sdk-runtime' export function registerWalletCommands(program: Command) { const wallet = program.command('wallet').description('Wallet management') @@ -27,23 +28,28 @@ export function registerWalletCommands(program: Command) { .option('--owner
', 'Wallet address (default: active wallet)') .option('--tokens ', 'Comma-separated: TRX,,...', 'TRX') .action(async (opts) => { - const tokenList = opts.tokens.split(',').map((t: string) => { - const trimmed = t.trim() - if (trimmed.toUpperCase() === 'TRX') { - return { address: opts.owner || '', type: 'TRX' as const } - } - return { address: opts.owner || '', type: 'TRC20' as const, tokenAddress: trimmed } - }) + const tokenList = opts.tokens.split(',').map((t: string) => t.trim()) await readAction({ spinnerLabel: 'Fetching balances...', errorLabel: 'Failed to get balances', - execute: (kit) => - kit.getBalances({ - network: getNetwork(), - ownerAddress: opts.owner, + execute: async (sdk) => { + const owner = opts.owner || (await sdk.runtime.getAddress()) + if (!owner) + throw new Error( + 'Wallet required. Set agent-wallet credentials before running this command.', + ) + const balances = await readBalances(sdk.runtime.tronClient, { + owner, tokens: tokenList, - }), + }) + return balances.map((item) => ({ + type: String(item.token).toUpperCase() === 'TRX' ? 'TRX' : 'TRC20', + tokenAddress: String(item.token).toUpperCase() === 'TRX' ? undefined : item.token, + balance: item.balance, + decimals: item.decimals, + })) + }, tableConfig: { headers: ['Type', 'Token Address', 'Balance'], toRow: (item: any) => [item.type, item.tokenAddress || 'TRX (native)', item.balance], diff --git a/src/lib/command.ts b/src/lib/command.ts index 15fb19e..075e96d 100644 --- a/src/lib/command.ts +++ b/src/lib/command.ts @@ -2,7 +2,6 @@ * Command helpers — eliminate boilerplate in write/read command handlers. */ -import type { SunKit, SunAPI } from '@sun-protocol/sun-kit' import { getKit, getApi, ensureWallet } from './context' import { output, @@ -13,6 +12,8 @@ import { printKeyValue, } from './output' import { confirm, printSummary } from './confirm' +import type { SunApiClient } from '@sun-protocol/sun-sdk-api' +import type { CliSunSDK } from './sdk/types' // --------------------------------------------------------------------------- // Dry-run state @@ -167,7 +168,7 @@ export function parseApiResponse( // writeAction — for state-changing commands (swap, liquidity, contract:send) // --------------------------------------------------------------------------- -export interface WriteActionOpts { +export interface WriteActionOpts { /** Title shown in the pre-execution summary */ title: string /** Key-value pairs displayed in summary */ @@ -177,7 +178,7 @@ export interface WriteActionOpts { /** Spinner label during execution */ spinnerLabel: string /** The async operation that calls kit methods */ - execute: (kit: SunKit) => Promise + execute: (kit: CliSunSDK) => Promise /** Error label prefix for outputError */ errorLabel: string /** Optional post-success callback (e.g. extra human-friendly output) */ @@ -227,14 +228,14 @@ export async function writeAction(opts: WriteActionOpts): Promise { } // --------------------------------------------------------------------------- -// readAction — for read commands that need SunKit (balances, contract:read) +// readAction — for read commands that need the protocol SDK runtime (balances, contract:read) // --------------------------------------------------------------------------- -export interface ReadActionOpts { +export interface ReadActionOpts { /** Spinner label */ spinnerLabel: string /** The async operation */ - execute: (kit: SunKit) => Promise + execute: (kit: CliSunSDK) => Promise /** Table config for human-readable output */ tableConfig?: { headers: string[]; toRow: (item: any) => string[] } /** Error label prefix */ @@ -256,14 +257,14 @@ export async function readAction(opts: ReadActionOpts): Promise { } // --------------------------------------------------------------------------- -// readApiAction — for read commands using SunAPI only (no wallet init) +// readApiAction — for read commands using the SUN API client only (no wallet init) // --------------------------------------------------------------------------- -export interface ReadApiActionOpts { +export interface ReadApiActionOpts { /** Spinner label */ spinnerLabel: string /** The async operation */ - execute: (api: SunAPI) => Promise + execute: (api: SunApiClient) => Promise /** Table config for human-readable output */ tableConfig?: { headers: string[]; toRow: (item: any) => string[] } /** Error label prefix */ diff --git a/src/lib/context.ts b/src/lib/context.ts index cf35af3..b61cc0e 100644 --- a/src/lib/context.ts +++ b/src/lib/context.ts @@ -1,34 +1,36 @@ /** - * Global application context — lazily initializes SunAPI, SunKit, and wallet. + * Global application context — lazily initializes SUN API, SunSDK, and wallet. */ -import { SunAPI, SunKit } from '@sun-protocol/sun-kit' +import type { SunApiClient } from '@sun-protocol/sun-sdk-api' import { initWallet, getWallet, isWalletConfigured } from './wallet' +import { createSunApiClient, createSunSDK } from './sdk/factory' +import type { CliSunSDK } from './sdk/types' -let _api: SunAPI | null = null -let _kit: SunKit | null = null +let _api: SunApiClient | null = null +let _sdk: CliSunSDK | null = null let _initialized = false function getNetwork(): string { return process.env.TRON_NETWORK || 'mainnet' } -export function getApi(): SunAPI { +export function getApi(): SunApiClient { if (!_api) { - _api = new SunAPI() + _api = createSunApiClient() } return _api } -export async function getKit(): Promise { +export async function getSdk(): Promise { if (!_initialized) { await initWallet() _initialized = true } - if (!_kit) { + if (!_sdk) { const wallet = isWalletConfigured() ? getWallet() : undefined - _kit = new SunKit({ + _sdk = await createSunSDK({ wallet, network: getNetwork(), tronGridApiKey: process.env.TRONGRID_API_KEY || process.env.TRON_GRID_API_KEY, @@ -36,7 +38,12 @@ export async function getKit(): Promise { }) } - return _kit + return _sdk +} + +/** @deprecated Use getSdk(). Kept for the existing sun-cli command API. */ +export async function getKit(): Promise { + return getSdk() } export async function ensureWallet(): Promise { diff --git a/src/lib/output.ts b/src/lib/output.ts index 626f24b..d2546a0 100644 --- a/src/lib/output.ts +++ b/src/lib/output.ts @@ -74,7 +74,7 @@ export function outputJson(data: unknown) { } function classifyError(message: string, error?: unknown): string { - // Prefer error object's own code (e.g. SunKitError.code) + // Prefer error object's own code from SDK/API errors. if ( error && typeof error === 'object' && diff --git a/src/lib/sdk/compat.ts b/src/lib/sdk/compat.ts new file mode 100644 index 0000000..91364a7 --- /dev/null +++ b/src/lib/sdk/compat.ts @@ -0,0 +1,28 @@ +export function toCliTxResult(result: T): unknown { + if (!result || typeof result !== 'object' || Array.isArray(result)) { + return result + } + + const record = result as Record + const finalResult = record.finalResult as Record | undefined + if (finalResult && finalResult.type === 'transaction' && typeof finalResult.txid === 'string') { + return { + txid: finalResult.txid, + raw: finalResult.raw, + } + } + + if (Array.isArray(record.txids) && typeof record.txids[record.txids.length - 1] === 'string') { + return { + txid: record.txids[record.txids.length - 1], + raw: result, + } + } + + if (typeof record.txid === 'string') return result + if (typeof record.txID !== 'string') return result + + const { txID, ...rest } = record + void txID + return { txid: record.txID, ...rest } as T & { txid: string } +} diff --git a/src/lib/sdk/constants.ts b/src/lib/sdk/constants.ts new file mode 100644 index 0000000..9df90d5 --- /dev/null +++ b/src/lib/sdk/constants.ts @@ -0,0 +1,6 @@ +import { getWrappedNativeAddress } from '@sun-protocol/sun-sdk-chains' +import { TRON_ZERO_ADDRESS } from '@sun-protocol/sun-sdk-core' + +export const TRX_ADDRESS = TRON_ZERO_ADDRESS +export const WTRX_MAINNET = getWrappedNativeAddress('mainnet') +export const WTRX_NILE = getWrappedNativeAddress('nile') diff --git a/src/lib/sdk/errors.ts b/src/lib/sdk/errors.ts new file mode 100644 index 0000000..d956d49 --- /dev/null +++ b/src/lib/sdk/errors.ts @@ -0,0 +1,21 @@ +import type { CliError } from './types' + +function hasStringCode(error: unknown): error is Error & { code: string } { + return error instanceof Error && typeof (error as any).code === 'string' +} + +export function mapSdkError(error: unknown, fallbackCode = 'UNKNOWN'): CliError { + if (error instanceof Error && /wallet required|agent-wallet|no wallet/i.test(error.message)) { + return Object.assign(error, { code: 'NO_WALLET' }) + } + + if (hasStringCode(error)) { + return error + } + + if (error instanceof Error) { + return Object.assign(error, { code: fallbackCode }) + } + + return Object.assign(new Error(String(error)), { code: fallbackCode, detail: error }) +} diff --git a/src/lib/sdk/factory.ts b/src/lib/sdk/factory.ts new file mode 100644 index 0000000..dce0e24 --- /dev/null +++ b/src/lib/sdk/factory.ts @@ -0,0 +1,81 @@ +import { SunApiClient } from '@sun-protocol/sun-sdk-api' +import type { Network } from '@sun-protocol/sun-sdk-core' +import { SunSDK } from '@sun-protocol/sun-sdk-protocols' +import { + createRuntime, + type Runtime, + type TronClient, + type WalletAdapter, +} from '@sun-protocol/sun-sdk-runtime' +import { TronWeb } from 'tronweb' +import { TRX_ADDRESS } from './constants' +import type { CliSunSDK } from './types' + +export interface SdkRuntimeOptions { + network?: string + wallet?: WalletAdapter + tronGridApiKey?: string + rpcUrl?: string +} + +export function getNetworkFromEnv(env: NodeJS.ProcessEnv = process.env): Network { + const network = env.TRON_NETWORK || 'mainnet' + if (network === 'mainnet' || network === 'nile') return network + throw new Error(`Unsupported SDK network: ${network}`) +} + +function getDefaultRpcUrl(network: Network): string { + return network === 'nile' ? 'https://nile.trongrid.io' : 'https://api.trongrid.io' +} + +export function createReadonlyTronWeb(options: SdkRuntimeOptions = {}): TronWeb { + const network = options.network ?? 'mainnet' + const fullHost = options.rpcUrl || getDefaultRpcUrl(assertSdkNetwork(network)) + const headers = options.tronGridApiKey + ? { 'TRON-PRO-API-KEY': options.tronGridApiKey } + : undefined + const tronWeb = new TronWeb({ fullHost, headers }) + // TronGrid requires owner_address even for constant calls. Set the zero address + // as a read-only caller without introducing a signing key. + tronWeb.setAddress(TRX_ADDRESS) + return tronWeb +} + +function assertSdkNetwork(network: string): Network { + if (network === 'mainnet' || network === 'nile') return network + throw new Error(`Unsupported SDK network: ${network}`) +} + +export async function createSdkRuntime(options: SdkRuntimeOptions = {}): Promise { + const network = assertSdkNetwork(options.network ?? 'mainnet') + const tronWeb = createReadonlyTronWeb(options) + const { createTronWebClient } = require('@sun-protocol/sun-sdk-runtime/node') as { + createTronWebClient(input: { network: Network; tronWeb: TronWeb }): TronClient + } + const tronClient = createTronWebClient({ + network, + tronWeb, + }) + return createRuntime({ + network, + tronClient, + wallet: options.wallet, + }) +} + +export function createSunApiClient(): SunApiClient { + return new SunApiClient() +} + +export async function createSunSDK(options: SdkRuntimeOptions = {}): Promise { + const network = assertSdkNetwork(options.network ?? 'mainnet') + const runtime = await createSdkRuntime(options) + const sdk = await SunSDK.create({ + network, + runtime, + }) + if (!sdk.runtime) { + throw new Error('SUN SDK runtime initialization failed') + } + return sdk as CliSunSDK +} diff --git a/src/lib/sdk/index.ts b/src/lib/sdk/index.ts new file mode 100644 index 0000000..6a71eec --- /dev/null +++ b/src/lib/sdk/index.ts @@ -0,0 +1,6 @@ +export * from './types' +export * from './errors' +export * from './compat' +export * from './constants' +export * from './wallet' +export * from './factory' diff --git a/src/lib/sdk/types.ts b/src/lib/sdk/types.ts new file mode 100644 index 0000000..600cbd3 --- /dev/null +++ b/src/lib/sdk/types.ts @@ -0,0 +1,16 @@ +import type { SunSDK } from '@sun-protocol/sun-sdk-protocols' +import type { Runtime } from '@sun-protocol/sun-sdk-runtime' + +export type CliNetwork = 'mainnet' | 'nile' | 'shasta' + +export type CliError = Error & { + code?: string + detail?: unknown +} + +/** + * The CLI always creates SunSDK with a runtime. Keep that invariant visible to + * command implementations even though the public SDK also supports read-only + * construction without one. + */ +export type CliSunSDK = SunSDK & { readonly runtime: Runtime } diff --git a/src/lib/sdk/wallet.ts b/src/lib/sdk/wallet.ts new file mode 100644 index 0000000..988a160 --- /dev/null +++ b/src/lib/sdk/wallet.ts @@ -0,0 +1,37 @@ +import type { + SignedTronTransaction, + TypedDataPayload, + UnsignedTronTransaction, + WalletAdapter, +} from '@sun-protocol/sun-sdk-runtime' +import type { Wallet as AgentWallet, Eip712Capable } from '@bankofai/agent-wallet' + +export class AgentWalletSdkAdapter implements WalletAdapter { + readonly kind = 'custom' as const + readonly capabilities = { + signTransaction: true, + signAndSendTransaction: false, + signTypedData: true, + userInteractive: false, + } + + constructor(private readonly agentWallet: AgentWallet) {} + + async getAddress(): Promise { + return this.agentWallet.getAddress() + } + + async signTransaction(tx: UnsignedTronTransaction): Promise { + const signedJson = await this.agentWallet.signTransaction(tx as Record) + return JSON.parse(signedJson) + } + + async signMessage(message: string): Promise { + return this.agentWallet.signMessage(Buffer.from(message, 'utf-8')) + } + + async signTypedData(payload: TypedDataPayload): Promise { + const sig = await (this.agentWallet as unknown as Eip712Capable).signTypedData(payload as any) + return sig.startsWith('0x') ? sig.slice(2) : sig + } +} diff --git a/src/lib/sunpump.ts b/src/lib/sunpump.ts deleted file mode 100644 index 7044c58..0000000 --- a/src/lib/sunpump.ts +++ /dev/null @@ -1,280 +0,0 @@ -/** - * SunPump API client — read-only GET endpoints. - * - * Mainnet only: https://api-v2.sunpump.meme/pump-api - * - * Standalone from sun-kit's SunAPI: SunPump is a separate service with its own - * base URL and schema. Uses Node's global fetch (>=18). - * - * Methods mirror the OpenAPI operationIds documented in docs/sunpump-api.md. - */ - -export const SUNPUMP_DEFAULT_BASE_URL = 'https://api-v2.sunpump.meme/pump-api' - -export interface SunPumpClientOptions { - baseUrl?: string - fetchImpl?: typeof fetch -} - -type QueryValue = string | number | boolean | null | undefined -export type Query = Record - -export interface AgentTokenLaunchParams { - name: string - symbol: string - description: string - /** Logo image content as a base64 string (no data-URI prefix). */ - imageBase64?: string - twitterUrl?: string - telegramUrl?: string - websiteUrl?: string - tweetUsername?: string -} - -export class SunPumpHttpError extends Error { - readonly code = 'SUNPUMP_HTTP_ERROR' - constructor( - message: string, - readonly status: number, - readonly body?: string, - ) { - super(message) - this.name = 'SunPumpHttpError' - } -} - -function buildQueryString(query?: Query): string { - if (!query) return '' - const usp = new URLSearchParams() - for (const [key, value] of Object.entries(query)) { - if (value === undefined || value === null || value === '') continue - usp.append(key, String(value)) - } - const s = usp.toString() - return s ? `?${s}` : '' -} - -export class SunPump { - private readonly baseUrl: string - private readonly fetchImpl: typeof fetch - - constructor(opts: SunPumpClientOptions = {}) { - const base = opts.baseUrl ?? process.env.SUNPUMP_API_BASE_URL ?? SUNPUMP_DEFAULT_BASE_URL - this.baseUrl = base.replace(/\/+$/, '') - this.fetchImpl = opts.fetchImpl ?? fetch - } - - async request(path: string, query?: Query): Promise { - return this.send(path, buildQueryString(query), { - method: 'GET', - headers: { Accept: 'application/json' }, - }) - } - - async post(path: string, body: unknown): Promise { - return this.send(path, '', { - method: 'POST', - headers: { Accept: 'application/json', 'Content-Type': 'application/json' }, - body: JSON.stringify(body), - }) - } - - private async send(path: string, queryString: string, init: RequestInit): Promise { - const url = `${this.baseUrl}${path}${queryString}` - const res = await this.fetchImpl(url, init) - const text = await res.text() - if (!res.ok) { - const excerpt = text.length > 500 ? text.slice(0, 500) + '…' : text - let apiMsg: string | undefined - try { - const parsed = JSON.parse(text) - if (parsed && typeof parsed === 'object' && typeof parsed.msg === 'string') { - apiMsg = parsed.msg - } - } catch { - // body not JSON; fall through - } - const suffix = apiMsg ? ` — ${apiMsg}` : '' - throw new SunPumpHttpError( - `SunPump request failed: ${res.status} ${res.statusText} (${path})${suffix}`, - res.status, - excerpt, - ) - } - if (!text) return undefined as unknown as T - try { - return JSON.parse(text) as T - } catch { - throw new SunPumpHttpError(`SunPump returned non-JSON body (${path})`, res.status, text) - } - } - - // --------------------------------------------------------------------------- - // Token Info - // --------------------------------------------------------------------------- - - listTokens( - query: { - contractAddress?: string - ownerAddress?: string - symbol?: string - name?: string - description?: string - page?: number - size?: number - sort?: string - } = {}, - ) { - return this.request('/token', query) - } - - getToken(contractAddress: string) { - return this.request(`/token/${encodeURIComponent(contractAddress)}`) - } - - searchTokens(query: { - query?: string - onSunSwap?: boolean - page?: number - size?: number - sort?: string - }) { - return this.request('/token/search', query) - } - - searchTokensV2(query: { - query?: string - onSunSwap?: boolean - filterDliveShowing?: boolean - filterAiHelper?: boolean - filterTwitterLaunch?: boolean - filterSunAgentLaunch?: boolean - page?: number - size?: number - sort?: string - }) { - return this.request('/token/searchV2', query) - } - - tokensByOwner(query: { address: string; page?: number; size?: number; sort?: string }) { - return this.request('/token/search/by_owner', query) - } - - tokenHolders(query: { - address: string - includeZeroBalance?: boolean - page?: number - size?: number - sort?: string - }) { - return this.request('/token/holders', query) - } - - tokenHoldersV2(query: { - address: string - includeZeroBalance?: boolean - page?: number - size?: number - sort?: string - }) { - return this.request('/token/holdersV2', query) - } - - userFavors(query: { - signature: string - signedMessage: string - userAddress: string - tokenAddress?: string - page?: number - size?: number - }) { - return this.request('/token/getUserFavors', query) - } - - ranking(query: { rankingType: string; size?: number }) { - return this.request('/token/getRanking', query) - } - - kingOfHill() { - return this.request('/token/getKingOfHill') - } - - pumpTokenList() { - return this.request('/token/SunPumpTokenList.json') - } - - // --------------------------------------------------------------------------- - // Transactions - // --------------------------------------------------------------------------- - - tokenTransactions( - contractAddress: string, - query: { - swapTranType?: string - swapPoolAddress?: string - txHash?: string - blockNum?: number - startTime?: number - endTime?: number - page?: number - size?: number - sort?: string - } = {}, - ) { - return this.request(`/transactions/token/${encodeURIComponent(contractAddress)}`, query) - } - - userTransactions( - ownerAddress: string, - query: { - swapTranType?: string - swapPoolAddress?: string - txHash?: string - blockNum?: number - startTime?: number - endTime?: number - page?: number - size?: number - sort?: string - } = {}, - ) { - return this.request(`/transactions/holder/${encodeURIComponent(ownerAddress)}`, query) - } - - // --------------------------------------------------------------------------- - // AI agent — token launch - // --------------------------------------------------------------------------- - - /** - * Launch a new token through the SunPump agent endpoint. The server creates - * the token on-chain itself — no local wallet or signing involved. Returns - * the full token object (contractAddress, createTxHash, …) in the envelope. - */ - agentTokenLaunch(params: AgentTokenLaunchParams) { - return this.post('/ai/agentTokenLaunch', params) - } - - // --------------------------------------------------------------------------- - // Holder portfolio - // --------------------------------------------------------------------------- - - portfolio( - address: string, - query: { - includeZeroBalance?: boolean - trxAmountMin?: number - page?: number - size?: number - sort?: string - } = {}, - ) { - return this.request(`/holders/${encodeURIComponent(address)}/tokens`, query) - } -} - -let _client: SunPump | null = null - -export function getSunPump(): SunPump { - if (!_client) _client = new SunPump() - return _client -} diff --git a/src/lib/tokens.ts b/src/lib/tokens.ts index 96e5549..9714220 100644 --- a/src/lib/tokens.ts +++ b/src/lib/tokens.ts @@ -3,7 +3,7 @@ * Provides a unified way to resolve token symbols to addresses across all commands. */ -import { TRX_ADDRESS, WTRX_MAINNET, WTRX_NILE } from '@sun-protocol/sun-kit' +import { TRX_ADDRESS, WTRX_MAINNET, WTRX_NILE } from './sdk/constants' export interface TokenInfo { symbol: string diff --git a/src/lib/wallet.ts b/src/lib/wallet.ts index e262ded..5add179 100644 --- a/src/lib/wallet.ts +++ b/src/lib/wallet.ts @@ -1,13 +1,8 @@ -import type { Wallet } from '@sun-protocol/sun-kit' -import { createReadonlyTronWeb } from '@sun-protocol/sun-kit' -import { - resolveWalletProvider, - type Wallet as BaseWallet, - type Eip712Capable, -} from '@bankofai/agent-wallet' -import { TronWeb } from 'tronweb' +import { resolveWalletProvider } from '@bankofai/agent-wallet' +import type { WalletAdapter } from '@sun-protocol/sun-sdk-runtime' +import { AgentWalletSdkAdapter } from './sdk/wallet' -export type { Wallet } +export type Wallet = WalletAdapter let _wallet: Wallet | null = null @@ -20,7 +15,7 @@ export async function initWallet(): Promise { _wallet = null return } - _wallet = new AgentWalletAdapter(activeWallet) + _wallet = new AgentWalletSdkAdapter(activeWallet) } catch { _wallet = null } @@ -42,87 +37,3 @@ export function isWalletConfigured(): boolean { export async function getWalletAddress(): Promise { return getWallet().getAddress() } - -class AgentWalletAdapter implements Wallet { - readonly type = 'agent-wallet' as const - - constructor(private readonly agentWallet: BaseWallet) {} - - async getAddress(): Promise { - return await this.agentWallet.getAddress() - } - - async getTronWeb(network = 'mainnet'): Promise { - const tronWeb = await createReadonlyTronWeb(network) - const ownerAddress = await this.agentWallet.getAddress() - const ownerHex = - typeof (tronWeb as any).address?.toHex === 'function' - ? (tronWeb as any).address.toHex(ownerAddress) - : ownerAddress - const ownerBase58 = - typeof (tronWeb as any).address?.fromHex === 'function' - ? (tronWeb as any).address.fromHex(ownerHex) - : ownerAddress - - const tronWebWithDefaultAddress = tronWeb as any - tronWebWithDefaultAddress.defaultAddress = { hex: ownerHex, base58: ownerBase58 } - return tronWeb - } - - async signAndBroadcast( - unsignedTx: Record, - network = 'mainnet', - ): Promise<{ result: boolean; txid: string }> { - const tx = (unsignedTx as any).transaction || unsignedTx - const txid = await this.buildSignBroadcast(tx, network) - return { result: true, txid } - } - - async buildSignBroadcast( - unsignedTx: Record, - network = 'mainnet', - ): Promise { - const signedTx = await this.signTransaction(unsignedTx) - const tronWeb = await createReadonlyTronWeb(network) - const result = await tronWeb.trx.sendRawTransaction(signedTx as any) - - if (result.result) { - return result.txid - } - - throw new Error(`Broadcast failed: ${JSON.stringify(result)}`) - } - - async signTransaction(unsignedTx: Record): Promise { - const signedJson = await this.agentWallet.signTransaction(unsignedTx) - return JSON.parse(signedJson) - } - - async signMessage(message: string): Promise { - return await this.agentWallet.signMessage(Buffer.from(message, 'utf-8')) - } - - async signTypedData( - primaryType: string, - domain: Record, - types: Record, - message: Record, - ): Promise { - const signer = this.agentWallet as unknown as Eip712Capable - const sig = await signer.signTypedData({ - domain, - types: { - EIP712Domain: [ - { name: 'name', type: 'string' }, - { name: 'chainId', type: 'uint256' }, - { name: 'verifyingContract', type: 'address' }, - ], - ...types, - }, - primaryType, - message, - }) - - return sig.startsWith('0x') ? sig.slice(2) : sig - } -} diff --git a/test/commands/liquidity-sdk.test.ts b/test/commands/liquidity-sdk.test.ts new file mode 100644 index 0000000..48fec3b --- /dev/null +++ b/test/commands/liquidity-sdk.test.ts @@ -0,0 +1,235 @@ +import { Command } from 'commander' + +describe('liquidity SDK integration', () => { + beforeEach(() => { + jest.resetModules() + jest.clearAllMocks() + }) + + async function captureWriteAction(args: string[]) { + jest.resetModules() + const writeAction = jest.fn().mockResolvedValue(undefined) + jest.doMock('../../src/lib/command', () => ({ + writeAction, + readAction: jest.fn().mockResolvedValue(undefined), + })) + jest.doMock('../../src/lib/context', () => ({ getNetwork: () => 'nile' })) + + const { registerLiquidityCommands } = require('../../src/commands/liquidity') + const program = new Command() + program.exitOverride() + registerLiquidityCommands(program) + await program.parseAsync(['node', 'sun', 'liquidity', ...args], { from: 'node' }) + + return writeAction.mock.calls[0][0] + } + + it('passes V2 remove minimum amounts to the SDK', async () => { + const action = await captureWriteAction([ + 'v2:remove', + '--token-a', + 'SUN', + '--token-b', + 'USDT', + '--liquidity', + '100', + '--min-a', + '11', + '--min-b', + '22', + ]) + const execute = jest.fn().mockResolvedValue({ txids: ['v2-remove-tx'] }) + + await action.execute({ liquidity: { v2: { remove: { execute } } } }) + expect(execute).toHaveBeenCalledWith( + expect.objectContaining({ + liquidity: '100', + amountAMin: '11', + amountBMin: '22', + }), + ) + }) + + it('passes V3 increase minimum amounts to the SDK', async () => { + const action = await captureWriteAction([ + 'v3:increase', + '--token-id', + '7', + '--amount0', + '100', + '--min0', + '11', + '--min1', + '22', + ]) + const execute = jest.fn().mockResolvedValue({ txids: ['v3-increase-tx'] }) + + await action.execute({ liquidity: { v3: { increase: { execute } } } }) + expect(execute).toHaveBeenCalledWith( + expect.objectContaining({ + tokenId: '7', + amount0: '100', + amount0Min: '11', + amount1Min: '22', + }), + ) + }) + + it('preserves automatic V3 mint ticks through the SDK tick range width', async () => { + const action = await captureWriteAction([ + 'v3:mint', + '--token0', + 'TRX', + '--token1', + 'USDT', + '--amount0', + '100', + '--amount1', + '200', + '--min0', + '11', + '--min1', + '22', + ]) + const execute = jest.fn().mockResolvedValue({ txids: ['v3-mint-tx'] }) + + await action.execute({ liquidity: { v3: { add: { execute } } } }) + expect(execute).toHaveBeenCalledWith( + expect.objectContaining({ + tickLower: undefined, + tickUpper: undefined, + tickRangeWidth: 100, + amount0: '200', + amount1: '100', + amount0Min: '22', + amount1Min: '11', + }), + ) + }) + + it('passes V3 decrease minimum amounts to the SDK', async () => { + const action = await captureWriteAction([ + 'v3:decrease', + '--token-id', + '7', + '--liquidity', + '100', + '--min0', + '11', + '--min1', + '22', + ]) + const execute = jest.fn().mockResolvedValue({ txids: ['v3-decrease-tx'] }) + + await action.execute({ positions: { v3: { remove: { execute } } } }) + expect(execute).toHaveBeenCalledWith( + expect.objectContaining({ + tokenId: '7', + liquidity: '100', + amount0Min: '11', + amount1Min: '22', + }), + ) + }) + + it('maps V4 mint defaults and recipient to the public SDK input', async () => { + const action = await captureWriteAction([ + 'v4:mint', + '--token0', + 'SUN', + '--token1', + 'USDT', + '--amount0', + '100', + '--recipient', + 'TOwner', + ]) + const execute = jest.fn().mockResolvedValue({ + finalResult: { type: 'transaction', txid: 'mint-tx', raw: { result: true } }, + }) + + await expect( + action.execute({ liquidity: { v4: { add: { execute } } } }), + ).resolves.toMatchObject({ txid: 'mint-tx' }) + expect(execute).toHaveBeenCalledWith( + expect.objectContaining({ + fee: 3000, + tickSpacing: 60, + tickLower: -120, + tickUpper: 120, + amount0: '100', + owner: 'TOwner', + }), + ) + expect(execute.mock.calls[0][0]).not.toHaveProperty('recipient') + }) + + it('lets the SDK derive V4 pool context from tokenId for increase', async () => { + const action = await captureWriteAction([ + 'v4:increase', + '--token-id', + '7', + '--token0', + 'SUN', + '--token1', + 'USDT', + '--amount1', + '200', + '--slippage', + '0.01', + ]) + const execute = jest.fn().mockResolvedValue({ txids: ['increase-tx'] }) + + await action.execute({ liquidity: { v4: { increase: { execute } } } }) + expect(execute).toHaveBeenCalledWith({ + tokenId: '7', + amount0: undefined, + amount1: '200', + slippage: '1%', + deadline: undefined, + }) + }) + + it('lets the SDK derive a valid V4 pool key for remove and collect', async () => { + const removeAction = await captureWriteAction([ + 'v4:decrease', + '--token-id', + '7', + '--liquidity', + '10', + '--token0', + 'SUN', + '--token1', + 'USDT', + '--fee', + '500', + '--min0', + '1', + ]) + const remove = jest.fn().mockResolvedValue({ txids: ['remove-tx'] }) + await removeAction.execute({ positions: { v4: { remove: { execute: remove } } } }) + expect(remove).toHaveBeenCalledWith({ + tokenId: '7', + liquidity: '10', + amount0Min: '1', + amount1Min: undefined, + slippage: undefined, + deadline: undefined, + }) + + const collectAction = await captureWriteAction([ + 'v4:collect', + '--token-id', + '7', + '--token0', + 'SUN', + '--token1', + 'USDT', + '--fee', + '500', + ]) + const collect = jest.fn().mockResolvedValue({ txids: ['collect-tx'] }) + await collectAction.execute({ positions: { v4: { collect: { execute: collect } } } }) + expect(collect).toHaveBeenCalledWith({ tokenId: '7', deadline: undefined }) + }) +}) diff --git a/test/commands/sdk-write-migration.test.ts b/test/commands/sdk-write-migration.test.ts new file mode 100644 index 0000000..2fda5f0 --- /dev/null +++ b/test/commands/sdk-write-migration.test.ts @@ -0,0 +1,127 @@ +import { Command } from 'commander' + +describe('SDK command migrations', () => { + beforeEach(() => { + jest.resetModules() + jest.clearAllMocks() + }) + + function makeProgram() { + const program = new Command() + program.exitOverride() + return program + } + + it('wallet balances calls SDK readBalances with string tokens', async () => { + const readAction = jest.fn().mockResolvedValue(undefined) + jest.doMock('../../src/lib/command', () => ({ readAction })) + jest.doMock('../../src/lib/context', () => ({ getNetwork: () => 'nile' })) + const readBalances = jest + .fn() + .mockResolvedValue([{ token: 'TRX', balance: '100', decimals: 6 }]) + jest.doMock('@sun-protocol/sun-sdk-runtime', () => ({ readBalances })) + + const { registerWalletCommands } = require('../../src/commands/wallet') + const program = makeProgram() + registerWalletCommands(program) + + await program.parseAsync( + ['node', 'sun', 'wallet', 'balances', '--owner', 'TOwner', '--tokens', 'TRX,TToken'], + { from: 'node' }, + ) + + const opts = readAction.mock.calls[0][0] + const tronClient = { kind: 'tron-client' } + const runtime = { tronClient } + await expect(opts.execute({ runtime })).resolves.toEqual([ + { type: 'TRX', tokenAddress: undefined, balance: '100', decimals: 6 }, + ]) + expect(readBalances).toHaveBeenCalledWith(tronClient, { + owner: 'TOwner', + tokens: ['TRX', 'TToken'], + }) + }) + + it('contract send calls SDK runtime contract client and maps tx result', async () => { + const writeAction = jest.fn().mockResolvedValue(undefined) + jest.doMock('../../src/lib/command', () => ({ writeAction })) + jest.doMock('../../src/lib/context', () => ({ getNetwork: () => 'nile' })) + const write = jest.fn().mockResolvedValue({ txid: 'tx1', raw: { result: true } }) + + const { registerContractCommands } = require('../../src/commands/contract') + const program = makeProgram() + registerContractCommands(program) + + await program.parseAsync( + [ + 'node', + 'sun', + 'contract', + 'send', + 'TContract', + 'transfer', + '--args', + '["TA","1"]', + '--abi', + '[{"type":"function","name":"transfer","inputs":[]}]', + '--value', + '10', + ], + { from: 'node' }, + ) + + const opts = writeAction.mock.calls[0][0] + const sdk = { runtime: { contract: { write } } } + await expect(opts.execute(sdk)).resolves.toEqual({ txid: 'tx1', raw: { result: true } }) + expect(write).toHaveBeenCalledWith({ + address: 'TContract', + functionName: 'transfer', + args: ['TA', '1'], + abi: [{ type: 'function', name: 'transfer', inputs: [] }], + callValue: 10n, + }) + }) + + it('swap execute quotes, executes, and maps SDK plan result', async () => { + const writeAction = jest.fn().mockResolvedValue(undefined) + jest.doMock('../../src/lib/command', () => ({ writeAction })) + jest.doMock('../../src/lib/context', () => ({ getNetwork: () => 'nile' })) + + const { registerSwapCommands } = require('../../src/commands/swap') + const program = makeProgram() + registerSwapCommands(program) + + await program.parseAsync(['node', 'sun', 'swap', 'SUN', 'USDT', '1', '--slippage', '0.005'], { + from: 'node', + }) + + const opts = writeAction.mock.calls[0][0] + const quote = { + kind: 'router', + bestRoute: { amountIn: '1', amountOut: '2', symbols: ['SUN', 'USDT'] }, + } + const sdk = { + swap: { + router: { + quote: jest.fn().mockResolvedValue(quote), + execute: jest.fn().mockResolvedValue({ + txids: ['swap-tx'], + finalResult: { type: 'transaction', txid: 'swap-tx', raw: { result: true } }, + }), + }, + }, + } + + await expect(opts.execute(sdk)).resolves.toEqual({ txid: 'swap-tx', raw: { result: true } }) + expect(sdk.swap.router.quote).toHaveBeenCalledWith({ + tokenIn: 'TWrZRHY9aKQZcyjpovdH6qeCEyYZrRQDZt', + tokenOut: 'TXYZopYRdj2D9XRtbG411XZZ3kM5VkAeBf', + amountIn: '1', + }) + expect(sdk.swap.router.execute).toHaveBeenCalledWith({ + quote, + route: quote.bestRoute, + slippage: '0.5%', + }) + }) +}) diff --git a/test/commands/sunpump-sdk.test.ts b/test/commands/sunpump-sdk.test.ts new file mode 100644 index 0000000..b7218d1 --- /dev/null +++ b/test/commands/sunpump-sdk.test.ts @@ -0,0 +1,60 @@ +import { Command } from 'commander' + +describe('sunpump SDK integration', () => { + beforeEach(() => { + jest.resetModules() + jest.clearAllMocks() + }) + + it('allows Nile search-v2 and initializes the SDK API client for nile', async () => { + const searchTokensV2 = jest.fn().mockResolvedValue({ code: 0, data: { list: [] } }) + const SunPumpApiClient = jest.fn().mockImplementation(() => ({ + searchTokensV2, + })) + const output = jest.fn() + const printPaginationFooter = jest.fn() + + jest.doMock('@sun-protocol/sun-sdk-api', () => ({ SunPumpApiClient })) + jest.doMock('../../src/lib/context', () => ({ + getNetwork: () => 'nile', + getKit: jest.fn(), + })) + jest.doMock('../../src/lib/output', () => ({ + output, + outputError: jest.fn(), + withSpinner: jest.fn().mockImplementation(async (_label, fn) => fn()), + printPaginationFooter, + printKeyValue: jest.fn(), + isJsonMode: jest.fn().mockReturnValue(true), + info: jest.fn(), + formatUsd: (v: unknown) => String(v ?? '-'), + formatTime: (v: unknown) => String(v ?? '-'), + formatAmount: (v: unknown) => String(v ?? '-'), + formatPct: (v: unknown) => String(v ?? '-'), + })) + + const { registerSunpumpCommands } = require('../../src/commands/sunpump') + const program = new Command() + program.exitOverride() + registerSunpumpCommands(program) + + await program.parseAsync( + ['node', 'sun', 'sunpump', 'token', 'search-v2', 'SUN', '--page', '1', '--size', '36'], + { from: 'node' }, + ) + + expect(SunPumpApiClient).toHaveBeenCalledWith({ network: 'nile' }) + expect(searchTokensV2).toHaveBeenCalledWith({ + query: 'SUN', + onSunSwap: undefined, + filterDliveShowing: undefined, + filterAiHelper: undefined, + filterTwitterLaunch: undefined, + filterSunAgentLaunch: undefined, + page: 1, + size: 36, + sort: undefined, + }) + expect(output).toHaveBeenCalledWith({ list: [] }, expect.any(Object)) + }) +}) diff --git a/test/contract/command-manifest.test.ts b/test/contract/command-manifest.test.ts new file mode 100644 index 0000000..123d572 --- /dev/null +++ b/test/contract/command-manifest.test.ts @@ -0,0 +1,22 @@ +import { commandHelpFixtures, rootHelpFixture } from './fixtures' +import { runCli } from '../helpers/run-cli' + +describe('CLI command manifest contract', () => { + it('keeps the root command groups and global options visible', async () => { + const result = await runCli(rootHelpFixture.args) + + expect(result.code).toBe(0) + for (const text of rootHelpFixture.includes) { + expect(result.stdout).toContain(text) + } + }) + + it.each(commandHelpFixtures)('keeps $name help compatible', async (fixture) => { + const result = await runCli(fixture.args) + + expect(result.code).toBe(0) + for (const text of fixture.includes) { + expect(result.stdout).toContain(text) + } + }) +}) diff --git a/test/contract/fixtures.ts b/test/contract/fixtures.ts new file mode 100644 index 0000000..d49790c --- /dev/null +++ b/test/contract/fixtures.ts @@ -0,0 +1,103 @@ +interface HelpFixture { + name: string + args: readonly string[] + includes: readonly string[] +} + +interface GoldenOutputFixture { + name: string + args: readonly string[] + expectCode: number + expectJson?: Record + stdoutIncludes?: readonly string[] + stderrIncludes?: readonly string[] +} + +export const rootHelpFixture: HelpFixture = { + name: 'root', + args: ['--help'], + includes: [ + 'Usage: sun [options] [command]', + '--network ', + '--dry-run', + 'wallet', + 'token', + 'pool', + 'protocol', + 'liquidity', + 'sunpump', + ], +} + +export const commandHelpFixtures: readonly HelpFixture[] = [ + { + name: 'token list', + args: ['token', 'list', '--help'], + includes: [ + 'Usage: sun token list [options]', + '--address ', + '--protocol ', + ], + }, + { + name: 'pool list', + args: ['pool', 'list', '--help'], + includes: ['Usage: sun pool list [options]', '--token ', '--no-blacklist'], + }, + { + name: 'protocol info', + args: ['protocol', 'info', '--help'], + includes: ['Usage: sun protocol info [options]', '--protocol '], + }, + { + name: 'wallet address', + args: ['wallet', 'address', '--help'], + includes: ['Usage: sun wallet address [options]', 'Show the active TRON wallet address'], + }, + { + name: 'contract read', + args: ['contract', 'read', '--help'], + includes: ['Usage: sun contract read [options]', '--abi ', '--args '], + }, + { + name: 'swap quote', + args: ['swap:quote', '--help'], + includes: ['Usage: sun swap:quote [options]', '--all'], + }, + { + name: 'liquidity', + args: ['liquidity', '--help'], + includes: ['v2:add', 'v3:mint', 'v4:mint', 'v4:info'], + }, + { + name: 'sunpump search v2', + args: ['sunpump', 'token', 'search-v2', '--help'], + includes: ['Usage: sun sunpump token search-v2 [options]', '--page ', '--size '], + }, +] + +export const goldenOutputFixtures: readonly GoldenOutputFixture[] = [ + { + name: 'dry-run swap output', + args: ['--json', '--network', 'nile', '--dry-run', 'swap', 'SUN', 'USDT', '1'], + expectCode: 0, + expectJson: { + dryRun: true, + action: 'Swap Preview', + params: { + 'Amount In': '1', + Network: 'nile', + }, + }, + }, + { + name: 'missing wallet error output', + args: ['--json', '--network', 'nile', 'wallet', 'address'], + expectCode: 1, + expectJson: { + error: 'Failed to get wallet address', + code: 'WALLET_NOT_CONFIGURED', + }, + stdoutIncludes: ['No wallet configured'], + }, +] diff --git a/test/contract/golden-output.test.ts b/test/contract/golden-output.test.ts new file mode 100644 index 0000000..d481192 --- /dev/null +++ b/test/contract/golden-output.test.ts @@ -0,0 +1,20 @@ +import { goldenOutputFixtures } from './fixtures' +import { runCli } from '../helpers/run-cli' + +describe('CLI golden output contract', () => { + it.each(goldenOutputFixtures)('$name', async (fixture) => { + const result = await runCli(fixture.args) + + expect(result.code).toBe(fixture.expectCode) + if (fixture.expectJson) { + expect(result.stdout).toContain('\n') + expect(JSON.parse(result.stdout)).toMatchObject(fixture.expectJson) + } + for (const text of fixture.stdoutIncludes ?? []) { + expect(result.stdout).toContain(text) + } + for (const text of fixture.stderrIncludes ?? []) { + expect(result.stderr).toContain(text) + } + }) +}) diff --git a/test/contract/sdk-dependency-boundary.test.ts b/test/contract/sdk-dependency-boundary.test.ts new file mode 100644 index 0000000..9de2802 --- /dev/null +++ b/test/contract/sdk-dependency-boundary.test.ts @@ -0,0 +1,40 @@ +import fs from 'node:fs' +import path from 'node:path' + +const root = path.resolve(__dirname, '../..') + +function sourceFiles(dir: string): string[] { + return fs.readdirSync(dir, { withFileTypes: true }).flatMap((entry) => { + const entryPath = path.join(dir, entry.name) + return entry.isDirectory() ? sourceFiles(entryPath) : [entryPath] + }) +} + +describe('public SUN SDK dependency boundary', () => { + it('uses only public @sun-protocol SDK packages in production code', () => { + const packageJson = fs.readFileSync(path.join(root, 'package.json'), 'utf8') + const source = sourceFiles(path.join(root, 'src')) + .map((file) => fs.readFileSync(file, 'utf8')) + .join('\n') + + expect(packageJson).not.toContain('@sun-protocol/sun-kit') + expect(packageJson).not.toContain('@sun-sdk/') + expect(source).not.toContain('@sun-sdk/') + }) + + it('pins the public SDK packages to the tested release', () => { + const packageJson = JSON.parse(fs.readFileSync(path.join(root, 'package.json'), 'utf8')) + const dependencies = packageJson.dependencies as Record + const sdkPackages = [ + '@sun-protocol/sun-sdk-api', + '@sun-protocol/sun-sdk-chains', + '@sun-protocol/sun-sdk-core', + '@sun-protocol/sun-sdk-protocols', + '@sun-protocol/sun-sdk-runtime', + ] + + for (const name of sdkPackages) { + expect(dependencies[name]).toBe('0.1.0') + } + }) +}) diff --git a/test/helpers/run-cli.ts b/test/helpers/run-cli.ts new file mode 100644 index 0000000..ba47414 --- /dev/null +++ b/test/helpers/run-cli.ts @@ -0,0 +1,79 @@ +import { spawn } from 'child_process' +import path from 'path' + +export interface CliResult { + code: number | null + signal: NodeJS.Signals | null + stdout: string + stderr: string +} + +export interface RunCliOptions { + env?: NodeJS.ProcessEnv + timeoutMs?: number +} + +const secretEnvKeys = [ + 'AGENT_WALLET_PRIVATE_KEY', + 'AGENT_WALLET_MNEMONIC', + 'AGENT_WALLET_PASSWORD', + 'AGENT_WALLET_DIR', + 'AGENT_WALLET_MNEMONIC_ACCOUNT_INDEX', + 'TRON_NETWORK', + 'TRON_RPC_URL', + 'TRONGRID_API_KEY', + 'TRON_GRID_API_KEY', +] + +export function runCli(args: readonly string[], options: RunCliOptions = {}): Promise { + const rootDir = path.resolve(__dirname, '..', '..') + const env: NodeJS.ProcessEnv = { + ...process.env, + FORCE_COLOR: '0', + NO_COLOR: '1', + DOTENV_CONFIG_PATH: path.join(rootDir, '.codex-test-empty.env'), + ...options.env, + } + + for (const key of secretEnvKeys) { + if (!options.env || !(key in options.env)) { + delete env[key] + } + } + + return new Promise((resolve, reject) => { + const child = spawn( + process.execPath, + ['-r', 'ts-node/register/transpile-only', path.join(rootDir, 'src/bin.ts'), ...args], + { + cwd: rootDir, + env, + stdio: ['ignore', 'pipe', 'pipe'], + }, + ) + + let stdout = '' + let stderr = '' + const timeout = setTimeout(() => { + child.kill('SIGTERM') + reject(new Error(`CLI timed out after ${options.timeoutMs ?? 10000}ms: ${args.join(' ')}`)) + }, options.timeoutMs ?? 10000) + + child.stdout.setEncoding('utf8') + child.stderr.setEncoding('utf8') + child.stdout.on('data', (chunk) => { + stdout += chunk + }) + child.stderr.on('data', (chunk) => { + stderr += chunk + }) + child.on('error', (error) => { + clearTimeout(timeout) + reject(error) + }) + child.on('close', (code, signal) => { + clearTimeout(timeout) + resolve({ code, signal, stdout, stderr }) + }) + }) +} diff --git a/test/lib/context.test.ts b/test/lib/context.test.ts index eb25d26..2e6c233 100644 --- a/test/lib/context.test.ts +++ b/test/lib/context.test.ts @@ -12,13 +12,13 @@ describe('context', () => { walletConfigured?: boolean wallet?: Record }) { - const wallet = options?.wallet ?? { type: 'agent-wallet' } + const wallet = options?.wallet ?? { kind: 'custom' } const initWallet = jest.fn().mockResolvedValue(undefined) const getWallet = jest.fn().mockReturnValue(wallet) const isWalletConfigured = jest.fn().mockReturnValue(options?.walletConfigured ?? true) - const SunAPI = jest.fn().mockImplementation(() => ({ kind: 'api-instance' })) - const SunKit = jest.fn().mockImplementation((config) => ({ kind: 'kit-instance', config })) + const createSunApiClient = jest.fn().mockReturnValue({ kind: 'api-instance' }) + const createSunSDK = jest.fn().mockResolvedValue({ kind: 'sdk-instance' }) jest.doMock('../../src/lib/wallet', () => ({ initWallet, @@ -26,9 +26,9 @@ describe('context', () => { isWalletConfigured, })) - jest.doMock('@sun-protocol/sun-kit', () => ({ - SunAPI, - SunKit, + jest.doMock('../../src/lib/sdk/factory', () => ({ + createSunApiClient, + createSunSDK, })) const contextModule = require('../../src/lib/context') @@ -37,54 +37,50 @@ describe('context', () => { initWallet, getWallet, isWalletConfigured, - SunAPI, - SunKit, + createSunApiClient, + createSunSDK, wallet, } } - it('lazily initializes SunKit once and reuses the same instance', async () => { + it('lazily initializes SunSDK once and reuses the same instance', async () => { process.env.TRON_NETWORK = 'nile' process.env.TRONGRID_API_KEY = 'grid-key' process.env.TRON_RPC_URL = 'https://rpc.local' - const { contextModule, initWallet, getWallet, SunKit, wallet } = loadContextModule() + const { contextModule, initWallet, getWallet, createSunSDK, wallet } = loadContextModule() - const first = await contextModule.getKit() + const first = await contextModule.getSdk() const second = await contextModule.getKit() expect(initWallet).toHaveBeenCalledTimes(1) expect(getWallet).toHaveBeenCalledTimes(1) - expect(SunKit).toHaveBeenCalledTimes(1) + expect(createSunSDK).toHaveBeenCalledTimes(1) expect(first).toBe(second) - expect(first).toEqual({ - kind: 'kit-instance', - config: { - wallet, - network: 'nile', - tronGridApiKey: 'grid-key', - rpcUrl: 'https://rpc.local', - }, + expect(createSunSDK).toHaveBeenCalledWith({ + wallet, + network: 'nile', + tronGridApiKey: 'grid-key', + rpcUrl: 'https://rpc.local', }) }) - it('creates SunKit without a wallet when none is configured', async () => { + it('creates SunSDK without a wallet when none is configured', async () => { process.env.TRON_GRID_API_KEY = 'fallback-key' - const { contextModule, getWallet, SunKit } = loadContextModule({ + const { contextModule, getWallet, createSunSDK } = loadContextModule({ walletConfigured: false, }) - const kit = await contextModule.getKit() + await contextModule.getSdk() expect(getWallet).not.toHaveBeenCalled() - expect(SunKit).toHaveBeenCalledWith({ + expect(createSunSDK).toHaveBeenCalledWith({ wallet: undefined, network: 'mainnet', tronGridApiKey: 'fallback-key', rpcUrl: undefined, }) - expect(kit.config.wallet).toBeUndefined() }) it('throws from ensureWallet when initialization completes without a wallet', async () => { @@ -99,13 +95,13 @@ describe('context', () => { expect(isWalletConfigured).toHaveBeenCalledTimes(1) }) - it('caches SunAPI instances', () => { - const { contextModule, SunAPI } = loadContextModule() + it('caches SUN API client instances', () => { + const { contextModule, createSunApiClient } = loadContextModule() const first = contextModule.getApi() const second = contextModule.getApi() - expect(SunAPI).toHaveBeenCalledTimes(1) + expect(createSunApiClient).toHaveBeenCalledTimes(1) expect(first).toBe(second) }) }) diff --git a/test/lib/sdk/compat.test.ts b/test/lib/sdk/compat.test.ts new file mode 100644 index 0000000..07b148b --- /dev/null +++ b/test/lib/sdk/compat.test.ts @@ -0,0 +1,29 @@ +import { toCliTxResult } from '../../../src/lib/sdk/compat' + +describe('SDK result compatibility', () => { + it('keeps nested txResult txid shape for explorer enrichment', () => { + expect(toCliTxResult({ txResult: { txid: 'abc' }, raw: { result: true } })).toEqual({ + txResult: { txid: 'abc' }, + raw: { result: true }, + }) + }) + + it('maps SDK txID to existing txid shape', () => { + expect(toCliTxResult({ txID: 'abc', result: true })).toEqual({ + txid: 'abc', + result: true, + }) + }) + + it('maps SDK transaction plan execution to txid shape', () => { + expect( + toCliTxResult({ + txids: ['first', 'last'], + finalResult: { type: 'transaction', txid: 'last', raw: { result: true } }, + }), + ).toEqual({ + txid: 'last', + raw: { result: true }, + }) + }) +}) diff --git a/test/lib/sdk/constants.test.ts b/test/lib/sdk/constants.test.ts new file mode 100644 index 0000000..618f72b --- /dev/null +++ b/test/lib/sdk/constants.test.ts @@ -0,0 +1,9 @@ +import { TRX_ADDRESS, WTRX_MAINNET, WTRX_NILE } from '../../../src/lib/sdk/constants' + +describe('SDK compatibility constants', () => { + it('keeps TRX and WTRX addresses compatible with the old CLI registry', () => { + expect(TRX_ADDRESS).toBe('T9yD14Nj9j7xAB4dbGeiX9h8unkKHxuWwb') + expect(WTRX_MAINNET).toBe('TNUC9Qb1rRpS5CbWLmNMxXBjyFoydXjWFR') + expect(WTRX_NILE).toBe('TYsbWxNnyTgsZaTFaue9hqpxkU3Fkco94a') + }) +}) diff --git a/test/lib/sdk/errors.test.ts b/test/lib/sdk/errors.test.ts new file mode 100644 index 0000000..c25bbe3 --- /dev/null +++ b/test/lib/sdk/errors.test.ts @@ -0,0 +1,18 @@ +import { mapSdkError } from '../../../src/lib/sdk/errors' + +describe('SDK error compatibility', () => { + it('preserves existing wallet error code', () => { + const err = mapSdkError( + new Error('Wallet required. Set agent-wallet credentials before running this command.'), + ) + + expect((err as any).code).toBe('NO_WALLET') + }) + + it('preserves SDK error codes when present', () => { + const err = mapSdkError(Object.assign(new Error('bad params'), { code: 'INVALID_ARGUMENT' })) + + expect(err.message).toBe('bad params') + expect((err as any).code).toBe('INVALID_ARGUMENT') + }) +}) diff --git a/test/lib/sdk/factory.test.ts b/test/lib/sdk/factory.test.ts new file mode 100644 index 0000000..683bcaa --- /dev/null +++ b/test/lib/sdk/factory.test.ts @@ -0,0 +1,32 @@ +describe('SDK factory', () => { + beforeEach(() => { + jest.resetModules() + jest.clearAllMocks() + }) + + it('defaults to mainnet and accepts nile', () => { + const { getNetworkFromEnv } = require('../../../src/lib/sdk/factory') + + expect(getNetworkFromEnv({})).toBe('mainnet') + expect(getNetworkFromEnv({ TRON_NETWORK: 'nile' })).toBe('nile') + }) + + it('sets a non-signing caller address for TronGrid constant calls', () => { + const { createReadonlyTronWeb } = require('../../../src/lib/sdk/factory') + const tronWeb = createReadonlyTronWeb({ network: 'nile' }) + + expect(tronWeb.defaultAddress).toEqual({ + base58: 'T9yD14Nj9j7xAB4dbGeiX9h8unkKHxuWwb', + hex: '410000000000000000000000000000000000000000', + }) + expect(tronWeb.defaultPrivateKey).toBe(false) + }) + + it('rejects SDK-unsupported networks before runtime creation', async () => { + const { createSdkRuntime } = require('../../../src/lib/sdk/factory') + + await expect(createSdkRuntime({ network: 'shasta' })).rejects.toThrow( + 'Unsupported SDK network: shasta', + ) + }) +}) diff --git a/test/lib/sdk/wallet.test.ts b/test/lib/sdk/wallet.test.ts new file mode 100644 index 0000000..c652df7 --- /dev/null +++ b/test/lib/sdk/wallet.test.ts @@ -0,0 +1,34 @@ +import { AgentWalletSdkAdapter } from '../../../src/lib/sdk/wallet' + +describe('AgentWalletSdkAdapter', () => { + it('adapts agent-wallet signing to SDK WalletAdapter', async () => { + const agentWallet = { + getAddress: jest.fn().mockResolvedValue('TAUN6FwrnwwmaEqYcckffC7wYmbaS6cBiX'), + signTransaction: jest + .fn() + .mockResolvedValue(JSON.stringify({ txID: 'abc', signature: ['sig'] })), + signMessage: jest.fn().mockResolvedValue('signed'), + signTypedData: jest.fn().mockResolvedValue('0xtyped'), + } + + const wallet = new AgentWalletSdkAdapter(agentWallet as any) + + expect(wallet.kind).toBe('custom') + expect(wallet.capabilities).toEqual({ + signTransaction: true, + signAndSendTransaction: false, + signTypedData: true, + userInteractive: false, + }) + expect(await wallet.getAddress()).toBe('TAUN6FwrnwwmaEqYcckffC7wYmbaS6cBiX') + expect(await wallet.signTransaction!({ txID: 'abc' })).toEqual({ + txID: 'abc', + signature: ['sig'], + }) + expect(await wallet.signMessage!('hello')).toBe('signed') + expect( + await wallet.signTypedData!({ domain: {}, types: {}, primaryType: 'Message', message: {} }), + ).toBe('typed') + expect(agentWallet.signMessage).toHaveBeenCalledWith(Buffer.from('hello', 'utf-8')) + }) +}) diff --git a/test/lib/sunpump.test.ts b/test/lib/sunpump.test.ts deleted file mode 100644 index 183e783..0000000 --- a/test/lib/sunpump.test.ts +++ /dev/null @@ -1,34 +0,0 @@ -import { SunPump, SunPumpHttpError, SUNPUMP_DEFAULT_BASE_URL } from '../../src/lib/sunpump' - -describe('SunPump', () => { - it('uses default base URL', () => { - const client = new SunPump() - expect(SUNPUMP_DEFAULT_BASE_URL).toBe('https://api-v2.sunpump.meme/pump-api') - }) - - it('accepts custom base URL', () => { - const client = new SunPump({ baseUrl: 'https://custom.example.com/api' }) - expect(client).toBeDefined() - }) - - it('accepts custom fetch implementation', () => { - const mockFetch = jest.fn() - const client = new SunPump({ fetchImpl: mockFetch as unknown as typeof fetch }) - expect(client).toBeDefined() - }) -}) - -describe('SunPumpHttpError', () => { - it('has correct error code', () => { - const err = new SunPumpHttpError('test error', 404, 'not found') - expect(err.code).toBe('SUNPUMP_HTTP_ERROR') - expect(err.status).toBe(404) - expect(err.body).toBe('not found') - expect(err.message).toBe('test error') - }) - - it('is an instance of Error', () => { - const err = new SunPumpHttpError('test', 500) - expect(err).toBeInstanceOf(Error) - }) -}) diff --git a/test/lib/wallet.test.ts b/test/lib/wallet.test.ts index d94e33b..f8a811a 100644 --- a/test/lib/wallet.test.ts +++ b/test/lib/wallet.test.ts @@ -20,7 +20,6 @@ describe('wallet', () => { function loadWalletModule(options?: { activeWallet?: Record - tronWeb?: Record getActiveWalletError?: Error getActiveWalletValue?: Record | null }) { @@ -31,16 +30,6 @@ describe('wallet', () => { signTypedData: jest.fn().mockResolvedValue('0xsignedtypeddata'), } - const tronWeb = options?.tronWeb ?? { - address: { - toHex: jest.fn().mockReturnValue('41abc'), - fromHex: jest.fn().mockReturnValue('TWalletAddress'), - }, - trx: { - sendRawTransaction: jest.fn().mockResolvedValue({ result: true, txid: 'tx-123' }), - }, - } - const provider = { getActiveWallet: options?.getActiveWalletError ? jest.fn().mockRejectedValue(options.getActiveWalletError) @@ -54,24 +43,17 @@ describe('wallet', () => { } const resolveWalletProvider = jest.fn().mockReturnValue(provider) - const createReadonlyTronWeb = jest.fn().mockResolvedValue(tronWeb) jest.doMock('@bankofai/agent-wallet', () => ({ resolveWalletProvider, })) - jest.doMock('@sun-protocol/sun-kit', () => ({ - createReadonlyTronWeb, - })) - const walletModule = require('../../src/lib/wallet') return { walletModule, activeWallet, - tronWeb, provider, resolveWalletProvider, - createReadonlyTronWeb, } } @@ -116,78 +98,36 @@ describe('wallet', () => { await expect(walletModule.getWalletAddress()).resolves.toBe('TWalletAddress') }) - it('builds tronWeb with the active wallet as default address', async () => { + it('returns an SDK wallet adapter that signs and normalizes typed-data signatures', async () => { process.env.AGENT_WALLET_PASSWORD = 'secret' - const { walletModule, tronWeb, createReadonlyTronWeb } = loadWalletModule() - - await walletModule.initWallet() - const wallet = walletModule.getWallet() as any - const resolvedTronWeb = await wallet.getTronWeb('nile') - - expect(createReadonlyTronWeb).toHaveBeenCalledWith('nile') - expect(resolvedTronWeb).toBe(tronWeb) - expect(tronWeb.address.toHex).toHaveBeenCalledWith('TWalletAddress') - expect(tronWeb.address.fromHex).toHaveBeenCalledWith('41abc') - expect((tronWeb as any).defaultAddress).toEqual({ - hex: '41abc', - base58: 'TWalletAddress', - }) - }) - - it('signs, broadcasts, and normalizes typed-data signatures', async () => { - process.env.AGENT_WALLET_PASSWORD = 'secret' - - const { walletModule, activeWallet, tronWeb } = loadWalletModule({ + const { walletModule, activeWallet } = loadWalletModule({ activeWallet: { getAddress: jest.fn().mockResolvedValue('TWalletAddress'), signTransaction: jest.fn().mockResolvedValue('{"txID":"signed","raw_data":{"x":1}}'), signMessage: jest.fn().mockResolvedValue('signed-message'), signTypedData: jest.fn().mockResolvedValue('0xdeadbeef'), }, - tronWeb: { - address: { - toHex: jest.fn().mockReturnValue('41abc'), - fromHex: jest.fn().mockReturnValue('TWalletAddress'), - }, - trx: { - sendRawTransaction: jest.fn().mockResolvedValue({ result: true, txid: 'tx-999' }), - }, - }, }) await walletModule.initWallet() const wallet = walletModule.getWallet() as any - await expect( - wallet.signAndBroadcast({ transaction: { raw_data: { contract: [] } } }, 'mainnet'), - ).resolves.toEqual({ result: true, txid: 'tx-999' }) - expect(activeWallet.signTransaction).toHaveBeenCalledWith({ raw_data: { contract: [] } }) - expect(tronWeb.trx.sendRawTransaction).toHaveBeenCalledWith({ + expect(wallet.kind).toBe('custom') + await expect(wallet.signTransaction({ raw_data: { contract: [] } })).resolves.toEqual({ txID: 'signed', raw_data: { x: 1 }, }) + expect(activeWallet.signTransaction).toHaveBeenCalledWith({ raw_data: { contract: [] } }) - await expect( - wallet.signTypedData( - 'Permit', - { name: 'Sun', chainId: 1, verifyingContract: 'TContract' }, - { Permit: [{ name: 'owner', type: 'address' }] }, - { owner: 'TWalletAddress' }, - ), - ).resolves.toBe('deadbeef') - expect(activeWallet.signTypedData).toHaveBeenCalledWith({ + const payload = { domain: { name: 'Sun', chainId: 1, verifyingContract: 'TContract' }, - types: { - EIP712Domain: [ - { name: 'name', type: 'string' }, - { name: 'chainId', type: 'uint256' }, - { name: 'verifyingContract', type: 'address' }, - ], - Permit: [{ name: 'owner', type: 'address' }], - }, - primaryType: 'Permit', + types: { Permit: [{ name: 'owner', type: 'address' }] }, message: { owner: 'TWalletAddress' }, + } + await expect(wallet.signTypedData(payload)).resolves.toBe('deadbeef') + expect(activeWallet.signTypedData).toHaveBeenCalledWith({ + ...payload, }) }) }) From 0211ca8b7d099211d3f0af3ed6459c3d6328ac2e Mon Sep 17 00:00:00 2001 From: Felix Date: Fri, 9 Oct 2026 15:53:54 +0800 Subject: [PATCH 02/12] fix: preserve liquidity command transaction semantics --- src/commands/liquidity.ts | 29 +++++++++++++++++++++++++---- test/commands/liquidity-sdk.test.ts | 12 ++++++++++++ 2 files changed, 37 insertions(+), 4 deletions(-) diff --git a/src/commands/liquidity.ts b/src/commands/liquidity.ts index 3b37653..7127a4e 100644 --- a/src/commands/liquidity.ts +++ b/src/commands/liquidity.ts @@ -116,6 +116,26 @@ interface PairReserves { pairExists: boolean } +export function unwrapContractAddressResult(result: unknown, label: string): string { + const value = Array.isArray(result) + ? result[0] + : result && typeof result === 'object' + ? Object.values(result as Record)[0] + : result + if (typeof value !== 'string') throw new Error(`${label} did not return an address`) + return value +} + +function toBase58ContractAddress( + tronWeb: ReturnType, + result: unknown, + label: string, +): string { + const value = unwrapContractAddressResult(result, label) + if (value.startsWith('T')) return value + return tronWeb.address.fromHex(value.startsWith('0x') ? `41${value.slice(2)}` : value) +} + async function getV2PairReserves( network: string, tokenA: string, @@ -128,8 +148,8 @@ async function getV2PairReserves( const lookupB = getLookupToken(tokenB, network) const factory = await tronWeb.contract(SUNSWAP_V2_FACTORY_MIN_ABI as any, factoryAddress) - const pairHex = await factory.getPair(lookupA, lookupB).call() - const pairBase58 = tronWeb.address.fromHex(pairHex) + const pairResult = await factory.getPair(lookupA, lookupB).call() + const pairBase58 = toBase58ContractAddress(tronWeb, pairResult, 'V2 factory getPair') const zeroBase58 = tronWeb.address.fromHex('410000000000000000000000000000000000000000') if (!pairBase58 || pairBase58 === zeroBase58) { @@ -138,9 +158,9 @@ async function getV2PairReserves( const pair = await tronWeb.contract(SUNSWAP_V2_PAIR_MIN_ABI as any, pairBase58) const reserves = await pair.getReserves().call() - const token0Hex = await pair.token0().call() + const token0Result = await pair.token0().call() - const token0 = tronWeb.address.fromHex(token0Hex) + const token0 = toBase58ContractAddress(tronWeb, token0Result, 'V2 pair token0') const reserve0 = (reserves._reserve0 ?? reserves[0]).toString() const reserve1 = (reserves._reserve1 ?? reserves[1]).toString() @@ -518,6 +538,7 @@ export function registerLiquidityCommands(program: Command) { liquidity: opts.liquidity, amount0Min: opts.min0, amount1Min: opts.min1, + collectFees: false, deadline: opts.deadline, }) .then(mapTx), diff --git a/test/commands/liquidity-sdk.test.ts b/test/commands/liquidity-sdk.test.ts index 48fec3b..a638733 100644 --- a/test/commands/liquidity-sdk.test.ts +++ b/test/commands/liquidity-sdk.test.ts @@ -6,6 +6,15 @@ describe('liquidity SDK integration', () => { jest.clearAllMocks() }) + it('unwraps TronWeb single-output address arrays', () => { + const { unwrapContractAddressResult } = require('../../src/commands/liquidity') + expect(unwrapContractAddressResult(['41abc'], 'getPair')).toBe('41abc') + expect(unwrapContractAddressResult({ pair: '41def' }, 'getPair')).toBe('41def') + expect(() => unwrapContractAddressResult([], 'getPair')).toThrow( + 'getPair did not return an address', + ) + }) + async function captureWriteAction(args: string[]) { jest.resetModules() const writeAction = jest.fn().mockResolvedValue(undefined) @@ -99,6 +108,8 @@ describe('liquidity SDK integration', () => { tickLower: undefined, tickUpper: undefined, tickRangeWidth: 100, + tokenA: 'TYsbWxNnyTgsZaTFaue9hqpxkU3Fkco94a', + tokenB: 'TXYZopYRdj2D9XRtbG411XZZ3kM5VkAeBf', amount0: '200', amount1: '100', amount0Min: '22', @@ -128,6 +139,7 @@ describe('liquidity SDK integration', () => { liquidity: '100', amount0Min: '11', amount1Min: '22', + collectFees: false, }), ) }) From 99c8fa0344e32b891e8cf302181b282b0fa66104 Mon Sep 17 00:00:00 2001 From: Felix Date: Tue, 13 Oct 2026 15:49:41 +0800 Subject: [PATCH 03/12] fix: update SUN SDK to 0.1.1-beta.0 --- package-lock.json | 120 +++++++++--------- package.json | 10 +- test/contract/sdk-dependency-boundary.test.ts | 2 +- 3 files changed, 66 insertions(+), 66 deletions(-) diff --git a/package-lock.json b/package-lock.json index 2dbc69a..9cf1696 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,11 +12,11 @@ "@bankofai/agent-wallet": "^2.3.0", "@scure/bip32": "^2.0.1", "@scure/bip39": "^2.0.1", - "@sun-protocol/sun-sdk-api": "0.1.0", - "@sun-protocol/sun-sdk-chains": "0.1.0", - "@sun-protocol/sun-sdk-core": "0.1.0", - "@sun-protocol/sun-sdk-protocols": "0.1.0", - "@sun-protocol/sun-sdk-runtime": "0.1.0", + "@sun-protocol/sun-sdk-api": "0.1.1-beta.0", + "@sun-protocol/sun-sdk-chains": "0.1.1-beta.0", + "@sun-protocol/sun-sdk-core": "0.1.1-beta.0", + "@sun-protocol/sun-sdk-protocols": "0.1.1-beta.0", + "@sun-protocol/sun-sdk-runtime": "0.1.1-beta.0", "chalk": "^4.1.2", "cli-table3": "^0.6.5", "commander": "^12.1.0", @@ -2588,32 +2588,32 @@ } }, "node_modules/@sun-protocol/sun-sdk-api": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/@sun-protocol/sun-sdk-api/-/sun-sdk-api-0.1.0.tgz", - "integrity": "sha512-ilaj6fH38pBiQ7bxzz1TtYmU8HGWr5J+YXGwGTlMuES/NTfoUIVzLjqA7N7h1gt2m1VCb+7rBf7+5VORRumVuQ==", + "version": "0.1.1-beta.0", + "resolved": "https://registry.npmjs.org/@sun-protocol/sun-sdk-api/-/sun-sdk-api-0.1.1-beta.0.tgz", + "integrity": "sha512-3XZib1T/BzQS1v+W3ZlY090OW27kz8YJVEP4Ztn002OjnxwD/98nq7NBXKKvgQrRRltJodUr+XeWwYE7GGQlCQ==", "dependencies": { - "@sun-protocol/sun-sdk-chains": "0.1.0", - "@sun-protocol/sun-sdk-core": "0.1.0" + "@sun-protocol/sun-sdk-chains": "0.1.1-beta.0", + "@sun-protocol/sun-sdk-core": "0.1.1-beta.0" }, "engines": { "node": ">=20" } }, "node_modules/@sun-protocol/sun-sdk-chains": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/@sun-protocol/sun-sdk-chains/-/sun-sdk-chains-0.1.0.tgz", - "integrity": "sha512-MOYksVAW70mKlY/SGspOO6VqRAcPB4Zf/lq3AopZ9pw79CEKWZAL9VOvb2LgWkStZkk5fvZGi5S8bQE5YlSnRA==", + "version": "0.1.1-beta.0", + "resolved": "https://registry.npmjs.org/@sun-protocol/sun-sdk-chains/-/sun-sdk-chains-0.1.1-beta.0.tgz", + "integrity": "sha512-X1pcDF8+woHeweNzA43ZswV+k7ydfK50J61KqJcl7dNTJVV/ritVzv6DHDIbi4KjadyZY+lQMgkMS+Cot+mpHg==", "dependencies": { - "@sun-protocol/sun-sdk-core": "0.1.0" + "@sun-protocol/sun-sdk-core": "0.1.1-beta.0" }, "engines": { "node": ">=20" } }, "node_modules/@sun-protocol/sun-sdk-core": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/@sun-protocol/sun-sdk-core/-/sun-sdk-core-0.1.0.tgz", - "integrity": "sha512-ZFk9ZS3cf2A09HLYbnHDX30eS0aIN/Aq6s2xunvSWNxP3569/5AVNMLMRVIPydnAjbqWZlHpD7h6RzxFccMD1w==", + "version": "0.1.1-beta.0", + "resolved": "https://registry.npmjs.org/@sun-protocol/sun-sdk-core/-/sun-sdk-core-0.1.1-beta.0.tgz", + "integrity": "sha512-VzdMJyzx+59aetjkv7vq9HM38brulbuwhhM9ELxyVadtaou+i7OhOPK7J8G8JzRDzPbxuRjZpfcbI5QYycd5cA==", "dependencies": { "@ethersproject/address": "^5.0.2", "@ethersproject/bignumber": "^5.5.0", @@ -2632,33 +2632,33 @@ } }, "node_modules/@sun-protocol/sun-sdk-launchpad": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/@sun-protocol/sun-sdk-launchpad/-/sun-sdk-launchpad-0.1.0.tgz", - "integrity": "sha512-0flPr/T1SqHK0/ZPEim0mr9vOQR4Lc6aEcZx9OaZ5o9j4zOUk6xbMNxkIzm5wy9LUlZisIg5VHerm5eWuCm1uA==", + "version": "0.1.1-beta.0", + "resolved": "https://registry.npmjs.org/@sun-protocol/sun-sdk-launchpad/-/sun-sdk-launchpad-0.1.1-beta.0.tgz", + "integrity": "sha512-sCw3eQGs+95Jsrt3W5p85vodVhyH8S+7buafADV69ooKM0h9XnsqEEH0Mv/GH8NOIvbehlSMQrEjOMsWRvea7A==", "dependencies": { - "@sun-protocol/sun-sdk-chains": "0.1.0", - "@sun-protocol/sun-sdk-core": "0.1.0", - "@sun-protocol/sun-sdk-runtime": "0.1.0" + "@sun-protocol/sun-sdk-chains": "0.1.1-beta.0", + "@sun-protocol/sun-sdk-core": "0.1.1-beta.0", + "@sun-protocol/sun-sdk-runtime": "0.1.1-beta.0" }, "engines": { "node": ">=20" } }, "node_modules/@sun-protocol/sun-sdk-protocols": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/@sun-protocol/sun-sdk-protocols/-/sun-sdk-protocols-0.1.0.tgz", - "integrity": "sha512-ifIkZKrYeVaJeD9mVjdfvUQNvR7OMti8BD/Ox5N4pixBQU8uZ6s2ZZMFRPXdNevx7HDwZ1r+uJBv30yW2qfTeA==", + "version": "0.1.1-beta.0", + "resolved": "https://registry.npmjs.org/@sun-protocol/sun-sdk-protocols/-/sun-sdk-protocols-0.1.1-beta.0.tgz", + "integrity": "sha512-oZ1DoVsYNqctBJKfVQKSvWmtpu4PHWK7jBq7+pRx8bLScxfnsB1gcMApQkaO0WDAwfR+Sbgy1G/TJkuFG3bNqQ==", "dependencies": { "@sun-protocol/permit2-sdk": "^1.0.0", - "@sun-protocol/sun-sdk-api": "0.1.0", - "@sun-protocol/sun-sdk-chains": "0.1.0", - "@sun-protocol/sun-sdk-core": "0.1.0", - "@sun-protocol/sun-sdk-launchpad": "0.1.0", - "@sun-protocol/sun-sdk-psm": "0.1.0", - "@sun-protocol/sun-sdk-runtime": "0.1.0", - "@sun-protocol/sun-sdk-sunswap-v2": "0.1.0", - "@sun-protocol/sun-sdk-sunswap-v3": "0.1.0", - "@sun-protocol/sun-sdk-sunswap-v4": "0.1.0", + "@sun-protocol/sun-sdk-api": "0.1.1-beta.0", + "@sun-protocol/sun-sdk-chains": "0.1.1-beta.0", + "@sun-protocol/sun-sdk-core": "0.1.1-beta.0", + "@sun-protocol/sun-sdk-launchpad": "0.1.1-beta.0", + "@sun-protocol/sun-sdk-psm": "0.1.1-beta.0", + "@sun-protocol/sun-sdk-runtime": "0.1.1-beta.0", + "@sun-protocol/sun-sdk-sunswap-v2": "0.1.1-beta.0", + "@sun-protocol/sun-sdk-sunswap-v3": "0.1.1-beta.0", + "@sun-protocol/sun-sdk-sunswap-v4": "0.1.1-beta.0", "@sun-protocol/universal-router-sdk": "^1.1.1" }, "engines": { @@ -2666,24 +2666,24 @@ } }, "node_modules/@sun-protocol/sun-sdk-psm": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/@sun-protocol/sun-sdk-psm/-/sun-sdk-psm-0.1.0.tgz", - "integrity": "sha512-o75VKy+XP613dhHmtmVVWTQvrEQgEikDFEFiwg9wd57Ogmhz/sDrNwZE1KLZcmUbNLbSjP0/BYQPwnob6YWKkw==", + "version": "0.1.1-beta.0", + "resolved": "https://registry.npmjs.org/@sun-protocol/sun-sdk-psm/-/sun-sdk-psm-0.1.1-beta.0.tgz", + "integrity": "sha512-YVjnmkgqT+j8+28a4Ek4u+yrdaQ2n9gTi5ArqllijtzMXAMzfIul2GoFQ68k8WbENd4+ASJ4er+bI+5fcXXNug==", "dependencies": { - "@sun-protocol/sun-sdk-chains": "0.1.0", - "@sun-protocol/sun-sdk-core": "0.1.0" + "@sun-protocol/sun-sdk-chains": "0.1.1-beta.0", + "@sun-protocol/sun-sdk-core": "0.1.1-beta.0" }, "engines": { "node": ">=20" } }, "node_modules/@sun-protocol/sun-sdk-runtime": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/@sun-protocol/sun-sdk-runtime/-/sun-sdk-runtime-0.1.0.tgz", - "integrity": "sha512-vporzxWRxoOX2fqJNTRgn1CJTqN/SZI1ukw8gn8OzY4Ytbad/D0c+Y7V3NE3y8ikrDh4PrA0NBAuHbhtLjkRzw==", + "version": "0.1.1-beta.0", + "resolved": "https://registry.npmjs.org/@sun-protocol/sun-sdk-runtime/-/sun-sdk-runtime-0.1.1-beta.0.tgz", + "integrity": "sha512-IKpIX23Og+aV2HAvn0C6ASbXf1UB68imGzUAjVlT0j2wzbCDLdEgr90YLqGt3PfWLCUb0oTOg69ay/QBBulZPg==", "dependencies": { - "@sun-protocol/sun-sdk-chains": "0.1.0", - "@sun-protocol/sun-sdk-core": "0.1.0" + "@sun-protocol/sun-sdk-chains": "0.1.1-beta.0", + "@sun-protocol/sun-sdk-core": "0.1.1-beta.0" }, "engines": { "node": ">=20" @@ -2698,16 +2698,16 @@ } }, "node_modules/@sun-protocol/sun-sdk-sunswap-v2": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/@sun-protocol/sun-sdk-sunswap-v2/-/sun-sdk-sunswap-v2-0.1.0.tgz", - "integrity": "sha512-GMGqceLCWvRTAVm3pYDEOK87sRHLFVqrQY55nkIQP0iwj/wIXznMY4ffiJ/cy/Ou8wL9V1diKQuefvAit/v8fA==", + "version": "0.1.1-beta.0", + "resolved": "https://registry.npmjs.org/@sun-protocol/sun-sdk-sunswap-v2/-/sun-sdk-sunswap-v2-0.1.1-beta.0.tgz", + "integrity": "sha512-g8p4ch+F/iD0NGVS6b7oPSCcYwCLwYvoNR1/pFPSuTn4PXgGgiLOrAwjshsA/k7q+GfRxTDyOmV14CI6Aa3+Ug==", "dependencies": { "@ethersproject/bignumber": "5.7.0", "@ethersproject/keccak256": "5.7.0", "@ethersproject/solidity": "5.7.0", "@noble/hashes": "1.4.0", - "@sun-protocol/sun-sdk-chains": "0.1.0", - "@sun-protocol/sun-sdk-core": "0.1.0", + "@sun-protocol/sun-sdk-chains": "0.1.1-beta.0", + "@sun-protocol/sun-sdk-core": "0.1.1-beta.0", "jsbi": "^3.1.4", "tiny-invariant": "^1.1.0" }, @@ -2749,9 +2749,9 @@ } }, "node_modules/@sun-protocol/sun-sdk-sunswap-v3": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/@sun-protocol/sun-sdk-sunswap-v3/-/sun-sdk-sunswap-v3-0.1.0.tgz", - "integrity": "sha512-kuQ1IfpwfxOrMGmY4JIF6+0Q7hX1B/2iXqHq6lTZovl/kYERlGMfoIcsBRE1NczOqC1Tz+n7QVh4nHUOdaelNg==", + "version": "0.1.1-beta.0", + "resolved": "https://registry.npmjs.org/@sun-protocol/sun-sdk-sunswap-v3/-/sun-sdk-sunswap-v3-0.1.1-beta.0.tgz", + "integrity": "sha512-P6Fp5pUQFjeTCewiOXrhOuV14EQfSRS06dUMdq70BRrdsvWJnRKA3Pkvmv2oCWsXlQHFjfOC03agHB9STOTszw==", "dependencies": { "@ethersproject/abi": "5.7.0", "@ethersproject/address": "5.7.0", @@ -2760,8 +2760,8 @@ "@ethersproject/logger": "5.7.0", "@ethersproject/solidity": "5.7.0", "@noble/hashes": "1.4.0", - "@sun-protocol/sun-sdk-chains": "0.1.0", - "@sun-protocol/sun-sdk-core": "0.1.0", + "@sun-protocol/sun-sdk-chains": "0.1.1-beta.0", + "@sun-protocol/sun-sdk-core": "0.1.1-beta.0", "jsbi": "^3.1.4", "tiny-invariant": "^1.1.0" }, @@ -2840,14 +2840,14 @@ } }, "node_modules/@sun-protocol/sun-sdk-sunswap-v4": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/@sun-protocol/sun-sdk-sunswap-v4/-/sun-sdk-sunswap-v4-0.1.0.tgz", - "integrity": "sha512-FRL2nqjmDxdeOVeRe0r/oATBekwcohHXoZ9SpA5bD6fWD9MIMW+jVQ0cmYn2HheCwNlp8yfCvM/SV4JXleBRoQ==", + "version": "0.1.1-beta.0", + "resolved": "https://registry.npmjs.org/@sun-protocol/sun-sdk-sunswap-v4/-/sun-sdk-sunswap-v4-0.1.1-beta.0.tgz", + "integrity": "sha512-5s9VPri9IMPrC7aemRPhKoVKZSZAtSyjbTaFlixk10TssJzeF0vULPFeGEr+vSyUqNIA+QDMfOwYaByaQ5HC0Q==", "dependencies": { "@ethersproject/keccak256": "5.7.0", "@noble/hashes": "1.4.0", - "@sun-protocol/sun-sdk-chains": "0.1.0", - "@sun-protocol/sun-sdk-core": "0.1.0" + "@sun-protocol/sun-sdk-chains": "0.1.1-beta.0", + "@sun-protocol/sun-sdk-core": "0.1.1-beta.0" }, "engines": { "node": ">=20" diff --git a/package.json b/package.json index 55518fa..10c202d 100644 --- a/package.json +++ b/package.json @@ -47,11 +47,11 @@ "@bankofai/agent-wallet": "^2.3.0", "@scure/bip32": "^2.0.1", "@scure/bip39": "^2.0.1", - "@sun-protocol/sun-sdk-api": "0.1.0", - "@sun-protocol/sun-sdk-chains": "0.1.0", - "@sun-protocol/sun-sdk-core": "0.1.0", - "@sun-protocol/sun-sdk-protocols": "0.1.0", - "@sun-protocol/sun-sdk-runtime": "0.1.0", + "@sun-protocol/sun-sdk-api": "0.1.1-beta.0", + "@sun-protocol/sun-sdk-chains": "0.1.1-beta.0", + "@sun-protocol/sun-sdk-core": "0.1.1-beta.0", + "@sun-protocol/sun-sdk-protocols": "0.1.1-beta.0", + "@sun-protocol/sun-sdk-runtime": "0.1.1-beta.0", "chalk": "^4.1.2", "cli-table3": "^0.6.5", "commander": "^12.1.0", diff --git a/test/contract/sdk-dependency-boundary.test.ts b/test/contract/sdk-dependency-boundary.test.ts index 9de2802..54ca9ec 100644 --- a/test/contract/sdk-dependency-boundary.test.ts +++ b/test/contract/sdk-dependency-boundary.test.ts @@ -34,7 +34,7 @@ describe('public SUN SDK dependency boundary', () => { ] for (const name of sdkPackages) { - expect(dependencies[name]).toBe('0.1.0') + expect(dependencies[name]).toBe('0.1.1-beta.0') } }) }) From 93f3328a379b82af20ad58626a2fcd1b5f6b4491 Mon Sep 17 00:00:00 2001 From: Felix Date: Tue, 13 Oct 2026 16:46:30 +0800 Subject: [PATCH 04/12] chore: remove internal migration design document --- SUN_SDK_MIGRATION_DESIGN.md | 177 ------------------------------------ 1 file changed, 177 deletions(-) delete mode 100644 SUN_SDK_MIGRATION_DESIGN.md diff --git a/SUN_SDK_MIGRATION_DESIGN.md b/SUN_SDK_MIGRATION_DESIGN.md deleted file mode 100644 index 02896a4..0000000 --- a/SUN_SDK_MIGRATION_DESIGN.md +++ /dev/null @@ -1,177 +0,0 @@ -# sun-cli 接入公网 SUN SDK 方案 - -## 1. 背景与目标 - -本次改造沿用 SUN.io 接入 SUN SDK 的原则:保留 `sun-cli` 已有命令、参数、输出和确认流程,命令底层改用公网 npm 上的 SUN SDK 实现。 - -目标: - -- 保持现有 CLI API 兼容,避免调用方迁移。 -- 删除 `@sun-protocol/sun-kit`、旧私有 `@sun-sdk/*` 和私有 npm registry 依赖。 -- 统一通过 SUN SDK 完成 API 查询、链上读取、交易构建和执行。 -- 复用 `agent-wallet`,通过适配器满足 SDK 的 `WalletAdapter` 契约。 -- 支持 mainnet 和 Nile;公网 SDK 当前不支持 Shasta,SDK 命令在 Shasta 下应返回明确错误。 - -非目标: - -- 不调整命令名称、参数语义或默认输出格式。 -- 不把带真实私钥的 E2E 脚本、报告或测试账户提交到仓库。 -- 不在本次需求中重做 SUN SDK 内部协议算法。 - -## 2. 依赖策略 - -正式依赖最终固定到通过验证的公网版本。当前 manifest 暂时保留 `0.1.0`,本地联调同时安装 SUN SDK 功能分支产出的 protocols 与 sunswap-v2 tarball;Steven 发布 beta 后,再把以下依赖统一更新并锁定到该 beta 版本: - -| 包 | 用途 | -| --------------------------------- | ------------------------------------------ | -| `@sun-protocol/sun-sdk-api` | SUN.IO 和 SunPump HTTP API | -| `@sun-protocol/sun-sdk-chains` | 网络配置、协议合约地址、WTRX 地址 | -| `@sun-protocol/sun-sdk-core` | Network、TRON 地址和通用类型 | -| `@sun-protocol/sun-sdk-protocols` | Swap、V2/V3/V4 流动性、Position、SunPump | -| `@sun-protocol/sun-sdk-runtime` | TronWeb client、合约读写、余额和钱包运行时 | - -SDK 要求 Node.js 20 及以上,因此 CLI 的 `engines.node` 和 CI 矩阵同步调整为 Node 20/22。版本先精确锁定,后续 SDK 升级单独验证并更新锁文件,避免协议行为随安装时间漂移。 - -## 3. 分层设计 - -### 3.1 SDK 工厂 - -`src/lib/sdk/factory.ts` 负责: - -1. 校验 SDK 网络。 -2. 根据 `TRON_RPC_URL`、TronGrid API Key 和网络创建只读 `TronWeb`。 -3. 使用公网 runtime 的 Node 入口创建 `TronClient`。 -4. 注入 `WalletAdapter` 创建 `Runtime`。 -5. 创建一个进程内复用的 `SunSDK`。 - -CLI 创建的 SDK 始终带 runtime,因此使用 `CliSunSDK` 将 SDK 中可选的 `runtime` 收紧为必选,命令层不再依赖 `any`。 - -### 3.2 钱包适配 - -`AgentWalletSdkAdapter` 把现有 `agent-wallet` 映射到 SUN SDK: - -- `getAddress()`:返回当前 TRON 地址。 -- `signTransaction()`:签名 SDK 构建的 TRON 交易。 -- `signMessage()` / `signTypedData()`:保留消息和 Permit2 typed-data 能力。 -- 不在 SDK 层读取或持久化私钥。 - -读命令可以在没有钱包时执行;写命令仍由 `writeAction` 在确认前检查钱包。 - -### 3.3 命令映射 - -| CLI 能力 | 公网 SDK 调用 | -| ------------------------ | --------------------------------------- | -| SUN.IO 查询 | `SunApiClient` | -| SunPump 查询 | `SunPumpApiClient` | -| 钱包余额 | `readBalances(runtime.tronClient, ...)` | -| 通用合约读取/发送 | `runtime.contract.read/write` | -| 高层 Swap | `sdk.swap.router.quote/execute` | -| V2 增减流动性 | `sdk.liquidity.v2.add/remove` | -| V3 Mint/Increase | `sdk.liquidity.v3.add/increase` | -| V3 Decrease/Collect | `sdk.positions.v3.remove/collect` | -| V4 Mint/Increase | `sdk.liquidity.v4.add/increase` | -| V4 Decrease/Collect/Info | `sdk.positions.v4.remove/collect/read` | -| SunPump 买卖 | `sdk.pump.buy/sell.quote/execute` | - -CLI 的小数滑点继续接受 `0.005` 形式,在适配边界转换成 SDK 的百分比形式 `0.5%`。TRX/WTRX、交易结果和 SDK 错误也集中在适配层转换,命令输出继续使用 CLI 原有结构。 - -## 4. 兼容性约束 - -- 命令清单、参数名称、帮助文本和退出码通过契约测试锁定。 -- 写操作保留摘要、二次确认、`--yes`、`--dry-run` 和 Tronscan 链接。 -- JSON 模式继续输出机器可解析对象;交易结果规范化为 `txid`。 -- 原生 TRX 在需要 ERC20/TRC20 地址的 V2/V3/V4 场景转换成对应网络 WTRX。 -- V4 mint 在旧 CLI 未提供 fee/tick 时使用 `fee=3000`、`tickSpacing=60` 和 `[-120, 120]`;`--recipient` 映射到公网 SDK 的 `owner`。V4 increase/remove/collect 由 SDK 根据 `tokenId` 读取并校验 pool key,CLI 不拼装不完整的 pool key。 -- 未配置钱包时,读命令正常工作;写命令统一返回 `NO_WALLET` 类错误。 -- `--network shasta` 参数为兼容性继续保留,但 SDK 支持范围仅 mainnet/Nile。 - -### 4.1 公网 0.1.0 的兼容补充 - -公网协议层 `0.1.0` 尚未把旧 CLI 的所有“显式最小到账量”暴露为高层入参: - -- V2 remove 的 `--min-a/--min-b`。 -- V3 increase 的 `--min0/--min1`。 -- V3 decrease 的 `--min0/--min1`。 - -高层 SDK 会按 slippage 自行计算 minimum,直接透传这些额外字段不能视为已经支持。已采用方案 A:在 SUN SDK 的 `feature/sun-cli-compat-minimums` 分支补充显式 minimum 字段及单元测试,并通过本地 pack 供 sun-cli 联调。待 beta 验证通过后,再将 CLI 的精确依赖版本升级到公网发布版。 - -本地 pack 的 Nile 真实链上 E2E 已验证 V2 remove、V3 increase 和 V3 decrease 的显式 minimum;正式发布验收仍需等 Steven 发布 beta 后,在干净安装环境重新执行同一组用例。 - -### 4.2 本地 pack 联调结果 - -- V2 add/remove:真实交易成功,remove 后 LP 余额精确减少指定的 `100000000`,并收到两种底层资产。 -- V3 mint:在 TRX/USDT 0.05% 池创建仓位 `#705`;同时确认自动 tick 参数由 CLI 按旧实现的 `100 × tickSpacing` 透传给 SDK。 -- V3 单边金额:补齐 SDK 的区间内自动配比逻辑,只传 `amount0` 或 `amount1` 时仍能生成非零流动性;CLI 在 token 输入顺序与池顺序相反时同步交换 amount/minimum,保持旧参数语义。 -- V3 increase:显式 `amount0Min=1`、`amount1Min=1`,仓位流动性由 `177636846` 增至 `195400530`。 -- V3 decrease:显式 `amount0Min=1`、`amount1Min=1`,移除 `10000000` 后仓位流动性精确变为 `185400530`。 -- 所有写交易均在 Nile 成功上链;密钥、临时执行脚本和原始签名交易均未写入仓库。 - -## 5. 自动化测试 - -### 5.1 单元测试 - -- SDK 网络选择和非法网络拒绝。 -- `agent-wallet` 到 `WalletAdapter` 的地址、交易、消息和 typed-data 映射。 -- SDK 错误到 CLI 错误码映射。 -- SDK 多步交易结果到 CLI `txid` 的映射。 -- TRX/WTRX 常量和网络地址。 -- Wallet、Contract、Swap 和 SunPump 命令是否调用正确的公网 SDK namespace。 - -### 5.2 契约测试 - -- 根命令及子命令 help 清单不变。 -- JSON 输出、错误输出和关键 golden output 不变。 -- 生产代码和 `package.json` 不允许重新引入 `@sun-sdk/*` 或 `sun-kit`。 -- 五个公网 SDK 直接依赖必须固定到已验证版本。 - -### 5.3 CI 门禁 - -Node 20 和 22 均执行: - -1. `npm ci` -2. `npm run build` -3. `npm test` - -另外执行 TypeScript 类型检查和 ESLint。`build` 必须先清理 `dist`,防止已删除的私有 SDK/E2E 产物进入 npm 包。 - -## 6. 人工自测用例 - -### 6.1 无钱包、无资金用例 - -| 场景 | 示例 | 期望 | -| ---------- | ------------------------------------------------------- | ------------------------------------- | -| CLI 契约 | `sun --help`、各命令 `--help` | 命令和参数保持兼容 | -| API 查询 | `sun price TRX --network nile --json` | 返回合法 JSON | -| Pool 查询 | `sun pool list --network nile --json` | API 正常、分页正常 | -| Swap 报价 | `sun swap:quote TRX USDT 1000000 --network nile --json` | 返回可解析路由 | -| 合约读取 | 带 ABI 执行 `contract read` | 使用 runtime contract client 返回结果 | -| 钱包缺失 | 执行任一写命令 | 确认前给出明确钱包错误,不广播交易 | -| Dry run | 任一写命令加 `--dry-run --json` | 只输出动作与参数,不创建 SDK 交易 | -| 非支持网络 | SDK 命令加 `--network shasta` | 返回明确的 unsupported network 错误 | - -### 6.2 Nile 小额写入用例 - -在隔离测试钱包、限额资金和显式 `--yes` 下执行: - -1. Contract send:最小可验证合约写入。 -2. Swap:TRX/WTRX 与常用 TRC20 的小额交换。 -3. V2:add 后 remove,校验 LP 和余额变化。 -4. V3:mint、increase、collect、decrease,校验 position 状态。 -5. V4:已有池 mint/increase/collect/decrease;新池创建单独验证。 -6. SunPump:quote-buy/quote-sell,再做允许范围内的小额 buy/sell。 - -每条写入验证:退出码为 0、输出包含 `txid`、Tronscan 可查询、交易 receipt 成功、前后余额或 position 状态符合预期。失败用例至少覆盖余额不足、滑点过低、无路由、无授权和过期 deadline。 - -真实网络 E2E 只用于发布前临时验证,脚本、密钥和运行产物保留在仓库外。 - -## 7. 发布与回滚 - -1. 在干净环境执行 `npm ci`、build、lint、test 和 `npm pack --dry-run`。 -2. 检查 tarball 中不存在旧 `sun-kit`、私有 `@sun-sdk/*`、E2E 命令或测试报告。 -3. Nile 完成小额冒烟后先发布 beta,观察 API/交易错误。 -4. mainnet 只做受控小额冒烟,通过后再提升 latest。 -5. 回滚以 npm dist-tag 指回上一稳定版为主;依赖已精确锁定,可复现本次构建。 - -### 7.1 当前依赖审计备注 - -截至 2026-10-08,已将 TronWeb 从锁文件中的 6.2.2 升级到 6.5.1,清除了旧版 `ws`、`form-data` 等高危链路。生产依赖审计仍报告 2 个 high,来源是 TronWeb 6.5.1 精确依赖的 Axios 1.18.0;`npm audit --force` 给出的方案会反向降级 TronWeb,不应采用。另有 SUN SDK V3 的 ethers v5/elliptic 低危无上游修复项。发布前需要跟踪 TronWeb 和 SUN SDK 的上游版本,而不是在 CLI 中强制覆盖不兼容的传递依赖。 From c14088f7c4169fee998d17fc610dddbbdba76c3f Mon Sep 17 00:00:00 2001 From: Steven Lin Date: Tue, 13 Oct 2026 18:01:04 +0800 Subject: [PATCH 05/12] chore: release v1.2.3-beta.0 --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 9cf1696..67a0ca9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@sun-protocol/sun-cli", - "version": "1.2.2", + "version": "1.2.3-beta.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@sun-protocol/sun-cli", - "version": "1.2.2", + "version": "1.2.3-beta.0", "license": "MIT", "dependencies": { "@bankofai/agent-wallet": "^2.3.0", diff --git a/package.json b/package.json index 10c202d..9b2c6b7 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@sun-protocol/sun-cli", - "version": "1.2.2", + "version": "1.2.3-beta.0", "description": "CLI tool for SUN.IO / SUNSWAP on TRON — for humans and AI agents", "main": "dist/bin.js", "type": "commonjs", From 11cd1206ef2e7a13c0213d42c9fd069a006684e7 Mon Sep 17 00:00:00 2001 From: Felix Date: Tue, 13 Oct 2026 19:47:31 +0800 Subject: [PATCH 06/12] fix: preserve cli output compatibility --- src/commands/liquidity.ts | 9 ++++-- src/commands/wallet.ts | 2 +- test/commands/liquidity-sdk.test.ts | 37 +++++++++++++++++++++++ test/commands/sdk-write-migration.test.ts | 2 +- 4 files changed, 45 insertions(+), 5 deletions(-) diff --git a/src/commands/liquidity.ts b/src/commands/liquidity.ts index 7127a4e..5dffbee 100644 --- a/src/commands/liquidity.ts +++ b/src/commands/liquidity.ts @@ -642,7 +642,7 @@ export function registerLiquidityCommands(program: Command) { summary: { Token0: `${token0Display}${token0Note} (${token0})`, Token1: `${token1Display}${token1Note} (${token1})`, - Fee: fee, + Fee: opts.fee || '3000 (default)', 'Tick Range': `[${tickLower}, ${tickUpper}]`, Amount0: opts.amount0 || '(auto)', Amount1: opts.amount1 || '(auto)', @@ -897,11 +897,14 @@ export function registerLiquidityCommands(program: Command) { }), transform: (result: any) => { if (!result) return { error: 'Position not found' } + const fee = result.poolKey?.fee return { currency0: result.poolKey?.currency0, currency1: result.poolKey?.currency1, - fee: result.poolKey?.fee, - tickSpacing: result.poolKey?.tickSpacing, + fee: fee === undefined ? undefined : String(fee), + tickSpacing: + result.poolKey?.tickSpacing ?? + (Number.isFinite(Number(fee)) ? tickSpacingForFee(Number(fee)) : undefined), tickLower: result.tickLower, tickUpper: result.tickUpper, liquidity: result.liquidity, diff --git a/src/commands/wallet.ts b/src/commands/wallet.ts index 55c1528..1316bbe 100644 --- a/src/commands/wallet.ts +++ b/src/commands/wallet.ts @@ -44,10 +44,10 @@ export function registerWalletCommands(program: Command) { tokens: tokenList, }) return balances.map((item) => ({ + address: owner, type: String(item.token).toUpperCase() === 'TRX' ? 'TRX' : 'TRC20', tokenAddress: String(item.token).toUpperCase() === 'TRX' ? undefined : item.token, balance: item.balance, - decimals: item.decimals, })) }, tableConfig: { diff --git a/test/commands/liquidity-sdk.test.ts b/test/commands/liquidity-sdk.test.ts index a638733..9c77828 100644 --- a/test/commands/liquidity-sdk.test.ts +++ b/test/commands/liquidity-sdk.test.ts @@ -163,6 +163,7 @@ describe('liquidity SDK integration', () => { await expect( action.execute({ liquidity: { v4: { add: { execute } } } }), ).resolves.toMatchObject({ txid: 'mint-tx' }) + expect(action.summary.Fee).toBe('3000 (default)') expect(execute).toHaveBeenCalledWith( expect.objectContaining({ fee: 3000, @@ -176,6 +177,42 @@ describe('liquidity SDK integration', () => { expect(execute.mock.calls[0][0]).not.toHaveProperty('recipient') }) + it('preserves the V4 info output shape while using corrected SDK values', async () => { + const readAction = jest.fn().mockResolvedValue(undefined) + jest.doMock('../../src/lib/command', () => ({ + writeAction: jest.fn().mockResolvedValue(undefined), + readAction, + })) + jest.doMock('../../src/lib/context', () => ({ getNetwork: () => 'nile' })) + + const { registerLiquidityCommands } = require('../../src/commands/liquidity') + const program = new Command() + program.exitOverride() + registerLiquidityCommands(program) + await program.parseAsync( + ['node', 'sun', 'liquidity', 'v4:info', '--pm', 'TPositionManager', '--token-id', '7'], + { from: 'node' }, + ) + + const action = readAction.mock.calls[0][0] + expect( + action.transform({ + poolKey: { currency0: 'TToken0', currency1: 'TToken1', fee: 500 }, + tickLower: -120, + tickUpper: 120, + liquidity: '199999', + }), + ).toEqual({ + currency0: 'TToken0', + currency1: 'TToken1', + fee: '500', + tickSpacing: 10, + tickLower: -120, + tickUpper: 120, + liquidity: '199999', + }) + }) + it('lets the SDK derive V4 pool context from tokenId for increase', async () => { const action = await captureWriteAction([ 'v4:increase', diff --git a/test/commands/sdk-write-migration.test.ts b/test/commands/sdk-write-migration.test.ts index 2fda5f0..435e560 100644 --- a/test/commands/sdk-write-migration.test.ts +++ b/test/commands/sdk-write-migration.test.ts @@ -34,7 +34,7 @@ describe('SDK command migrations', () => { const tronClient = { kind: 'tron-client' } const runtime = { tronClient } await expect(opts.execute({ runtime })).resolves.toEqual([ - { type: 'TRX', tokenAddress: undefined, balance: '100', decimals: 6 }, + { address: 'TOwner', type: 'TRX', tokenAddress: undefined, balance: '100' }, ]) expect(readBalances).toHaveBeenCalledWith(tronClient, { owner: 'TOwner', From 30be50c02b129be6093ce2675d1aef8abe8dd1a8 Mon Sep 17 00:00:00 2001 From: Felix Date: Thu, 15 Oct 2026 15:49:14 +0800 Subject: [PATCH 07/12] fix: preserve SunPump mainnet-only behavior --- src/commands/sunpump.ts | 23 ++++++++++++---- test/commands/sunpump-sdk.test.ts | 46 ++++++++++++++++--------------- 2 files changed, 42 insertions(+), 27 deletions(-) diff --git a/src/commands/sunpump.ts b/src/commands/sunpump.ts index 31fc6b4..69ffadb 100644 --- a/src/commands/sunpump.ts +++ b/src/commands/sunpump.ts @@ -34,12 +34,20 @@ interface PumpActionOpts { detailFor?: (data: any) => Record | null } -function getSunPumpApi(): SunPumpApiClient { +function assertMainnet(): void { const network = getNetwork() - if (network !== 'mainnet' && network !== 'nile') { - throw new Error(`Unsupported SDK network: ${network}`) + if (network !== 'mainnet') { + const error = new Error( + `SunPump is only available on mainnet (got "${network}"). Drop --network or pass --network mainnet.`, + ) + ;(error as Error & { code: string }).code = 'SDK_UNSUPPORTED_NETWORK' + throw error } - return new SunPumpApiClient({ network }) +} + +function getSunPumpApi(): SunPumpApiClient { + assertMainnet() + return new SunPumpApiClient({ network: 'mainnet' }) } async function pumpAction(opts: PumpActionOpts): Promise { @@ -279,7 +287,12 @@ const portfolioTable = { // --------------------------------------------------------------------------- export function registerSunpumpCommands(program: Command) { - const sp = program.command('sunpump').description('SunPump endpoints (mainnet and nile).') + const sp = program + .command('sunpump') + .description('SunPump endpoints (mainnet only: api-v2.sunpump.meme).') + .hook('preAction', () => { + assertMainnet() + }) // -------------------------- token group ---------------------------------- const token = sp.command('token').description('Token info, search, holders, ranking') diff --git a/test/commands/sunpump-sdk.test.ts b/test/commands/sunpump-sdk.test.ts index b7218d1..2ba8fa1 100644 --- a/test/commands/sunpump-sdk.test.ts +++ b/test/commands/sunpump-sdk.test.ts @@ -6,7 +6,7 @@ describe('sunpump SDK integration', () => { jest.clearAllMocks() }) - it('allows Nile search-v2 and initializes the SDK API client for nile', async () => { + it('rejects every SunPump subcommand on Nile before initializing an SDK client', async () => { const searchTokensV2 = jest.fn().mockResolvedValue({ code: 0, data: { list: [] } }) const SunPumpApiClient = jest.fn().mockImplementation(() => ({ searchTokensV2, @@ -15,9 +15,10 @@ describe('sunpump SDK integration', () => { const printPaginationFooter = jest.fn() jest.doMock('@sun-protocol/sun-sdk-api', () => ({ SunPumpApiClient })) + const getKit = jest.fn() jest.doMock('../../src/lib/context', () => ({ getNetwork: () => 'nile', - getKit: jest.fn(), + getKit, })) jest.doMock('../../src/lib/output', () => ({ output, @@ -34,27 +35,28 @@ describe('sunpump SDK integration', () => { })) const { registerSunpumpCommands } = require('../../src/commands/sunpump') - const program = new Command() - program.exitOverride() - registerSunpumpCommands(program) + const commands = [ + ['sunpump', 'token', 'search-v2', 'SUN', '--page', '1', '--size', '36'], + ['sunpump', 'quote-buy', 'TTestToken', '--trx', '1'], + ['sunpump', 'buy', 'TTestToken', '--trx', '1'], + ] - await program.parseAsync( - ['node', 'sun', 'sunpump', 'token', 'search-v2', 'SUN', '--page', '1', '--size', '36'], - { from: 'node' }, - ) + for (const args of commands) { + const program = new Command() + program.exitOverride() + registerSunpumpCommands(program) - expect(SunPumpApiClient).toHaveBeenCalledWith({ network: 'nile' }) - expect(searchTokensV2).toHaveBeenCalledWith({ - query: 'SUN', - onSunSwap: undefined, - filterDliveShowing: undefined, - filterAiHelper: undefined, - filterTwitterLaunch: undefined, - filterSunAgentLaunch: undefined, - page: 1, - size: 36, - sort: undefined, - }) - expect(output).toHaveBeenCalledWith({ list: [] }, expect.any(Object)) + await expect( + program.parseAsync(['node', 'sun', ...args], { from: 'node' }), + ).rejects.toMatchObject({ + code: 'SDK_UNSUPPORTED_NETWORK', + message: expect.stringContaining('SunPump is only available on mainnet'), + }) + } + + expect(SunPumpApiClient).not.toHaveBeenCalled() + expect(searchTokensV2).not.toHaveBeenCalled() + expect(getKit).not.toHaveBeenCalled() + expect(output).not.toHaveBeenCalled() }) }) From 0a5d4237e40eafccdff89dbbced3f99fcdca4094 Mon Sep 17 00:00:00 2001 From: Steven Lin Date: Thu, 15 Oct 2026 15:58:40 +0800 Subject: [PATCH 08/12] chore: release v1.2.3-beta.1 Co-Authored-By: Claude Opus 5.5 --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 67a0ca9..0aa9c4e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@sun-protocol/sun-cli", - "version": "1.2.3-beta.0", + "version": "1.2.3-beta.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@sun-protocol/sun-cli", - "version": "1.2.3-beta.0", + "version": "1.2.3-beta.1", "license": "MIT", "dependencies": { "@bankofai/agent-wallet": "^2.3.0", diff --git a/package.json b/package.json index 9b2c6b7..336db38 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@sun-protocol/sun-cli", - "version": "1.2.3-beta.0", + "version": "1.2.3-beta.1", "description": "CLI tool for SUN.IO / SUNSWAP on TRON — for humans and AI agents", "main": "dist/bin.js", "type": "commonjs", From 4d7dfb9fe5f7aace4fc7cd04806420ca19374aa5 Mon Sep 17 00:00:00 2001 From: Felix Date: Sun, 25 Oct 2026 15:37:23 +0800 Subject: [PATCH 09/12] feat: remove dependabot configuration --- .github/dependabot.yml | 18 ------------------ 1 file changed, 18 deletions(-) delete mode 100644 .github/dependabot.yml diff --git a/.github/dependabot.yml b/.github/dependabot.yml deleted file mode 100644 index 99c16bd..0000000 --- a/.github/dependabot.yml +++ /dev/null @@ -1,18 +0,0 @@ -version: 2 -updates: - - package-ecosystem: 'npm' - directory: '/' - schedule: - interval: 'weekly' - open-pull-requests-limit: 10 - groups: - dev-dependencies: - dependency-type: 'development' - update-types: - - 'minor' - - 'patch' - - - package-ecosystem: 'github-actions' - directory: '/' - schedule: - interval: 'weekly' From c4f2072209ac8d783fa96c0acde8c75f492d9bb3 Mon Sep 17 00:00:00 2001 From: Felix Date: Mon, 26 Oct 2026 16:14:16 +0800 Subject: [PATCH 10/12] fix: preserve V4 defaults and update supported Node versions --- .github/workflows/ci.yml | 8 +- README.md | 4 +- package-lock.json | 4 +- package.json | 4 +- src/commands/liquidity.ts | 65 ++++++----- src/lib/sdk/constants.ts | 5 + src/lib/sdk/liquidity-v4.ts | 82 +++++++++++++ test/commands/liquidity-sdk.test.ts | 74 +++++++++++- test/contract/sdk-dependency-boundary.test.ts | 2 + test/lib/sdk/liquidity-v4.test.ts | 108 ++++++++++++++++++ 10 files changed, 313 insertions(+), 43 deletions(-) create mode 100644 src/lib/sdk/liquidity-v4.ts create mode 100644 test/lib/sdk/liquidity-v4.test.ts diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 05780b3..ecc5ef3 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -12,8 +12,8 @@ jobs: strategy: matrix: - # The public SUN SDK requires Node.js 20 or newer. - node-version: [20.x, 22.x] + # Use the latest patches of the supported LTS release lines. + node-version: [22.x, 24.x] steps: - name: Checkout repository @@ -23,6 +23,7 @@ jobs: uses: actions/setup-node@v4 with: node-version: ${{ matrix.node-version }} + check-latest: true cache: 'npm' - name: Install dependencies @@ -44,7 +45,8 @@ jobs: - name: Setup Node.js uses: actions/setup-node@v4 with: - node-version: '20.x' + node-version: '24.x' + check-latest: true cache: 'npm' - name: Install dependencies diff --git a/README.md b/README.md index 4397ba6..51c10a3 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ ![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg) ![Network](https://img.shields.io/badge/Network-TRON-red) -![Node.js](https://img.shields.io/badge/Node.js-20%2B-339933) +![Node.js](https://img.shields.io/badge/Node.js-22%20%7C%2024%20LTS-339933) ![TypeScript](https://img.shields.io/badge/TypeScript-5.x-3178C6) > A CLI for AI-driven and human-operated DeFi workflows on TRON via the **SUN.IO / SUNSWAP** ecosystem. @@ -57,7 +57,7 @@ npm install -g @sun-protocol/sun-cli ``` -Requires Node.js **20+**. +Requires Node.js **22.23.4+ (22.x)** or **24.22.1+ (24.x)**. Use the latest patch release of either supported LTS line. --- diff --git a/package-lock.json b/package-lock.json index 0aa9c4e..2eb78fa 100644 --- a/package-lock.json +++ b/package-lock.json @@ -17,6 +17,8 @@ "@sun-protocol/sun-sdk-core": "0.1.1-beta.0", "@sun-protocol/sun-sdk-protocols": "0.1.1-beta.0", "@sun-protocol/sun-sdk-runtime": "0.1.1-beta.0", + "@sun-protocol/sun-sdk-sunswap-v3": "0.1.1-beta.0", + "@sun-protocol/sun-sdk-sunswap-v4": "0.1.1-beta.0", "chalk": "^4.1.2", "cli-table3": "^0.6.5", "commander": "^12.1.0", @@ -42,7 +44,7 @@ "typescript-eslint": "^8.56.1" }, "engines": { - "node": ">=20" + "node": "^22.23.4 || ^24.22.1" } }, "node_modules/@adraffy/ens-normalize": { diff --git a/package.json b/package.json index 336db38..6056dae 100644 --- a/package.json +++ b/package.json @@ -38,7 +38,7 @@ ], "license": "MIT", "engines": { - "node": ">=20" + "node": "^22.23.4 || ^24.22.1" }, "overrides": { "utf-8-validate": "5.0.10" @@ -52,6 +52,8 @@ "@sun-protocol/sun-sdk-core": "0.1.1-beta.0", "@sun-protocol/sun-sdk-protocols": "0.1.1-beta.0", "@sun-protocol/sun-sdk-runtime": "0.1.1-beta.0", + "@sun-protocol/sun-sdk-sunswap-v3": "0.1.1-beta.0", + "@sun-protocol/sun-sdk-sunswap-v4": "0.1.1-beta.0", "chalk": "^4.1.2", "cli-table3": "^0.6.5", "commander": "^12.1.0", diff --git a/src/commands/liquidity.ts b/src/commands/liquidity.ts index 5dffbee..e82b9a9 100644 --- a/src/commands/liquidity.ts +++ b/src/commands/liquidity.ts @@ -6,9 +6,16 @@ import { resolveTokenAddress, getSymbolOrAddress } from '../lib/tokens' import { getContractAddress } from '@sun-protocol/sun-sdk-chains' import type { Network } from '@sun-protocol/sun-sdk-core' import { TronWeb } from 'tronweb' -import { TRX_ADDRESS, WTRX_MAINNET, WTRX_NILE } from '../lib/sdk/constants' +import { + DEFAULT_V3_FEE, + DEFAULT_V4_FEE, + TRX_ADDRESS, + WTRX_MAINNET, + WTRX_NILE, +} from '../lib/sdk/constants' import { createReadonlyTronWeb } from '../lib/sdk/factory' import { toCliTxResult } from '../lib/sdk/compat' +import { resolveV4MintTicks, v4TickSpacingForFee } from '../lib/sdk/liquidity-v4' const SUNSWAP_V2_FACTORY_MIN_ABI = [ { @@ -44,13 +51,6 @@ const SUNSWAP_V2_PAIR_MIN_ABI = [ }, ] -const TICK_SPACING_BY_FEE: Record = { - 100: 1, - 500: 10, - 3000: 60, - 10000: 200, -} - function assertSdkNetwork(network: string): Network { if (network === 'mainnet' || network === 'nile') return network throw new Error(`Unsupported SDK network: ${network}`) @@ -74,10 +74,6 @@ function percentFromDecimal(value: string | undefined): `${number}%` | undefined return value === undefined ? undefined : (`${parseFloat(value) * 100}%` as `${number}%`) } -function tickSpacingForFee(fee: number): number { - return TICK_SPACING_BY_FEE[fee] ?? 60 -} - function toV3PoolOrder(token0: string, token1: string, value0: T, value1: T): [T, T] { const token0Hex = TronWeb.address.toHex(token0).toLowerCase() const token1Hex = TronWeb.address.toHex(token1).toLowerCase() @@ -355,7 +351,7 @@ export function registerLiquidityCommands(program: Command) { .option('--pm
', 'V3 NonfungiblePositionManager address (auto-detected by network)') .requiredOption('--token0 ', 'Token0 (symbol like TRX/USDT or address)') .requiredOption('--token1 ', 'Token1 (symbol like TRX/USDT or address)') - .option('--fee ', 'Pool fee tier (default: 3000)') + .option('--fee ', `Pool fee tier (default: ${DEFAULT_V3_FEE})`) .option('--tick-lower ', 'Lower tick (auto-computed if omitted)') .option('--tick-upper ', 'Upper tick (auto-computed if omitted)') .option('--amount0 ', 'Desired amount of token0') @@ -404,7 +400,7 @@ export function registerLiquidityCommands(program: Command) { 'Position Manager': pm, Token0: `${token0Display}${token0Note} (${token0})`, Token1: `${token1Display}${token1Note} (${token1})`, - Fee: opts.fee || '3000 (default)', + Fee: opts.fee || `${DEFAULT_V3_FEE} (default)`, 'Tick Range': tickRange, Amount0: opts.amount0 || '(auto)', Amount1: opts.amount1 || '(auto)', @@ -419,7 +415,7 @@ export function registerLiquidityCommands(program: Command) { contracts: withContracts({ sunswapV3PositionManager: pm }), tokenA: token0, tokenB: token1, - fee: opts.fee ? parseInt(opts.fee) : 3000, + fee: opts.fee ? parseInt(opts.fee) : DEFAULT_V3_FEE, tickLower: opts.tickLower ? parseInt(opts.tickLower) : undefined, tickUpper: opts.tickUpper ? parseInt(opts.tickUpper) : undefined, tickRangeWidth: @@ -596,7 +592,7 @@ export function registerLiquidityCommands(program: Command) { .description('Mint a new SUNSWAP V4 concentrated liquidity position') .requiredOption('--token0 ', 'Token0 (symbol like TRX/USDT or address)') .requiredOption('--token1 ', 'Token1 (symbol like TRX/USDT or address)') - .option('--fee ', 'Pool fee tier (e.g. 500)') + .option('--fee ', `Pool fee tier (default: ${DEFAULT_V4_FEE})`) .option('--tick-lower ', 'Lower tick (auto-computed if omitted)') .option('--tick-upper ', 'Upper tick (auto-computed if omitted)') .option('--amount0 ', 'Desired amount of token0') @@ -626,10 +622,9 @@ export function registerLiquidityCommands(program: Command) { const token1Display = getSymbolOrAddress(token1Input, network) const token0Note = token0 !== token0Input ? ` → WTRX` : '' const token1Note = token1 !== token1Input ? ` → WTRX` : '' - const fee = opts.fee ? parseInt(opts.fee) : 3000 - const tickSpacing = tickSpacingForFee(fee) - const tickLower = opts.tickLower ? parseInt(opts.tickLower) : -2 * tickSpacing - const tickUpper = opts.tickUpper ? parseInt(opts.tickUpper) : 2 * tickSpacing + const fee = opts.fee ? parseInt(opts.fee) : DEFAULT_V4_FEE + const tickLower = opts.tickLower !== undefined ? parseInt(opts.tickLower) : undefined + const tickUpper = opts.tickUpper !== undefined ? parseInt(opts.tickUpper) : undefined if (!opts.amount0 && !opts.amount1) { console.error('Error: At least one of --amount0 or --amount1 is required') @@ -642,8 +637,11 @@ export function registerLiquidityCommands(program: Command) { summary: { Token0: `${token0Display}${token0Note} (${token0})`, Token1: `${token1Display}${token1Note} (${token1})`, - Fee: opts.fee || '3000 (default)', - 'Tick Range': `[${tickLower}, ${tickUpper}]`, + Fee: opts.fee || `${DEFAULT_V4_FEE} (default)`, + 'Tick Range': + tickLower !== undefined && tickUpper !== undefined + ? `[${tickLower}, ${tickUpper}]` + : '(auto)', Amount0: opts.amount0 || '(auto)', Amount1: opts.amount1 || '(auto)', Slippage: opts.slippage @@ -655,24 +653,31 @@ export function registerLiquidityCommands(program: Command) { confirmMsg: 'Mint V4 position?', spinnerLabel: 'Minting V4 position...', errorLabel: 'V4 mint failed', - execute: (sdk) => - sdk.liquidity.v4.add + execute: async (sdk) => { + const ticks = await resolveV4MintTicks(sdk, { + token0, + token1, + fee, + tickLower, + tickUpper, + sqrtPriceX96: opts.sqrtPrice, + createPool: opts.createPool, + }) + return sdk.liquidity.v4.add .execute({ token0, token1, fee, - tickSpacing, - tickLower, - tickUpper, + ...ticks, amount0: opts.amount0, amount1: opts.amount1, slippage: percentFromDecimal(opts.slippage), owner: opts.recipient, deadline: opts.deadline, sqrtPriceX96: opts.sqrtPrice, - createPool: opts.createPool, }) - .then(mapTx), + .then(mapTx) + }, summarizeResult: (result: any) => { const out: Record = {} if (result?.poolCreated !== undefined) out['Pool Created'] = result.poolCreated @@ -904,7 +909,7 @@ export function registerLiquidityCommands(program: Command) { fee: fee === undefined ? undefined : String(fee), tickSpacing: result.poolKey?.tickSpacing ?? - (Number.isFinite(Number(fee)) ? tickSpacingForFee(Number(fee)) : undefined), + (Number.isFinite(Number(fee)) ? v4TickSpacingForFee(Number(fee)) : undefined), tickLower: result.tickLower, tickUpper: result.tickUpper, liquidity: result.liquidity, diff --git a/src/lib/sdk/constants.ts b/src/lib/sdk/constants.ts index 9df90d5..7f7a339 100644 --- a/src/lib/sdk/constants.ts +++ b/src/lib/sdk/constants.ts @@ -4,3 +4,8 @@ import { TRON_ZERO_ADDRESS } from '@sun-protocol/sun-sdk-core' export const TRX_ADDRESS = TRON_ZERO_ADDRESS export const WTRX_MAINNET = getWrappedNativeAddress('mainnet') export const WTRX_NILE = getWrappedNativeAddress('nile') + +// Preserve the fee defaults of the pre-migration SunKit implementation. +// Fees are expressed in millionths: 3000 = 0.3%, 500 = 0.05%. +export const DEFAULT_V3_FEE = 3000 +export const DEFAULT_V4_FEE = 500 diff --git a/src/lib/sdk/liquidity-v4.ts b/src/lib/sdk/liquidity-v4.ts new file mode 100644 index 0000000..517f12b --- /dev/null +++ b/src/lib/sdk/liquidity-v4.ts @@ -0,0 +1,82 @@ +import { Fraction } from '@sun-protocol/sun-sdk-core' +import { nearestUsableTick, TickMath } from '@sun-protocol/sun-sdk-sunswap-v3' +import { + computeV4PoolId, + createV4PoolKey, + encodeV4PoolParameters, +} from '@sun-protocol/sun-sdk-sunswap-v4' +import type { CliSunSDK } from './types' + +const DEFAULT_TICK_RANGE_FACTOR = 100 + +const TICK_SPACING_BY_FEE: Record = { + 100: 1, + 500: 10, + 3000: 60, + 10000: 200, +} + +export function v4TickSpacingForFee(fee: number): number { + return TICK_SPACING_BY_FEE[fee] ?? 10 +} + +interface V4MintTickInput { + token0: string + token1: string + fee: number + tickLower?: number + tickUpper?: number + sqrtPriceX96?: string + createPool?: boolean +} + +/** Resolve omitted V4 mint ticks from the pool state, as SunKit did. */ +export async function resolveV4MintTicks(sdk: CliSunSDK, input: V4MintTickInput) { + const tickSpacing = v4TickSpacingForFee(input.fee) + if (input.tickLower !== undefined && input.tickUpper !== undefined) { + return { + tickSpacing, + tickLower: input.tickLower, + tickUpper: input.tickUpper, + createPool: input.createPool, + } + } + + const poolKey = createV4PoolKey({ + network: sdk.network, + currencyA: input.token0, + currencyB: input.token1, + fee: input.fee, + parameters: encodeV4PoolParameters({ tickSpacing }), + }) + + let currentTick: number + let createPool = input.createPool + try { + const pool = await sdk.liquidity.v4.read({ poolId: computeV4PoolId(poolKey) }) + if (BigInt(pool.sqrtPriceX96) === 0n) throw new Error('V4 pool is not initialized') + currentTick = pool.tick + } catch (cause) { + if (input.sqrtPriceX96 === undefined) { + throw Object.assign( + new Error( + `V4 pool not found for ${input.token0}/${input.token1} fee=${input.fee}. Provide sqrtPriceX96 to create a new pool.`, + ), + { code: 'POOL_NOT_FOUND', cause }, + ) + } + currentTick = TickMath.getTickAtSqrtRatio(new Fraction(input.sqrtPriceX96).quotient) + createPool = true + } + + return { + tickSpacing, + tickLower: + input.tickLower ?? + nearestUsableTick(currentTick - DEFAULT_TICK_RANGE_FACTOR * tickSpacing, tickSpacing), + tickUpper: + input.tickUpper ?? + nearestUsableTick(currentTick + DEFAULT_TICK_RANGE_FACTOR * tickSpacing, tickSpacing), + createPool, + } +} diff --git a/test/commands/liquidity-sdk.test.ts b/test/commands/liquidity-sdk.test.ts index 9c77828..d234ea9 100644 --- a/test/commands/liquidity-sdk.test.ts +++ b/test/commands/liquidity-sdk.test.ts @@ -159,17 +159,21 @@ describe('liquidity SDK integration', () => { const execute = jest.fn().mockResolvedValue({ finalResult: { type: 'transaction', txid: 'mint-tx', raw: { result: true } }, }) + const read = jest + .fn() + .mockResolvedValue({ sqrtPriceX96: '79228162514264337593543950336', tick: 12347 }) await expect( - action.execute({ liquidity: { v4: { add: { execute } } } }), + action.execute({ network: 'nile', liquidity: { v4: { read, add: { execute } } } }), ).resolves.toMatchObject({ txid: 'mint-tx' }) - expect(action.summary.Fee).toBe('3000 (default)') + expect(action.summary.Fee).toBe('500 (default)') + expect(action.summary['Tick Range']).toBe('(auto)') expect(execute).toHaveBeenCalledWith( expect.objectContaining({ - fee: 3000, - tickSpacing: 60, - tickLower: -120, - tickUpper: 120, + fee: 500, + tickSpacing: 10, + tickLower: 11350, + tickUpper: 13350, amount0: '100', owner: 'TOwner', }), @@ -177,6 +181,64 @@ describe('liquidity SDK integration', () => { expect(execute.mock.calls[0][0]).not.toHaveProperty('recipient') }) + it('preserves explicit V4 fees and tick zero without an automatic pool read', async () => { + const action = await captureWriteAction([ + 'v4:mint', + '--token0', + 'SUN', + '--token1', + 'USDT', + '--amount0', + '100', + '--amount1', + '200', + '--fee', + '3000', + '--tick-lower', + '0', + '--tick-upper', + '120', + ]) + const execute = jest.fn().mockResolvedValue({ txids: ['mint-explicit-tx'] }) + const read = jest.fn() + + await action.execute({ network: 'nile', liquidity: { v4: { read, add: { execute } } } }) + + expect(read).not.toHaveBeenCalled() + expect(action.summary.Fee).toBe('3000') + expect(action.summary['Tick Range']).toBe('[0, 120]') + expect(execute).toHaveBeenCalledWith( + expect.objectContaining({ + fee: 3000, + tickSpacing: 60, + tickLower: 0, + tickUpper: 120, + }), + ) + }) + + it('does not submit V4 mint when automatic ticks cannot be resolved', async () => { + const action = await captureWriteAction([ + 'v4:mint', + '--token0', + 'SUN', + '--token1', + 'USDT', + '--amount0', + '100', + ]) + const execute = jest.fn() + const read = jest.fn().mockRejectedValue(new Error('Pool unavailable')) + + await expect( + action.execute({ + network: 'nile', + liquidity: { v4: { read, add: { execute } } }, + }), + ).rejects.toMatchObject({ code: 'POOL_NOT_FOUND' }) + expect(execute).not.toHaveBeenCalled() + }) + it('preserves the V4 info output shape while using corrected SDK values', async () => { const readAction = jest.fn().mockResolvedValue(undefined) jest.doMock('../../src/lib/command', () => ({ diff --git a/test/contract/sdk-dependency-boundary.test.ts b/test/contract/sdk-dependency-boundary.test.ts index 54ca9ec..9f8a750 100644 --- a/test/contract/sdk-dependency-boundary.test.ts +++ b/test/contract/sdk-dependency-boundary.test.ts @@ -31,6 +31,8 @@ describe('public SUN SDK dependency boundary', () => { '@sun-protocol/sun-sdk-core', '@sun-protocol/sun-sdk-protocols', '@sun-protocol/sun-sdk-runtime', + '@sun-protocol/sun-sdk-sunswap-v3', + '@sun-protocol/sun-sdk-sunswap-v4', ] for (const name of sdkPackages) { diff --git a/test/lib/sdk/liquidity-v4.test.ts b/test/lib/sdk/liquidity-v4.test.ts new file mode 100644 index 0000000..a104fc9 --- /dev/null +++ b/test/lib/sdk/liquidity-v4.test.ts @@ -0,0 +1,108 @@ +import { TickMath } from '@sun-protocol/sun-sdk-sunswap-v3' +import { + computeV4PoolId, + createV4PoolKey, + encodeV4PoolParameters, +} from '@sun-protocol/sun-sdk-sunswap-v4' +import { resolveV4MintTicks, v4TickSpacingForFee } from '../../../src/lib/sdk/liquidity-v4' +import type { CliSunSDK } from '../../../src/lib/sdk/types' + +const token0 = 'TYsbWxNnyTgsZaTFaue9hqpxkU3Fkco94a' +const token1 = 'TXYZopYRdj2D9XRtbG411XZZ3kM5VkAeBf' +const input = { token0, token1, fee: 500 } + +function mockSdk(read: jest.Mock): CliSunSDK { + return { network: 'nile', liquidity: { v4: { read } } } as unknown as CliSunSDK +} + +describe('V4 mint automatic ticks', () => { + it.each([ + [500, 10, 12347, 11350, 13350], + [500, 10, -12347, -13350, -11350], + [3000, 60, 12000, 6000, 18000], + [100, 1, 0, -100, 100], + [10000, 200, 4000, -16000, 24000], + [2500, 10, 12347, 11350, 13350], + ])( + 'derives the legacy 100-spacing range for fee %i at tick %i', + async (fee, tickSpacing, tick, tickLower, tickUpper) => { + const read = jest.fn().mockResolvedValue({ sqrtPriceX96: '1', tick }) + const result = await resolveV4MintTicks(mockSdk(read), { ...input, fee }) + + expect(result).toEqual({ tickSpacing, tickLower, tickUpper, createPool: undefined }) + expect(read).toHaveBeenCalledWith({ + poolId: computeV4PoolId( + createV4PoolKey({ + network: 'nile', + currencyA: token0, + currencyB: token1, + fee, + parameters: encodeV4PoolParameters({ tickSpacing }), + }), + ), + }) + }, + ) + + it('derives only the omitted bound and preserves an explicit zero', async () => { + const read = jest.fn().mockResolvedValue({ sqrtPriceX96: '1', tick: 1500 }) + expect(await resolveV4MintTicks(mockSdk(read), { ...input, tickLower: 0 })).toMatchObject({ + tickLower: 0, + tickUpper: 2500, + }) + expect(await resolveV4MintTicks(mockSdk(read), { ...input, tickUpper: 3000 })).toMatchObject({ + tickLower: 500, + tickUpper: 3000, + }) + }) + + it('uses the existing pool tick even when an initial price is also supplied', async () => { + const read = jest.fn().mockResolvedValue({ sqrtPriceX96: '1', tick: 1500 }) + expect( + await resolveV4MintTicks(mockSdk(read), { + ...input, + sqrtPriceX96: TickMath.getSqrtRatioAtTick(0).toString(), + createPool: true, + }), + ).toMatchObject({ tickLower: 500, tickUpper: 2500, createPool: true }) + }) + + it.each(['missing', 'uninitialized'])('uses the initial price for a %s pool', async (state) => { + const read = + state === 'missing' + ? jest.fn().mockRejectedValue(new Error('Pool unavailable')) + : jest.fn().mockResolvedValue({ sqrtPriceX96: '0', tick: 0 }) + const result = await resolveV4MintTicks(mockSdk(read), { + ...input, + sqrtPriceX96: TickMath.getSqrtRatioAtTick(1200).toString(), + }) + expect(result).toEqual({ tickSpacing: 10, tickLower: 200, tickUpper: 2200, createPool: true }) + }) + + it('fails if the pool is uninitialized and no initial price is available', async () => { + const read = jest.fn().mockResolvedValue({ sqrtPriceX96: '0', tick: 0 }) + await expect(resolveV4MintTicks(mockSdk(read), input)).rejects.toMatchObject({ + code: 'POOL_NOT_FOUND', + }) + }) + + it('does not turn a pool read failure into a static tick range', async () => { + const cause = new Error('RPC unavailable') + const read = jest.fn().mockRejectedValue(cause) + await expect(resolveV4MintTicks(mockSdk(read), input)).rejects.toMatchObject({ + code: 'POOL_NOT_FOUND', + cause, + }) + }) + + it('rejects an invalid initial sqrt price', async () => { + const read = jest.fn().mockRejectedValue(new Error('Pool unavailable')) + await expect( + resolveV4MintTicks(mockSdk(read), { ...input, sqrtPriceX96: '0' }), + ).rejects.toThrow() + }) + + it('keeps the legacy V4 tick-spacing fallback', () => { + expect(v4TickSpacingForFee(2500)).toBe(10) + }) +}) From 0eb0c55f5c9d20ed22744f9559f32d628c7b6faf Mon Sep 17 00:00:00 2001 From: Felix Date: Mon, 26 Oct 2026 17:13:08 +0800 Subject: [PATCH 11/12] chore: prepare v2.0.0-beta.0 --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 2eb78fa..22c345c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@sun-protocol/sun-cli", - "version": "1.2.3-beta.1", + "version": "2.0.0-beta.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@sun-protocol/sun-cli", - "version": "1.2.3-beta.1", + "version": "2.0.0-beta.0", "license": "MIT", "dependencies": { "@bankofai/agent-wallet": "^2.3.0", diff --git a/package.json b/package.json index 6056dae..7813c90 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@sun-protocol/sun-cli", - "version": "1.2.3-beta.1", + "version": "2.0.0-beta.0", "description": "CLI tool for SUN.IO / SUNSWAP on TRON — for humans and AI agents", "main": "dist/bin.js", "type": "commonjs", From c55868e165632f3f99f9deef6fab442988712ed6 Mon Sep 17 00:00:00 2001 From: Felix Date: Wed, 28 Oct 2026 14:50:46 +0800 Subject: [PATCH 12/12] fix: restore CLI compatibility after SUN SDK migration - restore ABI discovery, Shasta generic calls and custom ABI support - preserve SunPump min-out, API endpoint and transaction output - align V3/V4 liquidity behavior with pre-migration implementation - upgrade SUN SDK dependencies to 0.1.1-beta.1 - add compatibility regression tests --- package-lock.json | 124 ++++++------- package.json | 14 +- src/commands/contract.ts | 7 +- src/commands/liquidity.ts | 170 ++++++++++++++---- src/commands/sunpump.ts | 24 ++- src/commands/swap.ts | 22 ++- src/commands/wallet.ts | 2 +- src/lib/command.ts | 41 ++++- src/lib/context.ts | 20 ++- src/lib/sdk/compat.ts | 44 ++++- src/lib/sdk/constants.ts | 6 + src/lib/sdk/factory.ts | 60 ++++++- src/lib/sdk/liquidity-v3.ts | 71 ++++++++ src/lib/sdk/liquidity-v4.ts | 12 +- test/commands/liquidity-sdk.test.ts | 83 +++++++-- test/commands/sdk-write-migration.test.ts | 36 +++- test/commands/sunpump-sdk.test.ts | 77 ++++++++ test/contract/sdk-dependency-boundary.test.ts | 2 +- test/lib/sdk/compat.test.ts | 82 ++++++++- test/lib/sdk/factory.test.ts | 9 + test/lib/sdk/liquidity-v3.test.ts | 72 ++++++++ 21 files changed, 821 insertions(+), 157 deletions(-) create mode 100644 src/lib/sdk/liquidity-v3.ts create mode 100644 test/lib/sdk/liquidity-v3.test.ts diff --git a/package-lock.json b/package-lock.json index 22c345c..ee6f73b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,13 +12,13 @@ "@bankofai/agent-wallet": "^2.3.0", "@scure/bip32": "^2.0.1", "@scure/bip39": "^2.0.1", - "@sun-protocol/sun-sdk-api": "0.1.1-beta.0", - "@sun-protocol/sun-sdk-chains": "0.1.1-beta.0", - "@sun-protocol/sun-sdk-core": "0.1.1-beta.0", - "@sun-protocol/sun-sdk-protocols": "0.1.1-beta.0", - "@sun-protocol/sun-sdk-runtime": "0.1.1-beta.0", - "@sun-protocol/sun-sdk-sunswap-v3": "0.1.1-beta.0", - "@sun-protocol/sun-sdk-sunswap-v4": "0.1.1-beta.0", + "@sun-protocol/sun-sdk-api": "0.1.1-beta.1", + "@sun-protocol/sun-sdk-chains": "0.1.1-beta.1", + "@sun-protocol/sun-sdk-core": "0.1.1-beta.1", + "@sun-protocol/sun-sdk-protocols": "0.1.1-beta.1", + "@sun-protocol/sun-sdk-runtime": "0.1.1-beta.1", + "@sun-protocol/sun-sdk-sunswap-v3": "0.1.1-beta.1", + "@sun-protocol/sun-sdk-sunswap-v4": "0.1.1-beta.1", "chalk": "^4.1.2", "cli-table3": "^0.6.5", "commander": "^12.1.0", @@ -2590,32 +2590,32 @@ } }, "node_modules/@sun-protocol/sun-sdk-api": { - "version": "0.1.1-beta.0", - "resolved": "https://registry.npmjs.org/@sun-protocol/sun-sdk-api/-/sun-sdk-api-0.1.1-beta.0.tgz", - "integrity": "sha512-3XZib1T/BzQS1v+W3ZlY090OW27kz8YJVEP4Ztn002OjnxwD/98nq7NBXKKvgQrRRltJodUr+XeWwYE7GGQlCQ==", + "version": "0.1.1-beta.1", + "resolved": "https://registry.npmjs.org/@sun-protocol/sun-sdk-api/-/sun-sdk-api-0.1.1-beta.1.tgz", + "integrity": "sha512-kGxmCxe1pSSOtb4Zm1cuqrT/hRjIhao3dOPsM5shea/8Y0ZsPeggQFPH9zEcXa3/3gBIoSDvssqfH9ORkfdYzw==", "dependencies": { - "@sun-protocol/sun-sdk-chains": "0.1.1-beta.0", - "@sun-protocol/sun-sdk-core": "0.1.1-beta.0" + "@sun-protocol/sun-sdk-chains": "0.1.1-beta.1", + "@sun-protocol/sun-sdk-core": "0.1.1-beta.1" }, "engines": { "node": ">=20" } }, "node_modules/@sun-protocol/sun-sdk-chains": { - "version": "0.1.1-beta.0", - "resolved": "https://registry.npmjs.org/@sun-protocol/sun-sdk-chains/-/sun-sdk-chains-0.1.1-beta.0.tgz", - "integrity": "sha512-X1pcDF8+woHeweNzA43ZswV+k7ydfK50J61KqJcl7dNTJVV/ritVzv6DHDIbi4KjadyZY+lQMgkMS+Cot+mpHg==", + "version": "0.1.1-beta.1", + "resolved": "https://registry.npmjs.org/@sun-protocol/sun-sdk-chains/-/sun-sdk-chains-0.1.1-beta.1.tgz", + "integrity": "sha512-4ywzbFJksW1U7zgb7/MRxmj8CKnEgFRiaGobigfjV9BAeCIdPk6zNxrTs8z/z/p5iswXJgLAR5IsQvIFd1pT5w==", "dependencies": { - "@sun-protocol/sun-sdk-core": "0.1.1-beta.0" + "@sun-protocol/sun-sdk-core": "0.1.1-beta.1" }, "engines": { "node": ">=20" } }, "node_modules/@sun-protocol/sun-sdk-core": { - "version": "0.1.1-beta.0", - "resolved": "https://registry.npmjs.org/@sun-protocol/sun-sdk-core/-/sun-sdk-core-0.1.1-beta.0.tgz", - "integrity": "sha512-VzdMJyzx+59aetjkv7vq9HM38brulbuwhhM9ELxyVadtaou+i7OhOPK7J8G8JzRDzPbxuRjZpfcbI5QYycd5cA==", + "version": "0.1.1-beta.1", + "resolved": "https://registry.npmjs.org/@sun-protocol/sun-sdk-core/-/sun-sdk-core-0.1.1-beta.1.tgz", + "integrity": "sha512-WzQLbOtLJA7vwt3IGpMVi6PjYV1mdNtUkxMHSlscAICnQ41MErhkDuTqpK77ixVALKXHq9PemKdZk04NmacpYA==", "dependencies": { "@ethersproject/address": "^5.0.2", "@ethersproject/bignumber": "^5.5.0", @@ -2634,33 +2634,33 @@ } }, "node_modules/@sun-protocol/sun-sdk-launchpad": { - "version": "0.1.1-beta.0", - "resolved": "https://registry.npmjs.org/@sun-protocol/sun-sdk-launchpad/-/sun-sdk-launchpad-0.1.1-beta.0.tgz", - "integrity": "sha512-sCw3eQGs+95Jsrt3W5p85vodVhyH8S+7buafADV69ooKM0h9XnsqEEH0Mv/GH8NOIvbehlSMQrEjOMsWRvea7A==", + "version": "0.1.1-beta.1", + "resolved": "https://registry.npmjs.org/@sun-protocol/sun-sdk-launchpad/-/sun-sdk-launchpad-0.1.1-beta.1.tgz", + "integrity": "sha512-COt7mTPixaOAGcBYqrrcqupqipOOvunyhGYhRl5J5Qv23/oaxXvitILANGug70osXb+JJYFS/4vru+xGIGfgVQ==", "dependencies": { - "@sun-protocol/sun-sdk-chains": "0.1.1-beta.0", - "@sun-protocol/sun-sdk-core": "0.1.1-beta.0", - "@sun-protocol/sun-sdk-runtime": "0.1.1-beta.0" + "@sun-protocol/sun-sdk-chains": "0.1.1-beta.1", + "@sun-protocol/sun-sdk-core": "0.1.1-beta.1", + "@sun-protocol/sun-sdk-runtime": "0.1.1-beta.1" }, "engines": { "node": ">=20" } }, "node_modules/@sun-protocol/sun-sdk-protocols": { - "version": "0.1.1-beta.0", - "resolved": "https://registry.npmjs.org/@sun-protocol/sun-sdk-protocols/-/sun-sdk-protocols-0.1.1-beta.0.tgz", - "integrity": "sha512-oZ1DoVsYNqctBJKfVQKSvWmtpu4PHWK7jBq7+pRx8bLScxfnsB1gcMApQkaO0WDAwfR+Sbgy1G/TJkuFG3bNqQ==", + "version": "0.1.1-beta.1", + "resolved": "https://registry.npmjs.org/@sun-protocol/sun-sdk-protocols/-/sun-sdk-protocols-0.1.1-beta.1.tgz", + "integrity": "sha512-hrz9+fdGyU2DUVmurx3JhS280ZYVR9QVquzo2q4NV+at6xyJCFAB6tq8t0lmkNocC8p4v0hcyFbciUmIVOq81w==", "dependencies": { "@sun-protocol/permit2-sdk": "^1.0.0", - "@sun-protocol/sun-sdk-api": "0.1.1-beta.0", - "@sun-protocol/sun-sdk-chains": "0.1.1-beta.0", - "@sun-protocol/sun-sdk-core": "0.1.1-beta.0", - "@sun-protocol/sun-sdk-launchpad": "0.1.1-beta.0", - "@sun-protocol/sun-sdk-psm": "0.1.1-beta.0", - "@sun-protocol/sun-sdk-runtime": "0.1.1-beta.0", - "@sun-protocol/sun-sdk-sunswap-v2": "0.1.1-beta.0", - "@sun-protocol/sun-sdk-sunswap-v3": "0.1.1-beta.0", - "@sun-protocol/sun-sdk-sunswap-v4": "0.1.1-beta.0", + "@sun-protocol/sun-sdk-api": "0.1.1-beta.1", + "@sun-protocol/sun-sdk-chains": "0.1.1-beta.1", + "@sun-protocol/sun-sdk-core": "0.1.1-beta.1", + "@sun-protocol/sun-sdk-launchpad": "0.1.1-beta.1", + "@sun-protocol/sun-sdk-psm": "0.1.1-beta.1", + "@sun-protocol/sun-sdk-runtime": "0.1.1-beta.1", + "@sun-protocol/sun-sdk-sunswap-v2": "0.1.1-beta.1", + "@sun-protocol/sun-sdk-sunswap-v3": "0.1.1-beta.1", + "@sun-protocol/sun-sdk-sunswap-v4": "0.1.1-beta.1", "@sun-protocol/universal-router-sdk": "^1.1.1" }, "engines": { @@ -2668,24 +2668,24 @@ } }, "node_modules/@sun-protocol/sun-sdk-psm": { - "version": "0.1.1-beta.0", - "resolved": "https://registry.npmjs.org/@sun-protocol/sun-sdk-psm/-/sun-sdk-psm-0.1.1-beta.0.tgz", - "integrity": "sha512-YVjnmkgqT+j8+28a4Ek4u+yrdaQ2n9gTi5ArqllijtzMXAMzfIul2GoFQ68k8WbENd4+ASJ4er+bI+5fcXXNug==", + "version": "0.1.1-beta.1", + "resolved": "https://registry.npmjs.org/@sun-protocol/sun-sdk-psm/-/sun-sdk-psm-0.1.1-beta.1.tgz", + "integrity": "sha512-3CIgb2/4p8wOZPsy6wXayqVa19QWtTZ4awhhzVcU/6rcPaLtcKGjfC5+m4m5VoICtEGhnWpbZ7IniRp31i4nxg==", "dependencies": { - "@sun-protocol/sun-sdk-chains": "0.1.1-beta.0", - "@sun-protocol/sun-sdk-core": "0.1.1-beta.0" + "@sun-protocol/sun-sdk-chains": "0.1.1-beta.1", + "@sun-protocol/sun-sdk-core": "0.1.1-beta.1" }, "engines": { "node": ">=20" } }, "node_modules/@sun-protocol/sun-sdk-runtime": { - "version": "0.1.1-beta.0", - "resolved": "https://registry.npmjs.org/@sun-protocol/sun-sdk-runtime/-/sun-sdk-runtime-0.1.1-beta.0.tgz", - "integrity": "sha512-IKpIX23Og+aV2HAvn0C6ASbXf1UB68imGzUAjVlT0j2wzbCDLdEgr90YLqGt3PfWLCUb0oTOg69ay/QBBulZPg==", + "version": "0.1.1-beta.1", + "resolved": "https://registry.npmjs.org/@sun-protocol/sun-sdk-runtime/-/sun-sdk-runtime-0.1.1-beta.1.tgz", + "integrity": "sha512-NzvEZfwEEkg/1RtX5y93rhahGjRmAYWWVwPpkskk4v7xZVt5nT3eDtceoWA3aTUniy0K5UgUB0Fq8vQh6reVBg==", "dependencies": { - "@sun-protocol/sun-sdk-chains": "0.1.1-beta.0", - "@sun-protocol/sun-sdk-core": "0.1.1-beta.0" + "@sun-protocol/sun-sdk-chains": "0.1.1-beta.1", + "@sun-protocol/sun-sdk-core": "0.1.1-beta.1" }, "engines": { "node": ">=20" @@ -2700,16 +2700,16 @@ } }, "node_modules/@sun-protocol/sun-sdk-sunswap-v2": { - "version": "0.1.1-beta.0", - "resolved": "https://registry.npmjs.org/@sun-protocol/sun-sdk-sunswap-v2/-/sun-sdk-sunswap-v2-0.1.1-beta.0.tgz", - "integrity": "sha512-g8p4ch+F/iD0NGVS6b7oPSCcYwCLwYvoNR1/pFPSuTn4PXgGgiLOrAwjshsA/k7q+GfRxTDyOmV14CI6Aa3+Ug==", + "version": "0.1.1-beta.1", + "resolved": "https://registry.npmjs.org/@sun-protocol/sun-sdk-sunswap-v2/-/sun-sdk-sunswap-v2-0.1.1-beta.1.tgz", + "integrity": "sha512-eoaiz/c9feModOLXfvkX7MqWtUVghE/d4LyyHM/C5oIxjCMniNfz1TZC0CMw3NyhrEVlyfFNDj2SeBOqn7UBpQ==", "dependencies": { "@ethersproject/bignumber": "5.7.0", "@ethersproject/keccak256": "5.7.0", "@ethersproject/solidity": "5.7.0", "@noble/hashes": "1.4.0", - "@sun-protocol/sun-sdk-chains": "0.1.1-beta.0", - "@sun-protocol/sun-sdk-core": "0.1.1-beta.0", + "@sun-protocol/sun-sdk-chains": "0.1.1-beta.1", + "@sun-protocol/sun-sdk-core": "0.1.1-beta.1", "jsbi": "^3.1.4", "tiny-invariant": "^1.1.0" }, @@ -2751,9 +2751,9 @@ } }, "node_modules/@sun-protocol/sun-sdk-sunswap-v3": { - "version": "0.1.1-beta.0", - "resolved": "https://registry.npmjs.org/@sun-protocol/sun-sdk-sunswap-v3/-/sun-sdk-sunswap-v3-0.1.1-beta.0.tgz", - "integrity": "sha512-P6Fp5pUQFjeTCewiOXrhOuV14EQfSRS06dUMdq70BRrdsvWJnRKA3Pkvmv2oCWsXlQHFjfOC03agHB9STOTszw==", + "version": "0.1.1-beta.1", + "resolved": "https://registry.npmjs.org/@sun-protocol/sun-sdk-sunswap-v3/-/sun-sdk-sunswap-v3-0.1.1-beta.1.tgz", + "integrity": "sha512-ohcclFvUXC61NfzPzaSkm4hBP2Xacwm5bZdkDkyUPrpenOPZPFzQTHPXtoRAxPz7ptHDj+pUaF+2mt96VNjcuQ==", "dependencies": { "@ethersproject/abi": "5.7.0", "@ethersproject/address": "5.7.0", @@ -2762,8 +2762,8 @@ "@ethersproject/logger": "5.7.0", "@ethersproject/solidity": "5.7.0", "@noble/hashes": "1.4.0", - "@sun-protocol/sun-sdk-chains": "0.1.1-beta.0", - "@sun-protocol/sun-sdk-core": "0.1.1-beta.0", + "@sun-protocol/sun-sdk-chains": "0.1.1-beta.1", + "@sun-protocol/sun-sdk-core": "0.1.1-beta.1", "jsbi": "^3.1.4", "tiny-invariant": "^1.1.0" }, @@ -2842,14 +2842,14 @@ } }, "node_modules/@sun-protocol/sun-sdk-sunswap-v4": { - "version": "0.1.1-beta.0", - "resolved": "https://registry.npmjs.org/@sun-protocol/sun-sdk-sunswap-v4/-/sun-sdk-sunswap-v4-0.1.1-beta.0.tgz", - "integrity": "sha512-5s9VPri9IMPrC7aemRPhKoVKZSZAtSyjbTaFlixk10TssJzeF0vULPFeGEr+vSyUqNIA+QDMfOwYaByaQ5HC0Q==", + "version": "0.1.1-beta.1", + "resolved": "https://registry.npmjs.org/@sun-protocol/sun-sdk-sunswap-v4/-/sun-sdk-sunswap-v4-0.1.1-beta.1.tgz", + "integrity": "sha512-c9ioHOfLYy/9BHmP1H/psQdmeejOcykJaWRkl3nSYWrMxMhEOlfR13vzHyd24jlrR5N/L+wDHGQHLvbNM7kNyA==", "dependencies": { "@ethersproject/keccak256": "5.7.0", "@noble/hashes": "1.4.0", - "@sun-protocol/sun-sdk-chains": "0.1.1-beta.0", - "@sun-protocol/sun-sdk-core": "0.1.1-beta.0" + "@sun-protocol/sun-sdk-chains": "0.1.1-beta.1", + "@sun-protocol/sun-sdk-core": "0.1.1-beta.1" }, "engines": { "node": ">=20" diff --git a/package.json b/package.json index 7813c90..03dcfab 100644 --- a/package.json +++ b/package.json @@ -47,13 +47,13 @@ "@bankofai/agent-wallet": "^2.3.0", "@scure/bip32": "^2.0.1", "@scure/bip39": "^2.0.1", - "@sun-protocol/sun-sdk-api": "0.1.1-beta.0", - "@sun-protocol/sun-sdk-chains": "0.1.1-beta.0", - "@sun-protocol/sun-sdk-core": "0.1.1-beta.0", - "@sun-protocol/sun-sdk-protocols": "0.1.1-beta.0", - "@sun-protocol/sun-sdk-runtime": "0.1.1-beta.0", - "@sun-protocol/sun-sdk-sunswap-v3": "0.1.1-beta.0", - "@sun-protocol/sun-sdk-sunswap-v4": "0.1.1-beta.0", + "@sun-protocol/sun-sdk-api": "0.1.1-beta.1", + "@sun-protocol/sun-sdk-chains": "0.1.1-beta.1", + "@sun-protocol/sun-sdk-core": "0.1.1-beta.1", + "@sun-protocol/sun-sdk-protocols": "0.1.1-beta.1", + "@sun-protocol/sun-sdk-runtime": "0.1.1-beta.1", + "@sun-protocol/sun-sdk-sunswap-v3": "0.1.1-beta.1", + "@sun-protocol/sun-sdk-sunswap-v4": "0.1.1-beta.1", "chalk": "^4.1.2", "cli-table3": "^0.6.5", "commander": "^12.1.0", diff --git a/src/commands/contract.ts b/src/commands/contract.ts index e0d6fd1..cf4e4dc 100644 --- a/src/commands/contract.ts +++ b/src/commands/contract.ts @@ -1,11 +1,10 @@ import { Command } from 'commander' import { getNetwork } from '../lib/context' -import { writeAction, readAction } from '../lib/command' +import { writeRuntimeAction as writeAction, readRuntimeAction as readAction } from '../lib/command' import { toCliTxResult } from '../lib/sdk/compat' -function parseAbi(value: string | undefined): any[] { - if (!value) throw new Error('--abi is required for SDK contract calls') - return JSON.parse(value) +function parseAbi(value: string | undefined): any[] | undefined { + return value === undefined ? undefined : JSON.parse(value) } function parseValue(value: string | undefined): bigint | undefined { diff --git a/src/commands/liquidity.ts b/src/commands/liquidity.ts index e82b9a9..c44c7a6 100644 --- a/src/commands/liquidity.ts +++ b/src/commands/liquidity.ts @@ -13,9 +13,10 @@ import { WTRX_MAINNET, WTRX_NILE, } from '../lib/sdk/constants' -import { createReadonlyTronWeb } from '../lib/sdk/factory' +import { createReadonlyTronWeb, withLiquidityAbi } from '../lib/sdk/factory' import { toCliTxResult } from '../lib/sdk/compat' -import { resolveV4MintTicks, v4TickSpacingForFee } from '../lib/sdk/liquidity-v4' +import { resolveLegacyV3Pool } from '../lib/sdk/liquidity-v3' +import { resolveV4MintTicks, v4TickSpacingForFee, v4PoolKeyFromCli } from '../lib/sdk/liquidity-v4' const SUNSWAP_V2_FACTORY_MIN_ABI = [ { @@ -70,8 +71,20 @@ function getV2Factory(network: string): string { return getContractAddress(assertSdkNetwork(network), 'sunswapV2Factory') } -function percentFromDecimal(value: string | undefined): `${number}%` | undefined { - return value === undefined ? undefined : (`${parseFloat(value) * 100}%` as `${number}%`) +// SunKit V4 interpreted this existing CLI value as percent, despite its help text. +// Keep that historical interpretation and integer rounding during migration. +function legacyV4MaximumSlippage(value: string | undefined): `${number}%` { + const percent = value === undefined ? 0 : parseFloat(value) + const bips = percent > 0 ? Math.floor((100 + percent) * 100) - 10_000 : 0 + return `${bips / 100}%` +} + +function legacyV4Minimum(value: string | undefined, slippage: string | undefined): string { + const amount = BigInt(value ?? '0') + const percent = slippage === undefined ? 0 : parseFloat(slippage) + return percent > 0 + ? ((amount * BigInt(Math.floor((100 - percent) * 100))) / 10_000n).toString() + : amount.toString() } function toV3PoolOrder(token0: string, token1: string, value0: T, value1: T): [T, T] { @@ -87,8 +100,19 @@ function withContracts( return entries.length ? Object.fromEntries(entries) : undefined } -function mapTx(result: unknown): unknown { - return toCliTxResult(result) +function mapTx(result: unknown, reverseAmounts = false): unknown { + const mapped = toCliTxResult(result) as Record + const amounts = mapped?.computedAmounts as + | { amount0Desired: string; amount1Desired: string } + | undefined + if (!reverseAmounts || !amounts) return mapped + return { + ...mapped, + computedAmounts: { + amount0Desired: amounts.amount1Desired, + amount1Desired: amounts.amount0Desired, + }, + } } function toWTRXIfNative(tokenAddress: string, network: string): string { @@ -267,8 +291,8 @@ export function registerLiquidityCommands(program: Command) { confirmMsg: 'Add V2 liquidity?', spinnerLabel: 'Adding V2 liquidity...', errorLabel: 'V2 add liquidity failed', - execute: (sdk) => - sdk.liquidity.v2.add + execute: async (client) => + (await withLiquidityAbi(client, router, opts.abi)).liquidity.v2.add .execute({ contracts: withContracts({ sunswapV2Router: router }), tokenA, @@ -278,9 +302,10 @@ export function registerLiquidityCommands(program: Command) { amountAMin: opts.minA, amountBMin: opts.minB, recipient: opts.to, + ttlSeconds: 1800, deadline: opts.deadline, }) - .then(mapTx), + .then((result) => mapTx(result)), }) }) @@ -325,8 +350,8 @@ export function registerLiquidityCommands(program: Command) { confirmMsg: 'Remove V2 liquidity?', spinnerLabel: 'Removing V2 liquidity...', errorLabel: 'V2 remove liquidity failed', - execute: (sdk) => - sdk.liquidity.v2.remove + execute: async (sdk) => + (await withLiquidityAbi(sdk, router, opts.abi)).liquidity.v2.remove .execute({ contracts: withContracts({ sunswapV2Router: router }), tokenA, @@ -335,9 +360,10 @@ export function registerLiquidityCommands(program: Command) { amountAMin: opts.minA, amountBMin: opts.minB, recipient: opts.to, + ttlSeconds: 1800, deadline: opts.deadline, }) - .then(mapTx), + .then((result) => mapTx(result)), }) }) @@ -409,25 +435,35 @@ export function registerLiquidityCommands(program: Command) { confirmMsg: 'Mint V3 position?', spinnerLabel: 'Minting V3 position...', errorLabel: 'V3 mint failed', - execute: (sdk) => - sdk.liquidity.v3.add + execute: async (sdk) => + (await withLiquidityAbi(sdk, pm, opts.abi)).liquidity.v3.add .execute({ contracts: withContracts({ sunswapV3PositionManager: pm }), tokenA: token0, tokenB: token1, fee: opts.fee ? parseInt(opts.fee) : DEFAULT_V3_FEE, + pool: await resolveLegacyV3Pool(sdk, { + token0, + token1, + fee: opts.fee ? parseInt(opts.fee) : DEFAULT_V3_FEE, + }), tickLower: opts.tickLower ? parseInt(opts.tickLower) : undefined, tickUpper: opts.tickUpper ? parseInt(opts.tickUpper) : undefined, + tickRangeMode: 'legacy', + amountInputMode: 'legacy-cli', + includeComputedMetadata: true, tickRangeWidth: - opts.tickLower === undefined && opts.tickUpper === undefined ? 100 : undefined, + opts.tickLower === undefined || opts.tickUpper === undefined ? 100 : undefined, amount0, amount1, - amount0Min, - amount1Min, + // Legacy V3 defaults are zero, not the SDK's slippage-derived minima. + amount0Min: amount0Min ?? '0', + amount1Min: amount1Min ?? '0', recipient: opts.recipient, + ttlSeconds: 1800, deadline: opts.deadline, }) - .then(mapTx), + .then((result) => mapTx(result, toV3PoolOrder(token0, token1, false, true)[0])), summarizeResult: (result: any) => { const out: Record = {} if (result?.computedTicks) { @@ -477,15 +513,35 @@ export function registerLiquidityCommands(program: Command) { confirmMsg: 'Increase V3 liquidity?', spinnerLabel: 'Increasing V3 liquidity...', errorLabel: 'V3 increase liquidity failed', - execute: (sdk) => - sdk.liquidity.v3.increase + execute: async (sdk) => + ( + await withLiquidityAbi( + sdk, + pm, + opts.abi, + BigInt(opts.amount0 ?? '0') === 0n || BigInt(opts.amount1 ?? '0') === 0n + ? ['positions(uint256)'] + : [], + ) + ).liquidity.v3.increase .execute({ contracts: withContracts({ sunswapV3PositionManager: pm }), tokenId: opts.tokenId, amount0: opts.amount0, amount1: opts.amount1, - amount0Min: opts.min0, - amount1Min: opts.min1, + amountInputMode: 'legacy-cli', + includeComputedMetadata: true, + pool: + BigInt(opts.amount0 ?? '0') === 0n || BigInt(opts.amount1 ?? '0') === 0n + ? await resolveLegacyV3Pool(sdk, { + tokenId: opts.tokenId, + positionManager: pm, + positionAbi: opts.abi, + }) + : undefined, + amount0Min: opts.min0 ?? '0', + amount1Min: opts.min1 ?? '0', + ttlSeconds: 1800, deadline: opts.deadline, }) .then(mapTx), @@ -526,15 +582,16 @@ export function registerLiquidityCommands(program: Command) { confirmMsg: 'Decrease V3 liquidity?', spinnerLabel: 'Decreasing V3 liquidity...', errorLabel: 'V3 decrease liquidity failed', - execute: (sdk) => - sdk.positions.v3.remove + execute: async (sdk) => + (await withLiquidityAbi(sdk, pm, opts.abi)).positions.v3.remove .execute({ contracts: withContracts({ sunswapV3PositionManager: pm }), tokenId: opts.tokenId, liquidity: opts.liquidity, - amount0Min: opts.min0, - amount1Min: opts.min1, + amount0Min: opts.min0 ?? '0', + amount1Min: opts.min1 ?? '0', collectFees: false, + ttlSeconds: 1800, deadline: opts.deadline, }) .then(mapTx), @@ -563,12 +620,13 @@ export function registerLiquidityCommands(program: Command) { confirmMsg: 'Collect V3 fees?', spinnerLabel: 'Collecting V3 fees...', errorLabel: 'V3 collect failed', - execute: (sdk) => - sdk.positions.v3.collect + execute: async (sdk) => + (await withLiquidityAbi(sdk, pm, opts.abi)).positions.v3.collect .execute({ contracts: withContracts({ sunswapV3PositionManager: pm }), tokenId: opts.tokenId, recipient: opts.recipient, + estimateFees: true, }) .then(mapTx), summarizeResult: (result: any) => { @@ -671,12 +729,23 @@ export function registerLiquidityCommands(program: Command) { ...ticks, amount0: opts.amount0, amount1: opts.amount1, - slippage: percentFromDecimal(opts.slippage), - owner: opts.recipient, + slippage: legacyV4MaximumSlippage(opts.slippage), + roundingMode: 'floor', + amountInputMode: 'legacy-cli', + includeComputedMetadata: true, + transactionMode: 'multicall', + recipient: opts.recipient, + sweepRecipient: opts.recipient, + ttlSeconds: 1800, deadline: opts.deadline, sqrtPriceX96: opts.sqrtPrice, }) - .then(mapTx) + .then((result) => { + const mapped = mapTx(result) as Record + return tickLower === undefined || tickUpper === undefined + ? { ...mapped, computedTicks: ticks } + : mapped + }) }, summarizeResult: (result: any) => { const out: Record = {} @@ -748,9 +817,18 @@ export function registerLiquidityCommands(program: Command) { sdk.liquidity.v4.increase .execute({ tokenId: opts.tokenId, + token0, + token1, + fee: opts.fee ? parseInt(opts.fee) : DEFAULT_V4_FEE, + tickSpacing: v4TickSpacingForFee(opts.fee ? parseInt(opts.fee) : DEFAULT_V4_FEE), amount0: opts.amount0, amount1: opts.amount1, - slippage: percentFromDecimal(opts.slippage), + slippage: legacyV4MaximumSlippage(opts.slippage), + roundingMode: 'floor', + amountInputMode: 'legacy-cli', + includeComputedMetadata: true, + transactionMode: 'multicall', + ttlSeconds: 1800, deadline: opts.deadline, }) .then(mapTx), @@ -814,9 +892,18 @@ export function registerLiquidityCommands(program: Command) { .execute({ tokenId: opts.tokenId, liquidity: opts.liquidity, - amount0Min: opts.min0, - amount1Min: opts.min1, - slippage: percentFromDecimal(opts.slippage), + poolKey: v4PoolKeyFromCli( + assertSdkNetwork(network), + token0, + token1, + opts.fee ? parseInt(opts.fee) : DEFAULT_V4_FEE, + ), + amount0Min: legacyV4Minimum(opts.min0, opts.slippage), + amount1Min: legacyV4Minimum(opts.min1, opts.slippage), + includeComputedMetadata: true, + transactionMode: 'multicall', + settlementMode: 'close-currency', + ttlSeconds: 1800, deadline: opts.deadline, }) .then(mapTx), @@ -880,6 +967,19 @@ export function registerLiquidityCommands(program: Command) { sdk.positions.v4.collect .execute({ tokenId: opts.tokenId, + ...(token0 && token1 + ? { + poolKey: v4PoolKeyFromCli( + assertSdkNetwork(network), + token0, + token1, + opts.fee ? parseInt(opts.fee) : DEFAULT_V4_FEE, + ), + } + : {}), + transactionMode: 'multicall', + settlementMode: 'close-currency', + ttlSeconds: 1800, deadline: opts.deadline, }) .then(mapTx), diff --git a/src/commands/sunpump.ts b/src/commands/sunpump.ts index 69ffadb..93fd63f 100644 --- a/src/commands/sunpump.ts +++ b/src/commands/sunpump.ts @@ -47,7 +47,7 @@ function assertMainnet(): void { function getSunPumpApi(): SunPumpApiClient { assertMainnet() - return new SunPumpApiClient({ network: 'mainnet' }) + return new SunPumpApiClient({ network: 'mainnet', baseUrl: process.env.SUNPUMP_API_BASE_URL }) } async function pumpAction(opts: PumpActionOpts): Promise { @@ -754,7 +754,7 @@ export function registerSunpumpCommands(program: Command) { try { const quote: any = await withSpinner('Fetching buy quote...', async () => { const kit = await getKit() - return kit.pump.buy.quote({ token: tokenAddress, trxAmount: trxSun }) + return kit.pump.buy.quote({ token: tokenAddress, trxAmount: trxSun, launchpadOnly: true }) }) if (isJsonMode()) { output({ ...quote, tokenAddress, trxSun, trxIn: opts.trx }) @@ -787,7 +787,11 @@ export function registerSunpumpCommands(program: Command) { try { const quote: any = await withSpinner('Fetching sell quote...', async () => { const kit = await getKit() - return kit.pump.sell.quote({ token: tokenAddress, tokenAmount: tokenRaw }) + return kit.pump.sell.quote({ + token: tokenAddress, + tokenAmount: tokenRaw, + launchpadOnly: true, + }) }) if (isJsonMode()) { output({ ...quote, tokenAddress, tokenRaw, amountIn: opts.amount }) @@ -829,7 +833,7 @@ export function registerSunpumpCommands(program: Command) { try { const quote = await withSpinner('Fetching buy quote...', async () => { const kit = await getKit() - return kit.pump.buy.quote({ token: tokenAddress, trxAmount: trxSun }) + return kit.pump.buy.quote({ token: tokenAddress, trxAmount: trxSun, launchpadOnly: true }) }) summary['Tokens Out (expected)'] = baseUnitsToDecimal((quote as any).tokenAmount, 18) summary['Fee'] = `${baseUnitsToDecimal((quote as any).fee, 6)} TRX` @@ -850,8 +854,9 @@ export function registerSunpumpCommands(program: Command) { token: tokenAddress, trxAmount: trxSun, minTokenOut: opts.minOut, + launchpadOnly: true, slippage: `${slippage * 100}%`, - } as any) + }) .then(toCliTxResult), onSuccess: async (result: any) => { if (isJsonMode()) return @@ -901,7 +906,11 @@ export function registerSunpumpCommands(program: Command) { try { const quote = await withSpinner('Fetching sell quote...', async () => { const kit = await getKit() - return kit.pump.sell.quote({ token: tokenAddress, tokenAmount: tokenRaw }) + return kit.pump.sell.quote({ + token: tokenAddress, + tokenAmount: tokenRaw, + launchpadOnly: true, + }) }) summary['TRX Out (expected)'] = `${baseUnitsToDecimal((quote as any).trxAmount, 6)} TRX` summary['Fee'] = `${baseUnitsToDecimal((quote as any).fee, 6)} TRX` @@ -922,8 +931,9 @@ export function registerSunpumpCommands(program: Command) { token: tokenAddress, tokenAmount: tokenRaw, minTrxOut: opts.minOut, + launchpadOnly: true, slippage: `${slippage * 100}%`, - } as any) + }) .then(toCliTxResult), onSuccess: async (result: any) => { if (isJsonMode()) return diff --git a/src/commands/swap.ts b/src/commands/swap.ts index 42b2357..881de0f 100644 --- a/src/commands/swap.ts +++ b/src/commands/swap.ts @@ -1,6 +1,6 @@ import { Command } from 'commander' import { getNetwork } from '../lib/context' -import { writeAction, readAction } from '../lib/command' +import { writeAction, writeRuntimeAction, readRuntimeAction as readAction } from '../lib/command' import { isJsonMode, output, outputError, withSpinner } from '../lib/output' import { resolveTokenAddress, getSymbolOrAddress } from '../lib/tokens' import { toCliTxResult } from '../lib/sdk/compat' @@ -26,9 +26,8 @@ function cliSlippageToSdk(value: number): `${number}%` { return `${value * 100}%` as `${number}%` } -function parseAbi(value: string | undefined): any[] { - if (!value) throw new Error('--abi is required for SDK contract calls') - return JSON.parse(value) +function parseAbi(value: string | undefined): any[] | undefined { + return value === undefined ? undefined : JSON.parse(value) } async function fetchSwapQuote( @@ -109,8 +108,19 @@ export function registerSwapCommands(program: Command) { quote, route, slippage: cliSlippageToSdk(slippage), + // SunKit router swaps used a one-hour transaction deadline. + transactionValidForSeconds: 3600, }) - return toCliTxResult(result) + return { + txid: (toCliTxResult(result) as { txid: string }).txid, + route: { + amountIn: route.amountIn, + amountOut: route.amountOut, + symbols: route.symbols, + poolVersions: route.poolVersions, + impact: route.impact, + }, + } }, onSuccess: async (result: any) => { if (!isJsonMode()) { @@ -223,7 +233,7 @@ export function registerSwapCommands(program: Command) { .option('--value ', 'TRX call value in Sun') .option('--abi ', 'Optional router ABI as JSON array') .action(async (opts) => { - await writeAction({ + await writeRuntimeAction({ title: 'SwapExactInput', summary: { Router: opts.router, diff --git a/src/commands/wallet.ts b/src/commands/wallet.ts index 1316bbe..68e7dda 100644 --- a/src/commands/wallet.ts +++ b/src/commands/wallet.ts @@ -1,6 +1,6 @@ import { Command } from 'commander' import { getNetwork } from '../lib/context' -import { readAction } from '../lib/command' +import { readRuntimeAction as readAction } from '../lib/command' import { getWalletAddress, initWallet } from '../lib/wallet' import { output, outputError } from '../lib/output' import { readBalances } from '@sun-protocol/sun-sdk-runtime' diff --git a/src/lib/command.ts b/src/lib/command.ts index 075e96d..c3ad321 100644 --- a/src/lib/command.ts +++ b/src/lib/command.ts @@ -2,7 +2,8 @@ * Command helpers — eliminate boilerplate in write/read command handlers. */ -import { getKit, getApi, ensureWallet } from './context' +import { getKit, getApi, getRuntime, ensureWallet } from './context' +import type { ContractRuntime } from '@sun-protocol/sun-sdk-runtime' import { output, outputError, @@ -168,7 +169,7 @@ export function parseApiResponse( // writeAction — for state-changing commands (swap, liquidity, contract:send) // --------------------------------------------------------------------------- -export interface WriteActionOpts { +export interface WriteActionOpts { /** Title shown in the pre-execution summary */ title: string /** Key-value pairs displayed in summary */ @@ -178,7 +179,7 @@ export interface WriteActionOpts { /** Spinner label during execution */ spinnerLabel: string /** The async operation that calls kit methods */ - execute: (kit: CliSunSDK) => Promise + execute: (kit: Client) => Promise /** Error label prefix for outputError */ errorLabel: string /** Optional post-success callback (e.g. extra human-friendly output) */ @@ -188,6 +189,19 @@ export interface WriteActionOpts { } export async function writeAction(opts: WriteActionOpts): Promise { + return runWriteAction(opts, getKit) +} + +export async function writeRuntimeAction( + opts: WriteActionOpts, +): Promise { + return runWriteAction(opts, async () => ({ runtime: await getRuntime() })) +} + +async function runWriteAction( + opts: WriteActionOpts, + getClient: () => Promise, +): Promise { try { if (_dryRun) { output({ dryRun: true, action: opts.title, params: opts.summary }) @@ -195,7 +209,7 @@ export async function writeAction(opts: WriteActionOpts): Promise { } await ensureWallet() - const kit = await getKit() + const kit = await getClient() const network = typeof opts.summary.Network === 'string' ? String(opts.summary.Network) : undefined @@ -231,11 +245,11 @@ export async function writeAction(opts: WriteActionOpts): Promise { // readAction — for read commands that need the protocol SDK runtime (balances, contract:read) // --------------------------------------------------------------------------- -export interface ReadActionOpts { +export interface ReadActionOpts { /** Spinner label */ spinnerLabel: string /** The async operation */ - execute: (kit: CliSunSDK) => Promise + execute: (kit: Client) => Promise /** Table config for human-readable output */ tableConfig?: { headers: string[]; toRow: (item: any) => string[] } /** Error label prefix */ @@ -245,8 +259,21 @@ export interface ReadActionOpts { } export async function readAction(opts: ReadActionOpts): Promise { + return runReadAction(opts, getKit) +} + +export async function readRuntimeAction( + opts: ReadActionOpts, +): Promise { + return runReadAction(opts, async () => ({ runtime: await getRuntime() })) +} + +async function runReadAction( + opts: ReadActionOpts, + getClient: () => Promise, +): Promise { try { - const kit = await getKit() + const kit = await getClient() const result = await withSpinner(opts.spinnerLabel, () => opts.execute(kit)) const data = opts.transform ? opts.transform(result) : result diff --git a/src/lib/context.ts b/src/lib/context.ts index b61cc0e..dd85fa8 100644 --- a/src/lib/context.ts +++ b/src/lib/context.ts @@ -4,11 +4,13 @@ import type { SunApiClient } from '@sun-protocol/sun-sdk-api' import { initWallet, getWallet, isWalletConfigured } from './wallet' -import { createSunApiClient, createSunSDK } from './sdk/factory' +import { createSunApiClient, createSunSDK, createGenericRuntime } from './sdk/factory' +import type { ContractRuntime } from '@sun-protocol/sun-sdk-runtime' import type { CliSunSDK } from './sdk/types' let _api: SunApiClient | null = null let _sdk: CliSunSDK | null = null +let _runtime: ContractRuntime | null = null let _initialized = false function getNetwork(): string { @@ -41,6 +43,22 @@ export async function getSdk(): Promise { return _sdk } +export async function getRuntime(): Promise { + if (!_initialized) { + await initWallet() + _initialized = true + } + if (!_runtime) { + _runtime = await createGenericRuntime({ + wallet: isWalletConfigured() ? getWallet() : undefined, + network: getNetwork(), + tronGridApiKey: process.env.TRONGRID_API_KEY || process.env.TRON_GRID_API_KEY, + rpcUrl: process.env.TRON_RPC_URL, + }) + } + return _runtime +} + /** @deprecated Use getSdk(). Kept for the existing sun-cli command API. */ export async function getKit(): Promise { return getSdk() diff --git a/src/lib/sdk/compat.ts b/src/lib/sdk/compat.ts index 91364a7..e61f154 100644 --- a/src/lib/sdk/compat.ts +++ b/src/lib/sdk/compat.ts @@ -4,22 +4,56 @@ export function toCliTxResult(result: T): unknown { } const record = result as Record + const metadata = (record.plan as { metadata?: Record } | undefined)?.metadata const finalResult = record.finalResult as Record | undefined if (finalResult && finalResult.type === 'transaction' && typeof finalResult.txid === 'string') { - return { - txid: finalResult.txid, - raw: finalResult.raw, + const txResult = { result: true, txid: finalResult.txid } + if (metadata?.operation === 'launchpad-buy-exact-trx') { + return { + txResult, + tokenAddress: metadata.token, + trxSpent: metadata.trxAmount, + expectedTokens: metadata.quotedTokenAmount, + minTokenOut: metadata.minTokenOut, + } + } + if (metadata?.operation === 'launchpad-sell-exact-token') { + return { + txResult, + tokenAddress: metadata.token, + tokensSold: metadata.tokenAmount, + expectedTrx: metadata.quotedTrxAmount, + minTrxOut: metadata.minTrxOut, + } } + if (metadata?.protocol === 'sunswap-v3' || metadata?.protocol === 'sunswap-v4') { + if (metadata.protocol === 'sunswap-v3' && metadata.operation === 'remove-position') + return txResult + const mapped: Record = { txResult } + for (const key of [ + 'computedAmounts', + 'computedTicks', + 'computedAmountMin', + 'estimatedFees', + ]) { + if (metadata[key] !== undefined) mapped[key] = metadata[key] + } + if (metadata.operation === 'mint-position') mapped.poolCreated = metadata.createPool + return mapped + } + return txResult } if (Array.isArray(record.txids) && typeof record.txids[record.txids.length - 1] === 'string') { return { txid: record.txids[record.txids.length - 1], - raw: result, + result: true, } } - if (typeof record.txid === 'string') return result + if (typeof record.txid === 'string') { + return 'raw' in record ? { result: true, txid: record.txid } : result + } if (typeof record.txID !== 'string') return result const { txID, ...rest } = record diff --git a/src/lib/sdk/constants.ts b/src/lib/sdk/constants.ts index 7f7a339..10b8499 100644 --- a/src/lib/sdk/constants.ts +++ b/src/lib/sdk/constants.ts @@ -9,3 +9,9 @@ export const WTRX_NILE = getWrappedNativeAddress('nile') // Fees are expressed in millionths: 3000 = 0.3%, 500 = 0.05%. export const DEFAULT_V3_FEE = 3000 export const DEFAULT_V4_FEE = 500 + +// The CLI's legacy Nile V3 deployment differs from the SUN.io SDK default. +export const CLI_V3_FACTORY = { + mainnet: 'TThJt8zaJzJMhCEScH7zWKnp5buVZqys9x', + nile: 'TLJWAScHZ4Qmk1axyKMzrnoYuu2pSLer1F', +} as const diff --git a/src/lib/sdk/factory.ts b/src/lib/sdk/factory.ts index dce0e24..9c6f112 100644 --- a/src/lib/sdk/factory.ts +++ b/src/lib/sdk/factory.ts @@ -3,6 +3,11 @@ import type { Network } from '@sun-protocol/sun-sdk-core' import { SunSDK } from '@sun-protocol/sun-sdk-protocols' import { createRuntime, + createContractRuntime, + withContractAbi, + type ContractRuntime, + type RuntimeNetwork, + type AbiItem, type Runtime, type TronClient, type WalletAdapter, @@ -24,13 +29,14 @@ export function getNetworkFromEnv(env: NodeJS.ProcessEnv = process.env): Network throw new Error(`Unsupported SDK network: ${network}`) } -function getDefaultRpcUrl(network: Network): string { +function getDefaultRpcUrl(network: RuntimeNetwork): string { + if (network === 'shasta') return 'https://api.shasta.trongrid.io' return network === 'nile' ? 'https://nile.trongrid.io' : 'https://api.trongrid.io' } export function createReadonlyTronWeb(options: SdkRuntimeOptions = {}): TronWeb { const network = options.network ?? 'mainnet' - const fullHost = options.rpcUrl || getDefaultRpcUrl(assertSdkNetwork(network)) + const fullHost = options.rpcUrl || getDefaultRpcUrl(assertRuntimeNetwork(network)) const headers = options.tronGridApiKey ? { 'TRON-PRO-API-KEY': options.tronGridApiKey } : undefined @@ -46,6 +52,56 @@ function assertSdkNetwork(network: string): Network { throw new Error(`Unsupported SDK network: ${network}`) } +function assertRuntimeNetwork(network: string): RuntimeNetwork { + if (network === 'mainnet' || network === 'nile' || network === 'shasta') return network + throw new Error(`Unsupported SDK network: ${network}`) +} + +export async function createGenericRuntime( + options: SdkRuntimeOptions = {}, +): Promise { + const network = assertRuntimeNetwork(options.network ?? 'mainnet') + const tronWeb = createReadonlyTronWeb(options) + const { createTronWebClient } = require('@sun-protocol/sun-sdk-runtime/node') as { + createTronWebClient(input: { + network: RuntimeNetwork + tronWeb: TronWeb + }): TronClient + } + return createContractRuntime({ + network, + tronClient: createTronWebClient({ network, tronWeb }), + wallet: options.wallet, + }) +} + +/** A fresh runtime confines an explicit ABI to this command, without mutating shared SDK state. */ +export async function withLiquidityAbi( + sdk: CliSunSDK, + address: string, + json?: string, + readSelectors: readonly string[] = [], +): Promise { + if (json === undefined) return sdk + const abi = JSON.parse(json) as readonly AbiItem[] + const scopedClient = withContractAbi(sdk.runtime.tronClient, address, abi) + const runtime = createRuntime({ + network: sdk.network, + tronClient: { + ...scopedClient, + // Legacy PM overrides applied to writes and only to the positions read + // used by single-side increase. Extra SDK planning views use their own ABI. + readContract: (call) => + readSelectors.includes(call.functionSelector) + ? scopedClient.readContract(call) + : sdk.runtime.tronClient.readContract(call), + }, + getWallet: () => sdk.runtime.getWallet(), + }) + const scoped = await SunSDK.create({ network: sdk.network, runtime }) + return scoped as CliSunSDK +} + export async function createSdkRuntime(options: SdkRuntimeOptions = {}): Promise { const network = assertSdkNetwork(options.network ?? 'mainnet') const tronWeb = createReadonlyTronWeb(options) diff --git a/src/lib/sdk/liquidity-v3.ts b/src/lib/sdk/liquidity-v3.ts new file mode 100644 index 0000000..d98a25d --- /dev/null +++ b/src/lib/sdk/liquidity-v3.ts @@ -0,0 +1,71 @@ +import { assertTronAddress } from '@sun-protocol/sun-sdk-core' +import { buildV3PoolStateReads, buildV3PositionRead } from '@sun-protocol/sun-sdk-sunswap-v3' +import { TronWeb } from 'tronweb' +import { CLI_V3_FACTORY, TRX_ADDRESS } from './constants' +import type { CliSunSDK } from './types' + +function address(value: unknown): string { + const raw = String(Array.isArray(value) ? value[0] : value) + return raw.startsWith('T') + ? raw + : TronWeb.address.fromHex(raw.startsWith('0x') ? `41${raw.slice(2)}` : raw) +} + +/** Look up the exact legacy CLI pool on-chain; do not assume the SDK's factory/init-code hash. */ +export async function resolveLegacyV3Pool( + sdk: CliSunSDK, + input: { + token0?: string + token1?: string + fee?: number + tokenId?: string + positionManager?: string + positionAbi?: string + }, +) { + let { token0, token1, fee } = input + if (token0 === undefined || token1 === undefined || fee === undefined) { + if (!input.tokenId || !input.positionManager) throw new Error('V3 position context is required') + const position = (await (input.positionAbi === undefined + ? sdk.runtime.readContract( + buildV3PositionRead({ + network: sdk.network, + tokenId: input.tokenId, + positionManager: assertTronAddress(input.positionManager), + }), + ) + : sdk.runtime.contract.read({ + address: input.positionManager, + abi: JSON.parse(input.positionAbi), + functionName: 'positions', + args: [input.tokenId], + }))) as Record | readonly unknown[] + const values = position as Record + token0 = address(values.token0 ?? values[2]) + token1 = address(values.token1 ?? values[3]) + fee = Number(values.fee ?? values[4]) + } + const pool = address( + await sdk.runtime.contract.read({ + address: CLI_V3_FACTORY[sdk.network], + functionSelector: 'getPool(address,address,uint24)', + parameters: [ + { type: 'address', value: token0 }, + { type: 'address', value: token1 }, + { type: 'uint24', value: fee }, + ], + outputs: [{ type: 'address' }], + }), + ) + if (pool === TRX_ADDRESS) throw new Error(`V3 pool not found for ${token0}/${token1} fee=${fee}`) + const reads = buildV3PoolStateReads({ pool: assertTronAddress(pool) }) + const results = await sdk.runtime.multicall([reads[0]!, reads[1]!, reads[3]!]) + if (results.some((result) => !result.success)) throw new Error('V3 legacy pool state read failed') + const slot = results[0]!.value as Record + return { + sqrtRatioX96: String(slot.sqrtPriceX96 ?? slot[0]), + tickCurrent: Number(slot.tick ?? slot[1]), + liquidity: String(results[1]!.value), + tickSpacing: Number(results[2]!.value), + } +} diff --git a/src/lib/sdk/liquidity-v4.ts b/src/lib/sdk/liquidity-v4.ts index 517f12b..634cd1a 100644 --- a/src/lib/sdk/liquidity-v4.ts +++ b/src/lib/sdk/liquidity-v4.ts @@ -1,4 +1,4 @@ -import { Fraction } from '@sun-protocol/sun-sdk-core' +import { Fraction, type Network } from '@sun-protocol/sun-sdk-core' import { nearestUsableTick, TickMath } from '@sun-protocol/sun-sdk-sunswap-v3' import { computeV4PoolId, @@ -20,6 +20,16 @@ export function v4TickSpacingForFee(fee: number): number { return TICK_SPACING_BY_FEE[fee] ?? 10 } +export function v4PoolKeyFromCli(network: Network, token0: string, token1: string, fee: number) { + return createV4PoolKey({ + network, + currencyA: token0, + currencyB: token1, + fee, + parameters: encodeV4PoolParameters({ tickSpacing: v4TickSpacingForFee(fee) }), + }) +} + interface V4MintTickInput { token0: string token1: string diff --git a/test/commands/liquidity-sdk.test.ts b/test/commands/liquidity-sdk.test.ts index d234ea9..231db02 100644 --- a/test/commands/liquidity-sdk.test.ts +++ b/test/commands/liquidity-sdk.test.ts @@ -23,6 +23,14 @@ describe('liquidity SDK integration', () => { readAction: jest.fn().mockResolvedValue(undefined), })) jest.doMock('../../src/lib/context', () => ({ getNetwork: () => 'nile' })) + jest.doMock('../../src/lib/sdk/liquidity-v3', () => ({ + resolveLegacyV3Pool: async () => ({ + sqrtRatioX96: '79228162514264337593543950336', + liquidity: '1000000', + tickCurrent: 0, + tickSpacing: 60, + }), + })) const { registerLiquidityCommands } = require('../../src/commands/liquidity') const program = new Command() @@ -33,6 +41,34 @@ describe('liquidity SDK integration', () => { return writeAction.mock.calls[0][0] } + it('retains partial V3 tick ranges and zero default minima from the legacy CLI', async () => { + const action = await captureWriteAction([ + 'v3:mint', + '--token0', + 'SUN', + '--token1', + 'USDT', + '--tick-lower', + '-600', + '--amount0', + '1000', + ]) + const execute = jest.fn().mockResolvedValue({ txids: ['mint-tx'] }) + await action.execute({ liquidity: { v3: { add: { execute } } } }) + expect(execute).toHaveBeenCalledWith( + expect.objectContaining({ + tickLower: -600, + tickUpper: undefined, + tickRangeMode: 'legacy', + tickRangeWidth: 100, + amountInputMode: 'legacy-cli', + ttlSeconds: 1800, + amount0Min: '0', + amount1Min: '0', + }), + ) + }) + it('passes V2 remove minimum amounts to the SDK', async () => { const action = await captureWriteAction([ 'v2:remove', @@ -175,10 +211,11 @@ describe('liquidity SDK integration', () => { tickLower: 11350, tickUpper: 13350, amount0: '100', - owner: 'TOwner', + recipient: 'TOwner', + sweepRecipient: 'TOwner', }), ) - expect(execute.mock.calls[0][0]).not.toHaveProperty('recipient') + expect(execute.mock.calls[0][0]).not.toHaveProperty('owner') }) it('preserves explicit V4 fees and tick zero without an automatic pool read', async () => { @@ -275,7 +312,7 @@ describe('liquidity SDK integration', () => { }) }) - it('lets the SDK derive V4 pool context from tokenId for increase', async () => { + it('retains V4 input pair, fee, legacy slippage units and amount completion for increase', async () => { const action = await captureWriteAction([ 'v4:increase', '--token-id', @@ -294,9 +331,18 @@ describe('liquidity SDK integration', () => { await action.execute({ liquidity: { v4: { increase: { execute } } } }) expect(execute).toHaveBeenCalledWith({ tokenId: '7', + token0: 'TWrZRHY9aKQZcyjpovdH6qeCEyYZrRQDZt', + token1: 'TXYZopYRdj2D9XRtbG411XZZ3kM5VkAeBf', + fee: 500, + tickSpacing: 10, amount0: undefined, amount1: '200', - slippage: '1%', + slippage: '0.01%', + amountInputMode: 'legacy-cli', + includeComputedMetadata: true, + roundingMode: 'floor', + transactionMode: 'multicall', + ttlSeconds: 1800, deadline: undefined, }) }) @@ -319,14 +365,18 @@ describe('liquidity SDK integration', () => { ]) const remove = jest.fn().mockResolvedValue({ txids: ['remove-tx'] }) await removeAction.execute({ positions: { v4: { remove: { execute: remove } } } }) - expect(remove).toHaveBeenCalledWith({ - tokenId: '7', - liquidity: '10', - amount0Min: '1', - amount1Min: undefined, - slippage: undefined, - deadline: undefined, - }) + expect(remove).toHaveBeenCalledWith( + expect.objectContaining({ + tokenId: '7', + liquidity: '10', + amount0Min: '1', + amount1Min: '0', + poolKey: expect.objectContaining({ fee: 500 }), + transactionMode: 'multicall', + settlementMode: 'close-currency', + deadline: undefined, + }), + ) const collectAction = await captureWriteAction([ 'v4:collect', @@ -341,6 +391,13 @@ describe('liquidity SDK integration', () => { ]) const collect = jest.fn().mockResolvedValue({ txids: ['collect-tx'] }) await collectAction.execute({ positions: { v4: { collect: { execute: collect } } } }) - expect(collect).toHaveBeenCalledWith({ tokenId: '7', deadline: undefined }) + expect(collect).toHaveBeenCalledWith({ + tokenId: '7', + deadline: undefined, + poolKey: expect.objectContaining({ fee: 500 }), + transactionMode: 'multicall', + settlementMode: 'close-currency', + ttlSeconds: 1800, + }) }) }) diff --git a/test/commands/sdk-write-migration.test.ts b/test/commands/sdk-write-migration.test.ts index 435e560..ff99564 100644 --- a/test/commands/sdk-write-migration.test.ts +++ b/test/commands/sdk-write-migration.test.ts @@ -12,9 +12,27 @@ describe('SDK command migrations', () => { return program } + it.each(['read', 'send'])('keeps contract %s ABI optional for SDK discovery', async (verb) => { + const capture = jest.fn().mockResolvedValue(undefined) + jest.doMock('../../src/lib/command', () => ({ + readRuntimeAction: capture, + writeRuntimeAction: capture, + })) + jest.doMock('../../src/lib/context', () => ({ getNetwork: () => 'shasta' })) + const { registerContractCommands } = require('../../src/commands/contract') + const program = makeProgram() + registerContractCommands(program) + await program.parseAsync(['node', 'sun', 'contract', verb, 'TContract', 'totalSupply']) + const call = jest.fn().mockResolvedValue('42') + await capture.mock.calls[0][0].execute({ runtime: { contract: { read: call, write: call } } }) + expect(call).toHaveBeenCalledWith( + expect.objectContaining({ functionName: 'totalSupply', abi: undefined, args: [] }), + ) + }) + it('wallet balances calls SDK readBalances with string tokens', async () => { const readAction = jest.fn().mockResolvedValue(undefined) - jest.doMock('../../src/lib/command', () => ({ readAction })) + jest.doMock('../../src/lib/command', () => ({ readRuntimeAction: readAction })) jest.doMock('../../src/lib/context', () => ({ getNetwork: () => 'nile' })) const readBalances = jest .fn() @@ -44,7 +62,7 @@ describe('SDK command migrations', () => { it('contract send calls SDK runtime contract client and maps tx result', async () => { const writeAction = jest.fn().mockResolvedValue(undefined) - jest.doMock('../../src/lib/command', () => ({ writeAction })) + jest.doMock('../../src/lib/command', () => ({ writeRuntimeAction: writeAction })) jest.doMock('../../src/lib/context', () => ({ getNetwork: () => 'nile' })) const write = jest.fn().mockResolvedValue({ txid: 'tx1', raw: { result: true } }) @@ -72,7 +90,7 @@ describe('SDK command migrations', () => { const opts = writeAction.mock.calls[0][0] const sdk = { runtime: { contract: { write } } } - await expect(opts.execute(sdk)).resolves.toEqual({ txid: 'tx1', raw: { result: true } }) + await expect(opts.execute(sdk)).resolves.toEqual({ txid: 'tx1', result: true }) expect(write).toHaveBeenCalledWith({ address: 'TContract', functionName: 'transfer', @@ -112,7 +130,16 @@ describe('SDK command migrations', () => { }, } - await expect(opts.execute(sdk)).resolves.toEqual({ txid: 'swap-tx', raw: { result: true } }) + await expect(opts.execute(sdk)).resolves.toEqual({ + txid: 'swap-tx', + route: { + amountIn: '1', + amountOut: '2', + symbols: ['SUN', 'USDT'], + poolVersions: undefined, + impact: undefined, + }, + }) expect(sdk.swap.router.quote).toHaveBeenCalledWith({ tokenIn: 'TWrZRHY9aKQZcyjpovdH6qeCEyYZrRQDZt', tokenOut: 'TXYZopYRdj2D9XRtbG411XZZ3kM5VkAeBf', @@ -122,6 +149,7 @@ describe('SDK command migrations', () => { quote, route: quote.bestRoute, slippage: '0.5%', + transactionValidForSeconds: 3600, }) }) }) diff --git a/test/commands/sunpump-sdk.test.ts b/test/commands/sunpump-sdk.test.ts index 2ba8fa1..f4e4353 100644 --- a/test/commands/sunpump-sdk.test.ts +++ b/test/commands/sunpump-sdk.test.ts @@ -6,6 +6,83 @@ describe('sunpump SDK integration', () => { jest.clearAllMocks() }) + it.each(['buy', 'sell'])( + 'forwards %s minimum and preserves launchpad-only trading', + async (direction) => { + const writeAction = jest.fn().mockResolvedValue(undefined) + const quote = jest + .fn() + .mockResolvedValue({ tokenAmount: '1000', trxAmount: '1000', fee: '0' }) + jest.doMock('../../src/lib/command', () => ({ writeAction, isDryRun: () => true })) + jest.doMock('../../src/lib/context', () => ({ + getNetwork: () => 'mainnet', + getKit: async () => ({ pump: { [direction]: { quote } } }), + })) + jest.doMock('../../src/lib/output', () => ({ + isJsonMode: () => true, + withSpinner: async (_label: string, run: () => Promise) => run(), + })) + const { registerSunpumpCommands } = require('../../src/commands/sunpump') + const program = new Command() + program.exitOverride() + registerSunpumpCommands(program) + await program.parseAsync([ + 'node', + 'sun', + 'sunpump', + direction, + 'TToken', + direction === 'buy' ? '--trx' : '--amount', + '1', + '--min-out', + '990', + ]) + const execute = jest.fn().mockResolvedValue({ txids: ['pump-tx'] }) + await writeAction.mock.calls[0][0].execute({ pump: { [direction]: { execute } } }) + expect(quote).toHaveBeenCalledWith(expect.objectContaining({ launchpadOnly: true })) + expect(execute).toHaveBeenCalledWith( + expect.objectContaining({ + token: 'TToken', + launchpadOnly: true, + [direction === 'buy' ? 'minTokenOut' : 'minTrxOut']: '990', + }), + ) + }, + ) + + it('passes the existing custom API endpoint through to the public SDK client', async () => { + const previous = process.env.SUNPUMP_API_BASE_URL + process.env.SUNPUMP_API_BASE_URL = 'https://example.invalid/custom-pump' + try { + const SunPumpApiClient = jest + .fn() + .mockImplementation(() => ({ searchTokensV2: async () => ({ code: 0, data: [] }) })) + jest.doMock('@sun-protocol/sun-sdk-api', () => ({ SunPumpApiClient })) + jest.doMock('../../src/lib/context', () => ({ getNetwork: () => 'mainnet' })) + jest.doMock('../../src/lib/command', () => ({ + parseApiResponse: (raw: any) => ({ data: raw.data }), + })) + jest.doMock('../../src/lib/output', () => ({ + output: jest.fn(), + outputError: jest.fn(), + isJsonMode: () => true, + withSpinner: async (_label: string, execute: () => Promise) => execute(), + })) + const { registerSunpumpCommands } = require('../../src/commands/sunpump') + const program = new Command() + program.exitOverride() + registerSunpumpCommands(program) + await program.parseAsync(['node', 'sun', 'sunpump', 'token', 'search-v2', 'SUN']) + expect(SunPumpApiClient).toHaveBeenCalledWith({ + network: 'mainnet', + baseUrl: 'https://example.invalid/custom-pump', + }) + } finally { + if (previous === undefined) delete process.env.SUNPUMP_API_BASE_URL + else process.env.SUNPUMP_API_BASE_URL = previous + } + }) + it('rejects every SunPump subcommand on Nile before initializing an SDK client', async () => { const searchTokensV2 = jest.fn().mockResolvedValue({ code: 0, data: { list: [] } }) const SunPumpApiClient = jest.fn().mockImplementation(() => ({ diff --git a/test/contract/sdk-dependency-boundary.test.ts b/test/contract/sdk-dependency-boundary.test.ts index 9f8a750..bdb7c4e 100644 --- a/test/contract/sdk-dependency-boundary.test.ts +++ b/test/contract/sdk-dependency-boundary.test.ts @@ -36,7 +36,7 @@ describe('public SUN SDK dependency boundary', () => { ] for (const name of sdkPackages) { - expect(dependencies[name]).toBe('0.1.1-beta.0') + expect(dependencies[name]).toBe('0.1.1-beta.1') } }) }) diff --git a/test/lib/sdk/compat.test.ts b/test/lib/sdk/compat.test.ts index 07b148b..532c736 100644 --- a/test/lib/sdk/compat.test.ts +++ b/test/lib/sdk/compat.test.ts @@ -1,6 +1,86 @@ import { toCliTxResult } from '../../../src/lib/sdk/compat' describe('SDK result compatibility', () => { + const execution = (metadata: Record) => ({ + plan: { metadata }, + txids: ['approval', 'tx'], + finalResult: { type: 'transaction', txid: 'tx', raw: { result: true } }, + }) + + it('restores legacy Pump business fields rather than exposing the internal execution plan', () => { + expect( + toCliTxResult( + execution({ + operation: 'launchpad-buy-exact-trx', + token: 'token', + trxAmount: '100', + quotedTokenAmount: '1000', + minTokenOut: '0', + }), + ), + ).toEqual({ + txResult: { result: true, txid: 'tx' }, + tokenAddress: 'token', + trxSpent: '100', + expectedTokens: '1000', + minTokenOut: '0', + }) + expect( + toCliTxResult( + execution({ + operation: 'launchpad-sell-exact-token', + token: 'token', + tokenAmount: '100', + quotedTrxAmount: '900', + minTrxOut: '800', + }), + ), + ).toEqual({ + txResult: { result: true, txid: 'tx' }, + tokenAddress: 'token', + tokensSold: '100', + expectedTrx: '900', + minTrxOut: '800', + }) + }) + + it('restores liquidity output fields without leaking other SDK metadata', () => { + const computedTicks = { tickLower: -1000, tickUpper: 1000 } + const computedAmounts = { amount0Desired: '100', amount1Desired: '200' } + expect( + toCliTxResult( + execution({ + protocol: 'sunswap-v4', + operation: 'mint-position', + computedTicks, + computedAmounts, + createPool: false, + deadline: '123', + }), + ), + ).toEqual({ + txResult: { result: true, txid: 'tx' }, + computedTicks, + computedAmounts, + poolCreated: false, + }) + expect( + toCliTxResult( + execution({ + protocol: 'sunswap-v3', + operation: 'collect-position', + estimatedFees: { amount0: '1', amount1: '2' }, + }), + ), + ).toEqual({ + txResult: { result: true, txid: 'tx' }, + estimatedFees: { amount0: '1', amount1: '2' }, + }) + expect( + toCliTxResult(execution({ protocol: 'sunswap-v3', operation: 'remove-position' })), + ).toEqual({ result: true, txid: 'tx' }) + }) + it('keeps nested txResult txid shape for explorer enrichment', () => { expect(toCliTxResult({ txResult: { txid: 'abc' }, raw: { result: true } })).toEqual({ txResult: { txid: 'abc' }, @@ -23,7 +103,7 @@ describe('SDK result compatibility', () => { }), ).toEqual({ txid: 'last', - raw: { result: true }, + result: true, }) }) }) diff --git a/test/lib/sdk/factory.test.ts b/test/lib/sdk/factory.test.ts index 683bcaa..650206e 100644 --- a/test/lib/sdk/factory.test.ts +++ b/test/lib/sdk/factory.test.ts @@ -29,4 +29,13 @@ describe('SDK factory', () => { 'Unsupported SDK network: shasta', ) }) + + it('uses the legacy Shasta RPC for generic calls without configuring SUN protocol contracts', async () => { + const { createGenericRuntime, createReadonlyTronWeb } = require('../../../src/lib/sdk/factory') + const tronWeb = createReadonlyTronWeb({ network: 'shasta' }) + expect(tronWeb.fullNode.host).toBe('https://api.shasta.trongrid.io') + const runtime = await createGenericRuntime({ network: 'shasta' }) + expect(runtime.network).toBe('shasta') + expect(runtime.chain).toBeUndefined() + }) }) diff --git a/test/lib/sdk/liquidity-v3.test.ts b/test/lib/sdk/liquidity-v3.test.ts new file mode 100644 index 0000000..da713fe --- /dev/null +++ b/test/lib/sdk/liquidity-v3.test.ts @@ -0,0 +1,72 @@ +import { resolveLegacyV3Pool } from '../../../src/lib/sdk/liquidity-v3' + +describe('legacy CLI V3 deployment', () => { + it('uses the supplied PM ABI for the legacy single-side positions read', async () => { + const abi = [{ type: 'function', name: 'positions', inputs: [{ type: 'uint256' }] }] + const pm = 'TPQzqHbCzQfoVdAV6bLwGDos8Lk2UjXz2R' + const token0 = 'TYsbWxNnyTgsZaTFaue9hqpxkU3Fkco94a' + const token1 = 'TXYZopYRdj2D9XRtbG411XZZ3kM5VkAeBf' + const read = jest + .fn() + .mockResolvedValueOnce({ token0, token1, fee: '500' }) + .mockResolvedValueOnce('TE6MBGu85TcLvDpxMM9PjVobWi1kxDkjcW') + const multicall = jest.fn().mockResolvedValue([ + { success: true, value: ['123', 25700] }, + { success: true, value: '456' }, + { success: true, value: '10' }, + ]) + await resolveLegacyV3Pool( + { network: 'nile', runtime: { contract: { read }, multicall } } as never, + { + tokenId: '706', + positionManager: pm, + positionAbi: JSON.stringify(abi), + }, + ) + expect(read).toHaveBeenNthCalledWith(1, { + address: pm, + abi, + functionName: 'positions', + args: ['706'], + }) + }) + + it('rejects a missing legacy pool without substituting an SDK deployment', async () => { + const read = jest.fn().mockResolvedValue('T9yD14Nj9j7xAB4dbGeiX9h8unkKHxuWwb') + await expect( + resolveLegacyV3Pool({ network: 'nile', runtime: { contract: { read } } } as never, { + token0: 'TYsbWxNnyTgsZaTFaue9hqpxkU3Fkco94a', + token1: 'TXYZopYRdj2D9XRtbG411XZZ3kM5VkAeBf', + fee: 500, + }), + ).rejects.toThrow('V3 pool not found') + }) + + it('uses the legacy factory getPool result instead of deriving a different pool from SDK defaults', async () => { + const token0 = 'TYsbWxNnyTgsZaTFaue9hqpxkU3Fkco94a' + const token1 = 'TXYZopYRdj2D9XRtbG411XZZ3kM5VkAeBf' + const pool = 'TE6MBGu85TcLvDpxMM9PjVobWi1kxDkjcW' + const read = jest.fn().mockResolvedValue(pool) + const multicall = jest.fn().mockResolvedValue([ + { success: true, value: { sqrtPriceX96: '123', tick: 25700 } }, + { success: true, value: '456' }, + { success: true, value: '10' }, + ]) + const sdk = { network: 'nile', runtime: { contract: { read }, multicall } } + await expect(resolveLegacyV3Pool(sdk as never, { token0, token1, fee: 500 })).resolves.toEqual({ + sqrtRatioX96: '123', + tickCurrent: 25700, + liquidity: '456', + tickSpacing: 10, + }) + expect(read).toHaveBeenCalledWith( + expect.objectContaining({ + address: 'TLJWAScHZ4Qmk1axyKMzrnoYuu2pSLer1F', + functionSelector: 'getPool(address,address,uint24)', + }), + ) + expect( + multicall.mock.calls[0][0].every((call: { target: string }) => call.target === pool), + ).toBe(true) + }) +})