@aifinpay/agent 2.2.0 → 2.3.2
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/PAYMENT_RECEIPTS.md +46 -5
- package/README.md +38 -21
- package/dist/aifp1.d.ts +13 -7
- package/dist/aifp1.d.ts.map +1 -1
- package/dist/aifp1.js +78 -40
- package/dist/aifp1.js.map +1 -1
- package/dist/facilitators/index.d.ts +1 -1
- package/dist/facilitators/index.d.ts.map +1 -1
- package/dist/index.d.ts +3 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -0
- package/dist/index.js.map +1 -1
- package/dist/settlementV14.d.ts +4 -2
- package/dist/settlementV14.d.ts.map +1 -1
- package/dist/settlementV14.js +62 -13
- package/dist/settlementV14.js.map +1 -1
- package/dist/unifiedAgent.d.ts.map +1 -1
- package/dist/unifiedAgent.js +9 -7
- package/dist/unifiedAgent.js.map +1 -1
- package/package.json +1 -1
package/PAYMENT_RECEIPTS.md
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
#
|
|
1
|
+
# v1.4 payment authorization (2.3.0)
|
|
2
2
|
|
|
3
3
|
The existing v1.3 documentation below remains applicable to explicitly pinned
|
|
4
|
-
v1.3 callers. The
|
|
4
|
+
v1.3 callers. The v1.4 path uses the original backend-signed quote:
|
|
5
5
|
|
|
6
6
|
```ts
|
|
7
7
|
const response = await agent.fetchPaid(
|
|
@@ -23,7 +23,7 @@ const response = await agent.fetchPaid(
|
|
|
23
23
|
|
|
24
24
|
`nativeUsdPrice` may return `{ usd, observedAtMs }` asynchronously and is called
|
|
25
25
|
only when buying, not when reusing a receipt. `onPrepared` receives the original
|
|
26
|
-
quote, API/issuer, transaction hash and serialized signed transaction. It MUST
|
|
26
|
+
quote, selected chain, API/issuer, transaction hash and serialized signed transaction. It MUST
|
|
27
27
|
atomically persist and fsync private state before resolving. A failed callback
|
|
28
28
|
prevents broadcast. Keep a per-wallet operation lock and refuse another purchase
|
|
29
29
|
while a prepared payment is unresolved. Never log the raw transaction or receipt.
|
|
@@ -37,14 +37,55 @@ owner reconciliation; receipt recovery is not a replacement broadcast.
|
|
|
37
37
|
|
|
38
38
|
The public MCP candidate implements this private journal/cache/lock integration.
|
|
39
39
|
Applications using the SDK directly own persistence and process coordination.
|
|
40
|
-
|
|
41
|
-
|
|
40
|
+
`fetchPaid` supports Polygon and explicitly selected Base in live mode, with
|
|
41
|
+
native POL/ETH or a stablecoin pinned for that chain. It never falls back to a
|
|
42
|
+
legacy route or changes assets after a refusal. The low-level executor retains
|
|
43
|
+
Polygon/Amoy support and requires `expectedChain: "base"` to execute on Base.
|
|
42
44
|
|
|
43
45
|
A successful executor result requires a mined success receipt with the matching
|
|
44
46
|
Payment event. Unknown broadcast/confirmation yields the original hash. Profile
|
|
45
47
|
fees/treasury remain administratively mutable under the existing v1.4 contract
|
|
46
48
|
model. Separate funded acceptance and release approval are still required.
|
|
47
49
|
|
|
50
|
+
## Base ETH or USDC
|
|
51
|
+
|
|
52
|
+
Configure `evmRpcUrls.base` on `AiFinPayAgent` for an independent Base RPC. The
|
|
53
|
+
SDK verifies its `eth_chainId` is 8453 before signing. `polygonRpc` remains the
|
|
54
|
+
Polygon transport and is never reused for Base.
|
|
55
|
+
|
|
56
|
+
```ts
|
|
57
|
+
await agent.fetchPaid(url, {}, {
|
|
58
|
+
gatewayOrigins: ["https://merchant.example"],
|
|
59
|
+
resourcePathMode: "direct",
|
|
60
|
+
maxAmountUsd: ownerLimitUsd,
|
|
61
|
+
nativeUsdPrice: readFreshIndependentEthPrice,
|
|
62
|
+
v14: {
|
|
63
|
+
chain: "base", // trusted caller choice, never copied from a quote
|
|
64
|
+
asset: "ETH", // or "USDC"; omit nativeUsdPrice for a USDC purchase
|
|
65
|
+
maxGasWei: ownerFeeBudgetWei,
|
|
66
|
+
onPrepared: persistPrivatePaymentJournal,
|
|
67
|
+
},
|
|
68
|
+
});
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
`asset` defaults to ETH on Base and POL on Polygon. `nativeUsdPrice` must report
|
|
72
|
+
the chosen native asset's USD price and observation time; there is no automatic
|
|
73
|
+
POL fallback on Base. A valid merchant receipt already cached for the same
|
|
74
|
+
resource can still be reused after changing this payment preference.
|
|
75
|
+
|
|
76
|
+
For Base, fee preflight counts L2 `gas × maxFeePerGas`, plus a conservative
|
|
77
|
+
`GasPriceOracle.getL1FeeUpperBound` and `getOperatorFee` estimate with 20% headroom.
|
|
78
|
+
Both approval and settlement are included before approval signing. The estimates
|
|
79
|
+
are rechecked before settlement. Missing or malformed oracle answers block payment.
|
|
80
|
+
The gas cap and balance checks use this total, but L1/operator fees may change
|
|
81
|
+
before inclusion and are not capped by the signed EIP-1559 transaction.
|
|
82
|
+
|
|
83
|
+
New recovery records include `chain`. A record without this field is treated as
|
|
84
|
+
Polygon for compatibility and cannot recover a Base payment. The stored chain,
|
|
85
|
+
asset and original quote must agree before the SDK signs a receipt claim.
|
|
86
|
+
Recovery works after quote expiry when the existing transaction already settled;
|
|
87
|
+
it never signs a replacement payment. Receipt JWT format is unchanged.
|
|
88
|
+
|
|
48
89
|
---
|
|
49
90
|
|
|
50
91
|
# Authorize and recover payment receipts
|
package/README.md
CHANGED
|
@@ -1,15 +1,21 @@
|
|
|
1
1
|
# @aifinpay/agent (Node / TypeScript)
|
|
2
2
|
|
|
3
|
-
Version **2.
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
3
|
+
Version **2.3.0** adds Base ETH and USDC to the AIFP-1 `fetchPaid` flow.
|
|
4
|
+
Polygon remains the default. Select Base independently with `v14.chain: "base"`;
|
|
5
|
+
a merchant quote cannot change that choice. Runtime, signer, token, RPC chain and
|
|
6
|
+
current profile checks remain mandatory, along with a durable pre-broadcast
|
|
7
|
+
journal and an explicit gas budget. Legacy `call()` remains unavailable.
|
|
8
|
+
|
|
9
|
+
Native payments require a fresh independent `nativeUsdPrice`: POL/USD on Polygon,
|
|
10
|
+
ETH/USD on Base. The SDK does not infer or fetch this price from a payment quote.
|
|
11
|
+
Stablecoin payments use the selected deployment's pinned token and exact USD minor
|
|
12
|
+
units. No native price is needed for stablecoin settlement.
|
|
13
|
+
|
|
14
|
+
On Base, `maxGasWei` covers the estimated L2 execution fee plus buffered L1 data and
|
|
15
|
+
operator fees, including approval when needed. This preflight estimate can change
|
|
16
|
+
before inclusion; the transaction cannot enforce a hard cap on L1/operator fees.
|
|
17
|
+
An unavailable fee oracle blocks signing. See the [Base configuration and recovery
|
|
18
|
+
guide](./PAYMENT_RECEIPTS.md).
|
|
13
19
|
|
|
14
20
|
### Link the agent to its owner's dashboard
|
|
15
21
|
|
|
@@ -63,7 +69,7 @@ npm pack
|
|
|
63
69
|
Install the resulting tarball in your application:
|
|
64
70
|
|
|
65
71
|
```bash
|
|
66
|
-
npm install /absolute/path/to/sdk/node/aifinpay-agent
|
|
72
|
+
npm install /absolute/path/to/sdk/node/aifinpay-agent-<version>.tgz
|
|
67
73
|
```
|
|
68
74
|
|
|
69
75
|
## Quick start
|
|
@@ -98,7 +104,8 @@ inputs and wallet files out of chats, logs and version control.
|
|
|
98
104
|
The same configured inputs restore the same addresses after a process restart.
|
|
99
105
|
An explicit `evmPrivateKey` option overrides the derived EVM identity; retain
|
|
100
106
|
that separate key as well to recover the imported wallet. Loading a wallet
|
|
101
|
-
does not
|
|
107
|
+
does not by itself pay for anything; paid execution goes through `fetchPaid`
|
|
108
|
+
and its checks.
|
|
102
109
|
|
|
103
110
|
`AiFinPayAgent.new()` and `Agent.new()` intentionally create a fresh ephemeral
|
|
104
111
|
wallet each time. They do not load existing environment variables or keystores
|
|
@@ -120,24 +127,34 @@ const agent2 = Agent.fromSecretB58("3RvZm7Gw...");
|
|
|
120
127
|
|
|
121
128
|
## How x402 auth works under the hood
|
|
122
129
|
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
130
|
+
`Agent.pay(url)` (and `get`/`post`/`request`) handles an AiFinPay-native 402
|
|
131
|
+
with request-bound auth (version 2). For a gated request the SDK:
|
|
132
|
+
|
|
133
|
+
1. sends the request without credentials; the 402 body carries a one-time
|
|
134
|
+
`x-nonce`, its expiry `x-nonce-expires-at`, `x-aifinpay-auth-version: 2`
|
|
135
|
+
and the SHA-256 of the request body it was issued for;
|
|
136
|
+
2. refuses to sign unless the request went to the origin configured as the
|
|
137
|
+
agent's `baseUrl`, the response came from that origin, and the body digest
|
|
138
|
+
matches the body it sent;
|
|
139
|
+
3. signs, with Ed25519, the SHA-256 of
|
|
140
|
+
`JSON.stringify(["AiFinPay-x402", "v2", nonce, pubkey, origin, METHOD, path+query, bodySha256, expiresAt])`
|
|
141
|
+
and base58-encodes the signature;
|
|
128
142
|
4. retries the original request with headers:
|
|
129
143
|
- `x-agent-pubkey: <base58 pubkey>`
|
|
130
|
-
- `x-nonce: <
|
|
144
|
+
- `x-nonce: <nonce>`
|
|
131
145
|
- `x-signature: <base58 sig>`
|
|
146
|
+
- `x-aifinpay-auth-version: 2`
|
|
132
147
|
|
|
133
|
-
The server verifies the signature, checks the agent has a
|
|
134
|
-
on-chain, and serves the resource.
|
|
148
|
+
The server verifies the signature, consumes the nonce, checks the agent has a
|
|
149
|
+
live Seat PDA on-chain, and serves the resource. The retired v1 format
|
|
150
|
+
(`GET /nonce`, `SHA-256("AiFinPay-x402:{nonce}:{pubkey}")`) is refused, and
|
|
151
|
+
`Agent.authHeaders()` throws.
|
|
135
152
|
|
|
136
153
|
## Privacy
|
|
137
154
|
|
|
138
155
|
- **The server never sees your private key.** Period.
|
|
139
156
|
- Nonces are consumed on use; replay-resistant.
|
|
140
|
-
-
|
|
157
|
+
- Payments settle on public chains — Polygon, or Base when selected — and are visible on-chain.
|
|
141
158
|
|
|
142
159
|
## License
|
|
143
160
|
|
package/dist/aifp1.d.ts
CHANGED
|
@@ -29,6 +29,8 @@ export interface Aifp1PaymentRecovery {
|
|
|
29
29
|
quote: Aifp1Quote;
|
|
30
30
|
txRef: `0x${string}`;
|
|
31
31
|
asset: string;
|
|
32
|
+
/** Trusted payment chain saved before broadcast; omitted legacy journals mean Polygon. */
|
|
33
|
+
chain?: Aifp1V14Chain;
|
|
32
34
|
paymentIssuer?: string;
|
|
33
35
|
}
|
|
34
36
|
export interface Aifp1PaymentSigner {
|
|
@@ -36,7 +38,7 @@ export interface Aifp1PaymentSigner {
|
|
|
36
38
|
signPaymentAuthorization(message: string): Promise<string>;
|
|
37
39
|
fetchImpl?: typeof fetch;
|
|
38
40
|
}
|
|
39
|
-
/** Recover an already settled
|
|
41
|
+
/** Recover an already settled payment. Never sends an on-chain transaction. */
|
|
40
42
|
export declare function recoverAifp1Payment(recovery: Aifp1PaymentRecovery, signer: Aifp1PaymentSigner, options?: Pick<Aifp1FetchOptions, "settlementConfirmMs" | "apiTimeoutMs" | "paymentIssuer">): Promise<Aifp1PayResult>;
|
|
41
43
|
/** The gateway rejected a receipt we believed covered the request. */
|
|
42
44
|
export declare class Aifp1ReceiptRejectedError extends Aifp1Error {
|
|
@@ -98,7 +100,7 @@ export interface Aifp1Quote {
|
|
|
98
100
|
order_id: string;
|
|
99
101
|
};
|
|
100
102
|
};
|
|
101
|
-
/** Native
|
|
103
|
+
/** Native gross settlement in the selected chain currency, when backend readiness permits. */
|
|
102
104
|
native_settlement?: {
|
|
103
105
|
asset: string;
|
|
104
106
|
decimals: number;
|
|
@@ -338,16 +340,19 @@ export declare class Aifp1ReceiptCache {
|
|
|
338
340
|
summary(): Omit<Aifp1CachedReceipt, "jwt">[];
|
|
339
341
|
get size(): number;
|
|
340
342
|
}
|
|
343
|
+
export type Aifp1V14Chain = "polygon" | "base";
|
|
341
344
|
export interface Aifp1FetchOptions {
|
|
342
345
|
/** Explicit native v1.4 authorization. Deployment/signer pins come from the
|
|
343
346
|
* SDK registry, never from a merchant response. The journal must be durable
|
|
344
347
|
* before broadcast; a prepared transaction must be recovered, never replaced. */
|
|
345
348
|
v14?: {
|
|
349
|
+
/** Independently selected payment chain. Defaults to Polygon; never inferred from a quote. */
|
|
350
|
+
chain?: Aifp1V14Chain;
|
|
346
351
|
/**
|
|
347
|
-
*
|
|
348
|
-
*
|
|
349
|
-
*
|
|
350
|
-
*
|
|
352
|
+
* Native currency (POL/ETH), or a stablecoin pinned for the selected chain.
|
|
353
|
+
* Defaults to native. Stablecoins need no nativeUsdPrice; maxGasWei covers
|
|
354
|
+
* approval and settlement. On Base it includes buffered L1/operator fee
|
|
355
|
+
* estimates, which can change before inclusion and are not a consensus cap.
|
|
351
356
|
*/
|
|
352
357
|
asset?: string;
|
|
353
358
|
maxGasWei: bigint;
|
|
@@ -359,7 +364,8 @@ export interface Aifp1FetchOptions {
|
|
|
359
364
|
* The receipt flow currently verifies Polygon native settlements only. */
|
|
360
365
|
settlementPin?: import("./settlement.js").TrustedSettlementRoutePin;
|
|
361
366
|
/** Independent native/USD observation from the caller's trusted price source,
|
|
362
|
-
* never copied from the payment quote.
|
|
367
|
+
* never copied from the payment quote. POL/USD on Polygon, ETH/USD on Base.
|
|
368
|
+
* Required for native v1.3/v1.4 payments; <=60s old. */
|
|
363
369
|
nativeUsdPrice?: {
|
|
364
370
|
usd: number;
|
|
365
371
|
observedAtMs: number;
|
package/dist/aifp1.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"aifp1.d.ts","sourceRoot":"","sources":["../src/aifp1.ts"],"names":[],"mappings":"AAsCA,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAC5C,OAAO,EAA6B,KAAK,iBAAiB,EAAE,MAAM,oBAAoB,CAAC;AAWvF,qBAAa,UAAW,SAAQ,aAAa;CAAG;AAEhD,oEAAoE;AACpE,qBAAa,eAAgB,SAAQ,UAAU;CAAG;AAElD,sEAAsE;AACtE,qBAAa,+BAAgC,SAAQ,UAAU;CAAG;AAElE;;;;;;;;GAQG;AACH,qBAAa,aAAc,SAAQ,UAAU;aAGzB,KAAK,CAAC,EAAE,MAAM;aACd,OAAO,CAAC,EAAE,MAAM;aAChB,QAAQ,CAAC,EAAE,oBAAoB;gBAH/C,GAAG,EAAE,MAAM,EACK,KAAK,CAAC,EAAE,MAAM,YAAA,EACd,OAAO,CAAC,EAAE,MAAM,YAAA,EAChB,QAAQ,CAAC,EAAE,oBAAoB,YAAA;CAIlD;AAED,qFAAqF;AACrF,MAAM,WAAW,oBAAoB;IACnC,UAAU,EAAE,MAAM,CAAC;IACnB,KAAK,EAAE,UAAU,CAAC;IAClB,KAAK,EAAE,KAAK,MAAM,EAAE,CAAC;IACrB,KAAK,EAAE,MAAM,CAAC;IACd,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAED,MAAM,WAAW,kBAAkB;IACjC,YAAY,EAAE,MAAM,CAAC;IACrB,wBAAwB,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IAC3D,SAAS,CAAC,EAAE,OAAO,KAAK,CAAC;CAC1B;AAED
|
|
1
|
+
{"version":3,"file":"aifp1.d.ts","sourceRoot":"","sources":["../src/aifp1.ts"],"names":[],"mappings":"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"}
|
package/dist/aifp1.js
CHANGED
|
@@ -74,9 +74,22 @@ export class Aifp1PayError extends Aifp1Error {
|
|
|
74
74
|
this.recovery = recovery;
|
|
75
75
|
}
|
|
76
76
|
}
|
|
77
|
-
/** Recover an already settled
|
|
77
|
+
/** Recover an already settled payment. Never sends an on-chain transaction. */
|
|
78
78
|
export function recoverAifp1Payment(recovery, signer, options = {}) {
|
|
79
|
-
|
|
79
|
+
const chain = authorizedV14Chain(recovery.chain);
|
|
80
|
+
const call = recovery.quote.settlement_call;
|
|
81
|
+
if (chain !== "polygon" && call?.splitter_version !== "1.4") {
|
|
82
|
+
throw new Aifp1QuoteError("Base recovery requires the original v1.4 settlement call");
|
|
83
|
+
}
|
|
84
|
+
if (call?.splitter_version === "1.4" &&
|
|
85
|
+
(call.chain !== chain ||
|
|
86
|
+
call.asset !== recovery.asset ||
|
|
87
|
+
recovery.quote.accepted_chains.length !== 1 ||
|
|
88
|
+
recovery.quote.accepted_chains[0] !== chain ||
|
|
89
|
+
!recovery.quote.accepted_assets.includes(recovery.asset))) {
|
|
90
|
+
throw new Aifp1QuoteError("recovery chain or asset disagrees with the saved purchase");
|
|
91
|
+
}
|
|
92
|
+
return submitPayment({ ...signer, agentId: signer.payerAddress, fetchImpl: signer.fetchImpl ?? fetch }, recovery.apiBaseUrl, recovery.quote, recovery.txRef, recovery.asset, chain, { paymentIssuer: recovery.paymentIssuer, ...options });
|
|
80
93
|
}
|
|
81
94
|
/** The gateway rejected a receipt we believed covered the request. */
|
|
82
95
|
export class Aifp1ReceiptRejectedError extends Aifp1Error {
|
|
@@ -497,6 +510,8 @@ export async function aifp1Fetch(deps, url, init = {}, opts = {}) {
|
|
|
497
510
|
if (opts.maxAmountUsd !== undefined && (!Number.isFinite(opts.maxAmountUsd) || opts.maxAmountUsd < 0)) {
|
|
498
511
|
throw new Aifp1QuoteError("maxAmountUsd must be nonnegative and finite");
|
|
499
512
|
}
|
|
513
|
+
const chain = authorizedV14Chain(opts.v14?.chain);
|
|
514
|
+
const nativeAsset = chain === "base" ? "ETH" : "POL";
|
|
500
515
|
const direct = opts.resourcePathMode === "direct";
|
|
501
516
|
const parsed = parseGatewayUrl(url, opts.gatewayOrigins ?? DEFAULT_GATEWAY_ORIGINS, opts.resourcePathMode);
|
|
502
517
|
let site = parsed.site;
|
|
@@ -630,7 +645,7 @@ export async function aifp1Fetch(deps, url, init = {}, opts = {}) {
|
|
|
630
645
|
throw new Aifp1QuoteError("a settlement wallet signer is required to receive a payment receipt");
|
|
631
646
|
}
|
|
632
647
|
// 2. Quote.
|
|
633
|
-
const stableAsset = pinnedStableAsset(opts.v14?.asset);
|
|
648
|
+
const stableAsset = pinnedStableAsset(opts.v14?.asset, chain);
|
|
634
649
|
const apiBase = (opts.apiBaseUrl ?? DEFAULT_API_BASE).replace(/\/$/, "");
|
|
635
650
|
const { scope, resource } = resolveScope(opts, challenge);
|
|
636
651
|
const quote = await requestQuote(deps, apiBase, {
|
|
@@ -640,7 +655,7 @@ export async function aifp1Fetch(deps, url, init = {}, opts = {}) {
|
|
|
640
655
|
scope,
|
|
641
656
|
units: opts.units ?? defaultUnitsFor(challenge),
|
|
642
657
|
agent_id: deps.agentId,
|
|
643
|
-
...(
|
|
658
|
+
...(opts.v14 ? { asset: stableAsset ?? nativeAsset } : {}),
|
|
644
659
|
}, opts.apiTimeoutMs);
|
|
645
660
|
if (quote.payer && quote.payer.toLowerCase() !== deps.payerAddress.toLowerCase()) {
|
|
646
661
|
throw new Aifp1QuoteError("quote names a different paying wallet");
|
|
@@ -649,6 +664,17 @@ export async function aifp1Fetch(deps, url, init = {}, opts = {}) {
|
|
|
649
664
|
throw new Aifp1QuoteError("unsupported receipt authorization scheme");
|
|
650
665
|
}
|
|
651
666
|
trustedPaymentIssuer(quote, opts.paymentIssuer);
|
|
667
|
+
const merchantWallet = quote.pay_to[chain] ?? quote.pay_to.evm;
|
|
668
|
+
if (opts.v14 &&
|
|
669
|
+
(quote.accepted_chains.length !== 1 ||
|
|
670
|
+
quote.accepted_chains[0] !== chain ||
|
|
671
|
+
quote.settlement_call?.chain !== chain ||
|
|
672
|
+
!merchantWallet ||
|
|
673
|
+
(quote.pay_to[chain] !== undefined &&
|
|
674
|
+
quote.pay_to.evm !== undefined &&
|
|
675
|
+
quote.pay_to[chain].toLowerCase() !== quote.pay_to.evm.toLowerCase()))) {
|
|
676
|
+
throw new Aifp1QuoteError("quote does not match the independently selected payment chain");
|
|
677
|
+
}
|
|
652
678
|
// The merchant we are about to pay must be the merchant that refused us.
|
|
653
679
|
// Cheap, and the failure it catches is the one worth catching.
|
|
654
680
|
if (quote.merchant_id !== challenge.merchant_id) {
|
|
@@ -675,7 +701,7 @@ export async function aifp1Fetch(deps, url, init = {}, opts = {}) {
|
|
|
675
701
|
throw new Aifp1QuoteError(`quote ${quote.quote_id} has an unusable amount "${quote.amount}"`);
|
|
676
702
|
}
|
|
677
703
|
if (stableAsset) {
|
|
678
|
-
validateStableV14Quote(quote, stableAsset, deps.payerAddress, quoteExpiryMs);
|
|
704
|
+
validateStableV14Quote(quote, stableAsset, deps.payerAddress, quoteExpiryMs, chain);
|
|
679
705
|
}
|
|
680
706
|
else if (opts.settlementPin || opts.v14) {
|
|
681
707
|
const price = typeof opts.nativeUsdPrice === "function" ? await opts.nativeUsdPrice() : opts.nativeUsdPrice;
|
|
@@ -703,8 +729,12 @@ export async function aifp1Fetch(deps, url, init = {}, opts = {}) {
|
|
|
703
729
|
amountUsd = Math.max(amountUsd, debitUsd);
|
|
704
730
|
const call = quote.settlement_call;
|
|
705
731
|
const args = call?.args;
|
|
706
|
-
if (native.asset !==
|
|
707
|
-
|
|
732
|
+
if (native.asset !== nativeAsset ||
|
|
733
|
+
native.decimals !== 18 ||
|
|
734
|
+
(opts.v14
|
|
735
|
+
? quote.accepted_assets.length !== 1 || quote.accepted_assets[0] !== nativeAsset
|
|
736
|
+
: !quote.accepted_assets.includes(nativeAsset))) {
|
|
737
|
+
throw new Aifp1QuoteError(`native receipt settlement requires accepted ${nativeAsset} with 18 decimals`);
|
|
708
738
|
}
|
|
709
739
|
if (opts.v14) {
|
|
710
740
|
if (!call || call.splitter_version !== "1.4") {
|
|
@@ -712,10 +742,10 @@ export async function aifp1Fetch(deps, url, init = {}, opts = {}) {
|
|
|
712
742
|
}
|
|
713
743
|
const signed = call;
|
|
714
744
|
validateV14SettlementCall(signed, { orderId: quote.quote_id, payer: deps.payerAddress });
|
|
715
|
-
if (signed.chain !==
|
|
716
|
-
signed.asset !==
|
|
745
|
+
if (signed.chain !== chain ||
|
|
746
|
+
signed.asset !== nativeAsset ||
|
|
717
747
|
signed.route !== "merchant-aifp1" ||
|
|
718
|
-
signed.args.quote.merchant.toLowerCase() !==
|
|
748
|
+
signed.args.quote.merchant.toLowerCase() !== merchantWallet?.toLowerCase() ||
|
|
719
749
|
signed.args.quote.grossAmount !== native.total_wei ||
|
|
720
750
|
BigInt(signed.args.quote.validUntil) !== BigInt(Math.floor(quoteExpiryMs / 1000))) {
|
|
721
751
|
throw new Aifp1QuoteError("signed v1.4 call disagrees with the requested merchant, debit or expiry");
|
|
@@ -758,7 +788,7 @@ export async function aifp1Fetch(deps, url, init = {}, opts = {}) {
|
|
|
758
788
|
const gross = BigInt(signed.grossAmount);
|
|
759
789
|
const treasury = gross / 100n;
|
|
760
790
|
settleParams = {
|
|
761
|
-
merchantWallet:
|
|
791
|
+
merchantWallet: merchantWallet,
|
|
762
792
|
grossWei: gross,
|
|
763
793
|
merchantWei: gross - treasury,
|
|
764
794
|
treasuryWei: treasury,
|
|
@@ -771,13 +801,11 @@ export async function aifp1Fetch(deps, url, init = {}, opts = {}) {
|
|
|
771
801
|
else {
|
|
772
802
|
const native = quote.native_settlement;
|
|
773
803
|
if (!native) {
|
|
774
|
-
throw new Aifp1SettlementUnsupportedError(`quote ${quote.quote_id} carries no native_settlement
|
|
775
|
-
`
|
|
776
|
-
`${quote.accepted_assets.join("/")}. Retry when a POL rate is available or settle the quote yourself.`);
|
|
804
|
+
throw new Aifp1SettlementUnsupportedError(`quote ${quote.quote_id} carries no native_settlement for ${nativeAsset}; ` +
|
|
805
|
+
`retry when a live ${nativeAsset} rate is available or request a pinned stablecoin explicitly.`);
|
|
777
806
|
}
|
|
778
|
-
if (!quote.accepted_chains.includes(
|
|
779
|
-
throw new Aifp1SettlementUnsupportedError(`quote ${quote.quote_id} does not accept
|
|
780
|
-
`AIFP-1 settlement verification is Polygon-only server-side (aifp/verify-settlement.js)`);
|
|
807
|
+
if (!quote.accepted_chains.includes(chain) || !merchantWallet) {
|
|
808
|
+
throw new Aifp1SettlementUnsupportedError(`quote ${quote.quote_id} does not accept ${chain}`);
|
|
781
809
|
}
|
|
782
810
|
// The caps above were checked in USD; the wallet is about to be debited in
|
|
783
811
|
// wei. Nothing tied the two together, so a quote could pass a $0.10 cap and
|
|
@@ -818,7 +846,7 @@ export async function aifp1Fetch(deps, url, init = {}, opts = {}) {
|
|
|
818
846
|
throw new Aifp1QuoteError(`quote ${quote.quote_id} valid_until does not match expires_at`);
|
|
819
847
|
}
|
|
820
848
|
settleParams = {
|
|
821
|
-
merchantWallet:
|
|
849
|
+
merchantWallet: merchantWallet,
|
|
822
850
|
grossWei: nativeGross,
|
|
823
851
|
merchantWei: nativeMerchant,
|
|
824
852
|
treasuryWei: nativeTreasury,
|
|
@@ -843,6 +871,7 @@ export async function aifp1Fetch(deps, url, init = {}, opts = {}) {
|
|
|
843
871
|
quote,
|
|
844
872
|
txRef: tx.hash,
|
|
845
873
|
asset: paidAsset,
|
|
874
|
+
chain,
|
|
846
875
|
paymentIssuer: opts.paymentIssuer,
|
|
847
876
|
serializedTransaction: tx.serializedTransaction,
|
|
848
877
|
});
|
|
@@ -861,6 +890,7 @@ export async function aifp1Fetch(deps, url, init = {}, opts = {}) {
|
|
|
861
890
|
quote,
|
|
862
891
|
txRef: error.txHash,
|
|
863
892
|
asset: paidAsset,
|
|
893
|
+
chain,
|
|
864
894
|
paymentIssuer: opts.paymentIssuer,
|
|
865
895
|
};
|
|
866
896
|
try {
|
|
@@ -888,10 +918,11 @@ export async function aifp1Fetch(deps, url, init = {}, opts = {}) {
|
|
|
888
918
|
quote,
|
|
889
919
|
txRef,
|
|
890
920
|
asset: paidAsset,
|
|
921
|
+
chain,
|
|
891
922
|
paymentIssuer: opts.paymentIssuer,
|
|
892
923
|
});
|
|
893
924
|
}
|
|
894
|
-
const paid = await submitPayment(deps, apiBase, quote, txRef, paidAsset, opts);
|
|
925
|
+
const paid = await submitPayment(deps, apiBase, quote, txRef, paidAsset, chain, opts);
|
|
895
926
|
// 6. Keep the batch. This is the whole point of the design: the next call
|
|
896
927
|
// this receipt covers costs a header, not a transaction.
|
|
897
928
|
const entry = {
|
|
@@ -934,6 +965,7 @@ export async function aifp1Fetch(deps, url, init = {}, opts = {}) {
|
|
|
934
965
|
quote,
|
|
935
966
|
txRef,
|
|
936
967
|
asset: paidAsset,
|
|
968
|
+
chain,
|
|
937
969
|
paymentIssuer: opts.paymentIssuer,
|
|
938
970
|
});
|
|
939
971
|
}
|
|
@@ -969,18 +1001,22 @@ export async function aifp1Fetch(deps, url, init = {}, opts = {}) {
|
|
|
969
1001
|
return buyBatch();
|
|
970
1002
|
}
|
|
971
1003
|
// ── /v1/quote ─────────────────────────────────────────────────────────────
|
|
972
|
-
/**
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
1004
|
+
/** Trusted input only: neither a quote nor a receipt may choose a payment chain. */
|
|
1005
|
+
function authorizedV14Chain(chain) {
|
|
1006
|
+
if (chain === undefined)
|
|
1007
|
+
return "polygon";
|
|
1008
|
+
if (chain !== "polygon" && chain !== "base")
|
|
1009
|
+
throw new Aifp1QuoteError(`unsupported v1.4 chain "${chain}"`);
|
|
1010
|
+
return chain;
|
|
1011
|
+
}
|
|
1012
|
+
/** Native by default; stablecoin symbols must be pinned for the selected chain. */
|
|
1013
|
+
function pinnedStableAsset(asset, chain) {
|
|
1014
|
+
if (asset === undefined || asset === (chain === "base" ? "ETH" : "POL"))
|
|
979
1015
|
return null;
|
|
980
|
-
const pinned = V14_DEPLOYMENTS
|
|
1016
|
+
const pinned = V14_DEPLOYMENTS[chain]?.splitter.assets.find((a) => a.symbol === asset);
|
|
981
1017
|
if (!pinned) {
|
|
982
|
-
throw new Aifp1QuoteError(`v14.asset "${asset}" is not a stablecoin pinned for
|
|
983
|
-
`(${(V14_DEPLOYMENTS
|
|
1018
|
+
throw new Aifp1QuoteError(`v14.asset "${asset}" is not a stablecoin pinned for ${chain} v1.4 ` +
|
|
1019
|
+
`(${(V14_DEPLOYMENTS[chain]?.splitter.assets ?? []).map((a) => a.symbol).join(", ") || "none"})`);
|
|
984
1020
|
}
|
|
985
1021
|
return pinned.symbol;
|
|
986
1022
|
}
|
|
@@ -991,14 +1027,15 @@ function pinnedStableAsset(asset) {
|
|
|
991
1027
|
* 6-decimal micro-dollars; the token is the pinned one; the approval is for
|
|
992
1028
|
* exactly that gross; and the quote accepts nothing else.
|
|
993
1029
|
*/
|
|
994
|
-
function validateStableV14Quote(quote, asset, payer, quoteExpiryMs) {
|
|
1030
|
+
function validateStableV14Quote(quote, asset, payer, quoteExpiryMs, chain) {
|
|
995
1031
|
const call = quote.settlement_call;
|
|
996
1032
|
if (!call || call.splitter_version !== "1.4") {
|
|
997
1033
|
throw new Aifp1SettlementUnsupportedError("a stablecoin purchase requires a signed v1.4 quote; no legacy fallback");
|
|
998
1034
|
}
|
|
999
1035
|
const signed = call;
|
|
1000
1036
|
validateV14SettlementCall(signed, { orderId: quote.quote_id, payer });
|
|
1001
|
-
const token = V14_DEPLOYMENTS
|
|
1037
|
+
const token = V14_DEPLOYMENTS[chain]?.splitter.assets.find((a) => a.symbol === asset)?.address;
|
|
1038
|
+
const merchant = quote.pay_to[chain] ?? quote.pay_to.evm;
|
|
1002
1039
|
const q = signed.args.quote;
|
|
1003
1040
|
let micro;
|
|
1004
1041
|
try {
|
|
@@ -1008,14 +1045,15 @@ function validateStableV14Quote(quote, asset, payer, quoteExpiryMs) {
|
|
|
1008
1045
|
throw new Aifp1QuoteError(`quote ${quote.quote_id} has an unusable amount "${quote.amount}"`);
|
|
1009
1046
|
}
|
|
1010
1047
|
const units = quote.settlement?.total_units;
|
|
1011
|
-
if (signed.chain !==
|
|
1048
|
+
if (signed.chain !== chain ||
|
|
1012
1049
|
signed.asset !== asset ||
|
|
1013
1050
|
signed.route !== "merchant-aifp1" ||
|
|
1014
1051
|
!token ||
|
|
1015
1052
|
q.token.toLowerCase() !== token.toLowerCase() ||
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1053
|
+
quote.accepted_chains.length !== 1 ||
|
|
1054
|
+
quote.accepted_chains[0] !== chain ||
|
|
1055
|
+
!merchant ||
|
|
1056
|
+
q.merchant.toLowerCase() !== merchant.toLowerCase() ||
|
|
1019
1057
|
quote.native_settlement !== undefined ||
|
|
1020
1058
|
quote.accepted_assets.length !== 1 ||
|
|
1021
1059
|
quote.accepted_assets[0] !== asset ||
|
|
@@ -1073,8 +1111,7 @@ async function requestQuote(deps, apiBase, body, timeoutMs) {
|
|
|
1073
1111
|
* crosses with the server's first success replays that success rather than
|
|
1074
1112
|
* minting a second receipt.
|
|
1075
1113
|
*/
|
|
1076
|
-
async function submitPayment(deps, apiBase, quote, txRef, asset, opts) {
|
|
1077
|
-
const chain = "polygon";
|
|
1114
|
+
async function submitPayment(deps, apiBase, quote, txRef, asset, chain, opts) {
|
|
1078
1115
|
const idempotencyKey = idempotencyKeyFor({ quoteId: quote.quote_id, chain, asset, txRef });
|
|
1079
1116
|
const deadline = Date.now() + (opts.settlementConfirmMs ?? DEFAULT_SETTLEMENT_CONFIRM_MS);
|
|
1080
1117
|
const recovery = {
|
|
@@ -1082,6 +1119,7 @@ async function submitPayment(deps, apiBase, quote, txRef, asset, opts) {
|
|
|
1082
1119
|
quote,
|
|
1083
1120
|
txRef,
|
|
1084
1121
|
asset,
|
|
1122
|
+
chain,
|
|
1085
1123
|
paymentIssuer: opts.paymentIssuer,
|
|
1086
1124
|
};
|
|
1087
1125
|
const failure = (message) => new Aifp1PayError(message, txRef, quote.quote_id, recovery);
|
|
@@ -1150,7 +1188,7 @@ async function submitPayment(deps, apiBase, quote, txRef, asset, opts) {
|
|
|
1150
1188
|
}
|
|
1151
1189
|
if (quote.settlement_call?.splitter_version === "1.4") {
|
|
1152
1190
|
try {
|
|
1153
|
-
await verifyPaidReceipt(paid, quote, asset, txRef, deps.payerAddress, opts.paymentIssuer ?? DEFAULT_API_BASE, deps.fetchImpl, opts.apiTimeoutMs);
|
|
1191
|
+
await verifyPaidReceipt(paid, quote, asset, chain, txRef, deps.payerAddress, opts.paymentIssuer ?? DEFAULT_API_BASE, deps.fetchImpl, opts.apiTimeoutMs);
|
|
1154
1192
|
}
|
|
1155
1193
|
catch {
|
|
1156
1194
|
throw failure("receipt signature or purchase binding could not be verified; recover the existing payment");
|
|
@@ -1169,7 +1207,7 @@ async function submitPayment(deps, apiBase, quote, txRef, asset, opts) {
|
|
|
1169
1207
|
}
|
|
1170
1208
|
/** Only the configured issuer supplies keys; JWT headers cannot select a URL.
|
|
1171
1209
|
* v1.4 native purchases accept Ed25519 receipts bound to the exact purchase. */
|
|
1172
|
-
async function verifyPaidReceipt(paid, quote, asset, txRef, payer, issuer, fetchImpl, timeoutMs) {
|
|
1210
|
+
async function verifyPaidReceipt(paid, quote, asset, chain, txRef, payer, issuer, fetchImpl, timeoutMs) {
|
|
1173
1211
|
const reject = () => {
|
|
1174
1212
|
throw new Aifp1Error("invalid payment receipt");
|
|
1175
1213
|
};
|
|
@@ -1210,7 +1248,7 @@ async function verifyPaidReceipt(paid, quote, asset, txRef, payer, issuer, fetch
|
|
|
1210
1248
|
claims.tx_ref !== txRef ||
|
|
1211
1249
|
claims.scope !== quote.scope ||
|
|
1212
1250
|
claims.resource !== quote.resource ||
|
|
1213
|
-
claims.chain !==
|
|
1251
|
+
claims.chain !== chain ||
|
|
1214
1252
|
claims.asset !== asset ||
|
|
1215
1253
|
claims.currency !== "USD" ||
|
|
1216
1254
|
(claims.network_mode ?? "live") !== "live" ||
|