@t402/evm 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +183 -0
- package/dist/cjs/exact/client/index.d.ts +53 -0
- package/dist/cjs/exact/client/index.js +270 -0
- package/dist/cjs/exact/client/index.js.map +1 -0
- package/dist/cjs/exact/facilitator/index.d.ts +118 -0
- package/dist/cjs/exact/facilitator/index.js +735 -0
- package/dist/cjs/exact/facilitator/index.js.map +1 -0
- package/dist/cjs/exact/server/index.d.ts +36 -0
- package/dist/cjs/exact/server/index.js +438 -0
- package/dist/cjs/exact/server/index.js.map +1 -0
- package/dist/cjs/exact/v1/client/index.d.ts +37 -0
- package/dist/cjs/exact/v1/client/index.js +147 -0
- package/dist/cjs/exact/v1/client/index.js.map +1 -0
- package/dist/cjs/exact/v1/facilitator/index.d.ts +62 -0
- package/dist/cjs/exact/v1/facilitator/index.js +401 -0
- package/dist/cjs/exact/v1/facilitator/index.js.map +1 -0
- package/dist/cjs/index.d.ts +1537 -0
- package/dist/cjs/index.js +2368 -0
- package/dist/cjs/index.js.map +1 -0
- package/dist/cjs/scheme-C6uD7PdY.d.ts +130 -0
- package/dist/cjs/scheme-OojTBKAz.d.ts +35 -0
- package/dist/cjs/scheme-yqGaK9rK.d.ts +130 -0
- package/dist/cjs/signer-BkcAzwYi.d.ts +79 -0
- package/dist/cjs/v1/index.d.ts +7 -0
- package/dist/cjs/v1/index.js +171 -0
- package/dist/cjs/v1/index.js.map +1 -0
- package/dist/esm/chunk-ACDQ5QNT.mjs +305 -0
- package/dist/esm/chunk-ACDQ5QNT.mjs.map +1 -0
- package/dist/esm/chunk-JBWWBRYY.mjs +92 -0
- package/dist/esm/chunk-JBWWBRYY.mjs.map +1 -0
- package/dist/esm/chunk-LGSG73NJ.mjs +88 -0
- package/dist/esm/chunk-LGSG73NJ.mjs.map +1 -0
- package/dist/esm/chunk-OEXW2OK2.mjs +251 -0
- package/dist/esm/chunk-OEXW2OK2.mjs.map +1 -0
- package/dist/esm/chunk-QLXM7BIB.mjs +23 -0
- package/dist/esm/chunk-QLXM7BIB.mjs.map +1 -0
- package/dist/esm/chunk-XYKAO6KJ.mjs +141 -0
- package/dist/esm/chunk-XYKAO6KJ.mjs.map +1 -0
- package/dist/esm/exact/client/index.d.mts +53 -0
- package/dist/esm/exact/client/index.mjs +36 -0
- package/dist/esm/exact/client/index.mjs.map +1 -0
- package/dist/esm/exact/facilitator/index.d.mts +118 -0
- package/dist/esm/exact/facilitator/index.mjs +324 -0
- package/dist/esm/exact/facilitator/index.mjs.map +1 -0
- package/dist/esm/exact/server/index.d.mts +36 -0
- package/dist/esm/exact/server/index.mjs +218 -0
- package/dist/esm/exact/server/index.mjs.map +1 -0
- package/dist/esm/exact/v1/client/index.d.mts +37 -0
- package/dist/esm/exact/v1/client/index.mjs +8 -0
- package/dist/esm/exact/v1/client/index.mjs.map +1 -0
- package/dist/esm/exact/v1/facilitator/index.d.mts +62 -0
- package/dist/esm/exact/v1/facilitator/index.mjs +8 -0
- package/dist/esm/exact/v1/facilitator/index.mjs.map +1 -0
- package/dist/esm/index.d.mts +1537 -0
- package/dist/esm/index.mjs +1875 -0
- package/dist/esm/index.mjs.map +1 -0
- package/dist/esm/scheme-D4mOqq9l.d.mts +35 -0
- package/dist/esm/scheme-yqGaK9rK.d.mts +130 -0
- package/dist/esm/signer-BkcAzwYi.d.mts +79 -0
- package/dist/esm/v1/index.d.mts +7 -0
- package/dist/esm/v1/index.mjs +13 -0
- package/dist/esm/v1/index.mjs.map +1 -0
- package/package.json +127 -0
|
@@ -0,0 +1,1875 @@
|
|
|
1
|
+
import {
|
|
2
|
+
ExactEvmScheme
|
|
3
|
+
} from "./chunk-LGSG73NJ.mjs";
|
|
4
|
+
import {
|
|
5
|
+
TOKEN_PRIORITY,
|
|
6
|
+
TOKEN_REGISTRY,
|
|
7
|
+
USDC_ADDRESSES,
|
|
8
|
+
USDT0_ADDRESSES,
|
|
9
|
+
USDT_LEGACY_ADDRESSES,
|
|
10
|
+
getDefaultToken,
|
|
11
|
+
getEIP712Domain,
|
|
12
|
+
getNetworkTokens,
|
|
13
|
+
getNetworksForToken,
|
|
14
|
+
getTokenByAddress,
|
|
15
|
+
getTokenConfig,
|
|
16
|
+
getUsdt0Networks,
|
|
17
|
+
supportsEIP3009
|
|
18
|
+
} from "./chunk-OEXW2OK2.mjs";
|
|
19
|
+
import {
|
|
20
|
+
authorizationTypes,
|
|
21
|
+
createNonce,
|
|
22
|
+
eip3009ABI,
|
|
23
|
+
erc20LegacyABI,
|
|
24
|
+
legacyAuthorizationTypes
|
|
25
|
+
} from "./chunk-XYKAO6KJ.mjs";
|
|
26
|
+
|
|
27
|
+
// src/exact-legacy/client/scheme.ts
|
|
28
|
+
import { getAddress } from "viem";
|
|
29
|
+
var ExactLegacyEvmScheme = class {
|
|
30
|
+
/**
|
|
31
|
+
* Creates a new ExactLegacyEvmScheme instance.
|
|
32
|
+
*
|
|
33
|
+
* @param signer - The EVM signer for client operations
|
|
34
|
+
*/
|
|
35
|
+
constructor(signer) {
|
|
36
|
+
this.signer = signer;
|
|
37
|
+
this.scheme = "exact-legacy";
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Creates a payment payload for the exact-legacy scheme.
|
|
41
|
+
*
|
|
42
|
+
* @param t402Version - The t402 protocol version
|
|
43
|
+
* @param paymentRequirements - The payment requirements
|
|
44
|
+
* @returns Promise resolving to a payment payload
|
|
45
|
+
*/
|
|
46
|
+
async createPaymentPayload(t402Version, paymentRequirements) {
|
|
47
|
+
if (!paymentRequirements.extra?.spender) {
|
|
48
|
+
throw new Error(
|
|
49
|
+
"exact-legacy scheme requires 'spender' (facilitator address) in payment requirements extra field"
|
|
50
|
+
);
|
|
51
|
+
}
|
|
52
|
+
const spender = getAddress(paymentRequirements.extra.spender);
|
|
53
|
+
const nonce = createNonce();
|
|
54
|
+
const now = Math.floor(Date.now() / 1e3);
|
|
55
|
+
const authorization = {
|
|
56
|
+
from: this.signer.address,
|
|
57
|
+
to: getAddress(paymentRequirements.payTo),
|
|
58
|
+
value: paymentRequirements.amount,
|
|
59
|
+
validAfter: (now - 600).toString(),
|
|
60
|
+
// 10 minutes before
|
|
61
|
+
validBefore: (now + paymentRequirements.maxTimeoutSeconds).toString(),
|
|
62
|
+
nonce,
|
|
63
|
+
spender
|
|
64
|
+
};
|
|
65
|
+
const signature = await this.signAuthorization(authorization, paymentRequirements);
|
|
66
|
+
const payload = {
|
|
67
|
+
authorization,
|
|
68
|
+
signature
|
|
69
|
+
};
|
|
70
|
+
return {
|
|
71
|
+
t402Version,
|
|
72
|
+
payload
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Sign the legacy transfer authorization using EIP-712
|
|
77
|
+
*
|
|
78
|
+
* @param authorization - The authorization to sign
|
|
79
|
+
* @param requirements - The payment requirements
|
|
80
|
+
* @returns Promise resolving to the signature
|
|
81
|
+
*/
|
|
82
|
+
async signAuthorization(authorization, requirements) {
|
|
83
|
+
const chainId = parseInt(requirements.network.split(":")[1]);
|
|
84
|
+
const name = requirements.extra?.name || "T402LegacyTransfer";
|
|
85
|
+
const version = requirements.extra?.version || "1";
|
|
86
|
+
const domain = {
|
|
87
|
+
name,
|
|
88
|
+
version,
|
|
89
|
+
chainId,
|
|
90
|
+
verifyingContract: getAddress(requirements.asset)
|
|
91
|
+
};
|
|
92
|
+
const message = {
|
|
93
|
+
from: getAddress(authorization.from),
|
|
94
|
+
to: getAddress(authorization.to),
|
|
95
|
+
value: BigInt(authorization.value),
|
|
96
|
+
validAfter: BigInt(authorization.validAfter),
|
|
97
|
+
validBefore: BigInt(authorization.validBefore),
|
|
98
|
+
nonce: authorization.nonce,
|
|
99
|
+
spender: getAddress(authorization.spender)
|
|
100
|
+
};
|
|
101
|
+
return await this.signer.signTypedData({
|
|
102
|
+
domain,
|
|
103
|
+
types: legacyAuthorizationTypes,
|
|
104
|
+
primaryType: "LegacyTransferAuthorization",
|
|
105
|
+
message
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
// src/exact-legacy/server/scheme.ts
|
|
111
|
+
var ExactLegacyEvmScheme2 = class {
|
|
112
|
+
constructor(config = {}) {
|
|
113
|
+
this.scheme = "exact-legacy";
|
|
114
|
+
this.moneyParsers = [];
|
|
115
|
+
this.config = config;
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* Register a custom money parser in the parser chain.
|
|
119
|
+
*/
|
|
120
|
+
registerMoneyParser(parser) {
|
|
121
|
+
this.moneyParsers.push(parser);
|
|
122
|
+
return this;
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* Parses a price into an asset amount for legacy tokens.
|
|
126
|
+
*/
|
|
127
|
+
async parsePrice(price, network) {
|
|
128
|
+
if (typeof price === "object" && price !== null && "amount" in price) {
|
|
129
|
+
if (!price.asset) {
|
|
130
|
+
throw new Error(`Asset address must be specified for AssetAmount on network ${network}`);
|
|
131
|
+
}
|
|
132
|
+
return {
|
|
133
|
+
amount: price.amount,
|
|
134
|
+
asset: price.asset,
|
|
135
|
+
extra: {
|
|
136
|
+
...price.extra,
|
|
137
|
+
tokenType: "legacy"
|
|
138
|
+
}
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
const amount = this.parseMoneyToDecimal(price);
|
|
142
|
+
for (const parser of this.moneyParsers) {
|
|
143
|
+
const result = await parser(amount, network);
|
|
144
|
+
if (result !== null) {
|
|
145
|
+
return result;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
return this.defaultMoneyConversion(amount, network);
|
|
149
|
+
}
|
|
150
|
+
/**
|
|
151
|
+
* Build payment requirements for this scheme/network combination.
|
|
152
|
+
* Adds the spender (facilitator) address to the extra field.
|
|
153
|
+
*/
|
|
154
|
+
enhancePaymentRequirements(paymentRequirements, supportedKind, extensionKeys) {
|
|
155
|
+
void extensionKeys;
|
|
156
|
+
const spender = supportedKind.extra?.spender;
|
|
157
|
+
return Promise.resolve({
|
|
158
|
+
...paymentRequirements,
|
|
159
|
+
extra: {
|
|
160
|
+
...paymentRequirements.extra,
|
|
161
|
+
tokenType: "legacy",
|
|
162
|
+
...spender && { spender }
|
|
163
|
+
}
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
/**
|
|
167
|
+
* Parse Money (string | number) to a decimal number.
|
|
168
|
+
*/
|
|
169
|
+
parseMoneyToDecimal(money) {
|
|
170
|
+
if (typeof money === "number") {
|
|
171
|
+
return money;
|
|
172
|
+
}
|
|
173
|
+
const cleanMoney = money.replace(/^\$/, "").trim();
|
|
174
|
+
const amount = parseFloat(cleanMoney);
|
|
175
|
+
if (isNaN(amount)) {
|
|
176
|
+
throw new Error(`Invalid money format: ${money}`);
|
|
177
|
+
}
|
|
178
|
+
return amount;
|
|
179
|
+
}
|
|
180
|
+
/**
|
|
181
|
+
* Default money conversion implementation for legacy tokens.
|
|
182
|
+
*/
|
|
183
|
+
defaultMoneyConversion(amount, network) {
|
|
184
|
+
const token = this.getDefaultAsset(network);
|
|
185
|
+
const tokenAmount = this.convertToTokenAmount(amount.toString(), token.decimals);
|
|
186
|
+
return {
|
|
187
|
+
amount: tokenAmount,
|
|
188
|
+
asset: token.address,
|
|
189
|
+
extra: {
|
|
190
|
+
name: token.name,
|
|
191
|
+
version: token.version,
|
|
192
|
+
symbol: token.symbol,
|
|
193
|
+
tokenType: "legacy"
|
|
194
|
+
}
|
|
195
|
+
};
|
|
196
|
+
}
|
|
197
|
+
/**
|
|
198
|
+
* Convert decimal amount to token units
|
|
199
|
+
*/
|
|
200
|
+
convertToTokenAmount(decimalAmount, decimals) {
|
|
201
|
+
const amount = parseFloat(decimalAmount);
|
|
202
|
+
if (isNaN(amount)) {
|
|
203
|
+
throw new Error(`Invalid amount: ${decimalAmount}`);
|
|
204
|
+
}
|
|
205
|
+
const tokenAmount = Math.floor(amount * Math.pow(10, decimals));
|
|
206
|
+
return tokenAmount.toString();
|
|
207
|
+
}
|
|
208
|
+
/**
|
|
209
|
+
* Get the default legacy token for a network.
|
|
210
|
+
*/
|
|
211
|
+
getDefaultAsset(network) {
|
|
212
|
+
if (this.config.preferredToken) {
|
|
213
|
+
const preferred = getTokenConfig(network, this.config.preferredToken);
|
|
214
|
+
if (preferred && preferred.tokenType === "legacy") {
|
|
215
|
+
return preferred;
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
const usdt = getTokenConfig(network, "USDT");
|
|
219
|
+
if (usdt && usdt.tokenType === "legacy") {
|
|
220
|
+
return usdt;
|
|
221
|
+
}
|
|
222
|
+
const tokens = TOKEN_REGISTRY[network];
|
|
223
|
+
if (tokens) {
|
|
224
|
+
const legacyToken = Object.values(tokens).find((t) => t.tokenType === "legacy");
|
|
225
|
+
if (legacyToken) return legacyToken;
|
|
226
|
+
}
|
|
227
|
+
throw new Error(`No legacy tokens configured for network ${network}`);
|
|
228
|
+
}
|
|
229
|
+
/**
|
|
230
|
+
* Get all supported networks that have legacy tokens
|
|
231
|
+
*/
|
|
232
|
+
static getSupportedNetworks() {
|
|
233
|
+
return Object.keys(USDT_LEGACY_ADDRESSES);
|
|
234
|
+
}
|
|
235
|
+
/**
|
|
236
|
+
* Check if a network has legacy token support
|
|
237
|
+
*/
|
|
238
|
+
static isNetworkSupported(network) {
|
|
239
|
+
return network in USDT_LEGACY_ADDRESSES;
|
|
240
|
+
}
|
|
241
|
+
};
|
|
242
|
+
|
|
243
|
+
// src/exact-legacy/facilitator/scheme.ts
|
|
244
|
+
import { getAddress as getAddress2, isAddressEqual } from "viem";
|
|
245
|
+
var ExactLegacyEvmScheme3 = class {
|
|
246
|
+
/**
|
|
247
|
+
* Creates a new ExactLegacyEvmScheme instance.
|
|
248
|
+
*
|
|
249
|
+
* @param signer - The EVM signer for facilitator operations
|
|
250
|
+
* @param config - Optional configuration
|
|
251
|
+
*/
|
|
252
|
+
constructor(signer, config) {
|
|
253
|
+
this.signer = signer;
|
|
254
|
+
this.scheme = "exact-legacy";
|
|
255
|
+
this.caipFamily = "eip155:*";
|
|
256
|
+
this.config = {
|
|
257
|
+
minAllowanceRatio: config?.minAllowanceRatio ?? 1
|
|
258
|
+
};
|
|
259
|
+
}
|
|
260
|
+
/**
|
|
261
|
+
* Get mechanism-specific extra data for the supported kinds endpoint.
|
|
262
|
+
* For exact-legacy, returns the spender (facilitator) addresses.
|
|
263
|
+
*
|
|
264
|
+
* @param network - The network identifier
|
|
265
|
+
* @returns Extra data including spender addresses
|
|
266
|
+
*/
|
|
267
|
+
getExtra(network) {
|
|
268
|
+
void network;
|
|
269
|
+
const addresses = this.signer.getAddresses();
|
|
270
|
+
if (addresses.length > 0) {
|
|
271
|
+
return {
|
|
272
|
+
spender: addresses[0],
|
|
273
|
+
tokenType: "legacy"
|
|
274
|
+
};
|
|
275
|
+
}
|
|
276
|
+
return { tokenType: "legacy" };
|
|
277
|
+
}
|
|
278
|
+
/**
|
|
279
|
+
* Get signer addresses used by this facilitator.
|
|
280
|
+
*/
|
|
281
|
+
getSigners(network) {
|
|
282
|
+
void network;
|
|
283
|
+
return [...this.signer.getAddresses()];
|
|
284
|
+
}
|
|
285
|
+
/**
|
|
286
|
+
* Verifies a payment payload.
|
|
287
|
+
*/
|
|
288
|
+
async verify(payload, requirements) {
|
|
289
|
+
const legacyPayload = payload.payload;
|
|
290
|
+
if (payload.accepted.scheme !== "exact-legacy" || requirements.scheme !== "exact-legacy") {
|
|
291
|
+
return {
|
|
292
|
+
isValid: false,
|
|
293
|
+
invalidReason: "unsupported_scheme",
|
|
294
|
+
payer: legacyPayload.authorization.from
|
|
295
|
+
};
|
|
296
|
+
}
|
|
297
|
+
if (payload.accepted.network !== requirements.network) {
|
|
298
|
+
return {
|
|
299
|
+
isValid: false,
|
|
300
|
+
invalidReason: "network_mismatch",
|
|
301
|
+
payer: legacyPayload.authorization.from
|
|
302
|
+
};
|
|
303
|
+
}
|
|
304
|
+
const erc20Address = getAddress2(requirements.asset);
|
|
305
|
+
const spender = getAddress2(legacyPayload.authorization.spender);
|
|
306
|
+
const facilitatorAddresses = this.signer.getAddresses();
|
|
307
|
+
const isValidSpender = facilitatorAddresses.some((addr) => isAddressEqual(addr, spender));
|
|
308
|
+
if (!isValidSpender) {
|
|
309
|
+
return {
|
|
310
|
+
isValid: false,
|
|
311
|
+
invalidReason: "invalid_spender",
|
|
312
|
+
payer: legacyPayload.authorization.from
|
|
313
|
+
};
|
|
314
|
+
}
|
|
315
|
+
const name = requirements.extra?.name || "T402LegacyTransfer";
|
|
316
|
+
const version = requirements.extra?.version || "1";
|
|
317
|
+
const chainId = parseInt(requirements.network.split(":")[1]);
|
|
318
|
+
const domain = {
|
|
319
|
+
name,
|
|
320
|
+
version,
|
|
321
|
+
chainId,
|
|
322
|
+
verifyingContract: erc20Address
|
|
323
|
+
};
|
|
324
|
+
const message = {
|
|
325
|
+
from: legacyPayload.authorization.from,
|
|
326
|
+
to: legacyPayload.authorization.to,
|
|
327
|
+
value: BigInt(legacyPayload.authorization.value),
|
|
328
|
+
validAfter: BigInt(legacyPayload.authorization.validAfter),
|
|
329
|
+
validBefore: BigInt(legacyPayload.authorization.validBefore),
|
|
330
|
+
nonce: legacyPayload.authorization.nonce,
|
|
331
|
+
spender: legacyPayload.authorization.spender
|
|
332
|
+
};
|
|
333
|
+
try {
|
|
334
|
+
const isValid = await this.signer.verifyTypedData({
|
|
335
|
+
address: legacyPayload.authorization.from,
|
|
336
|
+
domain,
|
|
337
|
+
types: legacyAuthorizationTypes,
|
|
338
|
+
primaryType: "LegacyTransferAuthorization",
|
|
339
|
+
message,
|
|
340
|
+
signature: legacyPayload.signature
|
|
341
|
+
});
|
|
342
|
+
if (!isValid) {
|
|
343
|
+
return {
|
|
344
|
+
isValid: false,
|
|
345
|
+
invalidReason: "invalid_signature",
|
|
346
|
+
payer: legacyPayload.authorization.from
|
|
347
|
+
};
|
|
348
|
+
}
|
|
349
|
+
} catch {
|
|
350
|
+
return {
|
|
351
|
+
isValid: false,
|
|
352
|
+
invalidReason: "signature_verification_failed",
|
|
353
|
+
payer: legacyPayload.authorization.from
|
|
354
|
+
};
|
|
355
|
+
}
|
|
356
|
+
if (getAddress2(legacyPayload.authorization.to) !== getAddress2(requirements.payTo)) {
|
|
357
|
+
return {
|
|
358
|
+
isValid: false,
|
|
359
|
+
invalidReason: "recipient_mismatch",
|
|
360
|
+
payer: legacyPayload.authorization.from
|
|
361
|
+
};
|
|
362
|
+
}
|
|
363
|
+
const now = Math.floor(Date.now() / 1e3);
|
|
364
|
+
if (BigInt(legacyPayload.authorization.validBefore) < BigInt(now + 6)) {
|
|
365
|
+
return {
|
|
366
|
+
isValid: false,
|
|
367
|
+
invalidReason: "authorization_expired",
|
|
368
|
+
payer: legacyPayload.authorization.from
|
|
369
|
+
};
|
|
370
|
+
}
|
|
371
|
+
if (BigInt(legacyPayload.authorization.validAfter) > BigInt(now)) {
|
|
372
|
+
return {
|
|
373
|
+
isValid: false,
|
|
374
|
+
invalidReason: "authorization_not_yet_valid",
|
|
375
|
+
payer: legacyPayload.authorization.from
|
|
376
|
+
};
|
|
377
|
+
}
|
|
378
|
+
try {
|
|
379
|
+
const balance = await this.signer.readContract({
|
|
380
|
+
address: erc20Address,
|
|
381
|
+
abi: erc20LegacyABI,
|
|
382
|
+
functionName: "balanceOf",
|
|
383
|
+
args: [legacyPayload.authorization.from]
|
|
384
|
+
});
|
|
385
|
+
if (BigInt(balance) < BigInt(requirements.amount)) {
|
|
386
|
+
return {
|
|
387
|
+
isValid: false,
|
|
388
|
+
invalidReason: "insufficient_balance",
|
|
389
|
+
payer: legacyPayload.authorization.from
|
|
390
|
+
};
|
|
391
|
+
}
|
|
392
|
+
} catch {
|
|
393
|
+
}
|
|
394
|
+
try {
|
|
395
|
+
const allowance = await this.signer.readContract({
|
|
396
|
+
address: erc20Address,
|
|
397
|
+
abi: erc20LegacyABI,
|
|
398
|
+
functionName: "allowance",
|
|
399
|
+
args: [legacyPayload.authorization.from, spender]
|
|
400
|
+
});
|
|
401
|
+
const requiredAllowance = BigInt(
|
|
402
|
+
Math.floor(Number(requirements.amount) * this.config.minAllowanceRatio)
|
|
403
|
+
);
|
|
404
|
+
if (allowance < requiredAllowance) {
|
|
405
|
+
return {
|
|
406
|
+
isValid: false,
|
|
407
|
+
invalidReason: "insufficient_allowance",
|
|
408
|
+
payer: legacyPayload.authorization.from
|
|
409
|
+
};
|
|
410
|
+
}
|
|
411
|
+
} catch {
|
|
412
|
+
return {
|
|
413
|
+
isValid: false,
|
|
414
|
+
invalidReason: "allowance_check_failed",
|
|
415
|
+
payer: legacyPayload.authorization.from
|
|
416
|
+
};
|
|
417
|
+
}
|
|
418
|
+
if (BigInt(legacyPayload.authorization.value) < BigInt(requirements.amount)) {
|
|
419
|
+
return {
|
|
420
|
+
isValid: false,
|
|
421
|
+
invalidReason: "insufficient_amount",
|
|
422
|
+
payer: legacyPayload.authorization.from
|
|
423
|
+
};
|
|
424
|
+
}
|
|
425
|
+
return {
|
|
426
|
+
isValid: true,
|
|
427
|
+
invalidReason: void 0,
|
|
428
|
+
payer: legacyPayload.authorization.from
|
|
429
|
+
};
|
|
430
|
+
}
|
|
431
|
+
/**
|
|
432
|
+
* Settles a payment by executing transferFrom.
|
|
433
|
+
*/
|
|
434
|
+
async settle(payload, requirements) {
|
|
435
|
+
const legacyPayload = payload.payload;
|
|
436
|
+
const valid = await this.verify(payload, requirements);
|
|
437
|
+
if (!valid.isValid) {
|
|
438
|
+
return {
|
|
439
|
+
success: false,
|
|
440
|
+
network: payload.accepted.network,
|
|
441
|
+
transaction: "",
|
|
442
|
+
errorReason: valid.invalidReason ?? "invalid_payment",
|
|
443
|
+
payer: legacyPayload.authorization.from
|
|
444
|
+
};
|
|
445
|
+
}
|
|
446
|
+
try {
|
|
447
|
+
const tx = await this.signer.writeContract({
|
|
448
|
+
address: getAddress2(requirements.asset),
|
|
449
|
+
abi: erc20LegacyABI,
|
|
450
|
+
functionName: "transferFrom",
|
|
451
|
+
args: [
|
|
452
|
+
getAddress2(legacyPayload.authorization.from),
|
|
453
|
+
getAddress2(legacyPayload.authorization.to),
|
|
454
|
+
BigInt(legacyPayload.authorization.value)
|
|
455
|
+
]
|
|
456
|
+
});
|
|
457
|
+
const receipt = await this.signer.waitForTransactionReceipt({ hash: tx });
|
|
458
|
+
if (receipt.status !== "success") {
|
|
459
|
+
return {
|
|
460
|
+
success: false,
|
|
461
|
+
errorReason: "transaction_failed",
|
|
462
|
+
transaction: tx,
|
|
463
|
+
network: payload.accepted.network,
|
|
464
|
+
payer: legacyPayload.authorization.from
|
|
465
|
+
};
|
|
466
|
+
}
|
|
467
|
+
return {
|
|
468
|
+
success: true,
|
|
469
|
+
transaction: tx,
|
|
470
|
+
network: payload.accepted.network,
|
|
471
|
+
payer: legacyPayload.authorization.from
|
|
472
|
+
};
|
|
473
|
+
} catch (error) {
|
|
474
|
+
console.error("Failed to settle legacy transaction:", error);
|
|
475
|
+
return {
|
|
476
|
+
success: false,
|
|
477
|
+
errorReason: "settlement_failed",
|
|
478
|
+
transaction: "",
|
|
479
|
+
network: payload.accepted.network,
|
|
480
|
+
payer: legacyPayload.authorization.from
|
|
481
|
+
};
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
};
|
|
485
|
+
|
|
486
|
+
// src/signer.ts
|
|
487
|
+
function toClientEvmSigner(signer) {
|
|
488
|
+
return signer;
|
|
489
|
+
}
|
|
490
|
+
function toFacilitatorEvmSigner(client) {
|
|
491
|
+
return {
|
|
492
|
+
...client,
|
|
493
|
+
getAddresses: () => [client.address]
|
|
494
|
+
};
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
// src/bridge/constants.ts
|
|
498
|
+
var LAYERZERO_ENDPOINT_IDS = {
|
|
499
|
+
// Mainnets
|
|
500
|
+
ethereum: 30101,
|
|
501
|
+
arbitrum: 30110,
|
|
502
|
+
base: 30184,
|
|
503
|
+
optimism: 30111,
|
|
504
|
+
polygon: 30109,
|
|
505
|
+
avalanche: 30106,
|
|
506
|
+
bsc: 30102,
|
|
507
|
+
// USDT0 specific chains
|
|
508
|
+
ink: 30291,
|
|
509
|
+
// Ink mainnet
|
|
510
|
+
berachain: 30362,
|
|
511
|
+
// Berachain mainnet
|
|
512
|
+
unichain: 30320,
|
|
513
|
+
// Unichain mainnet
|
|
514
|
+
// Testnets
|
|
515
|
+
sepolia: 40161,
|
|
516
|
+
arbitrumSepolia: 40231,
|
|
517
|
+
baseSepolia: 40245
|
|
518
|
+
};
|
|
519
|
+
var NETWORK_TO_CHAIN = {
|
|
520
|
+
"eip155:1": "ethereum",
|
|
521
|
+
"eip155:42161": "arbitrum",
|
|
522
|
+
"eip155:8453": "base",
|
|
523
|
+
"eip155:10": "optimism",
|
|
524
|
+
"eip155:137": "polygon",
|
|
525
|
+
"eip155:43114": "avalanche",
|
|
526
|
+
"eip155:56": "bsc",
|
|
527
|
+
"eip155:57073": "ink",
|
|
528
|
+
"eip155:80094": "berachain",
|
|
529
|
+
"eip155:130": "unichain",
|
|
530
|
+
// Testnets
|
|
531
|
+
"eip155:11155111": "sepolia",
|
|
532
|
+
"eip155:421614": "arbitrumSepolia",
|
|
533
|
+
"eip155:84532": "baseSepolia"
|
|
534
|
+
};
|
|
535
|
+
var CHAIN_TO_NETWORK = Object.fromEntries(
|
|
536
|
+
Object.entries(NETWORK_TO_CHAIN).map(([k, v]) => [v, k])
|
|
537
|
+
);
|
|
538
|
+
var USDT0_OFT_ADDRESSES = {
|
|
539
|
+
// Ethereum is the OFT Adapter (locks/unlocks tokens)
|
|
540
|
+
ethereum: "0x6C96dE32CEa08842dcc4058c14d3aaAD7Fa41dee",
|
|
541
|
+
// Other chains have native USDT0 OFT contracts
|
|
542
|
+
arbitrum: "0xFd086bC7CD5C481DCC9C85ebE478A1C0b69FCbb9",
|
|
543
|
+
ink: "0x0200C29006150606B650577BBE7B6248F58470c1",
|
|
544
|
+
berachain: "0x779Ded0c9e1022225f8E0630b35a9b54bE713736",
|
|
545
|
+
unichain: "0x588ce4F028D8e7B53B687865d6A67b3A54C75518"
|
|
546
|
+
};
|
|
547
|
+
var LAYERZERO_ENDPOINT_V2 = "0x1a44076050125825900e736c501f859c50fE728c";
|
|
548
|
+
var DEFAULT_EXTRA_OPTIONS = "0x00030100110100000000000000000000000000030d40";
|
|
549
|
+
var OFT_SEND_ABI = [
|
|
550
|
+
{
|
|
551
|
+
inputs: [
|
|
552
|
+
{
|
|
553
|
+
components: [
|
|
554
|
+
{ name: "dstEid", type: "uint32" },
|
|
555
|
+
{ name: "to", type: "bytes32" },
|
|
556
|
+
{ name: "amountLD", type: "uint256" },
|
|
557
|
+
{ name: "minAmountLD", type: "uint256" },
|
|
558
|
+
{ name: "extraOptions", type: "bytes" },
|
|
559
|
+
{ name: "composeMsg", type: "bytes" },
|
|
560
|
+
{ name: "oftCmd", type: "bytes" }
|
|
561
|
+
],
|
|
562
|
+
name: "_sendParam",
|
|
563
|
+
type: "tuple"
|
|
564
|
+
},
|
|
565
|
+
{
|
|
566
|
+
components: [
|
|
567
|
+
{ name: "nativeFee", type: "uint256" },
|
|
568
|
+
{ name: "lzTokenFee", type: "uint256" }
|
|
569
|
+
],
|
|
570
|
+
name: "_fee",
|
|
571
|
+
type: "tuple"
|
|
572
|
+
},
|
|
573
|
+
{ name: "_refundAddress", type: "address" }
|
|
574
|
+
],
|
|
575
|
+
name: "send",
|
|
576
|
+
outputs: [
|
|
577
|
+
{
|
|
578
|
+
components: [
|
|
579
|
+
{ name: "guid", type: "bytes32" },
|
|
580
|
+
{ name: "nonce", type: "uint64" },
|
|
581
|
+
{
|
|
582
|
+
components: [
|
|
583
|
+
{ name: "nativeFee", type: "uint256" },
|
|
584
|
+
{ name: "lzTokenFee", type: "uint256" }
|
|
585
|
+
],
|
|
586
|
+
name: "fee",
|
|
587
|
+
type: "tuple"
|
|
588
|
+
}
|
|
589
|
+
],
|
|
590
|
+
name: "msgReceipt",
|
|
591
|
+
type: "tuple"
|
|
592
|
+
},
|
|
593
|
+
{
|
|
594
|
+
components: [
|
|
595
|
+
{ name: "amountSentLD", type: "uint256" },
|
|
596
|
+
{ name: "amountReceivedLD", type: "uint256" }
|
|
597
|
+
],
|
|
598
|
+
name: "oftReceipt",
|
|
599
|
+
type: "tuple"
|
|
600
|
+
}
|
|
601
|
+
],
|
|
602
|
+
stateMutability: "payable",
|
|
603
|
+
type: "function"
|
|
604
|
+
},
|
|
605
|
+
{
|
|
606
|
+
inputs: [
|
|
607
|
+
{
|
|
608
|
+
components: [
|
|
609
|
+
{ name: "dstEid", type: "uint32" },
|
|
610
|
+
{ name: "to", type: "bytes32" },
|
|
611
|
+
{ name: "amountLD", type: "uint256" },
|
|
612
|
+
{ name: "minAmountLD", type: "uint256" },
|
|
613
|
+
{ name: "extraOptions", type: "bytes" },
|
|
614
|
+
{ name: "composeMsg", type: "bytes" },
|
|
615
|
+
{ name: "oftCmd", type: "bytes" }
|
|
616
|
+
],
|
|
617
|
+
name: "_sendParam",
|
|
618
|
+
type: "tuple"
|
|
619
|
+
},
|
|
620
|
+
{ name: "_payInLzToken", type: "bool" }
|
|
621
|
+
],
|
|
622
|
+
name: "quoteSend",
|
|
623
|
+
outputs: [
|
|
624
|
+
{
|
|
625
|
+
components: [
|
|
626
|
+
{ name: "nativeFee", type: "uint256" },
|
|
627
|
+
{ name: "lzTokenFee", type: "uint256" }
|
|
628
|
+
],
|
|
629
|
+
name: "msgFee",
|
|
630
|
+
type: "tuple"
|
|
631
|
+
}
|
|
632
|
+
],
|
|
633
|
+
stateMutability: "view",
|
|
634
|
+
type: "function"
|
|
635
|
+
}
|
|
636
|
+
];
|
|
637
|
+
var ERC20_APPROVE_ABI = [
|
|
638
|
+
{
|
|
639
|
+
inputs: [
|
|
640
|
+
{ name: "spender", type: "address" },
|
|
641
|
+
{ name: "amount", type: "uint256" }
|
|
642
|
+
],
|
|
643
|
+
name: "approve",
|
|
644
|
+
outputs: [{ name: "", type: "bool" }],
|
|
645
|
+
stateMutability: "nonpayable",
|
|
646
|
+
type: "function"
|
|
647
|
+
},
|
|
648
|
+
{
|
|
649
|
+
inputs: [
|
|
650
|
+
{ name: "owner", type: "address" },
|
|
651
|
+
{ name: "spender", type: "address" }
|
|
652
|
+
],
|
|
653
|
+
name: "allowance",
|
|
654
|
+
outputs: [{ name: "", type: "uint256" }],
|
|
655
|
+
stateMutability: "view",
|
|
656
|
+
type: "function"
|
|
657
|
+
}
|
|
658
|
+
];
|
|
659
|
+
function getEndpointId(chain) {
|
|
660
|
+
return LAYERZERO_ENDPOINT_IDS[chain];
|
|
661
|
+
}
|
|
662
|
+
function getUsdt0OftAddress(chain) {
|
|
663
|
+
return USDT0_OFT_ADDRESSES[chain];
|
|
664
|
+
}
|
|
665
|
+
function supportsBridging(chain) {
|
|
666
|
+
return chain in USDT0_OFT_ADDRESSES && chain in LAYERZERO_ENDPOINT_IDS;
|
|
667
|
+
}
|
|
668
|
+
function getBridgeableChains() {
|
|
669
|
+
return Object.keys(USDT0_OFT_ADDRESSES).filter(
|
|
670
|
+
(chain) => chain in LAYERZERO_ENDPOINT_IDS
|
|
671
|
+
);
|
|
672
|
+
}
|
|
673
|
+
function addressToBytes32(address) {
|
|
674
|
+
const cleanAddress = address.slice(2).toLowerCase();
|
|
675
|
+
return `0x${cleanAddress.padStart(64, "0")}`;
|
|
676
|
+
}
|
|
677
|
+
function bytes32ToAddress(bytes32) {
|
|
678
|
+
return `0x${bytes32.slice(-40)}`;
|
|
679
|
+
}
|
|
680
|
+
|
|
681
|
+
// src/bridge/client.ts
|
|
682
|
+
var DEFAULT_SLIPPAGE = 0.5;
|
|
683
|
+
var ESTIMATED_BRIDGE_TIME = 300;
|
|
684
|
+
var Usdt0Bridge = class {
|
|
685
|
+
/**
|
|
686
|
+
* Create a new bridge client
|
|
687
|
+
*
|
|
688
|
+
* @param signer - Wallet signer with read/write capabilities
|
|
689
|
+
* @param chain - Source chain name (e.g., "arbitrum", "ethereum")
|
|
690
|
+
*/
|
|
691
|
+
constructor(signer, chain) {
|
|
692
|
+
if (!supportsBridging(chain)) {
|
|
693
|
+
throw new Error(
|
|
694
|
+
`Chain "${chain}" does not support USDT0 bridging. Supported chains: ${getBridgeableChains().join(", ")}`
|
|
695
|
+
);
|
|
696
|
+
}
|
|
697
|
+
this.signer = signer;
|
|
698
|
+
this.chain = chain;
|
|
699
|
+
}
|
|
700
|
+
/**
|
|
701
|
+
* Get a quote for bridging USDT0
|
|
702
|
+
*
|
|
703
|
+
* @param params - Bridge parameters
|
|
704
|
+
* @returns Quote with fee and amount information
|
|
705
|
+
*/
|
|
706
|
+
async quote(params) {
|
|
707
|
+
this.validateBridgeParams(params);
|
|
708
|
+
const sendParam = this.buildSendParam(params);
|
|
709
|
+
const oftAddress = getUsdt0OftAddress(params.fromChain);
|
|
710
|
+
const fee = await this.signer.readContract({
|
|
711
|
+
address: oftAddress,
|
|
712
|
+
abi: OFT_SEND_ABI,
|
|
713
|
+
functionName: "quoteSend",
|
|
714
|
+
args: [sendParam, false]
|
|
715
|
+
});
|
|
716
|
+
return {
|
|
717
|
+
nativeFee: fee.nativeFee,
|
|
718
|
+
amountToSend: params.amount,
|
|
719
|
+
minAmountToReceive: sendParam.minAmountLD,
|
|
720
|
+
estimatedTime: ESTIMATED_BRIDGE_TIME,
|
|
721
|
+
fromChain: params.fromChain,
|
|
722
|
+
toChain: params.toChain
|
|
723
|
+
};
|
|
724
|
+
}
|
|
725
|
+
/**
|
|
726
|
+
* Execute a bridge transaction
|
|
727
|
+
*
|
|
728
|
+
* @param params - Bridge execution parameters
|
|
729
|
+
* @returns Bridge result with transaction hash
|
|
730
|
+
*/
|
|
731
|
+
async send(params) {
|
|
732
|
+
this.validateBridgeParams(params);
|
|
733
|
+
const oftAddress = getUsdt0OftAddress(params.fromChain);
|
|
734
|
+
const sendParam = this.buildSendParam(params);
|
|
735
|
+
const refundAddress = params.refundAddress ?? this.signer.address;
|
|
736
|
+
const fee = await this.signer.readContract({
|
|
737
|
+
address: oftAddress,
|
|
738
|
+
abi: OFT_SEND_ABI,
|
|
739
|
+
functionName: "quoteSend",
|
|
740
|
+
args: [sendParam, false]
|
|
741
|
+
});
|
|
742
|
+
await this.ensureAllowance(oftAddress, params.amount);
|
|
743
|
+
const txHash = await this.signer.writeContract({
|
|
744
|
+
address: oftAddress,
|
|
745
|
+
abi: OFT_SEND_ABI,
|
|
746
|
+
functionName: "send",
|
|
747
|
+
args: [sendParam, fee, refundAddress],
|
|
748
|
+
value: fee.nativeFee
|
|
749
|
+
});
|
|
750
|
+
const receipt = await this.signer.waitForTransactionReceipt({ hash: txHash });
|
|
751
|
+
if (receipt.status !== "success") {
|
|
752
|
+
throw new Error(`Bridge transaction failed: ${txHash}`);
|
|
753
|
+
}
|
|
754
|
+
return {
|
|
755
|
+
txHash,
|
|
756
|
+
messageGuid: "0x" + "0".repeat(64),
|
|
757
|
+
// Would be extracted from event logs
|
|
758
|
+
amountSent: params.amount,
|
|
759
|
+
amountToReceive: sendParam.minAmountLD,
|
|
760
|
+
fromChain: params.fromChain,
|
|
761
|
+
toChain: params.toChain,
|
|
762
|
+
estimatedTime: ESTIMATED_BRIDGE_TIME
|
|
763
|
+
};
|
|
764
|
+
}
|
|
765
|
+
/**
|
|
766
|
+
* Ensure sufficient token allowance for the OFT contract
|
|
767
|
+
*/
|
|
768
|
+
async ensureAllowance(oftAddress, amount) {
|
|
769
|
+
const allowance = await this.signer.readContract({
|
|
770
|
+
address: oftAddress,
|
|
771
|
+
abi: ERC20_APPROVE_ABI,
|
|
772
|
+
functionName: "allowance",
|
|
773
|
+
args: [this.signer.address, oftAddress]
|
|
774
|
+
});
|
|
775
|
+
if (allowance < amount) {
|
|
776
|
+
const approveTx = await this.signer.writeContract({
|
|
777
|
+
address: oftAddress,
|
|
778
|
+
abi: ERC20_APPROVE_ABI,
|
|
779
|
+
functionName: "approve",
|
|
780
|
+
args: [oftAddress, amount]
|
|
781
|
+
});
|
|
782
|
+
await this.signer.waitForTransactionReceipt({ hash: approveTx });
|
|
783
|
+
}
|
|
784
|
+
}
|
|
785
|
+
/**
|
|
786
|
+
* Build SendParam struct for LayerZero
|
|
787
|
+
*/
|
|
788
|
+
buildSendParam(params) {
|
|
789
|
+
const dstEid = getEndpointId(params.toChain);
|
|
790
|
+
if (!dstEid) {
|
|
791
|
+
throw new Error(`Unknown destination chain: ${params.toChain}`);
|
|
792
|
+
}
|
|
793
|
+
const slippage = "slippageTolerance" in params ? params.slippageTolerance ?? DEFAULT_SLIPPAGE : DEFAULT_SLIPPAGE;
|
|
794
|
+
const minAmount = params.amount - params.amount * BigInt(Math.floor(slippage * 100)) / 10000n;
|
|
795
|
+
return {
|
|
796
|
+
dstEid,
|
|
797
|
+
to: addressToBytes32(params.recipient),
|
|
798
|
+
amountLD: params.amount,
|
|
799
|
+
minAmountLD: minAmount,
|
|
800
|
+
extraOptions: DEFAULT_EXTRA_OPTIONS,
|
|
801
|
+
composeMsg: "0x",
|
|
802
|
+
oftCmd: "0x"
|
|
803
|
+
};
|
|
804
|
+
}
|
|
805
|
+
/**
|
|
806
|
+
* Validate bridge parameters
|
|
807
|
+
*/
|
|
808
|
+
validateBridgeParams(params) {
|
|
809
|
+
if (params.fromChain !== this.chain) {
|
|
810
|
+
throw new Error(
|
|
811
|
+
`Source chain mismatch: bridge initialized for "${this.chain}" but got "${params.fromChain}"`
|
|
812
|
+
);
|
|
813
|
+
}
|
|
814
|
+
if (!supportsBridging(params.fromChain)) {
|
|
815
|
+
throw new Error(`Source chain "${params.fromChain}" does not support USDT0 bridging`);
|
|
816
|
+
}
|
|
817
|
+
if (!supportsBridging(params.toChain)) {
|
|
818
|
+
throw new Error(`Destination chain "${params.toChain}" does not support USDT0 bridging`);
|
|
819
|
+
}
|
|
820
|
+
if (params.fromChain === params.toChain) {
|
|
821
|
+
throw new Error("Source and destination chains must be different");
|
|
822
|
+
}
|
|
823
|
+
if (params.amount <= 0n) {
|
|
824
|
+
throw new Error("Amount must be greater than 0");
|
|
825
|
+
}
|
|
826
|
+
}
|
|
827
|
+
/**
|
|
828
|
+
* Get all supported destination chains from current chain
|
|
829
|
+
*/
|
|
830
|
+
getSupportedDestinations() {
|
|
831
|
+
return getBridgeableChains().filter((chain) => chain !== this.chain);
|
|
832
|
+
}
|
|
833
|
+
/**
|
|
834
|
+
* Check if a destination chain is supported
|
|
835
|
+
*/
|
|
836
|
+
supportsDestination(toChain) {
|
|
837
|
+
return toChain !== this.chain && supportsBridging(toChain);
|
|
838
|
+
}
|
|
839
|
+
};
|
|
840
|
+
function createUsdt0Bridge(signer, chain) {
|
|
841
|
+
return new Usdt0Bridge(signer, chain);
|
|
842
|
+
}
|
|
843
|
+
|
|
844
|
+
// src/erc4337/constants.ts
|
|
845
|
+
var ENTRYPOINT_V07_ADDRESS = "0x0000000071727De22E5E9d8BAf0edAc6f37da032";
|
|
846
|
+
var ENTRYPOINT_V06_ADDRESS = "0x5FF137D4b0FDCD49DcA30c7CF57E578a026d2789";
|
|
847
|
+
var DEFAULT_GAS_LIMITS = {
|
|
848
|
+
/** Gas for account validation */
|
|
849
|
+
verificationGasLimit: 150000n,
|
|
850
|
+
/** Gas for callData execution */
|
|
851
|
+
callGasLimit: 100000n,
|
|
852
|
+
/** Gas paid to bundler for overhead */
|
|
853
|
+
preVerificationGas: 50000n,
|
|
854
|
+
/** Gas for paymaster validation */
|
|
855
|
+
paymasterVerificationGasLimit: 50000n,
|
|
856
|
+
/** Gas for paymaster post-op */
|
|
857
|
+
paymasterPostOpGasLimit: 50000n
|
|
858
|
+
};
|
|
859
|
+
var ENTRYPOINT_V07_ABI = [
|
|
860
|
+
{
|
|
861
|
+
inputs: [
|
|
862
|
+
{
|
|
863
|
+
components: [
|
|
864
|
+
{ name: "sender", type: "address" },
|
|
865
|
+
{ name: "nonce", type: "uint256" },
|
|
866
|
+
{ name: "initCode", type: "bytes" },
|
|
867
|
+
{ name: "callData", type: "bytes" },
|
|
868
|
+
{ name: "accountGasLimits", type: "bytes32" },
|
|
869
|
+
{ name: "preVerificationGas", type: "uint256" },
|
|
870
|
+
{ name: "gasFees", type: "bytes32" },
|
|
871
|
+
{ name: "paymasterAndData", type: "bytes" },
|
|
872
|
+
{ name: "signature", type: "bytes" }
|
|
873
|
+
],
|
|
874
|
+
name: "ops",
|
|
875
|
+
type: "tuple[]"
|
|
876
|
+
},
|
|
877
|
+
{ name: "beneficiary", type: "address" }
|
|
878
|
+
],
|
|
879
|
+
name: "handleOps",
|
|
880
|
+
outputs: [],
|
|
881
|
+
stateMutability: "nonpayable",
|
|
882
|
+
type: "function"
|
|
883
|
+
},
|
|
884
|
+
{
|
|
885
|
+
inputs: [{ name: "sender", type: "address" }],
|
|
886
|
+
name: "getNonce",
|
|
887
|
+
outputs: [{ name: "nonce", type: "uint256" }],
|
|
888
|
+
stateMutability: "view",
|
|
889
|
+
type: "function"
|
|
890
|
+
},
|
|
891
|
+
{
|
|
892
|
+
inputs: [
|
|
893
|
+
{ name: "sender", type: "address" },
|
|
894
|
+
{ name: "key", type: "uint192" }
|
|
895
|
+
],
|
|
896
|
+
name: "getNonce",
|
|
897
|
+
outputs: [{ name: "nonce", type: "uint256" }],
|
|
898
|
+
stateMutability: "view",
|
|
899
|
+
type: "function"
|
|
900
|
+
},
|
|
901
|
+
{
|
|
902
|
+
inputs: [
|
|
903
|
+
{
|
|
904
|
+
components: [
|
|
905
|
+
{ name: "sender", type: "address" },
|
|
906
|
+
{ name: "nonce", type: "uint256" },
|
|
907
|
+
{ name: "initCode", type: "bytes" },
|
|
908
|
+
{ name: "callData", type: "bytes" },
|
|
909
|
+
{ name: "accountGasLimits", type: "bytes32" },
|
|
910
|
+
{ name: "preVerificationGas", type: "uint256" },
|
|
911
|
+
{ name: "gasFees", type: "bytes32" },
|
|
912
|
+
{ name: "paymasterAndData", type: "bytes" },
|
|
913
|
+
{ name: "signature", type: "bytes" }
|
|
914
|
+
],
|
|
915
|
+
name: "userOp",
|
|
916
|
+
type: "tuple"
|
|
917
|
+
}
|
|
918
|
+
],
|
|
919
|
+
name: "getUserOpHash",
|
|
920
|
+
outputs: [{ name: "", type: "bytes32" }],
|
|
921
|
+
stateMutability: "view",
|
|
922
|
+
type: "function"
|
|
923
|
+
}
|
|
924
|
+
];
|
|
925
|
+
var ACCOUNT_ABI = [
|
|
926
|
+
{
|
|
927
|
+
inputs: [
|
|
928
|
+
{
|
|
929
|
+
components: [
|
|
930
|
+
{ name: "sender", type: "address" },
|
|
931
|
+
{ name: "nonce", type: "uint256" },
|
|
932
|
+
{ name: "initCode", type: "bytes" },
|
|
933
|
+
{ name: "callData", type: "bytes" },
|
|
934
|
+
{ name: "accountGasLimits", type: "bytes32" },
|
|
935
|
+
{ name: "preVerificationGas", type: "uint256" },
|
|
936
|
+
{ name: "gasFees", type: "bytes32" },
|
|
937
|
+
{ name: "paymasterAndData", type: "bytes" },
|
|
938
|
+
{ name: "signature", type: "bytes" }
|
|
939
|
+
],
|
|
940
|
+
name: "userOp",
|
|
941
|
+
type: "tuple"
|
|
942
|
+
},
|
|
943
|
+
{ name: "userOpHash", type: "bytes32" },
|
|
944
|
+
{ name: "missingAccountFunds", type: "uint256" }
|
|
945
|
+
],
|
|
946
|
+
name: "validateUserOp",
|
|
947
|
+
outputs: [{ name: "validationData", type: "uint256" }],
|
|
948
|
+
stateMutability: "nonpayable",
|
|
949
|
+
type: "function"
|
|
950
|
+
},
|
|
951
|
+
{
|
|
952
|
+
inputs: [
|
|
953
|
+
{ name: "dest", type: "address" },
|
|
954
|
+
{ name: "value", type: "uint256" },
|
|
955
|
+
{ name: "func", type: "bytes" }
|
|
956
|
+
],
|
|
957
|
+
name: "execute",
|
|
958
|
+
outputs: [],
|
|
959
|
+
stateMutability: "nonpayable",
|
|
960
|
+
type: "function"
|
|
961
|
+
},
|
|
962
|
+
{
|
|
963
|
+
inputs: [
|
|
964
|
+
{ name: "dest", type: "address[]" },
|
|
965
|
+
{ name: "value", type: "uint256[]" },
|
|
966
|
+
{ name: "func", type: "bytes[]" }
|
|
967
|
+
],
|
|
968
|
+
name: "executeBatch",
|
|
969
|
+
outputs: [],
|
|
970
|
+
stateMutability: "nonpayable",
|
|
971
|
+
type: "function"
|
|
972
|
+
}
|
|
973
|
+
];
|
|
974
|
+
var BUNDLER_METHODS = {
|
|
975
|
+
sendUserOperation: "eth_sendUserOperation",
|
|
976
|
+
estimateUserOperationGas: "eth_estimateUserOperationGas",
|
|
977
|
+
getUserOperationByHash: "eth_getUserOperationByHash",
|
|
978
|
+
getUserOperationReceipt: "eth_getUserOperationReceipt",
|
|
979
|
+
supportedEntryPoints: "eth_supportedEntryPoints",
|
|
980
|
+
chainId: "eth_chainId"
|
|
981
|
+
};
|
|
982
|
+
var PaymasterType = /* @__PURE__ */ ((PaymasterType2) => {
|
|
983
|
+
PaymasterType2["None"] = "none";
|
|
984
|
+
PaymasterType2["Verifying"] = "verifying";
|
|
985
|
+
PaymasterType2["Token"] = "token";
|
|
986
|
+
PaymasterType2["Sponsoring"] = "sponsoring";
|
|
987
|
+
return PaymasterType2;
|
|
988
|
+
})(PaymasterType || {});
|
|
989
|
+
function packAccountGasLimits(verificationGasLimit, callGasLimit) {
|
|
990
|
+
const verificationHex = verificationGasLimit.toString(16).padStart(32, "0");
|
|
991
|
+
const callHex = callGasLimit.toString(16).padStart(32, "0");
|
|
992
|
+
return `0x${verificationHex}${callHex}`;
|
|
993
|
+
}
|
|
994
|
+
function unpackAccountGasLimits(packed) {
|
|
995
|
+
const hex = packed.slice(2);
|
|
996
|
+
const verificationHex = hex.slice(0, 32);
|
|
997
|
+
const callHex = hex.slice(32, 64);
|
|
998
|
+
return {
|
|
999
|
+
verificationGasLimit: BigInt("0x" + verificationHex),
|
|
1000
|
+
callGasLimit: BigInt("0x" + callHex)
|
|
1001
|
+
};
|
|
1002
|
+
}
|
|
1003
|
+
function packGasFees(maxPriorityFeePerGas, maxFeePerGas) {
|
|
1004
|
+
const priorityHex = maxPriorityFeePerGas.toString(16).padStart(32, "0");
|
|
1005
|
+
const maxHex = maxFeePerGas.toString(16).padStart(32, "0");
|
|
1006
|
+
return `0x${priorityHex}${maxHex}`;
|
|
1007
|
+
}
|
|
1008
|
+
function unpackGasFees(packed) {
|
|
1009
|
+
const hex = packed.slice(2);
|
|
1010
|
+
const priorityHex = hex.slice(0, 32);
|
|
1011
|
+
const maxHex = hex.slice(32, 64);
|
|
1012
|
+
return {
|
|
1013
|
+
maxPriorityFeePerGas: BigInt("0x" + priorityHex),
|
|
1014
|
+
maxFeePerGas: BigInt("0x" + maxHex)
|
|
1015
|
+
};
|
|
1016
|
+
}
|
|
1017
|
+
|
|
1018
|
+
// src/erc4337/builder.ts
|
|
1019
|
+
import { concat, pad, toHex } from "viem";
|
|
1020
|
+
var UserOpBuilder = class {
|
|
1021
|
+
constructor(options = {}) {
|
|
1022
|
+
this.entryPoint = options.entryPoint ?? ENTRYPOINT_V07_ADDRESS;
|
|
1023
|
+
this.gasMultiplier = options.gasMultiplier ?? 1.2;
|
|
1024
|
+
}
|
|
1025
|
+
/**
|
|
1026
|
+
* Build a UserOperation from a transaction intent
|
|
1027
|
+
*/
|
|
1028
|
+
async buildUserOp(signer, intent, client, gasEstimate, paymaster) {
|
|
1029
|
+
const sender = await signer.getAddress();
|
|
1030
|
+
const nonce = await this.getNonce(client, sender);
|
|
1031
|
+
const isDeployed = await signer.isDeployed();
|
|
1032
|
+
const initCode = isDeployed ? "0x" : await signer.getInitCode();
|
|
1033
|
+
const callData = signer.encodeExecute(
|
|
1034
|
+
intent.to,
|
|
1035
|
+
intent.value ?? 0n,
|
|
1036
|
+
intent.data ?? "0x"
|
|
1037
|
+
);
|
|
1038
|
+
const { maxFeePerGas, maxPriorityFeePerGas } = await this.getGasPrices(client);
|
|
1039
|
+
const gas = gasEstimate ?? DEFAULT_GAS_LIMITS;
|
|
1040
|
+
const verificationGasLimit = this.applyMultiplier(gas.verificationGasLimit);
|
|
1041
|
+
const callGasLimit = this.applyMultiplier(gas.callGasLimit);
|
|
1042
|
+
const preVerificationGas = this.applyMultiplier(gas.preVerificationGas);
|
|
1043
|
+
const paymasterAndData = paymaster ? this.encodePaymasterData(paymaster) : "0x";
|
|
1044
|
+
return {
|
|
1045
|
+
sender,
|
|
1046
|
+
nonce,
|
|
1047
|
+
initCode,
|
|
1048
|
+
callData,
|
|
1049
|
+
verificationGasLimit,
|
|
1050
|
+
callGasLimit,
|
|
1051
|
+
preVerificationGas,
|
|
1052
|
+
maxPriorityFeePerGas,
|
|
1053
|
+
maxFeePerGas,
|
|
1054
|
+
paymasterAndData,
|
|
1055
|
+
signature: "0x"
|
|
1056
|
+
// Will be filled after signing
|
|
1057
|
+
};
|
|
1058
|
+
}
|
|
1059
|
+
/**
|
|
1060
|
+
* Build a batch UserOperation from multiple transaction intents
|
|
1061
|
+
*/
|
|
1062
|
+
async buildBatchUserOp(signer, intents, client, gasEstimate, paymaster) {
|
|
1063
|
+
const sender = await signer.getAddress();
|
|
1064
|
+
const nonce = await this.getNonce(client, sender);
|
|
1065
|
+
const isDeployed = await signer.isDeployed();
|
|
1066
|
+
const initCode = isDeployed ? "0x" : await signer.getInitCode();
|
|
1067
|
+
const targets = intents.map((i) => i.to);
|
|
1068
|
+
const values = intents.map((i) => i.value ?? 0n);
|
|
1069
|
+
const datas = intents.map((i) => i.data ?? "0x");
|
|
1070
|
+
const callData = signer.encodeExecuteBatch(targets, values, datas);
|
|
1071
|
+
const { maxFeePerGas, maxPriorityFeePerGas } = await this.getGasPrices(client);
|
|
1072
|
+
const gas = gasEstimate ?? {
|
|
1073
|
+
verificationGasLimit: DEFAULT_GAS_LIMITS.verificationGasLimit,
|
|
1074
|
+
callGasLimit: DEFAULT_GAS_LIMITS.callGasLimit * BigInt(intents.length),
|
|
1075
|
+
preVerificationGas: DEFAULT_GAS_LIMITS.preVerificationGas
|
|
1076
|
+
};
|
|
1077
|
+
const verificationGasLimit = this.applyMultiplier(gas.verificationGasLimit);
|
|
1078
|
+
const callGasLimit = this.applyMultiplier(gas.callGasLimit);
|
|
1079
|
+
const preVerificationGas = this.applyMultiplier(gas.preVerificationGas);
|
|
1080
|
+
const paymasterAndData = paymaster ? this.encodePaymasterData(paymaster) : "0x";
|
|
1081
|
+
return {
|
|
1082
|
+
sender,
|
|
1083
|
+
nonce,
|
|
1084
|
+
initCode,
|
|
1085
|
+
callData,
|
|
1086
|
+
verificationGasLimit,
|
|
1087
|
+
callGasLimit,
|
|
1088
|
+
preVerificationGas,
|
|
1089
|
+
maxPriorityFeePerGas,
|
|
1090
|
+
maxFeePerGas,
|
|
1091
|
+
paymasterAndData,
|
|
1092
|
+
signature: "0x"
|
|
1093
|
+
};
|
|
1094
|
+
}
|
|
1095
|
+
/**
|
|
1096
|
+
* Pack a UserOperation for on-chain submission (v0.7 format)
|
|
1097
|
+
*/
|
|
1098
|
+
packUserOp(userOp) {
|
|
1099
|
+
return {
|
|
1100
|
+
sender: userOp.sender,
|
|
1101
|
+
nonce: userOp.nonce,
|
|
1102
|
+
initCode: userOp.initCode,
|
|
1103
|
+
callData: userOp.callData,
|
|
1104
|
+
accountGasLimits: packAccountGasLimits(
|
|
1105
|
+
userOp.verificationGasLimit,
|
|
1106
|
+
userOp.callGasLimit
|
|
1107
|
+
),
|
|
1108
|
+
preVerificationGas: userOp.preVerificationGas,
|
|
1109
|
+
gasFees: packGasFees(userOp.maxPriorityFeePerGas, userOp.maxFeePerGas),
|
|
1110
|
+
paymasterAndData: userOp.paymasterAndData,
|
|
1111
|
+
signature: userOp.signature
|
|
1112
|
+
};
|
|
1113
|
+
}
|
|
1114
|
+
/**
|
|
1115
|
+
* Compute the UserOperation hash for signing
|
|
1116
|
+
*/
|
|
1117
|
+
async getUserOpHash(userOp, client, chainId) {
|
|
1118
|
+
const packed = this.packUserOp(userOp);
|
|
1119
|
+
const userOpTuple = {
|
|
1120
|
+
sender: packed.sender,
|
|
1121
|
+
nonce: packed.nonce,
|
|
1122
|
+
initCode: packed.initCode,
|
|
1123
|
+
callData: packed.callData,
|
|
1124
|
+
accountGasLimits: packed.accountGasLimits,
|
|
1125
|
+
preVerificationGas: packed.preVerificationGas,
|
|
1126
|
+
gasFees: packed.gasFees,
|
|
1127
|
+
paymasterAndData: packed.paymasterAndData,
|
|
1128
|
+
signature: packed.signature
|
|
1129
|
+
};
|
|
1130
|
+
const hash = await client.readContract({
|
|
1131
|
+
address: this.entryPoint,
|
|
1132
|
+
abi: ENTRYPOINT_V07_ABI,
|
|
1133
|
+
functionName: "getUserOpHash",
|
|
1134
|
+
args: [userOpTuple]
|
|
1135
|
+
});
|
|
1136
|
+
return hash;
|
|
1137
|
+
}
|
|
1138
|
+
/**
|
|
1139
|
+
* Sign a UserOperation
|
|
1140
|
+
*/
|
|
1141
|
+
async signUserOp(userOp, signer, client, chainId) {
|
|
1142
|
+
const userOpHash = await this.getUserOpHash(userOp, client, chainId);
|
|
1143
|
+
const signature = await signer.signUserOpHash(userOpHash);
|
|
1144
|
+
return {
|
|
1145
|
+
...userOp,
|
|
1146
|
+
signature
|
|
1147
|
+
};
|
|
1148
|
+
}
|
|
1149
|
+
/**
|
|
1150
|
+
* Get the nonce for an account from EntryPoint
|
|
1151
|
+
*/
|
|
1152
|
+
async getNonce(client, sender) {
|
|
1153
|
+
try {
|
|
1154
|
+
const nonce = await client.readContract({
|
|
1155
|
+
address: this.entryPoint,
|
|
1156
|
+
abi: ENTRYPOINT_V07_ABI,
|
|
1157
|
+
functionName: "getNonce",
|
|
1158
|
+
args: [sender, 0n]
|
|
1159
|
+
// Use key 0 for default nonce space
|
|
1160
|
+
});
|
|
1161
|
+
return nonce;
|
|
1162
|
+
} catch {
|
|
1163
|
+
return 0n;
|
|
1164
|
+
}
|
|
1165
|
+
}
|
|
1166
|
+
/**
|
|
1167
|
+
* Get current gas prices from the chain
|
|
1168
|
+
*/
|
|
1169
|
+
async getGasPrices(client) {
|
|
1170
|
+
const block = await client.getBlock({ blockTag: "latest" });
|
|
1171
|
+
const baseFee = block.baseFeePerGas ?? 0n;
|
|
1172
|
+
const maxPriorityFeePerGas = 1500000000n;
|
|
1173
|
+
const maxFeePerGas = baseFee * 2n + maxPriorityFeePerGas;
|
|
1174
|
+
return { maxFeePerGas, maxPriorityFeePerGas };
|
|
1175
|
+
}
|
|
1176
|
+
/**
|
|
1177
|
+
* Apply gas multiplier for safety margin
|
|
1178
|
+
*/
|
|
1179
|
+
applyMultiplier(gas) {
|
|
1180
|
+
return BigInt(Math.ceil(Number(gas) * this.gasMultiplier));
|
|
1181
|
+
}
|
|
1182
|
+
/**
|
|
1183
|
+
* Encode paymaster data for the UserOperation
|
|
1184
|
+
*/
|
|
1185
|
+
encodePaymasterData(paymaster) {
|
|
1186
|
+
const paymasterAddress = paymaster.paymaster;
|
|
1187
|
+
const verificationGas = pad(toHex(paymaster.paymasterVerificationGasLimit), {
|
|
1188
|
+
size: 16
|
|
1189
|
+
});
|
|
1190
|
+
const postOpGas = pad(toHex(paymaster.paymasterPostOpGasLimit), { size: 16 });
|
|
1191
|
+
return concat([
|
|
1192
|
+
paymasterAddress,
|
|
1193
|
+
verificationGas,
|
|
1194
|
+
postOpGas,
|
|
1195
|
+
paymaster.paymasterData
|
|
1196
|
+
]);
|
|
1197
|
+
}
|
|
1198
|
+
};
|
|
1199
|
+
function createUserOpBuilder(options) {
|
|
1200
|
+
return new UserOpBuilder(options);
|
|
1201
|
+
}
|
|
1202
|
+
|
|
1203
|
+
// src/erc4337/bundler.ts
|
|
1204
|
+
var BundlerError = class extends Error {
|
|
1205
|
+
constructor(message, code, data) {
|
|
1206
|
+
super(message);
|
|
1207
|
+
this.code = code;
|
|
1208
|
+
this.data = data;
|
|
1209
|
+
this.name = "BundlerError";
|
|
1210
|
+
}
|
|
1211
|
+
};
|
|
1212
|
+
var BundlerClient = class {
|
|
1213
|
+
constructor(config) {
|
|
1214
|
+
this.requestId = 0;
|
|
1215
|
+
this.bundlerUrl = config.bundlerUrl;
|
|
1216
|
+
this.entryPoint = config.entryPoint ?? ENTRYPOINT_V07_ADDRESS;
|
|
1217
|
+
this.chainId = config.chainId;
|
|
1218
|
+
}
|
|
1219
|
+
/**
|
|
1220
|
+
* Send a UserOperation to the bundler
|
|
1221
|
+
*/
|
|
1222
|
+
async sendUserOperation(userOp) {
|
|
1223
|
+
const packed = this.packForRpc(userOp);
|
|
1224
|
+
const userOpHash = await this.rpcCall(
|
|
1225
|
+
BUNDLER_METHODS.sendUserOperation,
|
|
1226
|
+
[packed, this.entryPoint]
|
|
1227
|
+
);
|
|
1228
|
+
return {
|
|
1229
|
+
userOpHash,
|
|
1230
|
+
wait: () => this.waitForReceipt(userOpHash)
|
|
1231
|
+
};
|
|
1232
|
+
}
|
|
1233
|
+
/**
|
|
1234
|
+
* Estimate gas for a UserOperation
|
|
1235
|
+
*/
|
|
1236
|
+
async estimateUserOperationGas(userOp) {
|
|
1237
|
+
const estimationOp = {
|
|
1238
|
+
sender: userOp.sender,
|
|
1239
|
+
nonce: userOp.nonce ?? 0n,
|
|
1240
|
+
initCode: userOp.initCode ?? "0x",
|
|
1241
|
+
callData: userOp.callData,
|
|
1242
|
+
verificationGasLimit: userOp.verificationGasLimit ?? 1000000n,
|
|
1243
|
+
callGasLimit: userOp.callGasLimit ?? 1000000n,
|
|
1244
|
+
preVerificationGas: userOp.preVerificationGas ?? 100000n,
|
|
1245
|
+
maxPriorityFeePerGas: userOp.maxPriorityFeePerGas ?? 1000000000n,
|
|
1246
|
+
maxFeePerGas: userOp.maxFeePerGas ?? 10000000000n,
|
|
1247
|
+
paymasterAndData: userOp.paymasterAndData ?? "0x",
|
|
1248
|
+
signature: userOp.signature ?? // Dummy signature for estimation
|
|
1249
|
+
"0xfffffffffffffffffffffffffffffff0000000000000000000000000000000007aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa1c"
|
|
1250
|
+
};
|
|
1251
|
+
const packed = this.packForRpc(estimationOp);
|
|
1252
|
+
const result = await this.rpcCall(BUNDLER_METHODS.estimateUserOperationGas, [packed, this.entryPoint]);
|
|
1253
|
+
return {
|
|
1254
|
+
verificationGasLimit: BigInt(result.verificationGasLimit),
|
|
1255
|
+
callGasLimit: BigInt(result.callGasLimit),
|
|
1256
|
+
preVerificationGas: BigInt(result.preVerificationGas),
|
|
1257
|
+
paymasterVerificationGasLimit: result.paymasterVerificationGasLimit ? BigInt(result.paymasterVerificationGasLimit) : void 0,
|
|
1258
|
+
paymasterPostOpGasLimit: result.paymasterPostOpGasLimit ? BigInt(result.paymasterPostOpGasLimit) : void 0
|
|
1259
|
+
};
|
|
1260
|
+
}
|
|
1261
|
+
/**
|
|
1262
|
+
* Get UserOperation by hash
|
|
1263
|
+
*/
|
|
1264
|
+
async getUserOperationByHash(userOpHash) {
|
|
1265
|
+
const result = await this.rpcCall(BUNDLER_METHODS.getUserOperationByHash, [userOpHash]);
|
|
1266
|
+
return result;
|
|
1267
|
+
}
|
|
1268
|
+
/**
|
|
1269
|
+
* Get UserOperation receipt
|
|
1270
|
+
*/
|
|
1271
|
+
async getUserOperationReceipt(userOpHash) {
|
|
1272
|
+
const result = await this.rpcCall(BUNDLER_METHODS.getUserOperationReceipt, [userOpHash]);
|
|
1273
|
+
if (!result) return null;
|
|
1274
|
+
return {
|
|
1275
|
+
userOpHash: result.userOpHash,
|
|
1276
|
+
sender: result.sender,
|
|
1277
|
+
nonce: BigInt(result.nonce),
|
|
1278
|
+
paymaster: result.paymaster,
|
|
1279
|
+
actualGasCost: BigInt(result.actualGasCost),
|
|
1280
|
+
actualGasUsed: BigInt(result.actualGasUsed),
|
|
1281
|
+
success: result.success,
|
|
1282
|
+
reason: result.reason,
|
|
1283
|
+
receipt: {
|
|
1284
|
+
transactionHash: result.receipt.transactionHash,
|
|
1285
|
+
blockNumber: BigInt(result.receipt.blockNumber),
|
|
1286
|
+
blockHash: result.receipt.blockHash
|
|
1287
|
+
}
|
|
1288
|
+
};
|
|
1289
|
+
}
|
|
1290
|
+
/**
|
|
1291
|
+
* Get supported EntryPoints
|
|
1292
|
+
*/
|
|
1293
|
+
async getSupportedEntryPoints() {
|
|
1294
|
+
return this.rpcCall(BUNDLER_METHODS.supportedEntryPoints, []);
|
|
1295
|
+
}
|
|
1296
|
+
/**
|
|
1297
|
+
* Get chain ID from bundler
|
|
1298
|
+
*/
|
|
1299
|
+
async getChainId() {
|
|
1300
|
+
const result = await this.rpcCall(BUNDLER_METHODS.chainId, []);
|
|
1301
|
+
return Number(result);
|
|
1302
|
+
}
|
|
1303
|
+
/**
|
|
1304
|
+
* Wait for UserOperation receipt with polling
|
|
1305
|
+
*/
|
|
1306
|
+
async waitForReceipt(userOpHash, options = {}) {
|
|
1307
|
+
const timeout = options.timeout ?? 6e4;
|
|
1308
|
+
const pollingInterval = options.pollingInterval ?? 2e3;
|
|
1309
|
+
const startTime = Date.now();
|
|
1310
|
+
while (Date.now() - startTime < timeout) {
|
|
1311
|
+
const receipt = await this.getUserOperationReceipt(userOpHash);
|
|
1312
|
+
if (receipt) {
|
|
1313
|
+
return receipt;
|
|
1314
|
+
}
|
|
1315
|
+
await new Promise((resolve) => setTimeout(resolve, pollingInterval));
|
|
1316
|
+
}
|
|
1317
|
+
throw new BundlerError(
|
|
1318
|
+
`Timeout waiting for UserOperation receipt: ${userOpHash}`
|
|
1319
|
+
);
|
|
1320
|
+
}
|
|
1321
|
+
/**
|
|
1322
|
+
* Pack UserOperation for RPC (convert bigints to hex strings)
|
|
1323
|
+
*/
|
|
1324
|
+
packForRpc(userOp) {
|
|
1325
|
+
return {
|
|
1326
|
+
sender: userOp.sender,
|
|
1327
|
+
nonce: this.toHex(userOp.nonce),
|
|
1328
|
+
initCode: userOp.initCode,
|
|
1329
|
+
callData: userOp.callData,
|
|
1330
|
+
accountGasLimits: packAccountGasLimits(
|
|
1331
|
+
userOp.verificationGasLimit,
|
|
1332
|
+
userOp.callGasLimit
|
|
1333
|
+
),
|
|
1334
|
+
preVerificationGas: this.toHex(userOp.preVerificationGas),
|
|
1335
|
+
gasFees: packGasFees(userOp.maxPriorityFeePerGas, userOp.maxFeePerGas),
|
|
1336
|
+
paymasterAndData: userOp.paymasterAndData,
|
|
1337
|
+
signature: userOp.signature
|
|
1338
|
+
};
|
|
1339
|
+
}
|
|
1340
|
+
/**
|
|
1341
|
+
* Convert bigint to hex string
|
|
1342
|
+
*/
|
|
1343
|
+
toHex(value) {
|
|
1344
|
+
return `0x${value.toString(16)}`;
|
|
1345
|
+
}
|
|
1346
|
+
/**
|
|
1347
|
+
* Make a JSON-RPC call to the bundler
|
|
1348
|
+
*/
|
|
1349
|
+
async rpcCall(method, params) {
|
|
1350
|
+
const request = {
|
|
1351
|
+
jsonrpc: "2.0",
|
|
1352
|
+
id: ++this.requestId,
|
|
1353
|
+
method,
|
|
1354
|
+
params
|
|
1355
|
+
};
|
|
1356
|
+
const response = await fetch(this.bundlerUrl, {
|
|
1357
|
+
method: "POST",
|
|
1358
|
+
headers: {
|
|
1359
|
+
"Content-Type": "application/json"
|
|
1360
|
+
},
|
|
1361
|
+
body: JSON.stringify(request)
|
|
1362
|
+
});
|
|
1363
|
+
if (!response.ok) {
|
|
1364
|
+
throw new BundlerError(
|
|
1365
|
+
`HTTP error: ${response.status} ${response.statusText}`
|
|
1366
|
+
);
|
|
1367
|
+
}
|
|
1368
|
+
const json = await response.json();
|
|
1369
|
+
if (json.error) {
|
|
1370
|
+
throw new BundlerError(json.error.message, json.error.code, json.error.data);
|
|
1371
|
+
}
|
|
1372
|
+
return json.result;
|
|
1373
|
+
}
|
|
1374
|
+
};
|
|
1375
|
+
function createBundlerClient(config) {
|
|
1376
|
+
return new BundlerClient(config);
|
|
1377
|
+
}
|
|
1378
|
+
|
|
1379
|
+
// src/erc4337/paymaster.ts
|
|
1380
|
+
import { concat as concat2, pad as pad2, toHex as toHex2 } from "viem";
|
|
1381
|
+
var PaymasterClient = class {
|
|
1382
|
+
constructor(config) {
|
|
1383
|
+
this.config = config;
|
|
1384
|
+
}
|
|
1385
|
+
/**
|
|
1386
|
+
* Get paymaster data for a UserOperation
|
|
1387
|
+
*/
|
|
1388
|
+
async getPaymasterData(userOp, chainId, entryPoint, context) {
|
|
1389
|
+
switch (this.config.type) {
|
|
1390
|
+
case "verifying":
|
|
1391
|
+
return this.getVerifyingPaymasterData(userOp, chainId, entryPoint);
|
|
1392
|
+
case "sponsoring":
|
|
1393
|
+
return this.getSponsoringPaymasterData(
|
|
1394
|
+
userOp,
|
|
1395
|
+
chainId,
|
|
1396
|
+
entryPoint,
|
|
1397
|
+
context
|
|
1398
|
+
);
|
|
1399
|
+
case "token":
|
|
1400
|
+
return this.getTokenPaymasterData(userOp, chainId, entryPoint);
|
|
1401
|
+
default:
|
|
1402
|
+
throw new Error(`Unknown paymaster type: ${this.config.type}`);
|
|
1403
|
+
}
|
|
1404
|
+
}
|
|
1405
|
+
/**
|
|
1406
|
+
* Get gas estimates including paymaster gas
|
|
1407
|
+
*/
|
|
1408
|
+
async estimatePaymasterGas(userOp, _chainId) {
|
|
1409
|
+
return {
|
|
1410
|
+
verificationGasLimit: DEFAULT_GAS_LIMITS.verificationGasLimit,
|
|
1411
|
+
callGasLimit: DEFAULT_GAS_LIMITS.callGasLimit,
|
|
1412
|
+
preVerificationGas: DEFAULT_GAS_LIMITS.preVerificationGas,
|
|
1413
|
+
paymasterVerificationGasLimit: DEFAULT_GAS_LIMITS.paymasterVerificationGasLimit,
|
|
1414
|
+
paymasterPostOpGasLimit: DEFAULT_GAS_LIMITS.paymasterPostOpGasLimit
|
|
1415
|
+
};
|
|
1416
|
+
}
|
|
1417
|
+
/**
|
|
1418
|
+
* Check if the paymaster will sponsor this operation
|
|
1419
|
+
*/
|
|
1420
|
+
async willSponsor(userOp, chainId, entryPoint, context) {
|
|
1421
|
+
if (!this.config.url) {
|
|
1422
|
+
return true;
|
|
1423
|
+
}
|
|
1424
|
+
try {
|
|
1425
|
+
const response = await fetch(`${this.config.url}/check`, {
|
|
1426
|
+
method: "POST",
|
|
1427
|
+
headers: { "Content-Type": "application/json" },
|
|
1428
|
+
body: JSON.stringify({
|
|
1429
|
+
userOp: this.serializeUserOp(userOp),
|
|
1430
|
+
chainId,
|
|
1431
|
+
entryPoint,
|
|
1432
|
+
context
|
|
1433
|
+
})
|
|
1434
|
+
});
|
|
1435
|
+
if (!response.ok) return false;
|
|
1436
|
+
const result = await response.json();
|
|
1437
|
+
return result.willSponsor;
|
|
1438
|
+
} catch {
|
|
1439
|
+
return false;
|
|
1440
|
+
}
|
|
1441
|
+
}
|
|
1442
|
+
/**
|
|
1443
|
+
* Get verifying paymaster data (off-chain signature)
|
|
1444
|
+
*/
|
|
1445
|
+
async getVerifyingPaymasterData(userOp, chainId, entryPoint) {
|
|
1446
|
+
if (this.config.url) {
|
|
1447
|
+
return this.callPaymasterService(userOp, chainId, entryPoint);
|
|
1448
|
+
}
|
|
1449
|
+
return {
|
|
1450
|
+
paymaster: this.config.address,
|
|
1451
|
+
paymasterVerificationGasLimit: DEFAULT_GAS_LIMITS.paymasterVerificationGasLimit,
|
|
1452
|
+
paymasterPostOpGasLimit: DEFAULT_GAS_LIMITS.paymasterPostOpGasLimit,
|
|
1453
|
+
paymasterData: "0x"
|
|
1454
|
+
};
|
|
1455
|
+
}
|
|
1456
|
+
/**
|
|
1457
|
+
* Get sponsoring paymaster data (third-party pays)
|
|
1458
|
+
*/
|
|
1459
|
+
async getSponsoringPaymasterData(userOp, chainId, entryPoint, context) {
|
|
1460
|
+
if (!this.config.url) {
|
|
1461
|
+
throw new Error("Sponsoring paymaster requires a service URL");
|
|
1462
|
+
}
|
|
1463
|
+
const response = await fetch(`${this.config.url}/sponsor`, {
|
|
1464
|
+
method: "POST",
|
|
1465
|
+
headers: { "Content-Type": "application/json" },
|
|
1466
|
+
body: JSON.stringify({
|
|
1467
|
+
userOp: this.serializeUserOp(userOp),
|
|
1468
|
+
chainId,
|
|
1469
|
+
entryPoint,
|
|
1470
|
+
context
|
|
1471
|
+
})
|
|
1472
|
+
});
|
|
1473
|
+
if (!response.ok) {
|
|
1474
|
+
const error = await response.text();
|
|
1475
|
+
throw new Error(`Paymaster rejected sponsorship: ${error}`);
|
|
1476
|
+
}
|
|
1477
|
+
const result = await response.json();
|
|
1478
|
+
return {
|
|
1479
|
+
paymaster: result.paymaster,
|
|
1480
|
+
paymasterVerificationGasLimit: result.paymasterVerificationGasLimit,
|
|
1481
|
+
paymasterPostOpGasLimit: result.paymasterPostOpGasLimit,
|
|
1482
|
+
paymasterData: result.paymasterData
|
|
1483
|
+
};
|
|
1484
|
+
}
|
|
1485
|
+
/**
|
|
1486
|
+
* Get token paymaster data (pay gas with ERC20)
|
|
1487
|
+
*/
|
|
1488
|
+
async getTokenPaymasterData(userOp, chainId, entryPoint) {
|
|
1489
|
+
const tokenAddress = this.config.options?.tokenAddress;
|
|
1490
|
+
if (!tokenAddress) {
|
|
1491
|
+
throw new Error("Token paymaster requires tokenAddress in options");
|
|
1492
|
+
}
|
|
1493
|
+
if (this.config.url) {
|
|
1494
|
+
return this.callPaymasterService(userOp, chainId, entryPoint, {
|
|
1495
|
+
tokenAddress
|
|
1496
|
+
});
|
|
1497
|
+
}
|
|
1498
|
+
return {
|
|
1499
|
+
paymaster: this.config.address,
|
|
1500
|
+
paymasterVerificationGasLimit: DEFAULT_GAS_LIMITS.paymasterVerificationGasLimit,
|
|
1501
|
+
paymasterPostOpGasLimit: DEFAULT_GAS_LIMITS.paymasterPostOpGasLimit,
|
|
1502
|
+
paymasterData: tokenAddress
|
|
1503
|
+
// Token address as data
|
|
1504
|
+
};
|
|
1505
|
+
}
|
|
1506
|
+
/**
|
|
1507
|
+
* Call paymaster service API
|
|
1508
|
+
*/
|
|
1509
|
+
async callPaymasterService(userOp, chainId, entryPoint, context) {
|
|
1510
|
+
if (!this.config.url) {
|
|
1511
|
+
throw new Error("Paymaster service URL not configured");
|
|
1512
|
+
}
|
|
1513
|
+
const response = await fetch(`${this.config.url}/getPaymasterData`, {
|
|
1514
|
+
method: "POST",
|
|
1515
|
+
headers: { "Content-Type": "application/json" },
|
|
1516
|
+
body: JSON.stringify({
|
|
1517
|
+
userOp: this.serializeUserOp(userOp),
|
|
1518
|
+
chainId,
|
|
1519
|
+
entryPoint,
|
|
1520
|
+
context
|
|
1521
|
+
})
|
|
1522
|
+
});
|
|
1523
|
+
if (!response.ok) {
|
|
1524
|
+
const error = await response.text();
|
|
1525
|
+
throw new Error(`Paymaster service error: ${error}`);
|
|
1526
|
+
}
|
|
1527
|
+
const result = await response.json();
|
|
1528
|
+
return {
|
|
1529
|
+
paymaster: result.paymaster,
|
|
1530
|
+
paymasterVerificationGasLimit: BigInt(result.paymasterVerificationGasLimit),
|
|
1531
|
+
paymasterPostOpGasLimit: BigInt(result.paymasterPostOpGasLimit),
|
|
1532
|
+
paymasterData: result.paymasterData
|
|
1533
|
+
};
|
|
1534
|
+
}
|
|
1535
|
+
/**
|
|
1536
|
+
* Serialize UserOperation for API calls
|
|
1537
|
+
*/
|
|
1538
|
+
serializeUserOp(userOp) {
|
|
1539
|
+
const result = {};
|
|
1540
|
+
if (userOp.sender) result.sender = userOp.sender;
|
|
1541
|
+
if (userOp.nonce !== void 0)
|
|
1542
|
+
result.nonce = `0x${userOp.nonce.toString(16)}`;
|
|
1543
|
+
if (userOp.initCode) result.initCode = userOp.initCode;
|
|
1544
|
+
if (userOp.callData) result.callData = userOp.callData;
|
|
1545
|
+
if (userOp.verificationGasLimit !== void 0)
|
|
1546
|
+
result.verificationGasLimit = `0x${userOp.verificationGasLimit.toString(16)}`;
|
|
1547
|
+
if (userOp.callGasLimit !== void 0)
|
|
1548
|
+
result.callGasLimit = `0x${userOp.callGasLimit.toString(16)}`;
|
|
1549
|
+
if (userOp.preVerificationGas !== void 0)
|
|
1550
|
+
result.preVerificationGas = `0x${userOp.preVerificationGas.toString(16)}`;
|
|
1551
|
+
if (userOp.maxPriorityFeePerGas !== void 0)
|
|
1552
|
+
result.maxPriorityFeePerGas = `0x${userOp.maxPriorityFeePerGas.toString(16)}`;
|
|
1553
|
+
if (userOp.maxFeePerGas !== void 0)
|
|
1554
|
+
result.maxFeePerGas = `0x${userOp.maxFeePerGas.toString(16)}`;
|
|
1555
|
+
if (userOp.paymasterAndData) result.paymasterAndData = userOp.paymasterAndData;
|
|
1556
|
+
if (userOp.signature) result.signature = userOp.signature;
|
|
1557
|
+
return result;
|
|
1558
|
+
}
|
|
1559
|
+
};
|
|
1560
|
+
function createPaymasterClient(config) {
|
|
1561
|
+
return new PaymasterClient(config);
|
|
1562
|
+
}
|
|
1563
|
+
function encodePaymasterAndData(data) {
|
|
1564
|
+
return concat2([
|
|
1565
|
+
data.paymaster,
|
|
1566
|
+
pad2(toHex2(data.paymasterVerificationGasLimit), { size: 16 }),
|
|
1567
|
+
pad2(toHex2(data.paymasterPostOpGasLimit), { size: 16 }),
|
|
1568
|
+
data.paymasterData
|
|
1569
|
+
]);
|
|
1570
|
+
}
|
|
1571
|
+
function decodePaymasterAndData(paymasterAndData) {
|
|
1572
|
+
if (paymasterAndData === "0x" || paymasterAndData.length < 86) {
|
|
1573
|
+
return null;
|
|
1574
|
+
}
|
|
1575
|
+
const paymaster = `0x${paymasterAndData.slice(2, 42)}`;
|
|
1576
|
+
const paymasterVerificationGasLimit = BigInt(
|
|
1577
|
+
`0x${paymasterAndData.slice(42, 74)}`
|
|
1578
|
+
);
|
|
1579
|
+
const paymasterPostOpGasLimit = BigInt(`0x${paymasterAndData.slice(74, 106)}`);
|
|
1580
|
+
const paymasterData = `0x${paymasterAndData.slice(106)}`;
|
|
1581
|
+
return {
|
|
1582
|
+
paymaster,
|
|
1583
|
+
paymasterVerificationGasLimit,
|
|
1584
|
+
paymasterPostOpGasLimit,
|
|
1585
|
+
paymasterData
|
|
1586
|
+
};
|
|
1587
|
+
}
|
|
1588
|
+
|
|
1589
|
+
// src/erc4337/t402.ts
|
|
1590
|
+
import { encodeFunctionData as encodeFunctionData2 } from "viem";
|
|
1591
|
+
var ERC20_TRANSFER_ABI = [
|
|
1592
|
+
{
|
|
1593
|
+
inputs: [
|
|
1594
|
+
{ name: "to", type: "address" },
|
|
1595
|
+
{ name: "amount", type: "uint256" }
|
|
1596
|
+
],
|
|
1597
|
+
name: "transfer",
|
|
1598
|
+
outputs: [{ name: "", type: "bool" }],
|
|
1599
|
+
stateMutability: "nonpayable",
|
|
1600
|
+
type: "function"
|
|
1601
|
+
}
|
|
1602
|
+
];
|
|
1603
|
+
var EIP3009_TRANSFER_ABI = [
|
|
1604
|
+
{
|
|
1605
|
+
inputs: [
|
|
1606
|
+
{ name: "from", type: "address" },
|
|
1607
|
+
{ name: "to", type: "address" },
|
|
1608
|
+
{ name: "value", type: "uint256" },
|
|
1609
|
+
{ name: "validAfter", type: "uint256" },
|
|
1610
|
+
{ name: "validBefore", type: "uint256" },
|
|
1611
|
+
{ name: "nonce", type: "bytes32" },
|
|
1612
|
+
{ name: "v", type: "uint8" },
|
|
1613
|
+
{ name: "r", type: "bytes32" },
|
|
1614
|
+
{ name: "s", type: "bytes32" }
|
|
1615
|
+
],
|
|
1616
|
+
name: "transferWithAuthorization",
|
|
1617
|
+
outputs: [],
|
|
1618
|
+
stateMutability: "nonpayable",
|
|
1619
|
+
type: "function"
|
|
1620
|
+
}
|
|
1621
|
+
];
|
|
1622
|
+
var GaslessT402Client = class {
|
|
1623
|
+
constructor(config) {
|
|
1624
|
+
this.signer = config.signer;
|
|
1625
|
+
this.builder = new UserOpBuilder();
|
|
1626
|
+
this.bundler = new BundlerClient(config.bundler);
|
|
1627
|
+
this.paymaster = config.paymaster ? new PaymasterClient(config.paymaster) : void 0;
|
|
1628
|
+
this.chainId = config.chainId;
|
|
1629
|
+
this.publicClient = config.publicClient;
|
|
1630
|
+
}
|
|
1631
|
+
/**
|
|
1632
|
+
* Execute a T402 payment via ERC-4337
|
|
1633
|
+
*
|
|
1634
|
+
* This submits the payment as a UserOperation which can be:
|
|
1635
|
+
* - Sponsored by a paymaster (truly gasless)
|
|
1636
|
+
* - Paid from the smart account's balance
|
|
1637
|
+
*/
|
|
1638
|
+
async executePayment(params) {
|
|
1639
|
+
const callData = params.authorization ? this.buildAuthorizedTransferCallData(params) : this.buildTransferCallData(params);
|
|
1640
|
+
const intent = {
|
|
1641
|
+
to: params.tokenAddress,
|
|
1642
|
+
value: 0n,
|
|
1643
|
+
data: callData
|
|
1644
|
+
};
|
|
1645
|
+
const gasEstimate = await this.estimateGas(intent);
|
|
1646
|
+
const paymasterData = await this.getPaymasterData(gasEstimate);
|
|
1647
|
+
const userOp = await this.builder.buildUserOp(
|
|
1648
|
+
this.signer,
|
|
1649
|
+
intent,
|
|
1650
|
+
this.publicClient,
|
|
1651
|
+
gasEstimate,
|
|
1652
|
+
paymasterData
|
|
1653
|
+
);
|
|
1654
|
+
const signedUserOp = await this.builder.signUserOp(
|
|
1655
|
+
userOp,
|
|
1656
|
+
this.signer,
|
|
1657
|
+
this.publicClient,
|
|
1658
|
+
this.chainId
|
|
1659
|
+
);
|
|
1660
|
+
return this.bundler.sendUserOperation(signedUserOp);
|
|
1661
|
+
}
|
|
1662
|
+
/**
|
|
1663
|
+
* Execute multiple T402 payments in a single UserOperation
|
|
1664
|
+
*/
|
|
1665
|
+
async executeBatchPayments(payments) {
|
|
1666
|
+
const intents = payments.map((params) => ({
|
|
1667
|
+
to: params.tokenAddress,
|
|
1668
|
+
value: 0n,
|
|
1669
|
+
data: params.authorization ? this.buildAuthorizedTransferCallData(params) : this.buildTransferCallData(params)
|
|
1670
|
+
}));
|
|
1671
|
+
const gasEstimate = await this.estimateBatchGas(intents);
|
|
1672
|
+
const paymasterData = await this.getPaymasterData(gasEstimate);
|
|
1673
|
+
const userOp = await this.builder.buildBatchUserOp(
|
|
1674
|
+
this.signer,
|
|
1675
|
+
intents,
|
|
1676
|
+
this.publicClient,
|
|
1677
|
+
gasEstimate,
|
|
1678
|
+
paymasterData
|
|
1679
|
+
);
|
|
1680
|
+
const signedUserOp = await this.builder.signUserOp(
|
|
1681
|
+
userOp,
|
|
1682
|
+
this.signer,
|
|
1683
|
+
this.publicClient,
|
|
1684
|
+
this.chainId
|
|
1685
|
+
);
|
|
1686
|
+
return this.bundler.sendUserOperation(signedUserOp);
|
|
1687
|
+
}
|
|
1688
|
+
/**
|
|
1689
|
+
* Check if a payment can be sponsored (gasless)
|
|
1690
|
+
*/
|
|
1691
|
+
async canSponsor(params) {
|
|
1692
|
+
if (!this.paymaster) return false;
|
|
1693
|
+
const intent = {
|
|
1694
|
+
to: params.tokenAddress,
|
|
1695
|
+
value: 0n,
|
|
1696
|
+
data: this.buildTransferCallData(params)
|
|
1697
|
+
};
|
|
1698
|
+
const sender = await this.signer.getAddress();
|
|
1699
|
+
return this.paymaster.willSponsor(
|
|
1700
|
+
{ sender, callData: this.signer.encodeExecute(intent.to, 0n, intent.data) },
|
|
1701
|
+
this.chainId,
|
|
1702
|
+
ENTRYPOINT_V07_ADDRESS
|
|
1703
|
+
);
|
|
1704
|
+
}
|
|
1705
|
+
/**
|
|
1706
|
+
* Get the smart account address
|
|
1707
|
+
*/
|
|
1708
|
+
async getAccountAddress() {
|
|
1709
|
+
return this.signer.getAddress();
|
|
1710
|
+
}
|
|
1711
|
+
/**
|
|
1712
|
+
* Check if the smart account is deployed
|
|
1713
|
+
*/
|
|
1714
|
+
async isAccountDeployed() {
|
|
1715
|
+
return this.signer.isDeployed();
|
|
1716
|
+
}
|
|
1717
|
+
/**
|
|
1718
|
+
* Build call data for a simple ERC20 transfer
|
|
1719
|
+
*/
|
|
1720
|
+
buildTransferCallData(params) {
|
|
1721
|
+
return encodeFunctionData2({
|
|
1722
|
+
abi: ERC20_TRANSFER_ABI,
|
|
1723
|
+
functionName: "transfer",
|
|
1724
|
+
args: [params.to, params.amount]
|
|
1725
|
+
});
|
|
1726
|
+
}
|
|
1727
|
+
/**
|
|
1728
|
+
* Build call data for an authorized transfer (EIP-3009)
|
|
1729
|
+
*/
|
|
1730
|
+
buildAuthorizedTransferCallData(params) {
|
|
1731
|
+
if (!params.authorization) {
|
|
1732
|
+
throw new Error("Authorization required for authorized transfer");
|
|
1733
|
+
}
|
|
1734
|
+
const sig = params.authorization.signature;
|
|
1735
|
+
const r = `0x${sig.slice(2, 66)}`;
|
|
1736
|
+
const s = `0x${sig.slice(66, 130)}`;
|
|
1737
|
+
const v = parseInt(sig.slice(130, 132), 16);
|
|
1738
|
+
return encodeFunctionData2({
|
|
1739
|
+
abi: EIP3009_TRANSFER_ABI,
|
|
1740
|
+
functionName: "transferWithAuthorization",
|
|
1741
|
+
args: [
|
|
1742
|
+
params.to,
|
|
1743
|
+
// from (will be overwritten by smart account)
|
|
1744
|
+
params.to,
|
|
1745
|
+
params.amount,
|
|
1746
|
+
params.authorization.validAfter,
|
|
1747
|
+
params.authorization.validBefore,
|
|
1748
|
+
params.authorization.nonce,
|
|
1749
|
+
v,
|
|
1750
|
+
r,
|
|
1751
|
+
s
|
|
1752
|
+
]
|
|
1753
|
+
});
|
|
1754
|
+
}
|
|
1755
|
+
/**
|
|
1756
|
+
* Estimate gas for a single transaction
|
|
1757
|
+
*/
|
|
1758
|
+
async estimateGas(intent) {
|
|
1759
|
+
const sender = await this.signer.getAddress();
|
|
1760
|
+
const callData = this.signer.encodeExecute(
|
|
1761
|
+
intent.to,
|
|
1762
|
+
intent.value ?? 0n,
|
|
1763
|
+
intent.data ?? "0x"
|
|
1764
|
+
);
|
|
1765
|
+
try {
|
|
1766
|
+
return await this.bundler.estimateUserOperationGas({
|
|
1767
|
+
sender,
|
|
1768
|
+
callData
|
|
1769
|
+
});
|
|
1770
|
+
} catch {
|
|
1771
|
+
return {
|
|
1772
|
+
verificationGasLimit: 150000n,
|
|
1773
|
+
callGasLimit: 100000n,
|
|
1774
|
+
preVerificationGas: 50000n
|
|
1775
|
+
};
|
|
1776
|
+
}
|
|
1777
|
+
}
|
|
1778
|
+
/**
|
|
1779
|
+
* Estimate gas for a batch transaction
|
|
1780
|
+
*/
|
|
1781
|
+
async estimateBatchGas(intents) {
|
|
1782
|
+
const sender = await this.signer.getAddress();
|
|
1783
|
+
const callData = this.signer.encodeExecuteBatch(
|
|
1784
|
+
intents.map((i) => i.to),
|
|
1785
|
+
intents.map((i) => i.value ?? 0n),
|
|
1786
|
+
intents.map((i) => i.data ?? "0x")
|
|
1787
|
+
);
|
|
1788
|
+
try {
|
|
1789
|
+
return await this.bundler.estimateUserOperationGas({
|
|
1790
|
+
sender,
|
|
1791
|
+
callData
|
|
1792
|
+
});
|
|
1793
|
+
} catch {
|
|
1794
|
+
return {
|
|
1795
|
+
verificationGasLimit: 150000n,
|
|
1796
|
+
callGasLimit: 100000n * BigInt(intents.length),
|
|
1797
|
+
preVerificationGas: 50000n
|
|
1798
|
+
};
|
|
1799
|
+
}
|
|
1800
|
+
}
|
|
1801
|
+
/**
|
|
1802
|
+
* Get paymaster data if configured
|
|
1803
|
+
*/
|
|
1804
|
+
async getPaymasterData(_gasEstimate) {
|
|
1805
|
+
if (!this.paymaster) return void 0;
|
|
1806
|
+
const sender = await this.signer.getAddress();
|
|
1807
|
+
return this.paymaster.getPaymasterData(
|
|
1808
|
+
{ sender },
|
|
1809
|
+
this.chainId,
|
|
1810
|
+
ENTRYPOINT_V07_ADDRESS
|
|
1811
|
+
);
|
|
1812
|
+
}
|
|
1813
|
+
};
|
|
1814
|
+
function createGaslessT402Client(config) {
|
|
1815
|
+
return new GaslessT402Client(config);
|
|
1816
|
+
}
|
|
1817
|
+
export {
|
|
1818
|
+
ACCOUNT_ABI,
|
|
1819
|
+
BUNDLER_METHODS,
|
|
1820
|
+
BundlerClient,
|
|
1821
|
+
BundlerError,
|
|
1822
|
+
DEFAULT_GAS_LIMITS,
|
|
1823
|
+
ENTRYPOINT_V06_ADDRESS,
|
|
1824
|
+
ENTRYPOINT_V07_ABI,
|
|
1825
|
+
ENTRYPOINT_V07_ADDRESS,
|
|
1826
|
+
ExactEvmScheme,
|
|
1827
|
+
ExactLegacyEvmScheme as ExactLegacyEvmClientScheme,
|
|
1828
|
+
ExactLegacyEvmScheme3 as ExactLegacyEvmFacilitatorScheme,
|
|
1829
|
+
ExactLegacyEvmScheme2 as ExactLegacyEvmServerScheme,
|
|
1830
|
+
GaslessT402Client,
|
|
1831
|
+
LAYERZERO_ENDPOINT_IDS,
|
|
1832
|
+
LAYERZERO_ENDPOINT_V2,
|
|
1833
|
+
PaymasterClient,
|
|
1834
|
+
PaymasterType,
|
|
1835
|
+
TOKEN_PRIORITY,
|
|
1836
|
+
TOKEN_REGISTRY,
|
|
1837
|
+
USDC_ADDRESSES,
|
|
1838
|
+
USDT0_ADDRESSES,
|
|
1839
|
+
USDT0_OFT_ADDRESSES,
|
|
1840
|
+
USDT_LEGACY_ADDRESSES,
|
|
1841
|
+
Usdt0Bridge,
|
|
1842
|
+
UserOpBuilder,
|
|
1843
|
+
addressToBytes32,
|
|
1844
|
+
authorizationTypes,
|
|
1845
|
+
bytes32ToAddress,
|
|
1846
|
+
createBundlerClient,
|
|
1847
|
+
createGaslessT402Client,
|
|
1848
|
+
createPaymasterClient,
|
|
1849
|
+
createUsdt0Bridge,
|
|
1850
|
+
createUserOpBuilder,
|
|
1851
|
+
decodePaymasterAndData,
|
|
1852
|
+
eip3009ABI,
|
|
1853
|
+
encodePaymasterAndData,
|
|
1854
|
+
erc20LegacyABI,
|
|
1855
|
+
getBridgeableChains,
|
|
1856
|
+
getDefaultToken,
|
|
1857
|
+
getEIP712Domain,
|
|
1858
|
+
getEndpointId,
|
|
1859
|
+
getNetworkTokens,
|
|
1860
|
+
getNetworksForToken,
|
|
1861
|
+
getTokenByAddress,
|
|
1862
|
+
getTokenConfig,
|
|
1863
|
+
getUsdt0Networks,
|
|
1864
|
+
getUsdt0OftAddress,
|
|
1865
|
+
legacyAuthorizationTypes,
|
|
1866
|
+
packAccountGasLimits,
|
|
1867
|
+
packGasFees,
|
|
1868
|
+
supportsBridging,
|
|
1869
|
+
supportsEIP3009,
|
|
1870
|
+
toClientEvmSigner,
|
|
1871
|
+
toFacilitatorEvmSigner,
|
|
1872
|
+
unpackAccountGasLimits,
|
|
1873
|
+
unpackGasFees
|
|
1874
|
+
};
|
|
1875
|
+
//# sourceMappingURL=index.mjs.map
|