@t402/extensions 2.4.0 → 2.6.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.
Files changed (42) hide show
  1. package/dist/cjs/bazaar/index.d.ts +1 -1
  2. package/dist/cjs/eip2612-gas-sponsoring/index.d.ts +337 -0
  3. package/dist/cjs/eip2612-gas-sponsoring/index.js +314 -0
  4. package/dist/cjs/eip2612-gas-sponsoring/index.js.map +1 -0
  5. package/dist/cjs/erc20-approval-gas-sponsoring/index.d.ts +316 -0
  6. package/dist/cjs/erc20-approval-gas-sponsoring/index.js +264 -0
  7. package/dist/cjs/erc20-approval-gas-sponsoring/index.js.map +1 -0
  8. package/dist/cjs/{index-DYNleT-u.d.ts → index-Mk5Ypp8M.d.ts} +2 -2
  9. package/dist/cjs/index.d.ts +4 -1
  10. package/dist/cjs/index.js +650 -0
  11. package/dist/cjs/index.js.map +1 -1
  12. package/dist/cjs/payment-id/index.d.ts +142 -0
  13. package/dist/cjs/payment-id/index.js +101 -0
  14. package/dist/cjs/payment-id/index.js.map +1 -0
  15. package/dist/cjs/sign-in-with-x/index.d.ts +2 -1
  16. package/dist/cjs/sign-in-with-x/index.js +55 -0
  17. package/dist/cjs/sign-in-with-x/index.js.map +1 -1
  18. package/dist/esm/bazaar/index.d.mts +1 -1
  19. package/dist/esm/chunk-OAWKCEAR.mjs +226 -0
  20. package/dist/esm/chunk-OAWKCEAR.mjs.map +1 -0
  21. package/dist/esm/chunk-S36A7YLQ.mjs +70 -0
  22. package/dist/esm/chunk-S36A7YLQ.mjs.map +1 -0
  23. package/dist/esm/chunk-VINC22RD.mjs +278 -0
  24. package/dist/esm/chunk-VINC22RD.mjs.map +1 -0
  25. package/dist/esm/{chunk-J3ZMNCIA.mjs → chunk-YKZ5P2JW.mjs} +56 -1
  26. package/dist/esm/chunk-YKZ5P2JW.mjs.map +1 -0
  27. package/dist/esm/eip2612-gas-sponsoring/index.d.mts +337 -0
  28. package/dist/esm/eip2612-gas-sponsoring/index.mjs +27 -0
  29. package/dist/esm/eip2612-gas-sponsoring/index.mjs.map +1 -0
  30. package/dist/esm/erc20-approval-gas-sponsoring/index.d.mts +316 -0
  31. package/dist/esm/erc20-approval-gas-sponsoring/index.mjs +31 -0
  32. package/dist/esm/erc20-approval-gas-sponsoring/index.mjs.map +1 -0
  33. package/dist/esm/{index-DYNleT-u.d.mts → index-Mk5Ypp8M.d.mts} +2 -2
  34. package/dist/esm/index.d.mts +4 -1
  35. package/dist/esm/index.mjs +67 -1
  36. package/dist/esm/payment-id/index.d.mts +142 -0
  37. package/dist/esm/payment-id/index.mjs +17 -0
  38. package/dist/esm/payment-id/index.mjs.map +1 -0
  39. package/dist/esm/sign-in-with-x/index.d.mts +2 -1
  40. package/dist/esm/sign-in-with-x/index.mjs +1 -1
  41. package/package.json +53 -16
  42. package/dist/esm/chunk-J3ZMNCIA.mjs.map +0 -1
@@ -0,0 +1,226 @@
1
+ // src/erc20-approval-gas-sponsoring/server.ts
2
+ var ERC20_APPROVAL_GAS_SPONSOR_SCHEMA = {
3
+ type: "object",
4
+ required: ["network", "from", "asset", "amount", "signedApprovalTx", "chainId"],
5
+ properties: {
6
+ network: { type: "string" },
7
+ from: { type: "string" },
8
+ asset: { type: "string" },
9
+ amount: { type: "string" },
10
+ signedApprovalTx: { type: "string" },
11
+ chainId: { type: "number" },
12
+ nonce: { type: "number" }
13
+ }
14
+ };
15
+ function declareERC20ApprovalGasSponsorExtension(options) {
16
+ const info = {
17
+ sponsoredNetworks: options.sponsoredNetworks,
18
+ maxAmount: options.maxAmount,
19
+ sponsorAddress: options.sponsorAddress,
20
+ requiresAtomicBatch: options.requiresAtomicBatch ?? false
21
+ };
22
+ if (options.permit2Address) {
23
+ info.permit2Address = options.permit2Address;
24
+ }
25
+ return {
26
+ info,
27
+ schema: ERC20_APPROVAL_GAS_SPONSOR_SCHEMA
28
+ };
29
+ }
30
+ function parseERC20ApprovalGasSponsorHeader(header) {
31
+ if (!header) {
32
+ throw new Error("Missing ERC-20 approval gas sponsor header");
33
+ }
34
+ try {
35
+ const decoded = Buffer.from(header, "base64").toString("utf-8");
36
+ const payload = JSON.parse(decoded);
37
+ const required = ["network", "from", "asset", "amount", "signedApprovalTx", "chainId"];
38
+ for (const field of required) {
39
+ if (!(field in payload)) {
40
+ throw new Error(`Missing required field: ${field}`);
41
+ }
42
+ }
43
+ return payload;
44
+ } catch (error) {
45
+ if (error instanceof SyntaxError) {
46
+ throw new Error("Invalid ERC-20 approval gas sponsor header: malformed JSON");
47
+ }
48
+ throw error;
49
+ }
50
+ }
51
+ function validateERC20ApprovalGasSponsorPayload(payload, extensionInfo, options = {}) {
52
+ if (!extensionInfo.sponsoredNetworks.includes(payload.network)) {
53
+ return {
54
+ valid: false,
55
+ error: `Network ${payload.network} is not in sponsored networks: ${extensionInfo.sponsoredNetworks.join(", ")}`
56
+ };
57
+ }
58
+ const payloadAmount = BigInt(payload.amount);
59
+ const maxAmount = BigInt(extensionInfo.maxAmount);
60
+ if (payloadAmount > maxAmount) {
61
+ return {
62
+ valid: false,
63
+ error: `Amount ${payload.amount} exceeds maximum amount ${extensionInfo.maxAmount}`
64
+ };
65
+ }
66
+ if (options.expectedChainIds) {
67
+ const expectedChainId = options.expectedChainIds[payload.network];
68
+ if (expectedChainId !== void 0 && payload.chainId !== expectedChainId) {
69
+ return {
70
+ valid: false,
71
+ error: `Chain ID ${payload.chainId} does not match expected chain ID ${expectedChainId} for network ${payload.network}`
72
+ };
73
+ }
74
+ }
75
+ const txHex = payload.signedApprovalTx.startsWith("0x") ? payload.signedApprovalTx.slice(2) : payload.signedApprovalTx;
76
+ if (txHex.length === 0) {
77
+ return {
78
+ valid: false,
79
+ error: "Signed approval transaction is empty"
80
+ };
81
+ }
82
+ if (!/^[0-9a-fA-F]+$/.test(txHex)) {
83
+ return {
84
+ valid: false,
85
+ error: "Signed approval transaction is not valid hex"
86
+ };
87
+ }
88
+ const fromHex = payload.from.startsWith("0x") ? payload.from.slice(2) : payload.from;
89
+ if (fromHex.length !== 40 || !/^[0-9a-fA-F]+$/.test(fromHex)) {
90
+ return {
91
+ valid: false,
92
+ error: `Invalid from address: ${payload.from}`
93
+ };
94
+ }
95
+ const assetHex = payload.asset.startsWith("0x") ? payload.asset.slice(2) : payload.asset;
96
+ if (assetHex.length !== 40 || !/^[0-9a-fA-F]+$/.test(assetHex)) {
97
+ return {
98
+ valid: false,
99
+ error: `Invalid asset address: ${payload.asset}`
100
+ };
101
+ }
102
+ return { valid: true };
103
+ }
104
+
105
+ // src/erc20-approval-gas-sponsoring/client.ts
106
+ var ERC20_APPROVAL_GAS_SPONSOR_EXTENSION_KEY = "erc20ApprovalGasSponsoring";
107
+ var ERC20_APPROVAL_GAS_SPONSOR_HEADER_NAME = "X-T402-ERC20-Approval-Gas-Sponsoring";
108
+ var APPROVE_FUNCTION_SELECTOR = "0x095ea7b3";
109
+ function encodeApproveCalldata(spender, amount) {
110
+ const spenderHex = spender.startsWith("0x") ? spender.slice(2) : spender;
111
+ const paddedSpender = spenderHex.toLowerCase().padStart(64, "0");
112
+ const amountBigInt = BigInt(amount);
113
+ const amountHex = amountBigInt.toString(16).padStart(64, "0");
114
+ return APPROVE_FUNCTION_SELECTOR + paddedSpender + amountHex;
115
+ }
116
+ function createERC20ApprovalGasSponsorPayload(_info, params) {
117
+ const payload = {
118
+ network: params.network,
119
+ from: params.from,
120
+ asset: params.asset,
121
+ amount: params.amount,
122
+ signedApprovalTx: params.signedApprovalTx.startsWith("0x") ? params.signedApprovalTx : "0x" + params.signedApprovalTx,
123
+ chainId: params.chainId
124
+ };
125
+ if (params.nonce !== void 0) {
126
+ payload.nonce = params.nonce;
127
+ }
128
+ return payload;
129
+ }
130
+ function encodeERC20ApprovalGasSponsorHeader(payload) {
131
+ const json = JSON.stringify(payload);
132
+ if (typeof Buffer !== "undefined") {
133
+ return Buffer.from(json, "utf-8").toString("base64");
134
+ }
135
+ return btoa(json);
136
+ }
137
+
138
+ // src/erc20-approval-gas-sponsoring/facilitator.ts
139
+ function extractERC20ApprovalGasSponsorPayload(extensions) {
140
+ if (!extensions) {
141
+ return null;
142
+ }
143
+ const raw = extensions[ERC20_APPROVAL_GAS_SPONSOR_EXTENSION_KEY];
144
+ if (!raw || typeof raw !== "object") {
145
+ return null;
146
+ }
147
+ const payload = raw;
148
+ const required = ["network", "from", "asset", "amount", "signedApprovalTx", "chainId"];
149
+ for (const field of required) {
150
+ if (!(field in payload)) {
151
+ return null;
152
+ }
153
+ }
154
+ const result = {
155
+ network: payload.network,
156
+ from: payload.from,
157
+ asset: payload.asset,
158
+ amount: payload.amount,
159
+ signedApprovalTx: payload.signedApprovalTx,
160
+ chainId: payload.chainId
161
+ };
162
+ if ("nonce" in payload && payload.nonce !== void 0) {
163
+ result.nonce = payload.nonce;
164
+ }
165
+ return result;
166
+ }
167
+ function processERC20ApprovalPayload(payload, extensionInfo) {
168
+ const validationResult = validateERC20ApprovalGasSponsorPayload(payload, extensionInfo);
169
+ if (!validationResult.valid) {
170
+ return validationResult;
171
+ }
172
+ const txHex = payload.signedApprovalTx.startsWith("0x") ? payload.signedApprovalTx.slice(2) : payload.signedApprovalTx;
173
+ if (txHex.length < 8) {
174
+ return {
175
+ valid: false,
176
+ error: "Signed approval transaction is too short to contain function selector"
177
+ };
178
+ }
179
+ return { valid: true };
180
+ }
181
+ function validateAndExtractApproval(extensions, extensionInfo) {
182
+ const payload = extractERC20ApprovalGasSponsorPayload(extensions);
183
+ if (!payload) {
184
+ return {
185
+ valid: false,
186
+ error: `Missing or invalid ${ERC20_APPROVAL_GAS_SPONSOR_EXTENSION_KEY} extension in payment`
187
+ };
188
+ }
189
+ const result = processERC20ApprovalPayload(payload, extensionInfo);
190
+ if (!result.valid) {
191
+ return result;
192
+ }
193
+ return { valid: true, payload };
194
+ }
195
+ function decodeApproveCalldata(calldata) {
196
+ const hex = calldata.startsWith("0x") ? calldata.slice(2) : calldata;
197
+ if (hex.length < 136) {
198
+ return null;
199
+ }
200
+ const selector = "0x" + hex.slice(0, 8);
201
+ if (selector !== APPROVE_FUNCTION_SELECTOR) {
202
+ return null;
203
+ }
204
+ const spenderWord = hex.slice(8, 72);
205
+ const spender = "0x" + spenderWord.slice(24);
206
+ const amountHex = hex.slice(72, 136);
207
+ const amount = BigInt("0x" + amountHex).toString(10);
208
+ return { spender, amount };
209
+ }
210
+
211
+ export {
212
+ declareERC20ApprovalGasSponsorExtension,
213
+ parseERC20ApprovalGasSponsorHeader,
214
+ validateERC20ApprovalGasSponsorPayload,
215
+ ERC20_APPROVAL_GAS_SPONSOR_EXTENSION_KEY,
216
+ ERC20_APPROVAL_GAS_SPONSOR_HEADER_NAME,
217
+ APPROVE_FUNCTION_SELECTOR,
218
+ encodeApproveCalldata,
219
+ createERC20ApprovalGasSponsorPayload,
220
+ encodeERC20ApprovalGasSponsorHeader,
221
+ extractERC20ApprovalGasSponsorPayload,
222
+ processERC20ApprovalPayload,
223
+ validateAndExtractApproval,
224
+ decodeApproveCalldata
225
+ };
226
+ //# sourceMappingURL=chunk-OAWKCEAR.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/erc20-approval-gas-sponsoring/server.ts","../../src/erc20-approval-gas-sponsoring/client.ts","../../src/erc20-approval-gas-sponsoring/facilitator.ts"],"sourcesContent":["/**\n * ERC-20 Approval Gas Sponsoring Extension Server-Side Implementation\n *\n * Provides functions for servers to declare gas sponsoring requirements,\n * parse client headers, and validate approval payloads.\n */\n\nimport type {\n ERC20ApprovalGasSponsorExtension,\n ERC20ApprovalGasSponsorExtensionInfo,\n ERC20ApprovalGasSponsorPayload,\n DeclareERC20ApprovalGasSponsorOptions,\n ValidateERC20ApprovalGasSponsorOptions,\n ERC20ApprovalGasSponsorValidationResult,\n} from \"./types.js\";\n\n/**\n * JSON Schema for ERC-20 approval gas sponsor payload validation.\n */\nconst ERC20_APPROVAL_GAS_SPONSOR_SCHEMA = {\n type: \"object\",\n required: [\"network\", \"from\", \"asset\", \"amount\", \"signedApprovalTx\", \"chainId\"],\n properties: {\n network: { type: \"string\" },\n from: { type: \"string\" },\n asset: { type: \"string\" },\n amount: { type: \"string\" },\n signedApprovalTx: { type: \"string\" },\n chainId: { type: \"number\" },\n nonce: { type: \"number\" },\n },\n};\n\n/**\n * Declares an ERC-20 approval gas sponsor extension for server responses.\n *\n * @param options - Extension declaration options\n * @returns Gas sponsor extension object ready for response\n *\n * @example\n * ```typescript\n * const extension = declareERC20ApprovalGasSponsorExtension({\n * sponsoredNetworks: [\"eip155:8453\", \"eip155:42161\"],\n * maxAmount: \"1000000000\",\n * sponsorAddress: \"0xFacilitator...\",\n * requiresAtomicBatch: true,\n * });\n * ```\n */\nexport function declareERC20ApprovalGasSponsorExtension(\n options: DeclareERC20ApprovalGasSponsorOptions,\n): ERC20ApprovalGasSponsorExtension {\n const info: ERC20ApprovalGasSponsorExtensionInfo = {\n sponsoredNetworks: options.sponsoredNetworks,\n maxAmount: options.maxAmount,\n sponsorAddress: options.sponsorAddress,\n requiresAtomicBatch: options.requiresAtomicBatch ?? false,\n };\n\n if (options.permit2Address) {\n info.permit2Address = options.permit2Address;\n }\n\n return {\n info,\n schema: ERC20_APPROVAL_GAS_SPONSOR_SCHEMA,\n };\n}\n\n/**\n * Parses an ERC-20 approval gas sponsor header from client request.\n *\n * The header format is base64-encoded JSON.\n *\n * @param header - Base64-encoded gas sponsor header value\n * @returns Parsed gas sponsor payload\n * @throws Error if header is invalid\n *\n * @example\n * ```typescript\n * const payload = parseERC20ApprovalGasSponsorHeader(\n * request.headers['x-t402-erc20-approval-gas-sponsoring']\n * );\n * ```\n */\nexport function parseERC20ApprovalGasSponsorHeader(header: string): ERC20ApprovalGasSponsorPayload {\n if (!header) {\n throw new Error(\"Missing ERC-20 approval gas sponsor header\");\n }\n\n try {\n const decoded = Buffer.from(header, \"base64\").toString(\"utf-8\");\n const payload = JSON.parse(decoded) as ERC20ApprovalGasSponsorPayload;\n\n const required = [\"network\", \"from\", \"asset\", \"amount\", \"signedApprovalTx\", \"chainId\"];\n for (const field of required) {\n if (!(field in payload)) {\n throw new Error(`Missing required field: ${field}`);\n }\n }\n\n return payload;\n } catch (error) {\n if (error instanceof SyntaxError) {\n throw new Error(\"Invalid ERC-20 approval gas sponsor header: malformed JSON\");\n }\n throw error;\n }\n}\n\n/**\n * Validates an ERC-20 approval gas sponsor payload against server extension info.\n *\n * @param payload - The gas sponsor payload from the client\n * @param extensionInfo - The server's gas sponsor extension info\n * @param options - Validation options\n * @returns Validation result\n *\n * @example\n * ```typescript\n * const result = validateERC20ApprovalGasSponsorPayload(payload, extension.info);\n * if (!result.valid) {\n * throw new Error(result.error);\n * }\n * ```\n */\nexport function validateERC20ApprovalGasSponsorPayload(\n payload: ERC20ApprovalGasSponsorPayload,\n extensionInfo: ERC20ApprovalGasSponsorExtensionInfo,\n options: ValidateERC20ApprovalGasSponsorOptions = {},\n): ERC20ApprovalGasSponsorValidationResult {\n // Validate network is in sponsoredNetworks\n if (!extensionInfo.sponsoredNetworks.includes(payload.network)) {\n return {\n valid: false,\n error: `Network ${payload.network} is not in sponsored networks: ${extensionInfo.sponsoredNetworks.join(\", \")}`,\n };\n }\n\n // Validate amount does not exceed maxAmount\n const payloadAmount = BigInt(payload.amount);\n const maxAmount = BigInt(extensionInfo.maxAmount);\n if (payloadAmount > maxAmount) {\n return {\n valid: false,\n error: `Amount ${payload.amount} exceeds maximum amount ${extensionInfo.maxAmount}`,\n };\n }\n\n // Validate chainId matches expected value for network (if provided)\n if (options.expectedChainIds) {\n const expectedChainId = options.expectedChainIds[payload.network];\n if (expectedChainId !== undefined && payload.chainId !== expectedChainId) {\n return {\n valid: false,\n error: `Chain ID ${payload.chainId} does not match expected chain ID ${expectedChainId} for network ${payload.network}`,\n };\n }\n }\n\n // Validate signedApprovalTx is hex-encoded\n const txHex = payload.signedApprovalTx.startsWith(\"0x\")\n ? payload.signedApprovalTx.slice(2)\n : payload.signedApprovalTx;\n if (txHex.length === 0) {\n return {\n valid: false,\n error: \"Signed approval transaction is empty\",\n };\n }\n if (!/^[0-9a-fA-F]+$/.test(txHex)) {\n return {\n valid: false,\n error: \"Signed approval transaction is not valid hex\",\n };\n }\n\n // Validate from address format\n const fromHex = payload.from.startsWith(\"0x\") ? payload.from.slice(2) : payload.from;\n if (fromHex.length !== 40 || !/^[0-9a-fA-F]+$/.test(fromHex)) {\n return {\n valid: false,\n error: `Invalid from address: ${payload.from}`,\n };\n }\n\n // Validate asset address format\n const assetHex = payload.asset.startsWith(\"0x\") ? payload.asset.slice(2) : payload.asset;\n if (assetHex.length !== 40 || !/^[0-9a-fA-F]+$/.test(assetHex)) {\n return {\n valid: false,\n error: `Invalid asset address: ${payload.asset}`,\n };\n }\n\n return { valid: true };\n}\n","/**\n * ERC-20 Approval Gas Sponsoring Extension Client-Side Implementation\n *\n * Provides functions for clients to construct ERC-20 approve() calldata\n * and encode gas sponsor payloads for transmission.\n */\n\nimport type {\n ERC20ApprovalGasSponsorPayload,\n ERC20ApprovalGasSponsorExtensionInfo,\n CreateERC20ApprovalParams,\n} from \"./types.js\";\n\n/**\n * Extension key for ERC-20 approval gas sponsoring in payment requirements.\n */\nexport const ERC20_APPROVAL_GAS_SPONSOR_EXTENSION_KEY = \"erc20ApprovalGasSponsoring\";\n\n/**\n * HTTP header name for ERC-20 approval gas sponsor payload.\n */\nexport const ERC20_APPROVAL_GAS_SPONSOR_HEADER_NAME = \"X-T402-ERC20-Approval-Gas-Sponsoring\";\n\n/**\n * ERC-20 approve(address,uint256) function selector.\n */\nexport const APPROVE_FUNCTION_SELECTOR = \"0x095ea7b3\";\n\n/**\n * Encodes ERC-20 approve(address spender, uint256 amount) calldata.\n *\n * @param spender - The spender address to approve\n * @param amount - The approval amount in base units\n * @returns Hex-encoded calldata with 0x prefix\n *\n * @example\n * ```typescript\n * const calldata = encodeApproveCalldata(\"0xFacilitator...\", \"1000000\");\n * // Returns \"0x095ea7b3\" + abi-encoded args\n * ```\n */\nexport function encodeApproveCalldata(spender: string, amount: string): string {\n // Remove 0x prefix from spender address\n const spenderHex = spender.startsWith(\"0x\") ? spender.slice(2) : spender;\n\n // Pad spender address to 32 bytes (left-padded with zeros)\n const paddedSpender = spenderHex.toLowerCase().padStart(64, \"0\");\n\n // Convert amount to hex and pad to 32 bytes (left-padded with zeros)\n const amountBigInt = BigInt(amount);\n const amountHex = amountBigInt.toString(16).padStart(64, \"0\");\n\n return APPROVE_FUNCTION_SELECTOR + paddedSpender + amountHex;\n}\n\n/**\n * Creates an ERC-20 approval gas sponsor payload from params and extension info.\n *\n * @param info - The server's extension info\n * @param params - The approval parameters\n * @returns Gas sponsor payload ready for header encoding\n *\n * @example\n * ```typescript\n * const payload = createERC20ApprovalGasSponsorPayload(extensionInfo, {\n * network: \"eip155:8453\",\n * from: wallet.address,\n * asset: \"0xUSDT...\",\n * amount: \"1000000\",\n * signedApprovalTx: signedTx,\n * chainId: 8453,\n * });\n * ```\n */\nexport function createERC20ApprovalGasSponsorPayload(\n _info: ERC20ApprovalGasSponsorExtensionInfo,\n params: CreateERC20ApprovalParams,\n): ERC20ApprovalGasSponsorPayload {\n const payload: ERC20ApprovalGasSponsorPayload = {\n network: params.network,\n from: params.from,\n asset: params.asset,\n amount: params.amount,\n signedApprovalTx: params.signedApprovalTx.startsWith(\"0x\")\n ? params.signedApprovalTx\n : \"0x\" + params.signedApprovalTx,\n chainId: params.chainId,\n };\n\n if (params.nonce !== undefined) {\n payload.nonce = params.nonce;\n }\n\n return payload;\n}\n\n/**\n * Encodes an ERC-20 approval gas sponsor payload for transmission in HTTP header.\n *\n * @param payload - The gas sponsor payload to encode\n * @returns Base64-encoded JSON string\n *\n * @example\n * ```typescript\n * const header = encodeERC20ApprovalGasSponsorHeader(payload);\n * fetch(url, {\n * headers: { [ERC20_APPROVAL_GAS_SPONSOR_HEADER_NAME]: header }\n * });\n * ```\n */\nexport function encodeERC20ApprovalGasSponsorHeader(\n payload: ERC20ApprovalGasSponsorPayload,\n): string {\n const json = JSON.stringify(payload);\n if (typeof Buffer !== \"undefined\") {\n return Buffer.from(json, \"utf-8\").toString(\"base64\");\n }\n return btoa(json);\n}\n","/**\n * ERC-20 Approval Gas Sponsoring Extension Facilitator-Side Implementation\n *\n * Provides functions for facilitators to extract approval data from payment\n * extensions, validate the signed approve() transaction, and prepare for\n * on-chain submission.\n */\n\nimport type {\n ERC20ApprovalGasSponsorPayload,\n ERC20ApprovalGasSponsorExtensionInfo,\n ERC20ApprovalGasSponsorValidationResult,\n} from \"./types.js\";\nimport { ERC20_APPROVAL_GAS_SPONSOR_EXTENSION_KEY, APPROVE_FUNCTION_SELECTOR } from \"./client.js\";\nimport { validateERC20ApprovalGasSponsorPayload } from \"./server.js\";\n\n/**\n * Extracts the ERC-20 approval gas sponsor payload from payment extensions.\n *\n * @param extensions - The extensions map from a PaymentPayload\n * @returns The gas sponsor payload if present, or null\n *\n * @example\n * ```typescript\n * const approval = extractERC20ApprovalGasSponsorPayload(paymentPayload.extensions);\n * if (approval) {\n * // Validate and broadcast the approval tx, then settle\n * }\n * ```\n */\nexport function extractERC20ApprovalGasSponsorPayload(\n extensions: Record<string, unknown> | undefined,\n): ERC20ApprovalGasSponsorPayload | null {\n if (!extensions) {\n return null;\n }\n\n const raw = extensions[ERC20_APPROVAL_GAS_SPONSOR_EXTENSION_KEY];\n if (!raw || typeof raw !== \"object\") {\n return null;\n }\n\n const payload = raw as Record<string, unknown>;\n\n // Validate required fields are present\n const required = [\"network\", \"from\", \"asset\", \"amount\", \"signedApprovalTx\", \"chainId\"];\n for (const field of required) {\n if (!(field in payload)) {\n return null;\n }\n }\n\n const result: ERC20ApprovalGasSponsorPayload = {\n network: payload.network as string,\n from: payload.from as string,\n asset: payload.asset as string,\n amount: payload.amount as string,\n signedApprovalTx: payload.signedApprovalTx as string,\n chainId: payload.chainId as number,\n };\n\n if (\"nonce\" in payload && payload.nonce !== undefined) {\n result.nonce = payload.nonce as number;\n }\n\n return result;\n}\n\n/**\n * Processes and validates an ERC-20 approval payload for the facilitator.\n *\n * Combines extraction validation with approve() function selector verification.\n * Checks that the signed transaction data contains the correct approve() selector\n * and that the approval amount matches the declared amount.\n *\n * @param payload - The ERC-20 approval gas sponsor payload\n * @param extensionInfo - The server's gas sponsor extension info\n * @returns Validation result\n *\n * @example\n * ```typescript\n * const result = processERC20ApprovalPayload(payload, extensionInfo);\n * if (result.valid) {\n * // Safe to broadcast the approval tx and settle\n * }\n * ```\n */\nexport function processERC20ApprovalPayload(\n payload: ERC20ApprovalGasSponsorPayload,\n extensionInfo: ERC20ApprovalGasSponsorExtensionInfo,\n): ERC20ApprovalGasSponsorValidationResult {\n // Run standard validation\n const validationResult = validateERC20ApprovalGasSponsorPayload(payload, extensionInfo);\n if (!validationResult.valid) {\n return validationResult;\n }\n\n // Verify the signed tx is not empty\n const txHex = payload.signedApprovalTx.startsWith(\"0x\")\n ? payload.signedApprovalTx.slice(2)\n : payload.signedApprovalTx;\n\n if (txHex.length < 8) {\n return {\n valid: false,\n error: \"Signed approval transaction is too short to contain function selector\",\n };\n }\n\n return { valid: true };\n}\n\n/**\n * Validates and extracts the ERC-20 approval gas sponsor payload in one step.\n *\n * This is a convenience function for facilitators that combines extraction\n * and validation against the server's extension info.\n *\n * @param extensions - The extensions map from a PaymentPayload\n * @param extensionInfo - The server's gas sponsor extension info\n * @returns Validation result with the extracted payload if valid\n *\n * @example\n * ```typescript\n * const result = validateAndExtractApproval(\n * paymentPayload.extensions,\n * extensionInfo\n * );\n * if (result.valid && result.payload) {\n * // Broadcast approval tx, then settle\n * }\n * ```\n */\nexport function validateAndExtractApproval(\n extensions: Record<string, unknown> | undefined,\n extensionInfo: ERC20ApprovalGasSponsorExtensionInfo,\n): ERC20ApprovalGasSponsorValidationResult & { payload?: ERC20ApprovalGasSponsorPayload } {\n const payload = extractERC20ApprovalGasSponsorPayload(extensions);\n\n if (!payload) {\n return {\n valid: false,\n error: `Missing or invalid ${ERC20_APPROVAL_GAS_SPONSOR_EXTENSION_KEY} extension in payment`,\n };\n }\n\n const result = processERC20ApprovalPayload(payload, extensionInfo);\n\n if (!result.valid) {\n return result;\n }\n\n return { valid: true, payload };\n}\n\n/**\n * Decodes the approve() calldata from a hex string to extract spender and amount.\n *\n * @param calldata - Hex-encoded approve() calldata (with or without 0x prefix)\n * @returns Decoded spender and amount, or null if not valid approve() calldata\n *\n * @example\n * ```typescript\n * const decoded = decodeApproveCalldata(\"0x095ea7b3...\");\n * if (decoded) {\n * console.log(decoded.spender, decoded.amount);\n * }\n * ```\n */\nexport function decodeApproveCalldata(\n calldata: string,\n): { spender: string; amount: string } | null {\n const hex = calldata.startsWith(\"0x\") ? calldata.slice(2) : calldata;\n\n // approve(address,uint256) = 4 byte selector + 32 byte address + 32 byte amount = 136 hex chars\n if (hex.length < 136) {\n return null;\n }\n\n const selector = \"0x\" + hex.slice(0, 8);\n if (selector !== APPROVE_FUNCTION_SELECTOR) {\n return null;\n }\n\n // Extract spender address (bytes 4-36, last 20 bytes of the 32-byte word)\n const spenderWord = hex.slice(8, 72);\n const spender = \"0x\" + spenderWord.slice(24);\n\n // Extract amount (bytes 36-68)\n const amountHex = hex.slice(72, 136);\n const amount = BigInt(\"0x\" + amountHex).toString(10);\n\n return { spender, amount };\n}\n"],"mappings":"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","names":[]}
@@ -0,0 +1,70 @@
1
+ // src/payment-id/types.ts
2
+ var PAYMENT_ID_EXTENSION_KEY = "paymentId";
3
+
4
+ // src/payment-id/server.ts
5
+ import { randomUUID } from "crypto";
6
+ var PAYMENT_ID_SCHEMA = {
7
+ type: "object",
8
+ required: ["id"],
9
+ properties: {
10
+ id: { type: "string", format: "uuid" },
11
+ clientId: { type: "string" }
12
+ }
13
+ };
14
+ function declarePaymentIdExtension(options = {}) {
15
+ const info = {
16
+ id: options.id || randomUUID(),
17
+ idempotencyKey: options.idempotencyKey,
18
+ groupId: options.groupId,
19
+ metadata: options.metadata
20
+ };
21
+ return {
22
+ info,
23
+ schema: PAYMENT_ID_SCHEMA
24
+ };
25
+ }
26
+ function parsePaymentIdPayload(extensions) {
27
+ if (!extensions || !(PAYMENT_ID_EXTENSION_KEY in extensions)) {
28
+ return null;
29
+ }
30
+ const raw = extensions[PAYMENT_ID_EXTENSION_KEY];
31
+ if (typeof raw !== "object" || raw === null) {
32
+ throw new Error("Invalid paymentId extension: expected object");
33
+ }
34
+ const obj = raw;
35
+ if (typeof obj.id !== "string" || obj.id.length === 0) {
36
+ throw new Error("Invalid paymentId extension: missing or empty id");
37
+ }
38
+ return {
39
+ id: obj.id,
40
+ clientId: typeof obj.clientId === "string" ? obj.clientId : void 0
41
+ };
42
+ }
43
+ function validatePaymentId(payload, expected) {
44
+ return payload.id === expected.id;
45
+ }
46
+
47
+ // src/payment-id/client.ts
48
+ function createPaymentIdPayload(extension, clientId) {
49
+ return {
50
+ id: extension.info.id,
51
+ clientId
52
+ };
53
+ }
54
+ function encodePaymentIdHeader(payload) {
55
+ const json = JSON.stringify(payload);
56
+ if (typeof Buffer !== "undefined") {
57
+ return Buffer.from(json, "utf-8").toString("base64");
58
+ }
59
+ return btoa(json);
60
+ }
61
+
62
+ export {
63
+ PAYMENT_ID_EXTENSION_KEY,
64
+ declarePaymentIdExtension,
65
+ parsePaymentIdPayload,
66
+ validatePaymentId,
67
+ createPaymentIdPayload,
68
+ encodePaymentIdHeader
69
+ };
70
+ //# sourceMappingURL=chunk-S36A7YLQ.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/payment-id/types.ts","../../src/payment-id/server.ts","../../src/payment-id/client.ts"],"sourcesContent":["/**\n * Payment ID Extension Type Definitions\n *\n * Allows servers to attach unique identifiers to payments for\n * correlation, idempotency, and audit trails.\n */\n\n/**\n * Extension key for payment ID in requirements/payload extensions.\n */\nexport const PAYMENT_ID_EXTENSION_KEY = \"paymentId\";\n\n/**\n * Information provided by server about the payment identifier.\n */\nexport interface PaymentIdExtensionInfo {\n /** Unique payment identifier (UUID v4) */\n id: string;\n\n /** Optional idempotency key for replay protection */\n idempotencyKey?: string;\n\n /** Optional payment group for batching */\n groupId?: string;\n\n /** Optional metadata */\n metadata?: Record<string, string>;\n}\n\n/**\n * Payment ID extension declaration for server responses.\n */\nexport interface PaymentIdExtension {\n /** Extension information */\n info: PaymentIdExtensionInfo;\n\n /** JSON Schema for validation */\n schema: object;\n}\n\n/**\n * Payment ID payload echoed back by the client.\n */\nexport interface PaymentIdPayload {\n /** Payment ID echoed back from requirements */\n id: string;\n\n /** Optional client-generated correlation ID */\n clientId?: string;\n}\n\n/**\n * Options for declaring a payment ID extension.\n */\nexport interface DeclarePaymentIdOptions {\n /** Custom payment ID (defaults to crypto.randomUUID()) */\n id?: string;\n\n /** Optional idempotency key for replay protection */\n idempotencyKey?: string;\n\n /** Optional payment group for batching */\n groupId?: string;\n\n /** Optional metadata */\n metadata?: Record<string, string>;\n}\n","/**\n * Payment ID Extension Server-Side Implementation\n *\n * Provides functions for servers to declare payment ID requirements,\n * parse client payloads, and validate payment IDs.\n */\n\nimport { randomUUID } from \"crypto\";\nimport {\n PaymentIdExtension,\n PaymentIdExtensionInfo,\n PaymentIdPayload,\n DeclarePaymentIdOptions,\n PAYMENT_ID_EXTENSION_KEY,\n} from \"./types.js\";\n\n/**\n * JSON Schema for payment ID payload validation.\n */\nconst PAYMENT_ID_SCHEMA = {\n type: \"object\",\n required: [\"id\"],\n properties: {\n id: { type: \"string\", format: \"uuid\" },\n clientId: { type: \"string\" },\n },\n};\n\n/**\n * Declares a payment ID extension for server responses.\n *\n * @param options - Optional configuration for the payment ID\n * @returns Payment ID extension object ready for response\n *\n * @example\n * ```typescript\n * const extension = declarePaymentIdExtension();\n * // Include in PaymentRequired response extensions:\n * // extensions: { [PAYMENT_ID_EXTENSION_KEY]: extension }\n * ```\n */\nexport function declarePaymentIdExtension(\n options: DeclarePaymentIdOptions = {},\n): PaymentIdExtension {\n const info: PaymentIdExtensionInfo = {\n id: options.id || randomUUID(),\n idempotencyKey: options.idempotencyKey,\n groupId: options.groupId,\n metadata: options.metadata,\n };\n\n return {\n info,\n schema: PAYMENT_ID_SCHEMA,\n };\n}\n\n/**\n * Parses a payment ID payload from client request extensions.\n *\n * @param extensions - Extensions object from the payment payload\n * @returns Parsed payment ID payload, or null if not present\n * @throws Error if payload is present but invalid\n *\n * @example\n * ```typescript\n * const payload = parsePaymentIdPayload(paymentPayload.extensions);\n * if (payload) {\n * console.log(\"Payment ID:\", payload.id);\n * }\n * ```\n */\nexport function parsePaymentIdPayload(\n extensions?: Record<string, unknown>,\n): PaymentIdPayload | null {\n if (!extensions || !(PAYMENT_ID_EXTENSION_KEY in extensions)) {\n return null;\n }\n\n const raw = extensions[PAYMENT_ID_EXTENSION_KEY];\n if (typeof raw !== \"object\" || raw === null) {\n throw new Error(\"Invalid paymentId extension: expected object\");\n }\n\n const obj = raw as Record<string, unknown>;\n\n if (typeof obj.id !== \"string\" || obj.id.length === 0) {\n throw new Error(\"Invalid paymentId extension: missing or empty id\");\n }\n\n return {\n id: obj.id,\n clientId: typeof obj.clientId === \"string\" ? obj.clientId : undefined,\n };\n}\n\n/**\n * Validates that a client's payment ID payload matches the expected extension.\n *\n * @param payload - The client's payment ID payload\n * @param expected - The expected payment ID from the server extension\n * @returns True if the payment ID matches\n *\n * @example\n * ```typescript\n * const isValid = validatePaymentId(clientPayload, serverExtension.info);\n * if (!isValid) {\n * throw new Error(\"Payment ID mismatch\");\n * }\n * ```\n */\nexport function validatePaymentId(\n payload: PaymentIdPayload,\n expected: PaymentIdExtensionInfo,\n): boolean {\n return payload.id === expected.id;\n}\n","/**\n * Payment ID Extension Client-Side Implementation\n *\n * Provides functions for clients to echo payment IDs back to servers.\n */\n\nimport { PaymentIdExtension, PaymentIdPayload } from \"./types.js\";\n\n/**\n * Creates a payment ID payload from a server extension.\n *\n * Reads the payment ID from requirements and echoes it back.\n *\n * @param extension - Payment ID extension from server's 402 response\n * @param clientId - Optional client-generated correlation ID\n * @returns Payment ID payload for inclusion in payment extensions\n *\n * @example\n * ```typescript\n * const extension = paymentRequirements.extensions?.paymentId;\n * const payload = createPaymentIdPayload(extension, \"my-correlation-id\");\n * // Include in payment payload extensions:\n * // extensions: { [PAYMENT_ID_EXTENSION_KEY]: payload }\n * ```\n */\nexport function createPaymentIdPayload(\n extension: PaymentIdExtension,\n clientId?: string,\n): PaymentIdPayload {\n return {\n id: extension.info.id,\n clientId,\n };\n}\n\n/**\n * Encodes a payment ID payload for header-based transport.\n *\n * @param payload - The payment ID payload to encode\n * @returns Base64-encoded JSON string\n */\nexport function encodePaymentIdHeader(payload: PaymentIdPayload): string {\n const json = JSON.stringify(payload);\n if (typeof Buffer !== \"undefined\") {\n return Buffer.from(json, \"utf-8\").toString(\"base64\");\n }\n return btoa(json);\n}\n"],"mappings":";AAUO,IAAM,2BAA2B;;;ACHxC,SAAS,kBAAkB;AAY3B,IAAM,oBAAoB;AAAA,EACxB,MAAM;AAAA,EACN,UAAU,CAAC,IAAI;AAAA,EACf,YAAY;AAAA,IACV,IAAI,EAAE,MAAM,UAAU,QAAQ,OAAO;AAAA,IACrC,UAAU,EAAE,MAAM,SAAS;AAAA,EAC7B;AACF;AAeO,SAAS,0BACd,UAAmC,CAAC,GAChB;AACpB,QAAM,OAA+B;AAAA,IACnC,IAAI,QAAQ,MAAM,WAAW;AAAA,IAC7B,gBAAgB,QAAQ;AAAA,IACxB,SAAS,QAAQ;AAAA,IACjB,UAAU,QAAQ;AAAA,EACpB;AAEA,SAAO;AAAA,IACL;AAAA,IACA,QAAQ;AAAA,EACV;AACF;AAiBO,SAAS,sBACd,YACyB;AACzB,MAAI,CAAC,cAAc,EAAE,4BAA4B,aAAa;AAC5D,WAAO;AAAA,EACT;AAEA,QAAM,MAAM,WAAW,wBAAwB;AAC/C,MAAI,OAAO,QAAQ,YAAY,QAAQ,MAAM;AAC3C,UAAM,IAAI,MAAM,8CAA8C;AAAA,EAChE;AAEA,QAAM,MAAM;AAEZ,MAAI,OAAO,IAAI,OAAO,YAAY,IAAI,GAAG,WAAW,GAAG;AACrD,UAAM,IAAI,MAAM,kDAAkD;AAAA,EACpE;AAEA,SAAO;AAAA,IACL,IAAI,IAAI;AAAA,IACR,UAAU,OAAO,IAAI,aAAa,WAAW,IAAI,WAAW;AAAA,EAC9D;AACF;AAiBO,SAAS,kBACd,SACA,UACS;AACT,SAAO,QAAQ,OAAO,SAAS;AACjC;;;AC3FO,SAAS,uBACd,WACA,UACkB;AAClB,SAAO;AAAA,IACL,IAAI,UAAU,KAAK;AAAA,IACnB;AAAA,EACF;AACF;AAQO,SAAS,sBAAsB,SAAmC;AACvE,QAAM,OAAO,KAAK,UAAU,OAAO;AACnC,MAAI,OAAO,WAAW,aAAa;AACjC,WAAO,OAAO,KAAK,MAAM,OAAO,EAAE,SAAS,QAAQ;AAAA,EACrD;AACA,SAAO,KAAK,IAAI;AAClB;","names":[]}
@@ -0,0 +1,278 @@
1
+ // src/eip2612-gas-sponsoring/server.ts
2
+ var EIP2612_GAS_SPONSOR_SCHEMA = {
3
+ type: "object",
4
+ required: ["network", "permitSignature", "owner", "spender", "value", "deadline", "v", "r", "s"],
5
+ properties: {
6
+ network: { type: "string" },
7
+ permitSignature: { type: "string" },
8
+ owner: { type: "string" },
9
+ spender: { type: "string" },
10
+ value: { type: "string" },
11
+ deadline: { type: "number" },
12
+ v: { type: "number" },
13
+ r: { type: "string" },
14
+ s: { type: "string" }
15
+ }
16
+ };
17
+ function declareEip2612GasSponsorExtension(options) {
18
+ const info = {
19
+ sponsoredNetworks: options.sponsoredNetworks,
20
+ maxAmount: options.maxAmount,
21
+ permitDeadline: options.permitDeadline ?? 300,
22
+ sponsorAddress: options.sponsorAddress
23
+ };
24
+ return {
25
+ info,
26
+ schema: EIP2612_GAS_SPONSOR_SCHEMA
27
+ };
28
+ }
29
+ function parseEip2612GasSponsorHeader(header) {
30
+ if (!header) {
31
+ throw new Error("Missing EIP-2612 gas sponsor header");
32
+ }
33
+ try {
34
+ const decoded = Buffer.from(header, "base64").toString("utf-8");
35
+ const payload = JSON.parse(decoded);
36
+ const required = [
37
+ "network",
38
+ "permitSignature",
39
+ "owner",
40
+ "spender",
41
+ "value",
42
+ "deadline",
43
+ "v",
44
+ "r",
45
+ "s"
46
+ ];
47
+ for (const field of required) {
48
+ if (!(field in payload)) {
49
+ throw new Error(`Missing required field: ${field}`);
50
+ }
51
+ }
52
+ return payload;
53
+ } catch (error) {
54
+ if (error instanceof SyntaxError) {
55
+ throw new Error("Invalid EIP-2612 gas sponsor header: malformed JSON");
56
+ }
57
+ throw error;
58
+ }
59
+ }
60
+ function validateEip2612GasSponsorPayload(payload, extensionInfo, options = {}) {
61
+ const now = options.now ? options.now() : Date.now();
62
+ const nowSeconds = Math.floor(now / 1e3);
63
+ if (!extensionInfo.sponsoredNetworks.includes(payload.network)) {
64
+ return {
65
+ valid: false,
66
+ error: `Network ${payload.network} is not in sponsored networks: ${extensionInfo.sponsoredNetworks.join(", ")}`
67
+ };
68
+ }
69
+ const payloadValue = BigInt(payload.value);
70
+ const maxAmount = BigInt(extensionInfo.maxAmount);
71
+ if (payloadValue > maxAmount) {
72
+ return {
73
+ valid: false,
74
+ error: `Value ${payload.value} exceeds maximum amount ${extensionInfo.maxAmount}`
75
+ };
76
+ }
77
+ if (payload.deadline <= nowSeconds) {
78
+ return {
79
+ valid: false,
80
+ error: "Permit deadline has expired"
81
+ };
82
+ }
83
+ const maxDeadline = nowSeconds + extensionInfo.permitDeadline;
84
+ if (payload.deadline > maxDeadline) {
85
+ return {
86
+ valid: false,
87
+ error: `Permit deadline ${payload.deadline} exceeds maximum allowed deadline ${maxDeadline}`
88
+ };
89
+ }
90
+ if (payload.spender.toLowerCase() !== extensionInfo.sponsorAddress.toLowerCase()) {
91
+ return {
92
+ valid: false,
93
+ error: `Spender ${payload.spender} does not match sponsor address ${extensionInfo.sponsorAddress}`
94
+ };
95
+ }
96
+ const sigHex = payload.permitSignature.startsWith("0x") ? payload.permitSignature.slice(2) : payload.permitSignature;
97
+ if (sigHex.length !== 130) {
98
+ return {
99
+ valid: false,
100
+ error: `Invalid permit signature length: expected 130 hex chars, got ${sigHex.length}`
101
+ };
102
+ }
103
+ if (payload.v !== 27 && payload.v !== 28) {
104
+ return {
105
+ valid: false,
106
+ error: `Invalid v value: expected 27 or 28, got ${payload.v}`
107
+ };
108
+ }
109
+ const rHex = payload.r.startsWith("0x") ? payload.r.slice(2) : payload.r;
110
+ if (rHex.length !== 64) {
111
+ return {
112
+ valid: false,
113
+ error: `Invalid r length: expected 64 hex chars, got ${rHex.length}`
114
+ };
115
+ }
116
+ const sHex = payload.s.startsWith("0x") ? payload.s.slice(2) : payload.s;
117
+ if (sHex.length !== 64) {
118
+ return {
119
+ valid: false,
120
+ error: `Invalid s length: expected 64 hex chars, got ${sHex.length}`
121
+ };
122
+ }
123
+ return { valid: true };
124
+ }
125
+
126
+ // src/eip2612-gas-sponsoring/client.ts
127
+ var EIP2612_GAS_SPONSOR_EXTENSION_KEY = "eip2612GasSponsoring";
128
+ var EIP2612_GAS_SPONSOR_HEADER_NAME = "X-T402-EIP2612-Gas-Sponsoring";
129
+ async function createPermitSignature(params) {
130
+ const { signer, tokenAddress, tokenName, chainId, spender, value, deadline, nonce = 0 } = params;
131
+ const domain = {
132
+ name: tokenName,
133
+ version: "1",
134
+ chainId,
135
+ verifyingContract: tokenAddress
136
+ };
137
+ const types = {
138
+ Permit: [
139
+ { name: "owner", type: "address" },
140
+ { name: "spender", type: "address" },
141
+ { name: "value", type: "uint256" },
142
+ { name: "nonce", type: "uint256" },
143
+ { name: "deadline", type: "uint256" }
144
+ ]
145
+ };
146
+ const message = {
147
+ owner: signer.address,
148
+ spender,
149
+ value,
150
+ nonce,
151
+ deadline
152
+ };
153
+ const signature = await signer.signTypedData({
154
+ domain,
155
+ types,
156
+ primaryType: "Permit",
157
+ message
158
+ });
159
+ const sigHex = signature.startsWith("0x") ? signature.slice(2) : signature;
160
+ if (sigHex.length !== 130) {
161
+ throw new Error(`Invalid signature length: expected 130 hex chars, got ${sigHex.length}`);
162
+ }
163
+ const r = "0x" + sigHex.slice(0, 64);
164
+ const s = "0x" + sigHex.slice(64, 128);
165
+ let v = parseInt(sigHex.slice(128, 130), 16);
166
+ if (v < 27) {
167
+ v += 27;
168
+ }
169
+ return {
170
+ owner: signer.address,
171
+ spender,
172
+ value,
173
+ deadline,
174
+ v,
175
+ r,
176
+ s,
177
+ permitSignature: signature.startsWith("0x") ? signature : "0x" + signature
178
+ };
179
+ }
180
+ function createEip2612GasSponsorPayload(permit, network) {
181
+ return {
182
+ network,
183
+ permitSignature: permit.permitSignature,
184
+ owner: permit.owner,
185
+ spender: permit.spender,
186
+ value: permit.value,
187
+ deadline: permit.deadline,
188
+ v: permit.v,
189
+ r: permit.r,
190
+ s: permit.s
191
+ };
192
+ }
193
+ function encodeEip2612GasSponsorHeader(payload) {
194
+ const json = JSON.stringify(payload);
195
+ if (typeof Buffer !== "undefined") {
196
+ return Buffer.from(json, "utf-8").toString("base64");
197
+ }
198
+ return btoa(json);
199
+ }
200
+
201
+ // src/eip2612-gas-sponsoring/facilitator.ts
202
+ function extractEip2612GasSponsorPayload(extensions) {
203
+ if (!extensions) {
204
+ return null;
205
+ }
206
+ const raw = extensions[EIP2612_GAS_SPONSOR_EXTENSION_KEY];
207
+ if (!raw || typeof raw !== "object") {
208
+ return null;
209
+ }
210
+ const payload = raw;
211
+ const required = [
212
+ "network",
213
+ "permitSignature",
214
+ "owner",
215
+ "spender",
216
+ "value",
217
+ "deadline",
218
+ "v",
219
+ "r",
220
+ "s"
221
+ ];
222
+ for (const field of required) {
223
+ if (!(field in payload)) {
224
+ return null;
225
+ }
226
+ }
227
+ return {
228
+ network: payload.network,
229
+ permitSignature: payload.permitSignature,
230
+ owner: payload.owner,
231
+ spender: payload.spender,
232
+ value: payload.value,
233
+ deadline: payload.deadline,
234
+ v: payload.v,
235
+ r: payload.r,
236
+ s: payload.s
237
+ };
238
+ }
239
+ function validateAndExtractPermit(extensions, extensionInfo) {
240
+ const payload = extractEip2612GasSponsorPayload(extensions);
241
+ if (!payload) {
242
+ return {
243
+ valid: false,
244
+ error: `Missing or invalid ${EIP2612_GAS_SPONSOR_EXTENSION_KEY} extension in payment`
245
+ };
246
+ }
247
+ const result = validateEip2612GasSponsorPayload(payload, extensionInfo);
248
+ if (!result.valid) {
249
+ return result;
250
+ }
251
+ return { valid: true, payload };
252
+ }
253
+ function buildPermitCallData(payload) {
254
+ return {
255
+ owner: payload.owner,
256
+ spender: payload.spender,
257
+ value: payload.value,
258
+ deadline: payload.deadline,
259
+ v: payload.v,
260
+ r: payload.r,
261
+ s: payload.s
262
+ };
263
+ }
264
+
265
+ export {
266
+ declareEip2612GasSponsorExtension,
267
+ parseEip2612GasSponsorHeader,
268
+ validateEip2612GasSponsorPayload,
269
+ EIP2612_GAS_SPONSOR_EXTENSION_KEY,
270
+ EIP2612_GAS_SPONSOR_HEADER_NAME,
271
+ createPermitSignature,
272
+ createEip2612GasSponsorPayload,
273
+ encodeEip2612GasSponsorHeader,
274
+ extractEip2612GasSponsorPayload,
275
+ validateAndExtractPermit,
276
+ buildPermitCallData
277
+ };
278
+ //# sourceMappingURL=chunk-VINC22RD.mjs.map