@x402/evm 2.5.0 → 2.7.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 +25 -0
- package/dist/cjs/exact/client/index.d.ts +13 -6
- 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 +990 -609
- package/dist/cjs/exact/facilitator/index.js.map +1 -1
- package/dist/cjs/exact/server/index.js +19 -4
- 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 +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 +415 -178
- 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-CQbXqCMC.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-7KHQD5KT.mjs → chunk-IZEI7JTG.mjs} +517 -179
- package/dist/esm/chunk-IZEI7JTG.mjs.map +1 -0
- package/dist/esm/{chunk-GY6X5A3G.mjs → chunk-WJWNS4G4.mjs} +113 -20
- package/dist/esm/chunk-WJWNS4G4.mjs.map +1 -0
- package/dist/esm/exact/client/index.d.mts +13 -6
- 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 +500 -393
- package/dist/esm/exact/facilitator/index.mjs.map +1 -1
- package/dist/esm/exact/server/index.mjs +19 -4
- 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 +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-CGOcN7Et.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-7KHQD5KT.mjs.map +0 -1
- package/dist/esm/chunk-GY6X5A3G.mjs.map +0 -1
|
@@ -2,34 +2,98 @@ import {
|
|
|
2
2
|
isPermit2Payload
|
|
3
3
|
} from "../../chunk-TKN5V2BV.mjs";
|
|
4
4
|
import {
|
|
5
|
+
ERC20_APPROVAL_GAS_SPONSORING_KEY,
|
|
6
|
+
extractEip2612GasSponsoringInfo,
|
|
7
|
+
extractErc20ApprovalGasSponsoringInfo,
|
|
8
|
+
resolveErc20ApprovalExtensionSigner,
|
|
9
|
+
validateEip2612GasSponsoringInfo,
|
|
10
|
+
validateErc20ApprovalGasSponsoringInfo
|
|
11
|
+
} from "../../chunk-GD4MKCN7.mjs";
|
|
12
|
+
import {
|
|
13
|
+
DEFAULT_MAX_FEE_PER_GAS,
|
|
14
|
+
ERC20_APPROVE_GAS_LIMIT,
|
|
15
|
+
ErrEip2612AssetMismatch,
|
|
16
|
+
ErrEip2612DeadlineExpired,
|
|
17
|
+
ErrEip2612FromMismatch,
|
|
18
|
+
ErrEip2612SpenderNotPermit2,
|
|
19
|
+
ErrErc20ApprovalAssetMismatch,
|
|
20
|
+
ErrErc20ApprovalFromMismatch,
|
|
21
|
+
ErrErc20ApprovalInsufficientEthForGas,
|
|
22
|
+
ErrErc20ApprovalInvalidFormat,
|
|
23
|
+
ErrErc20ApprovalSpenderNotPermit2,
|
|
24
|
+
ErrErc20ApprovalTxFailed,
|
|
25
|
+
ErrErc20ApprovalTxInvalidCalldata,
|
|
26
|
+
ErrErc20ApprovalTxInvalidSignature,
|
|
27
|
+
ErrErc20ApprovalTxParseFailed,
|
|
28
|
+
ErrErc20ApprovalTxSignerMismatch,
|
|
29
|
+
ErrErc20ApprovalTxWrongSelector,
|
|
30
|
+
ErrErc20ApprovalTxWrongSpender,
|
|
31
|
+
ErrErc20ApprovalTxWrongTarget,
|
|
32
|
+
ErrInvalidAuthorizationValue,
|
|
33
|
+
ErrInvalidEip2612ExtensionFormat,
|
|
34
|
+
ErrInvalidScheme,
|
|
35
|
+
ErrInvalidSignature,
|
|
36
|
+
ErrInvalidTransactionState,
|
|
37
|
+
ErrMissingEip712Domain,
|
|
38
|
+
ErrNetworkMismatch,
|
|
39
|
+
ErrPermit2612AmountMismatch,
|
|
40
|
+
ErrPermit2AllowanceRequired,
|
|
41
|
+
ErrPermit2AmountMismatch,
|
|
42
|
+
ErrPermit2DeadlineExpired,
|
|
43
|
+
ErrPermit2InsufficientBalance,
|
|
44
|
+
ErrPermit2InvalidAmount,
|
|
45
|
+
ErrPermit2InvalidDestination,
|
|
46
|
+
ErrPermit2InvalidNonce,
|
|
47
|
+
ErrPermit2InvalidOwner,
|
|
48
|
+
ErrPermit2InvalidSignature,
|
|
49
|
+
ErrPermit2InvalidSpender,
|
|
50
|
+
ErrPermit2NotYetValid,
|
|
51
|
+
ErrPermit2PaymentTooEarly,
|
|
52
|
+
ErrPermit2ProxyNotDeployed,
|
|
53
|
+
ErrPermit2RecipientMismatch,
|
|
54
|
+
ErrPermit2SimulationFailed,
|
|
55
|
+
ErrPermit2TokenMismatch,
|
|
56
|
+
ErrRecipientMismatch,
|
|
57
|
+
ErrTransactionFailed,
|
|
58
|
+
ErrUndeployedSmartWallet,
|
|
59
|
+
ErrUnsupportedPayloadType,
|
|
60
|
+
ErrValidAfterInFuture,
|
|
61
|
+
ErrValidBeforeExpired,
|
|
5
62
|
ExactEvmSchemeV1,
|
|
63
|
+
MULTICALL3_ADDRESS,
|
|
6
64
|
NETWORKS,
|
|
7
65
|
PERMIT2_ADDRESS,
|
|
8
66
|
authorizationTypes,
|
|
67
|
+
diagnoseEip3009SimulationFailure,
|
|
9
68
|
eip3009ABI,
|
|
10
69
|
erc20AllowanceAbi,
|
|
11
70
|
erc20ApproveAbi,
|
|
71
|
+
executeTransferWithAuthorization,
|
|
12
72
|
getEvmChainId,
|
|
73
|
+
multicall,
|
|
74
|
+
multicall3GetEthBalanceAbi,
|
|
13
75
|
permit2WitnessTypes,
|
|
76
|
+
simulateEip3009Transfer,
|
|
14
77
|
x402ExactPermit2ProxyABI,
|
|
15
78
|
x402ExactPermit2ProxyAddress
|
|
16
|
-
} from "../../chunk-
|
|
79
|
+
} from "../../chunk-IZEI7JTG.mjs";
|
|
17
80
|
|
|
18
81
|
// src/exact/facilitator/eip3009.ts
|
|
19
|
-
import { getAddress, isAddressEqual, parseErc6492Signature
|
|
20
|
-
async function verifyEIP3009(signer, payload, requirements, eip3009Payload) {
|
|
82
|
+
import { getAddress, isAddressEqual, parseErc6492Signature } from "viem";
|
|
83
|
+
async function verifyEIP3009(signer, payload, requirements, eip3009Payload, options) {
|
|
21
84
|
const payer = eip3009Payload.authorization.from;
|
|
85
|
+
let eip6492Deployment;
|
|
22
86
|
if (payload.accepted.scheme !== "exact" || requirements.scheme !== "exact") {
|
|
23
87
|
return {
|
|
24
88
|
isValid: false,
|
|
25
|
-
invalidReason:
|
|
89
|
+
invalidReason: ErrInvalidScheme,
|
|
26
90
|
payer
|
|
27
91
|
};
|
|
28
92
|
}
|
|
29
93
|
if (!requirements.extra?.name || !requirements.extra?.version) {
|
|
30
94
|
return {
|
|
31
95
|
isValid: false,
|
|
32
|
-
invalidReason:
|
|
96
|
+
invalidReason: ErrMissingEip712Domain,
|
|
33
97
|
payer
|
|
34
98
|
};
|
|
35
99
|
}
|
|
@@ -38,7 +102,7 @@ async function verifyEIP3009(signer, payload, requirements, eip3009Payload) {
|
|
|
38
102
|
if (payload.accepted.network !== requirements.network) {
|
|
39
103
|
return {
|
|
40
104
|
isValid: false,
|
|
41
|
-
invalidReason:
|
|
105
|
+
invalidReason: ErrNetworkMismatch,
|
|
42
106
|
payer
|
|
43
107
|
};
|
|
44
108
|
}
|
|
@@ -60,47 +124,41 @@ async function verifyEIP3009(signer, payload, requirements, eip3009Payload) {
|
|
|
60
124
|
nonce: eip3009Payload.authorization.nonce
|
|
61
125
|
}
|
|
62
126
|
};
|
|
127
|
+
let isValid = false;
|
|
63
128
|
try {
|
|
64
|
-
|
|
129
|
+
isValid = await signer.verifyTypedData({
|
|
65
130
|
address: eip3009Payload.authorization.from,
|
|
66
131
|
...permitTypedData,
|
|
67
132
|
signature: eip3009Payload.signature
|
|
68
133
|
});
|
|
69
|
-
|
|
134
|
+
} catch {
|
|
135
|
+
isValid = false;
|
|
136
|
+
}
|
|
137
|
+
const signature = eip3009Payload.signature;
|
|
138
|
+
const sigLen = signature.startsWith("0x") ? signature.length - 2 : signature.length;
|
|
139
|
+
const erc6492Data = parseErc6492Signature(signature);
|
|
140
|
+
const hasDeploymentInfo = erc6492Data.address && erc6492Data.data && !isAddressEqual(erc6492Data.address, "0x0000000000000000000000000000000000000000");
|
|
141
|
+
if (hasDeploymentInfo) {
|
|
142
|
+
eip6492Deployment = {
|
|
143
|
+
factoryAddress: erc6492Data.address,
|
|
144
|
+
factoryCalldata: erc6492Data.data
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
if (!isValid) {
|
|
148
|
+
const isSmartWallet = sigLen > 130;
|
|
149
|
+
if (!isSmartWallet) {
|
|
70
150
|
return {
|
|
71
151
|
isValid: false,
|
|
72
|
-
invalidReason:
|
|
152
|
+
invalidReason: ErrInvalidSignature,
|
|
73
153
|
payer
|
|
74
154
|
};
|
|
75
155
|
}
|
|
76
|
-
|
|
77
|
-
const
|
|
78
|
-
|
|
79
|
-
const isSmartWallet = signatureLength > 130;
|
|
80
|
-
if (isSmartWallet) {
|
|
81
|
-
const payerAddress = eip3009Payload.authorization.from;
|
|
82
|
-
const bytecode = await signer.getCode({ address: payerAddress });
|
|
83
|
-
if (!bytecode || bytecode === "0x") {
|
|
84
|
-
const erc6492Data = parseErc6492Signature(signature);
|
|
85
|
-
const hasDeploymentInfo = erc6492Data.address && erc6492Data.data && !isAddressEqual(erc6492Data.address, "0x0000000000000000000000000000000000000000");
|
|
86
|
-
if (!hasDeploymentInfo) {
|
|
87
|
-
return {
|
|
88
|
-
isValid: false,
|
|
89
|
-
invalidReason: "invalid_exact_evm_payload_undeployed_smart_wallet",
|
|
90
|
-
payer: payerAddress
|
|
91
|
-
};
|
|
92
|
-
}
|
|
93
|
-
} else {
|
|
94
|
-
return {
|
|
95
|
-
isValid: false,
|
|
96
|
-
invalidReason: "invalid_exact_evm_payload_signature",
|
|
97
|
-
payer
|
|
98
|
-
};
|
|
99
|
-
}
|
|
100
|
-
} else {
|
|
156
|
+
const bytecode = await signer.getCode({ address: payer });
|
|
157
|
+
const isDeployed = bytecode && bytecode !== "0x";
|
|
158
|
+
if (!isDeployed && !hasDeploymentInfo) {
|
|
101
159
|
return {
|
|
102
160
|
isValid: false,
|
|
103
|
-
invalidReason:
|
|
161
|
+
invalidReason: ErrUndeployedSmartWallet,
|
|
104
162
|
payer
|
|
105
163
|
};
|
|
106
164
|
}
|
|
@@ -108,7 +166,7 @@ async function verifyEIP3009(signer, payload, requirements, eip3009Payload) {
|
|
|
108
166
|
if (getAddress(eip3009Payload.authorization.to) !== getAddress(requirements.payTo)) {
|
|
109
167
|
return {
|
|
110
168
|
isValid: false,
|
|
111
|
-
invalidReason:
|
|
169
|
+
invalidReason: ErrRecipientMismatch,
|
|
112
170
|
payer
|
|
113
171
|
};
|
|
114
172
|
}
|
|
@@ -116,41 +174,41 @@ async function verifyEIP3009(signer, payload, requirements, eip3009Payload) {
|
|
|
116
174
|
if (BigInt(eip3009Payload.authorization.validBefore) < BigInt(now + 6)) {
|
|
117
175
|
return {
|
|
118
176
|
isValid: false,
|
|
119
|
-
invalidReason:
|
|
177
|
+
invalidReason: ErrValidBeforeExpired,
|
|
120
178
|
payer
|
|
121
179
|
};
|
|
122
180
|
}
|
|
123
181
|
if (BigInt(eip3009Payload.authorization.validAfter) > BigInt(now)) {
|
|
124
182
|
return {
|
|
125
183
|
isValid: false,
|
|
126
|
-
invalidReason:
|
|
184
|
+
invalidReason: ErrValidAfterInFuture,
|
|
127
185
|
payer
|
|
128
186
|
};
|
|
129
187
|
}
|
|
130
|
-
|
|
131
|
-
const balance = await signer.readContract({
|
|
132
|
-
address: erc20Address,
|
|
133
|
-
abi: eip3009ABI,
|
|
134
|
-
functionName: "balanceOf",
|
|
135
|
-
args: [eip3009Payload.authorization.from]
|
|
136
|
-
});
|
|
137
|
-
if (BigInt(balance) < BigInt(requirements.amount)) {
|
|
138
|
-
return {
|
|
139
|
-
isValid: false,
|
|
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.`,
|
|
142
|
-
payer
|
|
143
|
-
};
|
|
144
|
-
}
|
|
145
|
-
} catch {
|
|
146
|
-
}
|
|
147
|
-
if (BigInt(eip3009Payload.authorization.value) < BigInt(requirements.amount)) {
|
|
188
|
+
if (BigInt(eip3009Payload.authorization.value) !== BigInt(requirements.amount)) {
|
|
148
189
|
return {
|
|
149
190
|
isValid: false,
|
|
150
|
-
invalidReason:
|
|
191
|
+
invalidReason: ErrInvalidAuthorizationValue,
|
|
151
192
|
payer
|
|
152
193
|
};
|
|
153
194
|
}
|
|
195
|
+
if (options?.simulate !== false) {
|
|
196
|
+
const simulationSucceeded = await simulateEip3009Transfer(
|
|
197
|
+
signer,
|
|
198
|
+
erc20Address,
|
|
199
|
+
eip3009Payload,
|
|
200
|
+
eip6492Deployment
|
|
201
|
+
);
|
|
202
|
+
if (!simulationSucceeded) {
|
|
203
|
+
return diagnoseEip3009SimulationFailure(
|
|
204
|
+
signer,
|
|
205
|
+
erc20Address,
|
|
206
|
+
eip3009Payload,
|
|
207
|
+
requirements,
|
|
208
|
+
requirements.amount
|
|
209
|
+
);
|
|
210
|
+
}
|
|
211
|
+
}
|
|
154
212
|
return {
|
|
155
213
|
isValid: true,
|
|
156
214
|
invalidReason: void 0,
|
|
@@ -159,19 +217,22 @@ async function verifyEIP3009(signer, payload, requirements, eip3009Payload) {
|
|
|
159
217
|
}
|
|
160
218
|
async function settleEIP3009(signer, payload, requirements, eip3009Payload, config) {
|
|
161
219
|
const payer = eip3009Payload.authorization.from;
|
|
162
|
-
const valid = await verifyEIP3009(signer, payload, requirements, eip3009Payload
|
|
220
|
+
const valid = await verifyEIP3009(signer, payload, requirements, eip3009Payload, {
|
|
221
|
+
simulate: config.simulateInSettle ?? false
|
|
222
|
+
});
|
|
163
223
|
if (!valid.isValid) {
|
|
164
224
|
return {
|
|
165
225
|
success: false,
|
|
166
226
|
network: payload.accepted.network,
|
|
167
227
|
transaction: "",
|
|
168
|
-
errorReason: valid.invalidReason ??
|
|
228
|
+
errorReason: valid.invalidReason ?? ErrInvalidScheme,
|
|
169
229
|
payer
|
|
170
230
|
};
|
|
171
231
|
}
|
|
172
232
|
try {
|
|
173
|
-
const
|
|
174
|
-
|
|
233
|
+
const { address: factoryAddress, data: factoryCalldata } = parseErc6492Signature(
|
|
234
|
+
eip3009Payload.signature
|
|
235
|
+
);
|
|
175
236
|
if (config.deployERC4337WithEIP6492 && factoryAddress && factoryCalldata && !isAddressEqual(factoryAddress, "0x0000000000000000000000000000000000000000")) {
|
|
176
237
|
const bytecode = await signer.getCode({ address: payer });
|
|
177
238
|
if (!bytecode || bytecode === "0x") {
|
|
@@ -182,48 +243,16 @@ async function settleEIP3009(signer, payload, requirements, eip3009Payload, conf
|
|
|
182
243
|
await signer.waitForTransactionReceipt({ hash: deployTx });
|
|
183
244
|
}
|
|
184
245
|
}
|
|
185
|
-
const
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
tx = await signer.writeContract({
|
|
191
|
-
address: getAddress(requirements.asset),
|
|
192
|
-
abi: eip3009ABI,
|
|
193
|
-
functionName: "transferWithAuthorization",
|
|
194
|
-
args: [
|
|
195
|
-
getAddress(eip3009Payload.authorization.from),
|
|
196
|
-
getAddress(eip3009Payload.authorization.to),
|
|
197
|
-
BigInt(eip3009Payload.authorization.value),
|
|
198
|
-
BigInt(eip3009Payload.authorization.validAfter),
|
|
199
|
-
BigInt(eip3009Payload.authorization.validBefore),
|
|
200
|
-
eip3009Payload.authorization.nonce,
|
|
201
|
-
parsedSig.v || parsedSig.yParity,
|
|
202
|
-
parsedSig.r,
|
|
203
|
-
parsedSig.s
|
|
204
|
-
]
|
|
205
|
-
});
|
|
206
|
-
} else {
|
|
207
|
-
tx = await signer.writeContract({
|
|
208
|
-
address: getAddress(requirements.asset),
|
|
209
|
-
abi: eip3009ABI,
|
|
210
|
-
functionName: "transferWithAuthorization",
|
|
211
|
-
args: [
|
|
212
|
-
getAddress(eip3009Payload.authorization.from),
|
|
213
|
-
getAddress(eip3009Payload.authorization.to),
|
|
214
|
-
BigInt(eip3009Payload.authorization.value),
|
|
215
|
-
BigInt(eip3009Payload.authorization.validAfter),
|
|
216
|
-
BigInt(eip3009Payload.authorization.validBefore),
|
|
217
|
-
eip3009Payload.authorization.nonce,
|
|
218
|
-
signature
|
|
219
|
-
]
|
|
220
|
-
});
|
|
221
|
-
}
|
|
246
|
+
const tx = await executeTransferWithAuthorization(
|
|
247
|
+
signer,
|
|
248
|
+
getAddress(requirements.asset),
|
|
249
|
+
eip3009Payload
|
|
250
|
+
);
|
|
222
251
|
const receipt = await signer.waitForTransactionReceipt({ hash: tx });
|
|
223
252
|
if (receipt.status !== "success") {
|
|
224
253
|
return {
|
|
225
254
|
success: false,
|
|
226
|
-
errorReason:
|
|
255
|
+
errorReason: ErrTransactionFailed,
|
|
227
256
|
transaction: tx,
|
|
228
257
|
network: payload.accepted.network,
|
|
229
258
|
payer
|
|
@@ -238,7 +267,7 @@ async function settleEIP3009(signer, payload, requirements, eip3009Payload, conf
|
|
|
238
267
|
} catch {
|
|
239
268
|
return {
|
|
240
269
|
success: false,
|
|
241
|
-
errorReason:
|
|
270
|
+
errorReason: ErrTransactionFailed,
|
|
242
271
|
transaction: "",
|
|
243
272
|
network: payload.accepted.network,
|
|
244
273
|
payer
|
|
@@ -247,33 +276,7 @@ async function settleEIP3009(signer, payload, requirements, eip3009Payload, conf
|
|
|
247
276
|
}
|
|
248
277
|
|
|
249
278
|
// src/exact/facilitator/permit2.ts
|
|
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";
|
|
279
|
+
import { getAddress as getAddress4 } from "viem";
|
|
277
280
|
|
|
278
281
|
// src/exact/facilitator/erc20approval.ts
|
|
279
282
|
import {
|
|
@@ -282,9 +285,6 @@ import {
|
|
|
282
285
|
decodeFunctionData,
|
|
283
286
|
recoverTransactionAddress
|
|
284
287
|
} from "viem";
|
|
285
|
-
import {
|
|
286
|
-
validateErc20ApprovalGasSponsoringInfo
|
|
287
|
-
} from "@x402/extensions";
|
|
288
288
|
var APPROVE_SELECTOR = "0x095ea7b3";
|
|
289
289
|
async function validateErc20ApprovalForPayment(info, payer, tokenAddress) {
|
|
290
290
|
if (!validateErc20ApprovalGasSponsoringInfo(info)) {
|
|
@@ -381,36 +381,285 @@ async function validateErc20ApprovalForPayment(info, payer, tokenAddress) {
|
|
|
381
381
|
return { isValid: true };
|
|
382
382
|
}
|
|
383
383
|
|
|
384
|
+
// src/exact/facilitator/permit2-utils.ts
|
|
385
|
+
import { encodeFunctionData, getAddress as getAddress3 } from "viem";
|
|
386
|
+
async function simulatePermit2Settle(signer, permit2Payload) {
|
|
387
|
+
try {
|
|
388
|
+
await signer.readContract({
|
|
389
|
+
address: x402ExactPermit2ProxyAddress,
|
|
390
|
+
abi: x402ExactPermit2ProxyABI,
|
|
391
|
+
functionName: "settle",
|
|
392
|
+
args: buildPermit2SettleArgs(permit2Payload)
|
|
393
|
+
});
|
|
394
|
+
return true;
|
|
395
|
+
} catch {
|
|
396
|
+
return false;
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
function splitEip2612Signature(signature) {
|
|
400
|
+
const sig = signature.startsWith("0x") ? signature.slice(2) : signature;
|
|
401
|
+
if (sig.length !== 130) {
|
|
402
|
+
throw new Error(
|
|
403
|
+
`invalid EIP-2612 signature length: expected 65 bytes (130 hex chars), got ${sig.length / 2} bytes`
|
|
404
|
+
);
|
|
405
|
+
}
|
|
406
|
+
const r = `0x${sig.slice(0, 64)}`;
|
|
407
|
+
const s = `0x${sig.slice(64, 128)}`;
|
|
408
|
+
const v = parseInt(sig.slice(128, 130), 16);
|
|
409
|
+
return { v, r, s };
|
|
410
|
+
}
|
|
411
|
+
function buildPermit2SettleArgs(permit2Payload) {
|
|
412
|
+
return [
|
|
413
|
+
{
|
|
414
|
+
permitted: {
|
|
415
|
+
token: getAddress3(permit2Payload.permit2Authorization.permitted.token),
|
|
416
|
+
amount: BigInt(permit2Payload.permit2Authorization.permitted.amount)
|
|
417
|
+
},
|
|
418
|
+
nonce: BigInt(permit2Payload.permit2Authorization.nonce),
|
|
419
|
+
deadline: BigInt(permit2Payload.permit2Authorization.deadline)
|
|
420
|
+
},
|
|
421
|
+
getAddress3(permit2Payload.permit2Authorization.from),
|
|
422
|
+
{
|
|
423
|
+
to: getAddress3(permit2Payload.permit2Authorization.witness.to),
|
|
424
|
+
validAfter: BigInt(permit2Payload.permit2Authorization.witness.validAfter)
|
|
425
|
+
},
|
|
426
|
+
permit2Payload.signature
|
|
427
|
+
];
|
|
428
|
+
}
|
|
429
|
+
function encodePermit2SettleCalldata(permit2Payload) {
|
|
430
|
+
return encodeFunctionData({
|
|
431
|
+
abi: x402ExactPermit2ProxyABI,
|
|
432
|
+
functionName: "settle",
|
|
433
|
+
args: buildPermit2SettleArgs(permit2Payload)
|
|
434
|
+
});
|
|
435
|
+
}
|
|
436
|
+
async function simulatePermit2SettleWithPermit(signer, permit2Payload, eip2612Info) {
|
|
437
|
+
try {
|
|
438
|
+
const { v, r, s } = splitEip2612Signature(eip2612Info.signature);
|
|
439
|
+
await signer.readContract({
|
|
440
|
+
address: x402ExactPermit2ProxyAddress,
|
|
441
|
+
abi: x402ExactPermit2ProxyABI,
|
|
442
|
+
functionName: "settleWithPermit",
|
|
443
|
+
args: [
|
|
444
|
+
{
|
|
445
|
+
value: BigInt(eip2612Info.amount),
|
|
446
|
+
deadline: BigInt(eip2612Info.deadline),
|
|
447
|
+
r,
|
|
448
|
+
s,
|
|
449
|
+
v
|
|
450
|
+
},
|
|
451
|
+
...buildPermit2SettleArgs(permit2Payload)
|
|
452
|
+
]
|
|
453
|
+
});
|
|
454
|
+
return true;
|
|
455
|
+
} catch {
|
|
456
|
+
return false;
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
async function diagnosePermit2SimulationFailure(signer, tokenAddress, permit2Payload, amountRequired) {
|
|
460
|
+
const payer = permit2Payload.permit2Authorization.from;
|
|
461
|
+
const diagnosticCalls = [
|
|
462
|
+
{
|
|
463
|
+
address: x402ExactPermit2ProxyAddress,
|
|
464
|
+
abi: x402ExactPermit2ProxyABI,
|
|
465
|
+
functionName: "PERMIT2"
|
|
466
|
+
},
|
|
467
|
+
{
|
|
468
|
+
address: tokenAddress,
|
|
469
|
+
abi: eip3009ABI,
|
|
470
|
+
functionName: "balanceOf",
|
|
471
|
+
args: [payer]
|
|
472
|
+
},
|
|
473
|
+
{
|
|
474
|
+
address: tokenAddress,
|
|
475
|
+
abi: erc20AllowanceAbi,
|
|
476
|
+
functionName: "allowance",
|
|
477
|
+
args: [payer, PERMIT2_ADDRESS]
|
|
478
|
+
}
|
|
479
|
+
];
|
|
480
|
+
try {
|
|
481
|
+
const results = await multicall(signer.readContract.bind(signer), diagnosticCalls);
|
|
482
|
+
const [proxyResult, balanceResult, allowanceResult] = results;
|
|
483
|
+
if (proxyResult.status === "failure") {
|
|
484
|
+
return { isValid: false, invalidReason: ErrPermit2ProxyNotDeployed, payer };
|
|
485
|
+
}
|
|
486
|
+
if (balanceResult.status === "success") {
|
|
487
|
+
const balance = balanceResult.result;
|
|
488
|
+
if (balance < BigInt(amountRequired)) {
|
|
489
|
+
return { isValid: false, invalidReason: ErrPermit2InsufficientBalance, payer };
|
|
490
|
+
}
|
|
491
|
+
}
|
|
492
|
+
if (allowanceResult.status === "success") {
|
|
493
|
+
const allowance = allowanceResult.result;
|
|
494
|
+
if (allowance < BigInt(amountRequired)) {
|
|
495
|
+
return { isValid: false, invalidReason: ErrPermit2AllowanceRequired, payer };
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
} catch {
|
|
499
|
+
}
|
|
500
|
+
return { isValid: false, invalidReason: ErrPermit2SimulationFailed, payer };
|
|
501
|
+
}
|
|
502
|
+
async function checkPermit2Prerequisites(signer, tokenAddress, payer, amountRequired) {
|
|
503
|
+
const diagnosticCalls = [
|
|
504
|
+
{
|
|
505
|
+
address: x402ExactPermit2ProxyAddress,
|
|
506
|
+
abi: x402ExactPermit2ProxyABI,
|
|
507
|
+
functionName: "PERMIT2"
|
|
508
|
+
},
|
|
509
|
+
{
|
|
510
|
+
address: tokenAddress,
|
|
511
|
+
abi: eip3009ABI,
|
|
512
|
+
functionName: "balanceOf",
|
|
513
|
+
args: [payer]
|
|
514
|
+
},
|
|
515
|
+
{
|
|
516
|
+
address: MULTICALL3_ADDRESS,
|
|
517
|
+
abi: multicall3GetEthBalanceAbi,
|
|
518
|
+
functionName: "getEthBalance",
|
|
519
|
+
args: [payer]
|
|
520
|
+
}
|
|
521
|
+
];
|
|
522
|
+
try {
|
|
523
|
+
const results = await multicall(signer.readContract.bind(signer), diagnosticCalls);
|
|
524
|
+
const [proxyResult, balanceResult, ethBalanceResult] = results;
|
|
525
|
+
if (proxyResult.status === "failure") {
|
|
526
|
+
return { isValid: false, invalidReason: ErrPermit2ProxyNotDeployed, payer };
|
|
527
|
+
}
|
|
528
|
+
if (balanceResult.status === "success") {
|
|
529
|
+
const balance = balanceResult.result;
|
|
530
|
+
if (balance < BigInt(amountRequired)) {
|
|
531
|
+
return { isValid: false, invalidReason: ErrPermit2InsufficientBalance, payer };
|
|
532
|
+
}
|
|
533
|
+
}
|
|
534
|
+
if (ethBalanceResult.status === "success") {
|
|
535
|
+
const minEthForApprovalGas = ERC20_APPROVE_GAS_LIMIT * DEFAULT_MAX_FEE_PER_GAS;
|
|
536
|
+
const ethBalance = ethBalanceResult.result;
|
|
537
|
+
if (ethBalance < minEthForApprovalGas) {
|
|
538
|
+
return {
|
|
539
|
+
isValid: false,
|
|
540
|
+
invalidReason: ErrErc20ApprovalInsufficientEthForGas,
|
|
541
|
+
payer
|
|
542
|
+
};
|
|
543
|
+
}
|
|
544
|
+
}
|
|
545
|
+
} catch {
|
|
546
|
+
}
|
|
547
|
+
return { isValid: true, invalidReason: void 0, payer };
|
|
548
|
+
}
|
|
549
|
+
async function simulatePermit2SettleWithErc20Approval(extensionSigner, permit2Payload, erc20Info) {
|
|
550
|
+
if (!extensionSigner.simulateTransactions) {
|
|
551
|
+
return false;
|
|
552
|
+
}
|
|
553
|
+
try {
|
|
554
|
+
const settleData = encodePermit2SettleCalldata(permit2Payload);
|
|
555
|
+
return await extensionSigner.simulateTransactions([
|
|
556
|
+
erc20Info.signedTransaction,
|
|
557
|
+
{ to: x402ExactPermit2ProxyAddress, data: settleData, gas: BigInt(3e5) }
|
|
558
|
+
]);
|
|
559
|
+
} catch {
|
|
560
|
+
return false;
|
|
561
|
+
}
|
|
562
|
+
}
|
|
563
|
+
async function waitAndReturn(signer, tx, payload, payer) {
|
|
564
|
+
const receipt = await signer.waitForTransactionReceipt({ hash: tx });
|
|
565
|
+
if (receipt.status !== "success") {
|
|
566
|
+
return {
|
|
567
|
+
success: false,
|
|
568
|
+
errorReason: ErrInvalidTransactionState,
|
|
569
|
+
transaction: tx,
|
|
570
|
+
network: payload.accepted.network,
|
|
571
|
+
payer
|
|
572
|
+
};
|
|
573
|
+
}
|
|
574
|
+
return {
|
|
575
|
+
success: true,
|
|
576
|
+
transaction: tx,
|
|
577
|
+
network: payload.accepted.network,
|
|
578
|
+
payer
|
|
579
|
+
};
|
|
580
|
+
}
|
|
581
|
+
function mapSettleError(error, payload, payer) {
|
|
582
|
+
let errorReason = ErrTransactionFailed;
|
|
583
|
+
if (error instanceof Error) {
|
|
584
|
+
const message = error.message;
|
|
585
|
+
if (message.includes("Permit2612AmountMismatch")) {
|
|
586
|
+
errorReason = ErrPermit2612AmountMismatch;
|
|
587
|
+
} else if (message.includes("InvalidAmount")) {
|
|
588
|
+
errorReason = ErrPermit2InvalidAmount;
|
|
589
|
+
} else if (message.includes("InvalidDestination")) {
|
|
590
|
+
errorReason = ErrPermit2InvalidDestination;
|
|
591
|
+
} else if (message.includes("InvalidOwner")) {
|
|
592
|
+
errorReason = ErrPermit2InvalidOwner;
|
|
593
|
+
} else if (message.includes("PaymentTooEarly")) {
|
|
594
|
+
errorReason = ErrPermit2PaymentTooEarly;
|
|
595
|
+
} else if (message.includes("InvalidSignature") || message.includes("SignatureExpired")) {
|
|
596
|
+
errorReason = ErrPermit2InvalidSignature;
|
|
597
|
+
} else if (message.includes("InvalidNonce")) {
|
|
598
|
+
errorReason = ErrPermit2InvalidNonce;
|
|
599
|
+
} else if (message.includes("erc20_approval_tx_failed")) {
|
|
600
|
+
errorReason = ErrErc20ApprovalTxFailed;
|
|
601
|
+
} else {
|
|
602
|
+
errorReason = `${ErrTransactionFailed}: ${message.slice(0, 500)}`;
|
|
603
|
+
}
|
|
604
|
+
}
|
|
605
|
+
return {
|
|
606
|
+
success: false,
|
|
607
|
+
errorReason,
|
|
608
|
+
transaction: "",
|
|
609
|
+
network: payload.accepted.network,
|
|
610
|
+
payer
|
|
611
|
+
};
|
|
612
|
+
}
|
|
613
|
+
function validateEip2612PermitForPayment(info, payer, tokenAddress) {
|
|
614
|
+
if (!validateEip2612GasSponsoringInfo(info)) {
|
|
615
|
+
return { isValid: false, invalidReason: ErrInvalidEip2612ExtensionFormat };
|
|
616
|
+
}
|
|
617
|
+
if (getAddress3(info.from) !== getAddress3(payer)) {
|
|
618
|
+
return { isValid: false, invalidReason: ErrEip2612FromMismatch };
|
|
619
|
+
}
|
|
620
|
+
if (getAddress3(info.asset) !== tokenAddress) {
|
|
621
|
+
return { isValid: false, invalidReason: ErrEip2612AssetMismatch };
|
|
622
|
+
}
|
|
623
|
+
if (getAddress3(info.spender) !== getAddress3(PERMIT2_ADDRESS)) {
|
|
624
|
+
return { isValid: false, invalidReason: ErrEip2612SpenderNotPermit2 };
|
|
625
|
+
}
|
|
626
|
+
const now = Math.floor(Date.now() / 1e3);
|
|
627
|
+
if (BigInt(info.deadline) < BigInt(now + 6)) {
|
|
628
|
+
return { isValid: false, invalidReason: ErrEip2612DeadlineExpired };
|
|
629
|
+
}
|
|
630
|
+
return { isValid: true };
|
|
631
|
+
}
|
|
632
|
+
|
|
384
633
|
// src/exact/facilitator/permit2.ts
|
|
385
|
-
async function verifyPermit2(signer, payload, requirements, permit2Payload, context) {
|
|
634
|
+
async function verifyPermit2(signer, payload, requirements, permit2Payload, context, options) {
|
|
386
635
|
const payer = permit2Payload.permit2Authorization.from;
|
|
387
636
|
if (payload.accepted.scheme !== "exact" || requirements.scheme !== "exact") {
|
|
388
637
|
return {
|
|
389
638
|
isValid: false,
|
|
390
|
-
invalidReason:
|
|
639
|
+
invalidReason: ErrUnsupportedPayloadType,
|
|
391
640
|
payer
|
|
392
641
|
};
|
|
393
642
|
}
|
|
394
643
|
if (payload.accepted.network !== requirements.network) {
|
|
395
644
|
return {
|
|
396
645
|
isValid: false,
|
|
397
|
-
invalidReason:
|
|
646
|
+
invalidReason: ErrNetworkMismatch,
|
|
398
647
|
payer
|
|
399
648
|
};
|
|
400
649
|
}
|
|
401
650
|
const chainId = getEvmChainId(requirements.network);
|
|
402
|
-
const tokenAddress =
|
|
403
|
-
if (
|
|
651
|
+
const tokenAddress = getAddress4(requirements.asset);
|
|
652
|
+
if (getAddress4(permit2Payload.permit2Authorization.spender) !== getAddress4(x402ExactPermit2ProxyAddress)) {
|
|
404
653
|
return {
|
|
405
654
|
isValid: false,
|
|
406
|
-
invalidReason:
|
|
655
|
+
invalidReason: ErrPermit2InvalidSpender,
|
|
407
656
|
payer
|
|
408
657
|
};
|
|
409
658
|
}
|
|
410
|
-
if (
|
|
659
|
+
if (getAddress4(permit2Payload.permit2Authorization.witness.to) !== getAddress4(requirements.payTo)) {
|
|
411
660
|
return {
|
|
412
661
|
isValid: false,
|
|
413
|
-
invalidReason:
|
|
662
|
+
invalidReason: ErrPermit2RecipientMismatch,
|
|
414
663
|
payer
|
|
415
664
|
};
|
|
416
665
|
}
|
|
@@ -418,28 +667,28 @@ async function verifyPermit2(signer, payload, requirements, permit2Payload, cont
|
|
|
418
667
|
if (BigInt(permit2Payload.permit2Authorization.deadline) < BigInt(now + 6)) {
|
|
419
668
|
return {
|
|
420
669
|
isValid: false,
|
|
421
|
-
invalidReason:
|
|
670
|
+
invalidReason: ErrPermit2DeadlineExpired,
|
|
422
671
|
payer
|
|
423
672
|
};
|
|
424
673
|
}
|
|
425
674
|
if (BigInt(permit2Payload.permit2Authorization.witness.validAfter) > BigInt(now)) {
|
|
426
675
|
return {
|
|
427
676
|
isValid: false,
|
|
428
|
-
invalidReason:
|
|
677
|
+
invalidReason: ErrPermit2NotYetValid,
|
|
429
678
|
payer
|
|
430
679
|
};
|
|
431
680
|
}
|
|
432
|
-
if (BigInt(permit2Payload.permit2Authorization.permitted.amount)
|
|
681
|
+
if (BigInt(permit2Payload.permit2Authorization.permitted.amount) !== BigInt(requirements.amount)) {
|
|
433
682
|
return {
|
|
434
683
|
isValid: false,
|
|
435
|
-
invalidReason:
|
|
684
|
+
invalidReason: ErrPermit2AmountMismatch,
|
|
436
685
|
payer
|
|
437
686
|
};
|
|
438
687
|
}
|
|
439
|
-
if (
|
|
688
|
+
if (getAddress4(permit2Payload.permit2Authorization.permitted.token) !== tokenAddress) {
|
|
440
689
|
return {
|
|
441
690
|
isValid: false,
|
|
442
|
-
invalidReason:
|
|
691
|
+
invalidReason: ErrPermit2TokenMismatch,
|
|
443
692
|
payer
|
|
444
693
|
};
|
|
445
694
|
}
|
|
@@ -453,125 +702,114 @@ async function verifyPermit2(signer, payload, requirements, permit2Payload, cont
|
|
|
453
702
|
},
|
|
454
703
|
message: {
|
|
455
704
|
permitted: {
|
|
456
|
-
token:
|
|
705
|
+
token: getAddress4(permit2Payload.permit2Authorization.permitted.token),
|
|
457
706
|
amount: BigInt(permit2Payload.permit2Authorization.permitted.amount)
|
|
458
707
|
},
|
|
459
|
-
spender:
|
|
708
|
+
spender: getAddress4(permit2Payload.permit2Authorization.spender),
|
|
460
709
|
nonce: BigInt(permit2Payload.permit2Authorization.nonce),
|
|
461
710
|
deadline: BigInt(permit2Payload.permit2Authorization.deadline),
|
|
462
711
|
witness: {
|
|
463
|
-
to:
|
|
712
|
+
to: getAddress4(permit2Payload.permit2Authorization.witness.to),
|
|
464
713
|
validAfter: BigInt(permit2Payload.permit2Authorization.witness.validAfter)
|
|
465
714
|
}
|
|
466
715
|
}
|
|
467
716
|
};
|
|
717
|
+
let signatureValid = false;
|
|
468
718
|
try {
|
|
469
|
-
|
|
719
|
+
signatureValid = await signer.verifyTypedData({
|
|
470
720
|
address: payer,
|
|
471
721
|
...permit2TypedData,
|
|
472
722
|
signature: permit2Payload.signature
|
|
473
723
|
});
|
|
474
|
-
if (!isValid) {
|
|
475
|
-
return {
|
|
476
|
-
isValid: false,
|
|
477
|
-
invalidReason: "invalid_permit2_signature",
|
|
478
|
-
payer
|
|
479
|
-
};
|
|
480
|
-
}
|
|
481
724
|
} catch {
|
|
482
|
-
|
|
483
|
-
isValid: false,
|
|
484
|
-
invalidReason: "invalid_permit2_signature",
|
|
485
|
-
payer
|
|
486
|
-
};
|
|
725
|
+
signatureValid = false;
|
|
487
726
|
}
|
|
488
|
-
|
|
489
|
-
signer
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
payer,
|
|
493
|
-
tokenAddress,
|
|
494
|
-
context
|
|
495
|
-
);
|
|
496
|
-
if (allowanceResult) {
|
|
497
|
-
return allowanceResult;
|
|
498
|
-
}
|
|
499
|
-
try {
|
|
500
|
-
const balance = await signer.readContract({
|
|
501
|
-
address: tokenAddress,
|
|
502
|
-
abi: eip3009ABI,
|
|
503
|
-
functionName: "balanceOf",
|
|
504
|
-
args: [payer]
|
|
505
|
-
});
|
|
506
|
-
if (balance < BigInt(requirements.amount)) {
|
|
727
|
+
if (!signatureValid) {
|
|
728
|
+
const bytecode = await signer.getCode({ address: payer });
|
|
729
|
+
const isDeployedContract = bytecode && bytecode !== "0x";
|
|
730
|
+
if (!isDeployedContract) {
|
|
507
731
|
return {
|
|
508
732
|
isValid: false,
|
|
509
|
-
invalidReason:
|
|
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.`,
|
|
733
|
+
invalidReason: ErrPermit2InvalidSignature,
|
|
511
734
|
payer
|
|
512
735
|
};
|
|
513
736
|
}
|
|
514
|
-
} catch {
|
|
515
737
|
}
|
|
516
|
-
|
|
517
|
-
isValid: true,
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
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;
|
|
738
|
+
if (options?.simulate === false) {
|
|
739
|
+
return { isValid: true, invalidReason: void 0, payer };
|
|
740
|
+
}
|
|
741
|
+
const eip2612Info = extractEip2612GasSponsoringInfo(payload);
|
|
742
|
+
if (eip2612Info) {
|
|
743
|
+
const fieldResult = validateEip2612PermitForPayment(eip2612Info, payer, tokenAddress);
|
|
744
|
+
if (!fieldResult.isValid) {
|
|
745
|
+
return { isValid: false, invalidReason: fieldResult.invalidReason, payer };
|
|
532
746
|
}
|
|
533
|
-
const
|
|
534
|
-
if (
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
747
|
+
const simOk2 = await simulatePermit2SettleWithPermit(signer, permit2Payload, eip2612Info);
|
|
748
|
+
if (!simOk2) {
|
|
749
|
+
return diagnosePermit2SimulationFailure(
|
|
750
|
+
signer,
|
|
751
|
+
tokenAddress,
|
|
752
|
+
permit2Payload,
|
|
753
|
+
requirements.amount
|
|
754
|
+
);
|
|
540
755
|
}
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
return
|
|
756
|
+
return { isValid: true, invalidReason: void 0, payer };
|
|
757
|
+
}
|
|
758
|
+
const erc20GasSponsorshipExtension = context?.getExtension(
|
|
759
|
+
ERC20_APPROVAL_GAS_SPONSORING_KEY
|
|
760
|
+
);
|
|
761
|
+
if (erc20GasSponsorshipExtension) {
|
|
762
|
+
const erc20Info = extractErc20ApprovalGasSponsoringInfo(payload);
|
|
763
|
+
if (erc20Info) {
|
|
764
|
+
const fieldResult = await validateErc20ApprovalForPayment(erc20Info, payer, tokenAddress);
|
|
765
|
+
if (!fieldResult.isValid) {
|
|
766
|
+
return { isValid: false, invalidReason: fieldResult.invalidReason, payer };
|
|
552
767
|
}
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
768
|
+
const extensionSigner = resolveErc20ApprovalExtensionSigner(
|
|
769
|
+
erc20GasSponsorshipExtension,
|
|
770
|
+
requirements.network
|
|
771
|
+
);
|
|
772
|
+
if (extensionSigner?.simulateTransactions) {
|
|
773
|
+
const simOk2 = await simulatePermit2SettleWithErc20Approval(
|
|
774
|
+
extensionSigner,
|
|
775
|
+
permit2Payload,
|
|
776
|
+
erc20Info
|
|
777
|
+
);
|
|
778
|
+
if (!simOk2) {
|
|
779
|
+
return diagnosePermit2SimulationFailure(
|
|
780
|
+
signer,
|
|
781
|
+
tokenAddress,
|
|
782
|
+
permit2Payload,
|
|
783
|
+
requirements.amount
|
|
784
|
+
);
|
|
785
|
+
}
|
|
786
|
+
return { isValid: true, invalidReason: void 0, payer };
|
|
561
787
|
}
|
|
788
|
+
return checkPermit2Prerequisites(signer, tokenAddress, payer, requirements.amount);
|
|
562
789
|
}
|
|
563
|
-
return null;
|
|
564
790
|
}
|
|
791
|
+
const simOk = await simulatePermit2Settle(signer, permit2Payload);
|
|
792
|
+
if (!simOk) {
|
|
793
|
+
return diagnosePermit2SimulationFailure(
|
|
794
|
+
signer,
|
|
795
|
+
tokenAddress,
|
|
796
|
+
permit2Payload,
|
|
797
|
+
requirements.amount
|
|
798
|
+
);
|
|
799
|
+
}
|
|
800
|
+
return { isValid: true, invalidReason: void 0, payer };
|
|
565
801
|
}
|
|
566
|
-
async function settlePermit2(signer, payload, requirements, permit2Payload, context) {
|
|
802
|
+
async function settlePermit2(signer, payload, requirements, permit2Payload, context, config) {
|
|
567
803
|
const payer = permit2Payload.permit2Authorization.from;
|
|
568
|
-
const valid = await verifyPermit2(signer, payload, requirements, permit2Payload, context
|
|
804
|
+
const valid = await verifyPermit2(signer, payload, requirements, permit2Payload, context, {
|
|
805
|
+
simulate: config?.simulateInSettle ?? false
|
|
806
|
+
});
|
|
569
807
|
if (!valid.isValid) {
|
|
570
808
|
return {
|
|
571
809
|
success: false,
|
|
572
810
|
network: payload.accepted.network,
|
|
573
811
|
transaction: "",
|
|
574
|
-
errorReason: valid.invalidReason ??
|
|
812
|
+
errorReason: valid.invalidReason ?? ErrInvalidScheme,
|
|
575
813
|
payer
|
|
576
814
|
};
|
|
577
815
|
}
|
|
@@ -582,15 +820,14 @@ async function settlePermit2(signer, payload, requirements, permit2Payload, cont
|
|
|
582
820
|
const erc20Info = extractErc20ApprovalGasSponsoringInfo(payload);
|
|
583
821
|
if (erc20Info) {
|
|
584
822
|
const erc20GasSponsorshipExtension = context?.getExtension(
|
|
585
|
-
|
|
823
|
+
ERC20_APPROVAL_GAS_SPONSORING_KEY
|
|
586
824
|
);
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
);
|
|
825
|
+
const extensionSigner = resolveErc20ApprovalExtensionSigner(
|
|
826
|
+
erc20GasSponsorshipExtension,
|
|
827
|
+
payload.accepted.network
|
|
828
|
+
);
|
|
829
|
+
if (extensionSigner) {
|
|
830
|
+
return _settlePermit2WithERC20Approval(extensionSigner, payload, permit2Payload, erc20Info);
|
|
594
831
|
}
|
|
595
832
|
}
|
|
596
833
|
return _settlePermit2Direct(signer, payload, permit2Payload);
|
|
@@ -611,69 +848,26 @@ async function _settlePermit2WithEIP2612(signer, payload, permit2Payload, eip261
|
|
|
611
848
|
s,
|
|
612
849
|
v
|
|
613
850
|
},
|
|
614
|
-
|
|
615
|
-
permitted: {
|
|
616
|
-
token: getAddress3(permit2Payload.permit2Authorization.permitted.token),
|
|
617
|
-
amount: BigInt(permit2Payload.permit2Authorization.permitted.amount)
|
|
618
|
-
},
|
|
619
|
-
nonce: BigInt(permit2Payload.permit2Authorization.nonce),
|
|
620
|
-
deadline: BigInt(permit2Payload.permit2Authorization.deadline)
|
|
621
|
-
},
|
|
622
|
-
getAddress3(payer),
|
|
623
|
-
{
|
|
624
|
-
to: getAddress3(permit2Payload.permit2Authorization.witness.to),
|
|
625
|
-
validAfter: BigInt(permit2Payload.permit2Authorization.witness.validAfter)
|
|
626
|
-
},
|
|
627
|
-
permit2Payload.signature
|
|
851
|
+
...buildPermit2SettleArgs(permit2Payload)
|
|
628
852
|
]
|
|
629
853
|
});
|
|
630
|
-
return
|
|
854
|
+
return waitAndReturn(signer, tx, payload, payer);
|
|
631
855
|
} catch (error) {
|
|
632
|
-
return
|
|
856
|
+
return mapSettleError(error, payload, payer);
|
|
633
857
|
}
|
|
634
858
|
}
|
|
635
859
|
async function _settlePermit2WithERC20Approval(extensionSigner, payload, permit2Payload, erc20Info) {
|
|
636
860
|
const payer = permit2Payload.permit2Authorization.from;
|
|
637
861
|
try {
|
|
638
|
-
const
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
return {
|
|
646
|
-
success: false,
|
|
647
|
-
errorReason: "erc20_approval_tx_failed",
|
|
648
|
-
transaction: approvalTxHash,
|
|
649
|
-
network: payload.accepted.network,
|
|
650
|
-
payer
|
|
651
|
-
};
|
|
652
|
-
}
|
|
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);
|
|
862
|
+
const settleData = encodePermit2SettleCalldata(permit2Payload);
|
|
863
|
+
const txHashes = await extensionSigner.sendTransactions([
|
|
864
|
+
erc20Info.signedTransaction,
|
|
865
|
+
{ to: x402ExactPermit2ProxyAddress, data: settleData, gas: BigInt(3e5) }
|
|
866
|
+
]);
|
|
867
|
+
const settleTxHash = txHashes[txHashes.length - 1];
|
|
868
|
+
return waitAndReturn(extensionSigner, settleTxHash, payload, payer);
|
|
675
869
|
} catch (error) {
|
|
676
|
-
return
|
|
870
|
+
return mapSettleError(error, payload, payer);
|
|
677
871
|
}
|
|
678
872
|
}
|
|
679
873
|
async function _settlePermit2Direct(signer, payload, permit2Payload) {
|
|
@@ -683,106 +877,12 @@ async function _settlePermit2Direct(signer, payload, permit2Payload) {
|
|
|
683
877
|
address: x402ExactPermit2ProxyAddress,
|
|
684
878
|
abi: x402ExactPermit2ProxyABI,
|
|
685
879
|
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
|
-
]
|
|
880
|
+
args: buildPermit2SettleArgs(permit2Payload)
|
|
702
881
|
});
|
|
703
|
-
return
|
|
882
|
+
return waitAndReturn(signer, tx, payload, payer);
|
|
704
883
|
} catch (error) {
|
|
705
|
-
return
|
|
706
|
-
}
|
|
707
|
-
}
|
|
708
|
-
async function _waitAndReturn(signer, tx, payload, payer) {
|
|
709
|
-
const receipt = await signer.waitForTransactionReceipt({ hash: tx });
|
|
710
|
-
if (receipt.status !== "success") {
|
|
711
|
-
return {
|
|
712
|
-
success: false,
|
|
713
|
-
errorReason: "invalid_transaction_state",
|
|
714
|
-
transaction: tx,
|
|
715
|
-
network: payload.accepted.network,
|
|
716
|
-
payer
|
|
717
|
-
};
|
|
884
|
+
return mapSettleError(error, payload, payer);
|
|
718
885
|
}
|
|
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 };
|
|
786
886
|
}
|
|
787
887
|
|
|
788
888
|
// src/exact/facilitator/scheme.ts
|
|
@@ -798,7 +898,8 @@ var ExactEvmScheme = class {
|
|
|
798
898
|
this.scheme = "exact";
|
|
799
899
|
this.caipFamily = "eip155:*";
|
|
800
900
|
this.config = {
|
|
801
|
-
deployERC4337WithEIP6492: config?.deployERC4337WithEIP6492 ?? false
|
|
901
|
+
deployERC4337WithEIP6492: config?.deployERC4337WithEIP6492 ?? false,
|
|
902
|
+
simulateInSettle: config?.simulateInSettle ?? false
|
|
802
903
|
};
|
|
803
904
|
}
|
|
804
905
|
/**
|
|
@@ -829,7 +930,8 @@ var ExactEvmScheme = class {
|
|
|
829
930
|
*/
|
|
830
931
|
async verify(payload, requirements, context) {
|
|
831
932
|
const rawPayload = payload.payload;
|
|
832
|
-
|
|
933
|
+
const isPermit2 = isPermit2Payload(rawPayload);
|
|
934
|
+
if (isPermit2) {
|
|
833
935
|
return verifyPermit2(this.signer, payload, requirements, rawPayload, context);
|
|
834
936
|
}
|
|
835
937
|
const eip3009Payload = rawPayload;
|
|
@@ -845,8 +947,11 @@ var ExactEvmScheme = class {
|
|
|
845
947
|
*/
|
|
846
948
|
async settle(payload, requirements, context) {
|
|
847
949
|
const rawPayload = payload.payload;
|
|
848
|
-
|
|
849
|
-
|
|
950
|
+
const isPermit2 = isPermit2Payload(rawPayload);
|
|
951
|
+
if (isPermit2) {
|
|
952
|
+
return settlePermit2(this.signer, payload, requirements, rawPayload, context, {
|
|
953
|
+
simulateInSettle: this.config.simulateInSettle
|
|
954
|
+
});
|
|
850
955
|
}
|
|
851
956
|
const eip3009Payload = rawPayload;
|
|
852
957
|
return settleEIP3009(this.signer, payload, requirements, eip3009Payload, this.config);
|
|
@@ -858,13 +963,15 @@ function registerExactEvmScheme(facilitator, config) {
|
|
|
858
963
|
facilitator.register(
|
|
859
964
|
config.networks,
|
|
860
965
|
new ExactEvmScheme(config.signer, {
|
|
861
|
-
deployERC4337WithEIP6492: config.deployERC4337WithEIP6492
|
|
966
|
+
deployERC4337WithEIP6492: config.deployERC4337WithEIP6492,
|
|
967
|
+
simulateInSettle: config.simulateInSettle
|
|
862
968
|
})
|
|
863
969
|
);
|
|
864
970
|
facilitator.registerV1(
|
|
865
971
|
NETWORKS,
|
|
866
972
|
new ExactEvmSchemeV1(config.signer, {
|
|
867
|
-
deployERC4337WithEIP6492: config.deployERC4337WithEIP6492
|
|
973
|
+
deployERC4337WithEIP6492: config.deployERC4337WithEIP6492,
|
|
974
|
+
simulateInSettle: config.simulateInSettle
|
|
868
975
|
})
|
|
869
976
|
);
|
|
870
977
|
return facilitator;
|