@subly_fi/pay 0.8.0 → 0.8.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -4,72 +4,214 @@ CLI and stdio MCP client for paying compatible x402 APIs with Kamino USDC vault
4
4
 
5
5
  Version 0.8 is beta software and has not had an external security audit. Vault operations use real mainnet funds. Yield accounting and owner policies depend on your relayer operator. Read the [security model](https://github.com/SublyFi/subly-payment-protocol/blob/main/docs/security-model.md).
6
6
 
7
+ [日本語の導入ガイド](https://github.com/SublyFi/subly-payment-protocol/blob/main/docs/getting-started.ja.md) · [Set up with an AI assistant](https://github.com/SublyFi/subly-payment-protocol/blob/main/docs/ai-setup-prompts.md)
8
+
7
9
  ## Quick start
8
10
 
9
- You need **Node.js 24+**, a Solana agent wallet with mainnet USDC, a trusted relayer URL, and a mainnet RPC endpoint that supports transaction simulation with inner instructions. Your wallet can be a local keypair or a supported custody signer. The owner who approves spending controls can use a passkey or a separate Solana wallet.
11
+ Follow the steps in order. Every Subly command below uses `npx`; no repository clone or global `pay` installation is needed. Replace example URLs, paths and IDs with your own values.
12
+
13
+ ### 1. Prepare the software and endpoints
14
+
15
+ - Install **Node.js 24+ with npm** from the [official download page](https://nodejs.org/en/download). Open a new terminal and check `node --version` and `npm --version`.
16
+ - Obtain a **trusted relayer's HTTPS URL**, running version 0.8.0 or newer. This guide supplies no guaranteed public endpoint. To operate one yourself, use the [operator guide](https://github.com/SublyFi/subly-payment-protocol/tree/main/deploy).
17
+ - Obtain a **Solana mainnet RPC URL** supporting transaction simulation with inner instructions. Keep RPC credentials in local configuration, not public issues or chat.
18
+ - Use a **dedicated agent wallet** with USDC on Solana mainnet. This wallet holds funds and signs transactions. The human owner's passkey or separate wallet approves spending controls. Supported custody signers are described under [Configuration](#configuration).
10
19
 
11
- ```bash
12
- npx -y @subly_fi/pay@0.8.0 --help
13
- export SUBLY_RELAYER_URL=https://your-relayer.example.com
14
- export SOLANA_RPC_URL=https://your-mainnet-rpc.example.com
15
- export SUBLY_DEMO_AGENT_KEYPAIR_PATH=/absolute/path/to/agent.json
16
- npx -y @subly_fi/pay@0.8.0 doctor
17
- npx -y @subly_fi/pay@0.8.0 vaults
20
+ ```sh
21
+ npx -y @subly_fi/pay@0.8.1 --version
22
+ npx -y @subly_fi/pay@0.8.1 --help
18
23
  ```
19
24
 
20
- Use an existing dedicated agent wallet, or create one with `solana-keygen new -o agent.json`. Keep its recovery material private and restrict the file to its owner (`chmod 600 agent.json`). Fund its public address with **USDC on Solana mainnet**. Subly does not create or fund wallets. Vault fees require a funded relayer sponsor; the final API payment requires the seller's facilitator fee payer.
25
+ The version should be `0.8.1`. A help screen alone does not check your wallet or endpoints.
26
+
27
+ ### 2. Prepare and fund the agent wallet
28
+
29
+ If you already have a dedicated **64-byte Solana JSON keypair**, use its absolute file path and skip creation. Do not overwrite an existing keypair.
30
+
31
+ To create one, install the Solana CLI using its [official installation guide](https://solana.com/docs/intro/installation). Subly does not install that CLI. On Windows, the official guide uses WSL: you can keep this entire terminal workflow in WSL and use the macOS/Linux examples below. Native PowerShell settings are also shown for a keypair accessible to Windows.
32
+
33
+ Run wallet creation yourself in a **private terminal**, not through an AI tool that records command output: `solana-keygen new` displays the recovery phrase. Do not copy its output into chat. In a macOS, Linux or WSL terminal:
34
+
35
+ ```sh
36
+ (
37
+ set -eu
38
+ command -v solana-keygen >/dev/null
39
+ umask 077
40
+ mkdir -p "$HOME/.subly"
41
+ if [ -e "$HOME/.subly/agent.json" ]; then
42
+ printf '%s\n' 'Keypair already exists; use it or choose another path.' >&2
43
+ exit 1
44
+ fi
45
+ solana-keygen new --outfile "$HOME/.subly/agent.json"
46
+ chmod 600 "$HOME/.subly/agent.json"
47
+ solana-keygen pubkey "$HOME/.subly/agent.json"
48
+ )
49
+ ```
21
50
 
22
- 1. Review the selected vault, its curator, fees and liquidity. For a catalogue supplied by your operator, install the reviewed file locally and set `SUBLY_VAULTS_FILE=/absolute/path/vaults.json`. `SUBLY_VAULT_ADDRESS` selects one listed vault for CLI commands. Never install transaction trust anchors merely because a remote response says to.
23
- 2. Create an owner setup link. On the first owner registration, this example pre-approves a **1.01 USDC** deposit; the selected vault's minimum can differ:
51
+ The last command prints the **public receiving address**. Save the recovery phrase privately; never paste it, the JSON file contents or a private key into an AI chat. The keypair file contains signing secrets even if wallet creation asked for a recovery passphrase. Restrict it to your OS account; for an existing Windows file, review its Security properties.
24
52
 
25
- ```bash
26
- npx -y @subly_fi/pay@0.8.0 setup-link --initial-deposit 1010000
27
- ```
53
+ Send **USDC on Solana mainnet** to that public address from your existing wallet or exchange, and confirm arrival. The example below deposits **1.01 USDC**; the selected vault's minimum may differ. Subly does not fund wallets. The relayer sponsor pays vault transaction fees; the seller's facilitator supplies the final API payment's fee payer. Ask the operator if sponsorship is unavailable.
28
54
 
29
- Open the returned `setupUrl`, review the wallet, vault and limits, then approve with your passkey or wallet. Links expire in 10 minutes. Treat setup and approval links as private capabilities. The first person completing an initial setup becomes the owner for that wallet/vault. Replacing an existing mandate requires a separate deposit approval; follow the approval link returned by the deposit command.
30
- 3. Check completion and deposit promptly; initial deposit approval lasts about 15 minutes:
55
+ ### 3. Configure this terminal and check it
31
56
 
32
- ```bash
33
- npx -y @subly_fi/pay@0.8.0 setup-status <sessionId>
34
- npx -y @subly_fi/pay@0.8.0 deposit 1010000
35
- npx -y @subly_fi/pay@0.8.0 budget
36
- ```
57
+ Choose one environment example. Settings apply to the current terminal; a new terminal or desktop MCP host needs its own configuration. Keep the same wallet, selected vault, relayer and pending-state path when continuing a payment. RPC URLs often contain API keys: enter the URL yourself at the hidden prompt in your private terminal, not in an AI chat or AI tool input. These examples keep it out of the command text and shell history.
37
58
 
38
- 4. Wait until **spendable yield** covers the price and vault fees. A new deposit does not immediately provide a payment budget. Then request a compatible API:
59
+ macOS/Linux/WSL (Bash or zsh):
39
60
 
40
- ```bash
41
- npx -y @subly_fi/pay@0.8.0 fetch https://seller.example.com/paid-resource
42
- ```
61
+ ```sh
62
+ export SUBLY_RELAYER_URL="https://your-relayer.example.com"
63
+ printf 'Solana mainnet RPC URL (hidden): '
64
+ read -r -s SOLANA_RPC_URL
65
+ printf '\n'
66
+ export SOLANA_RPC_URL
67
+ export SUBLY_DEMO_AGENT_KEYPAIR_PATH="$HOME/.subly/agent.json"
68
+ export SUBLY_MCP_STATE_PATH="$HOME/.subly/standard-x402-pending.json"
69
+ ```
70
+
71
+ Windows PowerShell, with a keypair already stored at this Windows path:
72
+
73
+ ```powershell
74
+ $env:SUBLY_RELAYER_URL = "https://your-relayer.example.com"
75
+ $sublyRpcSecret = Read-Host "Solana mainnet RPC URL" -AsSecureString
76
+ $env:SOLANA_RPC_URL = [System.Net.NetworkCredential]::new("", $sublyRpcSecret).Password
77
+ Remove-Variable sublyRpcSecret
78
+ $env:SUBLY_DEMO_AGENT_KEYPAIR_PATH = "$env:USERPROFILE\.subly\agent.json"
79
+ $env:SUBLY_MCP_STATE_PATH = "$env:USERPROFILE\.subly\standard-x402-pending.json"
80
+ ```
81
+
82
+ Use absolute paths valid where the client runs. Windows and WSL home directories differ; switching must not silently create a second pending-state file for the same wallet. If PowerShell blocks `npx.ps1`, invoke `npx.cmd` with the same arguments instead of changing the machine's execution policy.
83
+
84
+ For a custom operator catalogue, review and install the file, then set `SUBLY_VAULTS_FILE` to its absolute path using your shell's syntax above. `SUBLY_VAULT_ADDRESS` selects one listed vault. Configure this before checking the relayer; never install transaction trust anchors merely because a remote response says to.
85
+
86
+ ```sh
87
+ npx -y @subly_fi/pay@0.8.1 doctor
88
+ npx -y @subly_fi/pay@0.8.1 vaults
89
+ ```
43
90
 
44
- 5. Withdraw funds back to the same agent wallet when needed:
91
+ Continue when `doctor` returns `"ok": true` and the selected local vault matches the operator's catalogue. It checks configuration and reachability, not balance, simulation support, available yield or vault safety. Review the vault's curator, fees, minimum deposit and liquidity with the operator. `vaults` prints trusted local metadata, not a live balance.
45
92
 
46
- ```bash
47
- npx -y @subly_fi/pay@0.8.0 withdraw 1000000
48
- ```
93
+ ### 4. Register the owner and approve the first deposit
49
94
 
50
- All amounts are raw USDC integers: `1000000` = 1 USDC. `fetch` defaults to a **0.01 USDC cap**, configurable with `SUBLY_MCP_MAX_AMOUNT_RAW_USDC` or `fetch <URL> <capRawUSDC>`. The owner mandate may set stricter limits. `setup-link --help` lists policy options. A withdrawal can include principal and is subject to liquidity, fees and the owner's policy. A revoked mandate also blocks relayer withdrawals.
95
+ Use the same raw amount for setup and deposit. Amounts are six-decimal USDC integers: `1000000` = 1 USDC, `1010000` = 1.01 USDC, `10000` = 0.01 USDC.
51
96
 
52
- A subsequent deposit/payment/withdrawal may return `approvalRequired` with an `approveUrl`. After the owner approves, retry the same operation with the returned `apr_...` as a trailing argument. Never automatically retry a transaction reported as submitted or an API payment with an unknown outcome.
97
+ ```sh
98
+ npx -y @subly_fi/pay@0.8.1 setup-link --initial-deposit 1010000
99
+ ```
100
+
101
+ The result contains `sessionId` and `setupUrl`. Open `setupUrl` on your device, review the wallet, vault and limits, then approve with your passkey or owner wallet. Links expire in 10 minutes. Treat them as private capabilities: the first person completing initial setup becomes the owner for that wallet/vault.
102
+
103
+ After approval, **return to this terminal**, replacing the placeholder with the returned ID:
104
+
105
+ ```sh
106
+ npx -y @subly_fi/pay@0.8.1 setup-status st_YOUR_SESSION_ID
107
+ ```
108
+
109
+ Continue only on `"status": "completed"`. `pending` means approval is unfinished; `expired` means create a fresh setup link. On first registration, `initialDepositApproval` should be present and approved. Deposit promptly: it lasts about 15 minutes. Browser approval saves authorization; it does not run a CLI command. With MCP, tell the agent that approval is complete so it can check and continue.
110
+
111
+ Review policy options **before first registration**:
112
+
113
+ ```sh
114
+ npx -y @subly_fi/pay@0.8.1 setup-link --help
115
+ ```
116
+
117
+ **Current limitation:** another setup link does not update an existing active or revoked passkey mandate. CLI/MCP has no passkey policy-change, owner-recovery or revoke-reversal workflow. Ask the operator about the supported low-level procedure; do not assume a new link or credential can unlock it. Revocation also blocks relayer withdrawals. An existing wallet owner can re-sign where allowed, but replacement requires a separate deposit approval.
118
+
119
+ ### 5. Deposit and inspect the budget
120
+
121
+ ```sh
122
+ npx -y @subly_fi/pay@0.8.1 deposit 1010000
123
+ ```
124
+
125
+ Success prints `status: confirmed`, a `depositId`, a transaction link and the confirmed amount. Keep the ID. For `submitted`, use the [status procedure](#check-an-interrupted-deposit-or-withdrawal); the transaction may still land, so do not deposit again.
126
+
127
+ ```sh
128
+ npx -y @subly_fi/pay@0.8.1 budget
129
+ ```
53
130
 
54
- Status lookup requires a **0.8.0 or newer relayer** so it can disable rebroadcast of an unresolved transaction with the authenticated `?resubmit=false` option. Upgrade the relayer before using this command.
131
+ Inspect `spendableYieldRawUsdc`. A new deposit does **not** immediately provide a payment budget; principal is not spendable yield. Wait until yield covers the API price and vault fees, then check again. There is no guaranteed waiting time: performance, fees, deposited amount and liquidity matter. The 1.01 USDC example demonstrates setup and deposit, not an immediate paid call. A budget read can return the last synced view if refresh fails; live payment checks still decide whether it can proceed.
55
132
 
56
- For an interrupted deposit or withdrawal, keep its `depositId` (`dep_...`) or `withdrawalId` (`wdr_...`) and run `pay status <intentId>` with the same wallet, selected vault and relayer. This reads and reconciles the original operation; it does not prepare, sign or send another transaction. The result includes the requested/confirmed amount, transaction signature and next action. `submitted` means check the same ID again later. A successful status command exits zero even when the original operation is still pending or failed; inspect its `status` and `nextAction`. Wallet-auth message signing is required, but no client RPC call is needed.
133
+ ### 6. Request a compatible paid API
134
+
135
+ Obtain a real paid URL from its seller; the hostname below is a placeholder. Supported offers are **Solana mainnet USDC `exact`** with `extra.feePayer`. EVM, other tokens and unsponsored rails are refused. A seller name alone does not prove compatibility.
136
+
137
+ ```sh
138
+ npx -y @subly_fi/pay@0.8.1 fetch https://seller.example.com/paid-resource
139
+ ```
140
+
141
+ The default client cap is **0.01 USDC**. An explicit cap of 0.02 USDC looks like this:
142
+
143
+ ```sh
144
+ npx -y @subly_fi/pay@0.8.1 fetch https://seller.example.com/paid-resource 20000
145
+ ```
146
+
147
+ The owner policy may impose stricter limits. A successful paid call returns `"paid": true`, an HTTP 2xx `status` and the API response `body`. `paid: false` is not a confirmed paid call; read its reason or HTTP response. If the endpoint did not require payment, no payment was made.
148
+
149
+ For `approval_required`, open the returned `approveUrl`, approve, return to the terminal, and repeat the **same URL, request and cap** with the returned approval ID:
150
+
151
+ ```sh
152
+ npx -y @subly_fi/pay@0.8.1 fetch https://seller.example.com/paid-resource 20000 apr_YOUR_APPROVAL_ID
153
+ ```
154
+
155
+ Replace the placeholder ID. For MCP, tell the agent approval is complete and ask it to resume the same operation with that ID. An unknown payment outcome is not an approval retry; follow [Recovery and troubleshooting](#recovery-and-troubleshooting).
156
+
157
+ ### 7. Withdraw to the agent wallet
158
+
159
+ A withdrawal can include principal and is subject to liquidity, fees and owner policy. This requests 1 USDC back to the **same agent wallet**:
160
+
161
+ ```sh
162
+ npx -y @subly_fi/pay@0.8.1 withdraw 1000000
163
+ ```
164
+
165
+ Success prints `status: confirmed`, a `withdrawalId`, a transaction link and the confirmed amount. An approval-required result includes `approveUrl` and `approvalId`. After approval, repeat the same amount with that ID:
166
+
167
+ ```sh
168
+ npx -y @subly_fi/pay@0.8.1 withdraw 1000000 apr_YOUR_APPROVAL_ID
169
+ ```
170
+
171
+ Later deposits use the same approval pattern:
172
+
173
+ ```sh
174
+ npx -y @subly_fi/pay@0.8.1 deposit 1010000 apr_YOUR_APPROVAL_ID
175
+ ```
176
+
177
+ Only use an ID issued for that exact operation. Moving withdrawn funds onward to another wallet is a separate action outside this CLI.
178
+
179
+ ### Check an interrupted deposit or withdrawal
180
+
181
+ Keep the original `depositId` (`dep_...`) or `withdrawalId` (`wdr_...`). Replace this placeholder with its full ID:
182
+
183
+ ```sh
184
+ npx -y @subly_fi/pay@0.8.1 status wdr_YOUR_WITHDRAWAL_ID
185
+ ```
186
+
187
+ Use the original wallet, selected vault and relayer. Status requires **relayer 0.8.0 or newer** and reconciles with `?resubmit=false`: it does not prepare, sign or send another transaction. Wallet-auth message signing is required, but no client RPC call is needed.
188
+
189
+ | Result | What to do |
190
+ | --- | --- |
191
+ | `confirmed` / `nextAction: done` | The original operation is confirmed; inspect `actualAmountRawUsdc` and `txSignature`. |
192
+ | `submitted` / `nextAction: check_again` | Check the **same ID** later; do not repeat the deposit or withdrawal. |
193
+ | `prepared` | Status does not submit it. Keep the ID and ask the operator to reconcile before a new operation. |
194
+ | `nextAction: reconcile_with_operator` | Keep the ID and error code; ask the operator to reconcile the failed or expired operation. |
195
+
196
+ Exit zero means the status lookup worked, even if the operation is pending or failed. Inspect `status` and `nextAction`. An interrupted API payment uses its saved pending-state checkpoint instead, as described below.
57
197
 
58
198
  ## MCP configuration
59
199
 
60
- Add this to the MCP configuration of your editor or agent host. Replace all example values with your own absolute paths and endpoints. Pinning the version keeps upgrades explicit.
200
+ Use your host's **documented MCP configuration format**. This JSON is for hosts accepting an `mcpServers` object; it is not universal. Codex uses its own MCP settings/configuration: translate the command, arguments and environment into that interface. The [AI setup prompts](https://github.com/SublyFi/subly-payment-protocol/blob/main/docs/ai-setup-prompts.md) help you configure the selected host.
201
+
202
+ Replace every example value. Use the **same absolute pending-state path as the CLI** for the same wallet. Shell exports may not reach a desktop app; supply variables to the MCP process itself. JSON does not expand `$HOME` or `$env:USERPROFILE`. Use paths such as `/Users/your-name/...`, `/home/your-name/...`, or escaped Windows paths such as `C:\\Users\\your-name\\.subly\\agent.json`.
61
203
 
62
204
  ```json
63
205
  {
64
206
  "mcpServers": {
65
207
  "subly": {
66
208
  "command": "npx",
67
- "args": ["-y", "@subly_fi/pay@0.8.0", "mcp"],
209
+ "args": ["-y", "@subly_fi/pay@0.8.1", "mcp"],
68
210
  "env": {
69
211
  "SUBLY_RELAYER_URL": "https://your-relayer.example.com",
70
212
  "SOLANA_RPC_URL": "https://your-mainnet-rpc.example.com",
71
- "SUBLY_DEMO_AGENT_KEYPAIR_PATH": "/absolute/path/to/agent.json",
72
- "SUBLY_MCP_STATE_PATH": "/absolute/path/to/subly-pending.json",
213
+ "SUBLY_DEMO_AGENT_KEYPAIR_PATH": "/absolute/path/to/.subly/agent.json",
214
+ "SUBLY_MCP_STATE_PATH": "/absolute/path/to/.subly/standard-x402-pending.json",
73
215
  "SUBLY_MCP_MAX_AMOUNT_RAW_USDC": "10000"
74
216
  }
75
217
  }
@@ -77,6 +219,8 @@ Add this to the MCP configuration of your editor or agent host. Replace all exam
77
219
  }
78
220
  ```
79
221
 
222
+ Restart the host after configuration changes and confirm it exposes all nine Subly tools. Some Windows hosts require a documented command wrapper for `npx.cmd`; follow the host's instructions rather than assuming the Unix launcher works unchanged.
223
+
80
224
  Tools: `list_subly_vaults`, `select_subly_vault`, `create_subly_setup_link`, `check_subly_setup`, `check_subly_vault_operation`, `deposit_to_subly_vault`, `get_subly_yield_budget`, `withdraw_from_subly_vault`, `fetch_with_subly_payment`. Ask the agent to list vaults and follow owner setup before depositing. Each selected vault has its own mandate and accounting; changing selection never moves funds. Stdio stdout is reserved for MCP messages.
81
225
 
82
226
  ## Configuration
@@ -86,28 +230,27 @@ Tools: `list_subly_vaults`, `select_subly_vault`, `create_subly_setup_link`, `ch
86
230
  | `SUBLY_RELAYER_URL` | Chosen operator's HTTPS URL. Set explicitly; the historical demo fallback has no availability promise. |
87
231
  | `SOLANA_RPC_URL` | Your trusted mainnet RPC; fallback is the rate-limited public mainnet RPC. Used to verify lookup tables and simulate withdrawals before signing. |
88
232
  | `SUBLY_DEMO_AGENT_KEYPAIR_PATH` | Local Solana 64-byte JSON keypair; the historical `DEMO` name also applies in production. |
89
- | `SUBLY_DEMO_AGENT_KEYPAIR` | Alternative base58 64-byte secret; if set, takes precedence over the file. Avoid putting secrets in shell history. |
233
+ | `SUBLY_DEMO_AGENT_KEYPAIR` | Alternative base58 64-byte secret; if set, takes precedence over the file. Avoid putting secrets in shell history or chat. |
90
234
  | `SUBLY_SIGNER_PROVIDER` | `local` (default), `circle` or `privy`. |
91
235
  | `SUBLY_VAULTS_FILE` | Reviewed local vault catalogue. Without it, the built-in vault is used. |
92
236
  | `SUBLY_VAULT_ADDRESS` | Selected catalogue entry (or custom single-vault address with matching share mint/farm settings). |
93
237
  | `SUBLY_MCP_MAX_AMOUNT_RAW_USDC` | Client payment cap; default `10000`. |
94
- | `SUBLY_MCP_STATE_PATH` | Persistent pending-payment file. Default `~/.subly/standard-x402-pending.json`. Use one shared file for all clients paying from the same wallet. |
238
+ | `SUBLY_MCP_STATE_PATH` | Persistent pending-payment file. Default `~/.subly/standard-x402-pending.json`. Use one shared file for all CLI/MCP clients paying from the same wallet on this machine. Different machines do not coordinate payments. |
95
239
  | `SUBLY_PAY_METHOD` / `SUBLY_PAY_BODY` | Optional HTTP method and body for CLI `fetch`; MCP accepts these as tool arguments. |
96
240
 
97
- For Circle configure `CIRCLE_API_KEY`, `CIRCLE_ENTITY_SECRET`, `CIRCLE_WALLET_ID`. For Privy use `PRIVY_APP_ID`, `PRIVY_APP_SECRET`, `PRIVY_WALLET_ID`, and `PRIVY_AUTHORIZATION_KEY` when required by wallet ownership. Each accepts a `SUBLY_` prefix which takes precedence. See [provider details](https://github.com/SublyFi/subly-payment-protocol/blob/main/docs/agent-wallet-providers.md).
241
+ For Circle set `SUBLY_SIGNER_PROVIDER=circle` and configure `CIRCLE_API_KEY`, `CIRCLE_ENTITY_SECRET`, `CIRCLE_WALLET_ID`. For Privy set `SUBLY_SIGNER_PROVIDER=privy` and use `PRIVY_APP_ID`, `PRIVY_APP_SECRET`, `PRIVY_WALLET_ID`, and `PRIVY_AUTHORIZATION_KEY` when required by wallet ownership. Each accepts a `SUBLY_` prefix which takes precedence. These providers need a Solana mainnet wallet, not an EVM wallet; a local keypair is not required. The [provider implementation notes](https://github.com/SublyFi/subly-payment-protocol/blob/main/docs/agent-wallet-providers.md) describe supported transports and unimplemented proposals; they are not a wallet-creation tutorial.
98
242
 
99
243
  Only sellers offering **Solana mainnet USDC `exact`** with `extra.feePayer` are supported. EVM, unsupported tokens and unsponsored rails are refused. A supported seller does not need a Subly integration. Yield realization and x402 payment are separate transactions: if the payment fails after realization, USDC may remain in the agent wallet.
100
244
 
101
245
  ## Recovery and troubleshooting
102
246
 
103
- - `doctor` performs read-only configuration, relayer/vault and RPC checks. It never signs or transacts; it does not prove vault safety or available yield.
104
- - A withdrawal preview failure is a refusal to sign. Check your RPC's simulation support and liquidity; do not disable transaction validation.
105
- - Preserve the pending-state JSON across restarts and upgrades. An `external_outcome_unknown` record blocks a second payment until you investigate the seller/facilitator outcome.
106
- - When `fetch` stops while realizing yield, retry the same request with the same wallet, vault, relayer, method, body and headers. A saved withdrawal checkpoint resumes the original withdrawal and reuses its confirmed funds. It never prepares a replacement during recovery, even with `forceNewPayment`. If interruption occurred before the withdrawal ID was saved, or the original withdrawal ended unsuccessfully, reconcile that operation with the operator before starting another payment.
107
- - Older pending records remain blocked for investigation because they do not identify a resumable withdrawal. Keep the state file; do not downgrade the client while a realization is pending.
108
- - Concurrent clients using the same state file serialize payments with a `.lock` file. After a crash, stop **all** clients using that file before removing only the stale `.lock`. Preserve the JSON. A new file or another machine cannot coordinate with the old one.
109
- - `submitted` means the transaction may still confirm. For a manual deposit or withdrawal, run `pay status <intentId>` or call `check_subly_vault_operation` with `intentId` instead of repeating the operation. Keep the original wallet, selected vault and relayer. An API `fetch` uses the saved recovery checkpoint described above.
110
- - Setup passkeys bind to the operator's domain. Use the original domain and device credential; follow the documented recovery delay if access is lost.
247
+ - Preserve pending-state JSON across restarts, upgrades and CLI/MCP changes. An `external_outcome_unknown` record blocks another payment until you investigate the seller/facilitator outcome. Never delete the file to bypass it.
248
+ - For interrupted yield realization, retry the same request with the same wallet, vault, relayer, method, body and headers. A saved checkpoint resumes the original withdrawal and reuses its confirmed funds; even `forceNewPayment` cannot replace an unfinished realization. If no withdrawal ID was saved or it ended unsuccessfully, ask the operator to reconcile it before a new payment.
249
+ - Older pending records without a resumable withdrawal remain blocked for investigation. Keep the file; do not downgrade while a realization is pending.
250
+ - Concurrent clients sharing the state file serialize payments with a `.lock` file. After a crash, stop **all** clients using it before removing only a stale `.lock`. Preserve the JSON. Another path or machine cannot coordinate with the old file.
251
+ - For an interrupted manual deposit or withdrawal, use `npx -y @subly_fi/pay@0.8.1 status <intentId>` or MCP `check_subly_vault_operation` with the original ID and configuration, rather than repeating the financial operation.
252
+ - A withdrawal preview failure is a refusal to sign. Check RPC simulation support and liquidity; do not disable validation.
253
+ - Passkeys bind to the operator's domain. Use the original domain and credential. Lost-passkey recovery and existing passkey policy changes have no CLI/MCP command; contact the operator before attempting low-level recovery.
111
254
 
112
255
  [Full troubleshooting](https://github.com/SublyFi/subly-payment-protocol/blob/main/docs/troubleshooting.md) · [Support](https://github.com/SublyFi/subly-payment-protocol/blob/main/SUPPORT.md) · [Private security reports](https://github.com/SublyFi/subly-payment-protocol/security/advisories/new)
113
256
 
package/dist/cli.js CHANGED
@@ -1,8 +1,11 @@
1
1
  #!/usr/bin/env node
2
- import { readFileSync } from "node:fs";
2
+ import { existsSync, readFileSync } from "node:fs";
3
3
  import { dirname, join } from "node:path";
4
4
  import { fileURLToPath } from "node:url";
5
5
  const here = dirname(fileURLToPath(import.meta.url));
6
+ const manifest = existsSync(join(here, "package.json")) ? join(here, "package.json") : join(here, "..", "package.json");
7
+ const version = JSON.parse(readFileSync(manifest, "utf8")).version;
8
+ const command = `npx -y @subly_fi/pay@${version}`;
6
9
  const TARGETS = {
7
10
  mcp: "mcp-server.js", fetch: "pay.js", deposit: "deposit.js", withdraw: "withdraw.js",
8
11
  "setup-link": "setup-link.js", "setup-status": "setup-status.js",
@@ -10,7 +13,7 @@ const TARGETS = {
10
13
  };
11
14
  const HELP = `Subly — x402 payments from Kamino USDC vault yield
12
15
 
13
- Usage: pay <command> [arguments]
16
+ Usage: ${command} <command> [arguments]
14
17
  doctor Check configuration, relayer and mainnet RPC (no signing)
15
18
  vaults Print the locally trusted vault catalogue (offline)
16
19
  setup-link [options] Create a human owner approval link
@@ -27,17 +30,17 @@ Amounts: 1000000 raw USDC = 1 USDC. Requires Node.js 24+.
27
30
  Set SUBLY_RELAYER_URL, SOLANA_RPC_URL and a wallet signer before use.
28
31
  Local signer: SUBLY_DEMO_AGENT_KEYPAIR_PATH=/absolute/path/agent.json
29
32
  Payment cap: SUBLY_MCP_MAX_AMOUNT_RAW_USDC (default 10000 = 0.01 USDC).
30
- Run pay setup-link --help for policy options.
33
+ Run ${command} setup-link --help for policy options.
31
34
  Guide: https://github.com/SublyFi/subly-payment-protocol/tree/main/packages/pay
32
35
  `;
33
36
  const [sub, ...rest] = process.argv.slice(2);
34
37
  if (sub === "--version" || sub === "-v") {
35
- console.log(JSON.parse(readFileSync(join(here, "..", "package.json"), "utf8")).version);
38
+ console.log(version);
36
39
  } else if (!sub || sub === "--help" || sub === "-h" || sub === "help" || rest.includes("--help") || rest.includes("-h")) {
37
40
  console.log(HELP);
38
41
  if (sub === "setup-link") console.log("Policy options: --initial-deposit <rawUSDC> --approval-threshold <rawUSDC> --per-payment-cap <rawUSDC> --daily-api-cap <rawUSDC> --daily-deposit-cap <rawUSDC> --ttl-days <days>");
39
42
  } else if (!Object.hasOwn(TARGETS, sub)) {
40
- console.error(`Unknown command: ${sub}\nRun pay --help.`);
43
+ console.error(`Unknown command: ${sub}\nRun ${command} --help.`);
41
44
  process.exitCode = 1;
42
45
  } else {
43
46
  process.argv = [process.argv[0], join(here, TARGETS[sub]), ...rest];
package/dist/deposit.js CHANGED
@@ -2415,12 +2415,19 @@ function formatRawUsdc(raw) {
2415
2415
  return `${whole}.${fraction}`;
2416
2416
  }
2417
2417
 
2418
+ // ../../demo/cli-command.ts
2419
+ import { readFileSync as readFileSync3 } from "node:fs";
2420
+ var { version } = JSON.parse(
2421
+ readFileSync3(new URL("../package.json", import.meta.url), "utf8")
2422
+ );
2423
+ var PAY_COMMAND = `npx -y @subly_fi/pay@${version}`;
2424
+
2418
2425
  // ../../demo/deposit.ts
2419
2426
  var relayerBaseUrl = process.env.SUBLY_RELAYER_URL ?? process.env.SUBLY_FACILITATOR_URL ?? "https://api.demo.sublyfi.com";
2420
2427
  var amountRawUsdc = process.argv[2];
2421
2428
  if (amountRawUsdc === void 0 || !/^[1-9]\d*$/.test(amountRawUsdc)) {
2422
2429
  fail(
2423
- "Usage: pay deposit <amountRawUsdc> [apr_<approvalId>] (positive integer, e.g. 60000000 = 60 USDC)"
2430
+ `Usage: ${PAY_COMMAND} deposit <amountRawUsdc> [apr_<approvalId>] (positive integer, e.g. 60000000 = 60 USDC)`
2424
2431
  );
2425
2432
  }
2426
2433
  var approvalId = process.argv[3];
@@ -2463,7 +2470,7 @@ try {
2463
2470
  approvalId: details.approvalId ?? null,
2464
2471
  approveUrl: details.approveUrl ?? null,
2465
2472
  expiresAtMs: details.expiresAtMs ?? null,
2466
- message: `This deposit needs the owner's approval. Paste approveUrl to the user; once they approve (Face ID / wallet sign), retry: pay deposit ${amountRawUsdc} ${details.approvalId ?? "<approvalId>"}`
2473
+ message: `This deposit needs the owner's approval. Paste approveUrl to the user; once they approve (Face ID / wallet sign), retry: ${PAY_COMMAND} deposit ${amountRawUsdc} ${details.approvalId ?? "<approvalId>"}`
2467
2474
  },
2468
2475
  null,
2469
2476
  2
@@ -2478,7 +2485,7 @@ try {
2478
2485
  {
2479
2486
  ok: false,
2480
2487
  setupRequired: true,
2481
- message: `Deposits require a registered owner. Create a setup link (pay setup-link --initial-deposit ${amountRawUsdc}), paste the setupUrl to the user, wait for their Face ID, then deposit again \u2014 the first deposit is pre-approved.`
2488
+ message: `Deposits require a registered owner. Create a setup link (${PAY_COMMAND} setup-link --initial-deposit ${amountRawUsdc}), paste the setupUrl to the user, wait for their Face ID, then deposit again \u2014 the first deposit is pre-approved.`
2482
2489
  },
2483
2490
  null,
2484
2491
  2
@@ -2500,12 +2507,12 @@ if (submitted.txSignature !== null) {
2500
2507
  }
2501
2508
  if (submitted.status === "submitted") {
2502
2509
  fail(
2503
- `[deposit] broadcast but not yet confirmed \u2014 it may still land. Do NOT resubmit; run pay status ${submitted.depositId} with the same wallet, vault and relayer first`
2510
+ `[deposit] broadcast but not yet confirmed \u2014 it may still land. Do NOT resubmit; run ${PAY_COMMAND} status ${submitted.depositId} with the same wallet, vault and relayer first`
2504
2511
  );
2505
2512
  }
2506
2513
  if (submitted.status !== "confirmed") {
2507
2514
  fail(
2508
- `[deposit] not confirmed (errorCode=${submitted.errorCode}); run pay status ${submitted.depositId} and ask the operator to reconcile it`
2515
+ `[deposit] not confirmed (errorCode=${submitted.errorCode}); run ${PAY_COMMAND} status ${submitted.depositId} and ask the operator to reconcile it`
2509
2516
  );
2510
2517
  }
2511
2518
  console.log(
package/dist/pay.js CHANGED
@@ -3417,6 +3417,13 @@ function selectExpectedRequirement(requirements, expected) {
3417
3417
  return match;
3418
3418
  }
3419
3419
 
3420
+ // ../../demo/cli-command.ts
3421
+ import { readFileSync as readFileSync4 } from "node:fs";
3422
+ var { version } = JSON.parse(
3423
+ readFileSync4(new URL("../package.json", import.meta.url), "utf8")
3424
+ );
3425
+ var PAY_COMMAND = `npx -y @subly_fi/pay@${version}`;
3426
+
3420
3427
  // src/pay.ts
3421
3428
  function fail(message) {
3422
3429
  process.stderr.write(`${message}
@@ -3425,7 +3432,7 @@ function fail(message) {
3425
3432
  }
3426
3433
  var url = process.argv[2];
3427
3434
  if (url === void 0 || !/^https?:\/\//.test(url)) {
3428
- fail("Usage: pay fetch <url> [maxAmountRawUsdc] [apr_<approvalId>]");
3435
+ fail(`Usage: ${PAY_COMMAND} fetch <url> [maxAmountRawUsdc] [apr_<approvalId>]`);
3429
3436
  }
3430
3437
  var maxAmountArg;
3431
3438
  var approvalId;
@@ -3436,7 +3443,7 @@ for (const arg of process.argv.slice(3)) {
3436
3443
  maxAmountArg = arg;
3437
3444
  } else {
3438
3445
  fail(`unrecognized argument: ${arg}
3439
- Usage: pay fetch <url> [maxAmountRawUsdc] [apr_<approvalId>]`);
3446
+ Usage: ${PAY_COMMAND} fetch <url> [maxAmountRawUsdc] [apr_<approvalId>]`);
3440
3447
  }
3441
3448
  }
3442
3449
  var relayerBaseUrl = process.env.SUBLY_RELAYER_URL ?? process.env.SUBLY_FACILITATOR_URL ?? "https://api.demo.sublyfi.com";
@@ -3492,7 +3499,7 @@ try {
3492
3499
  message: error.message,
3493
3500
  detail: error.detail ?? null,
3494
3501
  ...refusalApprovalId === void 0 ? {} : {
3495
- retry: `pay fetch "${url}"${maxAmountArg === void 0 ? "" : ` ${maxAmountArg}`} ${refusalApprovalId}`
3502
+ retry: `${PAY_COMMAND} fetch "${url}"${maxAmountArg === void 0 ? "" : ` ${maxAmountArg}`} ${refusalApprovalId}`
3496
3503
  }
3497
3504
  },
3498
3505
  null,
@@ -2344,13 +2344,20 @@ function createRpc(url) {
2344
2344
  return createSolanaRpc(url);
2345
2345
  }
2346
2346
 
2347
+ // ../../demo/cli-command.ts
2348
+ import { readFileSync as readFileSync3 } from "node:fs";
2349
+ var { version } = JSON.parse(
2350
+ readFileSync3(new URL("../package.json", import.meta.url), "utf8")
2351
+ );
2352
+ var PAY_COMMAND = `npx -y @subly_fi/pay@${version}`;
2353
+
2347
2354
  // src/setup-link.ts
2348
2355
  function fail(message) {
2349
2356
  process.stderr.write(`${message}
2350
2357
  `);
2351
2358
  process.exit(1);
2352
2359
  }
2353
- var USAGE = "Usage: pay setup-link [--initial-deposit <rawUsdc>] [--approval-threshold <rawUsdc>] [--per-payment-cap <rawUsdc>] [--daily-api-cap <rawUsdc>] [--daily-deposit-cap <rawUsdc>] [--ttl-days <days>]";
2360
+ var USAGE = `Usage: ${PAY_COMMAND} setup-link [--initial-deposit <rawUsdc>] [--approval-threshold <rawUsdc>] [--per-payment-cap <rawUsdc>] [--daily-api-cap <rawUsdc>] [--daily-deposit-cap <rawUsdc>] [--ttl-days <days>]`;
2354
2361
  var FLAG_TO_POLICY_KEY = {
2355
2362
  "--approval-threshold": "approvalThresholdRawUsdc",
2356
2363
  "--per-payment-cap": "perPaymentCapRawUsdc",
@@ -1,3 +1,10 @@
1
+ // ../../demo/cli-command.ts
2
+ import { readFileSync } from "node:fs";
3
+ var { version } = JSON.parse(
4
+ readFileSync(new URL("../package.json", import.meta.url), "utf8")
5
+ );
6
+ var PAY_COMMAND = `npx -y @subly_fi/pay@${version}`;
7
+
1
8
  // src/setup-status.ts
2
9
  function fail(message) {
3
10
  process.stderr.write(`${message}
@@ -6,7 +13,7 @@ function fail(message) {
6
13
  }
7
14
  var arg = process.argv[2];
8
15
  if (arg === void 0) {
9
- fail("Usage: pay setup-status <st_sessionId | setupUrl>");
16
+ fail(`Usage: ${PAY_COMMAND} setup-status <st_sessionId | setupUrl>`);
10
17
  }
11
18
  var match = arg.match(/st_[0-9a-f]+/i);
12
19
  if (match === null) {
package/dist/status.js CHANGED
@@ -2344,10 +2344,17 @@ function createRpc(url) {
2344
2344
  return createSolanaRpc(url);
2345
2345
  }
2346
2346
 
2347
+ // ../../demo/cli-command.ts
2348
+ import { readFileSync as readFileSync3 } from "node:fs";
2349
+ var { version } = JSON.parse(
2350
+ readFileSync3(new URL("../package.json", import.meta.url), "utf8")
2351
+ );
2352
+ var PAY_COMMAND = `npx -y @subly_fi/pay@${version}`;
2353
+
2347
2354
  // src/status.ts
2348
2355
  var [intentId, ...extra] = process.argv.slice(2);
2349
2356
  if (intentId === void 0 || extra.length !== 0) {
2350
- throw new Error("Usage: pay status <dep_...|wdr_...> (the original intent ID)");
2357
+ throw new Error(`Usage: ${PAY_COMMAND} status <dep_...|wdr_...> (the original intent ID)`);
2351
2358
  }
2352
2359
  vaultOperationKind(intentId);
2353
2360
  var { signer } = await agentWalletSignerFromEnv();
package/dist/withdraw.js CHANGED
@@ -2356,12 +2356,19 @@ function formatRawUsdc(raw) {
2356
2356
  return `${whole}.${fraction}`;
2357
2357
  }
2358
2358
 
2359
+ // ../../demo/cli-command.ts
2360
+ import { readFileSync as readFileSync3 } from "node:fs";
2361
+ var { version } = JSON.parse(
2362
+ readFileSync3(new URL("../package.json", import.meta.url), "utf8")
2363
+ );
2364
+ var PAY_COMMAND = `npx -y @subly_fi/pay@${version}`;
2365
+
2359
2366
  // ../../demo/withdraw.ts
2360
2367
  var relayerBaseUrl = process.env.SUBLY_RELAYER_URL ?? process.env.SUBLY_FACILITATOR_URL ?? "https://api.demo.sublyfi.com";
2361
2368
  var amountRawUsdc = process.argv[2];
2362
2369
  if (amountRawUsdc === void 0 || !/^[1-9]\d*$/.test(amountRawUsdc)) {
2363
2370
  fail(
2364
- "Usage: pay withdraw <amountRawUsdc> [apr_<approvalId>] (positive integer, e.g. 1000000 = 1 USDC)"
2371
+ `Usage: ${PAY_COMMAND} withdraw <amountRawUsdc> [apr_<approvalId>] (positive integer, e.g. 1000000 = 1 USDC)`
2365
2372
  );
2366
2373
  }
2367
2374
  var approvalId = process.argv[3];
@@ -2401,7 +2408,7 @@ try {
2401
2408
  approvalId: details.approvalId ?? null,
2402
2409
  approveUrl: details.approveUrl ?? null,
2403
2410
  expiresAtMs: details.expiresAtMs ?? null,
2404
- message: `This withdrawal needs the owner's approval. Paste approveUrl to the user; once they approve, retry: pay withdraw ${amountRawUsdc} ${details.approvalId ?? "<approvalId>"}`
2411
+ message: `This withdrawal needs the owner's approval. Paste approveUrl to the user; once they approve, retry: ${PAY_COMMAND} withdraw ${amountRawUsdc} ${details.approvalId ?? "<approvalId>"}`
2405
2412
  },
2406
2413
  null,
2407
2414
  2
@@ -2424,12 +2431,12 @@ if (submitted.txSignature !== null) {
2424
2431
  }
2425
2432
  if (submitted.status === "submitted") {
2426
2433
  fail(
2427
- `[withdraw] broadcast but not yet confirmed \u2014 it may still land. Do NOT resubmit; run pay status ${submitted.withdrawalId} with the same wallet, vault and relayer first`
2434
+ `[withdraw] broadcast but not yet confirmed \u2014 it may still land. Do NOT resubmit; run ${PAY_COMMAND} status ${submitted.withdrawalId} with the same wallet, vault and relayer first`
2428
2435
  );
2429
2436
  }
2430
2437
  if (submitted.status !== "confirmed") {
2431
2438
  fail(
2432
- `[withdraw] not confirmed (errorCode=${submitted.errorCode}); run pay status ${submitted.withdrawalId} and ask the operator to reconcile it`
2439
+ `[withdraw] not confirmed (errorCode=${submitted.errorCode}); run ${PAY_COMMAND} status ${submitted.withdrawalId} and ask the operator to reconcile it`
2433
2440
  );
2434
2441
  }
2435
2442
  console.log(
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@subly_fi/pay",
3
- "version": "0.8.0",
3
+ "version": "0.8.1",
4
4
  "description": "Subly client: pay compatible standard x402 Solana USDC exact APIs from Kamino vault yield — the seller needs no Subly integration. Ships an MCP server and a one-shot pay/deposit CLI; non-custodial (signs locally with your own key).",
5
5
  "keywords": [
6
6
  "x402",