Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "chain-pilot"
version = "1.1.0"
version = "1.2.0"
description = "A CLI tool for on-chain DeFi operations on EVM-compatible networks"
homepage = "https://github.com/DODOEX/ChainPilot"
documentation = "https://github.com/DODOEX/ChainPilot#readme"
Expand Down
63 changes: 54 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ Supported environment variables are intentionally limited. Runtime config is rea
| `KEYSTORE_PASSWORD_FILE` | `--password-file` | — | Read keystore password from file |
| `KEYSTORE_PASSWORD_ENV` | `--password-env` | — | Read keystore password from named env var |
| `KEYSTORE_PASSWORD` | — | — | Default env var used for keystore password |
| `WALLET_ADDRESS` | `--wallet-address` | — | Wallet address for balance/simulate context |
| `WALLET_ADDRESS` | `--wallet-address` | — | Wallet address for balance/simulate and dry-run sender fallback |
| `--rpc-url` | CLI only | Chain's built-in public RPC | Explicit JSON-RPC override |
| `CHAIN_ID` | `--chain-id` | `1` (Ethereum mainnet) | Active chain ID |
| `DODO_API_KEY` | — | Compiled-in default | DODO routing API key |
Expand Down Expand Up @@ -220,7 +220,7 @@ Simulation is read-only — it costs no gas and does not broadcast any transacti

**Execute a swap:**
```bash
# Dry-run: build and simulate the transaction without broadcasting
# Dry-run: build an unsigned external-signer payload without broadcasting
chainpilot swap execute --quote-id <QUOTE_ID> --dry-run --wallet 0xYourAddress

# Live execution with a raw private key
Expand All @@ -245,12 +245,57 @@ chainpilot swap execute --quote-id <QUOTE_ID> --private-key 0x... --skip-estimat

| Execute flag | Description |
|---------------------|--------------------------------------------------------------------------|
| `--dry-run` | Build and simulate the tx without broadcasting; `--wallet` instead of key |
| `--dry-run` | Build an unsigned external-signer payload without signing or broadcasting; `--wallet` instead of key |
| `--wait` | Block until the tx is mined and show final on-chain status |
| `--gas-limit` | Hard override for gas limit |
| `--max-fee-gwei` | Override max fee per gas (EIP-1559), in gwei |
| `--gas-buffer-pct` | Add N% buffer on top of `eth_estimateGas` result (e.g. `20` = +20%) |
| `--skip-estimate` | Skip `eth_estimateGas` and use the quote's gas estimate directly |
| `--gas-limit` | Hard override for gas limit; included in dry-run payloads when set |
| `--max-fee-gwei` | Override max fee per gas (EIP-1559), in gwei; included in dry-run payloads as wei hex when set |
| `--gas-buffer-pct` | Live execution only: add N% buffer on top of `eth_estimateGas` result (e.g. `20` = +20%) |
| `--skip-estimate` | Live execution only: skip `eth_estimateGas` and use the quote's gas estimate directly |

**External signer dry-run contract:**
```bash
# Swap execution payload from a saved quote
chainpilot --json swap execute --quote-id <QUOTE_ID> --dry-run --wallet 0xYourAddress

# ERC-20 approval payload from a saved quote
chainpilot --json swap approve --quote-id <QUOTE_ID> --dry-run --wallet-address 0xYourAddress

# Explicit ERC-20 approval payload
chainpilot --json swap approve \
--token USDC \
--spender 0xSpenderAddr \
--amount 100 \
--dry-run \
--wallet-address 0xYourAddress

# ERC-20 revoke payload
chainpilot --json swap revoke \
--token 0xTokenAddr \
--spender 0xSpenderAddr \
--dry-run \
--wallet-address 0xYourAddress
```

In JSON mode, dry-run swap/approve/revoke responses do not sign or broadcast.
They include the legacy preview fields plus a stable external-signer payload:
`source`, `operation`, `chain_id`, `caip2`, `from`,
`transaction { to, value, data, chain_id }`, optional `quote`, and `risk`
metadata. `operation` is one of `swap_execute`, `approve`, or `revoke`.
For swaps, pass the dry-run wallet with `--wallet`; if omitted, execute dry-run
falls back to the global `--wallet-address` / `WALLET_ADDRESS` sender context.
For approve/revoke, pass the sender with the global `--wallet-address`.
Approve and revoke dry-runs include ERC-20 `approve(address,uint256)` calldata
in `transaction.data`. Systems such as
Privy should still apply their own authorization, confirmation, budget, and risk
checks before submitting the transaction.
All external-signer dry-runs require a sender wallet and fail instead of
omitting `data.from` or `data.transaction` when no sender can be resolved.
Execute dry-run resolves the sender from signer config, `--wallet`, or global
`--wallet-address` / `WALLET_ADDRESS`; approve/revoke dry-runs resolve it from
signer config or global `--wallet-address` / `WALLET_ADDRESS`.
`--gas-limit` and `--max-fee-gwei` are reflected in the unsigned swap
transaction payload; dry-run does not call `eth_estimateGas`, so
`--gas-buffer-pct` and `--skip-estimate` only affect live execution.

**Check transaction status:**
```bash
Expand Down Expand Up @@ -281,7 +326,7 @@ chainpilot swap approve --token USDC --spender 0x... --amount 1000 --private-key
chainpilot swap approve --token USDC --spender 0x... --private-key 0x...

# Dry-run to preview without sending
chainpilot swap approve --quote-id <QUOTE_ID> --dry-run
chainpilot --json swap approve --quote-id <QUOTE_ID> --dry-run --wallet-address 0xYourAddress
```

**Revoke an approval:**
Expand All @@ -292,7 +337,7 @@ chainpilot swap revoke --token 0xTokenAddr --spender 0xSpenderAddr --private-key
chainpilot --keystore-path ~/.chainpilot/main.json swap revoke --token 0xTokenAddr --spender 0xSpenderAddr

# Dry-run
chainpilot swap revoke --token 0xTokenAddr --spender 0xSpenderAddr --dry-run
chainpilot --json swap revoke --token 0xTokenAddr --spender 0xSpenderAddr --dry-run --wallet-address 0xYourAddress
```

### Token
Expand Down
61 changes: 52 additions & 9 deletions README_CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ DODO_PROJECT_ID=your-id
| `KEYSTORE_PASSWORD_FILE` | `--password-file` | — | 从文件读取 keystore 密码 |
| `KEYSTORE_PASSWORD_ENV` | `--password-env` | — | 从指定环境变量读取 keystore 密码 |
| `KEYSTORE_PASSWORD` | — | — | keystore 密码的默认环境变量 |
| `WALLET_ADDRESS` | `--wallet-address` | — | 余额查询 / 模拟时使用的钱包地址 |
| `WALLET_ADDRESS` | `--wallet-address` | — | 余额查询 / 模拟 / dry-run sender fallback 钱包地址 |
| `--rpc-url` | 仅 CLI | 链内置公共 RPC | 显式覆盖 JSON-RPC 端点 |
| `CHAIN_ID` | `--chain-id` | `1`(以太坊主网) | 当前链 ID |
| `DODO_API_KEY` | — | 编译时内嵌默认值 | DODO 路由 API Key |
Expand Down Expand Up @@ -212,7 +212,7 @@ chainpilot swap simulate --quote-id <QUOTE_ID> --wallet 0xYourAddress

**执行兑换:**
```bash
# 演习模式:构建并模拟交易,不广播
# 演习模式:构建未签名的外部 signer payload,不广播
chainpilot swap execute --quote-id <QUOTE_ID> --dry-run --wallet 0xYourAddress

# 用裸私钥正式执行
Expand All @@ -237,12 +237,55 @@ chainpilot swap execute --quote-id <QUOTE_ID> --private-key 0x... --skip-estimat

| 执行标志 | 说明 |
|---------------------|-------------------------------------------------------------------|
| `--dry-run` | 构建并模拟交易,不广播;使用 `--wallet` 代替私钥 |
| `--dry-run` | 构建未签名的外部 signer payload,不签名也不广播;使用 `--wallet` 代替私钥 |
| `--wait` | 阻塞直到交易上链,显示最终链上状态 |
| `--gas-limit` | 硬覆盖 Gas 上限 |
| `--max-fee-gwei` | 覆盖 EIP-1559 最大 Gas 费用(gwei) |
| `--gas-buffer-pct` | 在 `eth_estimateGas` 结果上添加 N% 缓冲(如 `20` = +20%) |
| `--skip-estimate` | 跳过 `eth_estimateGas`,直接使用报价中的 Gas 估算 |
| `--gas-limit` | 硬覆盖 Gas 上限;设置后会写入 dry-run payload |
| `--max-fee-gwei` | 覆盖 EIP-1559 最大 Gas 费用(gwei);设置后会以 wei hex 写入 dry-run payload |
| `--gas-buffer-pct` | 仅 live execution 生效:在 `eth_estimateGas` 结果上添加 N% 缓冲(如 `20` = +20%) |
| `--skip-estimate` | 仅 live execution 生效:跳过 `eth_estimateGas`,直接使用报价中的 Gas 估算 |

**外部 signer dry-run contract:**
```bash
# 从已保存报价生成兑换执行 payload
chainpilot --json swap execute --quote-id <QUOTE_ID> --dry-run --wallet 0xYourAddress

# 从已保存报价生成 ERC-20 授权 payload
chainpilot --json swap approve --quote-id <QUOTE_ID> --dry-run --wallet-address 0xYourAddress

# 显式指定 ERC-20 授权 payload
chainpilot --json swap approve \
--token USDC \
--spender 0xSpenderAddr \
--amount 100 \
--dry-run \
--wallet-address 0xYourAddress

# ERC-20 revoke payload
chainpilot --json swap revoke \
--token 0xTokenAddr \
--spender 0xSpenderAddr \
--dry-run \
--wallet-address 0xYourAddress
```

JSON 模式下,swap / approve / revoke 的 dry-run 不会签名或广播交易。
响应会保留原有预览字段,并额外包含稳定的外部 signer payload:
`source`、`operation`、`chain_id`、`caip2`、`from`、
`transaction { to, value, data, chain_id }`、可选 `quote` 和 `risk` metadata。
`operation` 取值为 `swap_execute`、`approve` 或 `revoke`。兑换执行优先使用
`--wallet` 传入 dry-run 钱包;如果省略,会 fallback 到全局
`--wallet-address/WALLET_ADDRESS` sender context。approve / revoke 使用全局
`--wallet-address`。approve / revoke dry-run 会在 `transaction.data` 中返回
ERC-20 `approve(address,uint256)` calldata。Privy 等外部 signer 系统仍需要在
提交前自行执行授权、确认、预算和风险校验。
所有外部 signer dry-run 都必须能解析出 sender wallet;无法解析时会失败,
而不是省略 `data.from` 或 `data.transaction`。execute dry-run 会从 signer
配置、`--wallet` 或全局 `--wallet-address/WALLET_ADDRESS` 解析 sender;
approve / revoke dry-run 会从 signer 配置或全局
`--wallet-address/WALLET_ADDRESS` 解析 sender。`--gas-limit` 和
`--max-fee-gwei` 会反映到未签名 swap 交易 payload 中;dry-run 不调用
`eth_estimateGas`,因此 `--gas-buffer-pct` 和 `--skip-estimate` 只影响 live
execution。

**查询交易状态:**
```bash
Expand Down Expand Up @@ -273,7 +316,7 @@ chainpilot swap approve --token USDC --spender 0x... --amount 1000 --private-key
chainpilot swap approve --token USDC --spender 0x... --private-key 0x...

# 演习模式,预览但不发送
chainpilot swap approve --quote-id <QUOTE_ID> --dry-run
chainpilot --json swap approve --quote-id <QUOTE_ID> --dry-run --wallet-address 0xYourAddress
```

**撤销授权:**
Expand All @@ -284,7 +327,7 @@ chainpilot swap revoke --token 0xTokenAddr --spender 0xSpenderAddr --private-key
chainpilot --keystore-path ~/.chainpilot/main.json swap revoke --token 0xTokenAddr --spender 0xSpenderAddr

# 演习模式
chainpilot swap revoke --token 0xTokenAddr --spender 0xSpenderAddr --dry-run
chainpilot --json swap revoke --token 0xTokenAddr --spender 0xSpenderAddr --dry-run --wallet-address 0xYourAddress
```

### Token(代币)
Expand Down
78 changes: 70 additions & 8 deletions skills/chainpilot/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ chainpilot [GLOBAL_FLAGS] <COMMAND> [SUBcommand_FLAGS]
| `--keystore-path <path>` | `KEYSTORE_PATH` | — | Encrypted JSON keystore for write transactions |
| `--password-file <path>` | `KEYSTORE_PASSWORD_FILE` | — | Read keystore password from file |
| `--password-env <NAME>` | `KEYSTORE_PASSWORD_ENV` | — | Read keystore password from the named env var |
| `--wallet-address <addr>` | `WALLET_ADDRESS` | — | Read-only wallet context |
| `--wallet-address <addr>` | `WALLET_ADDRESS` | — | Read-only wallet context and dry-run sender fallback |
| `--rpc-url <url>` | — | Chain's public RPC | Explicit JSON-RPC override |
| `--chain-id <id>` | `CHAIN_ID` | `1` | Global chain context |

Expand Down Expand Up @@ -215,6 +215,60 @@ Quotes have a **dual TTL**: the DODO-issued route expires in 20 minutes; the
local default TTL is 18 minutes and expires first. Both `simulate` and
`execute` reject stale quotes.

### External signer dry-run workflow

Use `--json --dry-run` when an external wallet service needs an unsigned
transaction payload instead of ChainPilot signing locally. Dry-run output is not
execution: it does not sign, broadcast, or return a transaction hash.

```bash
# Swap execution payload from a saved quote
chainpilot --json swap execute --quote-id <ID> --dry-run --wallet <ADDR>

# ERC-20 approval payload from a saved quote
chainpilot --json swap approve --quote-id <ID> --dry-run --wallet-address <ADDR>

# Explicit ERC-20 approval payload
chainpilot --json swap approve \
--token USDC \
--spender 0xSpenderAddr \
--amount 100 \
--dry-run \
--wallet-address <ADDR>

# ERC-20 revoke payload
chainpilot --json swap revoke \
--token 0xTokenAddr \
--spender 0xSpenderAddr \
--dry-run \
--wallet-address <ADDR>
```

The JSON `data` object includes the legacy preview fields plus:

| Field | Notes |
|---|---|
| `source` | Always `chainpilot` |
| `operation` | `swap_execute`, `approve`, or `revoke` |
| `chain_id` / `caip2` | EVM chain identity |
| `from` | Wallet address supplied for dry-run |
| `transaction.to` | Router for swaps; token contract for approve/revoke |
| `transaction.value` | Native value as hex, e.g. `0x0` |
| `transaction.data` | Calldata; approve/revoke use ERC-20 `approve(address,uint256)` |
| `transaction.chain_id` | Must match `chain_id` |
| `quote` | Present for quote-derived swap execution |
| `risk` | Token, amount, spender/router, and gas metadata when available |

All external-signer dry-run commands require a sender wallet. For
`swap execute --dry-run`, use `--wallet`; if it is omitted, the command falls
back to the global `--wallet-address` / `WALLET_ADDRESS` sender context. For
approve/revoke dry-runs, use the global `--wallet-address` / `WALLET_ADDRESS`
context. If no sender can be resolved, stop on the CLI error instead of
treating the payload as usable.

External signer integrations must still perform their own authorization,
confirmation, budget, and risk checks before submitting the transaction.

---

## `swap` Subcommands
Expand Down Expand Up @@ -304,8 +358,11 @@ chainpilot --keystore-path /path/to/keystore.json swap approve --token USDC --sp
# Unlimited approval (omit --amount), using the configured signer context
chainpilot swap approve --token USDC --spender 0x...

# Dry-run (no tx sent, signer not needed)
chainpilot swap approve --quote-id <ID> --dry-run
# Dry-run external-signer payload from a saved quote
chainpilot --json swap approve --quote-id <ID> --dry-run --wallet-address <ADDR>

# Dry-run external-signer payload with explicit token/spender/amount
chainpilot --json swap approve --token USDC --spender 0x... --amount 1000 --dry-run --wallet-address <ADDR>
```

### `swap execute`
Expand All @@ -325,12 +382,15 @@ chainpilot --keystore-path /path/to/keystore.json swap execute --quote-id <ID> [
# Non-interactive keystore execution
chainpilot --keystore-path /path/to/keystore.json --password-file /path/to/keystore.pass \
swap execute --quote-id <ID> [OPTIONS]

# Dry-run external-signer payload from a saved quote
chainpilot --json swap execute --quote-id <ID> --dry-run --wallet <ADDR>
```

| Flag | Description |
|---|---|
| `--dry-run` | Simulate execution without broadcasting — use `--wallet` instead of a signer |
| `--wallet <ADDR>` | Wallet address for dry-run (not `--wallet-address`) |
| `--dry-run` | Build an unsigned swap transaction payload without broadcasting — use `--wallet` or global wallet context instead of a signer |
| `--wallet <ADDR>` | Preferred wallet address for execute dry-run payloads; falls back to global `--wallet-address` / `WALLET_ADDRESS` when omitted |
| `--wait` | Block until mined, print final on-chain status |
| `--gas-limit <N>` | Hard-override gas limit |
| `--max-fee-gwei <N>` | Override EIP-1559 max fee (in gwei) |
Expand All @@ -341,11 +401,13 @@ chainpilot --keystore-path /path/to/keystore.json --password-file /path/to/keyst

```bash
chainpilot swap revoke --token <ADDR> --spender <ADDR> [--dry-run]
chainpilot swap revoke --token USDC --spender 0xRouter
chainpilot --keystore-path /path/to/keystore.json swap revoke --token USDC --spender 0xRouter
chainpilot swap revoke --token 0xTokenAddr --spender 0xRouter
chainpilot --keystore-path /path/to/keystore.json swap revoke --token 0xTokenAddr --spender 0xRouter
chainpilot --json swap revoke --token 0xTokenAddr --spender 0xSpenderAddr --dry-run --wallet-address <ADDR>
```

- `--dry-run`: dry-run mode, signer not required.
- `--dry-run`: build an unsigned ERC-20 revoke payload without sending; use
`--wallet-address` to populate `data.from`.

### `swap status`

Expand Down
3 changes: 2 additions & 1 deletion src/cli/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,8 @@ pub struct Cli {
#[arg(long, env = "KEYSTORE_PASSWORD_ENV", global = true)]
pub password_env: Option<String>,

/// Wallet address to use for quote/simulate context (overrides WALLET_ADDRESS env var)
/// Wallet address for quote/simulate context and dry-run sender fallback
/// (overrides WALLET_ADDRESS env var)
#[arg(long, env = "WALLET_ADDRESS", global = true)]
pub wallet_address: Option<String>,

Expand Down
Loading
Loading