@aifinpay/mcp 2.7.1 → 2.11.0
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 +174 -17
- package/bin/aifinpay-mcp.js +142 -41
- package/dist/client-framework.d.ts +3 -0
- package/dist/client-framework.d.ts.map +1 -0
- package/dist/client-framework.js +22 -0
- package/dist/client-framework.js.map +1 -0
- package/dist/config.d.ts +5 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +13 -0
- package/dist/config.js.map +1 -1
- package/dist/identity.d.ts +1 -0
- package/dist/identity.d.ts.map +1 -1
- package/dist/identity.js +55 -5
- package/dist/identity.js.map +1 -1
- package/dist/payment-state.d.ts.map +1 -1
- package/dist/safe-fetch.js.map +1 -1
- package/dist/server.d.ts +5 -1
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +38 -3
- package/dist/server.js.map +1 -1
- package/dist/tools/agent-address.d.ts.map +1 -1
- package/dist/tools/agent-claim-self.d.ts +1 -1
- package/dist/tools/agent-claim-self.d.ts.map +1 -1
- package/dist/tools/agent-history.d.ts.map +1 -1
- package/dist/tools/agent-quota.d.ts +1 -1
- package/dist/tools/agent-quota.d.ts.map +1 -1
- package/dist/tools/dev-payment-quote.d.ts.map +1 -1
- package/dist/tools/payable-fetch.d.ts +5 -0
- package/dist/tools/payable-fetch.d.ts.map +1 -1
- package/dist/tools/payable-fetch.js +41 -7
- package/dist/tools/payable-fetch.js.map +1 -1
- package/dist/tools/payment-setup-status.d.ts +84 -0
- package/dist/tools/payment-setup-status.d.ts.map +1 -0
- package/dist/tools/payment-setup-status.js +193 -0
- package/dist/tools/payment-setup-status.js.map +1 -0
- package/dist/tools/production-control.d.ts.map +1 -1
- package/dist/tools/production-control.js +106 -6
- package/dist/tools/production-control.js.map +1 -1
- package/dist/tools/supported-chains.d.ts.map +1 -1
- package/package.json +5 -6
- package/skills/SKILL.md +63 -20
package/README.md
CHANGED
|
@@ -4,13 +4,60 @@ AiFinPay MCP server for persistent agent identity, Agent Passport resolution,
|
|
|
4
4
|
route discovery and non-signing settlement invoices. Canonical domain:
|
|
5
5
|
**aifinpay.io**.
|
|
6
6
|
|
|
7
|
-
Version **2.
|
|
7
|
+
Version **2.11.0** is prepared in this checkout, on the published agent 2.8.0
|
|
8
|
+
and canonical skill 2.10.0 (AiFinPay/skill 49c2e2a, published 2026-10-19). The separate canonical producer's final immutable
|
|
9
|
+
source pin is a required handoff, not a moving-branch substitution.
|
|
8
10
|
The published baseline checked2026-10-04 was MCP2.5.0. Package publication and
|
|
9
11
|
real registry dependency/lock refresh remain release gates; a source-cohort
|
|
10
12
|
build does not prove those steps. Owner-enabled v1.4 purchases with `payable_fetch` are
|
|
11
13
|
released: on Polygon since 2.2.0, on Base (ETH or USDC) since 2.5.0; site-wide
|
|
12
|
-
batches with `scope: "merchant"` since 2.4.1. Without payment configuration the server
|
|
13
|
-
|
|
14
|
+
batches with `scope: "merchant"` since 2.4.1. Without payment configuration the server
|
|
15
|
+
lists every tool, including `payable_fetch`, but signs nothing: `payable_fetch`
|
|
16
|
+
and `payment_setup_status` answer with the setup checklist instead.
|
|
17
|
+
|
|
18
|
+
## First payment in five steps
|
|
19
|
+
|
|
20
|
+
Each step is one command or one tool call. Signing happens only in step 5,
|
|
21
|
+
and only once the owner has set every limit in step 4.
|
|
22
|
+
|
|
23
|
+
1. **Read the price.** `GET` the resource. The `402` names the merchant
|
|
24
|
+
(`merchant_id`), the price per request and the smallest batch (`$0.10` for
|
|
25
|
+
200 standard requests).
|
|
26
|
+
2. **Check the setup.** Call `payment_setup_status`. It returns the wallet
|
|
27
|
+
(persistent or ephemeral), whether payments are enabled, which owner limits
|
|
28
|
+
are set, the balance when the RPC answers, and `next_step`: the first thing
|
|
29
|
+
still missing.
|
|
30
|
+
3. **Persistent wallet.** If `wallet.persistent` is false, run
|
|
31
|
+
`npx @aifinpay/mcp init`. Without a terminal it prints one JSON document;
|
|
32
|
+
copy its `env` (the passphrase is only there) into the `aifinpay` server
|
|
33
|
+
entry of the MCP client config, reconnect the server, then call
|
|
34
|
+
`agent_reload`. Never fund an ephemeral address.
|
|
35
|
+
4. **Fund and enable.** Tell the owner `addresses.evm` and the amount: the
|
|
36
|
+
batch price in POL plus the gas cap, so about `0.5 POL` for one `$0.10`
|
|
37
|
+
batch at `$0.20`/POL plus up to `0.3 POL` gas (the SDK refuses before
|
|
38
|
+
signing if the worst-case fee exceeds the cap). When the owner confirms
|
|
39
|
+
funding, they add the payment settings to the same `env` and reconnect:
|
|
40
|
+
|
|
41
|
+
```json
|
|
42
|
+
{
|
|
43
|
+
"AIFINPAY_PAYMENTS_ENABLED": "1",
|
|
44
|
+
"AIFINPAY_GATEWAY_ORIGINS": "https://dev.ratersapp.com",
|
|
45
|
+
"AIFINPAY_GATEWAY_PATH_MODE": "direct",
|
|
46
|
+
"AIFINPAY_MAX_USD": "0.15",
|
|
47
|
+
"AIFINPAY_DAILY_USD": "1.00",
|
|
48
|
+
"AIFINPAY_MAX_GAS_POL": "0.3"
|
|
49
|
+
}
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
`payment_setup_status` then reports `ready: true`.
|
|
53
|
+
|
|
54
|
+
5. **Pay.** `payable_fetch({"url": "https://dev.ratersapp.com/api/agent/genres/list"})`
|
|
55
|
+
buys the batch on Polygon (splitter v1.4) and returns the content and the
|
|
56
|
+
receipt summary. Later requests reuse the batch.
|
|
57
|
+
|
|
58
|
+
Polygon and Base are served on v1.4 today; `settlement_routes` lists them
|
|
59
|
+
first, with `live: true`. Its `legacy_v13` section describes the older
|
|
60
|
+
splitter v1.3 routes, which `payable_fetch` does not use.
|
|
14
61
|
|
|
15
62
|
## Enable native paid GET requests
|
|
16
63
|
|
|
@@ -35,6 +82,23 @@ owner. `direct` uses the full path for self-hosted sites; `gateway` uses the
|
|
|
35
82
|
merchant slug on the hosted gateway. Restart/reconnect the MCP process after
|
|
36
83
|
changing its environment.
|
|
37
84
|
|
|
85
|
+
## Indexed wallet derivation
|
|
86
|
+
|
|
87
|
+
The wallet CLI and MCP can use deterministic child wallets from one recovery
|
|
88
|
+
seed. Create an MCP-compatible wallet with
|
|
89
|
+
`npx @aifinpay/wallet new --index 0`; the keystore retains the
|
|
90
|
+
root seed and adds the index as optional metadata without changing existing
|
|
91
|
+
fields. MCP uses that stored index automatically. To select another child in a different MCP
|
|
92
|
+
process, set `AIFINPAY_WALLET_INDEX` to its decimal index; the same variable
|
|
93
|
+
also applies when `SEED_HASH` or an `agents.json` record supplies the seed.
|
|
94
|
+
|
|
95
|
+
Each chain derives its own indexed key; for example,
|
|
96
|
+
`EVM = SHA-256("aifinpay:evm:v1\0" || decimalIndex || recoverySeed)` and
|
|
97
|
+
`Solana = Ed25519(SHA-256("aifinpay:solana:v1\0" || decimalIndex || recoverySeed))`.
|
|
98
|
+
The decimal UTF-8 index follows that chain's domain NUL separator. Index 0 is
|
|
99
|
+
the first wallet. Indices must be between 0 and 4294967295. With no selected
|
|
100
|
+
index, existing identities retain their current derivation.
|
|
101
|
+
|
|
38
102
|
`AIFINPAY_MAX_GAS_POL` caps the worst case, not the fee you expect to pay.
|
|
39
103
|
Before signing, the client prices the estimated gas plus 20% at the maximum fee
|
|
40
104
|
per gas the Polygon RPC quotes, and refuses with `V14_GAS_BUDGET_EXCEEDED` if
|
|
@@ -104,6 +168,24 @@ then `api.coingecko.com`; other chains use those HTTP price sources.
|
|
|
104
168
|
If none provides a valid fresh rate, `payable_fetch` stops before paying and
|
|
105
169
|
the error names each host it tried.
|
|
106
170
|
|
|
171
|
+
### Which AI client paid
|
|
172
|
+
|
|
173
|
+
`payable_fetch` tells the merchant which AI client is paying: the server maps the
|
|
174
|
+
`clientInfo.name` your MCP client sent at initialize to an `AIFP-Agent-Framework`
|
|
175
|
+
header on the payment requests (and on recovery of a pending payment).
|
|
176
|
+
|
|
177
|
+
| `clientInfo.name` | `AIFP-Agent-Framework` |
|
|
178
|
+
| -------------------------- | ---------------------- |
|
|
179
|
+
| `claude-ai`, `claude-code` | `claude` |
|
|
180
|
+
| `cursor`, `cursor-vscode` | `cursor` |
|
|
181
|
+
| `windsurf` | `windsurf` |
|
|
182
|
+
| `chatgpt`, `openai` | `chatgpt` |
|
|
183
|
+
| `gemini` | `gemini` |
|
|
184
|
+
| `perplexity` | `perplexity` |
|
|
185
|
+
|
|
186
|
+
Names are matched exactly. Any other client sends no header; the server never
|
|
187
|
+
guesses. Nothing in the payment, limits or receipts depends on it.
|
|
188
|
+
|
|
107
189
|
## Link the agent to its owner's dashboard
|
|
108
190
|
|
|
109
191
|
The owner sees the agent's balance, payments and receipts at
|
|
@@ -133,18 +215,31 @@ state file in chat. Unconfirmed payments consume the budget conservatively.
|
|
|
133
215
|
| `agent_reload` | Reload local wallet files in the current MCP connection. |
|
|
134
216
|
| `agent_quota` | Read the agent's quota. |
|
|
135
217
|
| `agent_passport_resolve` | Resolve the global Agent Passport identity. |
|
|
136
|
-
| `
|
|
218
|
+
| `payment_setup_status` | Read-only setup checklist with the one `next_step`. |
|
|
219
|
+
| `settlement_routes` | Splitter v1.4 routes (live now) first, then legacy v1.3. |
|
|
137
220
|
| `settlement_invoice` | Prepare a non-signing settlement invoice. |
|
|
138
221
|
| `agent_history` | Read indexed settlements or retained receipt batches. |
|
|
139
222
|
| `settlement_solana` | Build a non-signing Solana settlement invoice. |
|
|
140
223
|
| `settlement_casper` | Build a non-signing Casper settlement invoice. |
|
|
141
224
|
| `deployment_info` | Read deployments: addresses, assets, settlement status. |
|
|
142
225
|
|
|
143
|
-
`payable_fetch` is
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
226
|
+
`payable_fetch` is always listed. It signs only with a persistent wallet,
|
|
227
|
+
`AIFINPAY_PAYMENTS_ENABLED=1` and every owner limit; before that it returns the
|
|
228
|
+
`payment_setup_status` checklist as a tool error and contacts nothing. With
|
|
229
|
+
payments enabled and any owner limit missing or invalid, the server refuses to
|
|
230
|
+
start rather than hiding the tool. It accepts
|
|
231
|
+
only `url`, `max_amount_usd`, `scope` and `expected_merchant_id`; `max_amount_usd`
|
|
232
|
+
can lower the owner's per-payment cap for one call, never raise it.
|
|
233
|
+
|
|
234
|
+
Each gateway site (origin + slug) is pinned to the merchant this wallet first
|
|
235
|
+
paid it as. The agent SDK keeps these pins under `~/.aifinpay/merchant-pins`, or
|
|
236
|
+
`AIFINPAY_STATE_DIR/merchant-pins`; `AIFINPAY_HOME` does not move them. When a
|
|
237
|
+
site answers as another merchant, `payable_fetch` stops before any quote and
|
|
238
|
+
names both merchant ids. Nothing is paid, and the agent cannot approve the change
|
|
239
|
+
itself. Once the owner confirms the new merchant, a call with
|
|
240
|
+
`expected_merchant_id` set to it pays and re-pins the site. There is no blanket
|
|
241
|
+
"allow any merchant change" argument. A `410 gateway_retired` site is terminal:
|
|
242
|
+
the tool says not to retry. `dev_payment_quote` is registered
|
|
148
243
|
only with `AIFINPAY_MODE=dev`. Legacy `agent_call`, `agent_quote`,
|
|
149
244
|
`pay_with_split` and `quote_split` remain unregistered.
|
|
150
245
|
|
|
@@ -170,6 +265,41 @@ Invalid or ambiguous configured inputs fail; they never generate a replacement
|
|
|
170
265
|
wallet. With no configured wallet at all the server has an ephemeral identity:
|
|
171
266
|
**do not fund it**.
|
|
172
267
|
|
|
268
|
+
### Embed an external EVM wallet
|
|
269
|
+
|
|
270
|
+
An embedding host can pass a viem `WalletClient` directly to `createServer`.
|
|
271
|
+
This is programmatic-only; `npx @aifinpay/mcp` and environment variables cannot
|
|
272
|
+
carry a wallet client. Keep the existing local identity configured for Solana
|
|
273
|
+
and other identity surfaces. When supplied, the client account becomes the EVM
|
|
274
|
+
payment identity and the payment journal is keyed to that address. Its chain
|
|
275
|
+
must match the owner-selected `AIFINPAY_PAY_CHAIN` (Polygon by default); the
|
|
276
|
+
SDK refuses a mismatch rather than switching providers or falling back to the
|
|
277
|
+
local EVM key.
|
|
278
|
+
|
|
279
|
+
```ts
|
|
280
|
+
import { createServer, loadConfigFromEnv } from "@aifinpay/mcp";
|
|
281
|
+
import type { EvmWalletClient } from "@aifinpay/agent";
|
|
282
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
283
|
+
|
|
284
|
+
// Constructed by the host from its chosen external signer/provider.
|
|
285
|
+
declare const walletClient: EvmWalletClient;
|
|
286
|
+
|
|
287
|
+
const { server } = await createServer({
|
|
288
|
+
...loadConfigFromEnv(),
|
|
289
|
+
evmWalletClient: walletClient,
|
|
290
|
+
});
|
|
291
|
+
await server.connect(new StdioServerTransport());
|
|
292
|
+
```
|
|
293
|
+
|
|
294
|
+
The host must provide a viem `LocalAccount` with `signTransaction`, bound to
|
|
295
|
+
the selected chain. v1.4 persists exact signed transaction bytes before the
|
|
296
|
+
SDK broadcasts them through its verified RPC; JSON-RPC/send-only and smart
|
|
297
|
+
account clients are refused rather than bypassing durable recovery. MCP still
|
|
298
|
+
applies its configured spending limits, runtime and route checks, receipt
|
|
299
|
+
verification, and recovery. Changing the injected account while payment
|
|
300
|
+
recovery is pending requires reconciliation and reconnecting; it is not a
|
|
301
|
+
tool-level wallet switch.
|
|
302
|
+
|
|
173
303
|
## Initialize and connect
|
|
174
304
|
|
|
175
305
|
A quote or invoice is not a completed payment. `payable_fetch` pays only with the
|
|
@@ -184,7 +314,33 @@ AIFINPAY_WALLET_PASSPHRASE='<a long passphrase>' npx @aifinpay/mcp init
|
|
|
184
314
|
The keystore is encrypted at rest and the MCP server needs the same
|
|
185
315
|
`AIFINPAY_WALLET_PASSPHRASE` to load it. For a disposable test wallet only,
|
|
186
316
|
`npx @aifinpay/mcp init --plaintext` writes it unencrypted (mode 600); without
|
|
187
|
-
either, `init` refuses rather than create an unencrypted wallet
|
|
317
|
+
either, `init` on a terminal refuses rather than create an unencrypted wallet
|
|
318
|
+
you might fund.
|
|
319
|
+
|
|
320
|
+
Without a terminal on stdin (an agent's subprocess), or with
|
|
321
|
+
`AIFINPAY_NONINTERACTIVE=1`, `init` never prompts. It generates a 192-bit
|
|
322
|
+
passphrase, encrypts the keystore and prints one JSON document:
|
|
323
|
+
|
|
324
|
+
```json
|
|
325
|
+
{
|
|
326
|
+
"keystore": "/home/me/.aifinpay/agent.json",
|
|
327
|
+
"created": true,
|
|
328
|
+
"encrypted": true,
|
|
329
|
+
"source": "legacy-keystore",
|
|
330
|
+
"addresses": { "evm": "0x…", "solana": "…", "casper": "account-hash-…" },
|
|
331
|
+
"env": { "AIFINPAY_WALLET_PASSPHRASE": "<generated>" },
|
|
332
|
+
"passphrase_note": "…",
|
|
333
|
+
"payment_env_example": { "AIFINPAY_PAYMENTS_ENABLED": "1", "…": "…" },
|
|
334
|
+
"next_step": "Put \"env\" into the aifinpay server entry of the MCP client config …"
|
|
335
|
+
}
|
|
336
|
+
```
|
|
337
|
+
|
|
338
|
+
The passphrase appears only inside `env`, because the caller must put it in
|
|
339
|
+
the client config and there is no other channel; a process that captures that
|
|
340
|
+
output captures the passphrase, so treat the output as a secret. A second run
|
|
341
|
+
keeps the wallet and prints a placeholder instead of the passphrase. If the
|
|
342
|
+
keystore exists and `AIFINPAY_WALLET_PASSPHRASE` is unset, `init` prints one
|
|
343
|
+
line naming the variable and exits with status 2.
|
|
188
344
|
|
|
189
345
|
Select the wallet in this order: `SEED_HASH` → `./aifinpay/agents.json` →
|
|
190
346
|
legacy `AIFINPAY_AGENT_SECRET` → `~/.aifinpay/agent.json`.
|
|
@@ -247,7 +403,7 @@ snapshot. Verify `agent_address` against the wallet you intend to use.
|
|
|
247
403
|
| `AIFINPAY_MAX_USD` | — | Per-payment USD cap for `payable_fetch`; required when payments are enabled. |
|
|
248
404
|
| `AIFINPAY_GATEWAY_ORIGINS` | — | Comma-separated exact HTTPS origins the agent may pay; required with payments. |
|
|
249
405
|
| `AIFINPAY_GATEWAY_PATH_MODE` | `gateway` | Use `gateway` for merchant-slug identity or `direct` for full request-path identity. |
|
|
250
|
-
| `AIFINPAY_PAY_CHAIN` | `polygon` | Owner-selected payment chain; see the2.
|
|
406
|
+
| `AIFINPAY_PAY_CHAIN` | `polygon` | Owner-selected payment chain; see the2.8.1 source capability list. |
|
|
251
407
|
| `AIFINPAY_MAX_GAS` | — | Gas cap per payment in the pay chain's native currency (POL, ETH, AVAX, BNB, XRP). |
|
|
252
408
|
| `AIFINPAY_RPC_URL` | chain's public RPC | HTTPS RPC for the pay chain. |
|
|
253
409
|
| `AIFINPAY_CLAIM_ORIGINS` | AiFinPay dashboards | Exact origins `agent_claim_self` may link the agent to. |
|
|
@@ -297,14 +453,14 @@ executor; this MCP does not expose Amoy paid receipt purchases.
|
|
|
297
453
|
|
|
298
454
|
MIT.
|
|
299
455
|
|
|
300
|
-
## Additional EVM networks in the 2.
|
|
456
|
+
## Additional EVM networks in the 2.8.1 source candidate
|
|
301
457
|
|
|
302
458
|
Owner `AIFINPAY_PAY_CHAIN` accepts polygon, base, optimism, arbitrum, avalanche,
|
|
303
459
|
bnb, unichain, xrplevm and robinhood. `AIFINPAY_MAX_GAS` is in the selected native
|
|
304
460
|
POL/ETH/AVAX/BNB/XRP currency; the legacy POL cap applies only on Polygon. Assets
|
|
305
461
|
must be pinned for that chain, including BNB 18-decimal USDC/USDT and Robinhood
|
|
306
462
|
18-decimal USDe/6-decimal USDG. XRPL EVM has native XRP only. Metadata comes from
|
|
307
|
-
agent 2.
|
|
463
|
+
agent 2.6.1; the backend must still authorize and serve that merchant/network.
|
|
308
464
|
Source support does not activate networks in production. Pending journals retain
|
|
309
465
|
the original chain and are recovered without another settlement. A legacy
|
|
310
466
|
missing-chain journal is adopted only when the owner-configured chain and the
|
|
@@ -312,13 +468,14 @@ actual signed transaction's chain ID, payer, pinned target and exact call agree.
|
|
|
312
468
|
Insufficient evidence requires manual receipt reconciliation. A later quote
|
|
313
469
|
expiry does not invalidate receipt recovery for an existing payment.
|
|
314
470
|
|
|
315
|
-
Release order: publish reviewed canonical skill2.
|
|
471
|
+
Release order after final release gates: publish reviewed canonical skill2.9.0 and agent2.6.1, refresh MCP
|
|
316
472
|
dependencies and lock from those real registry releases, pass standalone MCP
|
|
317
|
-
CI, then publish MCP2.
|
|
318
|
-
agent2.
|
|
473
|
+
CI, then publish MCP2.8.0. This candidate requires fresh source-packed
|
|
474
|
+
agent2.6.0 and skill2.9.0, plus the exact Python2.5.1 release target. The existing
|
|
475
|
+
registry agent2.5.0 / skill2.8.0 entries remain until publication;
|
|
319
476
|
no npm integrity/resolution is fabricated. An old2.3.x agent cannot build the
|
|
320
477
|
new descriptor/helper imports, and old skill2.6.0 cannot satisfy the exact
|
|
321
|
-
release-target guard. Hosted source CI uses `scripts/check-mcp-source-cohort.mjs`: a byte-preserving
|
|
478
|
+
release-target guard. Hosted source CI uses `scripts/check-mcp-source-cohort.mjs`: a byte-preserving patched registry bootstrap and genuine same-commit Node plus fully pinned canonical skill packages in disposable directories. The Node producer's authoritative source lock seeds the genuine runtime; it is separate resolution evidence, since npm excludes it from the tarball. Exact package bytes, versions, bundled skill, high/critical audits and full tests must pass; the registry publication guard must still refuse a source cohort lacking a matching real registry lock. Standalone release CI requires the refreshed real registry lock. Never publish the candidate with the old registry dependency lock.
|
|
322
479
|
|
|
323
480
|
Use one shared local filesystem for a wallet's ledger/journal and reconcile
|
|
324
481
|
pending transactions before downgrading. Node 2.4 reservations never expire for
|
package/bin/aifinpay-mcp.js
CHANGED
|
@@ -75,16 +75,26 @@ if (arg === "--help" || arg === "-h" || arg === "help") {
|
|
|
75
75
|
Run init once. It writes ${KEYSTORE} with mode 600 and the server picks
|
|
76
76
|
it up automatically — you do not have to put the secret in a config file.
|
|
77
77
|
|
|
78
|
+
Without a terminal on stdin (an agent's subprocess), or with
|
|
79
|
+
AIFINPAY_NONINTERACTIVE=1, init never prompts: it generates the passphrase
|
|
80
|
+
itself, encrypts the keystore and prints one JSON document with the keystore
|
|
81
|
+
path, the public addresses and the exact "env" block for the MCP client config.
|
|
82
|
+
|
|
78
83
|
Env (all optional):
|
|
79
|
-
SEED_HASH 32-byte hex seed; highest priority
|
|
84
|
+
SEED_HASH 32-byte hex seed; highest priority
|
|
85
|
+
AIFINPAY_AGENTS_FILE project agents file (default ./aifinpay/agents.json)
|
|
86
|
+
AIFINPAY_AGENT_ID select one record when the file has multiple agents
|
|
87
|
+
AIFINPAY_AGENT_SECRET legacy base58 secret; after project wallet sources
|
|
88
|
+
AIFINPAY_WALLET_INDEX child index derived from the selected seed
|
|
80
89
|
AIFINPAY_WALLET_PASSPHRASE opens an encrypted keystore; the server needs it too
|
|
81
90
|
AIFINPAY_BASE_URL default https://aifinpay.io
|
|
82
91
|
AIFINPAY_TIMEOUT_MS default 30000
|
|
83
92
|
AIFINPAY_HOME default ~/.aifinpay
|
|
84
93
|
|
|
85
|
-
Payments (payable_fetch)
|
|
94
|
+
Payments (payable_fetch) sign nothing until the owner sets all of
|
|
86
95
|
AIFINPAY_PAYMENTS_ENABLED=1, AIFINPAY_GATEWAY_ORIGINS, AIFINPAY_MAX_USD,
|
|
87
|
-
AIFINPAY_DAILY_USD and AIFINPAY_MAX_GAS_POL. init prints an example block
|
|
96
|
+
AIFINPAY_DAILY_USD and AIFINPAY_MAX_GAS_POL. init prints an example block;
|
|
97
|
+
the payment_setup_status tool says which of them is still missing.
|
|
88
98
|
`);
|
|
89
99
|
process.exit(0);
|
|
90
100
|
}
|
|
@@ -96,7 +106,14 @@ if (arg === "--version" || arg === "-v" || arg === "version") {
|
|
|
96
106
|
|
|
97
107
|
// ── init ──────────────────────────────────────────────────────────────────
|
|
98
108
|
|
|
99
|
-
const
|
|
109
|
+
const PASSPHRASE_ENV = "AIFINPAY_WALLET_PASSPHRASE";
|
|
110
|
+
let PASSPHRASE = process.env[PASSPHRASE_ENV] || null;
|
|
111
|
+
// An agent's subprocess has no terminal on stdin. Nothing here may wait for a
|
|
112
|
+
// human then: a 2026-10-19 agent run hung 167 s on a passphrase it could not
|
|
113
|
+
// type and invented its own key handling instead. In this mode init generates
|
|
114
|
+
// the passphrase itself and prints JSON the agent can act on.
|
|
115
|
+
const NONINTERACTIVE = process.env.AIFINPAY_NONINTERACTIVE === "1" || !process.stdin.isTTY;
|
|
116
|
+
let GENERATED_PASSPHRASE = false;
|
|
100
117
|
|
|
101
118
|
// Optional at-rest encryption. Mode 600 keeps the keystore from OTHER users; it
|
|
102
119
|
// does nothing against malware running as YOU — the threat the OSINT write-up of
|
|
@@ -126,7 +143,7 @@ function encryptSecret(secretB58) {
|
|
|
126
143
|
};
|
|
127
144
|
}
|
|
128
145
|
function decryptSecret(store) {
|
|
129
|
-
if (!PASSPHRASE) throw new Error(`${KEYSTORE} is encrypted but
|
|
146
|
+
if (!PASSPHRASE) throw new Error(`${KEYSTORE} is encrypted but ${PASSPHRASE_ENV} is not set.`);
|
|
130
147
|
const key = scryptSync(PASSPHRASE, Buffer.from(store.salt, "base64"), 32, {
|
|
131
148
|
N: 1 << 15,
|
|
132
149
|
r: 8,
|
|
@@ -156,52 +173,97 @@ function readKeystore() {
|
|
|
156
173
|
return typeof parsed?.secretB58 === "string" ? parsed : null;
|
|
157
174
|
}
|
|
158
175
|
|
|
159
|
-
// The owner settings that register payable_fetch, as init prints them. README
|
|
160
|
-
// "Enable native paid GET requests" shows the same block; a test keeps the two
|
|
161
|
-
// equal and starts a server with it. The gas cap is a worst case at the current
|
|
162
|
-
// Polygon gas price, not the fee: 0.05 POL refused every payment at ~280 gwei.
|
|
163
|
-
const PAYMENT_ENV = {
|
|
164
|
-
AIFINPAY_PAYMENTS_ENABLED: "1",
|
|
165
|
-
AIFINPAY_GATEWAY_ORIGINS: "https://merchant.example",
|
|
166
|
-
AIFINPAY_GATEWAY_PATH_MODE: "direct",
|
|
167
|
-
AIFINPAY_MAX_USD: "0.15",
|
|
168
|
-
AIFINPAY_DAILY_USD: "1.00",
|
|
169
|
-
AIFINPAY_MAX_GAS_POL: "0.3",
|
|
170
|
-
};
|
|
171
|
-
|
|
172
176
|
if (arg === "init") {
|
|
173
177
|
const { Agent, AiFinPayAgent } = await import("@aifinpay/agent");
|
|
174
178
|
|
|
175
179
|
const { loadConfigFromEnv } = await import("../dist/config.js");
|
|
176
180
|
const { loadWalletIdentity } = await import("../dist/identity.js");
|
|
177
|
-
const
|
|
181
|
+
const { PAYMENT_ENV } = await import("../dist/tools/payment-setup-status.js");
|
|
182
|
+
const config = loadConfigFromEnv();
|
|
183
|
+
// A keystore that cannot be opened is reported in one line that names the
|
|
184
|
+
// variable, never as a stack trace — and never replaced.
|
|
185
|
+
const failKeystore = (error) => {
|
|
186
|
+
const line =
|
|
187
|
+
!PASSPHRASE && existsSync(KEYSTORE)
|
|
188
|
+
? `${KEYSTORE} exists and is encrypted; set ${PASSPHRASE_ENV} to the passphrase used at init (it is not set in this environment).`
|
|
189
|
+
: error.message;
|
|
190
|
+
if (NONINTERACTIVE)
|
|
191
|
+
process.stdout.write(
|
|
192
|
+
JSON.stringify({ error: line, keystore: KEYSTORE, ...(PASSPHRASE ? {} : { missing_env: PASSPHRASE_ENV }) }) +
|
|
193
|
+
"\n"
|
|
194
|
+
);
|
|
195
|
+
else process.stderr.write(line + "\n");
|
|
196
|
+
process.exit(2);
|
|
197
|
+
};
|
|
198
|
+
let selected;
|
|
199
|
+
try {
|
|
200
|
+
selected = loadWalletIdentity(config);
|
|
201
|
+
} catch (error) {
|
|
202
|
+
failKeystore(error);
|
|
203
|
+
}
|
|
204
|
+
const plaintext = process.argv.includes("--plaintext");
|
|
205
|
+
const addressesOf = (agent) => ({ evm: agent.evmAddress, solana: agent.solanaAddress, casper: agent.casperAddress });
|
|
206
|
+
const indexEnv =
|
|
207
|
+
selected?.derivationIndex === undefined ? {} : { AIFINPAY_WALLET_INDEX: String(selected.derivationIndex) };
|
|
208
|
+
// Non-interactive stdout is one JSON document and nothing else, so the
|
|
209
|
+
// caller can parse it. The same facts, as prose, go to a terminal.
|
|
210
|
+
const emit = (json, text) => process.stdout.write(NONINTERACTIVE ? JSON.stringify(json, null, 2) + "\n" : text);
|
|
211
|
+
const nextStep =
|
|
212
|
+
'Put "env" into the aifinpay server entry of the MCP client config and reconnect the server (or call agent_reload in an already connected one), then call payment_setup_status: it names the next step up to the first payment.';
|
|
213
|
+
|
|
178
214
|
if (selected && selected.source !== "legacy-keystore") {
|
|
179
215
|
const agent = selected.seedHash
|
|
180
|
-
? await AiFinPayAgent.fromSeed(selected.seedHash)
|
|
216
|
+
? await AiFinPayAgent.fromSeed(selected.seedHash, { derivationIndex: selected.derivationIndex })
|
|
181
217
|
: await AiFinPayAgent.fromSolanaSecret(selected.secretB58);
|
|
182
|
-
|
|
183
|
-
|
|
218
|
+
emit(
|
|
219
|
+
{
|
|
220
|
+
keystore: null,
|
|
221
|
+
created: false,
|
|
222
|
+
encrypted: null,
|
|
223
|
+
source: selected.source,
|
|
224
|
+
addresses: addressesOf(agent),
|
|
225
|
+
env: indexEnv,
|
|
226
|
+
payment_env_example: PAYMENT_ENV,
|
|
227
|
+
next_step: nextStep,
|
|
228
|
+
},
|
|
229
|
+
`Using ${selected.source}${selected.derivationIndex === undefined ? "" : ` (index ${selected.derivationIndex})`}; no replacement wallet created.\n` +
|
|
184
230
|
`EVM ${agent.evmAddress}\nSolana ${agent.solanaAddress}\nCasper ${agent.casperAddress}\n` +
|
|
185
231
|
`Keep your configured seed backed up privately. Call agent_reload in an already connected MCP server.\n`
|
|
186
232
|
);
|
|
187
233
|
process.exit(0);
|
|
188
234
|
}
|
|
189
235
|
|
|
190
|
-
|
|
236
|
+
const openExistingKeystore = () => {
|
|
237
|
+
try {
|
|
238
|
+
return readKeystore();
|
|
239
|
+
} catch (error) {
|
|
240
|
+
failKeystore(error);
|
|
241
|
+
}
|
|
242
|
+
};
|
|
243
|
+
let store = openExistingKeystore();
|
|
191
244
|
let freshlyCreated = false;
|
|
192
245
|
// A new wallet is encrypted at rest unless the owner asks otherwise, by name.
|
|
193
246
|
// It used to be written in plaintext whenever AIFINPAY_WALLET_PASSPHRASE was
|
|
194
247
|
// unset — which is the default — so the documented one-liner produced an
|
|
195
248
|
// unencrypted key for a wallet people then funded. Existing keystores are
|
|
196
249
|
// untouched; only creating a new plaintext one needs the flag.
|
|
197
|
-
if (!store && !PASSPHRASE && !
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
250
|
+
if (!store && !PASSPHRASE && !plaintext) {
|
|
251
|
+
if (NONINTERACTIVE) {
|
|
252
|
+
// 192 random bits. The passphrase is printed once, inside the env block
|
|
253
|
+
// below, because the caller has to put it in the client config and there
|
|
254
|
+
// is no other channel; this is the trade-off of a wallet created by a
|
|
255
|
+
// process that cannot be asked anything.
|
|
256
|
+
PASSPHRASE = randomBytes(24).toString("base64url");
|
|
257
|
+
GENERATED_PASSPHRASE = true;
|
|
258
|
+
} else {
|
|
259
|
+
process.stderr.write(
|
|
260
|
+
`No wallet at ${KEYSTORE}, and none will be created unencrypted by default.\n\n` +
|
|
261
|
+
` Encrypted (recommended): AIFINPAY_WALLET_PASSPHRASE='<a long passphrase>' npx @aifinpay/mcp init\n` +
|
|
262
|
+
` Disposable test wallet: npx @aifinpay/mcp init --plaintext\n\n` +
|
|
263
|
+
`Keep the passphrase: an encrypted wallet cannot be recovered without it.\n`
|
|
264
|
+
);
|
|
265
|
+
process.exit(2);
|
|
266
|
+
}
|
|
205
267
|
}
|
|
206
268
|
if (!store) {
|
|
207
269
|
mkdirSync(HOME, { recursive: true, mode: 0o700 });
|
|
@@ -228,7 +290,9 @@ if (arg === "init") {
|
|
|
228
290
|
freshlyCreated = true;
|
|
229
291
|
} catch (error) {
|
|
230
292
|
if (error.code !== "EEXIST") throw error;
|
|
231
|
-
|
|
293
|
+
// The other process won; a passphrase generated here opens nothing.
|
|
294
|
+
if (GENERATED_PASSPHRASE) PASSPHRASE = null;
|
|
295
|
+
store = openExistingKeystore();
|
|
232
296
|
if (!store) throw new Error(`Existing ${KEYSTORE} is unreadable; refusing to replace it.`);
|
|
233
297
|
}
|
|
234
298
|
} finally {
|
|
@@ -236,6 +300,44 @@ if (arg === "init") {
|
|
|
236
300
|
if (ownsTemporary) unlinkSync(temporary);
|
|
237
301
|
}
|
|
238
302
|
}
|
|
303
|
+
const agent = selected?.seedHash
|
|
304
|
+
? await AiFinPayAgent.fromSeed(selected.seedHash, { derivationIndex: selected.derivationIndex })
|
|
305
|
+
: await AiFinPayAgent.fromSolanaSecret(store.secretB58);
|
|
306
|
+
// The server cannot open an encrypted keystore without the passphrase, so a
|
|
307
|
+
// config block without it does not start.
|
|
308
|
+
const encrypted = freshlyCreated ? Boolean(PASSPHRASE) : Boolean(store.encrypted);
|
|
309
|
+
|
|
310
|
+
if (NONINTERACTIVE) {
|
|
311
|
+
emit(
|
|
312
|
+
{
|
|
313
|
+
keystore: KEYSTORE,
|
|
314
|
+
created: freshlyCreated,
|
|
315
|
+
encrypted,
|
|
316
|
+
source: "legacy-keystore",
|
|
317
|
+
addresses: addressesOf(agent),
|
|
318
|
+
env: {
|
|
319
|
+
...(encrypted
|
|
320
|
+
? {
|
|
321
|
+
[PASSPHRASE_ENV]: GENERATED_PASSPHRASE
|
|
322
|
+
? PASSPHRASE
|
|
323
|
+
: `<the ${PASSPHRASE_ENV} already set in this environment>`,
|
|
324
|
+
}
|
|
325
|
+
: {}),
|
|
326
|
+
...indexEnv,
|
|
327
|
+
},
|
|
328
|
+
...(GENERATED_PASSPHRASE
|
|
329
|
+
? {
|
|
330
|
+
passphrase_note: `${PASSPHRASE_ENV} was generated for this wallet and appears only here, inside env. The wallet cannot be opened or recovered without it.`,
|
|
331
|
+
}
|
|
332
|
+
: {}),
|
|
333
|
+
payment_env_example: PAYMENT_ENV,
|
|
334
|
+
next_step: nextStep,
|
|
335
|
+
},
|
|
336
|
+
""
|
|
337
|
+
);
|
|
338
|
+
process.exit(0);
|
|
339
|
+
}
|
|
340
|
+
|
|
239
341
|
if (freshlyCreated) {
|
|
240
342
|
process.stdout.write(
|
|
241
343
|
PASSPHRASE
|
|
@@ -265,26 +367,25 @@ if (arg === "init") {
|
|
|
265
367
|
);
|
|
266
368
|
}
|
|
267
369
|
|
|
268
|
-
const agent = await AiFinPayAgent.fromSolanaSecret(store.secretB58);
|
|
269
|
-
// The server cannot open an encrypted keystore without the passphrase, so a
|
|
270
|
-
// config block without it does not start.
|
|
271
|
-
const encrypted = freshlyCreated ? Boolean(PASSPHRASE) : Boolean(store.encrypted);
|
|
272
|
-
|
|
273
370
|
process.stdout.write(
|
|
274
|
-
`Your agent's addresses — the EVM one is the same on every EVM chain:\n\n` +
|
|
371
|
+
`Your agent's addresses${selected?.derivationIndex === undefined ? "" : ` (index ${selected.derivationIndex})`} — the EVM one is the same on every EVM chain:\n\n` +
|
|
275
372
|
` EVM ${agent.evmAddress}\n` +
|
|
276
373
|
` Solana ${agent.solanaAddress}\n` +
|
|
277
374
|
` Casper ${agent.casperAddress}\n\n` +
|
|
278
375
|
`Add this to your MCP client config and connect/reconnect this MCP server.\n` +
|
|
279
|
-
`As printed it cannot pay
|
|
280
|
-
`settings further down
|
|
376
|
+
`As printed it cannot pay: payable_fetch is listed but signs nothing until\n` +
|
|
377
|
+
`you add the payment settings further down (payment_setup_status says what\n` +
|
|
378
|
+
`is still missing):\n\n` +
|
|
281
379
|
JSON.stringify(
|
|
282
380
|
{
|
|
283
381
|
mcpServers: {
|
|
284
382
|
aifinpay: {
|
|
285
383
|
command: "npx",
|
|
286
384
|
args: ["-y", `@aifinpay/mcp@${VERSION}`],
|
|
287
|
-
env:
|
|
385
|
+
env: {
|
|
386
|
+
...(encrypted ? { AIFINPAY_WALLET_PASSPHRASE: "<the passphrase you used for init>" } : {}),
|
|
387
|
+
...indexEnv,
|
|
388
|
+
},
|
|
288
389
|
},
|
|
289
390
|
},
|
|
290
391
|
},
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client-framework.d.ts","sourceRoot":"","sources":["../src/client-framework.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AAqBtD,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,OAAO,GAAG,cAAc,GAAG,SAAS,CAE5E"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MCP `clientInfo.name`, as the client sent it at initialize, → the
|
|
3
|
+
* AIFP-Agent-Framework the backend records. Exact names only: a client not
|
|
4
|
+
* listed here sends no header rather than a guess. Entries are the names the
|
|
5
|
+
* product owner listed plus `cursor-vscode`, which Cursor is reported to send;
|
|
6
|
+
* confirm new ones against real initialize payloads before adding them.
|
|
7
|
+
*/
|
|
8
|
+
const CLIENT_FRAMEWORKS = new Map([
|
|
9
|
+
["claude-ai", "claude"],
|
|
10
|
+
["claude-code", "claude"],
|
|
11
|
+
["cursor", "cursor"],
|
|
12
|
+
["cursor-vscode", "cursor"],
|
|
13
|
+
["windsurf", "windsurf"],
|
|
14
|
+
["chatgpt", "chatgpt"],
|
|
15
|
+
["openai", "chatgpt"],
|
|
16
|
+
["gemini", "gemini"],
|
|
17
|
+
["perplexity", "perplexity"],
|
|
18
|
+
]);
|
|
19
|
+
export function frameworkForClient(name) {
|
|
20
|
+
return typeof name === "string" ? CLIENT_FRAMEWORKS.get(name) : undefined;
|
|
21
|
+
}
|
|
22
|
+
//# sourceMappingURL=client-framework.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client-framework.js","sourceRoot":"","sources":["../src/client-framework.ts"],"names":[],"mappings":"AAEA;;;;;;GAMG;AACH,MAAM,iBAAiB,GAAG,IAAI,GAAG,CAAyB;IACxD,CAAC,WAAW,EAAE,QAAQ,CAAC;IACvB,CAAC,aAAa,EAAE,QAAQ,CAAC;IACzB,CAAC,QAAQ,EAAE,QAAQ,CAAC;IACpB,CAAC,eAAe,EAAE,QAAQ,CAAC;IAC3B,CAAC,UAAU,EAAE,UAAU,CAAC;IACxB,CAAC,SAAS,EAAE,SAAS,CAAC;IACtB,CAAC,QAAQ,EAAE,SAAS,CAAC;IACrB,CAAC,QAAQ,EAAE,QAAQ,CAAC;IACpB,CAAC,YAAY,EAAE,YAAY,CAAC;CAC7B,CAAC,CAAC;AAEH,MAAM,UAAU,kBAAkB,CAAC,IAAa;IAC9C,OAAO,OAAO,IAAI,KAAK,QAAQ,CAAC,CAAC,CAAC,iBAAiB,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;AAC5E,CAAC"}
|
package/dist/config.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { EvmWalletClient } from "@aifinpay/agent";
|
|
1
2
|
/** Runtime configuration loaded from env. */
|
|
2
3
|
export interface McpConfig {
|
|
3
4
|
/** Legacy base58 secret, after SEED_HASH and the project agents file. */
|
|
@@ -8,6 +9,10 @@ export interface McpConfig {
|
|
|
8
9
|
agentId?: string;
|
|
9
10
|
walletHome?: string;
|
|
10
11
|
walletPassphrase?: string;
|
|
12
|
+
/** Child wallet number from a seed or indexed wallet keystore. */
|
|
13
|
+
walletIndex?: number;
|
|
14
|
+
/** Programmatic external EVM signer supplied by an embedding MCP host. */
|
|
15
|
+
evmWalletClient?: EvmWalletClient;
|
|
11
16
|
/** Dev environment. EVM payments stay live-only; Solana requires explicit devnet. */
|
|
12
17
|
devMode?: boolean;
|
|
13
18
|
/** Custom AiFinPay backend URL. Defaults to production. */
|
package/dist/config.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;AAGvD,6CAA6C;AAC7C,MAAM,WAAW,SAAS;IACxB,yEAAyE;IACzE,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,yEAAyE;IACzE,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,kEAAkE;IAClE,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,0EAA0E;IAC1E,eAAe,CAAC,EAAE,eAAe,CAAC;IAClC,qFAAqF;IACrF,OAAO,CAAC,EAAE,OAAO,CAAC;IAElB,2DAA2D;IAC3D,OAAO,CAAC,EAAE,MAAM,CAAC;IAEjB,6BAA6B;IAC7B,SAAS,CAAC,EAAE,MAAM,CAAC;IAEnB,wDAAwD;IACxD,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB;0EACsE;IACtE,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,iEAAiE;IACjE,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,0EAA0E;IAC1E,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,4EAA4E;IAC5E,aAAa,CAAC,EAAE,SAAS,GAAG,QAAQ,CAAC;IACrC,uEAAuE;IACvE,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,kEAAkE;IAClE,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB;yFACqF;IACrF,QAAQ,CAAC,EAAE,MAAM,CAAC;IAElB,8DAA8D;IAC9D,YAAY,CAAC,EAAE,MAAM,CAAC;IAEtB;;;;;;;;;;;+CAW2C;IAC3C,cAAc,CAAC,EAAE,MAAM,EAAE,CAAC;IAE1B,2EAA2E;IAC3E,eAAe,CAAC,EAAE,SAAS,GAAG,QAAQ,CAAC;IAEvC;;;;;;;;;8CAS0C;IAC1C,YAAY,CAAC,EAAE,MAAM,EAAE,CAAC;IAExB,qDAAqD;IACrD,KAAK,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,EAAE,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;CACjE;AAED,wBAAgB,iBAAiB,IAAI,SAAS,CAgC7C;AA4ED,oFAAoF;AACpF,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,SAAS,GAAG,MAAM,CAkF/D"}
|
package/dist/config.js
CHANGED
|
@@ -23,6 +23,7 @@ export function loadConfigFromEnv() {
|
|
|
23
23
|
agentId: process.env.AIFINPAY_AGENT_ID || undefined,
|
|
24
24
|
walletHome: process.env.AIFINPAY_HOME || undefined,
|
|
25
25
|
walletPassphrase: process.env.AIFINPAY_WALLET_PASSPHRASE || undefined,
|
|
26
|
+
walletIndex: parseWalletIndex(process.env.AIFINPAY_WALLET_INDEX),
|
|
26
27
|
agentSecretB58: process.env.AIFINPAY_AGENT_SECRET || undefined,
|
|
27
28
|
baseUrl: process.env.AIFINPAY_BASE_URL || undefined,
|
|
28
29
|
timeoutMs: process.env.AIFINPAY_TIMEOUT_MS ? Number(process.env.AIFINPAY_TIMEOUT_MS) : undefined,
|
|
@@ -32,6 +33,18 @@ export function loadConfigFromEnv() {
|
|
|
32
33
|
trustedHosts: splitList(process.env.AIFINPAY_TRUSTED_HOSTS),
|
|
33
34
|
};
|
|
34
35
|
}
|
|
36
|
+
function parseWalletIndex(raw) {
|
|
37
|
+
if (raw === undefined)
|
|
38
|
+
return undefined;
|
|
39
|
+
if (!/^(0|[1-9][0-9]*)$/.test(raw)) {
|
|
40
|
+
throw new Error("AIFINPAY_WALLET_INDEX must be a non-negative integer");
|
|
41
|
+
}
|
|
42
|
+
const index = Number(raw);
|
|
43
|
+
if (!Number.isSafeInteger(index) || index > 0xffffffff) {
|
|
44
|
+
throw new Error("AIFINPAY_WALLET_INDEX must be between 0 and 4294967295");
|
|
45
|
+
}
|
|
46
|
+
return index;
|
|
47
|
+
}
|
|
35
48
|
function parseSolanaNetwork(raw) {
|
|
36
49
|
if (raw === undefined)
|
|
37
50
|
return undefined;
|