@payai/x402-evm 2.2.3 → 2.3.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/dist/cjs/exact/client/index.d.ts +3 -3
- package/dist/cjs/exact/client/index.js +412 -168
- package/dist/cjs/exact/client/index.js.map +1 -1
- package/dist/cjs/exact/facilitator/index.d.ts +18 -18
- package/dist/cjs/exact/facilitator/index.js +477 -163
- package/dist/cjs/exact/facilitator/index.js.map +1 -1
- package/dist/cjs/exact/server/index.js +8 -1
- package/dist/cjs/exact/server/index.js.map +1 -1
- package/dist/cjs/exact/v1/client/index.d.ts +1 -1
- package/dist/cjs/exact/v1/client/index.js +18 -18
- package/dist/cjs/exact/v1/client/index.js.map +1 -1
- package/dist/cjs/exact/v1/facilitator/index.d.ts +1 -1
- package/dist/cjs/exact/v1/facilitator/index.js +11 -10
- package/dist/cjs/exact/v1/facilitator/index.js.map +1 -1
- package/dist/cjs/index.d.ts +3 -390
- package/dist/cjs/index.js +363 -109
- package/dist/cjs/index.js.map +1 -1
- package/dist/cjs/permit2-CQbXqCMC.d.ts +517 -0
- package/dist/cjs/signer-DC81R8wQ.d.ts +161 -0
- package/dist/cjs/v1/index.d.ts +12 -2
- package/dist/cjs/v1/index.js +15 -11
- package/dist/cjs/v1/index.js.map +1 -1
- package/dist/esm/chunk-LBIJBD7Q.mjs +425 -0
- package/dist/esm/chunk-LBIJBD7Q.mjs.map +1 -0
- package/dist/esm/{chunk-PFULIQAE.mjs → chunk-TKN5V2BV.mjs} +1 -1
- package/dist/esm/chunk-TKN5V2BV.mjs.map +1 -0
- package/dist/esm/{chunk-MVERYV34.mjs → chunk-XL6IFXCP.mjs} +96 -39
- package/dist/esm/chunk-XL6IFXCP.mjs.map +1 -0
- package/dist/esm/exact/client/index.d.mts +3 -3
- package/dist/esm/exact/client/index.mjs +4 -3
- package/dist/esm/exact/facilitator/index.d.mts +18 -18
- package/dist/esm/exact/facilitator/index.mjs +407 -103
- package/dist/esm/exact/facilitator/index.mjs.map +1 -1
- package/dist/esm/exact/server/index.mjs +8 -1
- package/dist/esm/exact/server/index.mjs.map +1 -1
- package/dist/esm/exact/v1/client/index.d.mts +1 -1
- package/dist/esm/exact/v1/client/index.mjs +2 -2
- package/dist/esm/exact/v1/facilitator/index.d.mts +1 -1
- package/dist/esm/exact/v1/facilitator/index.mjs +2 -2
- package/dist/esm/index.d.mts +3 -390
- package/dist/esm/index.mjs +29 -6
- package/dist/esm/index.mjs.map +1 -1
- package/dist/esm/permit2-CGOcN7Et.d.mts +517 -0
- package/dist/esm/signer-DC81R8wQ.d.mts +161 -0
- package/dist/esm/v1/index.d.mts +12 -2
- package/dist/esm/v1/index.mjs +6 -4
- package/package.json +5 -5
- package/dist/esm/chunk-MVERYV34.mjs.map +0 -1
- package/dist/esm/chunk-PFULIQAE.mjs.map +0 -1
- package/dist/esm/chunk-W556LH23.mjs +0 -229
- package/dist/esm/chunk-W556LH23.mjs.map +0 -1
- package/dist/esm/permit2-BsAoJiWD.d.mts +0 -103
- package/dist/esm/signer-5OVDxViv.d.mts +0 -79
|
@@ -1,16 +1,19 @@
|
|
|
1
1
|
import {
|
|
2
2
|
isPermit2Payload
|
|
3
|
-
} from "../../chunk-
|
|
3
|
+
} from "../../chunk-TKN5V2BV.mjs";
|
|
4
4
|
import {
|
|
5
|
-
|
|
5
|
+
ExactEvmSchemeV1,
|
|
6
6
|
NETWORKS,
|
|
7
7
|
PERMIT2_ADDRESS,
|
|
8
8
|
authorizationTypes,
|
|
9
9
|
eip3009ABI,
|
|
10
|
+
erc20AllowanceAbi,
|
|
11
|
+
erc20ApproveAbi,
|
|
12
|
+
getEvmChainId,
|
|
10
13
|
permit2WitnessTypes,
|
|
11
14
|
x402ExactPermit2ProxyABI,
|
|
12
15
|
x402ExactPermit2ProxyAddress
|
|
13
|
-
} from "../../chunk-
|
|
16
|
+
} from "../../chunk-XL6IFXCP.mjs";
|
|
14
17
|
|
|
15
18
|
// src/exact/facilitator/eip3009.ts
|
|
16
19
|
import { getAddress, isAddressEqual, parseErc6492Signature, parseSignature } from "viem";
|
|
@@ -45,7 +48,7 @@ async function verifyEIP3009(signer, payload, requirements, eip3009Payload) {
|
|
|
45
48
|
domain: {
|
|
46
49
|
name,
|
|
47
50
|
version,
|
|
48
|
-
chainId:
|
|
51
|
+
chainId: getEvmChainId(requirements.network),
|
|
49
52
|
verifyingContract: erc20Address
|
|
50
53
|
},
|
|
51
54
|
message: {
|
|
@@ -135,15 +138,16 @@ async function verifyEIP3009(signer, payload, requirements, eip3009Payload) {
|
|
|
135
138
|
return {
|
|
136
139
|
isValid: false,
|
|
137
140
|
invalidReason: "insufficient_funds",
|
|
141
|
+
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.`,
|
|
138
142
|
payer
|
|
139
143
|
};
|
|
140
144
|
}
|
|
141
145
|
} catch {
|
|
142
146
|
}
|
|
143
|
-
if (BigInt(eip3009Payload.authorization.value)
|
|
147
|
+
if (BigInt(eip3009Payload.authorization.value) !== BigInt(requirements.amount)) {
|
|
144
148
|
return {
|
|
145
149
|
isValid: false,
|
|
146
|
-
invalidReason: "
|
|
150
|
+
invalidReason: "invalid_exact_evm_payload_authorization_value_mismatch",
|
|
147
151
|
payer
|
|
148
152
|
};
|
|
149
153
|
}
|
|
@@ -243,20 +247,142 @@ async function settleEIP3009(signer, payload, requirements, eip3009Payload, conf
|
|
|
243
247
|
}
|
|
244
248
|
|
|
245
249
|
// src/exact/facilitator/permit2.ts
|
|
246
|
-
import {
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
250
|
+
import {
|
|
251
|
+
extractEip2612GasSponsoringInfo,
|
|
252
|
+
validateEip2612GasSponsoringInfo,
|
|
253
|
+
extractErc20ApprovalGasSponsoringInfo,
|
|
254
|
+
ERC20_APPROVAL_GAS_SPONSORING
|
|
255
|
+
} from "@x402/extensions";
|
|
256
|
+
import { getAddress as getAddress3 } from "viem";
|
|
257
|
+
|
|
258
|
+
// src/exact/facilitator/errors.ts
|
|
259
|
+
var ErrPermit2InvalidSignature = "invalid_permit2_signature";
|
|
260
|
+
var ErrPermit2InvalidAmount = "permit2_invalid_amount";
|
|
261
|
+
var ErrPermit2InvalidDestination = "permit2_invalid_destination";
|
|
262
|
+
var ErrPermit2InvalidOwner = "permit2_invalid_owner";
|
|
263
|
+
var ErrPermit2PaymentTooEarly = "permit2_payment_too_early";
|
|
264
|
+
var ErrPermit2InvalidNonce = "permit2_invalid_nonce";
|
|
265
|
+
var ErrPermit2612AmountMismatch = "permit2_2612_amount_mismatch";
|
|
266
|
+
var ErrErc20ApprovalInvalidFormat = "invalid_erc20_approval_extension_format";
|
|
267
|
+
var ErrErc20ApprovalFromMismatch = "erc20_approval_from_mismatch";
|
|
268
|
+
var ErrErc20ApprovalAssetMismatch = "erc20_approval_asset_mismatch";
|
|
269
|
+
var ErrErc20ApprovalSpenderNotPermit2 = "erc20_approval_spender_not_permit2";
|
|
270
|
+
var ErrErc20ApprovalTxWrongTarget = "erc20_approval_tx_wrong_target";
|
|
271
|
+
var ErrErc20ApprovalTxWrongSelector = "erc20_approval_tx_wrong_selector";
|
|
272
|
+
var ErrErc20ApprovalTxWrongSpender = "erc20_approval_tx_wrong_spender";
|
|
273
|
+
var ErrErc20ApprovalTxInvalidCalldata = "erc20_approval_tx_invalid_calldata";
|
|
274
|
+
var ErrErc20ApprovalTxSignerMismatch = "erc20_approval_tx_signer_mismatch";
|
|
275
|
+
var ErrErc20ApprovalTxInvalidSignature = "erc20_approval_tx_invalid_signature";
|
|
276
|
+
var ErrErc20ApprovalTxParseFailed = "erc20_approval_tx_parse_failed";
|
|
277
|
+
|
|
278
|
+
// src/exact/facilitator/erc20approval.ts
|
|
279
|
+
import {
|
|
280
|
+
getAddress as getAddress2,
|
|
281
|
+
parseTransaction,
|
|
282
|
+
decodeFunctionData,
|
|
283
|
+
recoverTransactionAddress
|
|
284
|
+
} from "viem";
|
|
285
|
+
import {
|
|
286
|
+
validateErc20ApprovalGasSponsoringInfo
|
|
287
|
+
} from "@x402/extensions";
|
|
288
|
+
var APPROVE_SELECTOR = "0x095ea7b3";
|
|
289
|
+
async function validateErc20ApprovalForPayment(info, payer, tokenAddress) {
|
|
290
|
+
if (!validateErc20ApprovalGasSponsoringInfo(info)) {
|
|
291
|
+
return {
|
|
292
|
+
isValid: false,
|
|
293
|
+
invalidReason: ErrErc20ApprovalInvalidFormat,
|
|
294
|
+
invalidMessage: "ERC-20 approval extension info failed schema validation"
|
|
295
|
+
};
|
|
296
|
+
}
|
|
297
|
+
if (getAddress2(info.from) !== getAddress2(payer)) {
|
|
298
|
+
return {
|
|
299
|
+
isValid: false,
|
|
300
|
+
invalidReason: ErrErc20ApprovalFromMismatch,
|
|
301
|
+
invalidMessage: `Expected from=${payer}, got ${info.from}`
|
|
302
|
+
};
|
|
303
|
+
}
|
|
304
|
+
if (getAddress2(info.asset) !== tokenAddress) {
|
|
305
|
+
return {
|
|
306
|
+
isValid: false,
|
|
307
|
+
invalidReason: ErrErc20ApprovalAssetMismatch,
|
|
308
|
+
invalidMessage: `Expected asset=${tokenAddress}, got ${info.asset}`
|
|
309
|
+
};
|
|
310
|
+
}
|
|
311
|
+
if (getAddress2(info.spender) !== getAddress2(PERMIT2_ADDRESS)) {
|
|
312
|
+
return {
|
|
313
|
+
isValid: false,
|
|
314
|
+
invalidReason: ErrErc20ApprovalSpenderNotPermit2,
|
|
315
|
+
invalidMessage: `Expected spender=${PERMIT2_ADDRESS}, got ${info.spender}`
|
|
316
|
+
};
|
|
317
|
+
}
|
|
318
|
+
try {
|
|
319
|
+
const serializedTx = info.signedTransaction;
|
|
320
|
+
const tx = parseTransaction(serializedTx);
|
|
321
|
+
if (!tx.to || getAddress2(tx.to) !== tokenAddress) {
|
|
322
|
+
return {
|
|
323
|
+
isValid: false,
|
|
324
|
+
invalidReason: ErrErc20ApprovalTxWrongTarget,
|
|
325
|
+
invalidMessage: `Transaction targets ${tx.to ?? "null"}, expected ${tokenAddress}`
|
|
326
|
+
};
|
|
327
|
+
}
|
|
328
|
+
const data = tx.data ?? "0x";
|
|
329
|
+
if (!data.startsWith(APPROVE_SELECTOR)) {
|
|
330
|
+
return {
|
|
331
|
+
isValid: false,
|
|
332
|
+
invalidReason: ErrErc20ApprovalTxWrongSelector,
|
|
333
|
+
invalidMessage: `Transaction calldata does not start with approve() selector ${APPROVE_SELECTOR}`
|
|
334
|
+
};
|
|
335
|
+
}
|
|
336
|
+
try {
|
|
337
|
+
const decoded = decodeFunctionData({
|
|
338
|
+
abi: erc20ApproveAbi,
|
|
339
|
+
data
|
|
340
|
+
});
|
|
341
|
+
const calldataSpender = getAddress2(decoded.args[0]);
|
|
342
|
+
if (calldataSpender !== getAddress2(PERMIT2_ADDRESS)) {
|
|
343
|
+
return {
|
|
344
|
+
isValid: false,
|
|
345
|
+
invalidReason: ErrErc20ApprovalTxWrongSpender,
|
|
346
|
+
invalidMessage: `approve() spender is ${calldataSpender}, expected Permit2 ${PERMIT2_ADDRESS}`
|
|
347
|
+
};
|
|
348
|
+
}
|
|
349
|
+
} catch {
|
|
350
|
+
return {
|
|
351
|
+
isValid: false,
|
|
352
|
+
invalidReason: ErrErc20ApprovalTxInvalidCalldata,
|
|
353
|
+
invalidMessage: "Failed to decode approve() calldata from the signed transaction"
|
|
354
|
+
};
|
|
355
|
+
}
|
|
356
|
+
try {
|
|
357
|
+
const recoveredAddress = await recoverTransactionAddress({
|
|
358
|
+
serializedTransaction: serializedTx
|
|
359
|
+
});
|
|
360
|
+
if (getAddress2(recoveredAddress) !== getAddress2(payer)) {
|
|
361
|
+
return {
|
|
362
|
+
isValid: false,
|
|
363
|
+
invalidReason: ErrErc20ApprovalTxSignerMismatch,
|
|
364
|
+
invalidMessage: `Transaction signed by ${recoveredAddress}, expected payer ${payer}`
|
|
365
|
+
};
|
|
366
|
+
}
|
|
367
|
+
} catch {
|
|
368
|
+
return {
|
|
369
|
+
isValid: false,
|
|
370
|
+
invalidReason: ErrErc20ApprovalTxInvalidSignature,
|
|
371
|
+
invalidMessage: "Failed to recover signer from the signed transaction"
|
|
372
|
+
};
|
|
373
|
+
}
|
|
374
|
+
} catch {
|
|
375
|
+
return {
|
|
376
|
+
isValid: false,
|
|
377
|
+
invalidReason: ErrErc20ApprovalTxParseFailed,
|
|
378
|
+
invalidMessage: "Failed to parse the signed transaction"
|
|
379
|
+
};
|
|
380
|
+
}
|
|
381
|
+
return { isValid: true };
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
// src/exact/facilitator/permit2.ts
|
|
385
|
+
async function verifyPermit2(signer, payload, requirements, permit2Payload, context) {
|
|
260
386
|
const payer = permit2Payload.permit2Authorization.from;
|
|
261
387
|
if (payload.accepted.scheme !== "exact" || requirements.scheme !== "exact") {
|
|
262
388
|
return {
|
|
@@ -272,16 +398,16 @@ async function verifyPermit2(signer, payload, requirements, permit2Payload) {
|
|
|
272
398
|
payer
|
|
273
399
|
};
|
|
274
400
|
}
|
|
275
|
-
const chainId =
|
|
276
|
-
const tokenAddress =
|
|
277
|
-
if (
|
|
401
|
+
const chainId = getEvmChainId(requirements.network);
|
|
402
|
+
const tokenAddress = getAddress3(requirements.asset);
|
|
403
|
+
if (getAddress3(permit2Payload.permit2Authorization.spender) !== getAddress3(x402ExactPermit2ProxyAddress)) {
|
|
278
404
|
return {
|
|
279
405
|
isValid: false,
|
|
280
406
|
invalidReason: "invalid_permit2_spender",
|
|
281
407
|
payer
|
|
282
408
|
};
|
|
283
409
|
}
|
|
284
|
-
if (
|
|
410
|
+
if (getAddress3(permit2Payload.permit2Authorization.witness.to) !== getAddress3(requirements.payTo)) {
|
|
285
411
|
return {
|
|
286
412
|
isValid: false,
|
|
287
413
|
invalidReason: "invalid_permit2_recipient_mismatch",
|
|
@@ -303,14 +429,14 @@ async function verifyPermit2(signer, payload, requirements, permit2Payload) {
|
|
|
303
429
|
payer
|
|
304
430
|
};
|
|
305
431
|
}
|
|
306
|
-
if (BigInt(permit2Payload.permit2Authorization.permitted.amount)
|
|
432
|
+
if (BigInt(permit2Payload.permit2Authorization.permitted.amount) !== BigInt(requirements.amount)) {
|
|
307
433
|
return {
|
|
308
434
|
isValid: false,
|
|
309
|
-
invalidReason: "
|
|
435
|
+
invalidReason: "permit2_amount_mismatch",
|
|
310
436
|
payer
|
|
311
437
|
};
|
|
312
438
|
}
|
|
313
|
-
if (
|
|
439
|
+
if (getAddress3(permit2Payload.permit2Authorization.permitted.token) !== tokenAddress) {
|
|
314
440
|
return {
|
|
315
441
|
isValid: false,
|
|
316
442
|
invalidReason: "permit2_token_mismatch",
|
|
@@ -327,16 +453,15 @@ async function verifyPermit2(signer, payload, requirements, permit2Payload) {
|
|
|
327
453
|
},
|
|
328
454
|
message: {
|
|
329
455
|
permitted: {
|
|
330
|
-
token:
|
|
456
|
+
token: getAddress3(permit2Payload.permit2Authorization.permitted.token),
|
|
331
457
|
amount: BigInt(permit2Payload.permit2Authorization.permitted.amount)
|
|
332
458
|
},
|
|
333
|
-
spender:
|
|
459
|
+
spender: getAddress3(permit2Payload.permit2Authorization.spender),
|
|
334
460
|
nonce: BigInt(permit2Payload.permit2Authorization.nonce),
|
|
335
461
|
deadline: BigInt(permit2Payload.permit2Authorization.deadline),
|
|
336
462
|
witness: {
|
|
337
|
-
to:
|
|
338
|
-
validAfter: BigInt(permit2Payload.permit2Authorization.witness.validAfter)
|
|
339
|
-
extra: permit2Payload.permit2Authorization.witness.extra
|
|
463
|
+
to: getAddress3(permit2Payload.permit2Authorization.witness.to),
|
|
464
|
+
validAfter: BigInt(permit2Payload.permit2Authorization.witness.validAfter)
|
|
340
465
|
}
|
|
341
466
|
}
|
|
342
467
|
};
|
|
@@ -360,21 +485,16 @@ async function verifyPermit2(signer, payload, requirements, permit2Payload) {
|
|
|
360
485
|
payer
|
|
361
486
|
};
|
|
362
487
|
}
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
invalidReason: "permit2_allowance_required",
|
|
374
|
-
payer
|
|
375
|
-
};
|
|
376
|
-
}
|
|
377
|
-
} catch {
|
|
488
|
+
const allowanceResult = await _verifyPermit2Allowance(
|
|
489
|
+
signer,
|
|
490
|
+
payload,
|
|
491
|
+
requirements,
|
|
492
|
+
payer,
|
|
493
|
+
tokenAddress,
|
|
494
|
+
context
|
|
495
|
+
);
|
|
496
|
+
if (allowanceResult) {
|
|
497
|
+
return allowanceResult;
|
|
378
498
|
}
|
|
379
499
|
try {
|
|
380
500
|
const balance = await signer.readContract({
|
|
@@ -387,6 +507,7 @@ async function verifyPermit2(signer, payload, requirements, permit2Payload) {
|
|
|
387
507
|
return {
|
|
388
508
|
isValid: false,
|
|
389
509
|
invalidReason: "insufficient_funds",
|
|
510
|
+
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.`,
|
|
390
511
|
payer
|
|
391
512
|
};
|
|
392
513
|
}
|
|
@@ -398,9 +519,53 @@ async function verifyPermit2(signer, payload, requirements, permit2Payload) {
|
|
|
398
519
|
payer
|
|
399
520
|
};
|
|
400
521
|
}
|
|
401
|
-
async function
|
|
522
|
+
async function _verifyPermit2Allowance(signer, payload, requirements, payer, tokenAddress, context) {
|
|
523
|
+
try {
|
|
524
|
+
const allowance = await signer.readContract({
|
|
525
|
+
address: tokenAddress,
|
|
526
|
+
abi: erc20AllowanceAbi,
|
|
527
|
+
functionName: "allowance",
|
|
528
|
+
args: [payer, PERMIT2_ADDRESS]
|
|
529
|
+
});
|
|
530
|
+
if (allowance >= BigInt(requirements.amount)) {
|
|
531
|
+
return null;
|
|
532
|
+
}
|
|
533
|
+
const eip2612Info = extractEip2612GasSponsoringInfo(payload);
|
|
534
|
+
if (eip2612Info) {
|
|
535
|
+
const result = validateEip2612PermitForPayment(eip2612Info, payer, tokenAddress);
|
|
536
|
+
if (!result.isValid) {
|
|
537
|
+
return { isValid: false, invalidReason: result.invalidReason, payer };
|
|
538
|
+
}
|
|
539
|
+
return null;
|
|
540
|
+
}
|
|
541
|
+
const erc20GasSponsorshipExtension = context?.getExtension(
|
|
542
|
+
ERC20_APPROVAL_GAS_SPONSORING.key
|
|
543
|
+
);
|
|
544
|
+
if (erc20GasSponsorshipExtension) {
|
|
545
|
+
const erc20Info = extractErc20ApprovalGasSponsoringInfo(payload);
|
|
546
|
+
if (erc20Info) {
|
|
547
|
+
const result = await validateErc20ApprovalForPayment(erc20Info, payer, tokenAddress);
|
|
548
|
+
if (!result.isValid) {
|
|
549
|
+
return { isValid: false, invalidReason: result.invalidReason, payer };
|
|
550
|
+
}
|
|
551
|
+
return null;
|
|
552
|
+
}
|
|
553
|
+
}
|
|
554
|
+
return { isValid: false, invalidReason: "permit2_allowance_required", payer };
|
|
555
|
+
} catch {
|
|
556
|
+
const eip2612Info = extractEip2612GasSponsoringInfo(payload);
|
|
557
|
+
if (eip2612Info) {
|
|
558
|
+
const result = validateEip2612PermitForPayment(eip2612Info, payer, tokenAddress);
|
|
559
|
+
if (!result.isValid) {
|
|
560
|
+
return { isValid: false, invalidReason: result.invalidReason, payer };
|
|
561
|
+
}
|
|
562
|
+
}
|
|
563
|
+
return null;
|
|
564
|
+
}
|
|
565
|
+
}
|
|
566
|
+
async function settlePermit2(signer, payload, requirements, permit2Payload, context) {
|
|
402
567
|
const payer = permit2Payload.permit2Authorization.from;
|
|
403
|
-
const valid = await verifyPermit2(signer, payload, requirements, permit2Payload);
|
|
568
|
+
const valid = await verifyPermit2(signer, payload, requirements, permit2Payload, context);
|
|
404
569
|
if (!valid.isValid) {
|
|
405
570
|
return {
|
|
406
571
|
success: false,
|
|
@@ -410,82 +575,223 @@ async function settlePermit2(signer, payload, requirements, permit2Payload) {
|
|
|
410
575
|
payer
|
|
411
576
|
};
|
|
412
577
|
}
|
|
578
|
+
const eip2612Info = extractEip2612GasSponsoringInfo(payload);
|
|
579
|
+
if (eip2612Info) {
|
|
580
|
+
return _settlePermit2WithEIP2612(signer, payload, permit2Payload, eip2612Info);
|
|
581
|
+
}
|
|
582
|
+
const erc20Info = extractErc20ApprovalGasSponsoringInfo(payload);
|
|
583
|
+
if (erc20Info) {
|
|
584
|
+
const erc20GasSponsorshipExtension = context?.getExtension(
|
|
585
|
+
ERC20_APPROVAL_GAS_SPONSORING.key
|
|
586
|
+
);
|
|
587
|
+
if (erc20GasSponsorshipExtension?.signer) {
|
|
588
|
+
return _settlePermit2WithERC20Approval(
|
|
589
|
+
erc20GasSponsorshipExtension.signer,
|
|
590
|
+
payload,
|
|
591
|
+
permit2Payload,
|
|
592
|
+
erc20Info
|
|
593
|
+
);
|
|
594
|
+
}
|
|
595
|
+
}
|
|
596
|
+
return _settlePermit2Direct(signer, payload, permit2Payload);
|
|
597
|
+
}
|
|
598
|
+
async function _settlePermit2WithEIP2612(signer, payload, permit2Payload, eip2612Info) {
|
|
599
|
+
const payer = permit2Payload.permit2Authorization.from;
|
|
413
600
|
try {
|
|
601
|
+
const { v, r, s } = splitEip2612Signature(eip2612Info.signature);
|
|
414
602
|
const tx = await signer.writeContract({
|
|
415
603
|
address: x402ExactPermit2ProxyAddress,
|
|
416
604
|
abi: x402ExactPermit2ProxyABI,
|
|
417
|
-
functionName: "
|
|
605
|
+
functionName: "settleWithPermit",
|
|
418
606
|
args: [
|
|
607
|
+
{
|
|
608
|
+
value: BigInt(eip2612Info.amount),
|
|
609
|
+
deadline: BigInt(eip2612Info.deadline),
|
|
610
|
+
r,
|
|
611
|
+
s,
|
|
612
|
+
v
|
|
613
|
+
},
|
|
419
614
|
{
|
|
420
615
|
permitted: {
|
|
421
|
-
token:
|
|
616
|
+
token: getAddress3(permit2Payload.permit2Authorization.permitted.token),
|
|
422
617
|
amount: BigInt(permit2Payload.permit2Authorization.permitted.amount)
|
|
423
618
|
},
|
|
424
619
|
nonce: BigInt(permit2Payload.permit2Authorization.nonce),
|
|
425
620
|
deadline: BigInt(permit2Payload.permit2Authorization.deadline)
|
|
426
621
|
},
|
|
427
|
-
|
|
622
|
+
getAddress3(payer),
|
|
428
623
|
{
|
|
429
|
-
to:
|
|
430
|
-
validAfter: BigInt(permit2Payload.permit2Authorization.witness.validAfter)
|
|
431
|
-
extra: permit2Payload.permit2Authorization.witness.extra
|
|
624
|
+
to: getAddress3(permit2Payload.permit2Authorization.witness.to),
|
|
625
|
+
validAfter: BigInt(permit2Payload.permit2Authorization.witness.validAfter)
|
|
432
626
|
},
|
|
433
627
|
permit2Payload.signature
|
|
434
628
|
]
|
|
435
629
|
});
|
|
436
|
-
|
|
437
|
-
|
|
630
|
+
return _waitAndReturn(signer, tx, payload, payer);
|
|
631
|
+
} catch (error) {
|
|
632
|
+
return _mapSettleError(error, payload, payer);
|
|
633
|
+
}
|
|
634
|
+
}
|
|
635
|
+
async function _settlePermit2WithERC20Approval(extensionSigner, payload, permit2Payload, erc20Info) {
|
|
636
|
+
const payer = permit2Payload.permit2Authorization.from;
|
|
637
|
+
try {
|
|
638
|
+
const approvalTxHash = await extensionSigner.sendRawTransaction({
|
|
639
|
+
serializedTransaction: erc20Info.signedTransaction
|
|
640
|
+
});
|
|
641
|
+
const approvalReceipt = await extensionSigner.waitForTransactionReceipt({
|
|
642
|
+
hash: approvalTxHash
|
|
643
|
+
});
|
|
644
|
+
if (approvalReceipt.status !== "success") {
|
|
438
645
|
return {
|
|
439
646
|
success: false,
|
|
440
|
-
errorReason: "
|
|
441
|
-
transaction:
|
|
647
|
+
errorReason: "erc20_approval_tx_failed",
|
|
648
|
+
transaction: approvalTxHash,
|
|
442
649
|
network: payload.accepted.network,
|
|
443
650
|
payer
|
|
444
651
|
};
|
|
445
652
|
}
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
653
|
+
const tx = await extensionSigner.writeContract({
|
|
654
|
+
address: x402ExactPermit2ProxyAddress,
|
|
655
|
+
abi: x402ExactPermit2ProxyABI,
|
|
656
|
+
functionName: "settle",
|
|
657
|
+
args: [
|
|
658
|
+
{
|
|
659
|
+
permitted: {
|
|
660
|
+
token: getAddress3(permit2Payload.permit2Authorization.permitted.token),
|
|
661
|
+
amount: BigInt(permit2Payload.permit2Authorization.permitted.amount)
|
|
662
|
+
},
|
|
663
|
+
nonce: BigInt(permit2Payload.permit2Authorization.nonce),
|
|
664
|
+
deadline: BigInt(permit2Payload.permit2Authorization.deadline)
|
|
665
|
+
},
|
|
666
|
+
getAddress3(payer),
|
|
667
|
+
{
|
|
668
|
+
to: getAddress3(permit2Payload.permit2Authorization.witness.to),
|
|
669
|
+
validAfter: BigInt(permit2Payload.permit2Authorization.witness.validAfter)
|
|
670
|
+
},
|
|
671
|
+
permit2Payload.signature
|
|
672
|
+
]
|
|
673
|
+
});
|
|
674
|
+
return _waitAndReturn(extensionSigner, tx, payload, payer);
|
|
452
675
|
} catch (error) {
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
676
|
+
return _mapSettleError(error, payload, payer);
|
|
677
|
+
}
|
|
678
|
+
}
|
|
679
|
+
async function _settlePermit2Direct(signer, payload, permit2Payload) {
|
|
680
|
+
const payer = permit2Payload.permit2Authorization.from;
|
|
681
|
+
try {
|
|
682
|
+
const tx = await signer.writeContract({
|
|
683
|
+
address: x402ExactPermit2ProxyAddress,
|
|
684
|
+
abi: x402ExactPermit2ProxyABI,
|
|
685
|
+
functionName: "settle",
|
|
686
|
+
args: [
|
|
687
|
+
{
|
|
688
|
+
permitted: {
|
|
689
|
+
token: getAddress3(permit2Payload.permit2Authorization.permitted.token),
|
|
690
|
+
amount: BigInt(permit2Payload.permit2Authorization.permitted.amount)
|
|
691
|
+
},
|
|
692
|
+
nonce: BigInt(permit2Payload.permit2Authorization.nonce),
|
|
693
|
+
deadline: BigInt(permit2Payload.permit2Authorization.deadline)
|
|
694
|
+
},
|
|
695
|
+
getAddress3(payer),
|
|
696
|
+
{
|
|
697
|
+
to: getAddress3(permit2Payload.permit2Authorization.witness.to),
|
|
698
|
+
validAfter: BigInt(permit2Payload.permit2Authorization.witness.validAfter)
|
|
699
|
+
},
|
|
700
|
+
permit2Payload.signature
|
|
701
|
+
]
|
|
702
|
+
});
|
|
703
|
+
return _waitAndReturn(signer, tx, payload, payer);
|
|
704
|
+
} catch (error) {
|
|
705
|
+
return _mapSettleError(error, payload, payer);
|
|
706
|
+
}
|
|
707
|
+
}
|
|
708
|
+
async function _waitAndReturn(signer, tx, payload, payer) {
|
|
709
|
+
const receipt = await signer.waitForTransactionReceipt({ hash: tx });
|
|
710
|
+
if (receipt.status !== "success") {
|
|
472
711
|
return {
|
|
473
712
|
success: false,
|
|
474
|
-
errorReason,
|
|
475
|
-
transaction:
|
|
713
|
+
errorReason: "invalid_transaction_state",
|
|
714
|
+
transaction: tx,
|
|
476
715
|
network: payload.accepted.network,
|
|
477
716
|
payer
|
|
478
717
|
};
|
|
479
718
|
}
|
|
719
|
+
return {
|
|
720
|
+
success: true,
|
|
721
|
+
transaction: tx,
|
|
722
|
+
network: payload.accepted.network,
|
|
723
|
+
payer
|
|
724
|
+
};
|
|
725
|
+
}
|
|
726
|
+
function _mapSettleError(error, payload, payer) {
|
|
727
|
+
let errorReason = "transaction_failed";
|
|
728
|
+
if (error instanceof Error) {
|
|
729
|
+
const message = error.message;
|
|
730
|
+
if (message.includes("Permit2612AmountMismatch")) {
|
|
731
|
+
errorReason = ErrPermit2612AmountMismatch;
|
|
732
|
+
} else if (message.includes("InvalidAmount")) {
|
|
733
|
+
errorReason = ErrPermit2InvalidAmount;
|
|
734
|
+
} else if (message.includes("InvalidDestination")) {
|
|
735
|
+
errorReason = ErrPermit2InvalidDestination;
|
|
736
|
+
} else if (message.includes("InvalidOwner")) {
|
|
737
|
+
errorReason = ErrPermit2InvalidOwner;
|
|
738
|
+
} else if (message.includes("PaymentTooEarly")) {
|
|
739
|
+
errorReason = ErrPermit2PaymentTooEarly;
|
|
740
|
+
} else if (message.includes("InvalidSignature") || message.includes("SignatureExpired")) {
|
|
741
|
+
errorReason = ErrPermit2InvalidSignature;
|
|
742
|
+
} else if (message.includes("InvalidNonce")) {
|
|
743
|
+
errorReason = ErrPermit2InvalidNonce;
|
|
744
|
+
} else {
|
|
745
|
+
errorReason = `transaction_failed: ${message.slice(0, 500)}`;
|
|
746
|
+
}
|
|
747
|
+
}
|
|
748
|
+
return {
|
|
749
|
+
success: false,
|
|
750
|
+
errorReason,
|
|
751
|
+
transaction: "",
|
|
752
|
+
network: payload.accepted.network,
|
|
753
|
+
payer
|
|
754
|
+
};
|
|
755
|
+
}
|
|
756
|
+
function validateEip2612PermitForPayment(info, payer, tokenAddress) {
|
|
757
|
+
if (!validateEip2612GasSponsoringInfo(info)) {
|
|
758
|
+
return { isValid: false, invalidReason: "invalid_eip2612_extension_format" };
|
|
759
|
+
}
|
|
760
|
+
if (getAddress3(info.from) !== getAddress3(payer)) {
|
|
761
|
+
return { isValid: false, invalidReason: "eip2612_from_mismatch" };
|
|
762
|
+
}
|
|
763
|
+
if (getAddress3(info.asset) !== tokenAddress) {
|
|
764
|
+
return { isValid: false, invalidReason: "eip2612_asset_mismatch" };
|
|
765
|
+
}
|
|
766
|
+
if (getAddress3(info.spender) !== getAddress3(PERMIT2_ADDRESS)) {
|
|
767
|
+
return { isValid: false, invalidReason: "eip2612_spender_not_permit2" };
|
|
768
|
+
}
|
|
769
|
+
const now = Math.floor(Date.now() / 1e3);
|
|
770
|
+
if (BigInt(info.deadline) < BigInt(now + 6)) {
|
|
771
|
+
return { isValid: false, invalidReason: "eip2612_deadline_expired" };
|
|
772
|
+
}
|
|
773
|
+
return { isValid: true };
|
|
774
|
+
}
|
|
775
|
+
function splitEip2612Signature(signature) {
|
|
776
|
+
const sig = signature.startsWith("0x") ? signature.slice(2) : signature;
|
|
777
|
+
if (sig.length !== 130) {
|
|
778
|
+
throw new Error(
|
|
779
|
+
`invalid EIP-2612 signature length: expected 65 bytes (130 hex chars), got ${sig.length / 2} bytes`
|
|
780
|
+
);
|
|
781
|
+
}
|
|
782
|
+
const r = `0x${sig.slice(0, 64)}`;
|
|
783
|
+
const s = `0x${sig.slice(64, 128)}`;
|
|
784
|
+
const v = parseInt(sig.slice(128, 130), 16);
|
|
785
|
+
return { v, r, s };
|
|
480
786
|
}
|
|
481
787
|
|
|
482
788
|
// src/exact/facilitator/scheme.ts
|
|
483
789
|
var ExactEvmScheme = class {
|
|
484
790
|
/**
|
|
485
|
-
* Creates a new
|
|
791
|
+
* Creates a new ExactEvmScheme facilitator instance.
|
|
486
792
|
*
|
|
487
793
|
* @param signer - The EVM signer for facilitator operations
|
|
488
|
-
* @param config - Optional configuration
|
|
794
|
+
* @param config - Optional configuration
|
|
489
795
|
*/
|
|
490
796
|
constructor(signer, config) {
|
|
491
797
|
this.signer = signer;
|
|
@@ -496,53 +802,51 @@ var ExactEvmScheme = class {
|
|
|
496
802
|
};
|
|
497
803
|
}
|
|
498
804
|
/**
|
|
499
|
-
*
|
|
500
|
-
* For EVM, no extra data is needed.
|
|
805
|
+
* Returns undefined — EVM has no mechanism-specific extra data.
|
|
501
806
|
*
|
|
502
|
-
* @param _ - The network identifier (unused
|
|
503
|
-
* @returns undefined
|
|
807
|
+
* @param _ - The network identifier (unused)
|
|
808
|
+
* @returns undefined
|
|
504
809
|
*/
|
|
505
810
|
getExtra(_) {
|
|
506
811
|
return void 0;
|
|
507
812
|
}
|
|
508
813
|
/**
|
|
509
|
-
*
|
|
510
|
-
* Returns all addresses this facilitator can use for signing/settling transactions.
|
|
814
|
+
* Returns facilitator wallet addresses for the supported response.
|
|
511
815
|
*
|
|
512
|
-
* @param _ - The network identifier (unused
|
|
816
|
+
* @param _ - The network identifier (unused, addresses are network-agnostic)
|
|
513
817
|
* @returns Array of facilitator wallet addresses
|
|
514
818
|
*/
|
|
515
819
|
getSigners(_) {
|
|
516
820
|
return [...this.signer.getAddresses()];
|
|
517
821
|
}
|
|
518
822
|
/**
|
|
519
|
-
* Verifies a payment payload.
|
|
520
|
-
* Routes to the appropriate verification logic based on payload type.
|
|
823
|
+
* Verifies a payment payload. Routes to Permit2 or EIP-3009 based on payload type.
|
|
521
824
|
*
|
|
522
825
|
* @param payload - The payment payload to verify
|
|
523
826
|
* @param requirements - The payment requirements
|
|
827
|
+
* @param context - Optional facilitator context for extension capabilities
|
|
524
828
|
* @returns Promise resolving to verification response
|
|
525
829
|
*/
|
|
526
|
-
async verify(payload, requirements) {
|
|
830
|
+
async verify(payload, requirements, context) {
|
|
527
831
|
const rawPayload = payload.payload;
|
|
528
832
|
if (isPermit2Payload(rawPayload)) {
|
|
529
|
-
return verifyPermit2(this.signer, payload, requirements, rawPayload);
|
|
833
|
+
return verifyPermit2(this.signer, payload, requirements, rawPayload, context);
|
|
530
834
|
}
|
|
531
835
|
const eip3009Payload = rawPayload;
|
|
532
836
|
return verifyEIP3009(this.signer, payload, requirements, eip3009Payload);
|
|
533
837
|
}
|
|
534
838
|
/**
|
|
535
|
-
* Settles a payment
|
|
536
|
-
* Routes to the appropriate settlement logic based on payload type.
|
|
839
|
+
* Settles a payment. Routes to Permit2 or EIP-3009 based on payload type.
|
|
537
840
|
*
|
|
538
841
|
* @param payload - The payment payload to settle
|
|
539
842
|
* @param requirements - The payment requirements
|
|
843
|
+
* @param context - Optional facilitator context for extension capabilities
|
|
540
844
|
* @returns Promise resolving to settlement response
|
|
541
845
|
*/
|
|
542
|
-
async settle(payload, requirements) {
|
|
846
|
+
async settle(payload, requirements, context) {
|
|
543
847
|
const rawPayload = payload.payload;
|
|
544
848
|
if (isPermit2Payload(rawPayload)) {
|
|
545
|
-
return settlePermit2(this.signer, payload, requirements, rawPayload);
|
|
849
|
+
return settlePermit2(this.signer, payload, requirements, rawPayload, context);
|
|
546
850
|
}
|
|
547
851
|
const eip3009Payload = rawPayload;
|
|
548
852
|
return settleEIP3009(this.signer, payload, requirements, eip3009Payload, this.config);
|