@payai/x402-evm 2.4.1 → 2.4.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/dist/cjs/batch-settlement/client/file-storage.d.ts +47 -0
- package/dist/cjs/batch-settlement/client/file-storage.js +116 -0
- package/dist/cjs/batch-settlement/client/file-storage.js.map +1 -0
- package/dist/cjs/batch-settlement/client/index.d.ts +111 -0
- package/dist/cjs/batch-settlement/client/index.js +1565 -0
- package/dist/cjs/batch-settlement/client/index.js.map +1 -0
- package/dist/cjs/batch-settlement/facilitator/index.d.ts +71 -0
- package/dist/cjs/batch-settlement/facilitator/index.js +2032 -0
- package/dist/cjs/batch-settlement/facilitator/index.js.map +1 -0
- package/dist/cjs/batch-settlement/server/file-storage.d.ts +53 -0
- package/dist/cjs/batch-settlement/server/file-storage.js +181 -0
- package/dist/cjs/batch-settlement/server/file-storage.js.map +1 -0
- package/dist/cjs/batch-settlement/server/index.d.ts +491 -0
- package/dist/cjs/batch-settlement/server/index.js +1960 -0
- package/dist/cjs/batch-settlement/server/index.js.map +1 -0
- package/dist/cjs/batch-settlement/server/redis-storage.d.ts +87 -0
- package/dist/cjs/batch-settlement/server/redis-storage.js +181 -0
- package/dist/cjs/batch-settlement/server/redis-storage.js.map +1 -0
- package/dist/cjs/exact/client/index.d.ts +6 -4
- package/dist/cjs/exact/client/index.js +7 -5
- package/dist/cjs/exact/client/index.js.map +1 -1
- package/dist/cjs/exact/facilitator/index.d.ts +16 -9
- package/dist/cjs/exact/facilitator/index.js +35 -7
- package/dist/cjs/exact/facilitator/index.js.map +1 -1
- package/dist/cjs/exact/server/index.js +40 -1
- package/dist/cjs/exact/server/index.js.map +1 -1
- package/dist/cjs/exact/v1/client/index.d.ts +2 -1
- package/dist/cjs/exact/v1/client/index.js.map +1 -1
- package/dist/cjs/exact/v1/facilitator/index.d.ts +11 -5
- package/dist/cjs/exact/v1/facilitator/index.js +16 -2
- package/dist/cjs/exact/v1/facilitator/index.js.map +1 -1
- package/dist/cjs/index.d.ts +113 -7
- package/dist/cjs/index.js +1353 -5
- package/dist/cjs/index.js.map +1 -1
- package/dist/{esm/permit2-CyZxwngN.d.mts → cjs/permit2-DhJRUcgY.d.ts} +1 -13
- package/dist/cjs/rpc-DULZzRne.d.ts +13 -0
- package/dist/cjs/scheme-CvkPJXBD.d.ts +307 -0
- package/dist/{esm/scheme-DCR7hsa3.d.mts → cjs/scheme-DTQFE9xp.d.ts} +2 -2
- package/dist/{esm/signer-D912R4mq.d.mts → cjs/signer-tYS6Y46X.d.ts} +3 -0
- package/dist/cjs/storage-6W5MO46W.d.ts +50 -0
- package/dist/cjs/storage-Bl6aD0Xg.d.ts +81 -0
- package/dist/cjs/types-CF8P2-NM.d.ts +180 -0
- package/dist/cjs/upto/client/index.d.ts +5 -3
- package/dist/cjs/upto/client/index.js +7 -5
- package/dist/cjs/upto/client/index.js.map +1 -1
- package/dist/cjs/upto/facilitator/index.d.ts +2 -1
- package/dist/cjs/upto/facilitator/index.js +2 -1
- package/dist/cjs/upto/facilitator/index.js.map +1 -1
- package/dist/cjs/upto/server/index.js +40 -1
- package/dist/cjs/upto/server/index.js.map +1 -1
- package/dist/cjs/v1/index.d.ts +2 -1
- package/dist/cjs/v1/index.js.map +1 -1
- package/dist/esm/batch-settlement/client/file-storage.d.mts +47 -0
- package/dist/esm/batch-settlement/client/file-storage.mjs +63 -0
- package/dist/esm/batch-settlement/client/file-storage.mjs.map +1 -0
- package/dist/esm/batch-settlement/client/index.d.mts +111 -0
- package/dist/esm/batch-settlement/client/index.mjs +59 -0
- package/dist/esm/batch-settlement/client/index.mjs.map +1 -0
- package/dist/esm/batch-settlement/facilitator/index.d.mts +71 -0
- package/dist/esm/batch-settlement/facilitator/index.mjs +1235 -0
- package/dist/esm/batch-settlement/facilitator/index.mjs.map +1 -0
- package/dist/esm/batch-settlement/server/file-storage.d.mts +53 -0
- package/dist/esm/batch-settlement/server/file-storage.mjs +128 -0
- package/dist/esm/batch-settlement/server/file-storage.mjs.map +1 -0
- package/dist/esm/batch-settlement/server/index.d.mts +491 -0
- package/dist/esm/batch-settlement/server/index.mjs +1645 -0
- package/dist/esm/batch-settlement/server/index.mjs.map +1 -0
- package/dist/esm/batch-settlement/server/redis-storage.d.mts +87 -0
- package/dist/esm/batch-settlement/server/redis-storage.mjs +156 -0
- package/dist/esm/batch-settlement/server/redis-storage.mjs.map +1 -0
- package/dist/esm/chunk-2EUQTNJO.mjs +38 -0
- package/dist/esm/chunk-2EUQTNJO.mjs.map +1 -0
- package/dist/esm/chunk-53USC5VE.mjs +47 -0
- package/dist/esm/chunk-53USC5VE.mjs.map +1 -0
- package/dist/esm/{chunk-GJ57SZGI.mjs → chunk-6WQOGWBE.mjs} +7 -5
- package/dist/esm/{chunk-GJ57SZGI.mjs.map → chunk-6WQOGWBE.mjs.map} +1 -1
- package/dist/esm/{chunk-F3OOHBAW.mjs → chunk-BTYNCDNS.mjs} +42 -2
- package/dist/esm/{chunk-F3OOHBAW.mjs.map → chunk-BTYNCDNS.mjs.map} +1 -1
- package/dist/esm/{chunk-ERK2ZPOY.mjs → chunk-CSQS7ZON.mjs} +27 -7
- package/dist/esm/chunk-CSQS7ZON.mjs.map +1 -0
- package/dist/esm/chunk-GD4MKCN7.mjs +57 -0
- package/dist/esm/chunk-GD4MKCN7.mjs.map +1 -0
- package/dist/esm/chunk-HYABYUBD.mjs +432 -0
- package/dist/esm/chunk-HYABYUBD.mjs.map +1 -0
- package/dist/esm/chunk-IN5YIT5C.mjs +159 -0
- package/dist/esm/chunk-IN5YIT5C.mjs.map +1 -0
- package/dist/esm/{chunk-JII456TS.mjs → chunk-JK7SLLF7.mjs} +1 -1
- package/dist/esm/chunk-JK7SLLF7.mjs.map +1 -0
- package/dist/esm/{chunk-C4ZQMS77.mjs → chunk-MACPBXCT.mjs} +2 -216
- package/dist/esm/chunk-MACPBXCT.mjs.map +1 -0
- package/dist/esm/chunk-NKYVYGRA.mjs +911 -0
- package/dist/esm/chunk-NKYVYGRA.mjs.map +1 -0
- package/dist/esm/{chunk-FQJR4RCF.mjs → chunk-R7I3RZFF.mjs} +10 -6
- package/dist/esm/{chunk-FQJR4RCF.mjs.map → chunk-R7I3RZFF.mjs.map} +1 -1
- package/dist/esm/{chunk-CRT6YNY5.mjs → chunk-RWLVVO3B.mjs} +21 -61
- package/dist/esm/chunk-RWLVVO3B.mjs.map +1 -0
- package/dist/esm/chunk-TGFAVNUD.mjs +111 -0
- package/dist/esm/chunk-TGFAVNUD.mjs.map +1 -0
- package/dist/esm/chunk-TW7Z65AO.mjs +34 -0
- package/dist/esm/chunk-TW7Z65AO.mjs.map +1 -0
- package/dist/esm/chunk-U4HCGTLU.mjs +35 -0
- package/dist/esm/chunk-U4HCGTLU.mjs.map +1 -0
- package/dist/esm/chunk-VS3RYAYE.mjs +80 -0
- package/dist/esm/chunk-VS3RYAYE.mjs.map +1 -0
- package/dist/esm/chunk-W6ON4LG2.mjs +39 -0
- package/dist/esm/chunk-W6ON4LG2.mjs.map +1 -0
- package/dist/esm/{chunk-WKBC5YMI.mjs → chunk-YMQCTKDU.mjs} +23 -55
- package/dist/esm/chunk-YMQCTKDU.mjs.map +1 -0
- package/dist/esm/exact/client/index.d.mts +6 -4
- package/dist/esm/exact/client/index.mjs +10 -5
- package/dist/esm/exact/facilitator/index.d.mts +16 -9
- package/dist/esm/exact/facilitator/index.mjs +36 -14
- package/dist/esm/exact/facilitator/index.mjs.map +1 -1
- package/dist/esm/exact/server/index.mjs +1 -1
- package/dist/esm/exact/v1/client/index.d.mts +2 -1
- package/dist/esm/exact/v1/client/index.mjs +5 -2
- package/dist/esm/exact/v1/facilitator/index.d.mts +11 -5
- package/dist/esm/exact/v1/facilitator/index.mjs +5 -2
- package/dist/esm/index.d.mts +113 -7
- package/dist/esm/index.mjs +53 -7
- package/dist/esm/index.mjs.map +1 -1
- package/dist/esm/permit2-DhJRUcgY.d.mts +729 -0
- package/dist/esm/rpc-DULZzRne.d.mts +13 -0
- package/dist/esm/scheme-DtbSS4Fk.d.mts +307 -0
- package/dist/esm/scheme-gtqAIYPJ.d.mts +47 -0
- package/dist/esm/signer-tYS6Y46X.d.mts +170 -0
- package/dist/esm/storage-6W5MO46W.d.mts +50 -0
- package/dist/esm/storage-sZ1CDS4P.d.mts +81 -0
- package/dist/esm/types-CF8P2-NM.d.mts +180 -0
- package/dist/esm/upto/client/index.d.mts +5 -3
- package/dist/esm/upto/client/index.mjs +9 -4
- package/dist/esm/upto/facilitator/index.d.mts +2 -1
- package/dist/esm/upto/facilitator/index.mjs +17 -9
- package/dist/esm/upto/facilitator/index.mjs.map +1 -1
- package/dist/esm/upto/server/index.mjs +1 -1
- package/dist/esm/v1/index.d.mts +2 -1
- package/dist/esm/v1/index.mjs +5 -2
- package/package.json +5 -5
- package/dist/esm/chunk-C4ZQMS77.mjs.map +0 -1
- package/dist/esm/chunk-CRT6YNY5.mjs.map +0 -1
- package/dist/esm/chunk-ERK2ZPOY.mjs.map +0 -1
- package/dist/esm/chunk-JII456TS.mjs.map +0 -1
- package/dist/esm/chunk-WKBC5YMI.mjs.map +0 -1
|
@@ -0,0 +1,911 @@
|
|
|
1
|
+
import {
|
|
2
|
+
buildErc3009DepositNonce
|
|
3
|
+
} from "./chunk-W6ON4LG2.mjs";
|
|
4
|
+
import {
|
|
5
|
+
BATCH_SETTLEMENT_ADDRESS,
|
|
6
|
+
BATCH_SETTLEMENT_SCHEME,
|
|
7
|
+
ERC3009_DEPOSIT_COLLECTOR_ADDRESS,
|
|
8
|
+
ErrCumulativeAmountBelowClaimed,
|
|
9
|
+
ErrCumulativeAmountMismatch,
|
|
10
|
+
ErrRefundAmountInvalid,
|
|
11
|
+
ErrRefundNoBalance,
|
|
12
|
+
MIN_WITHDRAW_DELAY,
|
|
13
|
+
PERMIT2_DEPOSIT_COLLECTOR_ADDRESS,
|
|
14
|
+
batchPermit2WitnessTypes,
|
|
15
|
+
batchSettlementABI,
|
|
16
|
+
computeChannelId,
|
|
17
|
+
getBatchSettlementEip712Domain,
|
|
18
|
+
receiveAuthorizationTypes,
|
|
19
|
+
voucherTypes
|
|
20
|
+
} from "./chunk-HYABYUBD.mjs";
|
|
21
|
+
import {
|
|
22
|
+
isBatchSettlementRefundPayload
|
|
23
|
+
} from "./chunk-U4HCGTLU.mjs";
|
|
24
|
+
import {
|
|
25
|
+
trySignEip2612PermitExtension,
|
|
26
|
+
trySignErc20ApprovalExtension
|
|
27
|
+
} from "./chunk-YMQCTKDU.mjs";
|
|
28
|
+
import {
|
|
29
|
+
PERMIT2_ADDRESS
|
|
30
|
+
} from "./chunk-MACPBXCT.mjs";
|
|
31
|
+
import {
|
|
32
|
+
createNonce,
|
|
33
|
+
createPermit2Nonce,
|
|
34
|
+
getEvmChainId
|
|
35
|
+
} from "./chunk-TW7Z65AO.mjs";
|
|
36
|
+
|
|
37
|
+
// src/batch-settlement/client/voucher.ts
|
|
38
|
+
async function signVoucher(signer, channelId, maxClaimableAmount, network) {
|
|
39
|
+
const chainId = getEvmChainId(network);
|
|
40
|
+
const signature = await signer.signTypedData({
|
|
41
|
+
domain: getBatchSettlementEip712Domain(chainId),
|
|
42
|
+
types: voucherTypes,
|
|
43
|
+
primaryType: "Voucher",
|
|
44
|
+
message: {
|
|
45
|
+
channelId,
|
|
46
|
+
maxClaimableAmount: BigInt(maxClaimableAmount)
|
|
47
|
+
}
|
|
48
|
+
});
|
|
49
|
+
return {
|
|
50
|
+
channelId,
|
|
51
|
+
maxClaimableAmount,
|
|
52
|
+
signature
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// src/batch-settlement/client/eip3009.ts
|
|
57
|
+
import { getAddress } from "viem";
|
|
58
|
+
async function createBatchSettlementEIP3009DepositPayload(signer, x402Version, paymentRequirements, channelConfig, depositAmount, maxClaimableAmount, voucherSigner) {
|
|
59
|
+
const salt = createNonce();
|
|
60
|
+
const now = Math.floor(Date.now() / 1e3);
|
|
61
|
+
const chainId = getEvmChainId(paymentRequirements.network);
|
|
62
|
+
if (!paymentRequirements.extra?.name || !paymentRequirements.extra?.version) {
|
|
63
|
+
throw new Error(
|
|
64
|
+
`EIP-712 domain parameters (name, version) are required in payment requirements for asset ${paymentRequirements.asset}`
|
|
65
|
+
);
|
|
66
|
+
}
|
|
67
|
+
const { name, version } = paymentRequirements.extra;
|
|
68
|
+
const channelId = computeChannelId(channelConfig, paymentRequirements.network);
|
|
69
|
+
const erc3009Nonce = buildErc3009DepositNonce(channelId, salt);
|
|
70
|
+
const signature = await signer.signTypedData({
|
|
71
|
+
domain: {
|
|
72
|
+
name,
|
|
73
|
+
version,
|
|
74
|
+
chainId,
|
|
75
|
+
verifyingContract: getAddress(paymentRequirements.asset)
|
|
76
|
+
},
|
|
77
|
+
types: receiveAuthorizationTypes,
|
|
78
|
+
primaryType: "ReceiveWithAuthorization",
|
|
79
|
+
message: {
|
|
80
|
+
from: getAddress(signer.address),
|
|
81
|
+
to: getAddress(ERC3009_DEPOSIT_COLLECTOR_ADDRESS),
|
|
82
|
+
value: BigInt(depositAmount),
|
|
83
|
+
validAfter: BigInt(now - 600),
|
|
84
|
+
validBefore: BigInt(now + paymentRequirements.maxTimeoutSeconds),
|
|
85
|
+
nonce: erc3009Nonce
|
|
86
|
+
}
|
|
87
|
+
});
|
|
88
|
+
const vSigner = voucherSigner ?? signer;
|
|
89
|
+
const voucher = await signVoucher(
|
|
90
|
+
vSigner,
|
|
91
|
+
channelId,
|
|
92
|
+
maxClaimableAmount,
|
|
93
|
+
paymentRequirements.network
|
|
94
|
+
);
|
|
95
|
+
const payload = {
|
|
96
|
+
type: "deposit",
|
|
97
|
+
channelConfig,
|
|
98
|
+
voucher,
|
|
99
|
+
deposit: {
|
|
100
|
+
amount: depositAmount,
|
|
101
|
+
authorization: {
|
|
102
|
+
erc3009Authorization: {
|
|
103
|
+
validAfter: (now - 600).toString(),
|
|
104
|
+
validBefore: (now + paymentRequirements.maxTimeoutSeconds).toString(),
|
|
105
|
+
salt,
|
|
106
|
+
signature
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
};
|
|
111
|
+
return {
|
|
112
|
+
x402Version,
|
|
113
|
+
payload
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// src/batch-settlement/client/storage.ts
|
|
118
|
+
var InMemoryClientChannelStorage = class {
|
|
119
|
+
constructor() {
|
|
120
|
+
this.channels = /* @__PURE__ */ new Map();
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* Returns the channel record for `key` if present.
|
|
124
|
+
*
|
|
125
|
+
* @param key - Channel storage key (channelId).
|
|
126
|
+
* @returns Persisted context or undefined.
|
|
127
|
+
*/
|
|
128
|
+
async get(key) {
|
|
129
|
+
return this.channels.get(key);
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* Stores or replaces the channel record for `key`.
|
|
133
|
+
*
|
|
134
|
+
* @param key - Channel storage key.
|
|
135
|
+
* @param context - Channel fields to persist.
|
|
136
|
+
* @returns Resolves when stored.
|
|
137
|
+
*/
|
|
138
|
+
async set(key, context) {
|
|
139
|
+
this.channels.set(key, context);
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* Removes the channel record for `key` if it exists.
|
|
143
|
+
*
|
|
144
|
+
* @param key - Channel storage key.
|
|
145
|
+
* @returns Resolves when removed.
|
|
146
|
+
*/
|
|
147
|
+
async delete(key) {
|
|
148
|
+
this.channels.delete(key);
|
|
149
|
+
}
|
|
150
|
+
};
|
|
151
|
+
|
|
152
|
+
// src/batch-settlement/client/config.ts
|
|
153
|
+
var DEFAULT_SALT = "0x0000000000000000000000000000000000000000000000000000000000000000";
|
|
154
|
+
function isBatchSettlementEvmSchemeOptions(o) {
|
|
155
|
+
return o !== void 0 && typeof o === "object" && ("storage" in o || "depositPolicy" in o || "depositStrategy" in o || "salt" in o || "payerAuthorizer" in o || "rpcUrl" in o || "voucherSigner" in o);
|
|
156
|
+
}
|
|
157
|
+
function resolveClientOptions(second) {
|
|
158
|
+
if (second === void 0) {
|
|
159
|
+
return { storage: new InMemoryClientChannelStorage(), salt: DEFAULT_SALT };
|
|
160
|
+
}
|
|
161
|
+
if (isBatchSettlementEvmSchemeOptions(second)) {
|
|
162
|
+
return {
|
|
163
|
+
storage: second.storage ?? new InMemoryClientChannelStorage(),
|
|
164
|
+
depositPolicy: second.depositPolicy,
|
|
165
|
+
depositStrategy: second.depositStrategy,
|
|
166
|
+
salt: second.salt ?? DEFAULT_SALT,
|
|
167
|
+
payerAuthorizer: second.payerAuthorizer,
|
|
168
|
+
voucherSigner: second.voucherSigner,
|
|
169
|
+
extensionRpcOptions: second.rpcUrl ? { rpcUrl: second.rpcUrl } : void 0
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
return {
|
|
173
|
+
storage: new InMemoryClientChannelStorage(),
|
|
174
|
+
depositPolicy: second,
|
|
175
|
+
salt: DEFAULT_SALT
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
function validateDepositPolicy(policy) {
|
|
179
|
+
if (!policy) return;
|
|
180
|
+
const m = policy.depositMultiplier;
|
|
181
|
+
if (m !== void 0 && (!Number.isInteger(m) || m < 3)) {
|
|
182
|
+
throw new Error("depositMultiplier must be an integer >= 3");
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
function depositAmountForRequest(policy, requestAmount) {
|
|
186
|
+
const mult = BigInt(policy?.depositMultiplier ?? 5);
|
|
187
|
+
return (mult * requestAmount).toString();
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// src/batch-settlement/client/channel.ts
|
|
191
|
+
import { decodePaymentResponseHeader } from "@payai/x402/http";
|
|
192
|
+
import { getAddress as getAddress2 } from "viem";
|
|
193
|
+
function readResponseChannelState(extra) {
|
|
194
|
+
const channelState = extra.channelState;
|
|
195
|
+
if (typeof channelState !== "object" || channelState === null) {
|
|
196
|
+
return void 0;
|
|
197
|
+
}
|
|
198
|
+
return channelState;
|
|
199
|
+
}
|
|
200
|
+
function buildChannelConfig(deps, paymentRequirements) {
|
|
201
|
+
const extra = paymentRequirements.extra;
|
|
202
|
+
const receiverAuthorizer = extra?.receiverAuthorizer;
|
|
203
|
+
if (!receiverAuthorizer || getAddress2(receiverAuthorizer) === "0x0000000000000000000000000000000000000000") {
|
|
204
|
+
throw new Error("Payment requirements must include a non-zero extra.receiverAuthorizer");
|
|
205
|
+
}
|
|
206
|
+
return {
|
|
207
|
+
payer: deps.signer.address,
|
|
208
|
+
payerAuthorizer: getAddress2(
|
|
209
|
+
deps.payerAuthorizer ?? deps.voucherSigner?.address ?? deps.signer.address
|
|
210
|
+
),
|
|
211
|
+
receiver: paymentRequirements.payTo,
|
|
212
|
+
receiverAuthorizer: getAddress2(receiverAuthorizer),
|
|
213
|
+
token: paymentRequirements.asset,
|
|
214
|
+
withdrawDelay: typeof extra?.withdrawDelay === "number" ? extra.withdrawDelay : MIN_WITHDRAW_DELAY,
|
|
215
|
+
salt: deps.salt
|
|
216
|
+
};
|
|
217
|
+
}
|
|
218
|
+
async function processSettleResponse(storage, settle) {
|
|
219
|
+
const extra = settle.extra ?? {};
|
|
220
|
+
const channelState = readResponseChannelState(extra);
|
|
221
|
+
if (!channelState) return;
|
|
222
|
+
const channelId = channelState.channelId;
|
|
223
|
+
const key = channelId.toLowerCase();
|
|
224
|
+
const prev = await storage.get(key);
|
|
225
|
+
const next = { ...prev ?? {} };
|
|
226
|
+
if (channelState.chargedCumulativeAmount !== void 0) {
|
|
227
|
+
next.chargedCumulativeAmount = String(channelState.chargedCumulativeAmount);
|
|
228
|
+
}
|
|
229
|
+
if (channelState.balance !== void 0) {
|
|
230
|
+
next.balance = String(channelState.balance);
|
|
231
|
+
}
|
|
232
|
+
if (channelState.totalClaimed !== void 0) {
|
|
233
|
+
next.totalClaimed = String(channelState.totalClaimed);
|
|
234
|
+
}
|
|
235
|
+
await storage.set(key, next);
|
|
236
|
+
}
|
|
237
|
+
async function updateChannelAfterRefund(storage, channelKey, settleExtra) {
|
|
238
|
+
const channelState = readResponseChannelState(settleExtra);
|
|
239
|
+
if (!channelState) {
|
|
240
|
+
await storage.delete(channelKey);
|
|
241
|
+
return;
|
|
242
|
+
}
|
|
243
|
+
const balanceAfter = channelState.balance !== void 0 ? BigInt(String(channelState.balance)) : void 0;
|
|
244
|
+
if (balanceAfter === void 0 || balanceAfter <= 0n) {
|
|
245
|
+
await storage.delete(channelKey);
|
|
246
|
+
return;
|
|
247
|
+
}
|
|
248
|
+
const prev = await storage.get(channelKey);
|
|
249
|
+
const next = { ...prev ?? {} };
|
|
250
|
+
next.balance = balanceAfter.toString();
|
|
251
|
+
if (channelState.chargedCumulativeAmount !== void 0) {
|
|
252
|
+
next.chargedCumulativeAmount = String(channelState.chargedCumulativeAmount);
|
|
253
|
+
}
|
|
254
|
+
if (channelState.totalClaimed !== void 0) {
|
|
255
|
+
next.totalClaimed = String(channelState.totalClaimed);
|
|
256
|
+
}
|
|
257
|
+
await storage.set(channelKey, next);
|
|
258
|
+
}
|
|
259
|
+
async function processPaymentResponse(storage, getHeader) {
|
|
260
|
+
const raw = getHeader("PAYMENT-RESPONSE");
|
|
261
|
+
if (!raw) return;
|
|
262
|
+
const settle = decodePaymentResponseHeader(raw);
|
|
263
|
+
await processSettleResponse(storage, settle);
|
|
264
|
+
}
|
|
265
|
+
async function recoverChannel(deps, paymentRequirements) {
|
|
266
|
+
if (!deps.signer.readContract) {
|
|
267
|
+
throw new Error("recoverChannel requires ClientEvmSigner.readContract");
|
|
268
|
+
}
|
|
269
|
+
const config = buildChannelConfig(deps, paymentRequirements);
|
|
270
|
+
const channelId = computeChannelId(config, paymentRequirements.network);
|
|
271
|
+
const [chBalance, chTotalClaimed] = await readChannelBalanceAndTotalClaimed(
|
|
272
|
+
deps.signer,
|
|
273
|
+
channelId
|
|
274
|
+
);
|
|
275
|
+
const ctx = {
|
|
276
|
+
chargedCumulativeAmount: chTotalClaimed.toString(),
|
|
277
|
+
balance: chBalance.toString(),
|
|
278
|
+
totalClaimed: chTotalClaimed.toString()
|
|
279
|
+
};
|
|
280
|
+
await deps.storage.set(channelId.toLowerCase(), ctx);
|
|
281
|
+
return ctx;
|
|
282
|
+
}
|
|
283
|
+
async function readChannelBalanceAndTotalClaimed(signer, channelId) {
|
|
284
|
+
if (!signer.readContract) {
|
|
285
|
+
throw new Error("readChannelBalanceAndTotalClaimed requires ClientEvmSigner.readContract");
|
|
286
|
+
}
|
|
287
|
+
return await signer.readContract({
|
|
288
|
+
address: BATCH_SETTLEMENT_ADDRESS,
|
|
289
|
+
abi: batchSettlementABI,
|
|
290
|
+
functionName: "channels",
|
|
291
|
+
args: [channelId]
|
|
292
|
+
});
|
|
293
|
+
}
|
|
294
|
+
async function hasChannel(storage, channelId) {
|
|
295
|
+
const channel = await storage.get(channelId.toLowerCase());
|
|
296
|
+
return channel !== void 0;
|
|
297
|
+
}
|
|
298
|
+
async function getChannel(storage, channelId) {
|
|
299
|
+
return storage.get(channelId.toLowerCase());
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
// src/batch-settlement/client/recovery.ts
|
|
303
|
+
import { getAddress as getAddress3, recoverTypedDataAddress } from "viem";
|
|
304
|
+
async function processCorrectivePaymentRequired(deps, paymentRequired) {
|
|
305
|
+
if (paymentRequired.error !== ErrCumulativeAmountMismatch && paymentRequired.error !== ErrCumulativeAmountBelowClaimed) {
|
|
306
|
+
return false;
|
|
307
|
+
}
|
|
308
|
+
const accept = paymentRequired.accepts.find((a) => a.scheme === BATCH_SETTLEMENT_SCHEME);
|
|
309
|
+
if (!accept) {
|
|
310
|
+
return false;
|
|
311
|
+
}
|
|
312
|
+
const channelState = accept.extra.channelState;
|
|
313
|
+
const voucherState = accept.extra.voucherState;
|
|
314
|
+
const hasSig = channelState?.chargedCumulativeAmount !== void 0 && voucherState?.signedMaxClaimable !== void 0 && voucherState.signature !== void 0;
|
|
315
|
+
if (!hasSig) {
|
|
316
|
+
return recoverFromOnChainState(deps, accept);
|
|
317
|
+
}
|
|
318
|
+
return recoverFromSignature(deps, accept, channelState, voucherState);
|
|
319
|
+
}
|
|
320
|
+
async function recoverFromSignature(deps, accept, channelState, voucherState) {
|
|
321
|
+
const chargedRaw = channelState.chargedCumulativeAmount;
|
|
322
|
+
const signedRaw = voucherState.signedMaxClaimable;
|
|
323
|
+
const sig = voucherState.signature;
|
|
324
|
+
const charged = BigInt(String(chargedRaw));
|
|
325
|
+
const signed = BigInt(String(signedRaw));
|
|
326
|
+
if (charged > signed) {
|
|
327
|
+
return false;
|
|
328
|
+
}
|
|
329
|
+
const config = buildChannelConfig(deps, accept);
|
|
330
|
+
const channelId = computeChannelId(config, accept.network);
|
|
331
|
+
if (!deps.signer.readContract) {
|
|
332
|
+
return false;
|
|
333
|
+
}
|
|
334
|
+
const [chBalance, chTotalClaimed] = await readChannelBalanceAndTotalClaimed(
|
|
335
|
+
deps.signer,
|
|
336
|
+
channelId
|
|
337
|
+
);
|
|
338
|
+
if (charged < chTotalClaimed) {
|
|
339
|
+
return false;
|
|
340
|
+
}
|
|
341
|
+
const chainId = getEvmChainId(accept.network);
|
|
342
|
+
const recovered = await recoverTypedDataAddress({
|
|
343
|
+
domain: getBatchSettlementEip712Domain(chainId),
|
|
344
|
+
types: voucherTypes,
|
|
345
|
+
primaryType: "Voucher",
|
|
346
|
+
message: {
|
|
347
|
+
channelId,
|
|
348
|
+
maxClaimableAmount: signed
|
|
349
|
+
},
|
|
350
|
+
signature: sig
|
|
351
|
+
});
|
|
352
|
+
const expectedSigner = getAddress3(
|
|
353
|
+
deps.payerAuthorizer ?? deps.voucherSigner?.address ?? deps.signer.address
|
|
354
|
+
);
|
|
355
|
+
if (recovered.toLowerCase() !== expectedSigner.toLowerCase()) {
|
|
356
|
+
return false;
|
|
357
|
+
}
|
|
358
|
+
const ctx = {
|
|
359
|
+
chargedCumulativeAmount: charged.toString(),
|
|
360
|
+
signedMaxClaimable: signed.toString(),
|
|
361
|
+
signature: sig,
|
|
362
|
+
balance: chBalance.toString(),
|
|
363
|
+
totalClaimed: chTotalClaimed.toString()
|
|
364
|
+
};
|
|
365
|
+
await deps.storage.set(channelId.toLowerCase(), ctx);
|
|
366
|
+
return true;
|
|
367
|
+
}
|
|
368
|
+
async function recoverFromOnChainState(deps, accept) {
|
|
369
|
+
if (!deps.signer.readContract) {
|
|
370
|
+
return false;
|
|
371
|
+
}
|
|
372
|
+
const config = buildChannelConfig(deps, accept);
|
|
373
|
+
const channelId = computeChannelId(config, accept.network);
|
|
374
|
+
const [chBalance, chTotalClaimed] = await readChannelBalanceAndTotalClaimed(
|
|
375
|
+
deps.signer,
|
|
376
|
+
channelId
|
|
377
|
+
);
|
|
378
|
+
const ctx = {
|
|
379
|
+
chargedCumulativeAmount: chTotalClaimed.toString(),
|
|
380
|
+
balance: chBalance.toString(),
|
|
381
|
+
totalClaimed: chTotalClaimed.toString()
|
|
382
|
+
};
|
|
383
|
+
await deps.storage.set(channelId.toLowerCase(), ctx);
|
|
384
|
+
return true;
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
// src/batch-settlement/client/hooks.ts
|
|
388
|
+
function createBatchSettlementClientHooks(deps) {
|
|
389
|
+
return {
|
|
390
|
+
onPaymentResponse: (ctx) => handleBatchSettlementPaymentResponse(deps, ctx)
|
|
391
|
+
};
|
|
392
|
+
}
|
|
393
|
+
async function handleBatchSettlementPaymentResponse(deps, ctx) {
|
|
394
|
+
if (ctx.settleResponse) {
|
|
395
|
+
if (isBatchSettlementRefundPayload(ctx.paymentPayload.payload)) {
|
|
396
|
+
const extra = ctx.settleResponse.extra ?? {};
|
|
397
|
+
const channelState = extra.channelState;
|
|
398
|
+
const channelId = typeof channelState === "object" && channelState !== null && "channelId" in channelState ? channelState.channelId : void 0;
|
|
399
|
+
if (typeof channelId === "string" && channelId) {
|
|
400
|
+
await updateChannelAfterRefund(deps.storage, channelId.toLowerCase(), extra);
|
|
401
|
+
}
|
|
402
|
+
return;
|
|
403
|
+
}
|
|
404
|
+
await processSettleResponse(deps.storage, ctx.settleResponse);
|
|
405
|
+
return;
|
|
406
|
+
}
|
|
407
|
+
if (ctx.paymentRequired) {
|
|
408
|
+
const recovered = await processCorrectivePaymentRequired(deps, ctx.paymentRequired);
|
|
409
|
+
return recovered ? { recovered: true } : void 0;
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
// src/batch-settlement/client/refund.ts
|
|
414
|
+
import { decodePaymentRequiredHeader, decodePaymentResponseHeader as decodePaymentResponseHeader2 } from "@payai/x402/http";
|
|
415
|
+
import { x402Client, x402HTTPClient } from "@payai/x402/client";
|
|
416
|
+
var NON_RECOVERABLE_REFUND_ERRORS = /* @__PURE__ */ new Set([
|
|
417
|
+
ErrRefundNoBalance,
|
|
418
|
+
ErrRefundAmountInvalid
|
|
419
|
+
]);
|
|
420
|
+
async function refundChannel(ctx, url, options) {
|
|
421
|
+
const fetchImpl = options?.fetch ?? globalThis.fetch;
|
|
422
|
+
if (!fetchImpl) {
|
|
423
|
+
throw new Error("refund requires a fetch implementation (globalThis.fetch unavailable)");
|
|
424
|
+
}
|
|
425
|
+
const refundAmount = normalizeRefundAmount(options?.amount);
|
|
426
|
+
const probe = await probeRefundRequirements(url, fetchImpl);
|
|
427
|
+
return executeRefund(ctx, url, probe, refundAmount, fetchImpl);
|
|
428
|
+
}
|
|
429
|
+
async function probeRefundRequirements(url, fetchImpl) {
|
|
430
|
+
const probe = await fetchImpl(url, { method: "GET" });
|
|
431
|
+
if (probe.status !== 402) {
|
|
432
|
+
throw new Error(`Refund probe expected 402, got ${probe.status}`);
|
|
433
|
+
}
|
|
434
|
+
const header = probe.headers.get("PAYMENT-REQUIRED");
|
|
435
|
+
if (!header) {
|
|
436
|
+
throw new Error("Refund probe response missing PAYMENT-REQUIRED header");
|
|
437
|
+
}
|
|
438
|
+
const paymentRequired = decodePaymentRequiredHeader(header);
|
|
439
|
+
const requirements = paymentRequired.accepts.find((a) => a.scheme === BATCH_SETTLEMENT_SCHEME);
|
|
440
|
+
if (!requirements) {
|
|
441
|
+
throw new Error(`No ${BATCH_SETTLEMENT_SCHEME} payment option at ${url}`);
|
|
442
|
+
}
|
|
443
|
+
const extra = requirements.extra;
|
|
444
|
+
if (!extra?.receiverAuthorizer) {
|
|
445
|
+
throw new Error("Refund requires a configured receiverAuthorizer on the receiver");
|
|
446
|
+
}
|
|
447
|
+
return { paymentRequired, requirements };
|
|
448
|
+
}
|
|
449
|
+
async function executeRefund(ctx, url, probe, refundAmount, fetchImpl) {
|
|
450
|
+
const maxAttempts = 2;
|
|
451
|
+
const { paymentRequired, requirements } = probe;
|
|
452
|
+
const httpClient = createRefundHttpClient(ctx, requirements);
|
|
453
|
+
for (let attempt = 1; attempt <= maxAttempts; attempt += 1) {
|
|
454
|
+
const paymentPayload = await buildRefundPaymentPayload(
|
|
455
|
+
ctx,
|
|
456
|
+
paymentRequired,
|
|
457
|
+
requirements,
|
|
458
|
+
refundAmount
|
|
459
|
+
);
|
|
460
|
+
const headers = httpClient.encodePaymentSignatureHeader(paymentPayload);
|
|
461
|
+
const response = await fetchImpl(url, { method: "GET", headers });
|
|
462
|
+
if (response.status === 402) {
|
|
463
|
+
const nonRecoverable = getNonRecoverableRefundFailure(response);
|
|
464
|
+
if (nonRecoverable) {
|
|
465
|
+
throw new Error(nonRecoverable);
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
const result = await httpClient.processPaymentResult(
|
|
469
|
+
paymentPayload,
|
|
470
|
+
(name) => response.headers.get(name),
|
|
471
|
+
response.status
|
|
472
|
+
);
|
|
473
|
+
if (response.status === 402) {
|
|
474
|
+
if (result.recovered && attempt < maxAttempts) {
|
|
475
|
+
continue;
|
|
476
|
+
}
|
|
477
|
+
if (result.recovered) {
|
|
478
|
+
throw new Error(`Refund failed: server returned 402 after ${attempt} attempt(s)`);
|
|
479
|
+
}
|
|
480
|
+
const corrective = getRefundPaymentRequired(response);
|
|
481
|
+
throw new Error(`Refund failed: ${corrective.error ?? "unknown"}`);
|
|
482
|
+
}
|
|
483
|
+
if (!result.settleResponse) {
|
|
484
|
+
throw new Error(
|
|
485
|
+
`Refund response missing PAYMENT-RESPONSE header (status ${response.status})`
|
|
486
|
+
);
|
|
487
|
+
}
|
|
488
|
+
return result.settleResponse;
|
|
489
|
+
}
|
|
490
|
+
throw new Error("Refund failed: retry budget exhausted");
|
|
491
|
+
}
|
|
492
|
+
async function buildRefundPaymentPayload(ctx, paymentRequired, requirements, refundAmount) {
|
|
493
|
+
const config = buildChannelConfig(ctx, requirements);
|
|
494
|
+
const channelId = computeChannelId(config, requirements.network);
|
|
495
|
+
const key = channelId.toLowerCase();
|
|
496
|
+
let channel = await ctx.storage.get(key);
|
|
497
|
+
if (channel === void 0 && ctx.signer.readContract) {
|
|
498
|
+
channel = await recoverChannel(ctx, requirements);
|
|
499
|
+
}
|
|
500
|
+
if (channel === void 0) {
|
|
501
|
+
throw new Error(
|
|
502
|
+
"Refund requires an existing channel record; deposit first or call from a context with an EVM RPC"
|
|
503
|
+
);
|
|
504
|
+
}
|
|
505
|
+
const charged = channel.chargedCumulativeAmount ?? "0";
|
|
506
|
+
if (channel.balance !== void 0 && BigInt(channel.balance) <= BigInt(charged)) {
|
|
507
|
+
throw new Error(
|
|
508
|
+
`Refund failed: channel has no remaining balance (balance=${channel.balance}, chargedCumulativeAmount=${charged})`
|
|
509
|
+
);
|
|
510
|
+
}
|
|
511
|
+
const voucherSigner = ctx.voucherSigner ?? ctx.signer;
|
|
512
|
+
const voucher = await signVoucher(voucherSigner, channelId, charged, requirements.network);
|
|
513
|
+
const payload = {
|
|
514
|
+
type: "refund",
|
|
515
|
+
channelConfig: config,
|
|
516
|
+
voucher,
|
|
517
|
+
...refundAmount !== void 0 ? { amount: refundAmount } : {}
|
|
518
|
+
};
|
|
519
|
+
return {
|
|
520
|
+
x402Version: 2,
|
|
521
|
+
accepted: requirements,
|
|
522
|
+
payload,
|
|
523
|
+
...paymentRequired.resource ? { resource: paymentRequired.resource } : {},
|
|
524
|
+
...paymentRequired.extensions ? { extensions: paymentRequired.extensions } : {}
|
|
525
|
+
};
|
|
526
|
+
}
|
|
527
|
+
function createRefundHttpClient(ctx, requirements) {
|
|
528
|
+
const client = new x402Client().register(requirements.network, {
|
|
529
|
+
scheme: BATCH_SETTLEMENT_SCHEME,
|
|
530
|
+
schemeHooks: createBatchSettlementClientHooks(ctx),
|
|
531
|
+
createPaymentPayload: async () => {
|
|
532
|
+
throw new Error("Refund payloads are built by refundChannel");
|
|
533
|
+
}
|
|
534
|
+
});
|
|
535
|
+
return new x402HTTPClient(client);
|
|
536
|
+
}
|
|
537
|
+
function getNonRecoverableRefundFailure(response) {
|
|
538
|
+
const settleHeader = response.headers.get("PAYMENT-RESPONSE");
|
|
539
|
+
if (settleHeader) {
|
|
540
|
+
return formatRefundFailure(decodePaymentResponseHeader2(settleHeader));
|
|
541
|
+
}
|
|
542
|
+
const paymentRequired = getRefundPaymentRequired(response);
|
|
543
|
+
const errorCode = paymentRequired.error;
|
|
544
|
+
if (errorCode && NON_RECOVERABLE_REFUND_ERRORS.has(errorCode)) {
|
|
545
|
+
return `Refund failed: ${errorCode}`;
|
|
546
|
+
}
|
|
547
|
+
}
|
|
548
|
+
function getRefundPaymentRequired(response) {
|
|
549
|
+
const requiredHeader = response.headers.get("PAYMENT-REQUIRED");
|
|
550
|
+
if (!requiredHeader) {
|
|
551
|
+
throw new Error("Refund 402 missing PAYMENT-REQUIRED header");
|
|
552
|
+
}
|
|
553
|
+
return decodePaymentRequiredHeader(requiredHeader);
|
|
554
|
+
}
|
|
555
|
+
function formatRefundFailure(settle) {
|
|
556
|
+
const reason = settle.errorReason ?? "unknown_settlement_error";
|
|
557
|
+
const message = settle.errorMessage;
|
|
558
|
+
if (message && message !== reason) {
|
|
559
|
+
return `Refund failed: ${reason}: ${message}`;
|
|
560
|
+
}
|
|
561
|
+
return `Refund failed: ${reason}`;
|
|
562
|
+
}
|
|
563
|
+
function normalizeRefundAmount(amount) {
|
|
564
|
+
if (amount === void 0) return void 0;
|
|
565
|
+
if (!/^\d+$/.test(amount) || amount === "0") {
|
|
566
|
+
throw new Error(`Invalid refund amount "${amount}": must be a positive integer string`);
|
|
567
|
+
}
|
|
568
|
+
return amount;
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
// src/batch-settlement/client/scheme.ts
|
|
572
|
+
import { getAddress as getAddress5 } from "viem";
|
|
573
|
+
|
|
574
|
+
// src/batch-settlement/client/permit2.ts
|
|
575
|
+
import { getAddress as getAddress4 } from "viem";
|
|
576
|
+
async function createBatchSettlementPermit2DepositPayload(signer, x402Version, paymentRequirements, channelConfig, depositAmount, maxClaimableAmount, voucherSigner) {
|
|
577
|
+
const chainId = getEvmChainId(paymentRequirements.network);
|
|
578
|
+
const nonce = createPermit2Nonce();
|
|
579
|
+
const deadline = Math.floor(Date.now() / 1e3 + paymentRequirements.maxTimeoutSeconds).toString();
|
|
580
|
+
const channelId = computeChannelId(channelConfig, paymentRequirements.network);
|
|
581
|
+
const permit2Authorization = {
|
|
582
|
+
from: signer.address,
|
|
583
|
+
permitted: {
|
|
584
|
+
token: getAddress4(paymentRequirements.asset),
|
|
585
|
+
amount: depositAmount
|
|
586
|
+
},
|
|
587
|
+
spender: getAddress4(PERMIT2_DEPOSIT_COLLECTOR_ADDRESS),
|
|
588
|
+
nonce,
|
|
589
|
+
deadline,
|
|
590
|
+
witness: {
|
|
591
|
+
channelId
|
|
592
|
+
}
|
|
593
|
+
};
|
|
594
|
+
const signature = await signer.signTypedData({
|
|
595
|
+
domain: { name: "Permit2", chainId, verifyingContract: PERMIT2_ADDRESS },
|
|
596
|
+
types: batchPermit2WitnessTypes,
|
|
597
|
+
primaryType: "PermitWitnessTransferFrom",
|
|
598
|
+
message: {
|
|
599
|
+
permitted: {
|
|
600
|
+
token: permit2Authorization.permitted.token,
|
|
601
|
+
amount: BigInt(permit2Authorization.permitted.amount)
|
|
602
|
+
},
|
|
603
|
+
spender: permit2Authorization.spender,
|
|
604
|
+
nonce: BigInt(permit2Authorization.nonce),
|
|
605
|
+
deadline: BigInt(permit2Authorization.deadline),
|
|
606
|
+
witness: {
|
|
607
|
+
channelId
|
|
608
|
+
}
|
|
609
|
+
}
|
|
610
|
+
});
|
|
611
|
+
const voucher = await signVoucher(
|
|
612
|
+
voucherSigner ?? signer,
|
|
613
|
+
channelId,
|
|
614
|
+
maxClaimableAmount,
|
|
615
|
+
paymentRequirements.network
|
|
616
|
+
);
|
|
617
|
+
const payload = {
|
|
618
|
+
type: "deposit",
|
|
619
|
+
channelConfig,
|
|
620
|
+
voucher,
|
|
621
|
+
deposit: {
|
|
622
|
+
amount: depositAmount,
|
|
623
|
+
authorization: {
|
|
624
|
+
permit2Authorization: {
|
|
625
|
+
...permit2Authorization,
|
|
626
|
+
signature
|
|
627
|
+
}
|
|
628
|
+
}
|
|
629
|
+
}
|
|
630
|
+
};
|
|
631
|
+
return { x402Version, payload };
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
// src/batch-settlement/client/scheme.ts
|
|
635
|
+
var BatchSettlementEvmScheme = class {
|
|
636
|
+
/**
|
|
637
|
+
* Constructs a batched client scheme.
|
|
638
|
+
*
|
|
639
|
+
* @param signer - Client EVM wallet used for signing vouchers and ERC-3009 authorizations.
|
|
640
|
+
* @param optionsOrPolicy - Either a full options object or a bare deposit-policy.
|
|
641
|
+
*/
|
|
642
|
+
constructor(signer, optionsOrPolicy) {
|
|
643
|
+
this.signer = signer;
|
|
644
|
+
this.scheme = BATCH_SETTLEMENT_SCHEME;
|
|
645
|
+
const {
|
|
646
|
+
storage,
|
|
647
|
+
depositPolicy,
|
|
648
|
+
depositStrategy,
|
|
649
|
+
salt,
|
|
650
|
+
payerAuthorizer,
|
|
651
|
+
voucherSigner,
|
|
652
|
+
extensionRpcOptions
|
|
653
|
+
} = resolveClientOptions(optionsOrPolicy);
|
|
654
|
+
this.storage = storage;
|
|
655
|
+
this.depositPolicy = depositPolicy;
|
|
656
|
+
this.depositStrategy = depositStrategy;
|
|
657
|
+
this.salt = salt;
|
|
658
|
+
this.payerAuthorizer = payerAuthorizer;
|
|
659
|
+
this.voucherSigner = voucherSigner;
|
|
660
|
+
this.extensionRpcOptions = extensionRpcOptions;
|
|
661
|
+
if (payerAuthorizer !== void 0 && voucherSigner !== void 0 && getAddress5(payerAuthorizer) !== getAddress5(voucherSigner.address)) {
|
|
662
|
+
throw new Error("payerAuthorizer address must match voucherSigner.address");
|
|
663
|
+
}
|
|
664
|
+
validateDepositPolicy(depositPolicy);
|
|
665
|
+
this.schemeHooks = createBatchSettlementClientHooks(this.deps());
|
|
666
|
+
}
|
|
667
|
+
/**
|
|
668
|
+
* Creates the payment payload for a batched request.
|
|
669
|
+
*
|
|
670
|
+
* If the channel has no onchain deposit (or needs a top-up), builds an
|
|
671
|
+
* ERC-3009 deposit payload bundled with a voucher. Otherwise, signs and
|
|
672
|
+
* returns a voucher-only payload.
|
|
673
|
+
*
|
|
674
|
+
* @param x402Version - Protocol version for the payload envelope.
|
|
675
|
+
* @param paymentRequirements - Server payment requirements (scheme, network, asset, amount).
|
|
676
|
+
* @param context - Optional payment payload context with extension hints.
|
|
677
|
+
* @returns A {@link PaymentPayloadResult} ready to be sent as the `X-PAYMENT` header.
|
|
678
|
+
*/
|
|
679
|
+
async createPaymentPayload(x402Version, paymentRequirements, context) {
|
|
680
|
+
const deps = this.deps();
|
|
681
|
+
const config = buildChannelConfig(deps, paymentRequirements);
|
|
682
|
+
const channelId = computeChannelId(config, paymentRequirements.network);
|
|
683
|
+
const key = channelId.toLowerCase();
|
|
684
|
+
let batchedCtx = await this.storage.get(key);
|
|
685
|
+
if (batchedCtx === void 0 && this.signer.readContract) {
|
|
686
|
+
batchedCtx = await recoverChannel(deps, paymentRequirements);
|
|
687
|
+
}
|
|
688
|
+
batchedCtx = batchedCtx ?? {};
|
|
689
|
+
const needsInitialDeposit = !batchedCtx.balance || batchedCtx.balance === "0";
|
|
690
|
+
const baseCumulative = BigInt(batchedCtx.chargedCumulativeAmount ?? "0");
|
|
691
|
+
const requestAmount = BigInt(paymentRequirements.amount);
|
|
692
|
+
const maxClaimableAmount = (baseCumulative + requestAmount).toString();
|
|
693
|
+
const currentBalance = BigInt(batchedCtx.balance ?? "0");
|
|
694
|
+
const needsTopUp = !needsInitialDeposit && BigInt(maxClaimableAmount) > currentBalance;
|
|
695
|
+
if (needsInitialDeposit || needsTopUp) {
|
|
696
|
+
const computedDeposit = depositAmountForRequest(this.depositPolicy, requestAmount);
|
|
697
|
+
const minimumDepositAmount = BigInt(maxClaimableAmount) - currentBalance;
|
|
698
|
+
const depositAmount = await this.resolveDepositAmount({
|
|
699
|
+
paymentRequirements,
|
|
700
|
+
channelConfig: config,
|
|
701
|
+
channelId,
|
|
702
|
+
clientContext: batchedCtx,
|
|
703
|
+
requestAmount: requestAmount.toString(),
|
|
704
|
+
maxClaimableAmount,
|
|
705
|
+
currentBalance: currentBalance.toString(),
|
|
706
|
+
minimumDepositAmount: minimumDepositAmount.toString(),
|
|
707
|
+
depositAmount: computedDeposit
|
|
708
|
+
});
|
|
709
|
+
if (depositAmount === false) {
|
|
710
|
+
return this.createVoucherPayload(
|
|
711
|
+
x402Version,
|
|
712
|
+
channelId,
|
|
713
|
+
maxClaimableAmount,
|
|
714
|
+
paymentRequirements.network,
|
|
715
|
+
config
|
|
716
|
+
);
|
|
717
|
+
}
|
|
718
|
+
const assetTransferMethod = paymentRequirements.extra?.assetTransferMethod ?? "eip3009";
|
|
719
|
+
if (assetTransferMethod === "eip3009") {
|
|
720
|
+
return createBatchSettlementEIP3009DepositPayload(
|
|
721
|
+
this.signer,
|
|
722
|
+
x402Version,
|
|
723
|
+
paymentRequirements,
|
|
724
|
+
config,
|
|
725
|
+
depositAmount,
|
|
726
|
+
maxClaimableAmount,
|
|
727
|
+
this.voucherSigner
|
|
728
|
+
);
|
|
729
|
+
}
|
|
730
|
+
if (assetTransferMethod !== "permit2") {
|
|
731
|
+
throw new Error(`unsupported batch-settlement assetTransferMethod: ${assetTransferMethod}`);
|
|
732
|
+
}
|
|
733
|
+
const result = await createBatchSettlementPermit2DepositPayload(
|
|
734
|
+
this.signer,
|
|
735
|
+
x402Version,
|
|
736
|
+
paymentRequirements,
|
|
737
|
+
config,
|
|
738
|
+
depositAmount,
|
|
739
|
+
maxClaimableAmount,
|
|
740
|
+
this.voucherSigner
|
|
741
|
+
);
|
|
742
|
+
const eip2612Extensions = await trySignEip2612PermitExtension(
|
|
743
|
+
this.signer,
|
|
744
|
+
this.extensionRpcOptions,
|
|
745
|
+
paymentRequirements,
|
|
746
|
+
result,
|
|
747
|
+
context,
|
|
748
|
+
depositAmount
|
|
749
|
+
);
|
|
750
|
+
if (eip2612Extensions) {
|
|
751
|
+
return { ...result, extensions: eip2612Extensions };
|
|
752
|
+
}
|
|
753
|
+
const erc20Extensions = await trySignErc20ApprovalExtension(
|
|
754
|
+
this.signer,
|
|
755
|
+
this.extensionRpcOptions,
|
|
756
|
+
paymentRequirements,
|
|
757
|
+
context,
|
|
758
|
+
depositAmount
|
|
759
|
+
);
|
|
760
|
+
if (erc20Extensions) {
|
|
761
|
+
return { ...result, extensions: erc20Extensions };
|
|
762
|
+
}
|
|
763
|
+
return result;
|
|
764
|
+
}
|
|
765
|
+
return this.createVoucherPayload(
|
|
766
|
+
x402Version,
|
|
767
|
+
channelId,
|
|
768
|
+
maxClaimableAmount,
|
|
769
|
+
paymentRequirements.network,
|
|
770
|
+
config
|
|
771
|
+
);
|
|
772
|
+
}
|
|
773
|
+
/**
|
|
774
|
+
* Sends a cooperative refund request.
|
|
775
|
+
*
|
|
776
|
+
* @param url - The route URL backing the channel to refund.
|
|
777
|
+
* @param options - Optional `amount` (partial refund) and `fetch` override.
|
|
778
|
+
* @returns The settle response describing the refund outcome.
|
|
779
|
+
*/
|
|
780
|
+
async refund(url, options) {
|
|
781
|
+
return refundChannel(this.deps(), url, options);
|
|
782
|
+
}
|
|
783
|
+
/**
|
|
784
|
+
* Updates local channel state from a settle response.
|
|
785
|
+
*
|
|
786
|
+
* @param settle - The parsed settle response from the server.
|
|
787
|
+
* @returns Resolves when local channel state has been updated.
|
|
788
|
+
*/
|
|
789
|
+
async processSettleResponse(settle) {
|
|
790
|
+
return processSettleResponse(this.storage, settle);
|
|
791
|
+
}
|
|
792
|
+
/**
|
|
793
|
+
* Resyncs local channel state from a corrective 402 response.
|
|
794
|
+
*
|
|
795
|
+
* @param paymentRequired - The decoded 402 response body.
|
|
796
|
+
* @returns `true` if local state was successfully resynced and a retry is warranted.
|
|
797
|
+
*/
|
|
798
|
+
async processCorrectivePaymentRequired(paymentRequired) {
|
|
799
|
+
return processCorrectivePaymentRequired(this.deps(), paymentRequired);
|
|
800
|
+
}
|
|
801
|
+
/**
|
|
802
|
+
* Builds the immutable {@link ChannelConfig} for a given set of payment
|
|
803
|
+
* requirements, using the scheme's own signer and salt.
|
|
804
|
+
*
|
|
805
|
+
* @param paymentRequirements - Server payment requirements for the channel.
|
|
806
|
+
* @returns The channel config that uniquely identifies the payment channel.
|
|
807
|
+
*/
|
|
808
|
+
buildChannelConfig(paymentRequirements) {
|
|
809
|
+
return buildChannelConfig(this.deps(), paymentRequirements);
|
|
810
|
+
}
|
|
811
|
+
/**
|
|
812
|
+
* Resolves the deposit amount after applying the optional custom strategy.
|
|
813
|
+
*
|
|
814
|
+
* @param context - Deposit attempt context exposed to the strategy.
|
|
815
|
+
* @returns The deposit amount to sign, or `false` to skip this deposit attempt.
|
|
816
|
+
*/
|
|
817
|
+
async resolveDepositAmount(context) {
|
|
818
|
+
const strategyResult = await this.depositStrategy?.(context);
|
|
819
|
+
if (strategyResult === false) return false;
|
|
820
|
+
if (strategyResult === void 0) return context.depositAmount;
|
|
821
|
+
const depositAmount = this.normalizeStrategyDepositAmount(strategyResult);
|
|
822
|
+
if (BigInt(depositAmount) < BigInt(context.minimumDepositAmount)) {
|
|
823
|
+
throw new Error(
|
|
824
|
+
`depositStrategy returned ${depositAmount}, below required top-up ${context.minimumDepositAmount}`
|
|
825
|
+
);
|
|
826
|
+
}
|
|
827
|
+
return depositAmount;
|
|
828
|
+
}
|
|
829
|
+
/**
|
|
830
|
+
* Normalizes and validates a strategy-provided base-unit deposit amount.
|
|
831
|
+
*
|
|
832
|
+
* @param value - Strategy-provided string or bigint amount.
|
|
833
|
+
* @returns Normalized decimal string.
|
|
834
|
+
*/
|
|
835
|
+
normalizeStrategyDepositAmount(value) {
|
|
836
|
+
if (typeof value === "bigint") {
|
|
837
|
+
if (value <= 0n) {
|
|
838
|
+
throw new Error("depositStrategy must return a positive integer deposit amount");
|
|
839
|
+
}
|
|
840
|
+
return value.toString();
|
|
841
|
+
}
|
|
842
|
+
if (/^\d+$/.test(value) && BigInt(value) > 0n) {
|
|
843
|
+
return BigInt(value).toString();
|
|
844
|
+
}
|
|
845
|
+
throw new Error("depositStrategy must return a positive integer deposit amount");
|
|
846
|
+
}
|
|
847
|
+
/**
|
|
848
|
+
* Signs a voucher-only payment payload for the current channel.
|
|
849
|
+
*
|
|
850
|
+
* @param x402Version - Protocol version for the payload envelope.
|
|
851
|
+
* @param channelId - Channel identifier for the voucher.
|
|
852
|
+
* @param maxClaimableAmount - Cumulative ceiling for the voucher.
|
|
853
|
+
* @param network - CAIP-2 network identifier.
|
|
854
|
+
* @param config - Immutable channel configuration.
|
|
855
|
+
* @returns Voucher-only payment payload.
|
|
856
|
+
*/
|
|
857
|
+
async createVoucherPayload(x402Version, channelId, maxClaimableAmount, network, config) {
|
|
858
|
+
const voucherSigner = this.voucherSigner ?? this.signer;
|
|
859
|
+
const voucher = await signVoucher(voucherSigner, channelId, maxClaimableAmount, network);
|
|
860
|
+
const payload = {
|
|
861
|
+
type: "voucher",
|
|
862
|
+
channelConfig: config,
|
|
863
|
+
voucher
|
|
864
|
+
};
|
|
865
|
+
return {
|
|
866
|
+
x402Version,
|
|
867
|
+
payload
|
|
868
|
+
};
|
|
869
|
+
}
|
|
870
|
+
/**
|
|
871
|
+
* Bundles the class state into the {@link BatchSettlementClientDeps} shape
|
|
872
|
+
* consumed by the `channel`, `recovery`, and `refund` modules.
|
|
873
|
+
*
|
|
874
|
+
* @returns Client deps wrapping the scheme's own signer and storage.
|
|
875
|
+
*/
|
|
876
|
+
deps() {
|
|
877
|
+
return {
|
|
878
|
+
signer: this.signer,
|
|
879
|
+
storage: this.storage,
|
|
880
|
+
salt: this.salt,
|
|
881
|
+
payerAuthorizer: this.payerAuthorizer,
|
|
882
|
+
voucherSigner: this.voucherSigner
|
|
883
|
+
};
|
|
884
|
+
}
|
|
885
|
+
};
|
|
886
|
+
|
|
887
|
+
export {
|
|
888
|
+
signVoucher,
|
|
889
|
+
createBatchSettlementEIP3009DepositPayload,
|
|
890
|
+
InMemoryClientChannelStorage,
|
|
891
|
+
isBatchSettlementEvmSchemeOptions,
|
|
892
|
+
resolveClientOptions,
|
|
893
|
+
validateDepositPolicy,
|
|
894
|
+
depositAmountForRequest,
|
|
895
|
+
buildChannelConfig,
|
|
896
|
+
processSettleResponse,
|
|
897
|
+
updateChannelAfterRefund,
|
|
898
|
+
processPaymentResponse,
|
|
899
|
+
recoverChannel,
|
|
900
|
+
readChannelBalanceAndTotalClaimed,
|
|
901
|
+
hasChannel,
|
|
902
|
+
getChannel,
|
|
903
|
+
processCorrectivePaymentRequired,
|
|
904
|
+
recoverFromSignature,
|
|
905
|
+
recoverFromOnChainState,
|
|
906
|
+
createBatchSettlementClientHooks,
|
|
907
|
+
handleBatchSettlementPaymentResponse,
|
|
908
|
+
refundChannel,
|
|
909
|
+
BatchSettlementEvmScheme
|
|
910
|
+
};
|
|
911
|
+
//# sourceMappingURL=chunk-NKYVYGRA.mjs.map
|