@bosonprotocol/x402-client 0.1.0-alpha-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/LICENSE +201 -0
- package/README.md +22 -0
- package/dist/cjs/index.d.ts +240 -0
- package/dist/cjs/index.js +923 -0
- package/dist/cjs/index.js.map +1 -0
- package/dist/cjs/package.json +3 -0
- package/dist/cjs/token-auth/index.d.ts +35 -0
- package/dist/cjs/token-auth/index.js +265 -0
- package/dist/cjs/token-auth/index.js.map +1 -0
- package/dist/cjs/types-DLOroMb1.d.ts +104 -0
- package/dist/esm/chunk-JTTWAQGC.js +359 -0
- package/dist/esm/chunk-JTTWAQGC.js.map +1 -0
- package/dist/esm/index.js +557 -0
- package/dist/esm/index.js.map +1 -0
- package/dist/esm/package.json +3 -0
- package/dist/esm/token-auth/index.js +3 -0
- package/dist/esm/token-auth/index.js.map +1 -0
- package/package.json +64 -0
|
@@ -0,0 +1,557 @@
|
|
|
1
|
+
import { NoCompatibleActionError, FulfillmentValidationError, createCoreSdkFactory, UnsupportedTokenAuthError, buildAndSignTokenAuth, MaxAmountExceededError, randomUint256 } from './chunk-JTTWAQGC.js';
|
|
2
|
+
export { FulfillmentValidationError, MaxAmountExceededError, NoCompatibleActionError, UnsupportedSchemeError, UnsupportedTokenAuthError, parseChainId } from './chunk-JTTWAQGC.js';
|
|
3
|
+
import Ajv from 'ajv';
|
|
4
|
+
import { parseEscrowPaymentRequirements, DECIMAL_UINT, parseEscrowPaymentPayload } from '@bosonprotocol/x402-core/schemes/escrow';
|
|
5
|
+
import { encodeSignedPayload } from '@bosonprotocol/x402-evm/codec';
|
|
6
|
+
import { serializeTypedData, getAddress } from 'viem';
|
|
7
|
+
|
|
8
|
+
// src/action.ts
|
|
9
|
+
var FLOW_A = "boson-createOfferAndCommit";
|
|
10
|
+
var FLOW_B = "boson-createOfferCommitAndRedeem";
|
|
11
|
+
function pickAction(requirements, policy) {
|
|
12
|
+
const redeemMode = policy?.redeemMode ?? "auto";
|
|
13
|
+
const flowAOffered = requirements.actions.next.some(
|
|
14
|
+
(a) => a.id === FLOW_A && a.channels.includes("server")
|
|
15
|
+
);
|
|
16
|
+
const flowBOffered = requirements.actions.next.some(
|
|
17
|
+
(a) => a.id === FLOW_B && a.channels.includes("server")
|
|
18
|
+
);
|
|
19
|
+
if (redeemMode === "commit-and-redeem") {
|
|
20
|
+
if (flowBOffered) return FLOW_B;
|
|
21
|
+
throw new NoCompatibleActionError(
|
|
22
|
+
`policy.redeemMode='commit-and-redeem' requires '${FLOW_B}' on the server channel; requirements only advertise [${requirements.actions.next.map((a) => a.id).join(", ")}]`
|
|
23
|
+
);
|
|
24
|
+
}
|
|
25
|
+
if (redeemMode === "commit-only") {
|
|
26
|
+
if (flowAOffered) return FLOW_A;
|
|
27
|
+
throw new NoCompatibleActionError(
|
|
28
|
+
`policy.redeemMode='commit-only' requires '${FLOW_A}' on the server channel; requirements only advertise [${requirements.actions.next.map((a) => a.id).join(", ")}]`
|
|
29
|
+
);
|
|
30
|
+
}
|
|
31
|
+
if (flowAOffered) return FLOW_A;
|
|
32
|
+
if (flowBOffered) return FLOW_B;
|
|
33
|
+
throw new NoCompatibleActionError(
|
|
34
|
+
`no commit-time action ('${FLOW_A}' or '${FLOW_B}') with 'server' channel found in requirements.actions.next`
|
|
35
|
+
);
|
|
36
|
+
}
|
|
37
|
+
function resolveFulfillment(requirements, config) {
|
|
38
|
+
const required = requirements.fulfillment?.required ?? false;
|
|
39
|
+
if (!required) {
|
|
40
|
+
return void 0;
|
|
41
|
+
}
|
|
42
|
+
const fulfillmentConfig = config.fulfillment;
|
|
43
|
+
if (!fulfillmentConfig) {
|
|
44
|
+
throw new FulfillmentValidationError(
|
|
45
|
+
"requirements.fulfillment.required is true but client config did not supply a fulfillment selection"
|
|
46
|
+
);
|
|
47
|
+
}
|
|
48
|
+
const option = requirements.fulfillment?.options.find((o) => o.id === fulfillmentConfig.option);
|
|
49
|
+
if (!option) {
|
|
50
|
+
const advertised = requirements.fulfillment?.options.map((o) => o.id).join(", ") ?? "<none>";
|
|
51
|
+
throw new FulfillmentValidationError(
|
|
52
|
+
`fulfillment option '${fulfillmentConfig.option}' not advertised by requirements (advertised: ${advertised})`
|
|
53
|
+
);
|
|
54
|
+
}
|
|
55
|
+
if (option.schema === null) {
|
|
56
|
+
if (fulfillmentConfig.data !== null) {
|
|
57
|
+
throw new FulfillmentValidationError(
|
|
58
|
+
`fulfillment option '${option.id}' accepts no buyer data; use null`
|
|
59
|
+
);
|
|
60
|
+
}
|
|
61
|
+
} else {
|
|
62
|
+
const ajv = new Ajv({ allErrors: true, strict: false });
|
|
63
|
+
const validate = ajv.compile(option.schema);
|
|
64
|
+
const ok = validate(fulfillmentConfig.data);
|
|
65
|
+
if (!ok) {
|
|
66
|
+
throw new FulfillmentValidationError(
|
|
67
|
+
`fulfillment data does not match option '${option.id}' schema: ${ajv.errorsText(validate.errors)}`
|
|
68
|
+
);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
return { option: fulfillmentConfig.option, data: fulfillmentConfig.data };
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// src/response.ts
|
|
75
|
+
var HEADER_NAME = "X-PAYMENT-RESPONSE";
|
|
76
|
+
function parsePaymentResponse(response) {
|
|
77
|
+
const raw = response.headers.get(HEADER_NAME);
|
|
78
|
+
if (!raw) {
|
|
79
|
+
return void 0;
|
|
80
|
+
}
|
|
81
|
+
const json = decodeBase64(raw);
|
|
82
|
+
const parsed = JSON.parse(json);
|
|
83
|
+
const summary = { raw: parsed };
|
|
84
|
+
if (typeof parsed === "object" && parsed !== null && !Array.isArray(parsed)) {
|
|
85
|
+
const obj = parsed;
|
|
86
|
+
const exchangeId = pickString(obj, ["exchangeId", "exchange_id"]);
|
|
87
|
+
if (exchangeId !== void 0) {
|
|
88
|
+
summary.exchangeId = exchangeId;
|
|
89
|
+
}
|
|
90
|
+
if (isClientStateShape(obj.state)) {
|
|
91
|
+
summary.state = obj.state;
|
|
92
|
+
}
|
|
93
|
+
if (summary.state === void 0 && typeof obj.nextActions === "object" && obj.nextActions !== null && !Array.isArray(obj.nextActions)) {
|
|
94
|
+
const na = obj.nextActions;
|
|
95
|
+
const exchange = pickString(na, ["exchangeState"]);
|
|
96
|
+
const dispute = pickString(na, ["disputeState"]);
|
|
97
|
+
if (exchange !== void 0) {
|
|
98
|
+
let candidate;
|
|
99
|
+
if (exchange === "DISPUTED") {
|
|
100
|
+
candidate = dispute !== void 0 ? { exchange, dispute } : void 0;
|
|
101
|
+
} else {
|
|
102
|
+
candidate = { exchange };
|
|
103
|
+
}
|
|
104
|
+
if (isClientStateShape(candidate)) {
|
|
105
|
+
summary.state = candidate;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
return summary;
|
|
111
|
+
}
|
|
112
|
+
function isClientStateShape(v) {
|
|
113
|
+
if (typeof v === "string") return v.length > 0;
|
|
114
|
+
if (typeof v !== "object" || v === null || Array.isArray(v)) return false;
|
|
115
|
+
const rec = v;
|
|
116
|
+
if (typeof rec.exchange !== "string" || rec.exchange.length === 0) return false;
|
|
117
|
+
if ("dispute" in rec && rec.dispute !== void 0 && typeof rec.dispute !== "string") {
|
|
118
|
+
return false;
|
|
119
|
+
}
|
|
120
|
+
return true;
|
|
121
|
+
}
|
|
122
|
+
function decodeBase64(value) {
|
|
123
|
+
if (typeof Buffer !== "undefined") {
|
|
124
|
+
return Buffer.from(value, "base64").toString("utf8");
|
|
125
|
+
}
|
|
126
|
+
const binary = atob(value);
|
|
127
|
+
const bytes = new Uint8Array(binary.length);
|
|
128
|
+
for (let i = 0; i < binary.length; i++) {
|
|
129
|
+
bytes[i] = binary.charCodeAt(i);
|
|
130
|
+
}
|
|
131
|
+
return new TextDecoder().decode(bytes);
|
|
132
|
+
}
|
|
133
|
+
function pickString(obj, keys) {
|
|
134
|
+
for (const key of keys) {
|
|
135
|
+
const v = obj[key];
|
|
136
|
+
if (typeof v === "string" && v.length > 0) {
|
|
137
|
+
return v;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
return void 0;
|
|
141
|
+
}
|
|
142
|
+
var X402_VERSION = 2;
|
|
143
|
+
function assemblePayload({
|
|
144
|
+
requirements,
|
|
145
|
+
action,
|
|
146
|
+
tokenAuthStrategy,
|
|
147
|
+
metaTx,
|
|
148
|
+
tokenAuth,
|
|
149
|
+
fulfillment,
|
|
150
|
+
buyer
|
|
151
|
+
}) {
|
|
152
|
+
const fulfillmentSlot = fulfillment === void 0 ? void 0 : action === "boson-createOfferCommitAndRedeem" ? { option: fulfillment.option, data: fulfillment.data } : { option: fulfillment.option };
|
|
153
|
+
const payload = {
|
|
154
|
+
x402Version: X402_VERSION,
|
|
155
|
+
scheme: "escrow",
|
|
156
|
+
network: requirements.network,
|
|
157
|
+
payload: {
|
|
158
|
+
action,
|
|
159
|
+
tokenAuthStrategy,
|
|
160
|
+
offerRef: {
|
|
161
|
+
fullOffer: requirements.offer.fullOffer,
|
|
162
|
+
sellerSig: requirements.offer.sellerSig
|
|
163
|
+
},
|
|
164
|
+
buyer,
|
|
165
|
+
metaTx,
|
|
166
|
+
...tokenAuth ? { tokenAuth } : {}
|
|
167
|
+
},
|
|
168
|
+
...fulfillmentSlot ? { fulfillment: fulfillmentSlot } : {}
|
|
169
|
+
};
|
|
170
|
+
parseEscrowPaymentPayload(payload);
|
|
171
|
+
return payload;
|
|
172
|
+
}
|
|
173
|
+
function assembleAndEncodePayload(args) {
|
|
174
|
+
const payload = assemblePayload(args);
|
|
175
|
+
const json = JSON.stringify(payload);
|
|
176
|
+
if (typeof Buffer !== "undefined") {
|
|
177
|
+
return Buffer.from(json, "utf8").toString("base64");
|
|
178
|
+
}
|
|
179
|
+
const bytes = new TextEncoder().encode(json);
|
|
180
|
+
let binary = "";
|
|
181
|
+
for (const b of bytes) {
|
|
182
|
+
binary += String.fromCharCode(b);
|
|
183
|
+
}
|
|
184
|
+
return btoa(binary);
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
// src/pre-commit.ts
|
|
188
|
+
async function signCreateOfferAndCommit({
|
|
189
|
+
requirements,
|
|
190
|
+
coreSdk,
|
|
191
|
+
buyer
|
|
192
|
+
}) {
|
|
193
|
+
const nonce = randomUint256();
|
|
194
|
+
const createOfferAndCommitArgs = buildCreateOfferAndCommitArgs(requirements, buyer);
|
|
195
|
+
const signed = await coreSdk.signMetaTxCreateOfferAndCommit({
|
|
196
|
+
nonce: nonce.toString(),
|
|
197
|
+
createOfferAndCommitArgs
|
|
198
|
+
});
|
|
199
|
+
return reshape(signed, buyer, nonce);
|
|
200
|
+
}
|
|
201
|
+
async function signCreateOfferCommitAndRedeem({
|
|
202
|
+
requirements,
|
|
203
|
+
coreSdk,
|
|
204
|
+
buyer
|
|
205
|
+
}) {
|
|
206
|
+
const nonce = randomUint256();
|
|
207
|
+
const createOfferAndCommitArgs = buildCreateOfferAndCommitArgs(requirements, buyer);
|
|
208
|
+
const signed = await coreSdk.signMetaTxCreateOfferCommitAndRedeem({
|
|
209
|
+
nonce: nonce.toString(),
|
|
210
|
+
createOfferAndCommitArgs
|
|
211
|
+
});
|
|
212
|
+
return reshape(signed, buyer, nonce);
|
|
213
|
+
}
|
|
214
|
+
function buildCreateOfferAndCommitArgs(requirements, buyer) {
|
|
215
|
+
return {
|
|
216
|
+
...requirements.offer.fullOffer,
|
|
217
|
+
signature: requirements.offer.sellerSig,
|
|
218
|
+
committer: buyer
|
|
219
|
+
};
|
|
220
|
+
}
|
|
221
|
+
function reshape(signed, buyer, nonce) {
|
|
222
|
+
return {
|
|
223
|
+
from: buyer,
|
|
224
|
+
nonce: nonce.toString(),
|
|
225
|
+
functionName: signed.functionName,
|
|
226
|
+
functionSignature: signed.functionSignature,
|
|
227
|
+
sig: {
|
|
228
|
+
v: Number(signed.v),
|
|
229
|
+
r: signed.r,
|
|
230
|
+
s: signed.s
|
|
231
|
+
}
|
|
232
|
+
};
|
|
233
|
+
}
|
|
234
|
+
async function signPostCommitAction(args, deps) {
|
|
235
|
+
const nonce = randomUint256();
|
|
236
|
+
const buyer = await deps.getBuyerAddress();
|
|
237
|
+
const { coreSdk } = deps.buildCoreSdk(args.network, args.escrowAddress);
|
|
238
|
+
const signed = await callSignMetaTx(coreSdk, args, nonce);
|
|
239
|
+
const metaTx = {
|
|
240
|
+
from: buyer,
|
|
241
|
+
nonce: nonce.toString(),
|
|
242
|
+
functionName: signed.functionName,
|
|
243
|
+
functionSignature: signed.functionSignature,
|
|
244
|
+
sig: {
|
|
245
|
+
v: Number(signed.v),
|
|
246
|
+
r: signed.r,
|
|
247
|
+
s: signed.s
|
|
248
|
+
}
|
|
249
|
+
};
|
|
250
|
+
return { metaTx, signedPayload: encodeSignedPayload(metaTx) };
|
|
251
|
+
}
|
|
252
|
+
async function callSignMetaTx(coreSdk, args, nonce) {
|
|
253
|
+
const nonceStr = nonce.toString();
|
|
254
|
+
const exchangeId = args.exchangeId;
|
|
255
|
+
switch (args.actionId) {
|
|
256
|
+
case "boson-redeem":
|
|
257
|
+
return coreSdk.signMetaTxRedeemVoucher({ nonce: nonceStr, exchangeId });
|
|
258
|
+
case "boson-cancelVoucher":
|
|
259
|
+
return coreSdk.signMetaTxCancelVoucher({ nonce: nonceStr, exchangeId });
|
|
260
|
+
case "boson-completeExchange":
|
|
261
|
+
return coreSdk.signMetaTxCompleteExchange({ nonce: nonceStr, exchangeId });
|
|
262
|
+
case "boson-raiseDispute":
|
|
263
|
+
return coreSdk.signMetaTxRaiseDispute({ nonce: nonceStr, exchangeId });
|
|
264
|
+
case "boson-retractDispute":
|
|
265
|
+
return coreSdk.signMetaTxRetractDispute({ nonce: nonceStr, exchangeId });
|
|
266
|
+
case "boson-escalateDispute":
|
|
267
|
+
return coreSdk.signMetaTxEscalateDispute({ nonce: nonceStr, exchangeId });
|
|
268
|
+
case "boson-resolveDispute":
|
|
269
|
+
return coreSdk.signMetaTxResolveDispute({
|
|
270
|
+
nonce: nonceStr,
|
|
271
|
+
exchangeId,
|
|
272
|
+
buyerPercent: args.buyerPercent,
|
|
273
|
+
counterpartySig: args.counterpartySig
|
|
274
|
+
});
|
|
275
|
+
default: {
|
|
276
|
+
const _exhaustive = args;
|
|
277
|
+
throw new Error(
|
|
278
|
+
`x402-client: unsupported post-commit action '${_exhaustive.actionId}'`
|
|
279
|
+
);
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
function normalizeEntityId(value) {
|
|
284
|
+
if (typeof value === "bigint") {
|
|
285
|
+
if (value < 0n) throw new Error(`entityId must be non-negative, got ${value.toString()}`);
|
|
286
|
+
return value.toString();
|
|
287
|
+
}
|
|
288
|
+
if (typeof value === "number") {
|
|
289
|
+
if (!Number.isFinite(value) || !Number.isInteger(value) || value < 0) {
|
|
290
|
+
throw new Error(`entityId must be a non-negative integer, got ${String(value)}`);
|
|
291
|
+
}
|
|
292
|
+
return value.toString();
|
|
293
|
+
}
|
|
294
|
+
const trimmed = value.trim();
|
|
295
|
+
if (trimmed.length === 0 || !DECIMAL_UINT.test(trimmed)) {
|
|
296
|
+
throw new Error(`entityId must be a decimal non-negative integer string, got "${value}"`);
|
|
297
|
+
}
|
|
298
|
+
return trimmed;
|
|
299
|
+
}
|
|
300
|
+
async function signWithdrawFunds(args, deps) {
|
|
301
|
+
const normalised = {
|
|
302
|
+
...args,
|
|
303
|
+
entityId: normalizeEntityId(args.entityId)
|
|
304
|
+
};
|
|
305
|
+
const { coreSdk } = deps.buildCoreSdk(normalised.network, normalised.escrowAddress);
|
|
306
|
+
const from = await deps.getSignerAddress();
|
|
307
|
+
return signWithdrawFundsInternal(normalised, coreSdk, from);
|
|
308
|
+
}
|
|
309
|
+
async function signWithdrawAllAvailableFunds(args, deps) {
|
|
310
|
+
const { coreSdk } = deps.buildCoreSdk(args.network, args.escrowAddress);
|
|
311
|
+
const sdk = coreSdk;
|
|
312
|
+
const from = await deps.getSignerAddress();
|
|
313
|
+
const entityId = await resolveEntityIdClientSide(sdk, args);
|
|
314
|
+
const funds = await sdk.getFunds({ fundsFilter: { accountId: entityId } });
|
|
315
|
+
const nonZero = funds.filter((f) => f.availableAmount !== "0" && BigInt(f.availableAmount) > 0n);
|
|
316
|
+
if (nonZero.length === 0) {
|
|
317
|
+
throw new Error(
|
|
318
|
+
`signWithdrawAllAvailableFunds: entity ${entityId} has no available funds to withdraw`
|
|
319
|
+
);
|
|
320
|
+
}
|
|
321
|
+
return signWithdrawFundsInternal(
|
|
322
|
+
{
|
|
323
|
+
entityId,
|
|
324
|
+
network: args.network,
|
|
325
|
+
escrowAddress: args.escrowAddress,
|
|
326
|
+
tokenList: nonZero.map((f) => f.token.address),
|
|
327
|
+
tokenAmounts: nonZero.map((f) => f.availableAmount)
|
|
328
|
+
},
|
|
329
|
+
sdk,
|
|
330
|
+
from
|
|
331
|
+
);
|
|
332
|
+
}
|
|
333
|
+
async function signWithdrawFundsInternal(args, coreSdk, from) {
|
|
334
|
+
if (args.tokenList.length !== args.tokenAmounts.length) {
|
|
335
|
+
throw new Error(
|
|
336
|
+
`signWithdrawFunds: tokenList (${args.tokenList.length}) and tokenAmounts (${args.tokenAmounts.length}) must be the same length`
|
|
337
|
+
);
|
|
338
|
+
}
|
|
339
|
+
const nonce = randomUint256();
|
|
340
|
+
const nonceStr = nonce.toString();
|
|
341
|
+
const signed = await coreSdk.signMetaTxWithdrawFunds({
|
|
342
|
+
nonce: nonceStr,
|
|
343
|
+
entityId: args.entityId,
|
|
344
|
+
tokenList: args.tokenList.map((t) => t),
|
|
345
|
+
tokenAmounts: args.tokenAmounts.map((a) => a)
|
|
346
|
+
});
|
|
347
|
+
const metaTx = {
|
|
348
|
+
from,
|
|
349
|
+
nonce: nonceStr,
|
|
350
|
+
functionName: signed.functionName,
|
|
351
|
+
functionSignature: signed.functionSignature,
|
|
352
|
+
sig: {
|
|
353
|
+
v: Number(signed.v),
|
|
354
|
+
r: signed.r,
|
|
355
|
+
s: signed.s
|
|
356
|
+
}
|
|
357
|
+
};
|
|
358
|
+
return {
|
|
359
|
+
metaTx,
|
|
360
|
+
signedPayload: encodeSignedPayload(metaTx),
|
|
361
|
+
entityId: normalizeEntityId(args.entityId),
|
|
362
|
+
tokenList: args.tokenList,
|
|
363
|
+
tokenAmounts: args.tokenAmounts.map((a) => String(a))
|
|
364
|
+
};
|
|
365
|
+
}
|
|
366
|
+
async function resolveEntityIdClientSide(coreSdk, selector) {
|
|
367
|
+
if ("entityId" in selector) return normalizeEntityId(selector.entityId);
|
|
368
|
+
const address = selector.address.toLowerCase();
|
|
369
|
+
const role = selector.role;
|
|
370
|
+
const [sellers, buyers] = await Promise.all([
|
|
371
|
+
role === "buyer" ? Promise.resolve([]) : coreSdk.getSellersByAddress(address),
|
|
372
|
+
role === "seller" ? Promise.resolve([]) : coreSdk.getBuyers({ buyersFilter: { wallet: address } })
|
|
373
|
+
]);
|
|
374
|
+
const sellerIds = sellers.map((s) => s.id);
|
|
375
|
+
const buyerIds = buyers.map((b) => b.id);
|
|
376
|
+
if (role === "seller") {
|
|
377
|
+
if (sellerIds.length === 0) {
|
|
378
|
+
throw new Error(
|
|
379
|
+
`signWithdrawAllAvailableFunds: no seller entity found for address ${address}`
|
|
380
|
+
);
|
|
381
|
+
}
|
|
382
|
+
if (sellerIds.length > 1) {
|
|
383
|
+
throw new Error(
|
|
384
|
+
`signWithdrawAllAvailableFunds: address ${address} resolves to multiple seller entities (ids ${sellerIds.join(", ")}); pass entityId to disambiguate`
|
|
385
|
+
);
|
|
386
|
+
}
|
|
387
|
+
return sellerIds[0];
|
|
388
|
+
}
|
|
389
|
+
if (role === "buyer") {
|
|
390
|
+
if (buyerIds.length === 0) {
|
|
391
|
+
throw new Error(
|
|
392
|
+
`signWithdrawAllAvailableFunds: no buyer entity found for address ${address}`
|
|
393
|
+
);
|
|
394
|
+
}
|
|
395
|
+
if (buyerIds.length > 1) {
|
|
396
|
+
throw new Error(
|
|
397
|
+
`signWithdrawAllAvailableFunds: address ${address} resolves to multiple buyer entities (ids ${buyerIds.join(", ")}); pass entityId to disambiguate`
|
|
398
|
+
);
|
|
399
|
+
}
|
|
400
|
+
return buyerIds[0];
|
|
401
|
+
}
|
|
402
|
+
const totalMatches = sellerIds.length + buyerIds.length;
|
|
403
|
+
if (totalMatches === 0) {
|
|
404
|
+
throw new Error(
|
|
405
|
+
`signWithdrawAllAvailableFunds: no seller or buyer entity found for address ${address}`
|
|
406
|
+
);
|
|
407
|
+
}
|
|
408
|
+
if (totalMatches > 1) {
|
|
409
|
+
throw new Error(
|
|
410
|
+
`signWithdrawAllAvailableFunds: address ${address} resolves to ${describeClientMatches(sellerIds, buyerIds)}; pass role and/or entityId to disambiguate`
|
|
411
|
+
);
|
|
412
|
+
}
|
|
413
|
+
if (sellerIds.length === 1) return sellerIds[0];
|
|
414
|
+
return buyerIds[0];
|
|
415
|
+
}
|
|
416
|
+
function describeClientMatches(sellerIds, buyerIds) {
|
|
417
|
+
const parts = [];
|
|
418
|
+
if (sellerIds.length > 0) {
|
|
419
|
+
parts.push(
|
|
420
|
+
sellerIds.length === 1 ? `seller (id ${sellerIds[0]})` : `${sellerIds.length} sellers (ids ${sellerIds.join(", ")})`
|
|
421
|
+
);
|
|
422
|
+
}
|
|
423
|
+
if (buyerIds.length > 0) {
|
|
424
|
+
parts.push(
|
|
425
|
+
buyerIds.length === 1 ? `buyer (id ${buyerIds[0]})` : `${buyerIds.length} buyers (ids ${buyerIds.join(", ")})`
|
|
426
|
+
);
|
|
427
|
+
}
|
|
428
|
+
return parts.join(" and ");
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
// src/client.ts
|
|
432
|
+
function createX402bClient(config) {
|
|
433
|
+
const buildCoreSdk = createCoreSdkFactory(config.signer, config);
|
|
434
|
+
const getBuyerAddress = async () => await config.signer.getAddress();
|
|
435
|
+
return {
|
|
436
|
+
async handle402(rawRequirements) {
|
|
437
|
+
const requirements = parseEscrowPaymentRequirements(rawRequirements);
|
|
438
|
+
const action = pickAction(requirements, config.policy);
|
|
439
|
+
const fulfillment = resolveFulfillment(requirements, config);
|
|
440
|
+
enforceMaxAmount(requirements.amount, config.policy?.maxAmount);
|
|
441
|
+
const buyer = await getBuyerAddress();
|
|
442
|
+
const { coreSdk, chainId } = buildCoreSdk(
|
|
443
|
+
requirements.network,
|
|
444
|
+
requirements.escrowAddress
|
|
445
|
+
);
|
|
446
|
+
let tokenAuth;
|
|
447
|
+
let strategy;
|
|
448
|
+
const forcedStrategy = config.policy?.tokenAuthStrategy;
|
|
449
|
+
if (forcedStrategy !== void 0) {
|
|
450
|
+
if (!requirements.tokenAuthStrategies.includes(forcedStrategy)) {
|
|
451
|
+
throw new UnsupportedTokenAuthError(
|
|
452
|
+
`policy.tokenAuthStrategy "${forcedStrategy}" is not in requirements.tokenAuthStrategies (${requirements.tokenAuthStrategies.join(", ")})`
|
|
453
|
+
);
|
|
454
|
+
}
|
|
455
|
+
if (forcedStrategy === "none") {
|
|
456
|
+
strategy = "none";
|
|
457
|
+
tokenAuth = void 0;
|
|
458
|
+
} else {
|
|
459
|
+
const built = await buildAndSignTokenAuth({
|
|
460
|
+
requirements: { ...requirements, tokenAuthStrategies: [forcedStrategy] },
|
|
461
|
+
buyer,
|
|
462
|
+
coreSdk,
|
|
463
|
+
tokenDomainResolver: config.tokenDomainResolver,
|
|
464
|
+
publicClient: config.publicClients?.[chainId]
|
|
465
|
+
});
|
|
466
|
+
strategy = built.strategy;
|
|
467
|
+
tokenAuth = built.tokenAuth;
|
|
468
|
+
}
|
|
469
|
+
} else {
|
|
470
|
+
const built = await buildAndSignTokenAuth({
|
|
471
|
+
requirements,
|
|
472
|
+
buyer,
|
|
473
|
+
coreSdk,
|
|
474
|
+
tokenDomainResolver: config.tokenDomainResolver,
|
|
475
|
+
publicClient: config.publicClients?.[chainId]
|
|
476
|
+
});
|
|
477
|
+
strategy = built.strategy;
|
|
478
|
+
tokenAuth = built.tokenAuth;
|
|
479
|
+
}
|
|
480
|
+
const signMetaTx = action === "boson-createOfferCommitAndRedeem" ? signCreateOfferCommitAndRedeem : signCreateOfferAndCommit;
|
|
481
|
+
const metaTx = await signMetaTx({ requirements, coreSdk, buyer });
|
|
482
|
+
return assembleAndEncodePayload({
|
|
483
|
+
requirements,
|
|
484
|
+
action,
|
|
485
|
+
tokenAuthStrategy: strategy,
|
|
486
|
+
metaTx,
|
|
487
|
+
tokenAuth,
|
|
488
|
+
fulfillment,
|
|
489
|
+
buyer
|
|
490
|
+
});
|
|
491
|
+
},
|
|
492
|
+
signAction(args) {
|
|
493
|
+
return signPostCommitAction(args, { buildCoreSdk, getBuyerAddress });
|
|
494
|
+
},
|
|
495
|
+
signWithdrawFunds(args) {
|
|
496
|
+
return signWithdrawFunds(args, { buildCoreSdk, getSignerAddress: getBuyerAddress });
|
|
497
|
+
},
|
|
498
|
+
signWithdrawAllAvailableFunds(args) {
|
|
499
|
+
return signWithdrawAllAvailableFunds(args, {
|
|
500
|
+
buildCoreSdk,
|
|
501
|
+
getSignerAddress: getBuyerAddress
|
|
502
|
+
});
|
|
503
|
+
},
|
|
504
|
+
parsePaymentResponse(response) {
|
|
505
|
+
return parsePaymentResponse(response);
|
|
506
|
+
}
|
|
507
|
+
};
|
|
508
|
+
}
|
|
509
|
+
function enforceMaxAmount(amount, maxAmount) {
|
|
510
|
+
if (maxAmount === void 0) {
|
|
511
|
+
return;
|
|
512
|
+
}
|
|
513
|
+
const amountBigInt = BigInt(amount);
|
|
514
|
+
const maxAmountBigInt = BigInt(maxAmount);
|
|
515
|
+
if (amountBigInt > maxAmountBigInt) {
|
|
516
|
+
throw new MaxAmountExceededError(
|
|
517
|
+
`x402-client: requirements.amount ${amount} exceeds policy.maxAmount ${maxAmount}`
|
|
518
|
+
);
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
function signerFromEthersAdapter(adapter) {
|
|
522
|
+
const resolveAddress = async () => getAddress(await adapter.getSignerAddress());
|
|
523
|
+
return {
|
|
524
|
+
getAddress: resolveAddress,
|
|
525
|
+
signTypedData: async ({ domain, types, primaryType, message }) => {
|
|
526
|
+
const from = await resolveAddress();
|
|
527
|
+
const typedData = {
|
|
528
|
+
domain,
|
|
529
|
+
types: { ...types, EIP712Domain: deriveEip712DomainType(domain) },
|
|
530
|
+
primaryType,
|
|
531
|
+
message
|
|
532
|
+
};
|
|
533
|
+
const json = serializeTypedData(typedData);
|
|
534
|
+
const sig = await adapter.send("eth_signTypedData_v4", [from, json]);
|
|
535
|
+
if (typeof sig !== "string" || !/^0x[0-9a-fA-F]+$/.test(sig)) {
|
|
536
|
+
throw new Error(
|
|
537
|
+
"signerFromEthersAdapter: adapter.send did not return a hex signature string"
|
|
538
|
+
);
|
|
539
|
+
}
|
|
540
|
+
return sig;
|
|
541
|
+
}
|
|
542
|
+
};
|
|
543
|
+
}
|
|
544
|
+
function deriveEip712DomainType(domain) {
|
|
545
|
+
const fields = [];
|
|
546
|
+
if (domain.name !== void 0) fields.push({ name: "name", type: "string" });
|
|
547
|
+
if (domain.version !== void 0) fields.push({ name: "version", type: "string" });
|
|
548
|
+
if (domain.chainId !== void 0) fields.push({ name: "chainId", type: "uint256" });
|
|
549
|
+
if (domain.verifyingContract !== void 0)
|
|
550
|
+
fields.push({ name: "verifyingContract", type: "address" });
|
|
551
|
+
if (domain.salt !== void 0) fields.push({ name: "salt", type: "bytes32" });
|
|
552
|
+
return fields;
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
export { createX402bClient, parsePaymentResponse, pickAction, resolveFulfillment, signerFromEthersAdapter };
|
|
556
|
+
//# sourceMappingURL=index.js.map
|
|
557
|
+
//# sourceMappingURL=index.js.map
|