@bankofai/x402-extensions 1.0.0-beta.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 +28 -0
- package/dist/cjs/bazaar/index.d.ts +4 -0
- package/dist/cjs/bazaar/index.js +1048 -0
- package/dist/cjs/bazaar/index.js.map +1 -0
- package/dist/cjs/builder-code/index.d.ts +225 -0
- package/dist/cjs/builder-code/index.js +369 -0
- package/dist/cjs/builder-code/index.js.map +1 -0
- package/dist/cjs/index-B8S563vv.d.ts +929 -0
- package/dist/cjs/index-BUV9B9f8.d.ts +929 -0
- package/dist/cjs/index.d.ts +373 -0
- package/dist/cjs/index.js +3536 -0
- package/dist/cjs/index.js.map +1 -0
- package/dist/cjs/offer-receipt/index.d.ts +702 -0
- package/dist/cjs/offer-receipt/index.js +909 -0
- package/dist/cjs/offer-receipt/index.js.map +1 -0
- package/dist/cjs/payment-identifier/index.d.ts +345 -0
- package/dist/cjs/payment-identifier/index.js +285 -0
- package/dist/cjs/payment-identifier/index.js.map +1 -0
- package/dist/cjs/sign-in-with-x/index.d.ts +1117 -0
- package/dist/cjs/sign-in-with-x/index.js +876 -0
- package/dist/cjs/sign-in-with-x/index.js.map +1 -0
- package/dist/esm/bazaar/index.d.mts +4 -0
- package/dist/esm/bazaar/index.mjs +51 -0
- package/dist/esm/bazaar/index.mjs.map +1 -0
- package/dist/esm/builder-code/index.d.mts +225 -0
- package/dist/esm/builder-code/index.mjs +27 -0
- package/dist/esm/builder-code/index.mjs.map +1 -0
- package/dist/esm/chunk-4IPDE3NS.mjs +990 -0
- package/dist/esm/chunk-4IPDE3NS.mjs.map +1 -0
- package/dist/esm/chunk-N74HQTNO.mjs +807 -0
- package/dist/esm/chunk-N74HQTNO.mjs.map +1 -0
- package/dist/esm/chunk-OWZP4CUR.mjs +333 -0
- package/dist/esm/chunk-OWZP4CUR.mjs.map +1 -0
- package/dist/esm/chunk-RERA4OZZ.mjs +233 -0
- package/dist/esm/chunk-RERA4OZZ.mjs.map +1 -0
- package/dist/esm/chunk-TIVMC3ZS.mjs +828 -0
- package/dist/esm/chunk-TIVMC3ZS.mjs.map +1 -0
- package/dist/esm/index-B8S563vv.d.mts +929 -0
- package/dist/esm/index-BUV9B9f8.d.mts +929 -0
- package/dist/esm/index.d.mts +373 -0
- package/dist/esm/index.mjs +455 -0
- package/dist/esm/index.mjs.map +1 -0
- package/dist/esm/offer-receipt/index.d.mts +702 -0
- package/dist/esm/offer-receipt/index.mjs +97 -0
- package/dist/esm/offer-receipt/index.mjs.map +1 -0
- package/dist/esm/payment-identifier/index.d.mts +345 -0
- package/dist/esm/payment-identifier/index.mjs +39 -0
- package/dist/esm/payment-identifier/index.mjs.map +1 -0
- package/dist/esm/sign-in-with-x/index.d.mts +1117 -0
- package/dist/esm/sign-in-with-x/index.mjs +73 -0
- package/dist/esm/sign-in-with-x/index.mjs.map +1 -0
- package/package.json +124 -0
|
@@ -0,0 +1,3536 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __export = (target, all) => {
|
|
9
|
+
for (var name in all)
|
|
10
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
11
|
+
};
|
|
12
|
+
var __copyProps = (to, from, except, desc) => {
|
|
13
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
14
|
+
for (let key of __getOwnPropNames(from))
|
|
15
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
16
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
17
|
+
}
|
|
18
|
+
return to;
|
|
19
|
+
};
|
|
20
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
+
mod
|
|
27
|
+
));
|
|
28
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
29
|
+
|
|
30
|
+
// src/index.ts
|
|
31
|
+
var src_exports = {};
|
|
32
|
+
__export(src_exports, {
|
|
33
|
+
BAZAAR: () => BAZAAR,
|
|
34
|
+
BUILDER_CODE: () => BUILDER_CODE,
|
|
35
|
+
BUILDER_CODE_PATTERN: () => BUILDER_CODE_PATTERN,
|
|
36
|
+
BUILDER_CODE_SCHEMA: () => BUILDER_CODE_SCHEMA,
|
|
37
|
+
BuilderCodeClientExtension: () => BuilderCodeClientExtension,
|
|
38
|
+
BuilderCodeFacilitatorExtension: () => BuilderCodeFacilitatorExtension,
|
|
39
|
+
EIP2612_GAS_SPONSORING: () => EIP2612_GAS_SPONSORING,
|
|
40
|
+
ERC20_APPROVAL_GAS_SPONSORING: () => ERC20_APPROVAL_GAS_SPONSORING,
|
|
41
|
+
ERC20_APPROVAL_GAS_SPONSORING_VERSION: () => ERC20_APPROVAL_GAS_SPONSORING_VERSION,
|
|
42
|
+
ERC_8021_MARKER: () => ERC_8021_MARKER,
|
|
43
|
+
InMemorySIWxStorage: () => InMemorySIWxStorage,
|
|
44
|
+
OFFER_RECEIPT: () => OFFER_RECEIPT,
|
|
45
|
+
OFFER_TYPES: () => OFFER_TYPES,
|
|
46
|
+
PAYMENT_IDENTIFIER: () => PAYMENT_IDENTIFIER,
|
|
47
|
+
PAYMENT_ID_MAX_LENGTH: () => PAYMENT_ID_MAX_LENGTH,
|
|
48
|
+
PAYMENT_ID_MIN_LENGTH: () => PAYMENT_ID_MIN_LENGTH,
|
|
49
|
+
PAYMENT_ID_PATTERN: () => PAYMENT_ID_PATTERN,
|
|
50
|
+
RECEIPT_TYPES: () => RECEIPT_TYPES,
|
|
51
|
+
SCHEMA_2_ID: () => SCHEMA_2_ID,
|
|
52
|
+
SIGN_IN_WITH_X: () => SIGN_IN_WITH_X,
|
|
53
|
+
SIWxPayloadSchema: () => SIWxPayloadSchema,
|
|
54
|
+
SOLANA_DEVNET: () => SOLANA_DEVNET,
|
|
55
|
+
SOLANA_MAINNET: () => SOLANA_MAINNET,
|
|
56
|
+
SOLANA_TESTNET: () => SOLANA_TESTNET,
|
|
57
|
+
appendPaymentIdentifierToExtensions: () => appendPaymentIdentifierToExtensions,
|
|
58
|
+
bazaarResourceServerExtension: () => bazaarResourceServerExtension,
|
|
59
|
+
buildSIWxSchema: () => buildSIWxSchema,
|
|
60
|
+
builderCodeResourceServerExtension: () => builderCodeResourceServerExtension,
|
|
61
|
+
canonicalize: () => canonicalize,
|
|
62
|
+
checkIfBazaarNeeded: () => import_server.checkIfBazaarNeeded,
|
|
63
|
+
convertNetworkStringToCAIP2: () => convertNetworkStringToCAIP2,
|
|
64
|
+
createEIP712OfferReceiptIssuer: () => createEIP712OfferReceiptIssuer,
|
|
65
|
+
createErc20ApprovalGasSponsoringExtension: () => createErc20ApprovalGasSponsoringExtension,
|
|
66
|
+
createJWS: () => createJWS,
|
|
67
|
+
createJWSOfferReceiptIssuer: () => createJWSOfferReceiptIssuer,
|
|
68
|
+
createOfferDomain: () => createOfferDomain,
|
|
69
|
+
createOfferEIP712: () => createOfferEIP712,
|
|
70
|
+
createOfferJWS: () => createOfferJWS,
|
|
71
|
+
createOfferReceiptExtension: () => createOfferReceiptExtension,
|
|
72
|
+
createReceiptDomain: () => createReceiptDomain,
|
|
73
|
+
createReceiptEIP712: () => createReceiptEIP712,
|
|
74
|
+
createReceiptJWS: () => createReceiptJWS,
|
|
75
|
+
createSIWxClientExtension: () => createSIWxClientExtension,
|
|
76
|
+
createSIWxClientHook: () => createSIWxClientHook,
|
|
77
|
+
createSIWxMessage: () => createSIWxMessage,
|
|
78
|
+
createSIWxPayload: () => createSIWxPayload,
|
|
79
|
+
createSIWxRequestHook: () => createSIWxRequestHook,
|
|
80
|
+
createSIWxResourceServerExtension: () => createSIWxResourceServerExtension,
|
|
81
|
+
createSIWxSettleHook: () => createSIWxSettleHook,
|
|
82
|
+
declareBuilderCodeExtension: () => declareBuilderCodeExtension,
|
|
83
|
+
declareDiscoveryExtension: () => declareDiscoveryExtension,
|
|
84
|
+
declareEip2612GasSponsoringExtension: () => declareEip2612GasSponsoringExtension,
|
|
85
|
+
declareErc20ApprovalGasSponsoringExtension: () => declareErc20ApprovalGasSponsoringExtension,
|
|
86
|
+
declareOfferReceiptExtension: () => declareOfferReceiptExtension,
|
|
87
|
+
declarePaymentIdentifierExtension: () => declarePaymentIdentifierExtension,
|
|
88
|
+
declareSIWxExtension: () => declareSIWxExtension,
|
|
89
|
+
decodeBase58: () => decodeBase58,
|
|
90
|
+
decodeSignedOffers: () => decodeSignedOffers,
|
|
91
|
+
encodeBase58: () => encodeBase58,
|
|
92
|
+
encodeBuilderCodeSuffix: () => encodeBuilderCodeSuffix,
|
|
93
|
+
encodeSIWxHeader: () => encodeSIWxHeader,
|
|
94
|
+
erc20ApprovalGasSponsoringSchema: () => erc20ApprovalGasSponsoringSchema,
|
|
95
|
+
extractAndValidatePaymentIdentifier: () => extractAndValidatePaymentIdentifier,
|
|
96
|
+
extractChainIdFromCAIP2: () => extractChainIdFromCAIP2,
|
|
97
|
+
extractDiscoveryInfo: () => extractDiscoveryInfo,
|
|
98
|
+
extractDiscoveryInfoFromExtension: () => extractDiscoveryInfoFromExtension,
|
|
99
|
+
extractDiscoveryInfoV1: () => extractDiscoveryInfoV1,
|
|
100
|
+
extractEIP155ChainId: () => extractEIP155ChainId,
|
|
101
|
+
extractEVMChainId: () => extractEVMChainId,
|
|
102
|
+
extractEip2612GasSponsoringInfo: () => extractEip2612GasSponsoringInfo,
|
|
103
|
+
extractErc20ApprovalGasSponsoringInfo: () => extractErc20ApprovalGasSponsoringInfo,
|
|
104
|
+
extractJWSHeader: () => extractJWSHeader,
|
|
105
|
+
extractJWSPayload: () => extractJWSPayload,
|
|
106
|
+
extractOfferPayload: () => extractOfferPayload,
|
|
107
|
+
extractOffersFromPaymentRequired: () => extractOffersFromPaymentRequired,
|
|
108
|
+
extractPaymentIdentifier: () => extractPaymentIdentifier,
|
|
109
|
+
extractPublicKeyFromKid: () => extractPublicKeyFromKid,
|
|
110
|
+
extractReceiptFromResponse: () => extractReceiptFromResponse,
|
|
111
|
+
extractReceiptPayload: () => extractReceiptPayload,
|
|
112
|
+
extractResourceMetadataV1: () => extractResourceMetadataV1,
|
|
113
|
+
extractSolanaChainReference: () => extractSolanaChainReference,
|
|
114
|
+
findAcceptsObjectFromSignedOffer: () => findAcceptsObjectFromSignedOffer,
|
|
115
|
+
formatSIWEMessage: () => formatSIWEMessage,
|
|
116
|
+
formatSIWSMessage: () => formatSIWSMessage,
|
|
117
|
+
generatePaymentId: () => generatePaymentId,
|
|
118
|
+
getCanonicalBytes: () => getCanonicalBytes,
|
|
119
|
+
getEVMAddress: () => getEVMAddress,
|
|
120
|
+
getSolanaAddress: () => getSolanaAddress,
|
|
121
|
+
hasPaymentIdentifier: () => hasPaymentIdentifier,
|
|
122
|
+
hashCanonical: () => hashCanonical,
|
|
123
|
+
hashOfferTypedData: () => hashOfferTypedData,
|
|
124
|
+
hashReceiptTypedData: () => hashReceiptTypedData,
|
|
125
|
+
isBodyExtensionConfig: () => isBodyExtensionConfig,
|
|
126
|
+
isDiscoverableV1: () => isDiscoverableV1,
|
|
127
|
+
isEIP712SignedOffer: () => isEIP712SignedOffer,
|
|
128
|
+
isEIP712SignedReceipt: () => isEIP712SignedReceipt,
|
|
129
|
+
isEIP712Signer: () => isEIP712Signer,
|
|
130
|
+
isEVMSigner: () => isEVMSigner,
|
|
131
|
+
isJWSSignedOffer: () => isJWSSignedOffer,
|
|
132
|
+
isJWSSignedReceipt: () => isJWSSignedReceipt,
|
|
133
|
+
isJWSSigner: () => isJWSSigner,
|
|
134
|
+
isMcpExtensionConfig: () => isMcpExtensionConfig,
|
|
135
|
+
isPaymentIdentifierExtension: () => isPaymentIdentifierExtension,
|
|
136
|
+
isPaymentIdentifierRequired: () => isPaymentIdentifierRequired,
|
|
137
|
+
isQueryExtensionConfig: () => isQueryExtensionConfig,
|
|
138
|
+
isSolanaSigner: () => isSolanaSigner,
|
|
139
|
+
isValidIconUrl: () => isValidIconUrl,
|
|
140
|
+
isValidPaymentId: () => isValidPaymentId,
|
|
141
|
+
isValidRouteTemplate: () => isValidRouteTemplate,
|
|
142
|
+
isValidServiceName: () => isValidServiceName,
|
|
143
|
+
parseBuilderCodeSuffixFromCalldata: () => parseBuilderCodeSuffixFromCalldata,
|
|
144
|
+
parseSIWxHeader: () => parseSIWxHeader,
|
|
145
|
+
paymentIdentifierResourceServerExtension: () => paymentIdentifierResourceServerExtension,
|
|
146
|
+
paymentIdentifierSchema: () => paymentIdentifierSchema,
|
|
147
|
+
prepareOfferForEIP712: () => prepareOfferForEIP712,
|
|
148
|
+
prepareReceiptForEIP712: () => prepareReceiptForEIP712,
|
|
149
|
+
sanitizeResourceServiceMetadata: () => sanitizeResourceServiceMetadata,
|
|
150
|
+
sanitizeTags: () => sanitizeTags,
|
|
151
|
+
signEVMMessage: () => signEVMMessage,
|
|
152
|
+
signOfferEIP712: () => signOfferEIP712,
|
|
153
|
+
signReceiptEIP712: () => signReceiptEIP712,
|
|
154
|
+
signSolanaMessage: () => signSolanaMessage,
|
|
155
|
+
validateAndExtract: () => validateAndExtract,
|
|
156
|
+
validateBazaarRouteExtensions: () => validateBazaarRouteExtensions,
|
|
157
|
+
validateDiscoveryExtension: () => validateDiscoveryExtension,
|
|
158
|
+
validateDiscoveryExtensionSpec: () => validateDiscoveryExtensionSpec,
|
|
159
|
+
validateEip2612GasSponsoringInfo: () => validateEip2612GasSponsoringInfo,
|
|
160
|
+
validateErc20ApprovalGasSponsoringInfo: () => validateErc20ApprovalGasSponsoringInfo,
|
|
161
|
+
validatePaymentIdentifier: () => validatePaymentIdentifier,
|
|
162
|
+
validatePaymentIdentifierRequirement: () => validatePaymentIdentifierRequirement,
|
|
163
|
+
validateRouteTemplate: () => validateRouteTemplate,
|
|
164
|
+
validateSIWxMessage: () => validateSIWxMessage,
|
|
165
|
+
verifyEVMSignature: () => verifyEVMSignature,
|
|
166
|
+
verifyOfferSignatureEIP712: () => verifyOfferSignatureEIP712,
|
|
167
|
+
verifyOfferSignatureJWS: () => verifyOfferSignatureJWS,
|
|
168
|
+
verifyReceiptMatchesOffer: () => verifyReceiptMatchesOffer,
|
|
169
|
+
verifyReceiptSignatureEIP712: () => verifyReceiptSignatureEIP712,
|
|
170
|
+
verifyReceiptSignatureJWS: () => verifyReceiptSignatureJWS,
|
|
171
|
+
verifySIWxSignature: () => verifySIWxSignature,
|
|
172
|
+
verifySolanaSignature: () => verifySolanaSignature,
|
|
173
|
+
withBazaar: () => withBazaar,
|
|
174
|
+
wrapFetchWithSIWx: () => wrapFetchWithSIWx
|
|
175
|
+
});
|
|
176
|
+
module.exports = __toCommonJS(src_exports);
|
|
177
|
+
|
|
178
|
+
// src/bazaar/http/types.ts
|
|
179
|
+
var isQueryExtensionConfig = (config) => {
|
|
180
|
+
return !("bodyType" in config) && !("toolName" in config);
|
|
181
|
+
};
|
|
182
|
+
var isBodyExtensionConfig = (config) => {
|
|
183
|
+
return "bodyType" in config;
|
|
184
|
+
};
|
|
185
|
+
|
|
186
|
+
// src/bazaar/mcp/types.ts
|
|
187
|
+
var isMcpExtensionConfig = (config) => {
|
|
188
|
+
return "toolName" in config;
|
|
189
|
+
};
|
|
190
|
+
|
|
191
|
+
// src/bazaar/types.ts
|
|
192
|
+
var BAZAAR = { key: "bazaar" };
|
|
193
|
+
|
|
194
|
+
// src/bazaar/http/resourceService.ts
|
|
195
|
+
function createQueryDiscoveryExtension({
|
|
196
|
+
method,
|
|
197
|
+
input = {},
|
|
198
|
+
inputSchema = { properties: {} },
|
|
199
|
+
pathParams,
|
|
200
|
+
pathParamsSchema,
|
|
201
|
+
output
|
|
202
|
+
}) {
|
|
203
|
+
return {
|
|
204
|
+
info: {
|
|
205
|
+
input: {
|
|
206
|
+
type: "http",
|
|
207
|
+
...method ? { method } : {},
|
|
208
|
+
...input ? { queryParams: input } : {},
|
|
209
|
+
...pathParams ? { pathParams } : {}
|
|
210
|
+
},
|
|
211
|
+
...output?.example ? {
|
|
212
|
+
output: {
|
|
213
|
+
type: "json",
|
|
214
|
+
example: output.example
|
|
215
|
+
}
|
|
216
|
+
} : {}
|
|
217
|
+
},
|
|
218
|
+
schema: {
|
|
219
|
+
$schema: "https://json-schema.org/draft/2020-12/schema",
|
|
220
|
+
type: "object",
|
|
221
|
+
properties: {
|
|
222
|
+
input: {
|
|
223
|
+
type: "object",
|
|
224
|
+
properties: {
|
|
225
|
+
type: {
|
|
226
|
+
type: "string",
|
|
227
|
+
const: "http"
|
|
228
|
+
},
|
|
229
|
+
method: {
|
|
230
|
+
type: "string",
|
|
231
|
+
enum: ["GET", "HEAD", "DELETE"]
|
|
232
|
+
},
|
|
233
|
+
...inputSchema ? {
|
|
234
|
+
queryParams: {
|
|
235
|
+
type: "object",
|
|
236
|
+
...typeof inputSchema === "object" ? inputSchema : {}
|
|
237
|
+
}
|
|
238
|
+
} : {},
|
|
239
|
+
...pathParamsSchema ? {
|
|
240
|
+
pathParams: {
|
|
241
|
+
type: "object",
|
|
242
|
+
...typeof pathParamsSchema === "object" ? pathParamsSchema : {}
|
|
243
|
+
}
|
|
244
|
+
} : {}
|
|
245
|
+
},
|
|
246
|
+
required: ["type", "method"],
|
|
247
|
+
// pathParams are not declared here at schema build time --
|
|
248
|
+
// the server extension's enrichDeclaration adds them to both info and schema
|
|
249
|
+
// atomically at request time, keeping data and schema consistent.
|
|
250
|
+
additionalProperties: false
|
|
251
|
+
},
|
|
252
|
+
...output?.example ? {
|
|
253
|
+
output: {
|
|
254
|
+
type: "object",
|
|
255
|
+
properties: {
|
|
256
|
+
type: {
|
|
257
|
+
type: "string"
|
|
258
|
+
},
|
|
259
|
+
example: {
|
|
260
|
+
type: "object",
|
|
261
|
+
...output.schema && typeof output.schema === "object" ? output.schema : {}
|
|
262
|
+
}
|
|
263
|
+
},
|
|
264
|
+
required: ["type"]
|
|
265
|
+
}
|
|
266
|
+
} : {}
|
|
267
|
+
},
|
|
268
|
+
required: ["input"]
|
|
269
|
+
}
|
|
270
|
+
};
|
|
271
|
+
}
|
|
272
|
+
function createBodyDiscoveryExtension({
|
|
273
|
+
method,
|
|
274
|
+
input = {},
|
|
275
|
+
inputSchema = { properties: {} },
|
|
276
|
+
pathParams,
|
|
277
|
+
pathParamsSchema,
|
|
278
|
+
bodyType,
|
|
279
|
+
output
|
|
280
|
+
}) {
|
|
281
|
+
return {
|
|
282
|
+
info: {
|
|
283
|
+
input: {
|
|
284
|
+
type: "http",
|
|
285
|
+
...method ? { method } : {},
|
|
286
|
+
bodyType,
|
|
287
|
+
body: input,
|
|
288
|
+
...pathParams ? { pathParams } : {}
|
|
289
|
+
},
|
|
290
|
+
...output?.example ? {
|
|
291
|
+
output: {
|
|
292
|
+
type: "json",
|
|
293
|
+
example: output.example
|
|
294
|
+
}
|
|
295
|
+
} : {}
|
|
296
|
+
},
|
|
297
|
+
schema: {
|
|
298
|
+
$schema: "https://json-schema.org/draft/2020-12/schema",
|
|
299
|
+
type: "object",
|
|
300
|
+
properties: {
|
|
301
|
+
input: {
|
|
302
|
+
type: "object",
|
|
303
|
+
properties: {
|
|
304
|
+
type: {
|
|
305
|
+
type: "string",
|
|
306
|
+
const: "http"
|
|
307
|
+
},
|
|
308
|
+
method: {
|
|
309
|
+
type: "string",
|
|
310
|
+
enum: ["POST", "PUT", "PATCH"]
|
|
311
|
+
},
|
|
312
|
+
bodyType: {
|
|
313
|
+
type: "string",
|
|
314
|
+
enum: ["json", "form-data", "text"]
|
|
315
|
+
},
|
|
316
|
+
body: inputSchema,
|
|
317
|
+
...pathParamsSchema ? {
|
|
318
|
+
pathParams: {
|
|
319
|
+
type: "object",
|
|
320
|
+
...typeof pathParamsSchema === "object" ? pathParamsSchema : {}
|
|
321
|
+
}
|
|
322
|
+
} : {}
|
|
323
|
+
},
|
|
324
|
+
required: ["type", "method", "bodyType", "body"],
|
|
325
|
+
// pathParams are not declared here at schema build time --
|
|
326
|
+
// the server extension's enrichDeclaration adds them to both info and schema
|
|
327
|
+
// atomically at request time, keeping data and schema consistent.
|
|
328
|
+
additionalProperties: false
|
|
329
|
+
},
|
|
330
|
+
...output?.example ? {
|
|
331
|
+
output: {
|
|
332
|
+
type: "object",
|
|
333
|
+
properties: {
|
|
334
|
+
type: {
|
|
335
|
+
type: "string"
|
|
336
|
+
},
|
|
337
|
+
example: {
|
|
338
|
+
type: "object",
|
|
339
|
+
...output.schema && typeof output.schema === "object" ? output.schema : {}
|
|
340
|
+
}
|
|
341
|
+
},
|
|
342
|
+
required: ["type"]
|
|
343
|
+
}
|
|
344
|
+
} : {}
|
|
345
|
+
},
|
|
346
|
+
required: ["input"]
|
|
347
|
+
}
|
|
348
|
+
};
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
// src/bazaar/mcp/resourceService.ts
|
|
352
|
+
function createMcpDiscoveryExtension({
|
|
353
|
+
toolName,
|
|
354
|
+
description,
|
|
355
|
+
transport,
|
|
356
|
+
inputSchema,
|
|
357
|
+
example,
|
|
358
|
+
output
|
|
359
|
+
}) {
|
|
360
|
+
return {
|
|
361
|
+
info: {
|
|
362
|
+
input: {
|
|
363
|
+
type: "mcp",
|
|
364
|
+
toolName,
|
|
365
|
+
...description !== void 0 ? { description } : {},
|
|
366
|
+
...transport !== void 0 ? { transport } : {},
|
|
367
|
+
inputSchema,
|
|
368
|
+
...example !== void 0 ? { example } : {}
|
|
369
|
+
},
|
|
370
|
+
...output?.example ? {
|
|
371
|
+
output: {
|
|
372
|
+
type: "json",
|
|
373
|
+
example: output.example
|
|
374
|
+
}
|
|
375
|
+
} : {}
|
|
376
|
+
},
|
|
377
|
+
schema: {
|
|
378
|
+
$schema: "https://json-schema.org/draft/2020-12/schema",
|
|
379
|
+
type: "object",
|
|
380
|
+
properties: {
|
|
381
|
+
input: {
|
|
382
|
+
type: "object",
|
|
383
|
+
properties: {
|
|
384
|
+
type: {
|
|
385
|
+
type: "string",
|
|
386
|
+
const: "mcp"
|
|
387
|
+
},
|
|
388
|
+
toolName: {
|
|
389
|
+
type: "string"
|
|
390
|
+
},
|
|
391
|
+
...description !== void 0 ? {
|
|
392
|
+
description: {
|
|
393
|
+
type: "string"
|
|
394
|
+
}
|
|
395
|
+
} : {},
|
|
396
|
+
...transport !== void 0 ? {
|
|
397
|
+
transport: {
|
|
398
|
+
type: "string",
|
|
399
|
+
...transport === "streamable-http" || transport === "sse" ? { enum: [transport] } : {}
|
|
400
|
+
}
|
|
401
|
+
} : {},
|
|
402
|
+
inputSchema: {
|
|
403
|
+
type: "object"
|
|
404
|
+
},
|
|
405
|
+
...example !== void 0 ? {
|
|
406
|
+
example: {
|
|
407
|
+
type: "object"
|
|
408
|
+
}
|
|
409
|
+
} : {}
|
|
410
|
+
},
|
|
411
|
+
required: ["type", "toolName", "inputSchema"],
|
|
412
|
+
additionalProperties: false
|
|
413
|
+
},
|
|
414
|
+
...output?.example ? {
|
|
415
|
+
output: {
|
|
416
|
+
type: "object",
|
|
417
|
+
properties: {
|
|
418
|
+
type: {
|
|
419
|
+
type: "string"
|
|
420
|
+
},
|
|
421
|
+
example: {
|
|
422
|
+
type: "object",
|
|
423
|
+
...output.schema && typeof output.schema === "object" ? output.schema : {}
|
|
424
|
+
}
|
|
425
|
+
},
|
|
426
|
+
required: ["type"]
|
|
427
|
+
}
|
|
428
|
+
} : {}
|
|
429
|
+
},
|
|
430
|
+
required: ["input"]
|
|
431
|
+
}
|
|
432
|
+
};
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
// src/bazaar/resourceService.ts
|
|
436
|
+
function declareDiscoveryExtension(config) {
|
|
437
|
+
if ("toolName" in config) {
|
|
438
|
+
const extension2 = createMcpDiscoveryExtension(config);
|
|
439
|
+
return { bazaar: extension2 };
|
|
440
|
+
}
|
|
441
|
+
const bodyType = config.bodyType;
|
|
442
|
+
const isBodyMethod2 = bodyType !== void 0;
|
|
443
|
+
const extension = isBodyMethod2 ? createBodyDiscoveryExtension(config) : createQueryDiscoveryExtension(config);
|
|
444
|
+
return { bazaar: extension };
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
// src/bazaar/server.ts
|
|
448
|
+
var BRACKET_PARAM_REGEX = /\[([^\]]+)\]/;
|
|
449
|
+
var BRACKET_PARAM_REGEX_ALL = /\[([^\]]+)\]/g;
|
|
450
|
+
var COLON_PARAM_REGEX = /:([a-zA-Z_][a-zA-Z0-9_]*)/;
|
|
451
|
+
function isHTTPRequestContext(ctx) {
|
|
452
|
+
return ctx !== null && typeof ctx === "object" && "method" in ctx && "adapter" in ctx;
|
|
453
|
+
}
|
|
454
|
+
function normalizeWildcardPattern(pattern) {
|
|
455
|
+
if (!pattern.includes("*")) {
|
|
456
|
+
return pattern;
|
|
457
|
+
}
|
|
458
|
+
let counter = 0;
|
|
459
|
+
return pattern.split("/").map((seg) => {
|
|
460
|
+
if (seg === "*") {
|
|
461
|
+
counter++;
|
|
462
|
+
return `:var${counter}`;
|
|
463
|
+
}
|
|
464
|
+
return seg;
|
|
465
|
+
}).join("/");
|
|
466
|
+
}
|
|
467
|
+
function extractDynamicRouteInfo(routePattern, urlPath) {
|
|
468
|
+
const hasBracket = BRACKET_PARAM_REGEX.test(routePattern);
|
|
469
|
+
const hasColon = COLON_PARAM_REGEX.test(routePattern);
|
|
470
|
+
if (!hasBracket && !hasColon) {
|
|
471
|
+
return null;
|
|
472
|
+
}
|
|
473
|
+
const normalizedPattern = hasBracket ? routePattern.replace(BRACKET_PARAM_REGEX_ALL, ":$1") : routePattern;
|
|
474
|
+
const pathParams = extractPathParams(normalizedPattern, urlPath, false);
|
|
475
|
+
return { routeTemplate: normalizedPattern, pathParams };
|
|
476
|
+
}
|
|
477
|
+
function extractPathParams(routePattern, urlPath, isBracket) {
|
|
478
|
+
const paramNames = [];
|
|
479
|
+
const splitRegex = isBracket ? BRACKET_PARAM_REGEX : COLON_PARAM_REGEX;
|
|
480
|
+
const parts = routePattern.split(splitRegex);
|
|
481
|
+
const regexParts = [];
|
|
482
|
+
parts.forEach((part, i) => {
|
|
483
|
+
if (i % 2 === 0) {
|
|
484
|
+
regexParts.push(part.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"));
|
|
485
|
+
} else {
|
|
486
|
+
paramNames.push(part);
|
|
487
|
+
regexParts.push("([^/]+)");
|
|
488
|
+
}
|
|
489
|
+
});
|
|
490
|
+
const regex = new RegExp(`^${regexParts.join("")}$`);
|
|
491
|
+
const match = urlPath.match(regex);
|
|
492
|
+
if (!match) return {};
|
|
493
|
+
const result = {};
|
|
494
|
+
paramNames.forEach((name, idx) => {
|
|
495
|
+
result[name] = match[idx + 1];
|
|
496
|
+
});
|
|
497
|
+
return result;
|
|
498
|
+
}
|
|
499
|
+
var bazaarResourceServerExtension = {
|
|
500
|
+
key: BAZAAR.key,
|
|
501
|
+
enrichDeclaration: (declaration, transportContext) => {
|
|
502
|
+
if (!isHTTPRequestContext(transportContext)) {
|
|
503
|
+
return declaration;
|
|
504
|
+
}
|
|
505
|
+
const extension = declaration;
|
|
506
|
+
if (extension.info?.input?.type === "mcp") {
|
|
507
|
+
return declaration;
|
|
508
|
+
}
|
|
509
|
+
const method = transportContext.method;
|
|
510
|
+
const existingInputProps = extension.schema?.properties?.input?.properties || {};
|
|
511
|
+
const updatedInputProps = {
|
|
512
|
+
...existingInputProps,
|
|
513
|
+
method: {
|
|
514
|
+
type: "string",
|
|
515
|
+
enum: [method]
|
|
516
|
+
}
|
|
517
|
+
};
|
|
518
|
+
const enrichedResult = {
|
|
519
|
+
...extension,
|
|
520
|
+
info: {
|
|
521
|
+
...extension.info || {},
|
|
522
|
+
input: {
|
|
523
|
+
...extension.info?.input || {},
|
|
524
|
+
method
|
|
525
|
+
}
|
|
526
|
+
},
|
|
527
|
+
schema: {
|
|
528
|
+
...extension.schema || {},
|
|
529
|
+
properties: {
|
|
530
|
+
...extension.schema?.properties || {},
|
|
531
|
+
input: {
|
|
532
|
+
...extension.schema?.properties?.input || {},
|
|
533
|
+
properties: updatedInputProps,
|
|
534
|
+
required: [
|
|
535
|
+
...extension.schema?.properties?.input?.required || [],
|
|
536
|
+
...!(extension.schema?.properties?.input?.required || []).includes("method") ? ["method"] : []
|
|
537
|
+
]
|
|
538
|
+
}
|
|
539
|
+
}
|
|
540
|
+
}
|
|
541
|
+
};
|
|
542
|
+
const rawRoutePattern = transportContext.routePattern;
|
|
543
|
+
const routePattern = rawRoutePattern ? normalizeWildcardPattern(rawRoutePattern) : void 0;
|
|
544
|
+
const dynamicRoute = routePattern ? extractDynamicRouteInfo(routePattern, transportContext.adapter.getPath()) : null;
|
|
545
|
+
if (dynamicRoute) {
|
|
546
|
+
const inputSchemaProps = enrichedResult.schema?.properties?.input?.properties || {};
|
|
547
|
+
const hasPathParamsInSchema = "pathParams" in inputSchemaProps;
|
|
548
|
+
return {
|
|
549
|
+
...enrichedResult,
|
|
550
|
+
routeTemplate: dynamicRoute.routeTemplate,
|
|
551
|
+
info: {
|
|
552
|
+
...enrichedResult.info,
|
|
553
|
+
input: { ...enrichedResult.info.input, pathParams: dynamicRoute.pathParams }
|
|
554
|
+
},
|
|
555
|
+
...!hasPathParamsInSchema ? {
|
|
556
|
+
schema: {
|
|
557
|
+
...enrichedResult.schema,
|
|
558
|
+
properties: {
|
|
559
|
+
...enrichedResult.schema?.properties,
|
|
560
|
+
input: {
|
|
561
|
+
...enrichedResult.schema?.properties?.input,
|
|
562
|
+
properties: {
|
|
563
|
+
...inputSchemaProps,
|
|
564
|
+
pathParams: { type: "object" }
|
|
565
|
+
}
|
|
566
|
+
}
|
|
567
|
+
}
|
|
568
|
+
}
|
|
569
|
+
} : {}
|
|
570
|
+
};
|
|
571
|
+
}
|
|
572
|
+
return enrichedResult;
|
|
573
|
+
}
|
|
574
|
+
};
|
|
575
|
+
|
|
576
|
+
// src/bazaar/facilitator.ts
|
|
577
|
+
var import_node_url = require("url");
|
|
578
|
+
var import__ = __toESM(require("ajv/dist/2020.js"));
|
|
579
|
+
|
|
580
|
+
// src/bazaar/v1/facilitator.ts
|
|
581
|
+
function hasV1OutputSchema(obj) {
|
|
582
|
+
return obj !== null && typeof obj === "object" && "input" in obj && obj.input !== null && typeof obj.input === "object" && "type" in obj.input && obj.input.type === "http" && "method" in obj.input;
|
|
583
|
+
}
|
|
584
|
+
function isQueryMethod(method) {
|
|
585
|
+
const upperMethod = method.toUpperCase();
|
|
586
|
+
return upperMethod === "GET" || upperMethod === "HEAD" || upperMethod === "DELETE";
|
|
587
|
+
}
|
|
588
|
+
function isBodyMethod(method) {
|
|
589
|
+
const upperMethod = method.toUpperCase();
|
|
590
|
+
return upperMethod === "POST" || upperMethod === "PUT" || upperMethod === "PATCH";
|
|
591
|
+
}
|
|
592
|
+
function extractQueryParams(v1Input) {
|
|
593
|
+
if (v1Input.queryParams && typeof v1Input.queryParams === "object") {
|
|
594
|
+
return v1Input.queryParams;
|
|
595
|
+
}
|
|
596
|
+
if (v1Input.query_params && typeof v1Input.query_params === "object") {
|
|
597
|
+
return v1Input.query_params;
|
|
598
|
+
}
|
|
599
|
+
if (v1Input.query && typeof v1Input.query === "object") {
|
|
600
|
+
return v1Input.query;
|
|
601
|
+
}
|
|
602
|
+
if (v1Input.params && typeof v1Input.params === "object") {
|
|
603
|
+
return v1Input.params;
|
|
604
|
+
}
|
|
605
|
+
return void 0;
|
|
606
|
+
}
|
|
607
|
+
function extractBodyInfo(v1Input) {
|
|
608
|
+
let bodyType = "json";
|
|
609
|
+
const bodyTypeField = v1Input.bodyType || v1Input.body_type;
|
|
610
|
+
if (bodyTypeField && typeof bodyTypeField === "string") {
|
|
611
|
+
const type = bodyTypeField.toLowerCase();
|
|
612
|
+
if (type.includes("form") || type.includes("multipart")) {
|
|
613
|
+
bodyType = "form-data";
|
|
614
|
+
} else if (type.includes("text") || type.includes("plain")) {
|
|
615
|
+
bodyType = "text";
|
|
616
|
+
} else {
|
|
617
|
+
bodyType = "json";
|
|
618
|
+
}
|
|
619
|
+
}
|
|
620
|
+
let body = {};
|
|
621
|
+
if (v1Input.bodyFields && typeof v1Input.bodyFields === "object") {
|
|
622
|
+
body = v1Input.bodyFields;
|
|
623
|
+
} else if (v1Input.body_fields && v1Input.body_fields !== null && typeof v1Input.body_fields === "object") {
|
|
624
|
+
body = v1Input.body_fields;
|
|
625
|
+
} else if (v1Input.bodyParams && typeof v1Input.bodyParams === "object") {
|
|
626
|
+
body = v1Input.bodyParams;
|
|
627
|
+
} else if (v1Input.body && typeof v1Input.body === "object") {
|
|
628
|
+
body = v1Input.body;
|
|
629
|
+
} else if (v1Input.data && typeof v1Input.data === "object") {
|
|
630
|
+
body = v1Input.data;
|
|
631
|
+
} else if (v1Input.properties && typeof v1Input.properties === "object") {
|
|
632
|
+
body = v1Input.properties;
|
|
633
|
+
}
|
|
634
|
+
return { body, bodyType };
|
|
635
|
+
}
|
|
636
|
+
function extractDiscoveryInfoV1(paymentRequirements) {
|
|
637
|
+
const { outputSchema } = paymentRequirements;
|
|
638
|
+
if (!outputSchema || !hasV1OutputSchema(outputSchema)) {
|
|
639
|
+
return null;
|
|
640
|
+
}
|
|
641
|
+
const v1Input = outputSchema.input;
|
|
642
|
+
const isDiscoverable = v1Input.discoverable ?? true;
|
|
643
|
+
if (!isDiscoverable) {
|
|
644
|
+
return null;
|
|
645
|
+
}
|
|
646
|
+
const method = typeof v1Input.method === "string" ? v1Input.method.toUpperCase() : "";
|
|
647
|
+
const headersRaw = v1Input.headerFields || v1Input.header_fields || v1Input.headers;
|
|
648
|
+
const headers = headersRaw && typeof headersRaw === "object" ? headersRaw : void 0;
|
|
649
|
+
const output = outputSchema.output ? {
|
|
650
|
+
type: "json",
|
|
651
|
+
example: outputSchema.output
|
|
652
|
+
} : void 0;
|
|
653
|
+
if (isQueryMethod(method)) {
|
|
654
|
+
const queryParams = extractQueryParams(v1Input);
|
|
655
|
+
const discoveryInfo = {
|
|
656
|
+
input: {
|
|
657
|
+
type: "http",
|
|
658
|
+
method,
|
|
659
|
+
...queryParams ? { queryParams } : {},
|
|
660
|
+
...headers ? { headers } : {}
|
|
661
|
+
},
|
|
662
|
+
...output ? { output } : {}
|
|
663
|
+
};
|
|
664
|
+
return discoveryInfo;
|
|
665
|
+
} else if (isBodyMethod(method)) {
|
|
666
|
+
const { body, bodyType } = extractBodyInfo(v1Input);
|
|
667
|
+
const queryParams = extractQueryParams(v1Input);
|
|
668
|
+
const discoveryInfo = {
|
|
669
|
+
input: {
|
|
670
|
+
type: "http",
|
|
671
|
+
method,
|
|
672
|
+
bodyType,
|
|
673
|
+
body,
|
|
674
|
+
...queryParams ? { queryParams } : {},
|
|
675
|
+
...headers ? { headers } : {}
|
|
676
|
+
},
|
|
677
|
+
...output ? { output } : {}
|
|
678
|
+
};
|
|
679
|
+
return discoveryInfo;
|
|
680
|
+
}
|
|
681
|
+
return null;
|
|
682
|
+
}
|
|
683
|
+
function isDiscoverableV1(paymentRequirements) {
|
|
684
|
+
return extractDiscoveryInfoV1(paymentRequirements) !== null;
|
|
685
|
+
}
|
|
686
|
+
function extractResourceMetadataV1(paymentRequirements) {
|
|
687
|
+
return {
|
|
688
|
+
url: paymentRequirements.resource,
|
|
689
|
+
description: paymentRequirements.description,
|
|
690
|
+
mimeType: paymentRequirements.mimeType
|
|
691
|
+
};
|
|
692
|
+
}
|
|
693
|
+
function buildBazaarExtensionFromDiscoveryInfo(discoveryInfo) {
|
|
694
|
+
return {
|
|
695
|
+
info: discoveryInfo,
|
|
696
|
+
schema: {
|
|
697
|
+
$schema: "https://json-schema.org/draft/2020-12/schema",
|
|
698
|
+
type: "object",
|
|
699
|
+
properties: {
|
|
700
|
+
input: { type: "object" },
|
|
701
|
+
output: { type: "object" }
|
|
702
|
+
},
|
|
703
|
+
required: ["input"]
|
|
704
|
+
}
|
|
705
|
+
};
|
|
706
|
+
}
|
|
707
|
+
function buildV1CatalogExtensions(existingExtensions, discoveryInfo) {
|
|
708
|
+
if (existingExtensions?.[BAZAAR.key]) {
|
|
709
|
+
return existingExtensions;
|
|
710
|
+
}
|
|
711
|
+
const extensions = existingExtensions ? { ...existingExtensions } : {};
|
|
712
|
+
delete extensions.outputSchema;
|
|
713
|
+
extensions[BAZAAR.key] = buildBazaarExtensionFromDiscoveryInfo(discoveryInfo);
|
|
714
|
+
return extensions;
|
|
715
|
+
}
|
|
716
|
+
|
|
717
|
+
// src/bazaar/facilitator.ts
|
|
718
|
+
var ROUTE_TEMPLATE_REGEX = /^\/[a-zA-Z0-9_/:.\-~%]+$/;
|
|
719
|
+
function isValidRouteTemplate(value) {
|
|
720
|
+
if (!value) return false;
|
|
721
|
+
if (!ROUTE_TEMPLATE_REGEX.test(value)) return false;
|
|
722
|
+
let decoded;
|
|
723
|
+
try {
|
|
724
|
+
decoded = decodeURIComponent(value);
|
|
725
|
+
} catch {
|
|
726
|
+
return false;
|
|
727
|
+
}
|
|
728
|
+
if (decoded.includes("..")) return false;
|
|
729
|
+
if (decoded.includes("://")) return false;
|
|
730
|
+
return true;
|
|
731
|
+
}
|
|
732
|
+
function validateRouteTemplate(value) {
|
|
733
|
+
return isValidRouteTemplate(value) ? value : void 0;
|
|
734
|
+
}
|
|
735
|
+
var MAX_SERVICE_NAME_LEN = 32;
|
|
736
|
+
var MAX_TAG_LEN = 32;
|
|
737
|
+
var MAX_TAGS = 5;
|
|
738
|
+
var MAX_ICON_URL_LEN = 2048;
|
|
739
|
+
var CONTROL_CHAR_REGEX = /[\x00-\x1f\x7f]/;
|
|
740
|
+
var PRINTABLE_ASCII_REGEX = /^[\x20-\x7e]+$/;
|
|
741
|
+
var UNICODE_CONTROL_REGEX = /\p{Cc}/u;
|
|
742
|
+
var LOOPBACK_HOSTNAMES = /* @__PURE__ */ new Set([
|
|
743
|
+
"localhost",
|
|
744
|
+
"localhost.localdomain",
|
|
745
|
+
"ip6-localhost",
|
|
746
|
+
"ip6-loopback"
|
|
747
|
+
]);
|
|
748
|
+
var IPV4_REGEX = /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/;
|
|
749
|
+
var ALL_DIGITS_REGEX = /^\d+$/;
|
|
750
|
+
var HEX_LITERAL_REGEX = /^0x[0-9a-f]+$/i;
|
|
751
|
+
function isValidServiceName(value) {
|
|
752
|
+
if (typeof value !== "string") return false;
|
|
753
|
+
if (value.length === 0 || value.length > MAX_SERVICE_NAME_LEN) return false;
|
|
754
|
+
if (UNICODE_CONTROL_REGEX.test(value)) return false;
|
|
755
|
+
if (!PRINTABLE_ASCII_REGEX.test(value)) return false;
|
|
756
|
+
return true;
|
|
757
|
+
}
|
|
758
|
+
function sanitizeTags(value) {
|
|
759
|
+
if (!Array.isArray(value)) return void 0;
|
|
760
|
+
const out = [];
|
|
761
|
+
const seen = /* @__PURE__ */ new Set();
|
|
762
|
+
for (const entry of value) {
|
|
763
|
+
if (typeof entry !== "string") continue;
|
|
764
|
+
if (entry.length === 0 || entry.length > MAX_TAG_LEN) continue;
|
|
765
|
+
if (UNICODE_CONTROL_REGEX.test(entry)) continue;
|
|
766
|
+
if (!PRINTABLE_ASCII_REGEX.test(entry)) continue;
|
|
767
|
+
const key = entry.toLowerCase();
|
|
768
|
+
if (seen.has(key)) continue;
|
|
769
|
+
seen.add(key);
|
|
770
|
+
out.push(entry);
|
|
771
|
+
if (out.length === MAX_TAGS) break;
|
|
772
|
+
}
|
|
773
|
+
return out.length > 0 ? out : void 0;
|
|
774
|
+
}
|
|
775
|
+
function isValidIconUrl(value) {
|
|
776
|
+
if (typeof value !== "string") return false;
|
|
777
|
+
if (value.length === 0 || value.length > MAX_ICON_URL_LEN) return false;
|
|
778
|
+
if (CONTROL_CHAR_REGEX.test(value)) return false;
|
|
779
|
+
let parsed;
|
|
780
|
+
try {
|
|
781
|
+
parsed = new URL(value);
|
|
782
|
+
} catch {
|
|
783
|
+
return false;
|
|
784
|
+
}
|
|
785
|
+
if (parsed.protocol !== "http:" && parsed.protocol !== "https:") return false;
|
|
786
|
+
if (parsed.username !== "" || parsed.password !== "") return false;
|
|
787
|
+
if (parsed.host.startsWith("[")) return false;
|
|
788
|
+
let hostname;
|
|
789
|
+
try {
|
|
790
|
+
hostname = decodeURIComponent(parsed.hostname);
|
|
791
|
+
} catch {
|
|
792
|
+
return false;
|
|
793
|
+
}
|
|
794
|
+
const asciiHost = (0, import_node_url.domainToASCII)(hostname);
|
|
795
|
+
if (asciiHost === "") return false;
|
|
796
|
+
hostname = asciiHost.toLowerCase();
|
|
797
|
+
if (hostname === "") return false;
|
|
798
|
+
if (LOOPBACK_HOSTNAMES.has(hostname)) return false;
|
|
799
|
+
if (IPV4_REGEX.test(hostname)) return false;
|
|
800
|
+
if (ALL_DIGITS_REGEX.test(hostname)) return false;
|
|
801
|
+
if (HEX_LITERAL_REGEX.test(hostname)) return false;
|
|
802
|
+
return true;
|
|
803
|
+
}
|
|
804
|
+
function sanitizeResourceServiceMetadata(resource) {
|
|
805
|
+
if (!resource) return {};
|
|
806
|
+
const out = {};
|
|
807
|
+
if (isValidServiceName(resource.serviceName)) {
|
|
808
|
+
out.serviceName = resource.serviceName;
|
|
809
|
+
}
|
|
810
|
+
const tags = sanitizeTags(resource.tags);
|
|
811
|
+
if (tags) {
|
|
812
|
+
out.tags = tags;
|
|
813
|
+
}
|
|
814
|
+
if (isValidIconUrl(resource.iconUrl)) {
|
|
815
|
+
out.iconUrl = resource.iconUrl;
|
|
816
|
+
}
|
|
817
|
+
return out;
|
|
818
|
+
}
|
|
819
|
+
function validateDiscoveryExtension(extension) {
|
|
820
|
+
try {
|
|
821
|
+
const ajv = new import__.default({ strict: false, allErrors: true });
|
|
822
|
+
const validate = ajv.compile(extension.schema);
|
|
823
|
+
const valid = validate(extension.info);
|
|
824
|
+
if (valid) {
|
|
825
|
+
return { valid: true };
|
|
826
|
+
}
|
|
827
|
+
const errors = validate.errors?.map((err) => {
|
|
828
|
+
const path = err.instancePath || "(root)";
|
|
829
|
+
return `${path}: ${err.message}`;
|
|
830
|
+
}) || ["Unknown validation error"];
|
|
831
|
+
return { valid: false, errors };
|
|
832
|
+
} catch (error) {
|
|
833
|
+
return {
|
|
834
|
+
valid: false,
|
|
835
|
+
errors: [
|
|
836
|
+
`Schema validation failed: ${error instanceof Error ? error.message : String(error)}`
|
|
837
|
+
]
|
|
838
|
+
};
|
|
839
|
+
}
|
|
840
|
+
}
|
|
841
|
+
var VALID_QUERY_METHODS = /* @__PURE__ */ new Set(["GET", "HEAD", "DELETE"]);
|
|
842
|
+
var VALID_BODY_METHODS = /* @__PURE__ */ new Set(["POST", "PUT", "PATCH"]);
|
|
843
|
+
var VALID_METHODS = /* @__PURE__ */ new Set([...VALID_QUERY_METHODS, ...VALID_BODY_METHODS]);
|
|
844
|
+
var VALID_BODY_TYPES = /* @__PURE__ */ new Set(["json", "form-data", "text"]);
|
|
845
|
+
var VALID_MCP_TRANSPORTS = /* @__PURE__ */ new Set(["streamable-http", "sse"]);
|
|
846
|
+
function validateDiscoveryExtensionSpec(extension) {
|
|
847
|
+
const errors = [];
|
|
848
|
+
const info = extension.info;
|
|
849
|
+
if (!info || typeof info !== "object") {
|
|
850
|
+
return { valid: false, errors: ["Missing or invalid 'info' field"] };
|
|
851
|
+
}
|
|
852
|
+
const input = info.input;
|
|
853
|
+
if (!input || typeof input !== "object") {
|
|
854
|
+
return { valid: false, errors: ["Missing or invalid 'info.input' field"] };
|
|
855
|
+
}
|
|
856
|
+
const inputObj = input;
|
|
857
|
+
const inputType = inputObj.type;
|
|
858
|
+
if (inputType !== "http" && inputType !== "mcp") {
|
|
859
|
+
errors.push(`info.input.type must be "http" or "mcp", got "${String(inputType)}"`);
|
|
860
|
+
return { valid: false, errors };
|
|
861
|
+
}
|
|
862
|
+
if (inputType === "http") {
|
|
863
|
+
const method = inputObj.method;
|
|
864
|
+
if (method !== void 0 && !VALID_METHODS.has(method)) {
|
|
865
|
+
errors.push(
|
|
866
|
+
`info.input.method must be one of ${[...VALID_METHODS].join(", ")}, got "${String(method)}"`
|
|
867
|
+
);
|
|
868
|
+
}
|
|
869
|
+
const bodyType = inputObj.bodyType;
|
|
870
|
+
if (bodyType !== void 0) {
|
|
871
|
+
if (!VALID_BODY_TYPES.has(bodyType)) {
|
|
872
|
+
errors.push(
|
|
873
|
+
`info.input.bodyType must be one of ${[...VALID_BODY_TYPES].join(", ")}, got "${String(bodyType)}"`
|
|
874
|
+
);
|
|
875
|
+
}
|
|
876
|
+
if (method !== void 0 && !VALID_BODY_METHODS.has(method)) {
|
|
877
|
+
errors.push(
|
|
878
|
+
`info.input.bodyType is set but method "${String(method)}" is not a body method (POST, PUT, PATCH)`
|
|
879
|
+
);
|
|
880
|
+
}
|
|
881
|
+
}
|
|
882
|
+
}
|
|
883
|
+
if (inputType === "mcp") {
|
|
884
|
+
if (typeof inputObj.toolName !== "string" || inputObj.toolName.length === 0) {
|
|
885
|
+
errors.push(
|
|
886
|
+
"info.input.toolName is required and must be a non-empty string for MCP extensions"
|
|
887
|
+
);
|
|
888
|
+
}
|
|
889
|
+
if (!inputObj.inputSchema || typeof inputObj.inputSchema !== "object") {
|
|
890
|
+
errors.push("info.input.inputSchema is required and must be an object for MCP extensions");
|
|
891
|
+
}
|
|
892
|
+
const transport = inputObj.transport;
|
|
893
|
+
if (transport !== void 0 && !VALID_MCP_TRANSPORTS.has(transport)) {
|
|
894
|
+
errors.push(
|
|
895
|
+
`info.input.transport must be one of ${[...VALID_MCP_TRANSPORTS].join(", ")}, got "${String(transport)}"`
|
|
896
|
+
);
|
|
897
|
+
}
|
|
898
|
+
}
|
|
899
|
+
return errors.length === 0 ? { valid: true } : { valid: false, errors };
|
|
900
|
+
}
|
|
901
|
+
function extractDiscoveryInfo(paymentPayload, paymentRequirements, validate = true) {
|
|
902
|
+
let discoveryInfo = null;
|
|
903
|
+
let resourceUrl;
|
|
904
|
+
let routeTemplate;
|
|
905
|
+
if (paymentPayload.x402Version === 2) {
|
|
906
|
+
resourceUrl = paymentPayload.resource?.url ?? "";
|
|
907
|
+
if (paymentPayload.extensions) {
|
|
908
|
+
const bazaarExtension = paymentPayload.extensions[BAZAAR.key];
|
|
909
|
+
if (bazaarExtension && typeof bazaarExtension === "object") {
|
|
910
|
+
try {
|
|
911
|
+
const rawExt = bazaarExtension;
|
|
912
|
+
const rawTemplate = typeof rawExt.routeTemplate === "string" ? rawExt.routeTemplate : void 0;
|
|
913
|
+
if (isValidRouteTemplate(rawTemplate)) {
|
|
914
|
+
routeTemplate = rawTemplate;
|
|
915
|
+
}
|
|
916
|
+
const extension = bazaarExtension;
|
|
917
|
+
if (validate) {
|
|
918
|
+
const result = validateDiscoveryExtension(extension);
|
|
919
|
+
if (!result.valid) {
|
|
920
|
+
console.warn(
|
|
921
|
+
`V2 discovery extension validation failed: ${result.errors?.join(", ")}`
|
|
922
|
+
);
|
|
923
|
+
} else {
|
|
924
|
+
discoveryInfo = extension.info;
|
|
925
|
+
}
|
|
926
|
+
} else {
|
|
927
|
+
discoveryInfo = extension.info;
|
|
928
|
+
}
|
|
929
|
+
} catch (error) {
|
|
930
|
+
console.warn(`V2 discovery extension extraction failed: ${error}`);
|
|
931
|
+
}
|
|
932
|
+
}
|
|
933
|
+
}
|
|
934
|
+
} else if (paymentPayload.x402Version === 1) {
|
|
935
|
+
const requirementsV1 = paymentRequirements;
|
|
936
|
+
resourceUrl = requirementsV1.resource;
|
|
937
|
+
discoveryInfo = extractDiscoveryInfoV1(requirementsV1);
|
|
938
|
+
} else {
|
|
939
|
+
return null;
|
|
940
|
+
}
|
|
941
|
+
if (!discoveryInfo) {
|
|
942
|
+
return null;
|
|
943
|
+
}
|
|
944
|
+
const url = new URL(resourceUrl);
|
|
945
|
+
const canonicalUrl = routeTemplate ? `${url.origin}${routeTemplate}` : `${url.origin}${url.pathname}`;
|
|
946
|
+
let description;
|
|
947
|
+
let mimeType;
|
|
948
|
+
let serviceMetadata = {};
|
|
949
|
+
if (paymentPayload.x402Version === 2) {
|
|
950
|
+
description = paymentPayload.resource?.description;
|
|
951
|
+
mimeType = paymentPayload.resource?.mimeType;
|
|
952
|
+
serviceMetadata = sanitizeResourceServiceMetadata(paymentPayload.resource);
|
|
953
|
+
} else if (paymentPayload.x402Version === 1) {
|
|
954
|
+
const requirementsV1 = paymentRequirements;
|
|
955
|
+
description = requirementsV1.description;
|
|
956
|
+
mimeType = requirementsV1.mimeType;
|
|
957
|
+
}
|
|
958
|
+
let extensions;
|
|
959
|
+
if (paymentPayload.x402Version === 2) {
|
|
960
|
+
extensions = paymentPayload.extensions;
|
|
961
|
+
} else if (paymentPayload.x402Version === 1) {
|
|
962
|
+
extensions = buildV1CatalogExtensions(paymentPayload.extensions, discoveryInfo);
|
|
963
|
+
}
|
|
964
|
+
const base = {
|
|
965
|
+
resourceUrl: canonicalUrl,
|
|
966
|
+
description,
|
|
967
|
+
mimeType,
|
|
968
|
+
...serviceMetadata,
|
|
969
|
+
x402Version: paymentPayload.x402Version,
|
|
970
|
+
discoveryInfo,
|
|
971
|
+
extensions
|
|
972
|
+
};
|
|
973
|
+
if (discoveryInfo.input.type === "mcp") {
|
|
974
|
+
return { ...base, toolName: discoveryInfo.input.toolName };
|
|
975
|
+
}
|
|
976
|
+
return { ...base, routeTemplate, method: discoveryInfo.input.method };
|
|
977
|
+
}
|
|
978
|
+
function extractDiscoveryInfoFromExtension(extension, validate = true) {
|
|
979
|
+
if (validate) {
|
|
980
|
+
const result = validateDiscoveryExtension(extension);
|
|
981
|
+
if (!result.valid) {
|
|
982
|
+
throw new Error(
|
|
983
|
+
`Invalid discovery extension: ${result.errors?.join(", ") || "Unknown error"}`
|
|
984
|
+
);
|
|
985
|
+
}
|
|
986
|
+
}
|
|
987
|
+
return extension.info;
|
|
988
|
+
}
|
|
989
|
+
function validateAndExtract(extension) {
|
|
990
|
+
const result = validateDiscoveryExtension(extension);
|
|
991
|
+
if (result.valid) {
|
|
992
|
+
return {
|
|
993
|
+
valid: true,
|
|
994
|
+
info: extension.info
|
|
995
|
+
};
|
|
996
|
+
}
|
|
997
|
+
return {
|
|
998
|
+
valid: false,
|
|
999
|
+
errors: result.errors
|
|
1000
|
+
};
|
|
1001
|
+
}
|
|
1002
|
+
|
|
1003
|
+
// src/bazaar/startupValidation.ts
|
|
1004
|
+
var import_server = require("@bankofai/x402-core/server");
|
|
1005
|
+
var HTTP_VERB_RE = /^(GET|POST|PUT|PATCH|DELETE|HEAD)\b/i;
|
|
1006
|
+
function withSyntheticMethod(ext, pattern) {
|
|
1007
|
+
const info = ext.info;
|
|
1008
|
+
const input = info?.input;
|
|
1009
|
+
if (!input || typeof input.method === "string" && input.method) {
|
|
1010
|
+
return ext;
|
|
1011
|
+
}
|
|
1012
|
+
const verbMatch = pattern.match(HTTP_VERB_RE);
|
|
1013
|
+
const method = verbMatch ? verbMatch[1].toUpperCase() : input.body !== void 0 || input.bodyType !== void 0 ? "POST" : "GET";
|
|
1014
|
+
return { ...ext, info: { ...info, input: { ...input, method } } };
|
|
1015
|
+
}
|
|
1016
|
+
function validateBazaarRouteExtensions(routes) {
|
|
1017
|
+
const entries = "accepts" in routes ? [["*", routes]] : Object.entries(routes);
|
|
1018
|
+
for (const [pattern, config] of entries) {
|
|
1019
|
+
const bazaarExt = config.extensions?.["bazaar"];
|
|
1020
|
+
if (!bazaarExt) continue;
|
|
1021
|
+
if (typeof bazaarExt === "object" && bazaarExt !== null && "info" in bazaarExt && "schema" in bazaarExt) {
|
|
1022
|
+
const specResult = validateDiscoveryExtensionSpec(bazaarExt);
|
|
1023
|
+
if (!specResult.valid) {
|
|
1024
|
+
console.warn(
|
|
1025
|
+
`x402: Route "${pattern}" has an invalid bazaar extension: ${specResult.errors?.join(", ")}`
|
|
1026
|
+
);
|
|
1027
|
+
continue;
|
|
1028
|
+
}
|
|
1029
|
+
const extForSchema = withSyntheticMethod(bazaarExt, pattern);
|
|
1030
|
+
const schemaResult = validateDiscoveryExtension(
|
|
1031
|
+
extForSchema
|
|
1032
|
+
);
|
|
1033
|
+
if (!schemaResult.valid) {
|
|
1034
|
+
console.warn(
|
|
1035
|
+
`x402: Route "${pattern}" has an invalid bazaar extension: ${schemaResult.errors?.join(", ")}`
|
|
1036
|
+
);
|
|
1037
|
+
}
|
|
1038
|
+
} else {
|
|
1039
|
+
console.warn(
|
|
1040
|
+
`x402: Route "${pattern}" declares a bazaar extension but it is malformed (expected an object with "info" and "schema" fields)`
|
|
1041
|
+
);
|
|
1042
|
+
}
|
|
1043
|
+
}
|
|
1044
|
+
}
|
|
1045
|
+
|
|
1046
|
+
// src/bazaar/facilitatorClient.ts
|
|
1047
|
+
function withBazaar(client) {
|
|
1048
|
+
const existingExtensions = client.extensions ?? {};
|
|
1049
|
+
const extended = client;
|
|
1050
|
+
extended.extensions = {
|
|
1051
|
+
...existingExtensions,
|
|
1052
|
+
bazaar: {
|
|
1053
|
+
async listResources(params) {
|
|
1054
|
+
let headers = {
|
|
1055
|
+
"Content-Type": "application/json"
|
|
1056
|
+
};
|
|
1057
|
+
const authHeaders = await client.createAuthHeaders("bazaar");
|
|
1058
|
+
headers = { ...headers, ...authHeaders.headers };
|
|
1059
|
+
const queryParams = new URLSearchParams();
|
|
1060
|
+
if (params?.type !== void 0) {
|
|
1061
|
+
queryParams.set("type", params.type);
|
|
1062
|
+
}
|
|
1063
|
+
if (params?.payTo !== void 0) {
|
|
1064
|
+
queryParams.set("payTo", params.payTo);
|
|
1065
|
+
}
|
|
1066
|
+
if (params?.scheme !== void 0) {
|
|
1067
|
+
queryParams.set("scheme", params.scheme);
|
|
1068
|
+
}
|
|
1069
|
+
if (params?.network !== void 0) {
|
|
1070
|
+
queryParams.set("network", params.network);
|
|
1071
|
+
}
|
|
1072
|
+
if (params?.extensions !== void 0) {
|
|
1073
|
+
queryParams.set("extensions", params.extensions);
|
|
1074
|
+
}
|
|
1075
|
+
if (params?.limit !== void 0) {
|
|
1076
|
+
queryParams.set("limit", params.limit.toString());
|
|
1077
|
+
}
|
|
1078
|
+
if (params?.offset !== void 0) {
|
|
1079
|
+
queryParams.set("offset", params.offset.toString());
|
|
1080
|
+
}
|
|
1081
|
+
const queryString = queryParams.toString();
|
|
1082
|
+
const endpoint = `${client.url}/discovery/resources${queryString ? `?${queryString}` : ""}`;
|
|
1083
|
+
const response = await fetch(endpoint, {
|
|
1084
|
+
method: "GET",
|
|
1085
|
+
headers
|
|
1086
|
+
});
|
|
1087
|
+
if (!response.ok) {
|
|
1088
|
+
const errorText = await response.text().catch(() => response.statusText);
|
|
1089
|
+
throw new Error(
|
|
1090
|
+
`Facilitator listDiscoveryResources failed (${response.status}): ${errorText}`
|
|
1091
|
+
);
|
|
1092
|
+
}
|
|
1093
|
+
return await response.json();
|
|
1094
|
+
},
|
|
1095
|
+
async search(params) {
|
|
1096
|
+
let headers = {
|
|
1097
|
+
"Content-Type": "application/json"
|
|
1098
|
+
};
|
|
1099
|
+
const authHeaders = await client.createAuthHeaders("bazaar");
|
|
1100
|
+
headers = { ...headers, ...authHeaders.headers };
|
|
1101
|
+
const queryParams = new URLSearchParams();
|
|
1102
|
+
queryParams.set("query", params.query);
|
|
1103
|
+
if (params.type !== void 0) {
|
|
1104
|
+
queryParams.set("type", params.type);
|
|
1105
|
+
}
|
|
1106
|
+
if (params.payTo !== void 0) {
|
|
1107
|
+
queryParams.set("payTo", params.payTo);
|
|
1108
|
+
}
|
|
1109
|
+
if (params.scheme !== void 0) {
|
|
1110
|
+
queryParams.set("scheme", params.scheme);
|
|
1111
|
+
}
|
|
1112
|
+
if (params.network !== void 0) {
|
|
1113
|
+
queryParams.set("network", params.network);
|
|
1114
|
+
}
|
|
1115
|
+
if (params.extensions !== void 0) {
|
|
1116
|
+
queryParams.set("extensions", params.extensions);
|
|
1117
|
+
}
|
|
1118
|
+
if (params.limit !== void 0) {
|
|
1119
|
+
queryParams.set("limit", params.limit.toString());
|
|
1120
|
+
}
|
|
1121
|
+
if (params.cursor !== void 0) {
|
|
1122
|
+
queryParams.set("cursor", params.cursor);
|
|
1123
|
+
}
|
|
1124
|
+
const endpoint = `${client.url}/discovery/search?${queryParams.toString()}`;
|
|
1125
|
+
const response = await fetch(endpoint, {
|
|
1126
|
+
method: "GET",
|
|
1127
|
+
headers
|
|
1128
|
+
});
|
|
1129
|
+
if (!response.ok) {
|
|
1130
|
+
const errorText = await response.text().catch(() => response.statusText);
|
|
1131
|
+
throw new Error(
|
|
1132
|
+
`Facilitator searchDiscoveryResources failed (${response.status}): ${errorText}`
|
|
1133
|
+
);
|
|
1134
|
+
}
|
|
1135
|
+
return await response.json();
|
|
1136
|
+
}
|
|
1137
|
+
}
|
|
1138
|
+
};
|
|
1139
|
+
return extended;
|
|
1140
|
+
}
|
|
1141
|
+
|
|
1142
|
+
// src/sign-in-with-x/types.ts
|
|
1143
|
+
var import_zod = require("zod");
|
|
1144
|
+
var SIGN_IN_WITH_X = "sign-in-with-x";
|
|
1145
|
+
var SIWxPayloadSchema = import_zod.z.object({
|
|
1146
|
+
domain: import_zod.z.string(),
|
|
1147
|
+
address: import_zod.z.string(),
|
|
1148
|
+
statement: import_zod.z.string().optional(),
|
|
1149
|
+
uri: import_zod.z.string(),
|
|
1150
|
+
version: import_zod.z.string(),
|
|
1151
|
+
chainId: import_zod.z.string(),
|
|
1152
|
+
type: import_zod.z.enum(["eip191", "ed25519"]),
|
|
1153
|
+
nonce: import_zod.z.string(),
|
|
1154
|
+
issuedAt: import_zod.z.string(),
|
|
1155
|
+
expirationTime: import_zod.z.string().optional(),
|
|
1156
|
+
notBefore: import_zod.z.string().optional(),
|
|
1157
|
+
requestId: import_zod.z.string().optional(),
|
|
1158
|
+
resources: import_zod.z.array(import_zod.z.string()).optional(),
|
|
1159
|
+
signatureScheme: import_zod.z.enum(["eip191", "eip1271", "eip6492", "siws"]).optional(),
|
|
1160
|
+
signature: import_zod.z.string()
|
|
1161
|
+
});
|
|
1162
|
+
|
|
1163
|
+
// src/sign-in-with-x/solana.ts
|
|
1164
|
+
var import_base = require("@scure/base");
|
|
1165
|
+
var import_tweetnacl = __toESM(require("tweetnacl"));
|
|
1166
|
+
var SOLANA_MAINNET = "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp";
|
|
1167
|
+
var SOLANA_DEVNET = "solana:EtWTRABZaYq6iMfeYKouRu166VU2xqa1";
|
|
1168
|
+
var SOLANA_TESTNET = "solana:4uhcVJyU9pJkvQyS88uRDiswHXSCkY3z";
|
|
1169
|
+
function extractSolanaChainReference(chainId) {
|
|
1170
|
+
const [, reference] = chainId.split(":");
|
|
1171
|
+
return reference;
|
|
1172
|
+
}
|
|
1173
|
+
function formatSIWSMessage(info, address) {
|
|
1174
|
+
const lines = [
|
|
1175
|
+
`${info.domain} wants you to sign in with your Solana account:`,
|
|
1176
|
+
address,
|
|
1177
|
+
""
|
|
1178
|
+
];
|
|
1179
|
+
if (info.statement) {
|
|
1180
|
+
lines.push(info.statement, "");
|
|
1181
|
+
}
|
|
1182
|
+
lines.push(
|
|
1183
|
+
`URI: ${info.uri}`,
|
|
1184
|
+
`Version: ${info.version}`,
|
|
1185
|
+
`Chain ID: ${extractSolanaChainReference(info.chainId)}`,
|
|
1186
|
+
`Nonce: ${info.nonce}`,
|
|
1187
|
+
`Issued At: ${info.issuedAt}`
|
|
1188
|
+
);
|
|
1189
|
+
if (info.expirationTime) {
|
|
1190
|
+
lines.push(`Expiration Time: ${info.expirationTime}`);
|
|
1191
|
+
}
|
|
1192
|
+
if (info.notBefore) {
|
|
1193
|
+
lines.push(`Not Before: ${info.notBefore}`);
|
|
1194
|
+
}
|
|
1195
|
+
if (info.requestId) {
|
|
1196
|
+
lines.push(`Request ID: ${info.requestId}`);
|
|
1197
|
+
}
|
|
1198
|
+
if (info.resources && info.resources.length > 0) {
|
|
1199
|
+
lines.push("Resources:");
|
|
1200
|
+
for (const resource of info.resources) {
|
|
1201
|
+
lines.push(`- ${resource}`);
|
|
1202
|
+
}
|
|
1203
|
+
}
|
|
1204
|
+
return lines.join("\n");
|
|
1205
|
+
}
|
|
1206
|
+
function verifySolanaSignature(message, signature, publicKey) {
|
|
1207
|
+
const messageBytes = new TextEncoder().encode(message);
|
|
1208
|
+
return import_tweetnacl.default.sign.detached.verify(messageBytes, signature, publicKey);
|
|
1209
|
+
}
|
|
1210
|
+
function decodeBase58(encoded) {
|
|
1211
|
+
return import_base.base58.decode(encoded);
|
|
1212
|
+
}
|
|
1213
|
+
function encodeBase58(bytes) {
|
|
1214
|
+
return import_base.base58.encode(bytes);
|
|
1215
|
+
}
|
|
1216
|
+
function isSolanaSigner(signer) {
|
|
1217
|
+
if ("signMessages" in signer && typeof signer.signMessages === "function") {
|
|
1218
|
+
return true;
|
|
1219
|
+
}
|
|
1220
|
+
if ("publicKey" in signer && signer.publicKey) {
|
|
1221
|
+
const pk = signer.publicKey;
|
|
1222
|
+
if (typeof pk === "object" && pk !== null && "toBase58" in pk) {
|
|
1223
|
+
return true;
|
|
1224
|
+
}
|
|
1225
|
+
if (typeof pk === "string" && !pk.startsWith("0x")) {
|
|
1226
|
+
return true;
|
|
1227
|
+
}
|
|
1228
|
+
}
|
|
1229
|
+
return false;
|
|
1230
|
+
}
|
|
1231
|
+
|
|
1232
|
+
// src/sign-in-with-x/schema.ts
|
|
1233
|
+
function buildSIWxSchema() {
|
|
1234
|
+
return {
|
|
1235
|
+
$schema: "https://json-schema.org/draft/2020-12/schema",
|
|
1236
|
+
type: "object",
|
|
1237
|
+
properties: {
|
|
1238
|
+
domain: { type: "string" },
|
|
1239
|
+
address: { type: "string" },
|
|
1240
|
+
statement: { type: "string" },
|
|
1241
|
+
uri: { type: "string", format: "uri" },
|
|
1242
|
+
version: { type: "string" },
|
|
1243
|
+
chainId: { type: "string" },
|
|
1244
|
+
type: { type: "string" },
|
|
1245
|
+
nonce: { type: "string" },
|
|
1246
|
+
issuedAt: { type: "string", format: "date-time" },
|
|
1247
|
+
expirationTime: { type: "string", format: "date-time" },
|
|
1248
|
+
notBefore: { type: "string", format: "date-time" },
|
|
1249
|
+
requestId: { type: "string" },
|
|
1250
|
+
resources: { type: "array", items: { type: "string", format: "uri" } },
|
|
1251
|
+
signature: { type: "string" }
|
|
1252
|
+
},
|
|
1253
|
+
required: [
|
|
1254
|
+
"domain",
|
|
1255
|
+
"address",
|
|
1256
|
+
"uri",
|
|
1257
|
+
"version",
|
|
1258
|
+
"chainId",
|
|
1259
|
+
"type",
|
|
1260
|
+
"nonce",
|
|
1261
|
+
"issuedAt",
|
|
1262
|
+
"signature"
|
|
1263
|
+
]
|
|
1264
|
+
};
|
|
1265
|
+
}
|
|
1266
|
+
|
|
1267
|
+
// src/sign-in-with-x/declare.ts
|
|
1268
|
+
function getSignatureType(network) {
|
|
1269
|
+
return network.startsWith("solana:") ? "ed25519" : "eip191";
|
|
1270
|
+
}
|
|
1271
|
+
function declareSIWxExtension(options = {}) {
|
|
1272
|
+
const info = {
|
|
1273
|
+
version: options.version ?? "1"
|
|
1274
|
+
};
|
|
1275
|
+
if (options.domain) {
|
|
1276
|
+
info.domain = options.domain;
|
|
1277
|
+
}
|
|
1278
|
+
if (options.resourceUri) {
|
|
1279
|
+
info.uri = options.resourceUri;
|
|
1280
|
+
info.resources = [options.resourceUri];
|
|
1281
|
+
}
|
|
1282
|
+
if (options.statement) {
|
|
1283
|
+
info.statement = options.statement;
|
|
1284
|
+
}
|
|
1285
|
+
let supportedChains = [];
|
|
1286
|
+
if (options.network) {
|
|
1287
|
+
const networks = Array.isArray(options.network) ? options.network : [options.network];
|
|
1288
|
+
supportedChains = networks.map((network) => ({
|
|
1289
|
+
chainId: network,
|
|
1290
|
+
type: getSignatureType(network)
|
|
1291
|
+
}));
|
|
1292
|
+
}
|
|
1293
|
+
const declaration = {
|
|
1294
|
+
info,
|
|
1295
|
+
supportedChains,
|
|
1296
|
+
schema: buildSIWxSchema(),
|
|
1297
|
+
_options: options
|
|
1298
|
+
};
|
|
1299
|
+
return { [SIGN_IN_WITH_X]: declaration };
|
|
1300
|
+
}
|
|
1301
|
+
|
|
1302
|
+
// src/sign-in-with-x/parse.ts
|
|
1303
|
+
var import_utils = require("@bankofai/x402-core/utils");
|
|
1304
|
+
function parseSIWxHeader(header) {
|
|
1305
|
+
if (!import_utils.Base64EncodedRegex.test(header)) {
|
|
1306
|
+
throw new Error("Invalid SIWX header: not valid base64");
|
|
1307
|
+
}
|
|
1308
|
+
const jsonStr = (0, import_utils.safeBase64Decode)(header);
|
|
1309
|
+
let rawPayload;
|
|
1310
|
+
try {
|
|
1311
|
+
rawPayload = JSON.parse(jsonStr);
|
|
1312
|
+
} catch (error) {
|
|
1313
|
+
if (error instanceof SyntaxError) {
|
|
1314
|
+
throw new Error("Invalid SIWX header: not valid JSON");
|
|
1315
|
+
}
|
|
1316
|
+
throw error;
|
|
1317
|
+
}
|
|
1318
|
+
const parsed = SIWxPayloadSchema.safeParse(rawPayload);
|
|
1319
|
+
if (!parsed.success) {
|
|
1320
|
+
const issues = parsed.error.issues.map((i) => `${i.path.join(".")}: ${i.message}`).join(", ");
|
|
1321
|
+
throw new Error(`Invalid SIWX header: ${issues}`);
|
|
1322
|
+
}
|
|
1323
|
+
return parsed.data;
|
|
1324
|
+
}
|
|
1325
|
+
|
|
1326
|
+
// src/sign-in-with-x/validate.ts
|
|
1327
|
+
var DEFAULT_MAX_AGE_MS = 5 * 60 * 1e3;
|
|
1328
|
+
async function validateSIWxMessage(message, expectedResourceUri, options = {}) {
|
|
1329
|
+
const expectedUrl = new URL(expectedResourceUri);
|
|
1330
|
+
const maxAge = options.maxAge ?? DEFAULT_MAX_AGE_MS;
|
|
1331
|
+
if (message.domain !== expectedUrl.hostname) {
|
|
1332
|
+
return {
|
|
1333
|
+
valid: false,
|
|
1334
|
+
error: `Domain mismatch: expected "${expectedUrl.hostname}", got "${message.domain}"`
|
|
1335
|
+
};
|
|
1336
|
+
}
|
|
1337
|
+
if (!message.uri.startsWith(expectedUrl.origin)) {
|
|
1338
|
+
return {
|
|
1339
|
+
valid: false,
|
|
1340
|
+
error: `URI mismatch: expected origin "${expectedUrl.origin}", got "${message.uri}"`
|
|
1341
|
+
};
|
|
1342
|
+
}
|
|
1343
|
+
const issuedAt = new Date(message.issuedAt);
|
|
1344
|
+
if (isNaN(issuedAt.getTime())) {
|
|
1345
|
+
return {
|
|
1346
|
+
valid: false,
|
|
1347
|
+
error: "Invalid issuedAt timestamp"
|
|
1348
|
+
};
|
|
1349
|
+
}
|
|
1350
|
+
const age = Date.now() - issuedAt.getTime();
|
|
1351
|
+
if (age > maxAge) {
|
|
1352
|
+
return {
|
|
1353
|
+
valid: false,
|
|
1354
|
+
error: `Message too old: ${Math.round(age / 1e3)}s exceeds ${maxAge / 1e3}s limit`
|
|
1355
|
+
};
|
|
1356
|
+
}
|
|
1357
|
+
if (age < 0) {
|
|
1358
|
+
return {
|
|
1359
|
+
valid: false,
|
|
1360
|
+
error: "issuedAt is in the future"
|
|
1361
|
+
};
|
|
1362
|
+
}
|
|
1363
|
+
if (message.expirationTime) {
|
|
1364
|
+
const expiration = new Date(message.expirationTime);
|
|
1365
|
+
if (isNaN(expiration.getTime())) {
|
|
1366
|
+
return {
|
|
1367
|
+
valid: false,
|
|
1368
|
+
error: "Invalid expirationTime timestamp"
|
|
1369
|
+
};
|
|
1370
|
+
}
|
|
1371
|
+
if (expiration < /* @__PURE__ */ new Date()) {
|
|
1372
|
+
return {
|
|
1373
|
+
valid: false,
|
|
1374
|
+
error: "Message expired"
|
|
1375
|
+
};
|
|
1376
|
+
}
|
|
1377
|
+
}
|
|
1378
|
+
if (message.notBefore) {
|
|
1379
|
+
const notBefore = new Date(message.notBefore);
|
|
1380
|
+
if (isNaN(notBefore.getTime())) {
|
|
1381
|
+
return {
|
|
1382
|
+
valid: false,
|
|
1383
|
+
error: "Invalid notBefore timestamp"
|
|
1384
|
+
};
|
|
1385
|
+
}
|
|
1386
|
+
if (/* @__PURE__ */ new Date() < notBefore) {
|
|
1387
|
+
return {
|
|
1388
|
+
valid: false,
|
|
1389
|
+
error: "Message not yet valid (notBefore is in the future)"
|
|
1390
|
+
};
|
|
1391
|
+
}
|
|
1392
|
+
}
|
|
1393
|
+
if (options.checkNonce) {
|
|
1394
|
+
const nonceValid = await options.checkNonce(message.nonce);
|
|
1395
|
+
if (!nonceValid) {
|
|
1396
|
+
return {
|
|
1397
|
+
valid: false,
|
|
1398
|
+
error: "Nonce validation failed (possible replay attack)"
|
|
1399
|
+
};
|
|
1400
|
+
}
|
|
1401
|
+
}
|
|
1402
|
+
return { valid: true };
|
|
1403
|
+
}
|
|
1404
|
+
|
|
1405
|
+
// src/sign-in-with-x/evm.ts
|
|
1406
|
+
var import_viem = require("viem");
|
|
1407
|
+
var import_siwe = require("@signinwithethereum/siwe");
|
|
1408
|
+
function extractEVMChainId(chainId) {
|
|
1409
|
+
const match = /^eip155:(\d+)$/.exec(chainId);
|
|
1410
|
+
if (!match) {
|
|
1411
|
+
throw new Error(`Invalid EVM chainId format: ${chainId}. Expected eip155:<number>`);
|
|
1412
|
+
}
|
|
1413
|
+
return parseInt(match[1], 10);
|
|
1414
|
+
}
|
|
1415
|
+
function formatSIWEMessage(info, address) {
|
|
1416
|
+
const numericChainId = extractEVMChainId(info.chainId);
|
|
1417
|
+
const siweMessage = new import_siwe.SiweMessage({
|
|
1418
|
+
domain: info.domain,
|
|
1419
|
+
address,
|
|
1420
|
+
statement: info.statement,
|
|
1421
|
+
uri: info.uri,
|
|
1422
|
+
version: info.version,
|
|
1423
|
+
chainId: numericChainId,
|
|
1424
|
+
nonce: info.nonce,
|
|
1425
|
+
issuedAt: info.issuedAt,
|
|
1426
|
+
expirationTime: info.expirationTime,
|
|
1427
|
+
notBefore: info.notBefore,
|
|
1428
|
+
requestId: info.requestId,
|
|
1429
|
+
resources: info.resources
|
|
1430
|
+
});
|
|
1431
|
+
return siweMessage.prepareMessage();
|
|
1432
|
+
}
|
|
1433
|
+
async function verifyEVMSignature(message, address, signature, verifier) {
|
|
1434
|
+
const args = {
|
|
1435
|
+
address,
|
|
1436
|
+
message,
|
|
1437
|
+
signature
|
|
1438
|
+
};
|
|
1439
|
+
if (verifier) {
|
|
1440
|
+
return verifier(args);
|
|
1441
|
+
}
|
|
1442
|
+
return (0, import_viem.verifyMessage)(args);
|
|
1443
|
+
}
|
|
1444
|
+
function isEVMSigner(signer) {
|
|
1445
|
+
if ("signMessages" in signer && typeof signer.signMessages === "function") {
|
|
1446
|
+
return false;
|
|
1447
|
+
}
|
|
1448
|
+
if ("publicKey" in signer && signer.publicKey) {
|
|
1449
|
+
const pk = signer.publicKey;
|
|
1450
|
+
if (typeof pk === "object" && pk !== null && "toBase58" in pk) {
|
|
1451
|
+
return false;
|
|
1452
|
+
}
|
|
1453
|
+
if (typeof pk === "string" && !pk.startsWith("0x")) {
|
|
1454
|
+
return false;
|
|
1455
|
+
}
|
|
1456
|
+
}
|
|
1457
|
+
if ("account" in signer && signer.account && typeof signer.account === "object") {
|
|
1458
|
+
const account = signer.account;
|
|
1459
|
+
if (account.address && account.address.startsWith("0x")) {
|
|
1460
|
+
return true;
|
|
1461
|
+
}
|
|
1462
|
+
}
|
|
1463
|
+
if ("address" in signer && typeof signer.address === "string" && signer.address.startsWith("0x")) {
|
|
1464
|
+
return true;
|
|
1465
|
+
}
|
|
1466
|
+
return false;
|
|
1467
|
+
}
|
|
1468
|
+
|
|
1469
|
+
// src/sign-in-with-x/verify.ts
|
|
1470
|
+
async function verifySIWxSignature(payload, options) {
|
|
1471
|
+
try {
|
|
1472
|
+
if (payload.chainId.startsWith("eip155:")) {
|
|
1473
|
+
return verifyEVMPayload(payload, options?.evmVerifier);
|
|
1474
|
+
}
|
|
1475
|
+
if (payload.chainId.startsWith("solana:")) {
|
|
1476
|
+
return verifySolanaPayload(payload);
|
|
1477
|
+
}
|
|
1478
|
+
return {
|
|
1479
|
+
valid: false,
|
|
1480
|
+
error: `Unsupported chain namespace: ${payload.chainId}. Supported: eip155:* (EVM), solana:* (Solana)`
|
|
1481
|
+
};
|
|
1482
|
+
} catch (error) {
|
|
1483
|
+
return {
|
|
1484
|
+
valid: false,
|
|
1485
|
+
error: error instanceof Error ? error.message : "Verification failed"
|
|
1486
|
+
};
|
|
1487
|
+
}
|
|
1488
|
+
}
|
|
1489
|
+
async function verifyEVMPayload(payload, verifier) {
|
|
1490
|
+
const message = formatSIWEMessage(
|
|
1491
|
+
{
|
|
1492
|
+
domain: payload.domain,
|
|
1493
|
+
uri: payload.uri,
|
|
1494
|
+
statement: payload.statement,
|
|
1495
|
+
version: payload.version,
|
|
1496
|
+
chainId: payload.chainId,
|
|
1497
|
+
type: payload.type,
|
|
1498
|
+
nonce: payload.nonce,
|
|
1499
|
+
issuedAt: payload.issuedAt,
|
|
1500
|
+
expirationTime: payload.expirationTime,
|
|
1501
|
+
notBefore: payload.notBefore,
|
|
1502
|
+
requestId: payload.requestId,
|
|
1503
|
+
resources: payload.resources
|
|
1504
|
+
},
|
|
1505
|
+
payload.address
|
|
1506
|
+
);
|
|
1507
|
+
try {
|
|
1508
|
+
const valid = await verifyEVMSignature(message, payload.address, payload.signature, verifier);
|
|
1509
|
+
if (!valid) {
|
|
1510
|
+
return {
|
|
1511
|
+
valid: false,
|
|
1512
|
+
error: "Signature verification failed"
|
|
1513
|
+
};
|
|
1514
|
+
}
|
|
1515
|
+
return {
|
|
1516
|
+
valid: true,
|
|
1517
|
+
address: payload.address
|
|
1518
|
+
};
|
|
1519
|
+
} catch (error) {
|
|
1520
|
+
return {
|
|
1521
|
+
valid: false,
|
|
1522
|
+
error: error instanceof Error ? error.message : "Signature verification failed"
|
|
1523
|
+
};
|
|
1524
|
+
}
|
|
1525
|
+
}
|
|
1526
|
+
function verifySolanaPayload(payload) {
|
|
1527
|
+
const message = formatSIWSMessage(
|
|
1528
|
+
{
|
|
1529
|
+
domain: payload.domain,
|
|
1530
|
+
uri: payload.uri,
|
|
1531
|
+
statement: payload.statement,
|
|
1532
|
+
version: payload.version,
|
|
1533
|
+
chainId: payload.chainId,
|
|
1534
|
+
type: payload.type,
|
|
1535
|
+
nonce: payload.nonce,
|
|
1536
|
+
issuedAt: payload.issuedAt,
|
|
1537
|
+
expirationTime: payload.expirationTime,
|
|
1538
|
+
notBefore: payload.notBefore,
|
|
1539
|
+
requestId: payload.requestId,
|
|
1540
|
+
resources: payload.resources
|
|
1541
|
+
},
|
|
1542
|
+
payload.address
|
|
1543
|
+
);
|
|
1544
|
+
let signature;
|
|
1545
|
+
let publicKey;
|
|
1546
|
+
try {
|
|
1547
|
+
signature = decodeBase58(payload.signature);
|
|
1548
|
+
publicKey = decodeBase58(payload.address);
|
|
1549
|
+
} catch (error) {
|
|
1550
|
+
return {
|
|
1551
|
+
valid: false,
|
|
1552
|
+
error: `Invalid Base58 encoding: ${error instanceof Error ? error.message : "decode failed"}`
|
|
1553
|
+
};
|
|
1554
|
+
}
|
|
1555
|
+
if (signature.length !== 64) {
|
|
1556
|
+
return {
|
|
1557
|
+
valid: false,
|
|
1558
|
+
error: `Invalid signature length: expected 64 bytes, got ${signature.length}`
|
|
1559
|
+
};
|
|
1560
|
+
}
|
|
1561
|
+
if (publicKey.length !== 32) {
|
|
1562
|
+
return {
|
|
1563
|
+
valid: false,
|
|
1564
|
+
error: `Invalid public key length: expected 32 bytes, got ${publicKey.length}`
|
|
1565
|
+
};
|
|
1566
|
+
}
|
|
1567
|
+
const valid = verifySolanaSignature(message, signature, publicKey);
|
|
1568
|
+
if (!valid) {
|
|
1569
|
+
return {
|
|
1570
|
+
valid: false,
|
|
1571
|
+
error: "Solana signature verification failed"
|
|
1572
|
+
};
|
|
1573
|
+
}
|
|
1574
|
+
return {
|
|
1575
|
+
valid: true,
|
|
1576
|
+
address: payload.address
|
|
1577
|
+
};
|
|
1578
|
+
}
|
|
1579
|
+
|
|
1580
|
+
// src/sign-in-with-x/sign.ts
|
|
1581
|
+
function getEVMAddress(signer) {
|
|
1582
|
+
if (signer.account?.address) {
|
|
1583
|
+
return signer.account.address;
|
|
1584
|
+
}
|
|
1585
|
+
if (signer.address) {
|
|
1586
|
+
return signer.address;
|
|
1587
|
+
}
|
|
1588
|
+
throw new Error("EVM signer missing address");
|
|
1589
|
+
}
|
|
1590
|
+
function getSolanaAddress(signer) {
|
|
1591
|
+
if ("address" in signer && signer.address) {
|
|
1592
|
+
return signer.address;
|
|
1593
|
+
}
|
|
1594
|
+
if ("publicKey" in signer) {
|
|
1595
|
+
const pk = signer.publicKey;
|
|
1596
|
+
return typeof pk === "string" ? pk : pk.toBase58();
|
|
1597
|
+
}
|
|
1598
|
+
throw new Error("Solana signer missing address or publicKey");
|
|
1599
|
+
}
|
|
1600
|
+
async function signEVMMessage(message, signer) {
|
|
1601
|
+
if (signer.account) {
|
|
1602
|
+
return signer.signMessage({ message, account: signer.account });
|
|
1603
|
+
}
|
|
1604
|
+
return signer.signMessage({ message });
|
|
1605
|
+
}
|
|
1606
|
+
async function signSolanaMessage(message, signer) {
|
|
1607
|
+
const messageBytes = new TextEncoder().encode(message);
|
|
1608
|
+
if ("signMessages" in signer) {
|
|
1609
|
+
const results = await signer.signMessages([{ content: messageBytes, signatures: {} }]);
|
|
1610
|
+
const sigDict = results[0];
|
|
1611
|
+
const signatureBytes = Object.values(sigDict)[0];
|
|
1612
|
+
return encodeBase58(signatureBytes);
|
|
1613
|
+
}
|
|
1614
|
+
if ("signMessage" in signer) {
|
|
1615
|
+
const signatureBytes = await signer.signMessage(messageBytes);
|
|
1616
|
+
return encodeBase58(signatureBytes);
|
|
1617
|
+
}
|
|
1618
|
+
throw new Error("Solana signer missing signMessage or signMessages method");
|
|
1619
|
+
}
|
|
1620
|
+
|
|
1621
|
+
// src/sign-in-with-x/message.ts
|
|
1622
|
+
function createSIWxMessage(serverInfo, address) {
|
|
1623
|
+
if (serverInfo.chainId.startsWith("eip155:")) {
|
|
1624
|
+
return formatSIWEMessage(serverInfo, address);
|
|
1625
|
+
}
|
|
1626
|
+
if (serverInfo.chainId.startsWith("solana:")) {
|
|
1627
|
+
return formatSIWSMessage(serverInfo, address);
|
|
1628
|
+
}
|
|
1629
|
+
throw new Error(
|
|
1630
|
+
`Unsupported chain namespace: ${serverInfo.chainId}. Supported: eip155:* (EVM), solana:* (Solana)`
|
|
1631
|
+
);
|
|
1632
|
+
}
|
|
1633
|
+
|
|
1634
|
+
// src/sign-in-with-x/client.ts
|
|
1635
|
+
async function createSIWxPayload(serverExtension, signer) {
|
|
1636
|
+
const isSolana = serverExtension.chainId.startsWith("solana:");
|
|
1637
|
+
const address = isSolana ? getSolanaAddress(signer) : getEVMAddress(signer);
|
|
1638
|
+
const message = createSIWxMessage(serverExtension, address);
|
|
1639
|
+
const signature = isSolana ? await signSolanaMessage(message, signer) : await signEVMMessage(message, signer);
|
|
1640
|
+
return {
|
|
1641
|
+
domain: serverExtension.domain,
|
|
1642
|
+
address,
|
|
1643
|
+
statement: serverExtension.statement,
|
|
1644
|
+
uri: serverExtension.uri,
|
|
1645
|
+
version: serverExtension.version,
|
|
1646
|
+
chainId: serverExtension.chainId,
|
|
1647
|
+
type: serverExtension.type,
|
|
1648
|
+
nonce: serverExtension.nonce,
|
|
1649
|
+
issuedAt: serverExtension.issuedAt,
|
|
1650
|
+
expirationTime: serverExtension.expirationTime,
|
|
1651
|
+
notBefore: serverExtension.notBefore,
|
|
1652
|
+
requestId: serverExtension.requestId,
|
|
1653
|
+
resources: serverExtension.resources,
|
|
1654
|
+
signatureScheme: serverExtension.signatureScheme,
|
|
1655
|
+
signature
|
|
1656
|
+
};
|
|
1657
|
+
}
|
|
1658
|
+
|
|
1659
|
+
// src/sign-in-with-x/encode.ts
|
|
1660
|
+
var import_utils2 = require("@bankofai/x402-core/utils");
|
|
1661
|
+
function encodeSIWxHeader(payload) {
|
|
1662
|
+
return (0, import_utils2.safeBase64Encode)(JSON.stringify(payload));
|
|
1663
|
+
}
|
|
1664
|
+
|
|
1665
|
+
// src/sign-in-with-x/hooks.ts
|
|
1666
|
+
function createSIWxSettleHook(options) {
|
|
1667
|
+
const { storage, onEvent } = options;
|
|
1668
|
+
return async (ctx) => {
|
|
1669
|
+
if (!ctx.result.success) return;
|
|
1670
|
+
const address = ctx.result.payer;
|
|
1671
|
+
if (!address) return;
|
|
1672
|
+
const resourceUrl = ctx.paymentPayload.resource?.url;
|
|
1673
|
+
if (!resourceUrl) return;
|
|
1674
|
+
const resource = new URL(resourceUrl).pathname;
|
|
1675
|
+
await storage.recordPayment(resource, address);
|
|
1676
|
+
onEvent?.({ type: "payment_recorded", resource, address });
|
|
1677
|
+
};
|
|
1678
|
+
}
|
|
1679
|
+
function createSIWxRequestHook(options) {
|
|
1680
|
+
const { storage, verifyOptions, onEvent } = options;
|
|
1681
|
+
const hasUsedNonce = typeof storage.hasUsedNonce === "function";
|
|
1682
|
+
const hasRecordNonce = typeof storage.recordNonce === "function";
|
|
1683
|
+
if (hasUsedNonce !== hasRecordNonce) {
|
|
1684
|
+
throw new Error(
|
|
1685
|
+
"SIWxStorage nonce tracking requires both hasUsedNonce and recordNonce to be implemented"
|
|
1686
|
+
);
|
|
1687
|
+
}
|
|
1688
|
+
return async (context, routeConfig) => {
|
|
1689
|
+
const header = context.adapter.getHeader(SIGN_IN_WITH_X) || context.adapter.getHeader(SIGN_IN_WITH_X.toLowerCase());
|
|
1690
|
+
if (!header) return;
|
|
1691
|
+
try {
|
|
1692
|
+
const payload = parseSIWxHeader(header);
|
|
1693
|
+
const resourceUri = context.adapter.getUrl();
|
|
1694
|
+
const validation = await validateSIWxMessage(payload, resourceUri);
|
|
1695
|
+
if (!validation.valid) {
|
|
1696
|
+
onEvent?.({ type: "validation_failed", resource: context.path, error: validation.error });
|
|
1697
|
+
return;
|
|
1698
|
+
}
|
|
1699
|
+
const verification = await verifySIWxSignature(payload, verifyOptions);
|
|
1700
|
+
if (!verification.valid || !verification.address) {
|
|
1701
|
+
onEvent?.({ type: "validation_failed", resource: context.path, error: verification.error });
|
|
1702
|
+
return;
|
|
1703
|
+
}
|
|
1704
|
+
if (storage.hasUsedNonce) {
|
|
1705
|
+
const nonceUsed = await storage.hasUsedNonce(payload.nonce);
|
|
1706
|
+
if (nonceUsed) {
|
|
1707
|
+
onEvent?.({ type: "nonce_reused", resource: context.path, nonce: payload.nonce });
|
|
1708
|
+
return;
|
|
1709
|
+
}
|
|
1710
|
+
}
|
|
1711
|
+
const isAuthOnly = Array.isArray(routeConfig?.accepts) && routeConfig.accepts.length === 0;
|
|
1712
|
+
const shouldGrant = isAuthOnly || await storage.hasPaid(context.path, verification.address);
|
|
1713
|
+
if (shouldGrant) {
|
|
1714
|
+
if (storage.recordNonce) {
|
|
1715
|
+
await storage.recordNonce(payload.nonce);
|
|
1716
|
+
}
|
|
1717
|
+
onEvent?.({
|
|
1718
|
+
type: "access_granted",
|
|
1719
|
+
resource: context.path,
|
|
1720
|
+
address: verification.address
|
|
1721
|
+
});
|
|
1722
|
+
return { grantAccess: true };
|
|
1723
|
+
}
|
|
1724
|
+
} catch (err) {
|
|
1725
|
+
onEvent?.({
|
|
1726
|
+
type: "validation_failed",
|
|
1727
|
+
resource: context.path,
|
|
1728
|
+
error: err instanceof Error ? err.message : "Unknown error"
|
|
1729
|
+
});
|
|
1730
|
+
}
|
|
1731
|
+
};
|
|
1732
|
+
}
|
|
1733
|
+
function createSIWxClientHook(signer) {
|
|
1734
|
+
const signerIsSolana = isSolanaSigner(signer);
|
|
1735
|
+
const expectedSignatureType = signerIsSolana ? "ed25519" : "eip191";
|
|
1736
|
+
return async (context) => {
|
|
1737
|
+
const extensions = context.paymentRequired.extensions ?? {};
|
|
1738
|
+
const siwxExtension = extensions[SIGN_IN_WITH_X];
|
|
1739
|
+
if (!siwxExtension?.supportedChains) return;
|
|
1740
|
+
try {
|
|
1741
|
+
const matchingChain = siwxExtension.supportedChains.find(
|
|
1742
|
+
(chain) => chain.type === expectedSignatureType
|
|
1743
|
+
);
|
|
1744
|
+
if (!matchingChain) {
|
|
1745
|
+
return;
|
|
1746
|
+
}
|
|
1747
|
+
const completeInfo = {
|
|
1748
|
+
...siwxExtension.info,
|
|
1749
|
+
chainId: matchingChain.chainId,
|
|
1750
|
+
type: matchingChain.type
|
|
1751
|
+
};
|
|
1752
|
+
const payload = await createSIWxPayload(completeInfo, signer);
|
|
1753
|
+
const header = encodeSIWxHeader(payload);
|
|
1754
|
+
return { headers: { [SIGN_IN_WITH_X]: header } };
|
|
1755
|
+
} catch {
|
|
1756
|
+
}
|
|
1757
|
+
};
|
|
1758
|
+
}
|
|
1759
|
+
function createSIWxClientExtension(options) {
|
|
1760
|
+
const hooks = options.signers.map(createSIWxClientHook);
|
|
1761
|
+
return {
|
|
1762
|
+
key: SIGN_IN_WITH_X,
|
|
1763
|
+
transportHooks: {
|
|
1764
|
+
http: {
|
|
1765
|
+
onPaymentRequired: async (_declaration, context) => {
|
|
1766
|
+
for (const hook of hooks) {
|
|
1767
|
+
const result = await hook(context);
|
|
1768
|
+
if (result?.headers) return result;
|
|
1769
|
+
}
|
|
1770
|
+
}
|
|
1771
|
+
}
|
|
1772
|
+
}
|
|
1773
|
+
};
|
|
1774
|
+
}
|
|
1775
|
+
|
|
1776
|
+
// src/sign-in-with-x/server.ts
|
|
1777
|
+
async function enrichSIWxPaymentRequiredResponse(declaration, context) {
|
|
1778
|
+
const decl = declaration;
|
|
1779
|
+
const opts = decl._options ?? {};
|
|
1780
|
+
const resourceUri = opts.resourceUri ?? context.resourceInfo.url;
|
|
1781
|
+
let domain = opts.domain;
|
|
1782
|
+
if (!domain && resourceUri) {
|
|
1783
|
+
try {
|
|
1784
|
+
domain = new URL(resourceUri).hostname;
|
|
1785
|
+
} catch {
|
|
1786
|
+
domain = void 0;
|
|
1787
|
+
}
|
|
1788
|
+
}
|
|
1789
|
+
let supportedNetworks;
|
|
1790
|
+
if (opts.network) {
|
|
1791
|
+
supportedNetworks = Array.isArray(opts.network) ? opts.network : [opts.network];
|
|
1792
|
+
} else {
|
|
1793
|
+
supportedNetworks = [...new Set(context.requirements.map((r) => r.network))];
|
|
1794
|
+
}
|
|
1795
|
+
const nonce = Array.from(globalThis.crypto.getRandomValues(new Uint8Array(16))).map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
1796
|
+
const issuedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
1797
|
+
const expirationTime = opts.expirationSeconds !== void 0 ? new Date(Date.now() + opts.expirationSeconds * 1e3).toISOString() : void 0;
|
|
1798
|
+
const info = {
|
|
1799
|
+
domain: domain ?? "",
|
|
1800
|
+
uri: resourceUri,
|
|
1801
|
+
version: opts.version ?? "1",
|
|
1802
|
+
nonce,
|
|
1803
|
+
issuedAt,
|
|
1804
|
+
resources: [resourceUri]
|
|
1805
|
+
};
|
|
1806
|
+
if (expirationTime) {
|
|
1807
|
+
info.expirationTime = expirationTime;
|
|
1808
|
+
}
|
|
1809
|
+
if (opts.statement) {
|
|
1810
|
+
info.statement = opts.statement;
|
|
1811
|
+
}
|
|
1812
|
+
const supportedChains = supportedNetworks.map((network) => ({
|
|
1813
|
+
chainId: network,
|
|
1814
|
+
type: getSignatureType(network)
|
|
1815
|
+
}));
|
|
1816
|
+
return {
|
|
1817
|
+
info,
|
|
1818
|
+
supportedChains,
|
|
1819
|
+
schema: buildSIWxSchema()
|
|
1820
|
+
};
|
|
1821
|
+
}
|
|
1822
|
+
function createSIWxResourceServerExtension(options) {
|
|
1823
|
+
const settleHook = createSIWxSettleHook(options);
|
|
1824
|
+
const requestHook = createSIWxRequestHook(options);
|
|
1825
|
+
return {
|
|
1826
|
+
key: SIGN_IN_WITH_X,
|
|
1827
|
+
enrichPaymentRequiredResponse: enrichSIWxPaymentRequiredResponse,
|
|
1828
|
+
transportHooks: {
|
|
1829
|
+
http: {
|
|
1830
|
+
onProtectedRequest: async (_declaration, context, routeConfig) => requestHook(context, routeConfig)
|
|
1831
|
+
}
|
|
1832
|
+
},
|
|
1833
|
+
hooks: {
|
|
1834
|
+
onAfterSettle: async (_declaration, context) => settleHook(context)
|
|
1835
|
+
}
|
|
1836
|
+
};
|
|
1837
|
+
}
|
|
1838
|
+
|
|
1839
|
+
// src/sign-in-with-x/fetch.ts
|
|
1840
|
+
var import_http = require("@bankofai/x402-core/http");
|
|
1841
|
+
function wrapFetchWithSIWx(fetch2, signer) {
|
|
1842
|
+
return async (input, init) => {
|
|
1843
|
+
const request = new Request(input, init);
|
|
1844
|
+
const clonedRequest = request.clone();
|
|
1845
|
+
const response = await fetch2(request);
|
|
1846
|
+
if (response.status !== 402) {
|
|
1847
|
+
return response;
|
|
1848
|
+
}
|
|
1849
|
+
const paymentRequiredHeader = response.headers.get("PAYMENT-REQUIRED");
|
|
1850
|
+
if (!paymentRequiredHeader) {
|
|
1851
|
+
return response;
|
|
1852
|
+
}
|
|
1853
|
+
const paymentRequired = (0, import_http.decodePaymentRequiredHeader)(paymentRequiredHeader);
|
|
1854
|
+
const siwxExtension = paymentRequired.extensions?.[SIGN_IN_WITH_X];
|
|
1855
|
+
if (!siwxExtension?.supportedChains) {
|
|
1856
|
+
return response;
|
|
1857
|
+
}
|
|
1858
|
+
if (clonedRequest.headers.has(SIGN_IN_WITH_X)) {
|
|
1859
|
+
throw new Error("SIWX authentication already attempted");
|
|
1860
|
+
}
|
|
1861
|
+
const paymentNetwork = paymentRequired.accepts?.[0]?.network;
|
|
1862
|
+
if (!paymentNetwork) {
|
|
1863
|
+
return response;
|
|
1864
|
+
}
|
|
1865
|
+
const matchingChain = siwxExtension.supportedChains.find(
|
|
1866
|
+
(chain) => chain.chainId === paymentNetwork
|
|
1867
|
+
);
|
|
1868
|
+
if (!matchingChain) {
|
|
1869
|
+
return response;
|
|
1870
|
+
}
|
|
1871
|
+
const completeInfo = {
|
|
1872
|
+
...siwxExtension.info,
|
|
1873
|
+
chainId: matchingChain.chainId,
|
|
1874
|
+
type: matchingChain.type
|
|
1875
|
+
};
|
|
1876
|
+
const payload = await createSIWxPayload(completeInfo, signer);
|
|
1877
|
+
const siwxHeader = encodeSIWxHeader(payload);
|
|
1878
|
+
clonedRequest.headers.set(SIGN_IN_WITH_X, siwxHeader);
|
|
1879
|
+
return fetch2(clonedRequest);
|
|
1880
|
+
};
|
|
1881
|
+
}
|
|
1882
|
+
|
|
1883
|
+
// src/sign-in-with-x/storage.ts
|
|
1884
|
+
var InMemorySIWxStorage = class {
|
|
1885
|
+
constructor() {
|
|
1886
|
+
this.paidAddresses = /* @__PURE__ */ new Map();
|
|
1887
|
+
}
|
|
1888
|
+
/**
|
|
1889
|
+
* Check if an address has paid for a resource.
|
|
1890
|
+
*
|
|
1891
|
+
* @param resource - The resource path
|
|
1892
|
+
* @param address - The wallet address to check
|
|
1893
|
+
* @returns True if the address has paid
|
|
1894
|
+
*/
|
|
1895
|
+
hasPaid(resource, address) {
|
|
1896
|
+
return this.paidAddresses.get(resource)?.has(address.toLowerCase()) ?? false;
|
|
1897
|
+
}
|
|
1898
|
+
/**
|
|
1899
|
+
* Record that an address has paid for a resource.
|
|
1900
|
+
*
|
|
1901
|
+
* @param resource - The resource path
|
|
1902
|
+
* @param address - The wallet address that paid
|
|
1903
|
+
*/
|
|
1904
|
+
recordPayment(resource, address) {
|
|
1905
|
+
if (!this.paidAddresses.has(resource)) {
|
|
1906
|
+
this.paidAddresses.set(resource, /* @__PURE__ */ new Set());
|
|
1907
|
+
}
|
|
1908
|
+
this.paidAddresses.get(resource).add(address.toLowerCase());
|
|
1909
|
+
}
|
|
1910
|
+
};
|
|
1911
|
+
|
|
1912
|
+
// src/offer-receipt/types.ts
|
|
1913
|
+
var OFFER_RECEIPT = "offer-receipt";
|
|
1914
|
+
function isJWSSignedOffer(offer) {
|
|
1915
|
+
return offer.format === "jws";
|
|
1916
|
+
}
|
|
1917
|
+
function isEIP712SignedOffer(offer) {
|
|
1918
|
+
return offer.format === "eip712";
|
|
1919
|
+
}
|
|
1920
|
+
function isJWSSignedReceipt(receipt) {
|
|
1921
|
+
return receipt.format === "jws";
|
|
1922
|
+
}
|
|
1923
|
+
function isEIP712SignedReceipt(receipt) {
|
|
1924
|
+
return receipt.format === "eip712";
|
|
1925
|
+
}
|
|
1926
|
+
function isJWSSigner(signer) {
|
|
1927
|
+
return signer.format === "jws";
|
|
1928
|
+
}
|
|
1929
|
+
function isEIP712Signer(signer) {
|
|
1930
|
+
return signer.format === "eip712";
|
|
1931
|
+
}
|
|
1932
|
+
|
|
1933
|
+
// src/offer-receipt/signing.ts
|
|
1934
|
+
var jose2 = __toESM(require("jose"));
|
|
1935
|
+
var import_viem2 = require("viem");
|
|
1936
|
+
|
|
1937
|
+
// src/offer-receipt/did.ts
|
|
1938
|
+
var jose = __toESM(require("jose"));
|
|
1939
|
+
var import_base2 = require("@scure/base");
|
|
1940
|
+
var import_secp256k1 = require("@noble/curves/secp256k1");
|
|
1941
|
+
var import_nist = require("@noble/curves/nist");
|
|
1942
|
+
var MULTICODEC_ED25519_PUB = 237;
|
|
1943
|
+
var MULTICODEC_SECP256K1_PUB = 231;
|
|
1944
|
+
var MULTICODEC_P256_PUB = 4608;
|
|
1945
|
+
async function extractPublicKeyFromKid(kid) {
|
|
1946
|
+
const [didPart, fragment] = kid.split("#");
|
|
1947
|
+
const parts = didPart.split(":");
|
|
1948
|
+
if (parts.length < 3 || parts[0] !== "did") {
|
|
1949
|
+
throw new Error(`Invalid DID format: ${kid}`);
|
|
1950
|
+
}
|
|
1951
|
+
const method = parts[1];
|
|
1952
|
+
const identifier = parts.slice(2).join(":");
|
|
1953
|
+
switch (method) {
|
|
1954
|
+
case "key":
|
|
1955
|
+
return extractKeyFromDidKey(identifier);
|
|
1956
|
+
case "jwk":
|
|
1957
|
+
return extractKeyFromDidJwk(identifier);
|
|
1958
|
+
case "web":
|
|
1959
|
+
return resolveDidWeb(identifier, fragment);
|
|
1960
|
+
default:
|
|
1961
|
+
throw new Error(
|
|
1962
|
+
`Unsupported DID method "${method}". Supported: did:key, did:jwk, did:web. Provide the public key directly for other methods.`
|
|
1963
|
+
);
|
|
1964
|
+
}
|
|
1965
|
+
}
|
|
1966
|
+
async function extractKeyFromDidKey(identifier) {
|
|
1967
|
+
if (!identifier.startsWith("z")) {
|
|
1968
|
+
throw new Error(`Unsupported multibase encoding. Expected 'z' (base58-btc).`);
|
|
1969
|
+
}
|
|
1970
|
+
const decoded = import_base2.base58.decode(identifier.slice(1));
|
|
1971
|
+
const { codec, keyBytes } = readMulticodec(decoded);
|
|
1972
|
+
switch (codec) {
|
|
1973
|
+
case MULTICODEC_ED25519_PUB:
|
|
1974
|
+
return importAsymmetricJWK({
|
|
1975
|
+
kty: "OKP",
|
|
1976
|
+
crv: "Ed25519",
|
|
1977
|
+
x: jose.base64url.encode(keyBytes)
|
|
1978
|
+
});
|
|
1979
|
+
case MULTICODEC_SECP256K1_PUB: {
|
|
1980
|
+
const point = import_secp256k1.secp256k1.Point.fromHex(keyBytes);
|
|
1981
|
+
const uncompressed = point.toBytes(false);
|
|
1982
|
+
return importAsymmetricJWK({
|
|
1983
|
+
kty: "EC",
|
|
1984
|
+
crv: "secp256k1",
|
|
1985
|
+
x: jose.base64url.encode(uncompressed.slice(1, 33)),
|
|
1986
|
+
y: jose.base64url.encode(uncompressed.slice(33, 65))
|
|
1987
|
+
});
|
|
1988
|
+
}
|
|
1989
|
+
case MULTICODEC_P256_PUB: {
|
|
1990
|
+
const point = import_nist.p256.Point.fromHex(keyBytes);
|
|
1991
|
+
const uncompressed = point.toBytes(false);
|
|
1992
|
+
return importAsymmetricJWK({
|
|
1993
|
+
kty: "EC",
|
|
1994
|
+
crv: "P-256",
|
|
1995
|
+
x: jose.base64url.encode(uncompressed.slice(1, 33)),
|
|
1996
|
+
y: jose.base64url.encode(uncompressed.slice(33, 65))
|
|
1997
|
+
});
|
|
1998
|
+
}
|
|
1999
|
+
default:
|
|
2000
|
+
throw new Error(
|
|
2001
|
+
`Unsupported key type in did:key (multicodec: 0x${codec.toString(16)}). Supported: Ed25519, secp256k1, P-256.`
|
|
2002
|
+
);
|
|
2003
|
+
}
|
|
2004
|
+
}
|
|
2005
|
+
async function extractKeyFromDidJwk(identifier) {
|
|
2006
|
+
const jwkJson = new TextDecoder().decode(jose.base64url.decode(identifier));
|
|
2007
|
+
const jwk = JSON.parse(jwkJson);
|
|
2008
|
+
return importAsymmetricJWK(jwk);
|
|
2009
|
+
}
|
|
2010
|
+
async function resolveDidWeb(identifier, fragment) {
|
|
2011
|
+
const parts = identifier.split(":");
|
|
2012
|
+
const domain = decodeURIComponent(parts[0]);
|
|
2013
|
+
const path = parts.slice(1).map(decodeURIComponent).join("/");
|
|
2014
|
+
const host = domain.split(":")[0];
|
|
2015
|
+
const scheme = host === "localhost" || host === "127.0.0.1" ? "http" : "https";
|
|
2016
|
+
const url = path ? `${scheme}://${domain}/${path}/did.json` : `${scheme}://${domain}/.well-known/did.json`;
|
|
2017
|
+
let didDocument;
|
|
2018
|
+
try {
|
|
2019
|
+
const response = await fetch(url, {
|
|
2020
|
+
headers: { Accept: "application/did+json, application/json" }
|
|
2021
|
+
});
|
|
2022
|
+
if (!response.ok) {
|
|
2023
|
+
throw new Error(`HTTP ${response.status}`);
|
|
2024
|
+
}
|
|
2025
|
+
didDocument = await response.json();
|
|
2026
|
+
} catch (error) {
|
|
2027
|
+
throw new Error(
|
|
2028
|
+
`Failed to resolve did:web:${identifier}: ${error instanceof Error ? error.message : error}`
|
|
2029
|
+
);
|
|
2030
|
+
}
|
|
2031
|
+
const fullDid = `did:web:${identifier}`;
|
|
2032
|
+
const keyId = fragment ? `${fullDid}#${fragment}` : void 0;
|
|
2033
|
+
const method = findVerificationMethod(didDocument, keyId);
|
|
2034
|
+
if (!method) {
|
|
2035
|
+
throw new Error(`No verification method found for ${keyId || fullDid}`);
|
|
2036
|
+
}
|
|
2037
|
+
if (method.publicKeyJwk) {
|
|
2038
|
+
return importAsymmetricJWK(method.publicKeyJwk);
|
|
2039
|
+
}
|
|
2040
|
+
if (method.publicKeyMultibase) {
|
|
2041
|
+
return extractKeyFromDidKey(method.publicKeyMultibase);
|
|
2042
|
+
}
|
|
2043
|
+
throw new Error(`Verification method ${method.id} has no supported key format`);
|
|
2044
|
+
}
|
|
2045
|
+
function readMulticodec(bytes) {
|
|
2046
|
+
let codec = 0;
|
|
2047
|
+
let shift = 0;
|
|
2048
|
+
let offset = 0;
|
|
2049
|
+
for (const byte of bytes) {
|
|
2050
|
+
codec |= (byte & 127) << shift;
|
|
2051
|
+
offset++;
|
|
2052
|
+
if ((byte & 128) === 0) break;
|
|
2053
|
+
shift += 7;
|
|
2054
|
+
}
|
|
2055
|
+
return { codec, keyBytes: bytes.slice(offset) };
|
|
2056
|
+
}
|
|
2057
|
+
async function importAsymmetricJWK(jwk) {
|
|
2058
|
+
const key = await jose.importJWK(jwk);
|
|
2059
|
+
if (key instanceof Uint8Array) {
|
|
2060
|
+
throw new Error("Symmetric keys are not supported");
|
|
2061
|
+
}
|
|
2062
|
+
return key;
|
|
2063
|
+
}
|
|
2064
|
+
function findVerificationMethod(doc, keyId) {
|
|
2065
|
+
const methods = doc.verificationMethod || [];
|
|
2066
|
+
if (keyId) {
|
|
2067
|
+
return methods.find((m) => m.id === keyId);
|
|
2068
|
+
}
|
|
2069
|
+
for (const ref of doc.assertionMethod || []) {
|
|
2070
|
+
if (typeof ref === "string") {
|
|
2071
|
+
const m = methods.find((m2) => m2.id === ref);
|
|
2072
|
+
if (m) return m;
|
|
2073
|
+
} else {
|
|
2074
|
+
return ref;
|
|
2075
|
+
}
|
|
2076
|
+
}
|
|
2077
|
+
for (const ref of doc.authentication || []) {
|
|
2078
|
+
if (typeof ref === "string") {
|
|
2079
|
+
const m = methods.find((m2) => m2.id === ref);
|
|
2080
|
+
if (m) return m;
|
|
2081
|
+
} else {
|
|
2082
|
+
return ref;
|
|
2083
|
+
}
|
|
2084
|
+
}
|
|
2085
|
+
return methods[0];
|
|
2086
|
+
}
|
|
2087
|
+
|
|
2088
|
+
// src/offer-receipt/signing.ts
|
|
2089
|
+
function canonicalize(value) {
|
|
2090
|
+
return serializeValue(value);
|
|
2091
|
+
}
|
|
2092
|
+
function serializeValue(value) {
|
|
2093
|
+
if (value === null) return "null";
|
|
2094
|
+
if (value === void 0) return "null";
|
|
2095
|
+
const type = typeof value;
|
|
2096
|
+
if (type === "boolean") return value ? "true" : "false";
|
|
2097
|
+
if (type === "number") return serializeNumber(value);
|
|
2098
|
+
if (type === "string") return serializeString(value);
|
|
2099
|
+
if (Array.isArray(value)) return serializeArray(value);
|
|
2100
|
+
if (type === "object") return serializeObject(value);
|
|
2101
|
+
throw new Error(`Cannot canonicalize value of type ${type}`);
|
|
2102
|
+
}
|
|
2103
|
+
function serializeNumber(num) {
|
|
2104
|
+
if (!Number.isFinite(num)) throw new Error("Cannot canonicalize Infinity or NaN");
|
|
2105
|
+
if (Object.is(num, -0)) return "0";
|
|
2106
|
+
return String(num);
|
|
2107
|
+
}
|
|
2108
|
+
function serializeString(str) {
|
|
2109
|
+
let result = '"';
|
|
2110
|
+
for (let i = 0; i < str.length; i++) {
|
|
2111
|
+
const char = str[i];
|
|
2112
|
+
const code = str.charCodeAt(i);
|
|
2113
|
+
if (code < 32) {
|
|
2114
|
+
result += "\\u" + code.toString(16).padStart(4, "0");
|
|
2115
|
+
} else if (char === '"') {
|
|
2116
|
+
result += '\\"';
|
|
2117
|
+
} else if (char === "\\") {
|
|
2118
|
+
result += "\\\\";
|
|
2119
|
+
} else {
|
|
2120
|
+
result += char;
|
|
2121
|
+
}
|
|
2122
|
+
}
|
|
2123
|
+
return result + '"';
|
|
2124
|
+
}
|
|
2125
|
+
function serializeArray(arr) {
|
|
2126
|
+
return "[" + arr.map(serializeValue).join(",") + "]";
|
|
2127
|
+
}
|
|
2128
|
+
function serializeObject(obj) {
|
|
2129
|
+
const keys = Object.keys(obj).sort((a, b) => a < b ? -1 : a > b ? 1 : 0);
|
|
2130
|
+
const pairs = [];
|
|
2131
|
+
for (const key of keys) {
|
|
2132
|
+
const value = obj[key];
|
|
2133
|
+
if (value !== void 0) {
|
|
2134
|
+
pairs.push(serializeString(key) + ":" + serializeValue(value));
|
|
2135
|
+
}
|
|
2136
|
+
}
|
|
2137
|
+
return "{" + pairs.join(",") + "}";
|
|
2138
|
+
}
|
|
2139
|
+
async function hashCanonical(obj) {
|
|
2140
|
+
const canonical = canonicalize(obj);
|
|
2141
|
+
const data = new TextEncoder().encode(canonical);
|
|
2142
|
+
const hashBuffer = await crypto.subtle.digest("SHA-256", data);
|
|
2143
|
+
return new Uint8Array(hashBuffer);
|
|
2144
|
+
}
|
|
2145
|
+
function getCanonicalBytes(obj) {
|
|
2146
|
+
return new TextEncoder().encode(canonicalize(obj));
|
|
2147
|
+
}
|
|
2148
|
+
async function createJWS(payload, signer) {
|
|
2149
|
+
const headerObj = { alg: signer.algorithm, kid: signer.kid };
|
|
2150
|
+
const headerB64 = jose2.base64url.encode(new TextEncoder().encode(JSON.stringify(headerObj)));
|
|
2151
|
+
const canonical = canonicalize(payload);
|
|
2152
|
+
const payloadB64 = jose2.base64url.encode(new TextEncoder().encode(canonical));
|
|
2153
|
+
const signingInput = new TextEncoder().encode(`${headerB64}.${payloadB64}`);
|
|
2154
|
+
const signatureB64 = await signer.sign(signingInput);
|
|
2155
|
+
return `${headerB64}.${payloadB64}.${signatureB64}`;
|
|
2156
|
+
}
|
|
2157
|
+
function extractJWSHeader(jws) {
|
|
2158
|
+
const parts = jws.split(".");
|
|
2159
|
+
if (parts.length !== 3) throw new Error("Invalid JWS format");
|
|
2160
|
+
const headerJson = jose2.base64url.decode(parts[0]);
|
|
2161
|
+
return JSON.parse(new TextDecoder().decode(headerJson));
|
|
2162
|
+
}
|
|
2163
|
+
function extractJWSPayload(jws) {
|
|
2164
|
+
const parts = jws.split(".");
|
|
2165
|
+
if (parts.length !== 3) throw new Error("Invalid JWS format");
|
|
2166
|
+
const payloadJson = jose2.base64url.decode(parts[1]);
|
|
2167
|
+
return JSON.parse(new TextDecoder().decode(payloadJson));
|
|
2168
|
+
}
|
|
2169
|
+
function createOfferDomain() {
|
|
2170
|
+
return { name: "x402 offer", version: "1", chainId: 1 };
|
|
2171
|
+
}
|
|
2172
|
+
function createReceiptDomain() {
|
|
2173
|
+
return { name: "x402 receipt", version: "1", chainId: 1 };
|
|
2174
|
+
}
|
|
2175
|
+
var OFFER_TYPES = {
|
|
2176
|
+
Offer: [
|
|
2177
|
+
{ name: "version", type: "uint256" },
|
|
2178
|
+
{ name: "resourceUrl", type: "string" },
|
|
2179
|
+
{ name: "scheme", type: "string" },
|
|
2180
|
+
{ name: "network", type: "string" },
|
|
2181
|
+
{ name: "asset", type: "string" },
|
|
2182
|
+
{ name: "payTo", type: "string" },
|
|
2183
|
+
{ name: "amount", type: "string" },
|
|
2184
|
+
{ name: "validUntil", type: "uint256" }
|
|
2185
|
+
]
|
|
2186
|
+
};
|
|
2187
|
+
var RECEIPT_TYPES = {
|
|
2188
|
+
Receipt: [
|
|
2189
|
+
{ name: "version", type: "uint256" },
|
|
2190
|
+
{ name: "network", type: "string" },
|
|
2191
|
+
{ name: "resourceUrl", type: "string" },
|
|
2192
|
+
{ name: "payer", type: "string" },
|
|
2193
|
+
{ name: "issuedAt", type: "uint256" },
|
|
2194
|
+
{ name: "transaction", type: "string" }
|
|
2195
|
+
]
|
|
2196
|
+
};
|
|
2197
|
+
function prepareOfferForEIP712(payload) {
|
|
2198
|
+
return {
|
|
2199
|
+
version: BigInt(payload.version),
|
|
2200
|
+
resourceUrl: payload.resourceUrl,
|
|
2201
|
+
scheme: payload.scheme,
|
|
2202
|
+
network: payload.network,
|
|
2203
|
+
asset: payload.asset,
|
|
2204
|
+
payTo: payload.payTo,
|
|
2205
|
+
amount: payload.amount,
|
|
2206
|
+
validUntil: BigInt(payload.validUntil)
|
|
2207
|
+
};
|
|
2208
|
+
}
|
|
2209
|
+
function prepareReceiptForEIP712(payload) {
|
|
2210
|
+
return {
|
|
2211
|
+
version: BigInt(payload.version),
|
|
2212
|
+
network: payload.network,
|
|
2213
|
+
resourceUrl: payload.resourceUrl,
|
|
2214
|
+
payer: payload.payer,
|
|
2215
|
+
issuedAt: BigInt(payload.issuedAt),
|
|
2216
|
+
transaction: payload.transaction
|
|
2217
|
+
};
|
|
2218
|
+
}
|
|
2219
|
+
function hashOfferTypedData(payload) {
|
|
2220
|
+
return (0, import_viem2.hashTypedData)({
|
|
2221
|
+
domain: createOfferDomain(),
|
|
2222
|
+
types: OFFER_TYPES,
|
|
2223
|
+
primaryType: "Offer",
|
|
2224
|
+
message: prepareOfferForEIP712(payload)
|
|
2225
|
+
});
|
|
2226
|
+
}
|
|
2227
|
+
function hashReceiptTypedData(payload) {
|
|
2228
|
+
return (0, import_viem2.hashTypedData)({
|
|
2229
|
+
domain: createReceiptDomain(),
|
|
2230
|
+
types: RECEIPT_TYPES,
|
|
2231
|
+
primaryType: "Receipt",
|
|
2232
|
+
message: prepareReceiptForEIP712(payload)
|
|
2233
|
+
});
|
|
2234
|
+
}
|
|
2235
|
+
async function signOfferEIP712(payload, signTypedData) {
|
|
2236
|
+
return signTypedData({
|
|
2237
|
+
domain: createOfferDomain(),
|
|
2238
|
+
types: OFFER_TYPES,
|
|
2239
|
+
primaryType: "Offer",
|
|
2240
|
+
message: prepareOfferForEIP712(payload)
|
|
2241
|
+
});
|
|
2242
|
+
}
|
|
2243
|
+
async function signReceiptEIP712(payload, signTypedData) {
|
|
2244
|
+
return signTypedData({
|
|
2245
|
+
domain: createReceiptDomain(),
|
|
2246
|
+
types: RECEIPT_TYPES,
|
|
2247
|
+
primaryType: "Receipt",
|
|
2248
|
+
message: prepareReceiptForEIP712(payload)
|
|
2249
|
+
});
|
|
2250
|
+
}
|
|
2251
|
+
function extractEIP155ChainId(network) {
|
|
2252
|
+
const match = network.match(/^eip155:(\d+)$/);
|
|
2253
|
+
if (!match) {
|
|
2254
|
+
throw new Error(`Invalid network format: ${network}. Expected "eip155:<chainId>"`);
|
|
2255
|
+
}
|
|
2256
|
+
return parseInt(match[1], 10);
|
|
2257
|
+
}
|
|
2258
|
+
var V1_EVM_NETWORK_CHAIN_IDS = {
|
|
2259
|
+
ethereum: 1,
|
|
2260
|
+
sepolia: 11155111,
|
|
2261
|
+
abstract: 2741,
|
|
2262
|
+
"abstract-testnet": 11124,
|
|
2263
|
+
"base-sepolia": 84532,
|
|
2264
|
+
base: 8453,
|
|
2265
|
+
"avalanche-fuji": 43113,
|
|
2266
|
+
avalanche: 43114,
|
|
2267
|
+
iotex: 4689,
|
|
2268
|
+
sei: 1329,
|
|
2269
|
+
"sei-testnet": 1328,
|
|
2270
|
+
polygon: 137,
|
|
2271
|
+
"polygon-amoy": 80002,
|
|
2272
|
+
peaq: 3338,
|
|
2273
|
+
story: 1514,
|
|
2274
|
+
educhain: 41923,
|
|
2275
|
+
"skale-base-sepolia": 324705682
|
|
2276
|
+
};
|
|
2277
|
+
var V1_SOLANA_NETWORKS = {
|
|
2278
|
+
solana: "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp",
|
|
2279
|
+
"solana-devnet": "solana:EtWTRABZaYq6iMfeYKouRu166VU2xqa1",
|
|
2280
|
+
"solana-testnet": "solana:4uhcVJyU9pJkvQyS88uRDiswHXSCkY3z"
|
|
2281
|
+
};
|
|
2282
|
+
function convertNetworkStringToCAIP2(network) {
|
|
2283
|
+
if (network.includes(":")) return network;
|
|
2284
|
+
const chainId = V1_EVM_NETWORK_CHAIN_IDS[network.toLowerCase()];
|
|
2285
|
+
if (chainId !== void 0) {
|
|
2286
|
+
return `eip155:${chainId}`;
|
|
2287
|
+
}
|
|
2288
|
+
const solanaNetwork = V1_SOLANA_NETWORKS[network.toLowerCase()];
|
|
2289
|
+
if (solanaNetwork) {
|
|
2290
|
+
return solanaNetwork;
|
|
2291
|
+
}
|
|
2292
|
+
throw new Error(
|
|
2293
|
+
`Unknown network identifier: "${network}". Expected CAIP-2 format (e.g., "eip155:8453") or v1 name (e.g., "base", "solana").`
|
|
2294
|
+
);
|
|
2295
|
+
}
|
|
2296
|
+
function extractChainIdFromCAIP2(network) {
|
|
2297
|
+
const [namespace, reference] = network.split(":");
|
|
2298
|
+
if (namespace === "eip155" && reference) {
|
|
2299
|
+
const chainId = parseInt(reference, 10);
|
|
2300
|
+
return isNaN(chainId) ? void 0 : chainId;
|
|
2301
|
+
}
|
|
2302
|
+
return void 0;
|
|
2303
|
+
}
|
|
2304
|
+
var DEFAULT_MAX_TIMEOUT_SECONDS = 300;
|
|
2305
|
+
var EXTENSION_VERSION = 1;
|
|
2306
|
+
function createOfferPayload(resourceUrl, input) {
|
|
2307
|
+
const now = Math.floor(Date.now() / 1e3);
|
|
2308
|
+
const offerValiditySeconds = input.offerValiditySeconds ?? DEFAULT_MAX_TIMEOUT_SECONDS;
|
|
2309
|
+
return {
|
|
2310
|
+
version: EXTENSION_VERSION,
|
|
2311
|
+
resourceUrl,
|
|
2312
|
+
scheme: input.scheme,
|
|
2313
|
+
network: input.network,
|
|
2314
|
+
asset: input.asset,
|
|
2315
|
+
payTo: input.payTo,
|
|
2316
|
+
amount: input.amount,
|
|
2317
|
+
validUntil: now + offerValiditySeconds
|
|
2318
|
+
};
|
|
2319
|
+
}
|
|
2320
|
+
async function createOfferJWS(resourceUrl, input, signer) {
|
|
2321
|
+
const payload = createOfferPayload(resourceUrl, input);
|
|
2322
|
+
const jws = await createJWS(payload, signer);
|
|
2323
|
+
return {
|
|
2324
|
+
format: "jws",
|
|
2325
|
+
acceptIndex: input.acceptIndex,
|
|
2326
|
+
signature: jws
|
|
2327
|
+
};
|
|
2328
|
+
}
|
|
2329
|
+
async function createOfferEIP712(resourceUrl, input, signTypedData) {
|
|
2330
|
+
const payload = createOfferPayload(resourceUrl, input);
|
|
2331
|
+
const signature = await signOfferEIP712(payload, signTypedData);
|
|
2332
|
+
return {
|
|
2333
|
+
format: "eip712",
|
|
2334
|
+
acceptIndex: input.acceptIndex,
|
|
2335
|
+
payload,
|
|
2336
|
+
signature
|
|
2337
|
+
};
|
|
2338
|
+
}
|
|
2339
|
+
function extractOfferPayload(offer) {
|
|
2340
|
+
if (isJWSSignedOffer(offer)) {
|
|
2341
|
+
return extractJWSPayload(offer.signature);
|
|
2342
|
+
} else if (isEIP712SignedOffer(offer)) {
|
|
2343
|
+
return offer.payload;
|
|
2344
|
+
}
|
|
2345
|
+
throw new Error(`Unknown offer format: ${offer.format}`);
|
|
2346
|
+
}
|
|
2347
|
+
function createReceiptPayloadForEIP712(input) {
|
|
2348
|
+
return {
|
|
2349
|
+
version: EXTENSION_VERSION,
|
|
2350
|
+
network: input.network,
|
|
2351
|
+
resourceUrl: input.resourceUrl,
|
|
2352
|
+
payer: input.payer,
|
|
2353
|
+
issuedAt: Math.floor(Date.now() / 1e3),
|
|
2354
|
+
transaction: input.transaction ?? ""
|
|
2355
|
+
};
|
|
2356
|
+
}
|
|
2357
|
+
function createReceiptPayloadForJWS(input) {
|
|
2358
|
+
const payload = {
|
|
2359
|
+
version: EXTENSION_VERSION,
|
|
2360
|
+
network: input.network,
|
|
2361
|
+
resourceUrl: input.resourceUrl,
|
|
2362
|
+
payer: input.payer,
|
|
2363
|
+
issuedAt: Math.floor(Date.now() / 1e3)
|
|
2364
|
+
};
|
|
2365
|
+
if (input.transaction) {
|
|
2366
|
+
payload.transaction = input.transaction;
|
|
2367
|
+
}
|
|
2368
|
+
return payload;
|
|
2369
|
+
}
|
|
2370
|
+
async function createReceiptJWS(input, signer) {
|
|
2371
|
+
const payload = createReceiptPayloadForJWS(input);
|
|
2372
|
+
const jws = await createJWS(payload, signer);
|
|
2373
|
+
return { format: "jws", signature: jws };
|
|
2374
|
+
}
|
|
2375
|
+
async function createReceiptEIP712(input, signTypedData) {
|
|
2376
|
+
const payload = createReceiptPayloadForEIP712(input);
|
|
2377
|
+
const signature = await signReceiptEIP712(payload, signTypedData);
|
|
2378
|
+
return { format: "eip712", payload, signature };
|
|
2379
|
+
}
|
|
2380
|
+
function extractReceiptPayload(receipt) {
|
|
2381
|
+
if (isJWSSignedReceipt(receipt)) {
|
|
2382
|
+
return extractJWSPayload(receipt.signature);
|
|
2383
|
+
} else if (isEIP712SignedReceipt(receipt)) {
|
|
2384
|
+
return receipt.payload;
|
|
2385
|
+
}
|
|
2386
|
+
throw new Error(`Unknown receipt format: ${receipt.format}`);
|
|
2387
|
+
}
|
|
2388
|
+
async function verifyOfferSignatureEIP712(offer) {
|
|
2389
|
+
if (offer.format !== "eip712") {
|
|
2390
|
+
throw new Error(`Expected eip712 format, got ${offer.format}`);
|
|
2391
|
+
}
|
|
2392
|
+
if (!offer.payload || !("scheme" in offer.payload)) {
|
|
2393
|
+
throw new Error("Invalid offer: missing or malformed payload");
|
|
2394
|
+
}
|
|
2395
|
+
const signer = await (0, import_viem2.recoverTypedDataAddress)({
|
|
2396
|
+
domain: createOfferDomain(),
|
|
2397
|
+
types: OFFER_TYPES,
|
|
2398
|
+
primaryType: "Offer",
|
|
2399
|
+
message: prepareOfferForEIP712(offer.payload),
|
|
2400
|
+
signature: offer.signature
|
|
2401
|
+
});
|
|
2402
|
+
return { signer, payload: offer.payload };
|
|
2403
|
+
}
|
|
2404
|
+
async function verifyReceiptSignatureEIP712(receipt) {
|
|
2405
|
+
if (receipt.format !== "eip712") {
|
|
2406
|
+
throw new Error(`Expected eip712 format, got ${receipt.format}`);
|
|
2407
|
+
}
|
|
2408
|
+
if (!receipt.payload || !("payer" in receipt.payload)) {
|
|
2409
|
+
throw new Error("Invalid receipt: missing or malformed payload");
|
|
2410
|
+
}
|
|
2411
|
+
const signer = await (0, import_viem2.recoverTypedDataAddress)({
|
|
2412
|
+
domain: createReceiptDomain(),
|
|
2413
|
+
types: RECEIPT_TYPES,
|
|
2414
|
+
primaryType: "Receipt",
|
|
2415
|
+
message: prepareReceiptForEIP712(receipt.payload),
|
|
2416
|
+
signature: receipt.signature
|
|
2417
|
+
});
|
|
2418
|
+
return { signer, payload: receipt.payload };
|
|
2419
|
+
}
|
|
2420
|
+
async function verifyOfferSignatureJWS(offer, publicKey) {
|
|
2421
|
+
if (offer.format !== "jws") {
|
|
2422
|
+
throw new Error(`Expected jws format, got ${offer.format}`);
|
|
2423
|
+
}
|
|
2424
|
+
const key = await resolveVerificationKey(offer.signature, publicKey);
|
|
2425
|
+
const { payload } = await jose2.compactVerify(offer.signature, key);
|
|
2426
|
+
return JSON.parse(new TextDecoder().decode(payload));
|
|
2427
|
+
}
|
|
2428
|
+
async function verifyReceiptSignatureJWS(receipt, publicKey) {
|
|
2429
|
+
if (receipt.format !== "jws") {
|
|
2430
|
+
throw new Error(`Expected jws format, got ${receipt.format}`);
|
|
2431
|
+
}
|
|
2432
|
+
const key = await resolveVerificationKey(receipt.signature, publicKey);
|
|
2433
|
+
const { payload } = await jose2.compactVerify(receipt.signature, key);
|
|
2434
|
+
return JSON.parse(new TextDecoder().decode(payload));
|
|
2435
|
+
}
|
|
2436
|
+
async function resolveVerificationKey(jws, providedKey) {
|
|
2437
|
+
if (providedKey) {
|
|
2438
|
+
if ("kty" in providedKey) {
|
|
2439
|
+
const key = await jose2.importJWK(providedKey);
|
|
2440
|
+
if (key instanceof Uint8Array) {
|
|
2441
|
+
throw new Error("Symmetric keys are not supported for JWS verification");
|
|
2442
|
+
}
|
|
2443
|
+
return key;
|
|
2444
|
+
}
|
|
2445
|
+
return providedKey;
|
|
2446
|
+
}
|
|
2447
|
+
const header = extractJWSHeader(jws);
|
|
2448
|
+
if (!header.kid) {
|
|
2449
|
+
throw new Error("No public key provided and JWS header missing kid");
|
|
2450
|
+
}
|
|
2451
|
+
return extractPublicKeyFromKid(header.kid);
|
|
2452
|
+
}
|
|
2453
|
+
|
|
2454
|
+
// src/offer-receipt/server.ts
|
|
2455
|
+
var OFFER_SCHEMA = {
|
|
2456
|
+
$schema: "https://json-schema.org/draft/2020-12/schema",
|
|
2457
|
+
type: "object",
|
|
2458
|
+
properties: {
|
|
2459
|
+
offers: {
|
|
2460
|
+
type: "array",
|
|
2461
|
+
items: {
|
|
2462
|
+
type: "object",
|
|
2463
|
+
properties: {
|
|
2464
|
+
format: { type: "string" },
|
|
2465
|
+
acceptIndex: { type: "integer" },
|
|
2466
|
+
payload: {
|
|
2467
|
+
type: "object",
|
|
2468
|
+
properties: {
|
|
2469
|
+
version: { type: "integer" },
|
|
2470
|
+
resourceUrl: { type: "string" },
|
|
2471
|
+
scheme: { type: "string" },
|
|
2472
|
+
network: { type: "string" },
|
|
2473
|
+
asset: { type: "string" },
|
|
2474
|
+
payTo: { type: "string" },
|
|
2475
|
+
amount: { type: "string" },
|
|
2476
|
+
validUntil: { type: "integer" }
|
|
2477
|
+
},
|
|
2478
|
+
required: ["version", "resourceUrl", "scheme", "network", "asset", "payTo", "amount"]
|
|
2479
|
+
},
|
|
2480
|
+
signature: { type: "string" }
|
|
2481
|
+
},
|
|
2482
|
+
required: ["format", "signature"]
|
|
2483
|
+
}
|
|
2484
|
+
}
|
|
2485
|
+
},
|
|
2486
|
+
required: ["offers"]
|
|
2487
|
+
};
|
|
2488
|
+
var RECEIPT_SCHEMA = {
|
|
2489
|
+
$schema: "https://json-schema.org/draft/2020-12/schema",
|
|
2490
|
+
type: "object",
|
|
2491
|
+
properties: {
|
|
2492
|
+
receipt: {
|
|
2493
|
+
type: "object",
|
|
2494
|
+
properties: {
|
|
2495
|
+
format: { type: "string" },
|
|
2496
|
+
payload: {
|
|
2497
|
+
type: "object",
|
|
2498
|
+
properties: {
|
|
2499
|
+
version: { type: "integer" },
|
|
2500
|
+
network: { type: "string" },
|
|
2501
|
+
resourceUrl: { type: "string" },
|
|
2502
|
+
payer: { type: "string" },
|
|
2503
|
+
issuedAt: { type: "integer" },
|
|
2504
|
+
transaction: { type: "string" }
|
|
2505
|
+
},
|
|
2506
|
+
required: ["version", "network", "resourceUrl", "payer", "issuedAt"]
|
|
2507
|
+
},
|
|
2508
|
+
signature: { type: "string" }
|
|
2509
|
+
},
|
|
2510
|
+
required: ["format", "signature"]
|
|
2511
|
+
}
|
|
2512
|
+
},
|
|
2513
|
+
required: ["receipt"]
|
|
2514
|
+
};
|
|
2515
|
+
function requirementsToOfferInput(requirements, acceptIndex, offerValiditySeconds) {
|
|
2516
|
+
return {
|
|
2517
|
+
acceptIndex,
|
|
2518
|
+
scheme: requirements.scheme,
|
|
2519
|
+
network: requirements.network,
|
|
2520
|
+
asset: requirements.asset,
|
|
2521
|
+
payTo: requirements.payTo,
|
|
2522
|
+
amount: requirements.amount,
|
|
2523
|
+
offerValiditySeconds: offerValiditySeconds ?? requirements.maxTimeoutSeconds
|
|
2524
|
+
};
|
|
2525
|
+
}
|
|
2526
|
+
function createOfferReceiptExtension(issuer) {
|
|
2527
|
+
return {
|
|
2528
|
+
key: OFFER_RECEIPT,
|
|
2529
|
+
// Add signed offers to 402 PaymentRequired response
|
|
2530
|
+
enrichPaymentRequiredResponse: async (declaration, context) => {
|
|
2531
|
+
const config = declaration;
|
|
2532
|
+
const resourceUrl = context.paymentRequiredResponse.resource?.url || context.transportContext?.request?.adapter?.getUrl?.();
|
|
2533
|
+
if (!resourceUrl) {
|
|
2534
|
+
console.warn("[offer-receipt] No resource URL available for signing offers");
|
|
2535
|
+
return void 0;
|
|
2536
|
+
}
|
|
2537
|
+
const offers = [];
|
|
2538
|
+
for (let i = 0; i < context.requirements.length; i++) {
|
|
2539
|
+
const requirement = context.requirements[i];
|
|
2540
|
+
try {
|
|
2541
|
+
const offerInput = requirementsToOfferInput(requirement, i, config?.offerValiditySeconds);
|
|
2542
|
+
const signedOffer = await issuer.issueOffer(resourceUrl, offerInput);
|
|
2543
|
+
offers.push(signedOffer);
|
|
2544
|
+
} catch (error) {
|
|
2545
|
+
console.error(`[offer-receipt] Failed to sign offer for requirement ${i}:`, error);
|
|
2546
|
+
}
|
|
2547
|
+
}
|
|
2548
|
+
if (offers.length === 0) {
|
|
2549
|
+
return void 0;
|
|
2550
|
+
}
|
|
2551
|
+
return {
|
|
2552
|
+
info: {
|
|
2553
|
+
offers
|
|
2554
|
+
},
|
|
2555
|
+
schema: OFFER_SCHEMA
|
|
2556
|
+
};
|
|
2557
|
+
},
|
|
2558
|
+
// Add signed receipt to settlement response
|
|
2559
|
+
enrichSettlementResponse: async (declaration, context) => {
|
|
2560
|
+
const config = declaration;
|
|
2561
|
+
if (!context.result.success) {
|
|
2562
|
+
return void 0;
|
|
2563
|
+
}
|
|
2564
|
+
const payer = context.result.payer;
|
|
2565
|
+
if (!payer) {
|
|
2566
|
+
console.warn("[offer-receipt] No payer available for signing receipt");
|
|
2567
|
+
return void 0;
|
|
2568
|
+
}
|
|
2569
|
+
const network = context.result.network;
|
|
2570
|
+
if (!network) {
|
|
2571
|
+
console.warn("[offer-receipt] No network available for signing receipt");
|
|
2572
|
+
return void 0;
|
|
2573
|
+
}
|
|
2574
|
+
const transaction = context.result.transaction;
|
|
2575
|
+
const resourceUrl = context.transportContext?.request?.adapter?.getUrl?.();
|
|
2576
|
+
if (!resourceUrl) {
|
|
2577
|
+
console.warn("[offer-receipt] No resource URL available for signing receipt");
|
|
2578
|
+
return void 0;
|
|
2579
|
+
}
|
|
2580
|
+
const includeTxHash = config?.includeTxHash === true;
|
|
2581
|
+
try {
|
|
2582
|
+
const signedReceipt = await issuer.issueReceipt(
|
|
2583
|
+
resourceUrl,
|
|
2584
|
+
payer,
|
|
2585
|
+
network,
|
|
2586
|
+
includeTxHash ? transaction || void 0 : void 0
|
|
2587
|
+
);
|
|
2588
|
+
return {
|
|
2589
|
+
info: {
|
|
2590
|
+
receipt: signedReceipt
|
|
2591
|
+
},
|
|
2592
|
+
schema: RECEIPT_SCHEMA
|
|
2593
|
+
};
|
|
2594
|
+
} catch (error) {
|
|
2595
|
+
console.error("[offer-receipt] Failed to sign receipt:", error);
|
|
2596
|
+
return void 0;
|
|
2597
|
+
}
|
|
2598
|
+
}
|
|
2599
|
+
};
|
|
2600
|
+
}
|
|
2601
|
+
function declareOfferReceiptExtension(config) {
|
|
2602
|
+
return {
|
|
2603
|
+
[OFFER_RECEIPT]: {
|
|
2604
|
+
includeTxHash: config?.includeTxHash,
|
|
2605
|
+
offerValiditySeconds: config?.offerValiditySeconds
|
|
2606
|
+
}
|
|
2607
|
+
};
|
|
2608
|
+
}
|
|
2609
|
+
function createJWSOfferReceiptIssuer(kid, jwsSigner) {
|
|
2610
|
+
return {
|
|
2611
|
+
kid,
|
|
2612
|
+
format: "jws",
|
|
2613
|
+
async issueOffer(resourceUrl, input) {
|
|
2614
|
+
return createOfferJWS(resourceUrl, input, jwsSigner);
|
|
2615
|
+
},
|
|
2616
|
+
async issueReceipt(resourceUrl, payer, network, transaction) {
|
|
2617
|
+
return createReceiptJWS({ resourceUrl, payer, network, transaction }, jwsSigner);
|
|
2618
|
+
}
|
|
2619
|
+
};
|
|
2620
|
+
}
|
|
2621
|
+
function createEIP712OfferReceiptIssuer(kid, signTypedData) {
|
|
2622
|
+
return {
|
|
2623
|
+
kid,
|
|
2624
|
+
format: "eip712",
|
|
2625
|
+
async issueOffer(resourceUrl, input) {
|
|
2626
|
+
return createOfferEIP712(resourceUrl, input, signTypedData);
|
|
2627
|
+
},
|
|
2628
|
+
async issueReceipt(resourceUrl, payer, network, transaction) {
|
|
2629
|
+
return createReceiptEIP712({ resourceUrl, payer, network, transaction }, signTypedData);
|
|
2630
|
+
}
|
|
2631
|
+
};
|
|
2632
|
+
}
|
|
2633
|
+
|
|
2634
|
+
// src/offer-receipt/client.ts
|
|
2635
|
+
var import_http2 = require("@bankofai/x402-core/http");
|
|
2636
|
+
function verifyReceiptMatchesOffer(receipt, offer, payerAddresses, maxAgeSeconds = 3600) {
|
|
2637
|
+
const payload = extractReceiptPayload(receipt);
|
|
2638
|
+
const resourceUrlMatch = payload.resourceUrl === offer.resourceUrl;
|
|
2639
|
+
const networkMatch = payload.network === offer.network;
|
|
2640
|
+
const payerMatch = payerAddresses.some(
|
|
2641
|
+
(addr) => payload.payer.toLowerCase() === addr.toLowerCase()
|
|
2642
|
+
);
|
|
2643
|
+
const issuedRecently = Math.floor(Date.now() / 1e3) - payload.issuedAt < maxAgeSeconds;
|
|
2644
|
+
return resourceUrlMatch && networkMatch && payerMatch && issuedRecently;
|
|
2645
|
+
}
|
|
2646
|
+
function extractOffersFromPaymentRequired(paymentRequired) {
|
|
2647
|
+
const extData = paymentRequired.extensions?.[OFFER_RECEIPT];
|
|
2648
|
+
return extData?.info?.offers ?? [];
|
|
2649
|
+
}
|
|
2650
|
+
function decodeSignedOffers(offers) {
|
|
2651
|
+
return offers.map((offer) => {
|
|
2652
|
+
const payload = extractOfferPayload(offer);
|
|
2653
|
+
return {
|
|
2654
|
+
// Spread payload fields at top level
|
|
2655
|
+
...payload,
|
|
2656
|
+
// Include metadata
|
|
2657
|
+
signedOffer: offer,
|
|
2658
|
+
format: offer.format,
|
|
2659
|
+
acceptIndex: offer.acceptIndex
|
|
2660
|
+
};
|
|
2661
|
+
});
|
|
2662
|
+
}
|
|
2663
|
+
function findAcceptsObjectFromSignedOffer(offer, accepts) {
|
|
2664
|
+
const isDecoded = "signedOffer" in offer;
|
|
2665
|
+
const payload = isDecoded ? offer : extractOfferPayload(offer);
|
|
2666
|
+
const acceptIndex = isDecoded ? offer.acceptIndex : offer.acceptIndex;
|
|
2667
|
+
if (acceptIndex !== void 0 && acceptIndex < accepts.length) {
|
|
2668
|
+
const hinted = accepts[acceptIndex];
|
|
2669
|
+
if (hinted.network === payload.network && hinted.scheme === payload.scheme && hinted.asset === payload.asset && hinted.payTo === payload.payTo && hinted.amount === payload.amount) {
|
|
2670
|
+
return hinted;
|
|
2671
|
+
}
|
|
2672
|
+
}
|
|
2673
|
+
return accepts.find(
|
|
2674
|
+
(req) => req.network === payload.network && req.scheme === payload.scheme && req.asset === payload.asset && req.payTo === payload.payTo && req.amount === payload.amount
|
|
2675
|
+
);
|
|
2676
|
+
}
|
|
2677
|
+
function extractReceiptFromResponse(response) {
|
|
2678
|
+
const paymentResponseHeader = response.headers.get("PAYMENT-RESPONSE") || response.headers.get("X-PAYMENT-RESPONSE");
|
|
2679
|
+
if (!paymentResponseHeader) {
|
|
2680
|
+
return void 0;
|
|
2681
|
+
}
|
|
2682
|
+
try {
|
|
2683
|
+
const settlementResponse = (0, import_http2.decodePaymentResponseHeader)(paymentResponseHeader);
|
|
2684
|
+
const receiptExtData = settlementResponse.extensions?.[OFFER_RECEIPT];
|
|
2685
|
+
return receiptExtData?.info?.receipt;
|
|
2686
|
+
} catch {
|
|
2687
|
+
return void 0;
|
|
2688
|
+
}
|
|
2689
|
+
}
|
|
2690
|
+
|
|
2691
|
+
// src/payment-identifier/types.ts
|
|
2692
|
+
var PAYMENT_IDENTIFIER = "payment-identifier";
|
|
2693
|
+
var PAYMENT_ID_MIN_LENGTH = 16;
|
|
2694
|
+
var PAYMENT_ID_MAX_LENGTH = 128;
|
|
2695
|
+
var PAYMENT_ID_PATTERN = /^[a-zA-Z0-9_-]+$/;
|
|
2696
|
+
|
|
2697
|
+
// src/payment-identifier/schema.ts
|
|
2698
|
+
var paymentIdentifierSchema = {
|
|
2699
|
+
$schema: "https://json-schema.org/draft/2020-12/schema",
|
|
2700
|
+
type: "object",
|
|
2701
|
+
properties: {
|
|
2702
|
+
required: {
|
|
2703
|
+
type: "boolean"
|
|
2704
|
+
},
|
|
2705
|
+
id: {
|
|
2706
|
+
type: "string",
|
|
2707
|
+
minLength: PAYMENT_ID_MIN_LENGTH,
|
|
2708
|
+
maxLength: PAYMENT_ID_MAX_LENGTH,
|
|
2709
|
+
pattern: "^[a-zA-Z0-9_-]+$"
|
|
2710
|
+
}
|
|
2711
|
+
},
|
|
2712
|
+
required: ["required"]
|
|
2713
|
+
};
|
|
2714
|
+
|
|
2715
|
+
// src/payment-identifier/utils.ts
|
|
2716
|
+
function generatePaymentId(prefix = "pay_") {
|
|
2717
|
+
const uuid = crypto.randomUUID().replace(/-/g, "");
|
|
2718
|
+
return `${prefix}${uuid}`;
|
|
2719
|
+
}
|
|
2720
|
+
function isValidPaymentId(id) {
|
|
2721
|
+
if (typeof id !== "string") {
|
|
2722
|
+
return false;
|
|
2723
|
+
}
|
|
2724
|
+
if (id.length < PAYMENT_ID_MIN_LENGTH || id.length > PAYMENT_ID_MAX_LENGTH) {
|
|
2725
|
+
return false;
|
|
2726
|
+
}
|
|
2727
|
+
return PAYMENT_ID_PATTERN.test(id);
|
|
2728
|
+
}
|
|
2729
|
+
|
|
2730
|
+
// src/payment-identifier/validation.ts
|
|
2731
|
+
var import__2 = __toESM(require("ajv/dist/2020.js"));
|
|
2732
|
+
function isPaymentIdentifierExtension(extension) {
|
|
2733
|
+
if (!extension || typeof extension !== "object") {
|
|
2734
|
+
return false;
|
|
2735
|
+
}
|
|
2736
|
+
const ext = extension;
|
|
2737
|
+
if (!ext.info || typeof ext.info !== "object") {
|
|
2738
|
+
return false;
|
|
2739
|
+
}
|
|
2740
|
+
const info = ext.info;
|
|
2741
|
+
if (typeof info.required !== "boolean") {
|
|
2742
|
+
return false;
|
|
2743
|
+
}
|
|
2744
|
+
return true;
|
|
2745
|
+
}
|
|
2746
|
+
function validatePaymentIdentifier(extension) {
|
|
2747
|
+
if (!extension || typeof extension !== "object") {
|
|
2748
|
+
return {
|
|
2749
|
+
valid: false,
|
|
2750
|
+
errors: ["Extension must be an object"]
|
|
2751
|
+
};
|
|
2752
|
+
}
|
|
2753
|
+
const ext = extension;
|
|
2754
|
+
if (!ext.info || typeof ext.info !== "object") {
|
|
2755
|
+
return {
|
|
2756
|
+
valid: false,
|
|
2757
|
+
errors: ["Extension must have an 'info' property"]
|
|
2758
|
+
};
|
|
2759
|
+
}
|
|
2760
|
+
const info = ext.info;
|
|
2761
|
+
if (typeof info.required !== "boolean") {
|
|
2762
|
+
return {
|
|
2763
|
+
valid: false,
|
|
2764
|
+
errors: ["Extension info must have a 'required' boolean property"]
|
|
2765
|
+
};
|
|
2766
|
+
}
|
|
2767
|
+
if (info.id !== void 0 && typeof info.id !== "string") {
|
|
2768
|
+
return {
|
|
2769
|
+
valid: false,
|
|
2770
|
+
errors: ["Extension info 'id' must be a string if provided"]
|
|
2771
|
+
};
|
|
2772
|
+
}
|
|
2773
|
+
if (info.id !== void 0 && !isValidPaymentId(info.id)) {
|
|
2774
|
+
return {
|
|
2775
|
+
valid: false,
|
|
2776
|
+
errors: [
|
|
2777
|
+
`Invalid payment ID format. ID must be 16-128 characters and contain only alphanumeric characters, hyphens, and underscores.`
|
|
2778
|
+
]
|
|
2779
|
+
};
|
|
2780
|
+
}
|
|
2781
|
+
if (ext.schema) {
|
|
2782
|
+
try {
|
|
2783
|
+
const ajv = new import__2.default({ strict: false, allErrors: true });
|
|
2784
|
+
const validate = ajv.compile(ext.schema);
|
|
2785
|
+
const valid = validate(ext.info);
|
|
2786
|
+
if (!valid && validate.errors) {
|
|
2787
|
+
const errors = validate.errors?.map((err) => {
|
|
2788
|
+
const path = err.instancePath || "(root)";
|
|
2789
|
+
return `${path}: ${err.message}`;
|
|
2790
|
+
}) || ["Unknown validation error"];
|
|
2791
|
+
return { valid: false, errors };
|
|
2792
|
+
}
|
|
2793
|
+
} catch (error) {
|
|
2794
|
+
return {
|
|
2795
|
+
valid: false,
|
|
2796
|
+
errors: [
|
|
2797
|
+
`Schema validation failed: ${error instanceof Error ? error.message : String(error)}`
|
|
2798
|
+
]
|
|
2799
|
+
};
|
|
2800
|
+
}
|
|
2801
|
+
}
|
|
2802
|
+
return { valid: true };
|
|
2803
|
+
}
|
|
2804
|
+
function extractPaymentIdentifier(paymentPayload, validate = true) {
|
|
2805
|
+
if (!paymentPayload.extensions) {
|
|
2806
|
+
return null;
|
|
2807
|
+
}
|
|
2808
|
+
const extension = paymentPayload.extensions[PAYMENT_IDENTIFIER];
|
|
2809
|
+
if (!extension || typeof extension !== "object") {
|
|
2810
|
+
return null;
|
|
2811
|
+
}
|
|
2812
|
+
const ext = extension;
|
|
2813
|
+
if (!ext.info || typeof ext.info !== "object") {
|
|
2814
|
+
return null;
|
|
2815
|
+
}
|
|
2816
|
+
const info = ext.info;
|
|
2817
|
+
if (typeof info.id !== "string") {
|
|
2818
|
+
return null;
|
|
2819
|
+
}
|
|
2820
|
+
if (validate && !isValidPaymentId(info.id)) {
|
|
2821
|
+
return null;
|
|
2822
|
+
}
|
|
2823
|
+
return info.id;
|
|
2824
|
+
}
|
|
2825
|
+
function extractAndValidatePaymentIdentifier(paymentPayload) {
|
|
2826
|
+
if (!paymentPayload.extensions) {
|
|
2827
|
+
return { id: null, validation: { valid: true } };
|
|
2828
|
+
}
|
|
2829
|
+
const extension = paymentPayload.extensions[PAYMENT_IDENTIFIER];
|
|
2830
|
+
if (!extension) {
|
|
2831
|
+
return { id: null, validation: { valid: true } };
|
|
2832
|
+
}
|
|
2833
|
+
const validation = validatePaymentIdentifier(extension);
|
|
2834
|
+
if (!validation.valid) {
|
|
2835
|
+
return { id: null, validation };
|
|
2836
|
+
}
|
|
2837
|
+
const ext = extension;
|
|
2838
|
+
return { id: ext.info.id ?? null, validation: { valid: true } };
|
|
2839
|
+
}
|
|
2840
|
+
function hasPaymentIdentifier(paymentPayload) {
|
|
2841
|
+
return !!(paymentPayload.extensions && paymentPayload.extensions[PAYMENT_IDENTIFIER]);
|
|
2842
|
+
}
|
|
2843
|
+
function isPaymentIdentifierRequired(extension) {
|
|
2844
|
+
if (!extension || typeof extension !== "object") {
|
|
2845
|
+
return false;
|
|
2846
|
+
}
|
|
2847
|
+
const ext = extension;
|
|
2848
|
+
if (!ext.info || typeof ext.info !== "object") {
|
|
2849
|
+
return false;
|
|
2850
|
+
}
|
|
2851
|
+
return ext.info.required === true;
|
|
2852
|
+
}
|
|
2853
|
+
function validatePaymentIdentifierRequirement(paymentPayload, serverRequired) {
|
|
2854
|
+
if (!serverRequired) {
|
|
2855
|
+
return { valid: true };
|
|
2856
|
+
}
|
|
2857
|
+
const id = extractPaymentIdentifier(paymentPayload, false);
|
|
2858
|
+
if (!id) {
|
|
2859
|
+
return {
|
|
2860
|
+
valid: false,
|
|
2861
|
+
errors: ["Server requires a payment identifier but none was provided"]
|
|
2862
|
+
};
|
|
2863
|
+
}
|
|
2864
|
+
if (!isValidPaymentId(id)) {
|
|
2865
|
+
return {
|
|
2866
|
+
valid: false,
|
|
2867
|
+
errors: [
|
|
2868
|
+
`Invalid payment ID format. ID must be 16-128 characters and contain only alphanumeric characters, hyphens, and underscores.`
|
|
2869
|
+
]
|
|
2870
|
+
};
|
|
2871
|
+
}
|
|
2872
|
+
return { valid: true };
|
|
2873
|
+
}
|
|
2874
|
+
|
|
2875
|
+
// src/payment-identifier/client.ts
|
|
2876
|
+
function appendPaymentIdentifierToExtensions(extensions, id) {
|
|
2877
|
+
const extension = extensions[PAYMENT_IDENTIFIER];
|
|
2878
|
+
if (!isPaymentIdentifierExtension(extension)) {
|
|
2879
|
+
return extensions;
|
|
2880
|
+
}
|
|
2881
|
+
const paymentId = id ?? generatePaymentId();
|
|
2882
|
+
if (!isValidPaymentId(paymentId)) {
|
|
2883
|
+
throw new Error(
|
|
2884
|
+
`Invalid payment ID: "${paymentId}". ID must be 16-128 characters and contain only alphanumeric characters, hyphens, and underscores.`
|
|
2885
|
+
);
|
|
2886
|
+
}
|
|
2887
|
+
extension.info.id = paymentId;
|
|
2888
|
+
return extensions;
|
|
2889
|
+
}
|
|
2890
|
+
|
|
2891
|
+
// src/payment-identifier/resourceServer.ts
|
|
2892
|
+
function declarePaymentIdentifierExtension(required = false) {
|
|
2893
|
+
return {
|
|
2894
|
+
info: { required },
|
|
2895
|
+
schema: paymentIdentifierSchema
|
|
2896
|
+
};
|
|
2897
|
+
}
|
|
2898
|
+
var paymentIdentifierResourceServerExtension = {
|
|
2899
|
+
key: PAYMENT_IDENTIFIER
|
|
2900
|
+
// No enrichment needed - the declaration is static
|
|
2901
|
+
// Future hooks for idempotency could be added here if needed
|
|
2902
|
+
};
|
|
2903
|
+
|
|
2904
|
+
// src/eip2612-gas-sponsoring/types.ts
|
|
2905
|
+
var EIP2612_GAS_SPONSORING = { key: "eip2612GasSponsoring" };
|
|
2906
|
+
|
|
2907
|
+
// src/eip2612-gas-sponsoring/resourceService.ts
|
|
2908
|
+
var eip2612GasSponsoringSchema = {
|
|
2909
|
+
$schema: "https://json-schema.org/draft/2020-12/schema",
|
|
2910
|
+
type: "object",
|
|
2911
|
+
properties: {
|
|
2912
|
+
from: {
|
|
2913
|
+
type: "string",
|
|
2914
|
+
pattern: "^0x[a-fA-F0-9]{40}$",
|
|
2915
|
+
description: "The address of the sender."
|
|
2916
|
+
},
|
|
2917
|
+
asset: {
|
|
2918
|
+
type: "string",
|
|
2919
|
+
pattern: "^0x[a-fA-F0-9]{40}$",
|
|
2920
|
+
description: "The address of the ERC-20 token contract."
|
|
2921
|
+
},
|
|
2922
|
+
spender: {
|
|
2923
|
+
type: "string",
|
|
2924
|
+
pattern: "^0x[a-fA-F0-9]{40}$",
|
|
2925
|
+
description: "The address of the spender (Canonical Permit2)."
|
|
2926
|
+
},
|
|
2927
|
+
amount: {
|
|
2928
|
+
type: "string",
|
|
2929
|
+
pattern: "^[0-9]+$",
|
|
2930
|
+
description: "The amount to approve (uint256). Typically MaxUint."
|
|
2931
|
+
},
|
|
2932
|
+
nonce: {
|
|
2933
|
+
type: "string",
|
|
2934
|
+
pattern: "^[0-9]+$",
|
|
2935
|
+
description: "The current nonce of the sender."
|
|
2936
|
+
},
|
|
2937
|
+
deadline: {
|
|
2938
|
+
type: "string",
|
|
2939
|
+
pattern: "^[0-9]+$",
|
|
2940
|
+
description: "The timestamp at which the signature expires."
|
|
2941
|
+
},
|
|
2942
|
+
signature: {
|
|
2943
|
+
type: "string",
|
|
2944
|
+
pattern: "^0x[a-fA-F0-9]+$",
|
|
2945
|
+
description: "The 65-byte concatenated signature (r, s, v) as a hex string."
|
|
2946
|
+
},
|
|
2947
|
+
version: {
|
|
2948
|
+
type: "string",
|
|
2949
|
+
pattern: "^[0-9]+(\\.[0-9]+)*$",
|
|
2950
|
+
description: "Schema version identifier."
|
|
2951
|
+
}
|
|
2952
|
+
},
|
|
2953
|
+
required: ["from", "asset", "spender", "amount", "nonce", "deadline", "signature", "version"]
|
|
2954
|
+
};
|
|
2955
|
+
function declareEip2612GasSponsoringExtension() {
|
|
2956
|
+
const key = EIP2612_GAS_SPONSORING.key;
|
|
2957
|
+
return {
|
|
2958
|
+
[key]: {
|
|
2959
|
+
info: {
|
|
2960
|
+
description: "The facilitator accepts EIP-2612 gasless Permit to `Permit2` canonical contract.",
|
|
2961
|
+
version: "1"
|
|
2962
|
+
},
|
|
2963
|
+
schema: eip2612GasSponsoringSchema
|
|
2964
|
+
}
|
|
2965
|
+
};
|
|
2966
|
+
}
|
|
2967
|
+
|
|
2968
|
+
// src/eip2612-gas-sponsoring/facilitator.ts
|
|
2969
|
+
function extractEip2612GasSponsoringInfo(paymentPayload) {
|
|
2970
|
+
if (!paymentPayload.extensions) {
|
|
2971
|
+
return null;
|
|
2972
|
+
}
|
|
2973
|
+
const extension = paymentPayload.extensions[EIP2612_GAS_SPONSORING.key];
|
|
2974
|
+
if (!extension?.info) {
|
|
2975
|
+
return null;
|
|
2976
|
+
}
|
|
2977
|
+
const info = extension.info;
|
|
2978
|
+
if (!info.from || !info.asset || !info.spender || !info.amount || !info.nonce || !info.deadline || !info.signature || !info.version) {
|
|
2979
|
+
return null;
|
|
2980
|
+
}
|
|
2981
|
+
return info;
|
|
2982
|
+
}
|
|
2983
|
+
function validateEip2612GasSponsoringInfo(info) {
|
|
2984
|
+
const addressPattern = /^0x[a-fA-F0-9]{40}$/;
|
|
2985
|
+
const numericPattern = /^[0-9]+$/;
|
|
2986
|
+
const hexPattern = /^0x[a-fA-F0-9]+$/;
|
|
2987
|
+
const versionPattern = /^[0-9]+(\.[0-9]+)*$/;
|
|
2988
|
+
return addressPattern.test(info.from) && addressPattern.test(info.asset) && addressPattern.test(info.spender) && numericPattern.test(info.amount) && numericPattern.test(info.nonce) && numericPattern.test(info.deadline) && hexPattern.test(info.signature) && versionPattern.test(info.version);
|
|
2989
|
+
}
|
|
2990
|
+
|
|
2991
|
+
// src/erc20-approval-gas-sponsoring/types.ts
|
|
2992
|
+
var ERC20_APPROVAL_GAS_SPONSORING = {
|
|
2993
|
+
key: "erc20ApprovalGasSponsoring"
|
|
2994
|
+
};
|
|
2995
|
+
var ERC20_APPROVAL_GAS_SPONSORING_VERSION = "1";
|
|
2996
|
+
function createErc20ApprovalGasSponsoringExtension(signer, signerForNetwork) {
|
|
2997
|
+
return { ...ERC20_APPROVAL_GAS_SPONSORING, signer, signerForNetwork };
|
|
2998
|
+
}
|
|
2999
|
+
|
|
3000
|
+
// src/erc20-approval-gas-sponsoring/resourceService.ts
|
|
3001
|
+
var erc20ApprovalGasSponsoringSchema = {
|
|
3002
|
+
$schema: "https://json-schema.org/draft/2020-12/schema",
|
|
3003
|
+
type: "object",
|
|
3004
|
+
properties: {
|
|
3005
|
+
from: {
|
|
3006
|
+
type: "string",
|
|
3007
|
+
pattern: "^0x[a-fA-F0-9]{40}$",
|
|
3008
|
+
description: "The address of the sender (token owner)."
|
|
3009
|
+
},
|
|
3010
|
+
asset: {
|
|
3011
|
+
type: "string",
|
|
3012
|
+
pattern: "^0x[a-fA-F0-9]{40}$",
|
|
3013
|
+
description: "The address of the ERC-20 token contract."
|
|
3014
|
+
},
|
|
3015
|
+
spender: {
|
|
3016
|
+
type: "string",
|
|
3017
|
+
pattern: "^0x[a-fA-F0-9]{40}$",
|
|
3018
|
+
description: "The address of the spender (Canonical Permit2)."
|
|
3019
|
+
},
|
|
3020
|
+
amount: {
|
|
3021
|
+
type: "string",
|
|
3022
|
+
pattern: "^[0-9]+$",
|
|
3023
|
+
description: "The amount approved (uint256). Always MaxUint256."
|
|
3024
|
+
},
|
|
3025
|
+
signedTransaction: {
|
|
3026
|
+
type: "string",
|
|
3027
|
+
pattern: "^0x[a-fA-F0-9]+$",
|
|
3028
|
+
description: "The RLP-encoded signed EIP-1559 transaction as a hex string."
|
|
3029
|
+
},
|
|
3030
|
+
version: {
|
|
3031
|
+
type: "string",
|
|
3032
|
+
pattern: "^[0-9]+(\\.[0-9]+)*$",
|
|
3033
|
+
description: "Schema version identifier."
|
|
3034
|
+
}
|
|
3035
|
+
},
|
|
3036
|
+
required: ["from", "asset", "spender", "amount", "signedTransaction", "version"]
|
|
3037
|
+
};
|
|
3038
|
+
function declareErc20ApprovalGasSponsoringExtension() {
|
|
3039
|
+
const key = ERC20_APPROVAL_GAS_SPONSORING.key;
|
|
3040
|
+
return {
|
|
3041
|
+
[key]: {
|
|
3042
|
+
info: {
|
|
3043
|
+
description: "The facilitator broadcasts a pre-signed ERC-20 approve() transaction to grant Permit2 allowance.",
|
|
3044
|
+
version: ERC20_APPROVAL_GAS_SPONSORING_VERSION
|
|
3045
|
+
},
|
|
3046
|
+
schema: erc20ApprovalGasSponsoringSchema
|
|
3047
|
+
}
|
|
3048
|
+
};
|
|
3049
|
+
}
|
|
3050
|
+
|
|
3051
|
+
// src/erc20-approval-gas-sponsoring/facilitator.ts
|
|
3052
|
+
var import__3 = __toESM(require("ajv/dist/2020.js"));
|
|
3053
|
+
function extractErc20ApprovalGasSponsoringInfo(paymentPayload) {
|
|
3054
|
+
if (!paymentPayload.extensions) {
|
|
3055
|
+
return null;
|
|
3056
|
+
}
|
|
3057
|
+
const extension = paymentPayload.extensions[ERC20_APPROVAL_GAS_SPONSORING.key];
|
|
3058
|
+
if (!extension?.info) {
|
|
3059
|
+
return null;
|
|
3060
|
+
}
|
|
3061
|
+
const info = extension.info;
|
|
3062
|
+
if (!info.from || !info.asset || !info.spender || !info.amount || !info.signedTransaction || !info.version) {
|
|
3063
|
+
return null;
|
|
3064
|
+
}
|
|
3065
|
+
return info;
|
|
3066
|
+
}
|
|
3067
|
+
function validateErc20ApprovalGasSponsoringInfo(info) {
|
|
3068
|
+
const ajv = new import__3.default({ strict: false, allErrors: true });
|
|
3069
|
+
const validate = ajv.compile(erc20ApprovalGasSponsoringSchema);
|
|
3070
|
+
return validate(info);
|
|
3071
|
+
}
|
|
3072
|
+
|
|
3073
|
+
// src/builder-code/types.ts
|
|
3074
|
+
var BUILDER_CODE = "builder-code";
|
|
3075
|
+
var ERC_8021_MARKER = "80218021802180218021802180218021";
|
|
3076
|
+
var SCHEMA_2_ID = 2;
|
|
3077
|
+
var BUILDER_CODE_PATTERN = /^[a-z0-9_]{1,32}$/;
|
|
3078
|
+
|
|
3079
|
+
// src/builder-code/cbor.ts
|
|
3080
|
+
function normalizeServiceCodes(s) {
|
|
3081
|
+
if (typeof s === "string") return [s];
|
|
3082
|
+
if (Array.isArray(s)) return s;
|
|
3083
|
+
return [];
|
|
3084
|
+
}
|
|
3085
|
+
function encodeCborMap(data) {
|
|
3086
|
+
const entries = [];
|
|
3087
|
+
let mapSize = 0;
|
|
3088
|
+
if (data.a) {
|
|
3089
|
+
mapSize++;
|
|
3090
|
+
entries.push(encodeCborString("a"));
|
|
3091
|
+
entries.push(encodeCborString(data.a));
|
|
3092
|
+
}
|
|
3093
|
+
if (data.w) {
|
|
3094
|
+
mapSize++;
|
|
3095
|
+
entries.push(encodeCborString("w"));
|
|
3096
|
+
entries.push(encodeCborString(data.w));
|
|
3097
|
+
}
|
|
3098
|
+
const serviceCodes = normalizeServiceCodes(data.s);
|
|
3099
|
+
if (serviceCodes.length > 0) {
|
|
3100
|
+
mapSize++;
|
|
3101
|
+
entries.push(encodeCborString("s"));
|
|
3102
|
+
entries.push(encodeCborArray(serviceCodes));
|
|
3103
|
+
}
|
|
3104
|
+
const header = encodeCborMajorType(5, mapSize);
|
|
3105
|
+
const totalLength = header.length + entries.reduce((sum, e) => sum + e.length, 0);
|
|
3106
|
+
const result = new Uint8Array(totalLength);
|
|
3107
|
+
let offset = 0;
|
|
3108
|
+
result.set(header, offset);
|
|
3109
|
+
offset += header.length;
|
|
3110
|
+
for (const entry of entries) {
|
|
3111
|
+
result.set(entry, offset);
|
|
3112
|
+
offset += entry.length;
|
|
3113
|
+
}
|
|
3114
|
+
return result;
|
|
3115
|
+
}
|
|
3116
|
+
function encodeCborString(value) {
|
|
3117
|
+
const encoded = new TextEncoder().encode(value);
|
|
3118
|
+
const header = encodeCborMajorType(3, encoded.length);
|
|
3119
|
+
const result = new Uint8Array(header.length + encoded.length);
|
|
3120
|
+
result.set(header, 0);
|
|
3121
|
+
result.set(encoded, header.length);
|
|
3122
|
+
return result;
|
|
3123
|
+
}
|
|
3124
|
+
function encodeCborArray(values) {
|
|
3125
|
+
const header = encodeCborMajorType(4, values.length);
|
|
3126
|
+
const encodedValues = values.map(encodeCborString);
|
|
3127
|
+
const totalLength = header.length + encodedValues.reduce((sum, e) => sum + e.length, 0);
|
|
3128
|
+
const result = new Uint8Array(totalLength);
|
|
3129
|
+
let offset = 0;
|
|
3130
|
+
result.set(header, offset);
|
|
3131
|
+
offset += header.length;
|
|
3132
|
+
for (const encoded of encodedValues) {
|
|
3133
|
+
result.set(encoded, offset);
|
|
3134
|
+
offset += encoded.length;
|
|
3135
|
+
}
|
|
3136
|
+
return result;
|
|
3137
|
+
}
|
|
3138
|
+
function encodeCborMajorType(majorType, value) {
|
|
3139
|
+
const mt = majorType << 5;
|
|
3140
|
+
if (value <= 23) {
|
|
3141
|
+
return new Uint8Array([mt | value]);
|
|
3142
|
+
}
|
|
3143
|
+
if (value <= 255) {
|
|
3144
|
+
return new Uint8Array([mt | 24, value]);
|
|
3145
|
+
}
|
|
3146
|
+
if (value <= 65535) {
|
|
3147
|
+
return new Uint8Array([mt | 25, value >> 8 & 255, value & 255]);
|
|
3148
|
+
}
|
|
3149
|
+
throw new Error(`CBOR value too large: ${value}`);
|
|
3150
|
+
}
|
|
3151
|
+
function encodeBuilderCodeSuffix(data) {
|
|
3152
|
+
const cborBytes = encodeCborMap(data);
|
|
3153
|
+
const cborLength = cborBytes.length;
|
|
3154
|
+
const lengthHigh = cborLength >> 8 & 255;
|
|
3155
|
+
const lengthLow = cborLength & 255;
|
|
3156
|
+
const suffixBytes = new Uint8Array(cborLength + 2 + 1 + 16);
|
|
3157
|
+
let offset = 0;
|
|
3158
|
+
suffixBytes.set(cborBytes, offset);
|
|
3159
|
+
offset += cborLength;
|
|
3160
|
+
suffixBytes[offset++] = lengthHigh;
|
|
3161
|
+
suffixBytes[offset++] = lengthLow;
|
|
3162
|
+
suffixBytes[offset++] = SCHEMA_2_ID;
|
|
3163
|
+
const markerBytes = hexToBytes(ERC_8021_MARKER);
|
|
3164
|
+
suffixBytes.set(markerBytes, offset);
|
|
3165
|
+
return `0x${bytesToHex(suffixBytes)}`;
|
|
3166
|
+
}
|
|
3167
|
+
function hexToBytes(hex) {
|
|
3168
|
+
const bytes = new Uint8Array(hex.length / 2);
|
|
3169
|
+
for (let i = 0; i < hex.length; i += 2) {
|
|
3170
|
+
bytes[i / 2] = parseInt(hex.substring(i, i + 2), 16);
|
|
3171
|
+
}
|
|
3172
|
+
return bytes;
|
|
3173
|
+
}
|
|
3174
|
+
function bytesToHex(bytes) {
|
|
3175
|
+
return Array.from(bytes).map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
3176
|
+
}
|
|
3177
|
+
function parseBuilderCodeSuffixFromCalldata(calldata) {
|
|
3178
|
+
const hex = calldata.startsWith("0x") ? calldata.slice(2) : calldata;
|
|
3179
|
+
const markerPos = hex.lastIndexOf(ERC_8021_MARKER.toLowerCase());
|
|
3180
|
+
if (markerPos < 6) {
|
|
3181
|
+
return void 0;
|
|
3182
|
+
}
|
|
3183
|
+
if (parseInt(hex.slice(markerPos - 2, markerPos), 16) !== SCHEMA_2_ID) {
|
|
3184
|
+
return void 0;
|
|
3185
|
+
}
|
|
3186
|
+
const cborLength = parseInt(hex.slice(markerPos - 6, markerPos - 2), 16);
|
|
3187
|
+
const suffixStart = markerPos - 6 - cborLength * 2;
|
|
3188
|
+
if (suffixStart < 0 || suffixStart + (cborLength + 19) * 2 !== hex.length) {
|
|
3189
|
+
return void 0;
|
|
3190
|
+
}
|
|
3191
|
+
const bytes = hexToBytes(hex.slice(suffixStart, markerPos - 6));
|
|
3192
|
+
let o = 0;
|
|
3193
|
+
if (bytes[o] >> 5 !== 5) {
|
|
3194
|
+
return void 0;
|
|
3195
|
+
}
|
|
3196
|
+
const mapInfo = bytes[o++] & 31;
|
|
3197
|
+
const mapSize = mapInfo <= 23 ? mapInfo : mapInfo === 24 ? bytes[o++] : void 0;
|
|
3198
|
+
if (mapSize === void 0) {
|
|
3199
|
+
return void 0;
|
|
3200
|
+
}
|
|
3201
|
+
const result = {};
|
|
3202
|
+
for (let entry = 0; entry < mapSize; entry++) {
|
|
3203
|
+
if (bytes[o] >> 5 !== 3) {
|
|
3204
|
+
return void 0;
|
|
3205
|
+
}
|
|
3206
|
+
const keyInfo = bytes[o++] & 31;
|
|
3207
|
+
const keyLen = keyInfo <= 23 ? keyInfo : keyInfo === 24 ? bytes[o++] : void 0;
|
|
3208
|
+
if (keyLen === void 0) {
|
|
3209
|
+
return void 0;
|
|
3210
|
+
}
|
|
3211
|
+
const key = new TextDecoder().decode(bytes.subarray(o, o + keyLen));
|
|
3212
|
+
o += keyLen;
|
|
3213
|
+
if (key === "a" || key === "w") {
|
|
3214
|
+
if (bytes[o] >> 5 !== 3) {
|
|
3215
|
+
return void 0;
|
|
3216
|
+
}
|
|
3217
|
+
const valueInfo = bytes[o++] & 31;
|
|
3218
|
+
const valueLen = valueInfo <= 23 ? valueInfo : valueInfo === 24 ? bytes[o++] : void 0;
|
|
3219
|
+
if (valueLen === void 0) {
|
|
3220
|
+
return void 0;
|
|
3221
|
+
}
|
|
3222
|
+
result[key] = new TextDecoder().decode(bytes.subarray(o, o + valueLen));
|
|
3223
|
+
o += valueLen;
|
|
3224
|
+
continue;
|
|
3225
|
+
}
|
|
3226
|
+
if (key === "s") {
|
|
3227
|
+
if (bytes[o] >> 5 !== 4) {
|
|
3228
|
+
return void 0;
|
|
3229
|
+
}
|
|
3230
|
+
const arrayInfo = bytes[o++] & 31;
|
|
3231
|
+
const arraySize = arrayInfo <= 23 ? arrayInfo : arrayInfo === 24 ? bytes[o++] : void 0;
|
|
3232
|
+
if (arraySize === void 0) {
|
|
3233
|
+
return void 0;
|
|
3234
|
+
}
|
|
3235
|
+
const codes = [];
|
|
3236
|
+
for (let i = 0; i < arraySize; i++) {
|
|
3237
|
+
if (bytes[o] >> 5 !== 3) {
|
|
3238
|
+
return void 0;
|
|
3239
|
+
}
|
|
3240
|
+
const itemInfo = bytes[o++] & 31;
|
|
3241
|
+
const itemLen = itemInfo <= 23 ? itemInfo : itemInfo === 24 ? bytes[o++] : void 0;
|
|
3242
|
+
if (itemLen === void 0) {
|
|
3243
|
+
return void 0;
|
|
3244
|
+
}
|
|
3245
|
+
codes.push(new TextDecoder().decode(bytes.subarray(o, o + itemLen)));
|
|
3246
|
+
o += itemLen;
|
|
3247
|
+
}
|
|
3248
|
+
if (codes.length > 0) {
|
|
3249
|
+
result.s = codes;
|
|
3250
|
+
}
|
|
3251
|
+
continue;
|
|
3252
|
+
}
|
|
3253
|
+
return void 0;
|
|
3254
|
+
}
|
|
3255
|
+
return result;
|
|
3256
|
+
}
|
|
3257
|
+
|
|
3258
|
+
// src/builder-code/server.ts
|
|
3259
|
+
var BUILDER_CODE_SCHEMA = {
|
|
3260
|
+
$schema: "https://json-schema.org/draft/2020-12/schema",
|
|
3261
|
+
type: "object",
|
|
3262
|
+
properties: {
|
|
3263
|
+
a: {
|
|
3264
|
+
type: "string",
|
|
3265
|
+
pattern: "^[a-z0-9_]{1,32}$",
|
|
3266
|
+
description: "App builder code"
|
|
3267
|
+
},
|
|
3268
|
+
w: {
|
|
3269
|
+
type: "string",
|
|
3270
|
+
pattern: "^[a-z0-9_]{1,32}$",
|
|
3271
|
+
description: "Wallet builder code"
|
|
3272
|
+
},
|
|
3273
|
+
s: {
|
|
3274
|
+
type: "array",
|
|
3275
|
+
items: {
|
|
3276
|
+
type: "string",
|
|
3277
|
+
pattern: "^[a-z0-9_]{1,32}$"
|
|
3278
|
+
},
|
|
3279
|
+
description: "Service builder codes"
|
|
3280
|
+
}
|
|
3281
|
+
},
|
|
3282
|
+
additionalProperties: false
|
|
3283
|
+
};
|
|
3284
|
+
function declareBuilderCodeExtension(appCode) {
|
|
3285
|
+
if (!BUILDER_CODE_PATTERN.test(appCode)) {
|
|
3286
|
+
throw new Error(
|
|
3287
|
+
`Invalid builder code: "${appCode}". Must be 1-32 characters, lowercase alphanumeric and underscores only.`
|
|
3288
|
+
);
|
|
3289
|
+
}
|
|
3290
|
+
return {
|
|
3291
|
+
info: { a: appCode },
|
|
3292
|
+
schema: BUILDER_CODE_SCHEMA
|
|
3293
|
+
};
|
|
3294
|
+
}
|
|
3295
|
+
var builderCodeResourceServerExtension = {
|
|
3296
|
+
key: BUILDER_CODE
|
|
3297
|
+
};
|
|
3298
|
+
|
|
3299
|
+
// src/builder-code/client.ts
|
|
3300
|
+
var BuilderCodeClientExtension = class {
|
|
3301
|
+
/**
|
|
3302
|
+
* Creates a client extension that attaches the given service code(s) to payments.
|
|
3303
|
+
*
|
|
3304
|
+
* Accepts a single code or an array of codes so layered clients (e.g. an MCP
|
|
3305
|
+
* middleware) can attribute multiple participants. Codes are normalized to an
|
|
3306
|
+
* array and sent as the `s` field.
|
|
3307
|
+
*
|
|
3308
|
+
* @param serviceCodes - Client service code(s) (`s`), each 1-32 lowercase alphanumeric/underscore characters
|
|
3309
|
+
*/
|
|
3310
|
+
constructor(serviceCodes) {
|
|
3311
|
+
this.key = BUILDER_CODE;
|
|
3312
|
+
const codes = Array.isArray(serviceCodes) ? serviceCodes : [serviceCodes];
|
|
3313
|
+
for (const code of codes) {
|
|
3314
|
+
if (!BUILDER_CODE_PATTERN.test(code)) {
|
|
3315
|
+
throw new Error(
|
|
3316
|
+
`Invalid builder code: "${code}". Must be 1-32 characters, lowercase alphanumeric and underscores only.`
|
|
3317
|
+
);
|
|
3318
|
+
}
|
|
3319
|
+
}
|
|
3320
|
+
this.serviceCodes = codes;
|
|
3321
|
+
}
|
|
3322
|
+
/**
|
|
3323
|
+
* Attaches this client's service code(s) (`s`).
|
|
3324
|
+
*
|
|
3325
|
+
* @param payload - Payment payload to enrich
|
|
3326
|
+
* @param _ - Server payment requirements; core merges server extension data
|
|
3327
|
+
* @returns Payment payload with builder-code extension data
|
|
3328
|
+
*/
|
|
3329
|
+
async enrichPaymentPayload(payload, _) {
|
|
3330
|
+
return {
|
|
3331
|
+
...payload,
|
|
3332
|
+
extensions: {
|
|
3333
|
+
...payload.extensions,
|
|
3334
|
+
[BUILDER_CODE]: { info: { s: this.serviceCodes } }
|
|
3335
|
+
}
|
|
3336
|
+
};
|
|
3337
|
+
}
|
|
3338
|
+
};
|
|
3339
|
+
|
|
3340
|
+
// src/builder-code/facilitator.ts
|
|
3341
|
+
function extractClientExtension(extensions) {
|
|
3342
|
+
const info = extensions?.[BUILDER_CODE]?.info;
|
|
3343
|
+
if (typeof info !== "object" || info === null || Array.isArray(info)) return void 0;
|
|
3344
|
+
return info;
|
|
3345
|
+
}
|
|
3346
|
+
function resolveServiceCodes(raw) {
|
|
3347
|
+
const candidates = typeof raw === "string" ? [raw] : Array.isArray(raw) ? raw : [];
|
|
3348
|
+
return candidates.filter(
|
|
3349
|
+
(v) => typeof v === "string" && BUILDER_CODE_PATTERN.test(v)
|
|
3350
|
+
);
|
|
3351
|
+
}
|
|
3352
|
+
var BuilderCodeFacilitatorExtension = class {
|
|
3353
|
+
/**
|
|
3354
|
+
* Creates a facilitator extension that encodes builder-code attribution at settlement.
|
|
3355
|
+
*
|
|
3356
|
+
* @param config - Optional facilitator builder-code configuration (wallet code `w`)
|
|
3357
|
+
*/
|
|
3358
|
+
constructor(config = {}) {
|
|
3359
|
+
this.key = BUILDER_CODE;
|
|
3360
|
+
if (config.builderCode && !BUILDER_CODE_PATTERN.test(config.builderCode)) {
|
|
3361
|
+
throw new Error(
|
|
3362
|
+
`Invalid builder code: "${config.builderCode}". Must be 1-32 characters, lowercase alphanumeric and underscores only.`
|
|
3363
|
+
);
|
|
3364
|
+
}
|
|
3365
|
+
this.config = config;
|
|
3366
|
+
}
|
|
3367
|
+
/**
|
|
3368
|
+
* Builds the ERC-8021 Schema 2 calldata suffix for a settlement transaction.
|
|
3369
|
+
*
|
|
3370
|
+
* - `a` and `s` are read from the client's payment payload extensions.
|
|
3371
|
+
* - `w` is the facilitator's own code when configured.
|
|
3372
|
+
*
|
|
3373
|
+
* @param ctx - Settlement context with payment-payload extensions
|
|
3374
|
+
* @returns Hex-encoded ERC-8021 builder-code calldata suffix, or undefined when no attribution is present
|
|
3375
|
+
*/
|
|
3376
|
+
buildDataSuffix(ctx) {
|
|
3377
|
+
const clientExt = extractClientExtension(ctx.paymentPayload.extensions);
|
|
3378
|
+
const a = typeof clientExt?.a === "string" && BUILDER_CODE_PATTERN.test(clientExt.a) ? clientExt.a : void 0;
|
|
3379
|
+
const s = resolveServiceCodes(clientExt?.s);
|
|
3380
|
+
const data = {
|
|
3381
|
+
...this.config.builderCode && { w: this.config.builderCode },
|
|
3382
|
+
...a && { a },
|
|
3383
|
+
...s.length > 0 && { s }
|
|
3384
|
+
};
|
|
3385
|
+
if (!data.a && !data.w && (!data.s || Array.isArray(data.s) && data.s.length === 0)) {
|
|
3386
|
+
return void 0;
|
|
3387
|
+
}
|
|
3388
|
+
return encodeBuilderCodeSuffix(data);
|
|
3389
|
+
}
|
|
3390
|
+
};
|
|
3391
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
3392
|
+
0 && (module.exports = {
|
|
3393
|
+
BAZAAR,
|
|
3394
|
+
BUILDER_CODE,
|
|
3395
|
+
BUILDER_CODE_PATTERN,
|
|
3396
|
+
BUILDER_CODE_SCHEMA,
|
|
3397
|
+
BuilderCodeClientExtension,
|
|
3398
|
+
BuilderCodeFacilitatorExtension,
|
|
3399
|
+
EIP2612_GAS_SPONSORING,
|
|
3400
|
+
ERC20_APPROVAL_GAS_SPONSORING,
|
|
3401
|
+
ERC20_APPROVAL_GAS_SPONSORING_VERSION,
|
|
3402
|
+
ERC_8021_MARKER,
|
|
3403
|
+
InMemorySIWxStorage,
|
|
3404
|
+
OFFER_RECEIPT,
|
|
3405
|
+
OFFER_TYPES,
|
|
3406
|
+
PAYMENT_IDENTIFIER,
|
|
3407
|
+
PAYMENT_ID_MAX_LENGTH,
|
|
3408
|
+
PAYMENT_ID_MIN_LENGTH,
|
|
3409
|
+
PAYMENT_ID_PATTERN,
|
|
3410
|
+
RECEIPT_TYPES,
|
|
3411
|
+
SCHEMA_2_ID,
|
|
3412
|
+
SIGN_IN_WITH_X,
|
|
3413
|
+
SIWxPayloadSchema,
|
|
3414
|
+
SOLANA_DEVNET,
|
|
3415
|
+
SOLANA_MAINNET,
|
|
3416
|
+
SOLANA_TESTNET,
|
|
3417
|
+
appendPaymentIdentifierToExtensions,
|
|
3418
|
+
bazaarResourceServerExtension,
|
|
3419
|
+
buildSIWxSchema,
|
|
3420
|
+
builderCodeResourceServerExtension,
|
|
3421
|
+
canonicalize,
|
|
3422
|
+
checkIfBazaarNeeded,
|
|
3423
|
+
convertNetworkStringToCAIP2,
|
|
3424
|
+
createEIP712OfferReceiptIssuer,
|
|
3425
|
+
createErc20ApprovalGasSponsoringExtension,
|
|
3426
|
+
createJWS,
|
|
3427
|
+
createJWSOfferReceiptIssuer,
|
|
3428
|
+
createOfferDomain,
|
|
3429
|
+
createOfferEIP712,
|
|
3430
|
+
createOfferJWS,
|
|
3431
|
+
createOfferReceiptExtension,
|
|
3432
|
+
createReceiptDomain,
|
|
3433
|
+
createReceiptEIP712,
|
|
3434
|
+
createReceiptJWS,
|
|
3435
|
+
createSIWxClientExtension,
|
|
3436
|
+
createSIWxClientHook,
|
|
3437
|
+
createSIWxMessage,
|
|
3438
|
+
createSIWxPayload,
|
|
3439
|
+
createSIWxRequestHook,
|
|
3440
|
+
createSIWxResourceServerExtension,
|
|
3441
|
+
createSIWxSettleHook,
|
|
3442
|
+
declareBuilderCodeExtension,
|
|
3443
|
+
declareDiscoveryExtension,
|
|
3444
|
+
declareEip2612GasSponsoringExtension,
|
|
3445
|
+
declareErc20ApprovalGasSponsoringExtension,
|
|
3446
|
+
declareOfferReceiptExtension,
|
|
3447
|
+
declarePaymentIdentifierExtension,
|
|
3448
|
+
declareSIWxExtension,
|
|
3449
|
+
decodeBase58,
|
|
3450
|
+
decodeSignedOffers,
|
|
3451
|
+
encodeBase58,
|
|
3452
|
+
encodeBuilderCodeSuffix,
|
|
3453
|
+
encodeSIWxHeader,
|
|
3454
|
+
erc20ApprovalGasSponsoringSchema,
|
|
3455
|
+
extractAndValidatePaymentIdentifier,
|
|
3456
|
+
extractChainIdFromCAIP2,
|
|
3457
|
+
extractDiscoveryInfo,
|
|
3458
|
+
extractDiscoveryInfoFromExtension,
|
|
3459
|
+
extractDiscoveryInfoV1,
|
|
3460
|
+
extractEIP155ChainId,
|
|
3461
|
+
extractEVMChainId,
|
|
3462
|
+
extractEip2612GasSponsoringInfo,
|
|
3463
|
+
extractErc20ApprovalGasSponsoringInfo,
|
|
3464
|
+
extractJWSHeader,
|
|
3465
|
+
extractJWSPayload,
|
|
3466
|
+
extractOfferPayload,
|
|
3467
|
+
extractOffersFromPaymentRequired,
|
|
3468
|
+
extractPaymentIdentifier,
|
|
3469
|
+
extractPublicKeyFromKid,
|
|
3470
|
+
extractReceiptFromResponse,
|
|
3471
|
+
extractReceiptPayload,
|
|
3472
|
+
extractResourceMetadataV1,
|
|
3473
|
+
extractSolanaChainReference,
|
|
3474
|
+
findAcceptsObjectFromSignedOffer,
|
|
3475
|
+
formatSIWEMessage,
|
|
3476
|
+
formatSIWSMessage,
|
|
3477
|
+
generatePaymentId,
|
|
3478
|
+
getCanonicalBytes,
|
|
3479
|
+
getEVMAddress,
|
|
3480
|
+
getSolanaAddress,
|
|
3481
|
+
hasPaymentIdentifier,
|
|
3482
|
+
hashCanonical,
|
|
3483
|
+
hashOfferTypedData,
|
|
3484
|
+
hashReceiptTypedData,
|
|
3485
|
+
isBodyExtensionConfig,
|
|
3486
|
+
isDiscoverableV1,
|
|
3487
|
+
isEIP712SignedOffer,
|
|
3488
|
+
isEIP712SignedReceipt,
|
|
3489
|
+
isEIP712Signer,
|
|
3490
|
+
isEVMSigner,
|
|
3491
|
+
isJWSSignedOffer,
|
|
3492
|
+
isJWSSignedReceipt,
|
|
3493
|
+
isJWSSigner,
|
|
3494
|
+
isMcpExtensionConfig,
|
|
3495
|
+
isPaymentIdentifierExtension,
|
|
3496
|
+
isPaymentIdentifierRequired,
|
|
3497
|
+
isQueryExtensionConfig,
|
|
3498
|
+
isSolanaSigner,
|
|
3499
|
+
isValidIconUrl,
|
|
3500
|
+
isValidPaymentId,
|
|
3501
|
+
isValidRouteTemplate,
|
|
3502
|
+
isValidServiceName,
|
|
3503
|
+
parseBuilderCodeSuffixFromCalldata,
|
|
3504
|
+
parseSIWxHeader,
|
|
3505
|
+
paymentIdentifierResourceServerExtension,
|
|
3506
|
+
paymentIdentifierSchema,
|
|
3507
|
+
prepareOfferForEIP712,
|
|
3508
|
+
prepareReceiptForEIP712,
|
|
3509
|
+
sanitizeResourceServiceMetadata,
|
|
3510
|
+
sanitizeTags,
|
|
3511
|
+
signEVMMessage,
|
|
3512
|
+
signOfferEIP712,
|
|
3513
|
+
signReceiptEIP712,
|
|
3514
|
+
signSolanaMessage,
|
|
3515
|
+
validateAndExtract,
|
|
3516
|
+
validateBazaarRouteExtensions,
|
|
3517
|
+
validateDiscoveryExtension,
|
|
3518
|
+
validateDiscoveryExtensionSpec,
|
|
3519
|
+
validateEip2612GasSponsoringInfo,
|
|
3520
|
+
validateErc20ApprovalGasSponsoringInfo,
|
|
3521
|
+
validatePaymentIdentifier,
|
|
3522
|
+
validatePaymentIdentifierRequirement,
|
|
3523
|
+
validateRouteTemplate,
|
|
3524
|
+
validateSIWxMessage,
|
|
3525
|
+
verifyEVMSignature,
|
|
3526
|
+
verifyOfferSignatureEIP712,
|
|
3527
|
+
verifyOfferSignatureJWS,
|
|
3528
|
+
verifyReceiptMatchesOffer,
|
|
3529
|
+
verifyReceiptSignatureEIP712,
|
|
3530
|
+
verifyReceiptSignatureJWS,
|
|
3531
|
+
verifySIWxSignature,
|
|
3532
|
+
verifySolanaSignature,
|
|
3533
|
+
withBazaar,
|
|
3534
|
+
wrapFetchWithSIWx
|
|
3535
|
+
});
|
|
3536
|
+
//# sourceMappingURL=index.js.map
|