@x402/evm 2.6.0 → 2.8.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 +29 -4
- package/dist/cjs/exact/client/index.d.ts +12 -5
- package/dist/cjs/exact/client/index.js +127 -28
- package/dist/cjs/exact/client/index.js.map +1 -1
- package/dist/cjs/exact/facilitator/index.d.ts +13 -1
- package/dist/cjs/exact/facilitator/index.js +987 -606
- package/dist/cjs/exact/facilitator/index.js.map +1 -1
- package/dist/cjs/exact/v1/client/index.d.ts +1 -1
- package/dist/cjs/exact/v1/client/index.js +11 -5
- package/dist/cjs/exact/v1/client/index.js.map +1 -1
- package/dist/cjs/exact/v1/facilitator/index.d.ts +16 -1
- package/dist/cjs/exact/v1/facilitator/index.js +414 -177
- package/dist/cjs/exact/v1/facilitator/index.js.map +1 -1
- package/dist/cjs/index.d.ts +2 -2
- package/dist/cjs/index.js +143 -30
- package/dist/cjs/index.js.map +1 -1
- package/dist/cjs/{permit2-DHAq6FTe.d.ts → permit2-U9Zolx3O.d.ts} +38 -5
- package/dist/{esm/signer-DC81R8wQ.d.mts → cjs/signer-D912R4mq.d.ts} +9 -3
- package/dist/cjs/v1/index.d.ts +1 -1
- package/dist/cjs/v1/index.js +6 -0
- package/dist/cjs/v1/index.js.map +1 -1
- package/dist/esm/chunk-GD4MKCN7.mjs +57 -0
- package/dist/esm/chunk-GD4MKCN7.mjs.map +1 -0
- package/dist/esm/{chunk-XL6IFXCP.mjs → chunk-IZEI7JTG.mjs} +516 -178
- package/dist/esm/chunk-IZEI7JTG.mjs.map +1 -0
- package/dist/esm/{chunk-LBIJBD7Q.mjs → chunk-WJWNS4G4.mjs} +113 -20
- package/dist/esm/chunk-WJWNS4G4.mjs.map +1 -0
- package/dist/esm/exact/client/index.d.mts +12 -5
- package/dist/esm/exact/client/index.mjs +3 -2
- package/dist/esm/exact/facilitator/index.d.mts +13 -1
- package/dist/esm/exact/facilitator/index.mjs +498 -391
- package/dist/esm/exact/facilitator/index.mjs.map +1 -1
- package/dist/esm/exact/v1/client/index.d.mts +1 -1
- package/dist/esm/exact/v1/client/index.mjs +1 -1
- package/dist/esm/exact/v1/facilitator/index.d.mts +16 -1
- package/dist/esm/exact/v1/facilitator/index.mjs +1 -1
- package/dist/esm/index.d.mts +2 -2
- package/dist/esm/index.mjs +7 -9
- package/dist/esm/index.mjs.map +1 -1
- package/dist/esm/{permit2-BuAhWvNC.d.mts → permit2-Bbh3a8_h.d.mts} +38 -5
- package/dist/{cjs/signer-DC81R8wQ.d.ts → esm/signer-D912R4mq.d.mts} +9 -3
- package/dist/esm/v1/index.d.mts +1 -1
- package/dist/esm/v1/index.mjs +1 -1
- package/package.json +2 -3
- package/dist/esm/chunk-LBIJBD7Q.mjs.map +0 -1
- package/dist/esm/chunk-XL6IFXCP.mjs.map +0 -1
|
@@ -31,7 +31,7 @@ function isPermit2Payload(payload) {
|
|
|
31
31
|
}
|
|
32
32
|
|
|
33
33
|
// src/exact/facilitator/eip3009.ts
|
|
34
|
-
var
|
|
34
|
+
var import_viem4 = require("viem");
|
|
35
35
|
|
|
36
36
|
// src/constants.ts
|
|
37
37
|
var authorizationTypes = {
|
|
@@ -107,6 +107,23 @@ var eip3009ABI = [
|
|
|
107
107
|
outputs: [{ name: "", type: "string" }],
|
|
108
108
|
stateMutability: "view",
|
|
109
109
|
type: "function"
|
|
110
|
+
},
|
|
111
|
+
{
|
|
112
|
+
inputs: [],
|
|
113
|
+
name: "name",
|
|
114
|
+
outputs: [{ name: "", type: "string" }],
|
|
115
|
+
stateMutability: "view",
|
|
116
|
+
type: "function"
|
|
117
|
+
},
|
|
118
|
+
{
|
|
119
|
+
inputs: [
|
|
120
|
+
{ name: "authorizer", type: "address" },
|
|
121
|
+
{ name: "nonce", type: "bytes32" }
|
|
122
|
+
],
|
|
123
|
+
name: "authorizationState",
|
|
124
|
+
outputs: [{ name: "", type: "bool" }],
|
|
125
|
+
stateMutability: "view",
|
|
126
|
+
type: "function"
|
|
110
127
|
}
|
|
111
128
|
];
|
|
112
129
|
var erc20ApproveAbi = [
|
|
@@ -133,6 +150,8 @@ var erc20AllowanceAbi = [
|
|
|
133
150
|
stateMutability: "view"
|
|
134
151
|
}
|
|
135
152
|
];
|
|
153
|
+
var ERC20_APPROVE_GAS_LIMIT = 70000n;
|
|
154
|
+
var DEFAULT_MAX_FEE_PER_GAS = 1000000000n;
|
|
136
155
|
var PERMIT2_ADDRESS = "0x000000000022D473030F116dDEE9F6B43aC78BA3";
|
|
137
156
|
var x402ExactPermit2ProxyAddress = "0x402085c248EeA27D92E8b30b2C58ed07f9E20001";
|
|
138
157
|
var permit2WitnessABIComponents = [
|
|
@@ -266,29 +285,327 @@ function getEvmChainId(network) {
|
|
|
266
285
|
throw new Error(`Unsupported network format: ${network} (expected eip155:CHAIN_ID)`);
|
|
267
286
|
}
|
|
268
287
|
|
|
288
|
+
// src/exact/facilitator/errors.ts
|
|
289
|
+
var ErrInvalidScheme = "invalid_exact_evm_scheme";
|
|
290
|
+
var ErrNetworkMismatch = "invalid_exact_evm_network_mismatch";
|
|
291
|
+
var ErrMissingEip712Domain = "invalid_exact_evm_missing_eip712_domain";
|
|
292
|
+
var ErrRecipientMismatch = "invalid_exact_evm_recipient_mismatch";
|
|
293
|
+
var ErrInvalidSignature = "invalid_exact_evm_signature";
|
|
294
|
+
var ErrValidBeforeExpired = "invalid_exact_evm_payload_authorization_valid_before";
|
|
295
|
+
var ErrValidAfterInFuture = "invalid_exact_evm_payload_authorization_valid_after";
|
|
296
|
+
var ErrInvalidAuthorizationValue = "invalid_exact_evm_authorization_value";
|
|
297
|
+
var ErrUndeployedSmartWallet = "invalid_exact_evm_payload_undeployed_smart_wallet";
|
|
298
|
+
var ErrTransactionFailed = "invalid_exact_evm_transaction_failed";
|
|
299
|
+
var ErrEip3009TokenNameMismatch = "invalid_exact_evm_token_name_mismatch";
|
|
300
|
+
var ErrEip3009TokenVersionMismatch = "invalid_exact_evm_token_version_mismatch";
|
|
301
|
+
var ErrEip3009NotSupported = "invalid_exact_evm_eip3009_not_supported";
|
|
302
|
+
var ErrEip3009NonceAlreadyUsed = "invalid_exact_evm_nonce_already_used";
|
|
303
|
+
var ErrEip3009InsufficientBalance = "invalid_exact_evm_insufficient_balance";
|
|
304
|
+
var ErrEip3009SimulationFailed = "invalid_exact_evm_transaction_simulation_failed";
|
|
305
|
+
var ErrPermit2InvalidSpender = "invalid_permit2_spender";
|
|
306
|
+
var ErrPermit2RecipientMismatch = "invalid_permit2_recipient_mismatch";
|
|
307
|
+
var ErrPermit2DeadlineExpired = "permit2_deadline_expired";
|
|
308
|
+
var ErrPermit2NotYetValid = "permit2_not_yet_valid";
|
|
309
|
+
var ErrPermit2AmountMismatch = "permit2_amount_mismatch";
|
|
310
|
+
var ErrPermit2TokenMismatch = "permit2_token_mismatch";
|
|
311
|
+
var ErrPermit2InvalidSignature = "invalid_permit2_signature";
|
|
312
|
+
var ErrPermit2AllowanceRequired = "permit2_allowance_required";
|
|
313
|
+
var ErrPermit2SimulationFailed = "permit2_simulation_failed";
|
|
314
|
+
var ErrPermit2InsufficientBalance = "permit2_insufficient_balance";
|
|
315
|
+
var ErrPermit2ProxyNotDeployed = "permit2_proxy_not_deployed";
|
|
316
|
+
var ErrPermit2InvalidAmount = "permit2_invalid_amount";
|
|
317
|
+
var ErrPermit2InvalidDestination = "permit2_invalid_destination";
|
|
318
|
+
var ErrPermit2InvalidOwner = "permit2_invalid_owner";
|
|
319
|
+
var ErrPermit2PaymentTooEarly = "permit2_payment_too_early";
|
|
320
|
+
var ErrPermit2InvalidNonce = "permit2_invalid_nonce";
|
|
321
|
+
var ErrPermit2612AmountMismatch = "permit2_2612_amount_mismatch";
|
|
322
|
+
var ErrErc20ApprovalInsufficientEthForGas = "erc20_approval_insufficient_eth_for_gas";
|
|
323
|
+
var ErrErc20ApprovalInvalidFormat = "invalid_erc20_approval_extension_format";
|
|
324
|
+
var ErrErc20ApprovalFromMismatch = "erc20_approval_from_mismatch";
|
|
325
|
+
var ErrErc20ApprovalAssetMismatch = "erc20_approval_asset_mismatch";
|
|
326
|
+
var ErrErc20ApprovalSpenderNotPermit2 = "erc20_approval_spender_not_permit2";
|
|
327
|
+
var ErrErc20ApprovalTxWrongTarget = "erc20_approval_tx_wrong_target";
|
|
328
|
+
var ErrErc20ApprovalTxWrongSelector = "erc20_approval_tx_wrong_selector";
|
|
329
|
+
var ErrErc20ApprovalTxWrongSpender = "erc20_approval_tx_wrong_spender";
|
|
330
|
+
var ErrErc20ApprovalTxInvalidCalldata = "erc20_approval_tx_invalid_calldata";
|
|
331
|
+
var ErrErc20ApprovalTxSignerMismatch = "erc20_approval_tx_signer_mismatch";
|
|
332
|
+
var ErrErc20ApprovalTxInvalidSignature = "erc20_approval_tx_invalid_signature";
|
|
333
|
+
var ErrErc20ApprovalTxParseFailed = "erc20_approval_tx_parse_failed";
|
|
334
|
+
var ErrErc20ApprovalTxFailed = "erc20_approval_tx_failed";
|
|
335
|
+
var ErrInvalidEip2612ExtensionFormat = "invalid_eip2612_extension_format";
|
|
336
|
+
var ErrEip2612FromMismatch = "eip2612_from_mismatch";
|
|
337
|
+
var ErrEip2612AssetMismatch = "eip2612_asset_mismatch";
|
|
338
|
+
var ErrEip2612SpenderNotPermit2 = "eip2612_spender_not_permit2";
|
|
339
|
+
var ErrEip2612DeadlineExpired = "eip2612_deadline_expired";
|
|
340
|
+
var ErrUnsupportedPayloadType = "unsupported_payload_type";
|
|
341
|
+
var ErrInvalidTransactionState = "invalid_transaction_state";
|
|
342
|
+
|
|
343
|
+
// src/exact/facilitator/eip3009-utils.ts
|
|
344
|
+
var import_viem3 = require("viem");
|
|
345
|
+
|
|
346
|
+
// src/multicall.ts
|
|
347
|
+
var import_viem2 = require("viem");
|
|
348
|
+
var MULTICALL3_ADDRESS = "0xcA11bde05977b3631167028862bE2a173976CA11";
|
|
349
|
+
var multicall3GetEthBalanceAbi = [
|
|
350
|
+
{
|
|
351
|
+
name: "getEthBalance",
|
|
352
|
+
inputs: [{ name: "addr", type: "address" }],
|
|
353
|
+
outputs: [{ name: "balance", type: "uint256" }],
|
|
354
|
+
stateMutability: "view",
|
|
355
|
+
type: "function"
|
|
356
|
+
}
|
|
357
|
+
];
|
|
358
|
+
var multicall3ABI = [
|
|
359
|
+
{
|
|
360
|
+
inputs: [
|
|
361
|
+
{ name: "requireSuccess", type: "bool" },
|
|
362
|
+
{
|
|
363
|
+
name: "calls",
|
|
364
|
+
type: "tuple[]",
|
|
365
|
+
components: [
|
|
366
|
+
{ name: "target", type: "address" },
|
|
367
|
+
{ name: "callData", type: "bytes" }
|
|
368
|
+
]
|
|
369
|
+
}
|
|
370
|
+
],
|
|
371
|
+
name: "tryAggregate",
|
|
372
|
+
outputs: [
|
|
373
|
+
{
|
|
374
|
+
name: "returnData",
|
|
375
|
+
type: "tuple[]",
|
|
376
|
+
components: [
|
|
377
|
+
{ name: "success", type: "bool" },
|
|
378
|
+
{ name: "returnData", type: "bytes" }
|
|
379
|
+
]
|
|
380
|
+
}
|
|
381
|
+
],
|
|
382
|
+
stateMutability: "payable",
|
|
383
|
+
type: "function"
|
|
384
|
+
}
|
|
385
|
+
];
|
|
386
|
+
async function multicall(readContract, calls) {
|
|
387
|
+
const aggregateCalls = calls.map((call) => {
|
|
388
|
+
if ("callData" in call) {
|
|
389
|
+
return { target: call.address, callData: call.callData };
|
|
390
|
+
}
|
|
391
|
+
const callData = (0, import_viem2.encodeFunctionData)({
|
|
392
|
+
abi: call.abi,
|
|
393
|
+
functionName: call.functionName,
|
|
394
|
+
args: call.args
|
|
395
|
+
});
|
|
396
|
+
return { target: call.address, callData };
|
|
397
|
+
});
|
|
398
|
+
const rawResults = await readContract({
|
|
399
|
+
address: MULTICALL3_ADDRESS,
|
|
400
|
+
abi: multicall3ABI,
|
|
401
|
+
functionName: "tryAggregate",
|
|
402
|
+
args: [false, aggregateCalls]
|
|
403
|
+
});
|
|
404
|
+
return rawResults.map((raw, i) => {
|
|
405
|
+
if (!raw.success) {
|
|
406
|
+
return {
|
|
407
|
+
status: "failure",
|
|
408
|
+
error: new Error(`multicall: call reverted (returnData: ${raw.returnData})`)
|
|
409
|
+
};
|
|
410
|
+
}
|
|
411
|
+
const call = calls[i];
|
|
412
|
+
if ("callData" in call) {
|
|
413
|
+
return { status: "success", result: void 0 };
|
|
414
|
+
}
|
|
415
|
+
try {
|
|
416
|
+
const decoded = (0, import_viem2.decodeFunctionResult)({
|
|
417
|
+
abi: call.abi,
|
|
418
|
+
functionName: call.functionName,
|
|
419
|
+
data: raw.returnData
|
|
420
|
+
});
|
|
421
|
+
return { status: "success", result: decoded };
|
|
422
|
+
} catch (err) {
|
|
423
|
+
return {
|
|
424
|
+
status: "failure",
|
|
425
|
+
error: err instanceof Error ? err : new Error(String(err))
|
|
426
|
+
};
|
|
427
|
+
}
|
|
428
|
+
});
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
// src/exact/facilitator/eip3009-utils.ts
|
|
432
|
+
async function simulateEip3009Transfer(signer, erc20Address, payload, eip6492Deployment) {
|
|
433
|
+
const auth = payload.authorization;
|
|
434
|
+
const transferArgs = [
|
|
435
|
+
(0, import_viem3.getAddress)(auth.from),
|
|
436
|
+
(0, import_viem3.getAddress)(auth.to),
|
|
437
|
+
BigInt(auth.value),
|
|
438
|
+
BigInt(auth.validAfter),
|
|
439
|
+
BigInt(auth.validBefore),
|
|
440
|
+
auth.nonce
|
|
441
|
+
];
|
|
442
|
+
if (eip6492Deployment) {
|
|
443
|
+
const { signature: innerSignature } = (0, import_viem3.parseErc6492Signature)(payload.signature);
|
|
444
|
+
const transferCalldata = (0, import_viem3.encodeFunctionData)({
|
|
445
|
+
abi: eip3009ABI,
|
|
446
|
+
functionName: "transferWithAuthorization",
|
|
447
|
+
args: [...transferArgs, innerSignature]
|
|
448
|
+
});
|
|
449
|
+
try {
|
|
450
|
+
const results = await multicall(signer.readContract.bind(signer), [
|
|
451
|
+
{
|
|
452
|
+
address: (0, import_viem3.getAddress)(eip6492Deployment.factoryAddress),
|
|
453
|
+
callData: eip6492Deployment.factoryCalldata
|
|
454
|
+
},
|
|
455
|
+
{
|
|
456
|
+
address: erc20Address,
|
|
457
|
+
callData: transferCalldata
|
|
458
|
+
}
|
|
459
|
+
]);
|
|
460
|
+
return results[1]?.status === "success";
|
|
461
|
+
} catch {
|
|
462
|
+
return false;
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
const sig = payload.signature;
|
|
466
|
+
const sigLength = sig.startsWith("0x") ? sig.length - 2 : sig.length;
|
|
467
|
+
const isECDSA = sigLength === 130;
|
|
468
|
+
try {
|
|
469
|
+
if (isECDSA) {
|
|
470
|
+
const parsedSig = (0, import_viem3.parseSignature)(sig);
|
|
471
|
+
await signer.readContract({
|
|
472
|
+
address: erc20Address,
|
|
473
|
+
abi: eip3009ABI,
|
|
474
|
+
functionName: "transferWithAuthorization",
|
|
475
|
+
args: [
|
|
476
|
+
...transferArgs,
|
|
477
|
+
parsedSig.v ?? parsedSig.yParity,
|
|
478
|
+
parsedSig.r,
|
|
479
|
+
parsedSig.s
|
|
480
|
+
]
|
|
481
|
+
});
|
|
482
|
+
} else {
|
|
483
|
+
await signer.readContract({
|
|
484
|
+
address: erc20Address,
|
|
485
|
+
abi: eip3009ABI,
|
|
486
|
+
functionName: "transferWithAuthorization",
|
|
487
|
+
args: [...transferArgs, sig]
|
|
488
|
+
});
|
|
489
|
+
}
|
|
490
|
+
return true;
|
|
491
|
+
} catch {
|
|
492
|
+
return false;
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
async function diagnoseEip3009SimulationFailure(signer, erc20Address, payload, requirements, amountRequired) {
|
|
496
|
+
const payer = payload.authorization.from;
|
|
497
|
+
const diagnosticCalls = [
|
|
498
|
+
{
|
|
499
|
+
address: erc20Address,
|
|
500
|
+
abi: eip3009ABI,
|
|
501
|
+
functionName: "balanceOf",
|
|
502
|
+
args: [payload.authorization.from]
|
|
503
|
+
},
|
|
504
|
+
{
|
|
505
|
+
address: erc20Address,
|
|
506
|
+
abi: eip3009ABI,
|
|
507
|
+
functionName: "name"
|
|
508
|
+
},
|
|
509
|
+
{
|
|
510
|
+
address: erc20Address,
|
|
511
|
+
abi: eip3009ABI,
|
|
512
|
+
functionName: "version"
|
|
513
|
+
},
|
|
514
|
+
{
|
|
515
|
+
address: erc20Address,
|
|
516
|
+
abi: eip3009ABI,
|
|
517
|
+
functionName: "authorizationState",
|
|
518
|
+
args: [payload.authorization.from, payload.authorization.nonce]
|
|
519
|
+
}
|
|
520
|
+
];
|
|
521
|
+
try {
|
|
522
|
+
const results = await multicall(signer.readContract.bind(signer), diagnosticCalls);
|
|
523
|
+
const [balanceResult, nameResult, versionResult, authStateResult] = results;
|
|
524
|
+
if (authStateResult.status === "failure") {
|
|
525
|
+
return { isValid: false, invalidReason: ErrEip3009NotSupported, payer };
|
|
526
|
+
}
|
|
527
|
+
if (authStateResult.status === "success" && authStateResult.result === true) {
|
|
528
|
+
return { isValid: false, invalidReason: ErrEip3009NonceAlreadyUsed, payer };
|
|
529
|
+
}
|
|
530
|
+
if (nameResult.status === "success" && requirements.extra?.name && nameResult.result !== requirements.extra.name) {
|
|
531
|
+
return { isValid: false, invalidReason: ErrEip3009TokenNameMismatch, payer };
|
|
532
|
+
}
|
|
533
|
+
if (versionResult.status === "success" && requirements.extra?.version && versionResult.result !== requirements.extra.version) {
|
|
534
|
+
return { isValid: false, invalidReason: ErrEip3009TokenVersionMismatch, payer };
|
|
535
|
+
}
|
|
536
|
+
if (balanceResult.status === "success") {
|
|
537
|
+
const balance = balanceResult.result;
|
|
538
|
+
if (balance < BigInt(amountRequired)) {
|
|
539
|
+
return {
|
|
540
|
+
isValid: false,
|
|
541
|
+
invalidReason: ErrEip3009InsufficientBalance,
|
|
542
|
+
payer
|
|
543
|
+
};
|
|
544
|
+
}
|
|
545
|
+
}
|
|
546
|
+
} catch {
|
|
547
|
+
}
|
|
548
|
+
return { isValid: false, invalidReason: ErrEip3009SimulationFailed, payer };
|
|
549
|
+
}
|
|
550
|
+
async function executeTransferWithAuthorization(signer, erc20Address, payload) {
|
|
551
|
+
const { signature } = (0, import_viem3.parseErc6492Signature)(payload.signature);
|
|
552
|
+
const signatureLength = signature.startsWith("0x") ? signature.length - 2 : signature.length;
|
|
553
|
+
const isECDSA = signatureLength === 130;
|
|
554
|
+
const auth = payload.authorization;
|
|
555
|
+
const baseArgs = [
|
|
556
|
+
(0, import_viem3.getAddress)(auth.from),
|
|
557
|
+
(0, import_viem3.getAddress)(auth.to),
|
|
558
|
+
BigInt(auth.value),
|
|
559
|
+
BigInt(auth.validAfter),
|
|
560
|
+
BigInt(auth.validBefore),
|
|
561
|
+
auth.nonce
|
|
562
|
+
];
|
|
563
|
+
if (isECDSA) {
|
|
564
|
+
const parsedSig = (0, import_viem3.parseSignature)(signature);
|
|
565
|
+
return signer.writeContract({
|
|
566
|
+
address: erc20Address,
|
|
567
|
+
abi: eip3009ABI,
|
|
568
|
+
functionName: "transferWithAuthorization",
|
|
569
|
+
args: [
|
|
570
|
+
...baseArgs,
|
|
571
|
+
parsedSig.v || parsedSig.yParity,
|
|
572
|
+
parsedSig.r,
|
|
573
|
+
parsedSig.s
|
|
574
|
+
]
|
|
575
|
+
});
|
|
576
|
+
}
|
|
577
|
+
return signer.writeContract({
|
|
578
|
+
address: erc20Address,
|
|
579
|
+
abi: eip3009ABI,
|
|
580
|
+
functionName: "transferWithAuthorization",
|
|
581
|
+
args: [...baseArgs, signature]
|
|
582
|
+
});
|
|
583
|
+
}
|
|
584
|
+
|
|
269
585
|
// src/exact/facilitator/eip3009.ts
|
|
270
|
-
async function verifyEIP3009(signer, payload, requirements, eip3009Payload) {
|
|
586
|
+
async function verifyEIP3009(signer, payload, requirements, eip3009Payload, options) {
|
|
271
587
|
const payer = eip3009Payload.authorization.from;
|
|
588
|
+
let eip6492Deployment;
|
|
272
589
|
if (payload.accepted.scheme !== "exact" || requirements.scheme !== "exact") {
|
|
273
590
|
return {
|
|
274
591
|
isValid: false,
|
|
275
|
-
invalidReason:
|
|
592
|
+
invalidReason: ErrInvalidScheme,
|
|
276
593
|
payer
|
|
277
594
|
};
|
|
278
595
|
}
|
|
279
596
|
if (!requirements.extra?.name || !requirements.extra?.version) {
|
|
280
597
|
return {
|
|
281
598
|
isValid: false,
|
|
282
|
-
invalidReason:
|
|
599
|
+
invalidReason: ErrMissingEip712Domain,
|
|
283
600
|
payer
|
|
284
601
|
};
|
|
285
602
|
}
|
|
286
603
|
const { name, version } = requirements.extra;
|
|
287
|
-
const erc20Address = (0,
|
|
604
|
+
const erc20Address = (0, import_viem4.getAddress)(requirements.asset);
|
|
288
605
|
if (payload.accepted.network !== requirements.network) {
|
|
289
606
|
return {
|
|
290
607
|
isValid: false,
|
|
291
|
-
invalidReason:
|
|
608
|
+
invalidReason: ErrNetworkMismatch,
|
|
292
609
|
payer
|
|
293
610
|
};
|
|
294
611
|
}
|
|
@@ -310,55 +627,49 @@ async function verifyEIP3009(signer, payload, requirements, eip3009Payload) {
|
|
|
310
627
|
nonce: eip3009Payload.authorization.nonce
|
|
311
628
|
}
|
|
312
629
|
};
|
|
630
|
+
let isValid = false;
|
|
313
631
|
try {
|
|
314
|
-
|
|
632
|
+
isValid = await signer.verifyTypedData({
|
|
315
633
|
address: eip3009Payload.authorization.from,
|
|
316
634
|
...permitTypedData,
|
|
317
635
|
signature: eip3009Payload.signature
|
|
318
636
|
});
|
|
319
|
-
|
|
637
|
+
} catch {
|
|
638
|
+
isValid = false;
|
|
639
|
+
}
|
|
640
|
+
const signature = eip3009Payload.signature;
|
|
641
|
+
const sigLen = signature.startsWith("0x") ? signature.length - 2 : signature.length;
|
|
642
|
+
const erc6492Data = (0, import_viem4.parseErc6492Signature)(signature);
|
|
643
|
+
const hasDeploymentInfo = erc6492Data.address && erc6492Data.data && !(0, import_viem4.isAddressEqual)(erc6492Data.address, "0x0000000000000000000000000000000000000000");
|
|
644
|
+
if (hasDeploymentInfo) {
|
|
645
|
+
eip6492Deployment = {
|
|
646
|
+
factoryAddress: erc6492Data.address,
|
|
647
|
+
factoryCalldata: erc6492Data.data
|
|
648
|
+
};
|
|
649
|
+
}
|
|
650
|
+
if (!isValid) {
|
|
651
|
+
const isSmartWallet = sigLen > 130;
|
|
652
|
+
if (!isSmartWallet) {
|
|
320
653
|
return {
|
|
321
654
|
isValid: false,
|
|
322
|
-
invalidReason:
|
|
655
|
+
invalidReason: ErrInvalidSignature,
|
|
323
656
|
payer
|
|
324
657
|
};
|
|
325
658
|
}
|
|
326
|
-
|
|
327
|
-
const
|
|
328
|
-
|
|
329
|
-
const isSmartWallet = signatureLength > 130;
|
|
330
|
-
if (isSmartWallet) {
|
|
331
|
-
const payerAddress = eip3009Payload.authorization.from;
|
|
332
|
-
const bytecode = await signer.getCode({ address: payerAddress });
|
|
333
|
-
if (!bytecode || bytecode === "0x") {
|
|
334
|
-
const erc6492Data = (0, import_viem2.parseErc6492Signature)(signature);
|
|
335
|
-
const hasDeploymentInfo = erc6492Data.address && erc6492Data.data && !(0, import_viem2.isAddressEqual)(erc6492Data.address, "0x0000000000000000000000000000000000000000");
|
|
336
|
-
if (!hasDeploymentInfo) {
|
|
337
|
-
return {
|
|
338
|
-
isValid: false,
|
|
339
|
-
invalidReason: "invalid_exact_evm_payload_undeployed_smart_wallet",
|
|
340
|
-
payer: payerAddress
|
|
341
|
-
};
|
|
342
|
-
}
|
|
343
|
-
} else {
|
|
344
|
-
return {
|
|
345
|
-
isValid: false,
|
|
346
|
-
invalidReason: "invalid_exact_evm_payload_signature",
|
|
347
|
-
payer
|
|
348
|
-
};
|
|
349
|
-
}
|
|
350
|
-
} else {
|
|
659
|
+
const bytecode = await signer.getCode({ address: payer });
|
|
660
|
+
const isDeployed = bytecode && bytecode !== "0x";
|
|
661
|
+
if (!isDeployed && !hasDeploymentInfo) {
|
|
351
662
|
return {
|
|
352
663
|
isValid: false,
|
|
353
|
-
invalidReason:
|
|
664
|
+
invalidReason: ErrUndeployedSmartWallet,
|
|
354
665
|
payer
|
|
355
666
|
};
|
|
356
667
|
}
|
|
357
668
|
}
|
|
358
|
-
if ((0,
|
|
669
|
+
if ((0, import_viem4.getAddress)(eip3009Payload.authorization.to) !== (0, import_viem4.getAddress)(requirements.payTo)) {
|
|
359
670
|
return {
|
|
360
671
|
isValid: false,
|
|
361
|
-
invalidReason:
|
|
672
|
+
invalidReason: ErrRecipientMismatch,
|
|
362
673
|
payer
|
|
363
674
|
};
|
|
364
675
|
}
|
|
@@ -366,41 +677,41 @@ async function verifyEIP3009(signer, payload, requirements, eip3009Payload) {
|
|
|
366
677
|
if (BigInt(eip3009Payload.authorization.validBefore) < BigInt(now + 6)) {
|
|
367
678
|
return {
|
|
368
679
|
isValid: false,
|
|
369
|
-
invalidReason:
|
|
680
|
+
invalidReason: ErrValidBeforeExpired,
|
|
370
681
|
payer
|
|
371
682
|
};
|
|
372
683
|
}
|
|
373
684
|
if (BigInt(eip3009Payload.authorization.validAfter) > BigInt(now)) {
|
|
374
685
|
return {
|
|
375
686
|
isValid: false,
|
|
376
|
-
invalidReason:
|
|
687
|
+
invalidReason: ErrValidAfterInFuture,
|
|
377
688
|
payer
|
|
378
689
|
};
|
|
379
690
|
}
|
|
380
|
-
try {
|
|
381
|
-
const balance = await signer.readContract({
|
|
382
|
-
address: erc20Address,
|
|
383
|
-
abi: eip3009ABI,
|
|
384
|
-
functionName: "balanceOf",
|
|
385
|
-
args: [eip3009Payload.authorization.from]
|
|
386
|
-
});
|
|
387
|
-
if (BigInt(balance) < BigInt(requirements.amount)) {
|
|
388
|
-
return {
|
|
389
|
-
isValid: false,
|
|
390
|
-
invalidReason: "insufficient_funds",
|
|
391
|
-
invalidMessage: `Insufficient funds to complete the payment. Required: ${requirements.amount} ${requirements.asset}, Available: ${balance.toString()} ${requirements.asset}. Please add funds to your wallet and try again.`,
|
|
392
|
-
payer
|
|
393
|
-
};
|
|
394
|
-
}
|
|
395
|
-
} catch {
|
|
396
|
-
}
|
|
397
691
|
if (BigInt(eip3009Payload.authorization.value) !== BigInt(requirements.amount)) {
|
|
398
692
|
return {
|
|
399
693
|
isValid: false,
|
|
400
|
-
invalidReason:
|
|
694
|
+
invalidReason: ErrInvalidAuthorizationValue,
|
|
401
695
|
payer
|
|
402
696
|
};
|
|
403
697
|
}
|
|
698
|
+
if (options?.simulate !== false) {
|
|
699
|
+
const simulationSucceeded = await simulateEip3009Transfer(
|
|
700
|
+
signer,
|
|
701
|
+
erc20Address,
|
|
702
|
+
eip3009Payload,
|
|
703
|
+
eip6492Deployment
|
|
704
|
+
);
|
|
705
|
+
if (!simulationSucceeded) {
|
|
706
|
+
return diagnoseEip3009SimulationFailure(
|
|
707
|
+
signer,
|
|
708
|
+
erc20Address,
|
|
709
|
+
eip3009Payload,
|
|
710
|
+
requirements,
|
|
711
|
+
requirements.amount
|
|
712
|
+
);
|
|
713
|
+
}
|
|
714
|
+
}
|
|
404
715
|
return {
|
|
405
716
|
isValid: true,
|
|
406
717
|
invalidReason: void 0,
|
|
@@ -409,20 +720,23 @@ async function verifyEIP3009(signer, payload, requirements, eip3009Payload) {
|
|
|
409
720
|
}
|
|
410
721
|
async function settleEIP3009(signer, payload, requirements, eip3009Payload, config) {
|
|
411
722
|
const payer = eip3009Payload.authorization.from;
|
|
412
|
-
const valid = await verifyEIP3009(signer, payload, requirements, eip3009Payload
|
|
723
|
+
const valid = await verifyEIP3009(signer, payload, requirements, eip3009Payload, {
|
|
724
|
+
simulate: config.simulateInSettle ?? false
|
|
725
|
+
});
|
|
413
726
|
if (!valid.isValid) {
|
|
414
727
|
return {
|
|
415
728
|
success: false,
|
|
416
729
|
network: payload.accepted.network,
|
|
417
730
|
transaction: "",
|
|
418
|
-
errorReason: valid.invalidReason ??
|
|
731
|
+
errorReason: valid.invalidReason ?? ErrInvalidScheme,
|
|
419
732
|
payer
|
|
420
733
|
};
|
|
421
734
|
}
|
|
422
735
|
try {
|
|
423
|
-
const
|
|
424
|
-
|
|
425
|
-
|
|
736
|
+
const { address: factoryAddress, data: factoryCalldata } = (0, import_viem4.parseErc6492Signature)(
|
|
737
|
+
eip3009Payload.signature
|
|
738
|
+
);
|
|
739
|
+
if (config.deployERC4337WithEIP6492 && factoryAddress && factoryCalldata && !(0, import_viem4.isAddressEqual)(factoryAddress, "0x0000000000000000000000000000000000000000")) {
|
|
426
740
|
const bytecode = await signer.getCode({ address: payer });
|
|
427
741
|
if (!bytecode || bytecode === "0x") {
|
|
428
742
|
const deployTx = await signer.sendTransaction({
|
|
@@ -432,48 +746,16 @@ async function settleEIP3009(signer, payload, requirements, eip3009Payload, conf
|
|
|
432
746
|
await signer.waitForTransactionReceipt({ hash: deployTx });
|
|
433
747
|
}
|
|
434
748
|
}
|
|
435
|
-
const
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
tx = await signer.writeContract({
|
|
441
|
-
address: (0, import_viem2.getAddress)(requirements.asset),
|
|
442
|
-
abi: eip3009ABI,
|
|
443
|
-
functionName: "transferWithAuthorization",
|
|
444
|
-
args: [
|
|
445
|
-
(0, import_viem2.getAddress)(eip3009Payload.authorization.from),
|
|
446
|
-
(0, import_viem2.getAddress)(eip3009Payload.authorization.to),
|
|
447
|
-
BigInt(eip3009Payload.authorization.value),
|
|
448
|
-
BigInt(eip3009Payload.authorization.validAfter),
|
|
449
|
-
BigInt(eip3009Payload.authorization.validBefore),
|
|
450
|
-
eip3009Payload.authorization.nonce,
|
|
451
|
-
parsedSig.v || parsedSig.yParity,
|
|
452
|
-
parsedSig.r,
|
|
453
|
-
parsedSig.s
|
|
454
|
-
]
|
|
455
|
-
});
|
|
456
|
-
} else {
|
|
457
|
-
tx = await signer.writeContract({
|
|
458
|
-
address: (0, import_viem2.getAddress)(requirements.asset),
|
|
459
|
-
abi: eip3009ABI,
|
|
460
|
-
functionName: "transferWithAuthorization",
|
|
461
|
-
args: [
|
|
462
|
-
(0, import_viem2.getAddress)(eip3009Payload.authorization.from),
|
|
463
|
-
(0, import_viem2.getAddress)(eip3009Payload.authorization.to),
|
|
464
|
-
BigInt(eip3009Payload.authorization.value),
|
|
465
|
-
BigInt(eip3009Payload.authorization.validAfter),
|
|
466
|
-
BigInt(eip3009Payload.authorization.validBefore),
|
|
467
|
-
eip3009Payload.authorization.nonce,
|
|
468
|
-
signature
|
|
469
|
-
]
|
|
470
|
-
});
|
|
471
|
-
}
|
|
749
|
+
const tx = await executeTransferWithAuthorization(
|
|
750
|
+
signer,
|
|
751
|
+
(0, import_viem4.getAddress)(requirements.asset),
|
|
752
|
+
eip3009Payload
|
|
753
|
+
);
|
|
472
754
|
const receipt = await signer.waitForTransactionReceipt({ hash: tx });
|
|
473
755
|
if (receipt.status !== "success") {
|
|
474
756
|
return {
|
|
475
757
|
success: false,
|
|
476
|
-
errorReason:
|
|
758
|
+
errorReason: ErrTransactionFailed,
|
|
477
759
|
transaction: tx,
|
|
478
760
|
network: payload.accepted.network,
|
|
479
761
|
payer
|
|
@@ -488,7 +770,7 @@ async function settleEIP3009(signer, payload, requirements, eip3009Payload, conf
|
|
|
488
770
|
} catch {
|
|
489
771
|
return {
|
|
490
772
|
success: false,
|
|
491
|
-
errorReason:
|
|
773
|
+
errorReason: ErrTransactionFailed,
|
|
492
774
|
transaction: "",
|
|
493
775
|
network: payload.accepted.network,
|
|
494
776
|
payer
|
|
@@ -496,57 +778,80 @@ async function settleEIP3009(signer, payload, requirements, eip3009Payload, conf
|
|
|
496
778
|
}
|
|
497
779
|
}
|
|
498
780
|
|
|
499
|
-
// src/exact/
|
|
500
|
-
var
|
|
501
|
-
var
|
|
781
|
+
// src/exact/extensions.ts
|
|
782
|
+
var EIP2612_GAS_SPONSORING_KEY = "eip2612GasSponsoring";
|
|
783
|
+
var ERC20_APPROVAL_GAS_SPONSORING_KEY = "erc20ApprovalGasSponsoring";
|
|
784
|
+
function _extractInfo(payload, extensionKey) {
|
|
785
|
+
const extensions = payload.extensions;
|
|
786
|
+
if (!extensions) return null;
|
|
787
|
+
const extension = extensions[extensionKey];
|
|
788
|
+
if (!extension?.info) return null;
|
|
789
|
+
return extension.info;
|
|
790
|
+
}
|
|
791
|
+
function extractEip2612GasSponsoringInfo(payload) {
|
|
792
|
+
const info = _extractInfo(payload, EIP2612_GAS_SPONSORING_KEY);
|
|
793
|
+
if (!info) return null;
|
|
794
|
+
if (!info.from || !info.asset || !info.spender || !info.amount || !info.nonce || !info.deadline || !info.signature || !info.version) {
|
|
795
|
+
return null;
|
|
796
|
+
}
|
|
797
|
+
return info;
|
|
798
|
+
}
|
|
799
|
+
function validateEip2612GasSponsoringInfo(info) {
|
|
800
|
+
const addressPattern = /^0x[a-fA-F0-9]{40}$/;
|
|
801
|
+
const numericPattern = /^[0-9]+$/;
|
|
802
|
+
const hexPattern = /^0x[a-fA-F0-9]+$/;
|
|
803
|
+
const versionPattern = /^[0-9]+(\.[0-9]+)*$/;
|
|
804
|
+
return addressPattern.test(info.from) && addressPattern.test(info.asset) && addressPattern.test(info.spender) && numericPattern.test(info.amount) && numericPattern.test(info.nonce) && numericPattern.test(info.deadline) && hexPattern.test(info.signature) && versionPattern.test(info.version);
|
|
805
|
+
}
|
|
806
|
+
function extractErc20ApprovalGasSponsoringInfo(payload) {
|
|
807
|
+
const info = _extractInfo(payload, ERC20_APPROVAL_GAS_SPONSORING_KEY);
|
|
808
|
+
if (!info) return null;
|
|
809
|
+
if (!info.from || !info.asset || !info.spender || !info.amount || !info.signedTransaction || !info.version) {
|
|
810
|
+
return null;
|
|
811
|
+
}
|
|
812
|
+
return info;
|
|
813
|
+
}
|
|
814
|
+
function validateErc20ApprovalGasSponsoringInfo(info) {
|
|
815
|
+
const addressPattern = /^0x[a-fA-F0-9]{40}$/;
|
|
816
|
+
const numericPattern = /^[0-9]+$/;
|
|
817
|
+
const hexPattern = /^0x[a-fA-F0-9]+$/;
|
|
818
|
+
const versionPattern = /^[0-9]+(\.[0-9]+)*$/;
|
|
819
|
+
return addressPattern.test(info.from) && addressPattern.test(info.asset) && addressPattern.test(info.spender) && numericPattern.test(info.amount) && hexPattern.test(info.signedTransaction) && versionPattern.test(info.version);
|
|
820
|
+
}
|
|
821
|
+
function resolveErc20ApprovalExtensionSigner(extension, network) {
|
|
822
|
+
if (!extension) return void 0;
|
|
823
|
+
return extension.signerForNetwork?.(network) ?? extension.signer;
|
|
824
|
+
}
|
|
502
825
|
|
|
503
|
-
// src/exact/facilitator/
|
|
504
|
-
var
|
|
505
|
-
var ErrPermit2InvalidAmount = "permit2_invalid_amount";
|
|
506
|
-
var ErrPermit2InvalidDestination = "permit2_invalid_destination";
|
|
507
|
-
var ErrPermit2InvalidOwner = "permit2_invalid_owner";
|
|
508
|
-
var ErrPermit2PaymentTooEarly = "permit2_payment_too_early";
|
|
509
|
-
var ErrPermit2InvalidNonce = "permit2_invalid_nonce";
|
|
510
|
-
var ErrPermit2612AmountMismatch = "permit2_2612_amount_mismatch";
|
|
511
|
-
var ErrErc20ApprovalInvalidFormat = "invalid_erc20_approval_extension_format";
|
|
512
|
-
var ErrErc20ApprovalFromMismatch = "erc20_approval_from_mismatch";
|
|
513
|
-
var ErrErc20ApprovalAssetMismatch = "erc20_approval_asset_mismatch";
|
|
514
|
-
var ErrErc20ApprovalSpenderNotPermit2 = "erc20_approval_spender_not_permit2";
|
|
515
|
-
var ErrErc20ApprovalTxWrongTarget = "erc20_approval_tx_wrong_target";
|
|
516
|
-
var ErrErc20ApprovalTxWrongSelector = "erc20_approval_tx_wrong_selector";
|
|
517
|
-
var ErrErc20ApprovalTxWrongSpender = "erc20_approval_tx_wrong_spender";
|
|
518
|
-
var ErrErc20ApprovalTxInvalidCalldata = "erc20_approval_tx_invalid_calldata";
|
|
519
|
-
var ErrErc20ApprovalTxSignerMismatch = "erc20_approval_tx_signer_mismatch";
|
|
520
|
-
var ErrErc20ApprovalTxInvalidSignature = "erc20_approval_tx_invalid_signature";
|
|
521
|
-
var ErrErc20ApprovalTxParseFailed = "erc20_approval_tx_parse_failed";
|
|
826
|
+
// src/exact/facilitator/permit2.ts
|
|
827
|
+
var import_viem7 = require("viem");
|
|
522
828
|
|
|
523
829
|
// src/exact/facilitator/erc20approval.ts
|
|
524
|
-
var
|
|
525
|
-
var import_extensions = require("@x402/extensions");
|
|
830
|
+
var import_viem5 = require("viem");
|
|
526
831
|
var APPROVE_SELECTOR = "0x095ea7b3";
|
|
527
832
|
async function validateErc20ApprovalForPayment(info, payer, tokenAddress) {
|
|
528
|
-
if (!
|
|
833
|
+
if (!validateErc20ApprovalGasSponsoringInfo(info)) {
|
|
529
834
|
return {
|
|
530
835
|
isValid: false,
|
|
531
836
|
invalidReason: ErrErc20ApprovalInvalidFormat,
|
|
532
837
|
invalidMessage: "ERC-20 approval extension info failed schema validation"
|
|
533
838
|
};
|
|
534
839
|
}
|
|
535
|
-
if ((0,
|
|
840
|
+
if ((0, import_viem5.getAddress)(info.from) !== (0, import_viem5.getAddress)(payer)) {
|
|
536
841
|
return {
|
|
537
842
|
isValid: false,
|
|
538
843
|
invalidReason: ErrErc20ApprovalFromMismatch,
|
|
539
844
|
invalidMessage: `Expected from=${payer}, got ${info.from}`
|
|
540
845
|
};
|
|
541
846
|
}
|
|
542
|
-
if ((0,
|
|
847
|
+
if ((0, import_viem5.getAddress)(info.asset) !== tokenAddress) {
|
|
543
848
|
return {
|
|
544
849
|
isValid: false,
|
|
545
850
|
invalidReason: ErrErc20ApprovalAssetMismatch,
|
|
546
851
|
invalidMessage: `Expected asset=${tokenAddress}, got ${info.asset}`
|
|
547
852
|
};
|
|
548
853
|
}
|
|
549
|
-
if ((0,
|
|
854
|
+
if ((0, import_viem5.getAddress)(info.spender) !== (0, import_viem5.getAddress)(PERMIT2_ADDRESS)) {
|
|
550
855
|
return {
|
|
551
856
|
isValid: false,
|
|
552
857
|
invalidReason: ErrErc20ApprovalSpenderNotPermit2,
|
|
@@ -555,8 +860,8 @@ async function validateErc20ApprovalForPayment(info, payer, tokenAddress) {
|
|
|
555
860
|
}
|
|
556
861
|
try {
|
|
557
862
|
const serializedTx = info.signedTransaction;
|
|
558
|
-
const tx = (0,
|
|
559
|
-
if (!tx.to || (0,
|
|
863
|
+
const tx = (0, import_viem5.parseTransaction)(serializedTx);
|
|
864
|
+
if (!tx.to || (0, import_viem5.getAddress)(tx.to) !== tokenAddress) {
|
|
560
865
|
return {
|
|
561
866
|
isValid: false,
|
|
562
867
|
invalidReason: ErrErc20ApprovalTxWrongTarget,
|
|
@@ -572,12 +877,12 @@ async function validateErc20ApprovalForPayment(info, payer, tokenAddress) {
|
|
|
572
877
|
};
|
|
573
878
|
}
|
|
574
879
|
try {
|
|
575
|
-
const decoded = (0,
|
|
880
|
+
const decoded = (0, import_viem5.decodeFunctionData)({
|
|
576
881
|
abi: erc20ApproveAbi,
|
|
577
882
|
data
|
|
578
883
|
});
|
|
579
|
-
const calldataSpender = (0,
|
|
580
|
-
if (calldataSpender !== (0,
|
|
884
|
+
const calldataSpender = (0, import_viem5.getAddress)(decoded.args[0]);
|
|
885
|
+
if (calldataSpender !== (0, import_viem5.getAddress)(PERMIT2_ADDRESS)) {
|
|
581
886
|
return {
|
|
582
887
|
isValid: false,
|
|
583
888
|
invalidReason: ErrErc20ApprovalTxWrongSpender,
|
|
@@ -592,10 +897,10 @@ async function validateErc20ApprovalForPayment(info, payer, tokenAddress) {
|
|
|
592
897
|
};
|
|
593
898
|
}
|
|
594
899
|
try {
|
|
595
|
-
const recoveredAddress = await (0,
|
|
900
|
+
const recoveredAddress = await (0, import_viem5.recoverTransactionAddress)({
|
|
596
901
|
serializedTransaction: serializedTx
|
|
597
902
|
});
|
|
598
|
-
if ((0,
|
|
903
|
+
if ((0, import_viem5.getAddress)(recoveredAddress) !== (0, import_viem5.getAddress)(payer)) {
|
|
599
904
|
return {
|
|
600
905
|
isValid: false,
|
|
601
906
|
invalidReason: ErrErc20ApprovalTxSignerMismatch,
|
|
@@ -619,65 +924,314 @@ async function validateErc20ApprovalForPayment(info, payer, tokenAddress) {
|
|
|
619
924
|
return { isValid: true };
|
|
620
925
|
}
|
|
621
926
|
|
|
622
|
-
// src/exact/facilitator/permit2.ts
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
invalidReason: "network_mismatch",
|
|
636
|
-
payer
|
|
637
|
-
};
|
|
638
|
-
}
|
|
639
|
-
const chainId = getEvmChainId(requirements.network);
|
|
640
|
-
const tokenAddress = (0, import_viem4.getAddress)(requirements.asset);
|
|
641
|
-
if ((0, import_viem4.getAddress)(permit2Payload.permit2Authorization.spender) !== (0, import_viem4.getAddress)(x402ExactPermit2ProxyAddress)) {
|
|
642
|
-
return {
|
|
643
|
-
isValid: false,
|
|
644
|
-
invalidReason: "invalid_permit2_spender",
|
|
645
|
-
payer
|
|
646
|
-
};
|
|
927
|
+
// src/exact/facilitator/permit2-utils.ts
|
|
928
|
+
var import_viem6 = require("viem");
|
|
929
|
+
async function simulatePermit2Settle(signer, permit2Payload) {
|
|
930
|
+
try {
|
|
931
|
+
await signer.readContract({
|
|
932
|
+
address: x402ExactPermit2ProxyAddress,
|
|
933
|
+
abi: x402ExactPermit2ProxyABI,
|
|
934
|
+
functionName: "settle",
|
|
935
|
+
args: buildPermit2SettleArgs(permit2Payload)
|
|
936
|
+
});
|
|
937
|
+
return true;
|
|
938
|
+
} catch {
|
|
939
|
+
return false;
|
|
647
940
|
}
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
941
|
+
}
|
|
942
|
+
function splitEip2612Signature(signature) {
|
|
943
|
+
const sig = signature.startsWith("0x") ? signature.slice(2) : signature;
|
|
944
|
+
if (sig.length !== 130) {
|
|
945
|
+
throw new Error(
|
|
946
|
+
`invalid EIP-2612 signature length: expected 65 bytes (130 hex chars), got ${sig.length / 2} bytes`
|
|
947
|
+
);
|
|
654
948
|
}
|
|
655
|
-
const
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
949
|
+
const r = `0x${sig.slice(0, 64)}`;
|
|
950
|
+
const s = `0x${sig.slice(64, 128)}`;
|
|
951
|
+
const v = parseInt(sig.slice(128, 130), 16);
|
|
952
|
+
return { v, r, s };
|
|
953
|
+
}
|
|
954
|
+
function buildPermit2SettleArgs(permit2Payload) {
|
|
955
|
+
return [
|
|
956
|
+
{
|
|
957
|
+
permitted: {
|
|
958
|
+
token: (0, import_viem6.getAddress)(permit2Payload.permit2Authorization.permitted.token),
|
|
959
|
+
amount: BigInt(permit2Payload.permit2Authorization.permitted.amount)
|
|
960
|
+
},
|
|
961
|
+
nonce: BigInt(permit2Payload.permit2Authorization.nonce),
|
|
962
|
+
deadline: BigInt(permit2Payload.permit2Authorization.deadline)
|
|
963
|
+
},
|
|
964
|
+
(0, import_viem6.getAddress)(permit2Payload.permit2Authorization.from),
|
|
965
|
+
{
|
|
966
|
+
to: (0, import_viem6.getAddress)(permit2Payload.permit2Authorization.witness.to),
|
|
967
|
+
validAfter: BigInt(permit2Payload.permit2Authorization.witness.validAfter)
|
|
968
|
+
},
|
|
969
|
+
permit2Payload.signature
|
|
970
|
+
];
|
|
971
|
+
}
|
|
972
|
+
function encodePermit2SettleCalldata(permit2Payload) {
|
|
973
|
+
return (0, import_viem6.encodeFunctionData)({
|
|
974
|
+
abi: x402ExactPermit2ProxyABI,
|
|
975
|
+
functionName: "settle",
|
|
976
|
+
args: buildPermit2SettleArgs(permit2Payload)
|
|
977
|
+
});
|
|
978
|
+
}
|
|
979
|
+
async function simulatePermit2SettleWithPermit(signer, permit2Payload, eip2612Info) {
|
|
980
|
+
try {
|
|
981
|
+
const { v, r, s } = splitEip2612Signature(eip2612Info.signature);
|
|
982
|
+
await signer.readContract({
|
|
983
|
+
address: x402ExactPermit2ProxyAddress,
|
|
984
|
+
abi: x402ExactPermit2ProxyABI,
|
|
985
|
+
functionName: "settleWithPermit",
|
|
986
|
+
args: [
|
|
987
|
+
{
|
|
988
|
+
value: BigInt(eip2612Info.amount),
|
|
989
|
+
deadline: BigInt(eip2612Info.deadline),
|
|
990
|
+
r,
|
|
991
|
+
s,
|
|
992
|
+
v
|
|
993
|
+
},
|
|
994
|
+
...buildPermit2SettleArgs(permit2Payload)
|
|
995
|
+
]
|
|
996
|
+
});
|
|
997
|
+
return true;
|
|
998
|
+
} catch {
|
|
999
|
+
return false;
|
|
1000
|
+
}
|
|
1001
|
+
}
|
|
1002
|
+
async function diagnosePermit2SimulationFailure(signer, tokenAddress, permit2Payload, amountRequired) {
|
|
1003
|
+
const payer = permit2Payload.permit2Authorization.from;
|
|
1004
|
+
const diagnosticCalls = [
|
|
1005
|
+
{
|
|
1006
|
+
address: x402ExactPermit2ProxyAddress,
|
|
1007
|
+
abi: x402ExactPermit2ProxyABI,
|
|
1008
|
+
functionName: "PERMIT2"
|
|
1009
|
+
},
|
|
1010
|
+
{
|
|
1011
|
+
address: tokenAddress,
|
|
1012
|
+
abi: eip3009ABI,
|
|
1013
|
+
functionName: "balanceOf",
|
|
1014
|
+
args: [payer]
|
|
1015
|
+
},
|
|
1016
|
+
{
|
|
1017
|
+
address: tokenAddress,
|
|
1018
|
+
abi: erc20AllowanceAbi,
|
|
1019
|
+
functionName: "allowance",
|
|
1020
|
+
args: [payer, PERMIT2_ADDRESS]
|
|
1021
|
+
}
|
|
1022
|
+
];
|
|
1023
|
+
try {
|
|
1024
|
+
const results = await multicall(signer.readContract.bind(signer), diagnosticCalls);
|
|
1025
|
+
const [proxyResult, balanceResult, allowanceResult] = results;
|
|
1026
|
+
if (proxyResult.status === "failure") {
|
|
1027
|
+
return { isValid: false, invalidReason: ErrPermit2ProxyNotDeployed, payer };
|
|
1028
|
+
}
|
|
1029
|
+
if (balanceResult.status === "success") {
|
|
1030
|
+
const balance = balanceResult.result;
|
|
1031
|
+
if (balance < BigInt(amountRequired)) {
|
|
1032
|
+
return { isValid: false, invalidReason: ErrPermit2InsufficientBalance, payer };
|
|
1033
|
+
}
|
|
1034
|
+
}
|
|
1035
|
+
if (allowanceResult.status === "success") {
|
|
1036
|
+
const allowance = allowanceResult.result;
|
|
1037
|
+
if (allowance < BigInt(amountRequired)) {
|
|
1038
|
+
return { isValid: false, invalidReason: ErrPermit2AllowanceRequired, payer };
|
|
1039
|
+
}
|
|
1040
|
+
}
|
|
1041
|
+
} catch {
|
|
1042
|
+
}
|
|
1043
|
+
return { isValid: false, invalidReason: ErrPermit2SimulationFailed, payer };
|
|
1044
|
+
}
|
|
1045
|
+
async function checkPermit2Prerequisites(signer, tokenAddress, payer, amountRequired) {
|
|
1046
|
+
const diagnosticCalls = [
|
|
1047
|
+
{
|
|
1048
|
+
address: x402ExactPermit2ProxyAddress,
|
|
1049
|
+
abi: x402ExactPermit2ProxyABI,
|
|
1050
|
+
functionName: "PERMIT2"
|
|
1051
|
+
},
|
|
1052
|
+
{
|
|
1053
|
+
address: tokenAddress,
|
|
1054
|
+
abi: eip3009ABI,
|
|
1055
|
+
functionName: "balanceOf",
|
|
1056
|
+
args: [payer]
|
|
1057
|
+
},
|
|
1058
|
+
{
|
|
1059
|
+
address: MULTICALL3_ADDRESS,
|
|
1060
|
+
abi: multicall3GetEthBalanceAbi,
|
|
1061
|
+
functionName: "getEthBalance",
|
|
1062
|
+
args: [payer]
|
|
1063
|
+
}
|
|
1064
|
+
];
|
|
1065
|
+
try {
|
|
1066
|
+
const results = await multicall(signer.readContract.bind(signer), diagnosticCalls);
|
|
1067
|
+
const [proxyResult, balanceResult, ethBalanceResult] = results;
|
|
1068
|
+
if (proxyResult.status === "failure") {
|
|
1069
|
+
return { isValid: false, invalidReason: ErrPermit2ProxyNotDeployed, payer };
|
|
1070
|
+
}
|
|
1071
|
+
if (balanceResult.status === "success") {
|
|
1072
|
+
const balance = balanceResult.result;
|
|
1073
|
+
if (balance < BigInt(amountRequired)) {
|
|
1074
|
+
return { isValid: false, invalidReason: ErrPermit2InsufficientBalance, payer };
|
|
1075
|
+
}
|
|
1076
|
+
}
|
|
1077
|
+
if (ethBalanceResult.status === "success") {
|
|
1078
|
+
const minEthForApprovalGas = ERC20_APPROVE_GAS_LIMIT * DEFAULT_MAX_FEE_PER_GAS;
|
|
1079
|
+
const ethBalance = ethBalanceResult.result;
|
|
1080
|
+
if (ethBalance < minEthForApprovalGas) {
|
|
1081
|
+
return {
|
|
1082
|
+
isValid: false,
|
|
1083
|
+
invalidReason: ErrErc20ApprovalInsufficientEthForGas,
|
|
1084
|
+
payer
|
|
1085
|
+
};
|
|
1086
|
+
}
|
|
1087
|
+
}
|
|
1088
|
+
} catch {
|
|
1089
|
+
}
|
|
1090
|
+
return { isValid: true, invalidReason: void 0, payer };
|
|
1091
|
+
}
|
|
1092
|
+
async function simulatePermit2SettleWithErc20Approval(extensionSigner, permit2Payload, erc20Info) {
|
|
1093
|
+
if (!extensionSigner.simulateTransactions) {
|
|
1094
|
+
return false;
|
|
1095
|
+
}
|
|
1096
|
+
try {
|
|
1097
|
+
const settleData = encodePermit2SettleCalldata(permit2Payload);
|
|
1098
|
+
return await extensionSigner.simulateTransactions([
|
|
1099
|
+
erc20Info.signedTransaction,
|
|
1100
|
+
{ to: x402ExactPermit2ProxyAddress, data: settleData, gas: BigInt(3e5) }
|
|
1101
|
+
]);
|
|
1102
|
+
} catch {
|
|
1103
|
+
return false;
|
|
1104
|
+
}
|
|
1105
|
+
}
|
|
1106
|
+
async function waitAndReturn(signer, tx, payload, payer) {
|
|
1107
|
+
const receipt = await signer.waitForTransactionReceipt({ hash: tx });
|
|
1108
|
+
if (receipt.status !== "success") {
|
|
1109
|
+
return {
|
|
1110
|
+
success: false,
|
|
1111
|
+
errorReason: ErrInvalidTransactionState,
|
|
1112
|
+
transaction: tx,
|
|
1113
|
+
network: payload.accepted.network,
|
|
1114
|
+
payer
|
|
1115
|
+
};
|
|
1116
|
+
}
|
|
1117
|
+
return {
|
|
1118
|
+
success: true,
|
|
1119
|
+
transaction: tx,
|
|
1120
|
+
network: payload.accepted.network,
|
|
1121
|
+
payer
|
|
1122
|
+
};
|
|
1123
|
+
}
|
|
1124
|
+
function mapSettleError(error, payload, payer) {
|
|
1125
|
+
let errorReason = ErrTransactionFailed;
|
|
1126
|
+
if (error instanceof Error) {
|
|
1127
|
+
const message = error.message;
|
|
1128
|
+
if (message.includes("Permit2612AmountMismatch")) {
|
|
1129
|
+
errorReason = ErrPermit2612AmountMismatch;
|
|
1130
|
+
} else if (message.includes("InvalidAmount")) {
|
|
1131
|
+
errorReason = ErrPermit2InvalidAmount;
|
|
1132
|
+
} else if (message.includes("InvalidDestination")) {
|
|
1133
|
+
errorReason = ErrPermit2InvalidDestination;
|
|
1134
|
+
} else if (message.includes("InvalidOwner")) {
|
|
1135
|
+
errorReason = ErrPermit2InvalidOwner;
|
|
1136
|
+
} else if (message.includes("PaymentTooEarly")) {
|
|
1137
|
+
errorReason = ErrPermit2PaymentTooEarly;
|
|
1138
|
+
} else if (message.includes("InvalidSignature") || message.includes("SignatureExpired")) {
|
|
1139
|
+
errorReason = ErrPermit2InvalidSignature;
|
|
1140
|
+
} else if (message.includes("InvalidNonce")) {
|
|
1141
|
+
errorReason = ErrPermit2InvalidNonce;
|
|
1142
|
+
} else if (message.includes("erc20_approval_tx_failed")) {
|
|
1143
|
+
errorReason = ErrErc20ApprovalTxFailed;
|
|
1144
|
+
} else {
|
|
1145
|
+
errorReason = `${ErrTransactionFailed}: ${message.slice(0, 500)}`;
|
|
1146
|
+
}
|
|
1147
|
+
}
|
|
1148
|
+
return {
|
|
1149
|
+
success: false,
|
|
1150
|
+
errorReason,
|
|
1151
|
+
transaction: "",
|
|
1152
|
+
network: payload.accepted.network,
|
|
1153
|
+
payer
|
|
1154
|
+
};
|
|
1155
|
+
}
|
|
1156
|
+
function validateEip2612PermitForPayment(info, payer, tokenAddress) {
|
|
1157
|
+
if (!validateEip2612GasSponsoringInfo(info)) {
|
|
1158
|
+
return { isValid: false, invalidReason: ErrInvalidEip2612ExtensionFormat };
|
|
1159
|
+
}
|
|
1160
|
+
if ((0, import_viem6.getAddress)(info.from) !== (0, import_viem6.getAddress)(payer)) {
|
|
1161
|
+
return { isValid: false, invalidReason: ErrEip2612FromMismatch };
|
|
1162
|
+
}
|
|
1163
|
+
if ((0, import_viem6.getAddress)(info.asset) !== tokenAddress) {
|
|
1164
|
+
return { isValid: false, invalidReason: ErrEip2612AssetMismatch };
|
|
1165
|
+
}
|
|
1166
|
+
if ((0, import_viem6.getAddress)(info.spender) !== (0, import_viem6.getAddress)(PERMIT2_ADDRESS)) {
|
|
1167
|
+
return { isValid: false, invalidReason: ErrEip2612SpenderNotPermit2 };
|
|
1168
|
+
}
|
|
1169
|
+
const now = Math.floor(Date.now() / 1e3);
|
|
1170
|
+
if (BigInt(info.deadline) < BigInt(now + 6)) {
|
|
1171
|
+
return { isValid: false, invalidReason: ErrEip2612DeadlineExpired };
|
|
1172
|
+
}
|
|
1173
|
+
return { isValid: true };
|
|
1174
|
+
}
|
|
1175
|
+
|
|
1176
|
+
// src/exact/facilitator/permit2.ts
|
|
1177
|
+
async function verifyPermit2(signer, payload, requirements, permit2Payload, context, options) {
|
|
1178
|
+
const payer = permit2Payload.permit2Authorization.from;
|
|
1179
|
+
if (payload.accepted.scheme !== "exact" || requirements.scheme !== "exact") {
|
|
1180
|
+
return {
|
|
1181
|
+
isValid: false,
|
|
1182
|
+
invalidReason: ErrUnsupportedPayloadType,
|
|
1183
|
+
payer
|
|
1184
|
+
};
|
|
1185
|
+
}
|
|
1186
|
+
if (payload.accepted.network !== requirements.network) {
|
|
1187
|
+
return {
|
|
1188
|
+
isValid: false,
|
|
1189
|
+
invalidReason: ErrNetworkMismatch,
|
|
1190
|
+
payer
|
|
1191
|
+
};
|
|
1192
|
+
}
|
|
1193
|
+
const chainId = getEvmChainId(requirements.network);
|
|
1194
|
+
const tokenAddress = (0, import_viem7.getAddress)(requirements.asset);
|
|
1195
|
+
if ((0, import_viem7.getAddress)(permit2Payload.permit2Authorization.spender) !== (0, import_viem7.getAddress)(x402ExactPermit2ProxyAddress)) {
|
|
1196
|
+
return {
|
|
1197
|
+
isValid: false,
|
|
1198
|
+
invalidReason: ErrPermit2InvalidSpender,
|
|
1199
|
+
payer
|
|
1200
|
+
};
|
|
1201
|
+
}
|
|
1202
|
+
if ((0, import_viem7.getAddress)(permit2Payload.permit2Authorization.witness.to) !== (0, import_viem7.getAddress)(requirements.payTo)) {
|
|
1203
|
+
return {
|
|
1204
|
+
isValid: false,
|
|
1205
|
+
invalidReason: ErrPermit2RecipientMismatch,
|
|
1206
|
+
payer
|
|
1207
|
+
};
|
|
1208
|
+
}
|
|
1209
|
+
const now = Math.floor(Date.now() / 1e3);
|
|
1210
|
+
if (BigInt(permit2Payload.permit2Authorization.deadline) < BigInt(now + 6)) {
|
|
1211
|
+
return {
|
|
1212
|
+
isValid: false,
|
|
1213
|
+
invalidReason: ErrPermit2DeadlineExpired,
|
|
1214
|
+
payer
|
|
661
1215
|
};
|
|
662
1216
|
}
|
|
663
1217
|
if (BigInt(permit2Payload.permit2Authorization.witness.validAfter) > BigInt(now)) {
|
|
664
1218
|
return {
|
|
665
1219
|
isValid: false,
|
|
666
|
-
invalidReason:
|
|
1220
|
+
invalidReason: ErrPermit2NotYetValid,
|
|
667
1221
|
payer
|
|
668
1222
|
};
|
|
669
1223
|
}
|
|
670
1224
|
if (BigInt(permit2Payload.permit2Authorization.permitted.amount) !== BigInt(requirements.amount)) {
|
|
671
1225
|
return {
|
|
672
1226
|
isValid: false,
|
|
673
|
-
invalidReason:
|
|
1227
|
+
invalidReason: ErrPermit2AmountMismatch,
|
|
674
1228
|
payer
|
|
675
1229
|
};
|
|
676
1230
|
}
|
|
677
|
-
if ((0,
|
|
1231
|
+
if ((0, import_viem7.getAddress)(permit2Payload.permit2Authorization.permitted.token) !== tokenAddress) {
|
|
678
1232
|
return {
|
|
679
1233
|
isValid: false,
|
|
680
|
-
invalidReason:
|
|
1234
|
+
invalidReason: ErrPermit2TokenMismatch,
|
|
681
1235
|
payer
|
|
682
1236
|
};
|
|
683
1237
|
}
|
|
@@ -691,144 +1245,132 @@ async function verifyPermit2(signer, payload, requirements, permit2Payload, cont
|
|
|
691
1245
|
},
|
|
692
1246
|
message: {
|
|
693
1247
|
permitted: {
|
|
694
|
-
token: (0,
|
|
1248
|
+
token: (0, import_viem7.getAddress)(permit2Payload.permit2Authorization.permitted.token),
|
|
695
1249
|
amount: BigInt(permit2Payload.permit2Authorization.permitted.amount)
|
|
696
1250
|
},
|
|
697
|
-
spender: (0,
|
|
1251
|
+
spender: (0, import_viem7.getAddress)(permit2Payload.permit2Authorization.spender),
|
|
698
1252
|
nonce: BigInt(permit2Payload.permit2Authorization.nonce),
|
|
699
1253
|
deadline: BigInt(permit2Payload.permit2Authorization.deadline),
|
|
700
1254
|
witness: {
|
|
701
|
-
to: (0,
|
|
1255
|
+
to: (0, import_viem7.getAddress)(permit2Payload.permit2Authorization.witness.to),
|
|
702
1256
|
validAfter: BigInt(permit2Payload.permit2Authorization.witness.validAfter)
|
|
703
1257
|
}
|
|
704
1258
|
}
|
|
705
1259
|
};
|
|
1260
|
+
let signatureValid = false;
|
|
706
1261
|
try {
|
|
707
|
-
|
|
1262
|
+
signatureValid = await signer.verifyTypedData({
|
|
708
1263
|
address: payer,
|
|
709
1264
|
...permit2TypedData,
|
|
710
1265
|
signature: permit2Payload.signature
|
|
711
1266
|
});
|
|
712
|
-
if (!isValid) {
|
|
713
|
-
return {
|
|
714
|
-
isValid: false,
|
|
715
|
-
invalidReason: "invalid_permit2_signature",
|
|
716
|
-
payer
|
|
717
|
-
};
|
|
718
|
-
}
|
|
719
1267
|
} catch {
|
|
720
|
-
|
|
721
|
-
isValid: false,
|
|
722
|
-
invalidReason: "invalid_permit2_signature",
|
|
723
|
-
payer
|
|
724
|
-
};
|
|
1268
|
+
signatureValid = false;
|
|
725
1269
|
}
|
|
726
|
-
|
|
727
|
-
signer
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
payer,
|
|
731
|
-
tokenAddress,
|
|
732
|
-
context
|
|
733
|
-
);
|
|
734
|
-
if (allowanceResult) {
|
|
735
|
-
return allowanceResult;
|
|
736
|
-
}
|
|
737
|
-
try {
|
|
738
|
-
const balance = await signer.readContract({
|
|
739
|
-
address: tokenAddress,
|
|
740
|
-
abi: eip3009ABI,
|
|
741
|
-
functionName: "balanceOf",
|
|
742
|
-
args: [payer]
|
|
743
|
-
});
|
|
744
|
-
if (balance < BigInt(requirements.amount)) {
|
|
1270
|
+
if (!signatureValid) {
|
|
1271
|
+
const bytecode = await signer.getCode({ address: payer });
|
|
1272
|
+
const isDeployedContract = bytecode && bytecode !== "0x";
|
|
1273
|
+
if (!isDeployedContract) {
|
|
745
1274
|
return {
|
|
746
1275
|
isValid: false,
|
|
747
|
-
invalidReason:
|
|
748
|
-
invalidMessage: `Insufficient funds to complete the payment. Required: ${requirements.amount} ${requirements.asset}, Available: ${balance.toString()} ${requirements.asset}. Please add funds to your wallet and try again.`,
|
|
1276
|
+
invalidReason: ErrPermit2InvalidSignature,
|
|
749
1277
|
payer
|
|
750
1278
|
};
|
|
751
1279
|
}
|
|
752
|
-
} catch {
|
|
753
1280
|
}
|
|
754
|
-
|
|
755
|
-
isValid: true,
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
const allowance = await signer.readContract({
|
|
763
|
-
address: tokenAddress,
|
|
764
|
-
abi: erc20AllowanceAbi,
|
|
765
|
-
functionName: "allowance",
|
|
766
|
-
args: [payer, PERMIT2_ADDRESS]
|
|
767
|
-
});
|
|
768
|
-
if (allowance >= BigInt(requirements.amount)) {
|
|
769
|
-
return null;
|
|
1281
|
+
if (options?.simulate === false) {
|
|
1282
|
+
return { isValid: true, invalidReason: void 0, payer };
|
|
1283
|
+
}
|
|
1284
|
+
const eip2612Info = extractEip2612GasSponsoringInfo(payload);
|
|
1285
|
+
if (eip2612Info) {
|
|
1286
|
+
const fieldResult = validateEip2612PermitForPayment(eip2612Info, payer, tokenAddress);
|
|
1287
|
+
if (!fieldResult.isValid) {
|
|
1288
|
+
return { isValid: false, invalidReason: fieldResult.invalidReason, payer };
|
|
770
1289
|
}
|
|
771
|
-
const
|
|
772
|
-
if (
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
1290
|
+
const simOk2 = await simulatePermit2SettleWithPermit(signer, permit2Payload, eip2612Info);
|
|
1291
|
+
if (!simOk2) {
|
|
1292
|
+
return diagnosePermit2SimulationFailure(
|
|
1293
|
+
signer,
|
|
1294
|
+
tokenAddress,
|
|
1295
|
+
permit2Payload,
|
|
1296
|
+
requirements.amount
|
|
1297
|
+
);
|
|
778
1298
|
}
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
return
|
|
1299
|
+
return { isValid: true, invalidReason: void 0, payer };
|
|
1300
|
+
}
|
|
1301
|
+
const erc20GasSponsorshipExtension = context?.getExtension(
|
|
1302
|
+
ERC20_APPROVAL_GAS_SPONSORING_KEY
|
|
1303
|
+
);
|
|
1304
|
+
if (erc20GasSponsorshipExtension) {
|
|
1305
|
+
const erc20Info = extractErc20ApprovalGasSponsoringInfo(payload);
|
|
1306
|
+
if (erc20Info) {
|
|
1307
|
+
const fieldResult = await validateErc20ApprovalForPayment(erc20Info, payer, tokenAddress);
|
|
1308
|
+
if (!fieldResult.isValid) {
|
|
1309
|
+
return { isValid: false, invalidReason: fieldResult.invalidReason, payer };
|
|
790
1310
|
}
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
1311
|
+
const extensionSigner = resolveErc20ApprovalExtensionSigner(
|
|
1312
|
+
erc20GasSponsorshipExtension,
|
|
1313
|
+
requirements.network
|
|
1314
|
+
);
|
|
1315
|
+
if (extensionSigner?.simulateTransactions) {
|
|
1316
|
+
const simOk2 = await simulatePermit2SettleWithErc20Approval(
|
|
1317
|
+
extensionSigner,
|
|
1318
|
+
permit2Payload,
|
|
1319
|
+
erc20Info
|
|
1320
|
+
);
|
|
1321
|
+
if (!simOk2) {
|
|
1322
|
+
return diagnosePermit2SimulationFailure(
|
|
1323
|
+
signer,
|
|
1324
|
+
tokenAddress,
|
|
1325
|
+
permit2Payload,
|
|
1326
|
+
requirements.amount
|
|
1327
|
+
);
|
|
1328
|
+
}
|
|
1329
|
+
return { isValid: true, invalidReason: void 0, payer };
|
|
799
1330
|
}
|
|
1331
|
+
return checkPermit2Prerequisites(signer, tokenAddress, payer, requirements.amount);
|
|
800
1332
|
}
|
|
801
|
-
return null;
|
|
802
1333
|
}
|
|
1334
|
+
const simOk = await simulatePermit2Settle(signer, permit2Payload);
|
|
1335
|
+
if (!simOk) {
|
|
1336
|
+
return diagnosePermit2SimulationFailure(
|
|
1337
|
+
signer,
|
|
1338
|
+
tokenAddress,
|
|
1339
|
+
permit2Payload,
|
|
1340
|
+
requirements.amount
|
|
1341
|
+
);
|
|
1342
|
+
}
|
|
1343
|
+
return { isValid: true, invalidReason: void 0, payer };
|
|
803
1344
|
}
|
|
804
|
-
async function settlePermit2(signer, payload, requirements, permit2Payload, context) {
|
|
1345
|
+
async function settlePermit2(signer, payload, requirements, permit2Payload, context, config) {
|
|
805
1346
|
const payer = permit2Payload.permit2Authorization.from;
|
|
806
|
-
const valid = await verifyPermit2(signer, payload, requirements, permit2Payload, context
|
|
1347
|
+
const valid = await verifyPermit2(signer, payload, requirements, permit2Payload, context, {
|
|
1348
|
+
simulate: config?.simulateInSettle ?? false
|
|
1349
|
+
});
|
|
807
1350
|
if (!valid.isValid) {
|
|
808
1351
|
return {
|
|
809
1352
|
success: false,
|
|
810
1353
|
network: payload.accepted.network,
|
|
811
1354
|
transaction: "",
|
|
812
|
-
errorReason: valid.invalidReason ??
|
|
1355
|
+
errorReason: valid.invalidReason ?? ErrInvalidScheme,
|
|
813
1356
|
payer
|
|
814
1357
|
};
|
|
815
1358
|
}
|
|
816
|
-
const eip2612Info =
|
|
1359
|
+
const eip2612Info = extractEip2612GasSponsoringInfo(payload);
|
|
817
1360
|
if (eip2612Info) {
|
|
818
1361
|
return _settlePermit2WithEIP2612(signer, payload, permit2Payload, eip2612Info);
|
|
819
1362
|
}
|
|
820
|
-
const erc20Info =
|
|
1363
|
+
const erc20Info = extractErc20ApprovalGasSponsoringInfo(payload);
|
|
821
1364
|
if (erc20Info) {
|
|
822
1365
|
const erc20GasSponsorshipExtension = context?.getExtension(
|
|
823
|
-
|
|
1366
|
+
ERC20_APPROVAL_GAS_SPONSORING_KEY
|
|
824
1367
|
);
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
);
|
|
1368
|
+
const extensionSigner = resolveErc20ApprovalExtensionSigner(
|
|
1369
|
+
erc20GasSponsorshipExtension,
|
|
1370
|
+
payload.accepted.network
|
|
1371
|
+
);
|
|
1372
|
+
if (extensionSigner) {
|
|
1373
|
+
return _settlePermit2WithERC20Approval(extensionSigner, payload, permit2Payload, erc20Info);
|
|
832
1374
|
}
|
|
833
1375
|
}
|
|
834
1376
|
return _settlePermit2Direct(signer, payload, permit2Payload);
|
|
@@ -849,69 +1391,26 @@ async function _settlePermit2WithEIP2612(signer, payload, permit2Payload, eip261
|
|
|
849
1391
|
s,
|
|
850
1392
|
v
|
|
851
1393
|
},
|
|
852
|
-
|
|
853
|
-
permitted: {
|
|
854
|
-
token: (0, import_viem4.getAddress)(permit2Payload.permit2Authorization.permitted.token),
|
|
855
|
-
amount: BigInt(permit2Payload.permit2Authorization.permitted.amount)
|
|
856
|
-
},
|
|
857
|
-
nonce: BigInt(permit2Payload.permit2Authorization.nonce),
|
|
858
|
-
deadline: BigInt(permit2Payload.permit2Authorization.deadline)
|
|
859
|
-
},
|
|
860
|
-
(0, import_viem4.getAddress)(payer),
|
|
861
|
-
{
|
|
862
|
-
to: (0, import_viem4.getAddress)(permit2Payload.permit2Authorization.witness.to),
|
|
863
|
-
validAfter: BigInt(permit2Payload.permit2Authorization.witness.validAfter)
|
|
864
|
-
},
|
|
865
|
-
permit2Payload.signature
|
|
1394
|
+
...buildPermit2SettleArgs(permit2Payload)
|
|
866
1395
|
]
|
|
867
1396
|
});
|
|
868
|
-
return
|
|
1397
|
+
return waitAndReturn(signer, tx, payload, payer);
|
|
869
1398
|
} catch (error) {
|
|
870
|
-
return
|
|
1399
|
+
return mapSettleError(error, payload, payer);
|
|
871
1400
|
}
|
|
872
1401
|
}
|
|
873
1402
|
async function _settlePermit2WithERC20Approval(extensionSigner, payload, permit2Payload, erc20Info) {
|
|
874
1403
|
const payer = permit2Payload.permit2Authorization.from;
|
|
875
1404
|
try {
|
|
876
|
-
const
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
return {
|
|
884
|
-
success: false,
|
|
885
|
-
errorReason: "erc20_approval_tx_failed",
|
|
886
|
-
transaction: approvalTxHash,
|
|
887
|
-
network: payload.accepted.network,
|
|
888
|
-
payer
|
|
889
|
-
};
|
|
890
|
-
}
|
|
891
|
-
const tx = await extensionSigner.writeContract({
|
|
892
|
-
address: x402ExactPermit2ProxyAddress,
|
|
893
|
-
abi: x402ExactPermit2ProxyABI,
|
|
894
|
-
functionName: "settle",
|
|
895
|
-
args: [
|
|
896
|
-
{
|
|
897
|
-
permitted: {
|
|
898
|
-
token: (0, import_viem4.getAddress)(permit2Payload.permit2Authorization.permitted.token),
|
|
899
|
-
amount: BigInt(permit2Payload.permit2Authorization.permitted.amount)
|
|
900
|
-
},
|
|
901
|
-
nonce: BigInt(permit2Payload.permit2Authorization.nonce),
|
|
902
|
-
deadline: BigInt(permit2Payload.permit2Authorization.deadline)
|
|
903
|
-
},
|
|
904
|
-
(0, import_viem4.getAddress)(payer),
|
|
905
|
-
{
|
|
906
|
-
to: (0, import_viem4.getAddress)(permit2Payload.permit2Authorization.witness.to),
|
|
907
|
-
validAfter: BigInt(permit2Payload.permit2Authorization.witness.validAfter)
|
|
908
|
-
},
|
|
909
|
-
permit2Payload.signature
|
|
910
|
-
]
|
|
911
|
-
});
|
|
912
|
-
return _waitAndReturn(extensionSigner, tx, payload, payer);
|
|
1405
|
+
const settleData = encodePermit2SettleCalldata(permit2Payload);
|
|
1406
|
+
const txHashes = await extensionSigner.sendTransactions([
|
|
1407
|
+
erc20Info.signedTransaction,
|
|
1408
|
+
{ to: x402ExactPermit2ProxyAddress, data: settleData, gas: BigInt(3e5) }
|
|
1409
|
+
]);
|
|
1410
|
+
const settleTxHash = txHashes[txHashes.length - 1];
|
|
1411
|
+
return waitAndReturn(extensionSigner, settleTxHash, payload, payer);
|
|
913
1412
|
} catch (error) {
|
|
914
|
-
return
|
|
1413
|
+
return mapSettleError(error, payload, payer);
|
|
915
1414
|
}
|
|
916
1415
|
}
|
|
917
1416
|
async function _settlePermit2Direct(signer, payload, permit2Payload) {
|
|
@@ -921,106 +1420,12 @@ async function _settlePermit2Direct(signer, payload, permit2Payload) {
|
|
|
921
1420
|
address: x402ExactPermit2ProxyAddress,
|
|
922
1421
|
abi: x402ExactPermit2ProxyABI,
|
|
923
1422
|
functionName: "settle",
|
|
924
|
-
args:
|
|
925
|
-
{
|
|
926
|
-
permitted: {
|
|
927
|
-
token: (0, import_viem4.getAddress)(permit2Payload.permit2Authorization.permitted.token),
|
|
928
|
-
amount: BigInt(permit2Payload.permit2Authorization.permitted.amount)
|
|
929
|
-
},
|
|
930
|
-
nonce: BigInt(permit2Payload.permit2Authorization.nonce),
|
|
931
|
-
deadline: BigInt(permit2Payload.permit2Authorization.deadline)
|
|
932
|
-
},
|
|
933
|
-
(0, import_viem4.getAddress)(payer),
|
|
934
|
-
{
|
|
935
|
-
to: (0, import_viem4.getAddress)(permit2Payload.permit2Authorization.witness.to),
|
|
936
|
-
validAfter: BigInt(permit2Payload.permit2Authorization.witness.validAfter)
|
|
937
|
-
},
|
|
938
|
-
permit2Payload.signature
|
|
939
|
-
]
|
|
1423
|
+
args: buildPermit2SettleArgs(permit2Payload)
|
|
940
1424
|
});
|
|
941
|
-
return
|
|
1425
|
+
return waitAndReturn(signer, tx, payload, payer);
|
|
942
1426
|
} catch (error) {
|
|
943
|
-
return
|
|
944
|
-
}
|
|
945
|
-
}
|
|
946
|
-
async function _waitAndReturn(signer, tx, payload, payer) {
|
|
947
|
-
const receipt = await signer.waitForTransactionReceipt({ hash: tx });
|
|
948
|
-
if (receipt.status !== "success") {
|
|
949
|
-
return {
|
|
950
|
-
success: false,
|
|
951
|
-
errorReason: "invalid_transaction_state",
|
|
952
|
-
transaction: tx,
|
|
953
|
-
network: payload.accepted.network,
|
|
954
|
-
payer
|
|
955
|
-
};
|
|
956
|
-
}
|
|
957
|
-
return {
|
|
958
|
-
success: true,
|
|
959
|
-
transaction: tx,
|
|
960
|
-
network: payload.accepted.network,
|
|
961
|
-
payer
|
|
962
|
-
};
|
|
963
|
-
}
|
|
964
|
-
function _mapSettleError(error, payload, payer) {
|
|
965
|
-
let errorReason = "transaction_failed";
|
|
966
|
-
if (error instanceof Error) {
|
|
967
|
-
const message = error.message;
|
|
968
|
-
if (message.includes("Permit2612AmountMismatch")) {
|
|
969
|
-
errorReason = ErrPermit2612AmountMismatch;
|
|
970
|
-
} else if (message.includes("InvalidAmount")) {
|
|
971
|
-
errorReason = ErrPermit2InvalidAmount;
|
|
972
|
-
} else if (message.includes("InvalidDestination")) {
|
|
973
|
-
errorReason = ErrPermit2InvalidDestination;
|
|
974
|
-
} else if (message.includes("InvalidOwner")) {
|
|
975
|
-
errorReason = ErrPermit2InvalidOwner;
|
|
976
|
-
} else if (message.includes("PaymentTooEarly")) {
|
|
977
|
-
errorReason = ErrPermit2PaymentTooEarly;
|
|
978
|
-
} else if (message.includes("InvalidSignature") || message.includes("SignatureExpired")) {
|
|
979
|
-
errorReason = ErrPermit2InvalidSignature;
|
|
980
|
-
} else if (message.includes("InvalidNonce")) {
|
|
981
|
-
errorReason = ErrPermit2InvalidNonce;
|
|
982
|
-
} else {
|
|
983
|
-
errorReason = `transaction_failed: ${message.slice(0, 500)}`;
|
|
984
|
-
}
|
|
985
|
-
}
|
|
986
|
-
return {
|
|
987
|
-
success: false,
|
|
988
|
-
errorReason,
|
|
989
|
-
transaction: "",
|
|
990
|
-
network: payload.accepted.network,
|
|
991
|
-
payer
|
|
992
|
-
};
|
|
993
|
-
}
|
|
994
|
-
function validateEip2612PermitForPayment(info, payer, tokenAddress) {
|
|
995
|
-
if (!(0, import_extensions2.validateEip2612GasSponsoringInfo)(info)) {
|
|
996
|
-
return { isValid: false, invalidReason: "invalid_eip2612_extension_format" };
|
|
1427
|
+
return mapSettleError(error, payload, payer);
|
|
997
1428
|
}
|
|
998
|
-
if ((0, import_viem4.getAddress)(info.from) !== (0, import_viem4.getAddress)(payer)) {
|
|
999
|
-
return { isValid: false, invalidReason: "eip2612_from_mismatch" };
|
|
1000
|
-
}
|
|
1001
|
-
if ((0, import_viem4.getAddress)(info.asset) !== tokenAddress) {
|
|
1002
|
-
return { isValid: false, invalidReason: "eip2612_asset_mismatch" };
|
|
1003
|
-
}
|
|
1004
|
-
if ((0, import_viem4.getAddress)(info.spender) !== (0, import_viem4.getAddress)(PERMIT2_ADDRESS)) {
|
|
1005
|
-
return { isValid: false, invalidReason: "eip2612_spender_not_permit2" };
|
|
1006
|
-
}
|
|
1007
|
-
const now = Math.floor(Date.now() / 1e3);
|
|
1008
|
-
if (BigInt(info.deadline) < BigInt(now + 6)) {
|
|
1009
|
-
return { isValid: false, invalidReason: "eip2612_deadline_expired" };
|
|
1010
|
-
}
|
|
1011
|
-
return { isValid: true };
|
|
1012
|
-
}
|
|
1013
|
-
function splitEip2612Signature(signature) {
|
|
1014
|
-
const sig = signature.startsWith("0x") ? signature.slice(2) : signature;
|
|
1015
|
-
if (sig.length !== 130) {
|
|
1016
|
-
throw new Error(
|
|
1017
|
-
`invalid EIP-2612 signature length: expected 65 bytes (130 hex chars), got ${sig.length / 2} bytes`
|
|
1018
|
-
);
|
|
1019
|
-
}
|
|
1020
|
-
const r = `0x${sig.slice(0, 64)}`;
|
|
1021
|
-
const s = `0x${sig.slice(64, 128)}`;
|
|
1022
|
-
const v = parseInt(sig.slice(128, 130), 16);
|
|
1023
|
-
return { v, r, s };
|
|
1024
1429
|
}
|
|
1025
1430
|
|
|
1026
1431
|
// src/exact/facilitator/scheme.ts
|
|
@@ -1036,7 +1441,8 @@ var ExactEvmScheme = class {
|
|
|
1036
1441
|
this.scheme = "exact";
|
|
1037
1442
|
this.caipFamily = "eip155:*";
|
|
1038
1443
|
this.config = {
|
|
1039
|
-
deployERC4337WithEIP6492: config?.deployERC4337WithEIP6492 ?? false
|
|
1444
|
+
deployERC4337WithEIP6492: config?.deployERC4337WithEIP6492 ?? false,
|
|
1445
|
+
simulateInSettle: config?.simulateInSettle ?? false
|
|
1040
1446
|
};
|
|
1041
1447
|
}
|
|
1042
1448
|
/**
|
|
@@ -1067,7 +1473,8 @@ var ExactEvmScheme = class {
|
|
|
1067
1473
|
*/
|
|
1068
1474
|
async verify(payload, requirements, context) {
|
|
1069
1475
|
const rawPayload = payload.payload;
|
|
1070
|
-
|
|
1476
|
+
const isPermit2 = isPermit2Payload(rawPayload);
|
|
1477
|
+
if (isPermit2) {
|
|
1071
1478
|
return verifyPermit2(this.signer, payload, requirements, rawPayload, context);
|
|
1072
1479
|
}
|
|
1073
1480
|
const eip3009Payload = rawPayload;
|
|
@@ -1083,8 +1490,11 @@ var ExactEvmScheme = class {
|
|
|
1083
1490
|
*/
|
|
1084
1491
|
async settle(payload, requirements, context) {
|
|
1085
1492
|
const rawPayload = payload.payload;
|
|
1086
|
-
|
|
1087
|
-
|
|
1493
|
+
const isPermit2 = isPermit2Payload(rawPayload);
|
|
1494
|
+
if (isPermit2) {
|
|
1495
|
+
return settlePermit2(this.signer, payload, requirements, rawPayload, context, {
|
|
1496
|
+
simulateInSettle: this.config.simulateInSettle
|
|
1497
|
+
});
|
|
1088
1498
|
}
|
|
1089
1499
|
const eip3009Payload = rawPayload;
|
|
1090
1500
|
return settleEIP3009(this.signer, payload, requirements, eip3009Payload, this.config);
|
|
@@ -1092,10 +1502,10 @@ var ExactEvmScheme = class {
|
|
|
1092
1502
|
};
|
|
1093
1503
|
|
|
1094
1504
|
// src/exact/v1/facilitator/scheme.ts
|
|
1095
|
-
var
|
|
1505
|
+
var import_viem9 = require("viem");
|
|
1096
1506
|
|
|
1097
1507
|
// src/exact/v1/client/scheme.ts
|
|
1098
|
-
var
|
|
1508
|
+
var import_viem8 = require("viem");
|
|
1099
1509
|
|
|
1100
1510
|
// src/v1/index.ts
|
|
1101
1511
|
var EVM_NETWORK_CHAIN_ID_MAP = {
|
|
@@ -1141,7 +1551,8 @@ var ExactEvmSchemeV12 = class {
|
|
|
1141
1551
|
this.scheme = "exact";
|
|
1142
1552
|
this.caipFamily = "eip155:*";
|
|
1143
1553
|
this.config = {
|
|
1144
|
-
deployERC4337WithEIP6492: config?.deployERC4337WithEIP6492 ?? false
|
|
1554
|
+
deployERC4337WithEIP6492: config?.deployERC4337WithEIP6492 ?? false,
|
|
1555
|
+
simulateInSettle: config?.simulateInSettle ?? false
|
|
1145
1556
|
};
|
|
1146
1557
|
}
|
|
1147
1558
|
/**
|
|
@@ -1172,14 +1583,95 @@ var ExactEvmSchemeV12 = class {
|
|
|
1172
1583
|
* @returns Promise resolving to verification response
|
|
1173
1584
|
*/
|
|
1174
1585
|
async verify(payload, requirements) {
|
|
1586
|
+
return this._verify(payload, requirements);
|
|
1587
|
+
}
|
|
1588
|
+
/**
|
|
1589
|
+
* Settles a payment by executing the transfer (V1).
|
|
1590
|
+
*
|
|
1591
|
+
* @param payload - The payment payload to settle
|
|
1592
|
+
* @param requirements - The payment requirements
|
|
1593
|
+
* @returns Promise resolving to settlement response
|
|
1594
|
+
*/
|
|
1595
|
+
async settle(payload, requirements) {
|
|
1596
|
+
const payloadV1 = payload;
|
|
1597
|
+
const exactEvmPayload = payload.payload;
|
|
1598
|
+
const valid = await this._verify(payload, requirements, {
|
|
1599
|
+
simulate: this.config.simulateInSettle ?? false
|
|
1600
|
+
});
|
|
1601
|
+
if (!valid.isValid) {
|
|
1602
|
+
return {
|
|
1603
|
+
success: false,
|
|
1604
|
+
network: payloadV1.network,
|
|
1605
|
+
transaction: "",
|
|
1606
|
+
errorReason: valid.invalidReason ?? ErrInvalidScheme,
|
|
1607
|
+
payer: exactEvmPayload.authorization.from
|
|
1608
|
+
};
|
|
1609
|
+
}
|
|
1610
|
+
try {
|
|
1611
|
+
const { address: factoryAddress, data: factoryCalldata } = (0, import_viem9.parseErc6492Signature)(
|
|
1612
|
+
exactEvmPayload.signature
|
|
1613
|
+
);
|
|
1614
|
+
if (this.config.deployERC4337WithEIP6492 && factoryAddress && factoryCalldata && !(0, import_viem9.isAddressEqual)(factoryAddress, "0x0000000000000000000000000000000000000000")) {
|
|
1615
|
+
const payerAddress = exactEvmPayload.authorization.from;
|
|
1616
|
+
const bytecode = await this.signer.getCode({ address: payerAddress });
|
|
1617
|
+
if (!bytecode || bytecode === "0x") {
|
|
1618
|
+
const deployTx = await this.signer.sendTransaction({
|
|
1619
|
+
to: factoryAddress,
|
|
1620
|
+
data: factoryCalldata
|
|
1621
|
+
});
|
|
1622
|
+
await this.signer.waitForTransactionReceipt({ hash: deployTx });
|
|
1623
|
+
}
|
|
1624
|
+
}
|
|
1625
|
+
const tx = await executeTransferWithAuthorization(
|
|
1626
|
+
this.signer,
|
|
1627
|
+
(0, import_viem9.getAddress)(requirements.asset),
|
|
1628
|
+
exactEvmPayload
|
|
1629
|
+
);
|
|
1630
|
+
const receipt = await this.signer.waitForTransactionReceipt({ hash: tx });
|
|
1631
|
+
if (receipt.status !== "success") {
|
|
1632
|
+
return {
|
|
1633
|
+
success: false,
|
|
1634
|
+
errorReason: ErrTransactionFailed,
|
|
1635
|
+
transaction: tx,
|
|
1636
|
+
network: payloadV1.network,
|
|
1637
|
+
payer: exactEvmPayload.authorization.from
|
|
1638
|
+
};
|
|
1639
|
+
}
|
|
1640
|
+
return {
|
|
1641
|
+
success: true,
|
|
1642
|
+
transaction: tx,
|
|
1643
|
+
network: payloadV1.network,
|
|
1644
|
+
payer: exactEvmPayload.authorization.from
|
|
1645
|
+
};
|
|
1646
|
+
} catch (error) {
|
|
1647
|
+
return {
|
|
1648
|
+
success: false,
|
|
1649
|
+
errorReason: error instanceof Error ? error.message : ErrTransactionFailed,
|
|
1650
|
+
transaction: "",
|
|
1651
|
+
network: payloadV1.network,
|
|
1652
|
+
payer: exactEvmPayload.authorization.from
|
|
1653
|
+
};
|
|
1654
|
+
}
|
|
1655
|
+
}
|
|
1656
|
+
/**
|
|
1657
|
+
* Internal verify with optional simulation control.
|
|
1658
|
+
*
|
|
1659
|
+
* @param payload - The payment payload to verify
|
|
1660
|
+
* @param requirements - The payment requirements
|
|
1661
|
+
* @param options - Verification options (e.g. simulate)
|
|
1662
|
+
* @returns Promise resolving to verification response
|
|
1663
|
+
*/
|
|
1664
|
+
async _verify(payload, requirements, options) {
|
|
1175
1665
|
const requirementsV1 = requirements;
|
|
1176
1666
|
const payloadV1 = payload;
|
|
1177
1667
|
const exactEvmPayload = payload.payload;
|
|
1668
|
+
const payer = exactEvmPayload.authorization.from;
|
|
1669
|
+
let eip6492Deployment;
|
|
1178
1670
|
if (payloadV1.scheme !== "exact" || requirements.scheme !== "exact") {
|
|
1179
1671
|
return {
|
|
1180
1672
|
isValid: false,
|
|
1181
|
-
invalidReason:
|
|
1182
|
-
payer
|
|
1673
|
+
invalidReason: ErrInvalidScheme,
|
|
1674
|
+
payer
|
|
1183
1675
|
};
|
|
1184
1676
|
}
|
|
1185
1677
|
let chainId;
|
|
@@ -1188,24 +1680,24 @@ var ExactEvmSchemeV12 = class {
|
|
|
1188
1680
|
} catch {
|
|
1189
1681
|
return {
|
|
1190
1682
|
isValid: false,
|
|
1191
|
-
invalidReason:
|
|
1192
|
-
payer
|
|
1683
|
+
invalidReason: ErrNetworkMismatch,
|
|
1684
|
+
payer
|
|
1193
1685
|
};
|
|
1194
1686
|
}
|
|
1195
1687
|
if (!requirements.extra?.name || !requirements.extra?.version) {
|
|
1196
1688
|
return {
|
|
1197
1689
|
isValid: false,
|
|
1198
|
-
invalidReason:
|
|
1199
|
-
payer
|
|
1690
|
+
invalidReason: ErrMissingEip712Domain,
|
|
1691
|
+
payer
|
|
1200
1692
|
};
|
|
1201
1693
|
}
|
|
1202
1694
|
const { name, version } = requirements.extra;
|
|
1203
|
-
const erc20Address = (0,
|
|
1695
|
+
const erc20Address = (0, import_viem9.getAddress)(requirements.asset);
|
|
1204
1696
|
if (payloadV1.network !== requirements.network) {
|
|
1205
1697
|
return {
|
|
1206
1698
|
isValid: false,
|
|
1207
|
-
invalidReason:
|
|
1208
|
-
payer
|
|
1699
|
+
invalidReason: ErrNetworkMismatch,
|
|
1700
|
+
payer
|
|
1209
1701
|
};
|
|
1210
1702
|
}
|
|
1211
1703
|
const permitTypedData = {
|
|
@@ -1226,210 +1718,97 @@ var ExactEvmSchemeV12 = class {
|
|
|
1226
1718
|
nonce: exactEvmPayload.authorization.nonce
|
|
1227
1719
|
}
|
|
1228
1720
|
};
|
|
1721
|
+
let isValid = false;
|
|
1229
1722
|
try {
|
|
1230
|
-
|
|
1231
|
-
address:
|
|
1723
|
+
isValid = await this.signer.verifyTypedData({
|
|
1724
|
+
address: payer,
|
|
1232
1725
|
...permitTypedData,
|
|
1233
1726
|
signature: exactEvmPayload.signature
|
|
1234
1727
|
});
|
|
1235
|
-
|
|
1728
|
+
} catch {
|
|
1729
|
+
isValid = false;
|
|
1730
|
+
}
|
|
1731
|
+
const signature = exactEvmPayload.signature;
|
|
1732
|
+
const sigLen = signature.startsWith("0x") ? signature.length - 2 : signature.length;
|
|
1733
|
+
const erc6492Data = (0, import_viem9.parseErc6492Signature)(signature);
|
|
1734
|
+
const hasDeploymentInfo = erc6492Data.address && erc6492Data.data && !(0, import_viem9.isAddressEqual)(erc6492Data.address, "0x0000000000000000000000000000000000000000");
|
|
1735
|
+
if (hasDeploymentInfo) {
|
|
1736
|
+
eip6492Deployment = {
|
|
1737
|
+
factoryAddress: erc6492Data.address,
|
|
1738
|
+
factoryCalldata: erc6492Data.data
|
|
1739
|
+
};
|
|
1740
|
+
}
|
|
1741
|
+
if (!isValid) {
|
|
1742
|
+
const isSmartWallet = sigLen > 130;
|
|
1743
|
+
if (!isSmartWallet) {
|
|
1236
1744
|
return {
|
|
1237
1745
|
isValid: false,
|
|
1238
|
-
invalidReason:
|
|
1239
|
-
payer
|
|
1746
|
+
invalidReason: ErrInvalidSignature,
|
|
1747
|
+
payer
|
|
1240
1748
|
};
|
|
1241
1749
|
}
|
|
1242
|
-
|
|
1243
|
-
const
|
|
1244
|
-
|
|
1245
|
-
const isSmartWallet = signatureLength > 130;
|
|
1246
|
-
if (isSmartWallet) {
|
|
1247
|
-
const payerAddress = exactEvmPayload.authorization.from;
|
|
1248
|
-
const bytecode = await this.signer.getCode({ address: payerAddress });
|
|
1249
|
-
if (!bytecode || bytecode === "0x") {
|
|
1250
|
-
const erc6492Data = (0, import_viem6.parseErc6492Signature)(signature);
|
|
1251
|
-
const hasDeploymentInfo = erc6492Data.address && erc6492Data.data && !(0, import_viem6.isAddressEqual)(erc6492Data.address, "0x0000000000000000000000000000000000000000");
|
|
1252
|
-
if (!hasDeploymentInfo) {
|
|
1253
|
-
return {
|
|
1254
|
-
isValid: false,
|
|
1255
|
-
invalidReason: "invalid_exact_evm_payload_undeployed_smart_wallet",
|
|
1256
|
-
payer: payerAddress
|
|
1257
|
-
};
|
|
1258
|
-
}
|
|
1259
|
-
} else {
|
|
1260
|
-
return {
|
|
1261
|
-
isValid: false,
|
|
1262
|
-
invalidReason: "invalid_exact_evm_payload_signature",
|
|
1263
|
-
payer: exactEvmPayload.authorization.from
|
|
1264
|
-
};
|
|
1265
|
-
}
|
|
1266
|
-
} else {
|
|
1750
|
+
const bytecode = await this.signer.getCode({ address: payer });
|
|
1751
|
+
const isDeployed = bytecode && bytecode !== "0x";
|
|
1752
|
+
if (!isDeployed && !hasDeploymentInfo) {
|
|
1267
1753
|
return {
|
|
1268
1754
|
isValid: false,
|
|
1269
|
-
invalidReason:
|
|
1270
|
-
payer
|
|
1755
|
+
invalidReason: ErrUndeployedSmartWallet,
|
|
1756
|
+
payer
|
|
1271
1757
|
};
|
|
1272
1758
|
}
|
|
1273
1759
|
}
|
|
1274
|
-
if ((0,
|
|
1760
|
+
if ((0, import_viem9.getAddress)(exactEvmPayload.authorization.to) !== (0, import_viem9.getAddress)(requirements.payTo)) {
|
|
1275
1761
|
return {
|
|
1276
1762
|
isValid: false,
|
|
1277
|
-
invalidReason:
|
|
1278
|
-
payer
|
|
1763
|
+
invalidReason: ErrRecipientMismatch,
|
|
1764
|
+
payer
|
|
1279
1765
|
};
|
|
1280
1766
|
}
|
|
1281
1767
|
const now = Math.floor(Date.now() / 1e3);
|
|
1282
1768
|
if (BigInt(exactEvmPayload.authorization.validBefore) < BigInt(now + 6)) {
|
|
1283
1769
|
return {
|
|
1284
1770
|
isValid: false,
|
|
1285
|
-
invalidReason:
|
|
1286
|
-
payer
|
|
1771
|
+
invalidReason: ErrValidBeforeExpired,
|
|
1772
|
+
payer
|
|
1287
1773
|
};
|
|
1288
1774
|
}
|
|
1289
1775
|
if (BigInt(exactEvmPayload.authorization.validAfter) > BigInt(now)) {
|
|
1290
1776
|
return {
|
|
1291
1777
|
isValid: false,
|
|
1292
|
-
invalidReason:
|
|
1293
|
-
payer
|
|
1778
|
+
invalidReason: ErrValidAfterInFuture,
|
|
1779
|
+
payer
|
|
1294
1780
|
};
|
|
1295
1781
|
}
|
|
1296
|
-
try {
|
|
1297
|
-
const balance = await this.signer.readContract({
|
|
1298
|
-
address: erc20Address,
|
|
1299
|
-
abi: eip3009ABI,
|
|
1300
|
-
functionName: "balanceOf",
|
|
1301
|
-
args: [exactEvmPayload.authorization.from]
|
|
1302
|
-
});
|
|
1303
|
-
if (BigInt(balance) < BigInt(requirementsV1.maxAmountRequired)) {
|
|
1304
|
-
return {
|
|
1305
|
-
isValid: false,
|
|
1306
|
-
invalidReason: "insufficient_funds",
|
|
1307
|
-
invalidMessage: `Insufficient funds to complete the payment. Required: ${requirementsV1.maxAmountRequired} ${requirements.asset}, Available: ${balance.toString()} ${requirements.asset}. Please add funds to your wallet and try again.`,
|
|
1308
|
-
payer: exactEvmPayload.authorization.from
|
|
1309
|
-
};
|
|
1310
|
-
}
|
|
1311
|
-
} catch {
|
|
1312
|
-
}
|
|
1313
1782
|
if (BigInt(exactEvmPayload.authorization.value) !== BigInt(requirementsV1.maxAmountRequired)) {
|
|
1314
1783
|
return {
|
|
1315
1784
|
isValid: false,
|
|
1316
|
-
invalidReason:
|
|
1317
|
-
payer
|
|
1785
|
+
invalidReason: ErrInvalidAuthorizationValue,
|
|
1786
|
+
payer
|
|
1318
1787
|
};
|
|
1319
1788
|
}
|
|
1789
|
+
if (options?.simulate !== false) {
|
|
1790
|
+
const simulationSucceeded = await simulateEip3009Transfer(
|
|
1791
|
+
this.signer,
|
|
1792
|
+
erc20Address,
|
|
1793
|
+
exactEvmPayload,
|
|
1794
|
+
eip6492Deployment
|
|
1795
|
+
);
|
|
1796
|
+
if (!simulationSucceeded) {
|
|
1797
|
+
return diagnoseEip3009SimulationFailure(
|
|
1798
|
+
this.signer,
|
|
1799
|
+
erc20Address,
|
|
1800
|
+
exactEvmPayload,
|
|
1801
|
+
requirements,
|
|
1802
|
+
requirementsV1.maxAmountRequired
|
|
1803
|
+
);
|
|
1804
|
+
}
|
|
1805
|
+
}
|
|
1320
1806
|
return {
|
|
1321
1807
|
isValid: true,
|
|
1322
1808
|
invalidReason: void 0,
|
|
1323
|
-
payer
|
|
1809
|
+
payer
|
|
1324
1810
|
};
|
|
1325
1811
|
}
|
|
1326
|
-
/**
|
|
1327
|
-
* Settles a payment by executing the transfer (V1).
|
|
1328
|
-
*
|
|
1329
|
-
* @param payload - The payment payload to settle
|
|
1330
|
-
* @param requirements - The payment requirements
|
|
1331
|
-
* @returns Promise resolving to settlement response
|
|
1332
|
-
*/
|
|
1333
|
-
async settle(payload, requirements) {
|
|
1334
|
-
const payloadV1 = payload;
|
|
1335
|
-
const exactEvmPayload = payload.payload;
|
|
1336
|
-
const valid = await this.verify(payload, requirements);
|
|
1337
|
-
if (!valid.isValid) {
|
|
1338
|
-
return {
|
|
1339
|
-
success: false,
|
|
1340
|
-
network: payloadV1.network,
|
|
1341
|
-
transaction: "",
|
|
1342
|
-
errorReason: valid.invalidReason ?? "invalid_scheme",
|
|
1343
|
-
payer: exactEvmPayload.authorization.from
|
|
1344
|
-
};
|
|
1345
|
-
}
|
|
1346
|
-
try {
|
|
1347
|
-
const parseResult = (0, import_viem6.parseErc6492Signature)(exactEvmPayload.signature);
|
|
1348
|
-
const { signature, address: factoryAddress, data: factoryCalldata } = parseResult;
|
|
1349
|
-
if (this.config.deployERC4337WithEIP6492 && factoryAddress && factoryCalldata && !(0, import_viem6.isAddressEqual)(factoryAddress, "0x0000000000000000000000000000000000000000")) {
|
|
1350
|
-
const payerAddress = exactEvmPayload.authorization.from;
|
|
1351
|
-
const bytecode = await this.signer.getCode({ address: payerAddress });
|
|
1352
|
-
if (!bytecode || bytecode === "0x") {
|
|
1353
|
-
try {
|
|
1354
|
-
console.log(`Deploying ERC-4337 smart wallet for ${payerAddress} via EIP-6492`);
|
|
1355
|
-
const deployTx = await this.signer.sendTransaction({
|
|
1356
|
-
to: factoryAddress,
|
|
1357
|
-
data: factoryCalldata
|
|
1358
|
-
});
|
|
1359
|
-
await this.signer.waitForTransactionReceipt({ hash: deployTx });
|
|
1360
|
-
console.log(`Successfully deployed smart wallet for ${payerAddress}`);
|
|
1361
|
-
} catch (deployError) {
|
|
1362
|
-
console.error("Smart wallet deployment failed:", deployError);
|
|
1363
|
-
throw deployError;
|
|
1364
|
-
}
|
|
1365
|
-
} else {
|
|
1366
|
-
console.log(`Smart wallet for ${payerAddress} already deployed, skipping deployment`);
|
|
1367
|
-
}
|
|
1368
|
-
}
|
|
1369
|
-
const signatureLength = signature.startsWith("0x") ? signature.length - 2 : signature.length;
|
|
1370
|
-
const isECDSA = signatureLength === 130;
|
|
1371
|
-
let tx;
|
|
1372
|
-
if (isECDSA) {
|
|
1373
|
-
const parsedSig = (0, import_viem6.parseSignature)(signature);
|
|
1374
|
-
tx = await this.signer.writeContract({
|
|
1375
|
-
address: (0, import_viem6.getAddress)(requirements.asset),
|
|
1376
|
-
abi: eip3009ABI,
|
|
1377
|
-
functionName: "transferWithAuthorization",
|
|
1378
|
-
args: [
|
|
1379
|
-
(0, import_viem6.getAddress)(exactEvmPayload.authorization.from),
|
|
1380
|
-
(0, import_viem6.getAddress)(exactEvmPayload.authorization.to),
|
|
1381
|
-
BigInt(exactEvmPayload.authorization.value),
|
|
1382
|
-
BigInt(exactEvmPayload.authorization.validAfter),
|
|
1383
|
-
BigInt(exactEvmPayload.authorization.validBefore),
|
|
1384
|
-
exactEvmPayload.authorization.nonce,
|
|
1385
|
-
parsedSig.v || parsedSig.yParity,
|
|
1386
|
-
parsedSig.r,
|
|
1387
|
-
parsedSig.s
|
|
1388
|
-
]
|
|
1389
|
-
});
|
|
1390
|
-
} else {
|
|
1391
|
-
tx = await this.signer.writeContract({
|
|
1392
|
-
address: (0, import_viem6.getAddress)(requirements.asset),
|
|
1393
|
-
abi: eip3009ABI,
|
|
1394
|
-
functionName: "transferWithAuthorization",
|
|
1395
|
-
args: [
|
|
1396
|
-
(0, import_viem6.getAddress)(exactEvmPayload.authorization.from),
|
|
1397
|
-
(0, import_viem6.getAddress)(exactEvmPayload.authorization.to),
|
|
1398
|
-
BigInt(exactEvmPayload.authorization.value),
|
|
1399
|
-
BigInt(exactEvmPayload.authorization.validAfter),
|
|
1400
|
-
BigInt(exactEvmPayload.authorization.validBefore),
|
|
1401
|
-
exactEvmPayload.authorization.nonce,
|
|
1402
|
-
signature
|
|
1403
|
-
]
|
|
1404
|
-
});
|
|
1405
|
-
}
|
|
1406
|
-
const receipt = await this.signer.waitForTransactionReceipt({ hash: tx });
|
|
1407
|
-
if (receipt.status !== "success") {
|
|
1408
|
-
return {
|
|
1409
|
-
success: false,
|
|
1410
|
-
errorReason: "invalid_transaction_state",
|
|
1411
|
-
transaction: tx,
|
|
1412
|
-
network: payloadV1.network,
|
|
1413
|
-
payer: exactEvmPayload.authorization.from
|
|
1414
|
-
};
|
|
1415
|
-
}
|
|
1416
|
-
return {
|
|
1417
|
-
success: true,
|
|
1418
|
-
transaction: tx,
|
|
1419
|
-
network: payloadV1.network,
|
|
1420
|
-
payer: exactEvmPayload.authorization.from
|
|
1421
|
-
};
|
|
1422
|
-
} catch (error) {
|
|
1423
|
-
console.error("Failed to settle transaction:", error);
|
|
1424
|
-
return {
|
|
1425
|
-
success: false,
|
|
1426
|
-
errorReason: "transaction_failed",
|
|
1427
|
-
transaction: "",
|
|
1428
|
-
network: payloadV1.network,
|
|
1429
|
-
payer: exactEvmPayload.authorization.from
|
|
1430
|
-
};
|
|
1431
|
-
}
|
|
1432
|
-
}
|
|
1433
1812
|
};
|
|
1434
1813
|
|
|
1435
1814
|
// src/exact/facilitator/register.ts
|
|
@@ -1437,13 +1816,15 @@ function registerExactEvmScheme(facilitator, config) {
|
|
|
1437
1816
|
facilitator.register(
|
|
1438
1817
|
config.networks,
|
|
1439
1818
|
new ExactEvmScheme(config.signer, {
|
|
1440
|
-
deployERC4337WithEIP6492: config.deployERC4337WithEIP6492
|
|
1819
|
+
deployERC4337WithEIP6492: config.deployERC4337WithEIP6492,
|
|
1820
|
+
simulateInSettle: config.simulateInSettle
|
|
1441
1821
|
})
|
|
1442
1822
|
);
|
|
1443
1823
|
facilitator.registerV1(
|
|
1444
1824
|
NETWORKS,
|
|
1445
1825
|
new ExactEvmSchemeV12(config.signer, {
|
|
1446
|
-
deployERC4337WithEIP6492: config.deployERC4337WithEIP6492
|
|
1826
|
+
deployERC4337WithEIP6492: config.deployERC4337WithEIP6492,
|
|
1827
|
+
simulateInSettle: config.simulateInSettle
|
|
1447
1828
|
})
|
|
1448
1829
|
);
|
|
1449
1830
|
return facilitator;
|