@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 +191 -48
- package/dist/cli.js +8 -5
- package/dist/deposit.js +12 -5
- package/dist/pay.js +10 -3
- package/dist/setup-link.js +8 -1
- package/dist/setup-status.js +8 -1
- package/dist/status.js +8 -1
- package/dist/withdraw.js +11 -4
- package/package.json +1 -1
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
|
-
|
|
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
|
-
```
|
|
12
|
-
npx -y @subly_fi/pay@0.8.
|
|
13
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
59
|
+
macOS/Linux/WSL (Bash or zsh):
|
|
39
60
|
|
|
40
|
-
|
|
41
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
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/
|
|
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.
|
|
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
|
-
-
|
|
104
|
-
-
|
|
105
|
-
-
|
|
106
|
-
-
|
|
107
|
-
-
|
|
108
|
-
-
|
|
109
|
-
-
|
|
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:
|
|
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
|
|
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(
|
|
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
|
|
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
|
-
|
|
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:
|
|
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 (
|
|
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
|
|
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
|
|
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(
|
|
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:
|
|
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:
|
|
3502
|
+
retry: `${PAY_COMMAND} fetch "${url}"${maxAmountArg === void 0 ? "" : ` ${maxAmountArg}`} ${refusalApprovalId}`
|
|
3496
3503
|
}
|
|
3497
3504
|
},
|
|
3498
3505
|
null,
|
package/dist/setup-link.js
CHANGED
|
@@ -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 =
|
|
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",
|
package/dist/setup-status.js
CHANGED
|
@@ -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(
|
|
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(
|
|
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
|
-
|
|
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:
|
|
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
|
|
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
|
|
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.
|
|
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",
|