@x402r/refund 0.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +139 -0
- package/dist/cjs/index.d.ts +253 -0
- package/dist/cjs/index.js +713 -0
- package/dist/cjs/index.js.map +1 -0
- package/dist/esm/index.d.mts +253 -0
- package/dist/esm/index.mjs +678 -0
- package/dist/esm/index.mjs.map +1 -0
- package/package.json +68 -0
- package/src/facilitator.ts +778 -0
- package/src/index.ts +119 -0
- package/src/server/computeRelayAddress.ts +73 -0
- package/src/server.ts +334 -0
- package/src/types.ts +74 -0
|
@@ -0,0 +1,713 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/index.ts
|
|
21
|
+
var src_exports = {};
|
|
22
|
+
__export(src_exports, {
|
|
23
|
+
REFUND_EXTENSION_KEY: () => REFUND_EXTENSION_KEY,
|
|
24
|
+
REFUND_MARKER_KEY: () => REFUND_MARKER_KEY,
|
|
25
|
+
computeRelayAddress: () => computeRelayAddress,
|
|
26
|
+
declareRefundExtension: () => declareRefundExtension,
|
|
27
|
+
extractRefundInfo: () => extractRefundInfo,
|
|
28
|
+
isRefundableOption: () => isRefundableOption,
|
|
29
|
+
refundable: () => refundable,
|
|
30
|
+
settleWithRefundHelper: () => settleWithRefundHelper,
|
|
31
|
+
withRefund: () => withRefund
|
|
32
|
+
});
|
|
33
|
+
module.exports = __toCommonJS(src_exports);
|
|
34
|
+
|
|
35
|
+
// src/types.ts
|
|
36
|
+
var REFUND_EXTENSION_KEY = "refund";
|
|
37
|
+
var REFUND_MARKER_KEY = "_x402_refund";
|
|
38
|
+
function isRefundableOption(option) {
|
|
39
|
+
return option.extra !== void 0 && typeof option.extra === "object" && option.extra !== null && REFUND_MARKER_KEY in option.extra && option.extra[REFUND_MARKER_KEY] === true;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// src/server/computeRelayAddress.ts
|
|
43
|
+
var import_viem = require("viem");
|
|
44
|
+
var import_predict_deterministic_address = require("@whoislewys/predict-deterministic-address");
|
|
45
|
+
function computeRelayAddress(createxAddress, factoryAddress, merchantPayout) {
|
|
46
|
+
const createx = (0, import_viem.getAddress)(createxAddress);
|
|
47
|
+
const factory = (0, import_viem.getAddress)(factoryAddress);
|
|
48
|
+
const merchant = (0, import_viem.getAddress)(merchantPayout);
|
|
49
|
+
const salt = (0, import_viem.keccak256)((0, import_viem.encodePacked)(["address", "address"], [factory, merchant]));
|
|
50
|
+
const guardedSalt = (0, import_viem.keccak256)(
|
|
51
|
+
(0, import_viem.encodeAbiParameters)([{ type: "bytes32" }], [salt])
|
|
52
|
+
);
|
|
53
|
+
return (0, import_predict_deterministic_address.predictCreate3Address)(createx, guardedSalt);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// src/server.ts
|
|
57
|
+
function declareRefundExtension(factoryAddress, merchantPayouts) {
|
|
58
|
+
return {
|
|
59
|
+
[REFUND_EXTENSION_KEY]: {
|
|
60
|
+
info: {
|
|
61
|
+
factoryAddress,
|
|
62
|
+
merchantPayouts
|
|
63
|
+
},
|
|
64
|
+
schema: {
|
|
65
|
+
$schema: "https://json-schema.org/draft/2020-12/schema",
|
|
66
|
+
type: "object",
|
|
67
|
+
properties: {
|
|
68
|
+
factoryAddress: {
|
|
69
|
+
type: "string",
|
|
70
|
+
pattern: "^0x[a-fA-F0-9]{40}$",
|
|
71
|
+
description: "The X402DepositRelayFactory contract address"
|
|
72
|
+
},
|
|
73
|
+
merchantPayouts: {
|
|
74
|
+
type: "object",
|
|
75
|
+
additionalProperties: {
|
|
76
|
+
type: "string",
|
|
77
|
+
pattern: "^0x[a-fA-F0-9]{40}$"
|
|
78
|
+
},
|
|
79
|
+
description: "Map of proxy address to merchant payout address"
|
|
80
|
+
}
|
|
81
|
+
},
|
|
82
|
+
required: ["factoryAddress", "merchantPayouts"],
|
|
83
|
+
additionalProperties: false
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
function refundable(option) {
|
|
89
|
+
const clonedOption = {
|
|
90
|
+
...option,
|
|
91
|
+
extra: {
|
|
92
|
+
...option.extra
|
|
93
|
+
}
|
|
94
|
+
};
|
|
95
|
+
if (!clonedOption.extra) {
|
|
96
|
+
clonedOption.extra = {};
|
|
97
|
+
}
|
|
98
|
+
clonedOption.extra[REFUND_MARKER_KEY] = true;
|
|
99
|
+
return clonedOption;
|
|
100
|
+
}
|
|
101
|
+
var STANDARD_CREATEX_ADDRESSES = {
|
|
102
|
+
// Ethereum Mainnet
|
|
103
|
+
"eip155:1": "0xba5Ed099633D3B313e4D5F7bdc1305d3c32ba066",
|
|
104
|
+
// Base Mainnet
|
|
105
|
+
"eip155:8453": "0xba5Ed099633D3B313e4D5F7bdc1305d3c28ba5Ed",
|
|
106
|
+
// Base Sepolia
|
|
107
|
+
"eip155:84532": "0xba5Ed099633D3B313e4D5F7bdc1305d3c28ba5Ed"
|
|
108
|
+
// Add more networks as CreateX deployments become available
|
|
109
|
+
};
|
|
110
|
+
function withRefund(routes, factoryAddress, createxAddress) {
|
|
111
|
+
if (typeof routes === "object" && routes !== null && !("accepts" in routes)) {
|
|
112
|
+
const nestedRoutes = routes;
|
|
113
|
+
const processedRoutes = {};
|
|
114
|
+
for (const [pattern, config] of Object.entries(nestedRoutes)) {
|
|
115
|
+
processedRoutes[pattern] = processRouteConfig(config, factoryAddress, createxAddress);
|
|
116
|
+
}
|
|
117
|
+
return processedRoutes;
|
|
118
|
+
} else {
|
|
119
|
+
return processRouteConfig(routes, factoryAddress, createxAddress);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
function getCreateXAddress(network, providedAddress) {
|
|
123
|
+
if (providedAddress) {
|
|
124
|
+
return providedAddress;
|
|
125
|
+
}
|
|
126
|
+
const standardAddress = STANDARD_CREATEX_ADDRESSES[network];
|
|
127
|
+
if (standardAddress) {
|
|
128
|
+
return standardAddress;
|
|
129
|
+
}
|
|
130
|
+
throw new Error(
|
|
131
|
+
`CreateX address not provided and no standard address found for network ${network}. Please provide createxAddress parameter or check if CreateX is deployed on this network. See https://github.com/pcaversaccio/createx#createx-deployments for standard deployments.`
|
|
132
|
+
);
|
|
133
|
+
}
|
|
134
|
+
function processRouteConfig(config, factoryAddress, createxAddress) {
|
|
135
|
+
const firstOption = Array.isArray(config.accepts) ? config.accepts[0] : config.accepts;
|
|
136
|
+
const network = firstOption?.network;
|
|
137
|
+
if (!network) {
|
|
138
|
+
throw new Error("Payment option must have a network field to determine CreateX address");
|
|
139
|
+
}
|
|
140
|
+
const resolvedCreatexAddress = getCreateXAddress(network, createxAddress);
|
|
141
|
+
const hasRefundable = Array.isArray(config.accepts) ? config.accepts.some(isRefundableOption) : isRefundableOption(config.accepts);
|
|
142
|
+
const merchantPayoutsMap = {};
|
|
143
|
+
if (hasRefundable) {
|
|
144
|
+
const refundableOptions = Array.isArray(config.accepts) ? config.accepts.filter(isRefundableOption) : isRefundableOption(config.accepts) ? [config.accepts] : [];
|
|
145
|
+
for (const option of refundableOptions) {
|
|
146
|
+
if (typeof option.payTo === "string") {
|
|
147
|
+
const merchantPayout = option.payTo;
|
|
148
|
+
const proxyAddress = computeRelayAddress(
|
|
149
|
+
resolvedCreatexAddress,
|
|
150
|
+
factoryAddress,
|
|
151
|
+
merchantPayout
|
|
152
|
+
);
|
|
153
|
+
merchantPayoutsMap[proxyAddress.toLowerCase()] = merchantPayout;
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
const processedConfig = {
|
|
158
|
+
...config,
|
|
159
|
+
accepts: Array.isArray(config.accepts) ? config.accepts.map(
|
|
160
|
+
(option) => processPaymentOption(option, factoryAddress, resolvedCreatexAddress)
|
|
161
|
+
) : processPaymentOption(config.accepts, factoryAddress, resolvedCreatexAddress),
|
|
162
|
+
extensions: {
|
|
163
|
+
...config.extensions
|
|
164
|
+
}
|
|
165
|
+
};
|
|
166
|
+
if (hasRefundable && Object.keys(merchantPayoutsMap).length > 0) {
|
|
167
|
+
processedConfig.extensions = {
|
|
168
|
+
...processedConfig.extensions,
|
|
169
|
+
...declareRefundExtension(factoryAddress, merchantPayoutsMap)
|
|
170
|
+
};
|
|
171
|
+
} else if (hasRefundable) {
|
|
172
|
+
throw new Error(
|
|
173
|
+
"Refundable option must have a string payTo address. DynamicPayTo is not supported for refundable options."
|
|
174
|
+
);
|
|
175
|
+
}
|
|
176
|
+
return processedConfig;
|
|
177
|
+
}
|
|
178
|
+
function processPaymentOption(option, factoryAddress, createxAddress) {
|
|
179
|
+
if (isRefundableOption(option)) {
|
|
180
|
+
const merchantPayout = option.payTo;
|
|
181
|
+
if (typeof merchantPayout !== "string") {
|
|
182
|
+
throw new Error(
|
|
183
|
+
"DynamicPayTo is not supported for refundable options. Use a static address."
|
|
184
|
+
);
|
|
185
|
+
}
|
|
186
|
+
const proxyAddress = computeRelayAddress(createxAddress, factoryAddress, merchantPayout);
|
|
187
|
+
const processedOption = {
|
|
188
|
+
...option,
|
|
189
|
+
payTo: proxyAddress,
|
|
190
|
+
// Set payTo to proxy address
|
|
191
|
+
extra: {
|
|
192
|
+
...option.extra
|
|
193
|
+
}
|
|
194
|
+
};
|
|
195
|
+
if (processedOption.extra) {
|
|
196
|
+
delete processedOption.extra[REFUND_MARKER_KEY];
|
|
197
|
+
}
|
|
198
|
+
return processedOption;
|
|
199
|
+
}
|
|
200
|
+
return { ...option, extra: { ...option.extra } };
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
// src/facilitator.ts
|
|
204
|
+
var import_viem2 = require("viem");
|
|
205
|
+
function isRateLimitError(error) {
|
|
206
|
+
if (error && typeof error === "object") {
|
|
207
|
+
const err = error;
|
|
208
|
+
if (err.status === 429) {
|
|
209
|
+
return true;
|
|
210
|
+
}
|
|
211
|
+
const message = err.message || err.details || "";
|
|
212
|
+
if (typeof message === "string" && message.toLowerCase().includes("rate limit")) {
|
|
213
|
+
return true;
|
|
214
|
+
}
|
|
215
|
+
if (err.cause && isRateLimitError(err.cause)) {
|
|
216
|
+
return true;
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
return false;
|
|
220
|
+
}
|
|
221
|
+
async function readContractWithRetry(signer, args, maxRetries = 5) {
|
|
222
|
+
let lastError;
|
|
223
|
+
for (let attempt = 0; attempt <= maxRetries; attempt++) {
|
|
224
|
+
try {
|
|
225
|
+
return await signer.readContract(args);
|
|
226
|
+
} catch (error) {
|
|
227
|
+
lastError = error;
|
|
228
|
+
if (!isRateLimitError(error)) {
|
|
229
|
+
throw error;
|
|
230
|
+
}
|
|
231
|
+
if (attempt >= maxRetries) {
|
|
232
|
+
break;
|
|
233
|
+
}
|
|
234
|
+
const delayMs = Math.min(1e3 * Math.pow(2, attempt), 16e3);
|
|
235
|
+
await new Promise((resolve) => setTimeout(resolve, delayMs));
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
throw lastError;
|
|
239
|
+
}
|
|
240
|
+
var FACTORY_ABI = [
|
|
241
|
+
{
|
|
242
|
+
name: "getMerchantFromRelay",
|
|
243
|
+
type: "function",
|
|
244
|
+
stateMutability: "view",
|
|
245
|
+
inputs: [{ name: "relayAddress", type: "address" }],
|
|
246
|
+
outputs: [{ name: "", type: "address" }]
|
|
247
|
+
},
|
|
248
|
+
{
|
|
249
|
+
name: "getRelayAddress",
|
|
250
|
+
type: "function",
|
|
251
|
+
stateMutability: "view",
|
|
252
|
+
inputs: [{ name: "merchantPayout", type: "address" }],
|
|
253
|
+
outputs: [{ name: "", type: "address" }]
|
|
254
|
+
},
|
|
255
|
+
{
|
|
256
|
+
name: "deployRelay",
|
|
257
|
+
type: "function",
|
|
258
|
+
stateMutability: "nonpayable",
|
|
259
|
+
inputs: [{ name: "merchantPayout", type: "address" }],
|
|
260
|
+
outputs: [{ name: "", type: "address" }]
|
|
261
|
+
},
|
|
262
|
+
{
|
|
263
|
+
name: "getCreateX",
|
|
264
|
+
type: "function",
|
|
265
|
+
stateMutability: "view",
|
|
266
|
+
inputs: [],
|
|
267
|
+
outputs: [{ name: "", type: "address" }]
|
|
268
|
+
}
|
|
269
|
+
];
|
|
270
|
+
var ESCROW_ABI = [
|
|
271
|
+
{
|
|
272
|
+
name: "registerMerchant",
|
|
273
|
+
type: "function",
|
|
274
|
+
stateMutability: "nonpayable",
|
|
275
|
+
inputs: [
|
|
276
|
+
{ name: "merchantPayout", type: "address" },
|
|
277
|
+
{ name: "arbiter", type: "address" }
|
|
278
|
+
],
|
|
279
|
+
outputs: []
|
|
280
|
+
},
|
|
281
|
+
{
|
|
282
|
+
name: "registeredMerchants",
|
|
283
|
+
type: "function",
|
|
284
|
+
stateMutability: "view",
|
|
285
|
+
inputs: [{ name: "merchantPayout", type: "address" }],
|
|
286
|
+
outputs: [{ name: "", type: "bool" }]
|
|
287
|
+
},
|
|
288
|
+
{
|
|
289
|
+
name: "merchantArbiters",
|
|
290
|
+
type: "function",
|
|
291
|
+
stateMutability: "view",
|
|
292
|
+
inputs: [{ name: "merchantPayout", type: "address" }],
|
|
293
|
+
outputs: [{ name: "", type: "address" }]
|
|
294
|
+
},
|
|
295
|
+
{
|
|
296
|
+
name: "getArbiter",
|
|
297
|
+
type: "function",
|
|
298
|
+
stateMutability: "view",
|
|
299
|
+
inputs: [{ name: "merchantPayout", type: "address" }],
|
|
300
|
+
outputs: [{ name: "", type: "address" }]
|
|
301
|
+
},
|
|
302
|
+
{
|
|
303
|
+
name: "noteDeposit",
|
|
304
|
+
type: "function",
|
|
305
|
+
stateMutability: "nonpayable",
|
|
306
|
+
inputs: [
|
|
307
|
+
{ name: "user", type: "address" },
|
|
308
|
+
{ name: "merchantPayout", type: "address" },
|
|
309
|
+
{ name: "amount", type: "uint256" }
|
|
310
|
+
],
|
|
311
|
+
outputs: [{ name: "", type: "uint256" }]
|
|
312
|
+
},
|
|
313
|
+
{
|
|
314
|
+
name: "release",
|
|
315
|
+
type: "function",
|
|
316
|
+
stateMutability: "nonpayable",
|
|
317
|
+
inputs: [
|
|
318
|
+
{ name: "user", type: "address" },
|
|
319
|
+
{ name: "depositNonce", type: "uint256" }
|
|
320
|
+
],
|
|
321
|
+
outputs: []
|
|
322
|
+
},
|
|
323
|
+
{
|
|
324
|
+
name: "refund",
|
|
325
|
+
type: "function",
|
|
326
|
+
stateMutability: "nonpayable",
|
|
327
|
+
inputs: [
|
|
328
|
+
{ name: "user", type: "address" },
|
|
329
|
+
{ name: "depositNonce", type: "uint256" }
|
|
330
|
+
],
|
|
331
|
+
outputs: []
|
|
332
|
+
},
|
|
333
|
+
{
|
|
334
|
+
name: "deposits",
|
|
335
|
+
type: "function",
|
|
336
|
+
stateMutability: "view",
|
|
337
|
+
inputs: [
|
|
338
|
+
{ name: "user", type: "address" },
|
|
339
|
+
{ name: "depositNonce", type: "uint256" }
|
|
340
|
+
],
|
|
341
|
+
outputs: [
|
|
342
|
+
{ name: "principal", type: "uint256" },
|
|
343
|
+
{ name: "timestamp", type: "uint256" },
|
|
344
|
+
{ name: "nonce", type: "uint256" },
|
|
345
|
+
{ name: "merchantPayout", type: "address" }
|
|
346
|
+
]
|
|
347
|
+
}
|
|
348
|
+
];
|
|
349
|
+
var RELAY_PROXY_ABI = [
|
|
350
|
+
{
|
|
351
|
+
name: "executeDeposit",
|
|
352
|
+
type: "function",
|
|
353
|
+
stateMutability: "nonpayable",
|
|
354
|
+
inputs: [
|
|
355
|
+
{ name: "fromUser", type: "address" },
|
|
356
|
+
{ name: "amount", type: "uint256" },
|
|
357
|
+
{ name: "validAfter", type: "uint256" },
|
|
358
|
+
{ name: "validBefore", type: "uint256" },
|
|
359
|
+
{ name: "nonce", type: "bytes32" },
|
|
360
|
+
{ name: "v", type: "uint8" },
|
|
361
|
+
{ name: "r", type: "bytes32" },
|
|
362
|
+
{ name: "s", type: "bytes32" }
|
|
363
|
+
],
|
|
364
|
+
outputs: []
|
|
365
|
+
},
|
|
366
|
+
{
|
|
367
|
+
name: "MERCHANT_PAYOUT",
|
|
368
|
+
type: "function",
|
|
369
|
+
stateMutability: "view",
|
|
370
|
+
inputs: [],
|
|
371
|
+
outputs: [{ name: "", type: "address" }]
|
|
372
|
+
},
|
|
373
|
+
{
|
|
374
|
+
name: "TOKEN",
|
|
375
|
+
type: "function",
|
|
376
|
+
stateMutability: "view",
|
|
377
|
+
inputs: [],
|
|
378
|
+
outputs: [{ name: "", type: "address" }]
|
|
379
|
+
},
|
|
380
|
+
{
|
|
381
|
+
name: "ESCROW",
|
|
382
|
+
type: "function",
|
|
383
|
+
stateMutability: "view",
|
|
384
|
+
inputs: [],
|
|
385
|
+
outputs: [{ name: "", type: "address" }]
|
|
386
|
+
}
|
|
387
|
+
];
|
|
388
|
+
function extractRefundInfo(paymentPayload, _) {
|
|
389
|
+
const extension = paymentPayload.extensions?.[REFUND_EXTENSION_KEY];
|
|
390
|
+
if (!extension || !extension.info || !extension.info.factoryAddress) {
|
|
391
|
+
return null;
|
|
392
|
+
}
|
|
393
|
+
const factoryAddress = extension.info.factoryAddress;
|
|
394
|
+
const merchantPayouts = extension.info.merchantPayouts || {};
|
|
395
|
+
if (!(0, import_viem2.isAddress)(factoryAddress)) {
|
|
396
|
+
return null;
|
|
397
|
+
}
|
|
398
|
+
return { factoryAddress, merchantPayouts };
|
|
399
|
+
}
|
|
400
|
+
async function settleWithRefundHelper(paymentPayload, paymentRequirements, signer) {
|
|
401
|
+
const refundInfo = extractRefundInfo(paymentPayload, paymentRequirements);
|
|
402
|
+
if (!refundInfo) {
|
|
403
|
+
return null;
|
|
404
|
+
}
|
|
405
|
+
const factoryAddress = refundInfo.factoryAddress;
|
|
406
|
+
const merchantPayouts = refundInfo.merchantPayouts;
|
|
407
|
+
try {
|
|
408
|
+
const factoryCode = await signer.getCode({ address: (0, import_viem2.getAddress)(factoryAddress) });
|
|
409
|
+
if (!factoryCode || factoryCode === "0x" || factoryCode.length <= 2) {
|
|
410
|
+
throw new Error(
|
|
411
|
+
`Factory contract does not exist at ${factoryAddress}. Invalid refund extension.`
|
|
412
|
+
);
|
|
413
|
+
}
|
|
414
|
+
} catch (error2) {
|
|
415
|
+
throw new Error(
|
|
416
|
+
`Failed to check factory contract: ${error2 instanceof Error ? error2.message : String(error2)}`
|
|
417
|
+
);
|
|
418
|
+
}
|
|
419
|
+
const proxyAddress = (0, import_viem2.getAddress)(paymentRequirements.payTo);
|
|
420
|
+
const relayCode = await signer.getCode({ address: proxyAddress });
|
|
421
|
+
const relayExists = relayCode && relayCode !== "0x" && relayCode.length > 2;
|
|
422
|
+
let merchantPayout;
|
|
423
|
+
let escrowAddress;
|
|
424
|
+
if (relayExists) {
|
|
425
|
+
try {
|
|
426
|
+
merchantPayout = await readContractWithRetry(signer, {
|
|
427
|
+
address: proxyAddress,
|
|
428
|
+
abi: RELAY_PROXY_ABI,
|
|
429
|
+
functionName: "MERCHANT_PAYOUT",
|
|
430
|
+
args: []
|
|
431
|
+
});
|
|
432
|
+
escrowAddress = await readContractWithRetry(signer, {
|
|
433
|
+
address: proxyAddress,
|
|
434
|
+
abi: RELAY_PROXY_ABI,
|
|
435
|
+
functionName: "ESCROW",
|
|
436
|
+
args: []
|
|
437
|
+
});
|
|
438
|
+
} catch (error2) {
|
|
439
|
+
return null;
|
|
440
|
+
}
|
|
441
|
+
} else {
|
|
442
|
+
const proxyAddressLower = proxyAddress.toLowerCase();
|
|
443
|
+
merchantPayout = merchantPayouts[proxyAddress] || merchantPayouts[proxyAddressLower];
|
|
444
|
+
if (!merchantPayout || merchantPayout === import_viem2.zeroAddress) {
|
|
445
|
+
return null;
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
if (!merchantPayout || merchantPayout === import_viem2.zeroAddress) {
|
|
449
|
+
return null;
|
|
450
|
+
}
|
|
451
|
+
if (!relayExists) {
|
|
452
|
+
try {
|
|
453
|
+
const expectedAddress = await readContractWithRetry(signer, {
|
|
454
|
+
address: (0, import_viem2.getAddress)(factoryAddress),
|
|
455
|
+
abi: FACTORY_ABI,
|
|
456
|
+
functionName: "getRelayAddress",
|
|
457
|
+
args: [(0, import_viem2.getAddress)(merchantPayout)]
|
|
458
|
+
});
|
|
459
|
+
if (expectedAddress.toLowerCase() !== proxyAddress.toLowerCase()) {
|
|
460
|
+
throw new Error(
|
|
461
|
+
`Address mismatch: Factory computed ${expectedAddress} but expected ${proxyAddress}. This may indicate a version or CreateX address mismatch.`
|
|
462
|
+
);
|
|
463
|
+
}
|
|
464
|
+
const txHash = await signer.writeContract({
|
|
465
|
+
address: (0, import_viem2.getAddress)(factoryAddress),
|
|
466
|
+
abi: FACTORY_ABI,
|
|
467
|
+
functionName: "deployRelay",
|
|
468
|
+
args: [(0, import_viem2.getAddress)(merchantPayout)]
|
|
469
|
+
});
|
|
470
|
+
const receipt = await signer.waitForTransactionReceipt({ hash: txHash });
|
|
471
|
+
if (receipt.status !== "success") {
|
|
472
|
+
throw new Error(`Relay deployment transaction failed: ${txHash}. Transaction reverted.`);
|
|
473
|
+
}
|
|
474
|
+
let deployedCode;
|
|
475
|
+
for (let i = 0; i < 5; i++) {
|
|
476
|
+
if (i > 0) {
|
|
477
|
+
const delay = 1e3 * i;
|
|
478
|
+
await new Promise((resolve) => setTimeout(resolve, delay));
|
|
479
|
+
}
|
|
480
|
+
deployedCode = await signer.getCode({ address: proxyAddress });
|
|
481
|
+
if (deployedCode && deployedCode !== "0x" && deployedCode.length > 2) {
|
|
482
|
+
break;
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
if (!deployedCode || deployedCode === "0x" || deployedCode.length <= 2) {
|
|
486
|
+
const actualAddress = await readContractWithRetry(signer, {
|
|
487
|
+
address: (0, import_viem2.getAddress)(factoryAddress),
|
|
488
|
+
abi: FACTORY_ABI,
|
|
489
|
+
functionName: "getRelayAddress",
|
|
490
|
+
args: [(0, import_viem2.getAddress)(merchantPayout)]
|
|
491
|
+
});
|
|
492
|
+
throw new Error(
|
|
493
|
+
`Relay deployment completed but contract code not found at ${proxyAddress}. Transaction hash: ${txHash}. Factory computed address: ${actualAddress}. Expected address: ${proxyAddress}. This may indicate a CREATE3 deployment issue, timing problem, or address computation mismatch.`
|
|
494
|
+
);
|
|
495
|
+
}
|
|
496
|
+
escrowAddress = await readContractWithRetry(signer, {
|
|
497
|
+
address: proxyAddress,
|
|
498
|
+
abi: RELAY_PROXY_ABI,
|
|
499
|
+
functionName: "ESCROW",
|
|
500
|
+
args: []
|
|
501
|
+
});
|
|
502
|
+
} catch (error2) {
|
|
503
|
+
const errorMessage2 = error2 instanceof Error ? error2.message : String(error2);
|
|
504
|
+
const errorString = errorMessage2.toLowerCase();
|
|
505
|
+
if (errorString.includes("insufficient funds") || errorString.includes("exceeds the balance") || errorString.includes("insufficient balance") || errorString.includes("the total cost") || errorString.includes("exceeds the balance of the account")) {
|
|
506
|
+
const facilitatorAddress = signer.getAddresses()[0];
|
|
507
|
+
throw new Error(
|
|
508
|
+
`Failed to deploy relay: Insufficient funds in facilitator account.
|
|
509
|
+
The facilitator account (${facilitatorAddress}) does not have enough ETH to pay for gas to deploy the relay contract.
|
|
510
|
+
Please fund the facilitator account with ETH to cover gas costs.
|
|
511
|
+
Original error: ${errorMessage2}`
|
|
512
|
+
);
|
|
513
|
+
}
|
|
514
|
+
throw new Error(
|
|
515
|
+
`Failed to deploy relay: ${errorMessage2}`
|
|
516
|
+
);
|
|
517
|
+
}
|
|
518
|
+
}
|
|
519
|
+
if (!escrowAddress) {
|
|
520
|
+
throw new Error("Internal error: escrowAddress not set after deployment check");
|
|
521
|
+
}
|
|
522
|
+
let isRegistered;
|
|
523
|
+
try {
|
|
524
|
+
isRegistered = await readContractWithRetry(signer, {
|
|
525
|
+
address: (0, import_viem2.getAddress)(escrowAddress),
|
|
526
|
+
abi: ESCROW_ABI,
|
|
527
|
+
functionName: "registeredMerchants",
|
|
528
|
+
args: [(0, import_viem2.getAddress)(merchantPayout)]
|
|
529
|
+
});
|
|
530
|
+
} catch (error2) {
|
|
531
|
+
throw new Error(
|
|
532
|
+
`Failed to check merchant registration: ${error2 instanceof Error ? error2.message : String(error2)}`
|
|
533
|
+
);
|
|
534
|
+
}
|
|
535
|
+
if (!isRegistered) {
|
|
536
|
+
throw new Error(
|
|
537
|
+
`Merchant ${merchantPayout} is not registered. Please register at https://app.402r.org to enable refund functionality.`
|
|
538
|
+
);
|
|
539
|
+
}
|
|
540
|
+
const payload = paymentPayload.payload;
|
|
541
|
+
if (!payload.authorization || !payload.signature) {
|
|
542
|
+
return null;
|
|
543
|
+
}
|
|
544
|
+
const { authorization, signature } = payload;
|
|
545
|
+
const authTo = (0, import_viem2.getAddress)(authorization.to);
|
|
546
|
+
if (authTo !== proxyAddress) {
|
|
547
|
+
throw new Error(
|
|
548
|
+
`Authorization 'to' address (${authTo}) does not match proxy address (${proxyAddress}). The ERC3009 signature must be signed with to=proxyAddress.`
|
|
549
|
+
);
|
|
550
|
+
}
|
|
551
|
+
try {
|
|
552
|
+
const proxyToken = await readContractWithRetry(signer, {
|
|
553
|
+
address: proxyAddress,
|
|
554
|
+
abi: RELAY_PROXY_ABI,
|
|
555
|
+
functionName: "TOKEN",
|
|
556
|
+
args: []
|
|
557
|
+
});
|
|
558
|
+
const proxyTokenNormalized = (0, import_viem2.getAddress)(proxyToken);
|
|
559
|
+
const paymentAssetNormalized = (0, import_viem2.getAddress)(paymentRequirements.asset);
|
|
560
|
+
if (proxyTokenNormalized !== paymentAssetNormalized) {
|
|
561
|
+
const errorMsg = `\u274C ADDRESS MISMATCH: Proxy has ${proxyTokenNormalized} but payment requires ${paymentAssetNormalized}. The proxy was deployed with the wrong address. This causes transferWithAuthorization to fail. Solution: Redeploy the proxy with the correct address: ${paymentAssetNormalized}`;
|
|
562
|
+
throw new Error(errorMsg);
|
|
563
|
+
}
|
|
564
|
+
const proxyEscrow = await readContractWithRetry(signer, {
|
|
565
|
+
address: proxyAddress,
|
|
566
|
+
abi: RELAY_PROXY_ABI,
|
|
567
|
+
functionName: "ESCROW",
|
|
568
|
+
args: []
|
|
569
|
+
});
|
|
570
|
+
if (proxyEscrow.toLowerCase() !== escrowAddress.toLowerCase()) {
|
|
571
|
+
throw new Error(
|
|
572
|
+
`Proxy ESCROW mismatch: proxy reports ${proxyEscrow} but we read ${escrowAddress} earlier`
|
|
573
|
+
);
|
|
574
|
+
}
|
|
575
|
+
} catch (error2) {
|
|
576
|
+
throw new Error(
|
|
577
|
+
`Failed to read proxy immutables. This may indicate a proxy deployment issue. Error: ${error2 instanceof Error ? error2.message : String(error2)}`
|
|
578
|
+
);
|
|
579
|
+
}
|
|
580
|
+
let parsedSignature;
|
|
581
|
+
try {
|
|
582
|
+
const erc6492Result = (0, import_viem2.parseErc6492Signature)(signature);
|
|
583
|
+
parsedSignature = erc6492Result.signature;
|
|
584
|
+
} catch {
|
|
585
|
+
parsedSignature = signature;
|
|
586
|
+
}
|
|
587
|
+
const signatureLength = parsedSignature.startsWith("0x") ? parsedSignature.length - 2 : parsedSignature.length;
|
|
588
|
+
const isECDSA = signatureLength === 130;
|
|
589
|
+
if (!isECDSA) {
|
|
590
|
+
return null;
|
|
591
|
+
}
|
|
592
|
+
const parsedSig = (0, import_viem2.parseSignature)(parsedSignature);
|
|
593
|
+
const v = parsedSig.v ?? parsedSig.yParity ?? 0;
|
|
594
|
+
const r = parsedSig.r;
|
|
595
|
+
const s = parsedSig.s;
|
|
596
|
+
try {
|
|
597
|
+
const tokenAddress = (0, import_viem2.getAddress)(paymentRequirements.asset);
|
|
598
|
+
const nonceUsed = await readContractWithRetry(signer, {
|
|
599
|
+
address: tokenAddress,
|
|
600
|
+
abi: [
|
|
601
|
+
{
|
|
602
|
+
inputs: [
|
|
603
|
+
{ name: "authorizer", type: "address" },
|
|
604
|
+
{ name: "nonce", type: "bytes32" }
|
|
605
|
+
],
|
|
606
|
+
name: "authorizationState",
|
|
607
|
+
outputs: [{ name: "", type: "bool" }],
|
|
608
|
+
stateMutability: "view",
|
|
609
|
+
type: "function"
|
|
610
|
+
}
|
|
611
|
+
],
|
|
612
|
+
functionName: "authorizationState",
|
|
613
|
+
args: [(0, import_viem2.getAddress)(authorization.from), authorization.nonce]
|
|
614
|
+
});
|
|
615
|
+
if (nonceUsed) {
|
|
616
|
+
throw new Error(
|
|
617
|
+
`ERC3009 nonce ${authorization.nonce} has already been used. This authorization cannot be reused.`
|
|
618
|
+
);
|
|
619
|
+
}
|
|
620
|
+
} catch (error2) {
|
|
621
|
+
}
|
|
622
|
+
let lastError;
|
|
623
|
+
const maxRetries = 5;
|
|
624
|
+
for (let attempt = 0; attempt <= maxRetries; attempt++) {
|
|
625
|
+
try {
|
|
626
|
+
const txHash = await signer.writeContract({
|
|
627
|
+
address: proxyAddress,
|
|
628
|
+
abi: RELAY_PROXY_ABI,
|
|
629
|
+
functionName: "executeDeposit",
|
|
630
|
+
args: [
|
|
631
|
+
(0, import_viem2.getAddress)(authorization.from),
|
|
632
|
+
BigInt(authorization.value),
|
|
633
|
+
BigInt(authorization.validAfter),
|
|
634
|
+
BigInt(authorization.validBefore),
|
|
635
|
+
authorization.nonce,
|
|
636
|
+
v,
|
|
637
|
+
r,
|
|
638
|
+
s
|
|
639
|
+
]
|
|
640
|
+
});
|
|
641
|
+
const receipt = await signer.waitForTransactionReceipt({ hash: txHash });
|
|
642
|
+
if (receipt.status !== "success") {
|
|
643
|
+
throw new Error(`Proxy.executeDeposit transaction failed: ${txHash}`);
|
|
644
|
+
}
|
|
645
|
+
return {
|
|
646
|
+
success: true,
|
|
647
|
+
transaction: txHash,
|
|
648
|
+
network: paymentRequirements.network,
|
|
649
|
+
payer: authorization.from
|
|
650
|
+
};
|
|
651
|
+
} catch (error2) {
|
|
652
|
+
lastError = error2;
|
|
653
|
+
if (attempt >= maxRetries) {
|
|
654
|
+
break;
|
|
655
|
+
}
|
|
656
|
+
const delayMs = (attempt + 1) * 1e3;
|
|
657
|
+
await new Promise((resolve) => setTimeout(resolve, delayMs));
|
|
658
|
+
}
|
|
659
|
+
}
|
|
660
|
+
const error = lastError;
|
|
661
|
+
let errorMessage = error instanceof Error ? error.message : String(error);
|
|
662
|
+
let revertReason;
|
|
663
|
+
if (error && typeof error === "object") {
|
|
664
|
+
const errorObj = error;
|
|
665
|
+
if (errorObj.cause && typeof errorObj.cause === "object") {
|
|
666
|
+
const cause = errorObj.cause;
|
|
667
|
+
if (typeof cause.reason === "string") {
|
|
668
|
+
revertReason = cause.reason;
|
|
669
|
+
}
|
|
670
|
+
if (cause.data && typeof cause.data === "string") {
|
|
671
|
+
if (cause.data.startsWith("0x08c379a0")) {
|
|
672
|
+
try {
|
|
673
|
+
const lengthHex = cause.data.slice(138, 202);
|
|
674
|
+
const length = parseInt(lengthHex, 16);
|
|
675
|
+
const stringHex = cause.data.slice(202, 202 + length * 2);
|
|
676
|
+
const decodedReason = Buffer.from(stringHex, "hex").toString("utf8").replace(/\0/g, "");
|
|
677
|
+
if (decodedReason) {
|
|
678
|
+
revertReason = decodedReason;
|
|
679
|
+
}
|
|
680
|
+
} catch (decodeErr) {
|
|
681
|
+
}
|
|
682
|
+
}
|
|
683
|
+
}
|
|
684
|
+
}
|
|
685
|
+
if (typeof errorObj.shortMessage === "string") {
|
|
686
|
+
if (!revertReason && errorObj.shortMessage.includes("reverted")) {
|
|
687
|
+
const match = errorObj.shortMessage.match(/reverted(?:[: ]+)?(.+)/i);
|
|
688
|
+
if (match && match[1]) {
|
|
689
|
+
revertReason = match[1].trim();
|
|
690
|
+
}
|
|
691
|
+
}
|
|
692
|
+
}
|
|
693
|
+
}
|
|
694
|
+
if (revertReason && revertReason !== "execution reverted") {
|
|
695
|
+
errorMessage = `Contract reverted: ${revertReason}`;
|
|
696
|
+
} else {
|
|
697
|
+
errorMessage = `Contract execution reverted (no specific revert reason available). All pre-checks passed: merchant registered, nonce unused, proxy immutables readable. Possible failure points in executeDeposit: 1) _readImmutable staticcall failing (unlikely - we can read immutables directly), 2) ERC3009 transferWithAuthorization failing when called through proxy (most likely), 3) Transfer to escrow failing, 4) Escrow.noteDeposit failing - POOL.supply() may be paused, asset not configured in Aave pool, or pool has restrictions. Check Aave pool status and asset configuration on Base Sepolia. Debugging: Use a transaction trace/debugger on the failed transaction to see exact revert point. The simulation succeeds, so the contract logic is correct - this is likely an execution context issue. Original error: ${errorMessage}`;
|
|
698
|
+
}
|
|
699
|
+
throw new Error(`Failed to execute proxy.executeDeposit: ${errorMessage}`);
|
|
700
|
+
}
|
|
701
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
702
|
+
0 && (module.exports = {
|
|
703
|
+
REFUND_EXTENSION_KEY,
|
|
704
|
+
REFUND_MARKER_KEY,
|
|
705
|
+
computeRelayAddress,
|
|
706
|
+
declareRefundExtension,
|
|
707
|
+
extractRefundInfo,
|
|
708
|
+
isRefundableOption,
|
|
709
|
+
refundable,
|
|
710
|
+
settleWithRefundHelper,
|
|
711
|
+
withRefund
|
|
712
|
+
});
|
|
713
|
+
//# sourceMappingURL=index.js.map
|