@aifinpay/mcp 2.5.0 → 2.7.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 +111 -20
- package/dist/config.d.ts +6 -2
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +39 -4
- package/dist/config.js.map +1 -1
- package/dist/native-price.d.ts +1 -1
- package/dist/native-price.d.ts.map +1 -1
- package/dist/native-price.js +3 -3
- package/dist/native-price.js.map +1 -1
- package/dist/pay-chains.d.ts +5 -4
- package/dist/pay-chains.d.ts.map +1 -1
- package/dist/pay-chains.js +29 -28
- package/dist/pay-chains.js.map +1 -1
- package/dist/payment-identity.d.ts +17 -0
- package/dist/payment-identity.d.ts.map +1 -0
- package/dist/payment-identity.js +75 -0
- package/dist/payment-identity.js.map +1 -0
- package/dist/payment-state.d.ts +2 -1
- package/dist/payment-state.d.ts.map +1 -1
- package/dist/payment-state.js +52 -17
- package/dist/payment-state.js.map +1 -1
- package/dist/server.d.ts +1 -1
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +3 -2
- package/dist/server.js.map +1 -1
- package/dist/tools/agent-claim-self.d.ts +3 -3
- package/dist/tools/agent-claim-self.d.ts.map +1 -1
- package/dist/tools/agent-claim-self.js +65 -88
- package/dist/tools/agent-claim-self.js.map +1 -1
- package/dist/tools/agent-history.d.ts +0 -1
- package/dist/tools/agent-history.d.ts.map +1 -1
- package/dist/tools/agent-history.js +51 -52
- package/dist/tools/agent-history.js.map +1 -1
- package/dist/tools/agent-quota.d.ts.map +1 -1
- package/dist/tools/agent-quota.js +42 -11
- package/dist/tools/agent-quota.js.map +1 -1
- package/dist/tools/payable-fetch.d.ts.map +1 -1
- package/dist/tools/payable-fetch.js +156 -57
- package/dist/tools/payable-fetch.js.map +1 -1
- package/package.json +6 -6
- package/skills/SKILL.md +165 -43
- package/dist/directory.d.ts +0 -55
- package/dist/directory.d.ts.map +0 -1
- package/dist/directory.js +0 -56
- package/dist/directory.js.map +0 -1
- package/dist/tools/agent-call.d.ts +0 -68
- package/dist/tools/agent-call.d.ts.map +0 -1
- package/dist/tools/agent-call.js +0 -129
- package/dist/tools/agent-call.js.map +0 -1
- package/dist/tools/agent-quote.d.ts +0 -41
- package/dist/tools/agent-quote.d.ts.map +0 -1
- package/dist/tools/agent-quote.js +0 -81
- package/dist/tools/agent-quote.js.map +0 -1
- package/dist/tools/pay-with-split.d.ts +0 -93
- package/dist/tools/pay-with-split.d.ts.map +0 -1
- package/dist/tools/pay-with-split.js +0 -138
- package/dist/tools/pay-with-split.js.map +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"agent-history.d.ts","sourceRoot":"","sources":["../../src/tools/agent-history.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"agent-history.d.ts","sourceRoot":"","sources":["../../src/tools/agent-history.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAShD,wBAAgB,gBAAgB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EA0B/B;AAED,wBAAsB,eAAe,CAAC,GAAG,EAAE,WAAW,EAAE,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;;;;;;;;;;;GA+HpF"}
|
|
@@ -1,8 +1,10 @@
|
|
|
1
|
+
import { AGENT_RECEIPT_FIELDS, AGENT_TRANSACTION_FIELDS } from "@aifinpay/agent";
|
|
1
2
|
import { apiUrl } from "../api.js";
|
|
3
|
+
import { isSolanaPublicKey, solanaHistorySelection, assertSolanaReadContext, solanaIndexingMetadata, } from "../payment-identity.js";
|
|
2
4
|
export function agentHistoryTool() {
|
|
3
5
|
return {
|
|
4
6
|
name: "agent_history",
|
|
5
|
-
description: "Read an agent's payment history by
|
|
7
|
+
description: "Read an agent's indexed AiFinPay payment history by wallet address, public passport identifier, or both. Defaults to the configured payment chain and its local wallet. Solana history requires the owner-selected cluster; arbitrary wallet transfers are excluded. Receipts include retained prepaid batches and test payments. Never pass a seed, private key or API secret as a passport.",
|
|
6
8
|
inputSchema: {
|
|
7
9
|
type: "object",
|
|
8
10
|
additionalProperties: false,
|
|
@@ -14,8 +16,7 @@ export function agentHistoryTool() {
|
|
|
14
16
|
},
|
|
15
17
|
network: {
|
|
16
18
|
type: "string",
|
|
17
|
-
|
|
18
|
-
description: "Exact passport wallet network; indexed transactions currently support polygon only",
|
|
19
|
+
description: "Exact wallet chain; defaults to the configured payment chain (Polygon when unset). Solana cluster comes from owner configuration.",
|
|
19
20
|
},
|
|
20
21
|
source: { type: "string", enum: ["transactions", "receipts"], default: "transactions" },
|
|
21
22
|
limit: { type: "integer", minimum: 1, maximum: 100, default: 25 },
|
|
@@ -27,7 +28,8 @@ export function agentHistoryTool() {
|
|
|
27
28
|
}
|
|
28
29
|
export async function runAgentHistory(ctx, args) {
|
|
29
30
|
const base = ctx.config.baseUrl || "https://aifinpay.io";
|
|
30
|
-
const source = args.source ?? "transactions", network = args.network ?? "polygon";
|
|
31
|
+
const source = args.source ?? "transactions", network = args.network ?? ctx.config.payChain ?? "polygon";
|
|
32
|
+
const solana = network === "solana";
|
|
31
33
|
const limit = args.limit ?? 25, offset = args.offset ?? 0;
|
|
32
34
|
const failure = (message) => ({
|
|
33
35
|
isError: true,
|
|
@@ -51,6 +53,9 @@ export async function runAgentHistory(ctx, args) {
|
|
|
51
53
|
return (await response.json());
|
|
52
54
|
}
|
|
53
55
|
try {
|
|
56
|
+
if (typeof network !== "string")
|
|
57
|
+
throw new Error("History network must be an exact chain name");
|
|
58
|
+
const solanaSelection = solana ? solanaHistorySelection(ctx.config) : undefined;
|
|
54
59
|
let address = args.address == null ? undefined : String(args.address);
|
|
55
60
|
if (args.passport) {
|
|
56
61
|
const identifier = String(args.passport).trim();
|
|
@@ -60,67 +65,59 @@ export async function runAgentHistory(ctx, args) {
|
|
|
60
65
|
const resolved = await get(`/api/agent/resolve/${encodeURIComponent(identifier)}`);
|
|
61
66
|
if (resolved?.agent?.status !== "active" || !Array.isArray(resolved.agent.wallets))
|
|
62
67
|
throw new Error("Passport is not active or has no verified wallets");
|
|
63
|
-
const wallets = resolved.agent.wallets.filter((w) => w.network === network && w.chain_family === "evm" && Number(w.verified_at) > 0);
|
|
68
|
+
const wallets = resolved.agent.wallets.filter((w) => w.network === network && w.chain_family === (solana ? "solana" : "evm") && Number(w.verified_at) > 0);
|
|
64
69
|
const wallet = wallets.find((w) => w.is_primary) || wallets[0];
|
|
65
70
|
if (!wallet || typeof wallet.address !== "string")
|
|
66
|
-
throw new Error("Passport has no verified
|
|
67
|
-
if (address && address.toLowerCase() !== wallet.address.toLowerCase())
|
|
71
|
+
throw new Error("Passport has no verified wallet on the requested network");
|
|
72
|
+
if (address && (solana ? address !== wallet.address : address.toLowerCase() !== wallet.address.toLowerCase()))
|
|
68
73
|
throw new Error("Address does not match passport wallet");
|
|
69
74
|
address = wallet.address;
|
|
70
75
|
}
|
|
71
|
-
address ??= ctx.agent.evmAddress;
|
|
72
|
-
if (!/^0x[0-9a-fA-F]{40}$/.test(address))
|
|
73
|
-
return failure("Expected an EVM address");
|
|
76
|
+
address ??= solana ? ctx.agent.solanaAddress : ctx.agent.evmAddress;
|
|
77
|
+
if (solana ? !isSolanaPublicKey(address) : !/^0x[0-9a-fA-F]{40}$/.test(address))
|
|
78
|
+
return failure(solana ? "Expected a canonical 32-byte Solana address" : "Expected an EVM address");
|
|
79
|
+
const canonicalAddress = solana ? address : address.toLowerCase();
|
|
74
80
|
const query = new URLSearchParams({ limit: String(limit), offset: String(offset) });
|
|
75
|
-
if (source === "transactions")
|
|
81
|
+
if (source === "transactions" || solana)
|
|
76
82
|
query.set("chain", String(network));
|
|
77
|
-
|
|
83
|
+
if (solanaSelection) {
|
|
84
|
+
query.set("network", solanaSelection.network);
|
|
85
|
+
query.set("program", solanaSelection.program);
|
|
86
|
+
}
|
|
87
|
+
const body = await get(`/v1/agents/${canonicalAddress}/${source}?${query}`);
|
|
78
88
|
if (!Array.isArray(body?.[source]))
|
|
79
89
|
throw new Error("Invalid history response");
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
"
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
"
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
"tx_hash",
|
|
104
|
-
"log_index",
|
|
105
|
-
"chain",
|
|
106
|
-
"payment_id",
|
|
107
|
-
"block_number",
|
|
108
|
-
"block_ts",
|
|
109
|
-
"agent_address",
|
|
110
|
-
"merchant_address",
|
|
111
|
-
"token_address",
|
|
112
|
-
"total_amount",
|
|
113
|
-
"merchant_amount",
|
|
114
|
-
"treasury_fee",
|
|
115
|
-
"ip_creator_fee",
|
|
116
|
-
];
|
|
117
|
-
const items = body[source].map((row) => Object.fromEntries(fields.filter((f) => row?.[f] !== undefined).map((f) => [f, row[f]])));
|
|
90
|
+
if (solanaSelection)
|
|
91
|
+
assertSolanaReadContext(body, canonicalAddress, solanaSelection, "address");
|
|
92
|
+
const fields = [
|
|
93
|
+
...(source === "receipts" ? AGENT_RECEIPT_FIELDS : AGENT_TRANSACTION_FIELDS),
|
|
94
|
+
"program_id",
|
|
95
|
+
"idl_sha256",
|
|
96
|
+
"token_decimals",
|
|
97
|
+
"asset",
|
|
98
|
+
"cost_allocation",
|
|
99
|
+
];
|
|
100
|
+
const items = body[source].map((row) => {
|
|
101
|
+
if (solanaSelection)
|
|
102
|
+
assertSolanaReadContext(row, canonicalAddress, solanaSelection, source === "receipts" ? "payer" : "agent_address");
|
|
103
|
+
if (solanaSelection &&
|
|
104
|
+
fields.some((f) => row[f] !== undefined &&
|
|
105
|
+
row[f] !== null &&
|
|
106
|
+
(typeof row[f] === "object" ||
|
|
107
|
+
!["string", "number", "boolean"].includes(typeof row[f]) ||
|
|
108
|
+
(typeof row[f] === "number" && !Number.isFinite(row[f])))))
|
|
109
|
+
throw new Error("Invalid structured Solana public metadata");
|
|
110
|
+
return Object.fromEntries(fields.filter((f) => row?.[f] !== undefined).map((f) => [f, row[f]]));
|
|
111
|
+
});
|
|
112
|
+
const indexing = solanaSelection && source === "transactions" ? solanaIndexingMetadata(body.indexing) : undefined;
|
|
118
113
|
return {
|
|
119
114
|
content: [
|
|
120
115
|
{
|
|
121
116
|
type: "text",
|
|
122
117
|
text: JSON.stringify({
|
|
123
|
-
address:
|
|
118
|
+
address: canonicalAddress,
|
|
119
|
+
...(solanaSelection ? { chain: "solana", ...solanaSelection } : {}),
|
|
120
|
+
...(indexing ? { indexing } : {}),
|
|
124
121
|
source,
|
|
125
122
|
items,
|
|
126
123
|
limit,
|
|
@@ -128,7 +125,9 @@ export async function runAgentHistory(ctx, args) {
|
|
|
128
125
|
next_offset: typeof body.next_offset === "number" ? body.next_offset : null,
|
|
129
126
|
coverage: source === "receipts"
|
|
130
127
|
? "Retained receipts only; externally metered quotas may lag"
|
|
131
|
-
:
|
|
128
|
+
: solanaSelection
|
|
129
|
+
? "Verified Solana settlements from retained RPC inventory only; partial history, no full archive or wallet coverage attestation; shared or unquoted transaction costs may be unavailable"
|
|
130
|
+
: "Indexed AiFinPay settlements for the requested chain and cluster; excludes arbitrary wallet transfers and may lag the chain",
|
|
132
131
|
}, null, 2),
|
|
133
132
|
},
|
|
134
133
|
],
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"agent-history.js","sourceRoot":"","sources":["../../src/tools/agent-history.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"agent-history.js","sourceRoot":"","sources":["../../src/tools/agent-history.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"agent-quota.d.ts","sourceRoot":"","sources":["../../src/tools/agent-quota.ts"],"names":[],"mappings":"AAqBA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;
|
|
1
|
+
{"version":3,"file":"agent-quota.d.ts","sourceRoot":"","sources":["../../src/tools/agent-quota.ts"],"names":[],"mappings":"AAqBA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAUhD,wBAAgB,cAAc;;;;;;;;;;;;;;;;;;;;;;;;;EA0B7B;AAkBD,wBAAsB,aAAa,CAAC,GAAG,EAAE,WAAW,EAAE,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;;;;;;;;;;;GAgGlF"}
|
|
@@ -1,11 +1,12 @@
|
|
|
1
|
+
import { isSolanaPublicKey, solanaHistorySelection, assertSolanaReadContext, assertSolanaQuota, } from "../payment-identity.js";
|
|
1
2
|
const DEFAULT_BASE = "https://api.aifinpay.io";
|
|
2
3
|
export function agentQuotaTool() {
|
|
3
4
|
return {
|
|
4
5
|
name: "agent_quota",
|
|
5
|
-
description: "
|
|
6
|
+
description: "Read this agent's prepaid quota, per service. Lists the " +
|
|
6
7
|
"agent's active quota batches (merchant, resource, used, remaining, " +
|
|
7
8
|
"expiry). Read-only; never spends, pays, or signs anything. Ask it " +
|
|
8
|
-
"things like: how
|
|
9
|
+
"things like: how much prepaid quota do I have left at mrch_x?",
|
|
9
10
|
inputSchema: {
|
|
10
11
|
type: "object",
|
|
11
12
|
properties: {
|
|
@@ -27,12 +28,21 @@ export function agentQuotaTool() {
|
|
|
27
28
|
}
|
|
28
29
|
export async function runAgentQuota(ctx, args) {
|
|
29
30
|
const base = (ctx.config.baseUrl ?? DEFAULT_BASE).replace(/\/+$/, "");
|
|
30
|
-
const
|
|
31
|
+
const solana = ctx.config.payChain === "solana";
|
|
32
|
+
const address = solana ? ctx.agent.solanaAddress : ctx.agent.evmAddress;
|
|
31
33
|
const merchantFilter = typeof args.merchant_id === "string" && args.merchant_id ? args.merchant_id : null;
|
|
32
34
|
const includeExhausted = args.include_exhausted === true;
|
|
33
35
|
let resp;
|
|
36
|
+
let solanaSelection;
|
|
34
37
|
try {
|
|
35
|
-
|
|
38
|
+
let query = "";
|
|
39
|
+
if (solana) {
|
|
40
|
+
if (!isSolanaPublicKey(address))
|
|
41
|
+
throw new Error("Expected a canonical 32-byte Solana address");
|
|
42
|
+
solanaSelection = solanaHistorySelection(ctx.config);
|
|
43
|
+
query = `?${new URLSearchParams({ chain: "solana", network: solanaSelection.network, program: solanaSelection.program })}`;
|
|
44
|
+
}
|
|
45
|
+
resp = await ctx.agent.inner.fetchImpl(`${base}/v1/agents/${address}/receipts${query}`, {
|
|
36
46
|
method: "GET",
|
|
37
47
|
});
|
|
38
48
|
}
|
|
@@ -43,7 +53,22 @@ export async function runAgentQuota(ctx, args) {
|
|
|
43
53
|
if (!resp.ok) {
|
|
44
54
|
return errorResult(`quota lookup failed: HTTP ${resp.status}`);
|
|
45
55
|
}
|
|
46
|
-
|
|
56
|
+
let body;
|
|
57
|
+
try {
|
|
58
|
+
body = (await resp.json());
|
|
59
|
+
if (solanaSelection) {
|
|
60
|
+
assertSolanaReadContext(body, address, solanaSelection, "address");
|
|
61
|
+
if (!Array.isArray(body.receipts))
|
|
62
|
+
throw new Error("Invalid Solana quota response");
|
|
63
|
+
for (const row of body.receipts) {
|
|
64
|
+
assertSolanaReadContext(row, address, solanaSelection, "payer");
|
|
65
|
+
assertSolanaQuota(row);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
catch {
|
|
70
|
+
return errorResult("Quota metadata is unavailable or does not match the selected wallet/network/program");
|
|
71
|
+
}
|
|
47
72
|
const now = Math.floor(Date.now() / 1000);
|
|
48
73
|
let batches = (body.receipts ?? [])
|
|
49
74
|
.filter((r) => r.exp == null || r.exp > now)
|
|
@@ -56,7 +81,7 @@ export async function runAgentQuota(ctx, args) {
|
|
|
56
81
|
tier: r.tier,
|
|
57
82
|
used: r.used ?? 0,
|
|
58
83
|
remaining: r.remaining ?? 0,
|
|
59
|
-
quota: r.quota ?? 1,
|
|
84
|
+
quota: solanaSelection ? r.unit_quota : (r.quota ?? 1),
|
|
60
85
|
paid: r.amount != null ? `${r.amount} ${r.currency ?? "USD"}` : undefined,
|
|
61
86
|
expires: r.exp != null ? new Date(r.exp * 1000).toISOString() : undefined,
|
|
62
87
|
receipt_id: r.receipt_id,
|
|
@@ -70,19 +95,25 @@ export async function runAgentQuota(ctx, args) {
|
|
|
70
95
|
const key = b.merchant_id ?? "unknown";
|
|
71
96
|
const cur = perMerchant.get(key) ?? { remaining: 0, batches: 0 };
|
|
72
97
|
cur.remaining += b.remaining;
|
|
98
|
+
if (solanaSelection && !Number.isSafeInteger(cur.remaining))
|
|
99
|
+
return errorResult("Quota total exceeds safe integer range");
|
|
73
100
|
cur.batches += 1;
|
|
74
101
|
perMerchant.set(key, cur);
|
|
75
102
|
}
|
|
76
103
|
const payload = {
|
|
77
104
|
agent: address,
|
|
105
|
+
...(solanaSelection ? { chain: "solana", ...solanaSelection } : {}),
|
|
106
|
+
...(solanaSelection ? { unit: "billing_units" } : {}),
|
|
78
107
|
...(merchantFilter ? { merchant_filter: merchantFilter } : {}),
|
|
79
108
|
totals: Object.fromEntries(perMerchant),
|
|
80
109
|
batches,
|
|
81
|
-
note:
|
|
82
|
-
?
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
110
|
+
note: solanaSelection
|
|
111
|
+
? "Solana remaining/used/quota are billing units. The number of callable requests depends on each route's server-defined cost_units. Hosted gateway usage is durable; external merchant metering may lag. An empty result means no retained active quota in this selected wallet/network/program."
|
|
112
|
+
: batches.length === 0
|
|
113
|
+
? merchantFilter
|
|
114
|
+
? `no active quota at ${merchantFilter} — a new batch starts with that service's 402 (or agent_quote its URL first)`
|
|
115
|
+
: "no active quota anywhere — pay a service's 402 to start a batch"
|
|
116
|
+
: "`remaining` is authoritative where AiFinPay meters (hosted gateway); a merchant running its own @aifinpay/gate meters locally and our copy can lag behind by their traffic.",
|
|
86
117
|
};
|
|
87
118
|
return {
|
|
88
119
|
content: [{ type: "text", text: JSON.stringify(payload, null, 2) }],
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"agent-quota.js","sourceRoot":"","sources":["../../src/tools/agent-quota.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"agent-quota.js","sourceRoot":"","sources":["../../src/tools/agent-quota.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"payable-fetch.d.ts","sourceRoot":"","sources":["../../src/tools/payable-fetch.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"payable-fetch.d.ts","sourceRoot":"","sources":["../../src/tools/payable-fetch.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAWhD,wBAAgB,gBAAgB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EA0B/B;AAED,2FAA2F;AAC3F,wBAAsB,eAAe,CAAC,GAAG,EAAE,WAAW,EAAE,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;;;;;GAsWpF"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Aifp1SettlementUnsupportedError, parseGatewayUrl } from "@aifinpay/agent";
|
|
1
|
+
import { Aifp1SettlementUnsupportedError, Aifp1FinalizedFailureError, assertPreparedV14Recovery, parseGatewayUrl, } from "@aifinpay/agent";
|
|
2
2
|
import { validatePaymentConfig } from "../config.js";
|
|
3
3
|
import { PaymentStateError } from "../payment-state.js";
|
|
4
4
|
import { independentNativeUsd } from "../native-price.js";
|
|
@@ -9,7 +9,7 @@ const SCOPES = ["exact", "merchant"];
|
|
|
9
9
|
export function payableFetchTool() {
|
|
10
10
|
return {
|
|
11
11
|
name: "payable_fetch",
|
|
12
|
-
description: 'Fetch a GET resource from an owner-approved AiFinPay merchant. Buys a prepaid batch through verified v1.4 on the chain the owner set in AIFINPAY_PAY_CHAIN (Polygon by default
|
|
12
|
+
description: 'Fetch a GET resource from an owner-approved AiFinPay merchant. Buys a prepaid batch through verified v1.4 on the chain the owner set in AIFINPAY_PAY_CHAIN (Polygon by default; other supported networks require explicit owner selection) — in its native currency (POL, SOL, ETH, AVAX, BNB or XRP), or in the stablecoin the owner set in AIFINPAY_PAY_ASSET (e.g. USDC; gas is still native) — within owner limits, then reuses its receipt. With scope "merchant" one batch covers every path on the site. Pending payments are recovered without sending another transaction. Other payment protocols are unsupported.',
|
|
13
13
|
inputSchema: {
|
|
14
14
|
type: "object",
|
|
15
15
|
properties: {
|
|
@@ -36,9 +36,9 @@ export async function runPayableFetch(ctx, args) {
|
|
|
36
36
|
let state;
|
|
37
37
|
let chain;
|
|
38
38
|
try {
|
|
39
|
-
let
|
|
39
|
+
let feeCap;
|
|
40
40
|
try {
|
|
41
|
-
|
|
41
|
+
feeCap = validatePaymentConfig(ctx.config);
|
|
42
42
|
chain = payChain(ctx.config.payChain);
|
|
43
43
|
}
|
|
44
44
|
catch (error) {
|
|
@@ -75,25 +75,89 @@ export async function runPayableFetch(ctx, args) {
|
|
|
75
75
|
ctx.agent.aifp1Receipts.evict(entry);
|
|
76
76
|
for (const entry of state.receipts)
|
|
77
77
|
ctx.agent.aifp1Receipts.put(entry);
|
|
78
|
+
const reconcileFinalizedFailure = (error) => {
|
|
79
|
+
if (!(error instanceof Aifp1FinalizedFailureError))
|
|
80
|
+
return false;
|
|
81
|
+
const pending = state.pending;
|
|
82
|
+
const recovery = error.recovery;
|
|
83
|
+
if (!pending ||
|
|
84
|
+
pending.recovery.family !== "solana" ||
|
|
85
|
+
recovery?.family !== "solana" ||
|
|
86
|
+
JSON.stringify(recovery) !== JSON.stringify(pending.recovery) ||
|
|
87
|
+
pending.amountUsd !== recovery.reservedAmountUsd ||
|
|
88
|
+
!Number.isFinite(error.feeAmountUsd) ||
|
|
89
|
+
error.feeAmountUsd < 0 ||
|
|
90
|
+
error.feeAmountUsd > pending.amountUsd ||
|
|
91
|
+
typeof error.actualFeeLamports !== "bigint" ||
|
|
92
|
+
error.actualFeeLamports < 0n)
|
|
93
|
+
throw new Error("Finalized failure does not match the pending payment");
|
|
94
|
+
const entries = state.spend.filter((entry) => entry.tx === recovery.txRef);
|
|
95
|
+
if (entries.length !== 1 || entries[0].failure || entries[0].usd !== pending.amountUsd)
|
|
96
|
+
throw new Error("Finalized failure does not match the original debit");
|
|
97
|
+
// Only the SDK's exact canonical proof and durable ledger reconciliation
|
|
98
|
+
// can produce this terminal outcome. Persist our fee debit and removal
|
|
99
|
+
// of the pending guard together; never retry the purchase in this call.
|
|
100
|
+
entries[0].usd = error.feeAmountUsd;
|
|
101
|
+
entries[0].failure = true;
|
|
102
|
+
delete state.pending;
|
|
103
|
+
store.save(state);
|
|
104
|
+
return true;
|
|
105
|
+
};
|
|
106
|
+
const finalizedFailureResult = () => errorResult("The original Solana transaction finalized with failure. Its verified network fee remains in the spending ledger. No receipt was issued and no replacement payment was submitted.");
|
|
78
107
|
if (state.pending) {
|
|
79
108
|
const pending = state.pending;
|
|
80
109
|
// Always recover the existing operation first, even if the caller asks
|
|
81
110
|
// for another resource. This method cannot broadcast a transaction.
|
|
82
111
|
const configuredApi = (ctx.config.baseUrl ?? "https://api.aifinpay.io").replace(/\/+$/, "");
|
|
83
112
|
const call = pending.recovery.quote.settlement_call;
|
|
113
|
+
const solana = pay.name === "solana";
|
|
114
|
+
const payerMatches = solana
|
|
115
|
+
? pending.recovery.quote.payer === ctx.agent.solanaAddress
|
|
116
|
+
: pending.recovery.quote.payer?.toLowerCase() === ctx.agent.evmAddress.toLowerCase();
|
|
84
117
|
if (pending.recovery.apiBaseUrl.replace(/\/+$/, "") !== configuredApi ||
|
|
85
118
|
(pending.recovery.paymentIssuer ?? configuredApi).replace(/\/+$/, "") !== configuredApi ||
|
|
86
|
-
|
|
119
|
+
!payerMatches ||
|
|
120
|
+
(pending.recovery.chain !== undefined && pending.recovery.chain !== pay.name) ||
|
|
87
121
|
call?.chain !== pay.name ||
|
|
88
122
|
call.splitter_version !== "1.4" ||
|
|
89
|
-
// Native calls from earlier versions may lack the field; they were POL.
|
|
90
123
|
(call.asset ?? "POL") !== pending.recovery.asset ||
|
|
91
|
-
![pay.native, payAsset].includes(pending.recovery.asset)
|
|
92
|
-
|
|
93
|
-
// to that chain; it is never re-sent here and never forgotten.
|
|
124
|
+
![pay.native, payAsset].includes(pending.recovery.asset) ||
|
|
125
|
+
(solana ? pending.recovery.family !== "solana" : pending.recovery.family === "solana"))
|
|
94
126
|
throw new Error(`Pending payment does not match the configured wallet/API/${pay.name} v1.4 route and asset`);
|
|
127
|
+
let recovery;
|
|
128
|
+
if (pending.recovery.family === "solana") {
|
|
129
|
+
if (pending.recovery.solana.network !== ctx.config.solanaNetwork ||
|
|
130
|
+
call.network !== ctx.config.solanaNetwork)
|
|
131
|
+
throw new Error("Pending Solana payment does not match the owner-selected network");
|
|
132
|
+
// The SDK verifies exact signed bytes, local Ed25519 identity and
|
|
133
|
+
// the original bound reservation. Recovery cannot submit a new tx.
|
|
134
|
+
recovery = pending.recovery;
|
|
95
135
|
}
|
|
96
|
-
|
|
136
|
+
else {
|
|
137
|
+
if (pay.name === "solana")
|
|
138
|
+
throw new Error("EVM payment cannot recover on Solana");
|
|
139
|
+
if (pending.recovery.chain === undefined) {
|
|
140
|
+
await assertPreparedV14Recovery(call, { hash: pending.recovery.txRef, serializedTransaction: pending.serializedTransaction }, pay.name, ctx.agent.evmAddress, pending.recovery.quote.quote_id);
|
|
141
|
+
}
|
|
142
|
+
recovery = { ...pending.recovery, chain: pending.recovery.chain ?? pay.name };
|
|
143
|
+
}
|
|
144
|
+
const paid = await ctx.agent
|
|
145
|
+
.recoverPaidPayment(recovery, {
|
|
146
|
+
paymentIssuer: configuredApi,
|
|
147
|
+
...(pay.name === "solana"
|
|
148
|
+
? {
|
|
149
|
+
solanaNetwork: ctx.config.solanaNetwork,
|
|
150
|
+
solanaEnvironment: ctx.config.devMode ? "dev" : "prod",
|
|
151
|
+
}
|
|
152
|
+
: {}),
|
|
153
|
+
})
|
|
154
|
+
.catch((error) => {
|
|
155
|
+
if (reconcileFinalizedFailure(error))
|
|
156
|
+
return null;
|
|
157
|
+
throw error;
|
|
158
|
+
});
|
|
159
|
+
if (!paid)
|
|
160
|
+
return finalizedFailureResult();
|
|
97
161
|
const receipt = {
|
|
98
162
|
site: pending.site,
|
|
99
163
|
merchantId: paid.merchant_id,
|
|
@@ -116,6 +180,27 @@ export async function runPayableFetch(ctx, args) {
|
|
|
116
180
|
const spent = store.spent24h(state);
|
|
117
181
|
const priorReceiptIds = new Set(state.receipts.map((entry) => entry.receiptId));
|
|
118
182
|
let price;
|
|
183
|
+
const persistPrepared = (prepared, usd, serializedTransaction) => {
|
|
184
|
+
if (state.pending)
|
|
185
|
+
throw new Error("A payment is already pending");
|
|
186
|
+
if (!Number.isFinite(usd) ||
|
|
187
|
+
usd <= 0 ||
|
|
188
|
+
usd > maximum ||
|
|
189
|
+
store.spent24h(state) + usd > ctx.config.dailyAmountUsd)
|
|
190
|
+
throw new Error("Owner spending limit reached");
|
|
191
|
+
const site = ctx.config.gatewayPathMode === "direct"
|
|
192
|
+
? `direct:${JSON.stringify([url.origin, prepared.quote.merchant_id])}`
|
|
193
|
+
: parsed.site;
|
|
194
|
+
state.pending = {
|
|
195
|
+
recovery: prepared,
|
|
196
|
+
...(serializedTransaction ? { serializedTransaction } : {}),
|
|
197
|
+
site,
|
|
198
|
+
amountUsd: usd,
|
|
199
|
+
};
|
|
200
|
+
state.spend.push({ at: Date.now(), usd, tx: prepared.txRef });
|
|
201
|
+
// Durable atomic fsync completes before the SDK may broadcast.
|
|
202
|
+
store.save(state);
|
|
203
|
+
};
|
|
119
204
|
try {
|
|
120
205
|
const response = await ctx.agent.fetchPaid(url.href, { method: "GET", redirect: "manual" }, {
|
|
121
206
|
apiBaseUrl: ctx.config.baseUrl,
|
|
@@ -123,7 +208,10 @@ export async function runPayableFetch(ctx, args) {
|
|
|
123
208
|
gatewayOrigins: ctx.config.gatewayOrigins,
|
|
124
209
|
resourcePathMode: ctx.config.gatewayPathMode,
|
|
125
210
|
scope,
|
|
126
|
-
|
|
211
|
+
// A pending Solana quote pins this owner limit across restarts and
|
|
212
|
+
// rollover. Fresh daily allowance is checked again in persistPrepared
|
|
213
|
+
// under the operation lock, before any broadcast.
|
|
214
|
+
maxAmountUsd: pay.name === "solana" ? maximum : Math.min(maximum, Math.max(0, ctx.config.dailyAmountUsd - spent)),
|
|
127
215
|
nativeUsdPrice: async () => {
|
|
128
216
|
// Independent public price, never the quote API's rate: Chainlink
|
|
129
217
|
// over the agent's own RPC for the pay chain first (no extra
|
|
@@ -136,53 +224,59 @@ export async function runPayableFetch(ctx, args) {
|
|
|
136
224
|
price = { usd: found.usd, observedAtMs: found.observedAtMs };
|
|
137
225
|
return price;
|
|
138
226
|
},
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
throw new Error("Owner spending limit reached");
|
|
166
|
-
const site = ctx.config.gatewayPathMode === "direct"
|
|
167
|
-
? `direct:${JSON.stringify([url.origin, prepared.quote.merchant_id])}`
|
|
168
|
-
: parsed.site;
|
|
169
|
-
state.pending = {
|
|
170
|
-
recovery: {
|
|
171
|
-
apiBaseUrl: prepared.apiBaseUrl,
|
|
172
|
-
quote: prepared.quote,
|
|
173
|
-
txRef: prepared.txRef,
|
|
174
|
-
asset: prepared.asset,
|
|
175
|
-
paymentIssuer: prepared.paymentIssuer,
|
|
227
|
+
...(pay.name === "solana"
|
|
228
|
+
? {
|
|
229
|
+
solanaV14: {
|
|
230
|
+
environment: ctx.config.devMode ? "dev" : "prod",
|
|
231
|
+
network: ctx.config.solanaNetwork,
|
|
232
|
+
asset: payAsset,
|
|
233
|
+
maxFeeLamports: feeCap,
|
|
234
|
+
onPrepared: async (prepared) => {
|
|
235
|
+
if (prepared.family !== "solana" ||
|
|
236
|
+
prepared.chain !== "solana" ||
|
|
237
|
+
prepared.solana.network !== ctx.config.solanaNetwork ||
|
|
238
|
+
prepared.quote.payer !== ctx.agent.solanaAddress ||
|
|
239
|
+
prepared.asset !== payAsset)
|
|
240
|
+
throw new Error("Prepared Solana payment disagrees with the owner configuration");
|
|
241
|
+
if (!price || Date.now() - price.observedAtMs > 60_000 || price.observedAtMs > Date.now())
|
|
242
|
+
throw new Error("Independent SOL price expired before preparation");
|
|
243
|
+
const native = prepared.quote.native_settlement;
|
|
244
|
+
const floor = payAsset === "SOL"
|
|
245
|
+
? Math.max(Number(prepared.quote.amount), (Number(BigInt(native.total_lamports)) / 1e9) * price.usd)
|
|
246
|
+
: Number(prepared.quote.amount);
|
|
247
|
+
if (!Number.isFinite(floor) ||
|
|
248
|
+
floor <= 0 ||
|
|
249
|
+
!Number.isFinite(prepared.reservedAmountUsd) ||
|
|
250
|
+
prepared.reservedAmountUsd < floor)
|
|
251
|
+
throw new Error("Prepared Solana admission amount is inconsistent");
|
|
252
|
+
persistPrepared(prepared, prepared.reservedAmountUsd);
|
|
176
253
|
},
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
254
|
+
},
|
|
255
|
+
}
|
|
256
|
+
: {
|
|
257
|
+
v14: {
|
|
258
|
+
chain: pay.name,
|
|
259
|
+
...(payAsset !== pay.native ? { asset: payAsset } : {}),
|
|
260
|
+
maxGasWei: feeCap,
|
|
261
|
+
onPrepared: async (prepared) => {
|
|
262
|
+
let usd;
|
|
263
|
+
if (payAsset !== pay.native) {
|
|
264
|
+
if (prepared.asset !== payAsset)
|
|
265
|
+
throw new Error("Prepared payment is not in the configured asset");
|
|
266
|
+
usd = Number(prepared.quote.amount);
|
|
267
|
+
}
|
|
268
|
+
else {
|
|
269
|
+
if (!price || Date.now() - price.observedAtMs > 60_000)
|
|
270
|
+
throw new Error("Independent price expired before preparation");
|
|
271
|
+
const native = prepared.quote.native_settlement;
|
|
272
|
+
usd = Math.max(Number(prepared.quote.amount), (Number(BigInt(native.total_wei)) / 1e18) * price.usd);
|
|
273
|
+
}
|
|
274
|
+
// Copy the full recovery, including the new wallet binding
|
|
275
|
+
// marker and any signed approval; retain legacy byte field.
|
|
276
|
+
persistPrepared(prepared, usd, prepared.serializedTransaction);
|
|
277
|
+
},
|
|
278
|
+
},
|
|
279
|
+
}),
|
|
186
280
|
});
|
|
187
281
|
if (response === null)
|
|
188
282
|
return errorResult("Payment skipped: owner budget reached.");
|
|
@@ -218,6 +312,11 @@ export async function runPayableFetch(ctx, args) {
|
|
|
218
312
|
],
|
|
219
313
|
};
|
|
220
314
|
}
|
|
315
|
+
catch (error) {
|
|
316
|
+
if (reconcileFinalizedFailure(error))
|
|
317
|
+
return finalizedFailureResult();
|
|
318
|
+
throw error;
|
|
319
|
+
}
|
|
221
320
|
finally {
|
|
222
321
|
// Receipt caching precedes content retry in the SDK. Retain it even
|
|
223
322
|
// when that content request fails; never discard already bought access.
|