@supercuts-walrus/backend 0.1.0 → 0.1.1

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.
@@ -67,8 +67,10 @@ export class X402ResourceService {
67
67
  challenge(artifact) {
68
68
  // The live Sui exact facilitator requires the x402 nonce to be exactly
69
69
  // 32 random bytes (hex encoded), not merely an application nonce.
70
- const requirements = this.requirements(artifact), nonce = this.#ledger.challenge(artifact.requestId, requirementsHash(requirements), () => randomBytes(32).toString("hex"));
71
- requirements.extra = { ...requirements.extra, nonce };
70
+ const requirements = this.requirements(artifact), baseHash = requirementsHash(requirements), nonce = this.#ledger.challenge(artifact.requestId, baseHash, () => randomBytes(32).toString("hex"));
71
+ const stored = this.#ledger.getChallenge(artifact.requestId, baseHash) ?? fail("X402_CHALLENGE_MISSING", "resource challenge was not persisted");
72
+ const expiresAt = stored.createdAt + requirements.maxTimeoutSeconds * 1_000;
73
+ requirements.extra = { ...requirements.extra, nonce, expiresAt };
72
74
  // The nonce changes the signed requirement set and is persisted per request.
73
75
  const finalHash = requirementsHash(requirements);
74
76
  return { x402Version: 2, resource: { url: this.resourceUrl(artifact.requestId), description: "Supercuts seller capability artifact", mimeType: "application/json" }, accepts: [{ ...requirements, extra: { ...requirements.extra, requirementsHash: finalHash } }] };
@@ -96,10 +98,10 @@ export class X402ResourceService {
96
98
  const offered = payload.accepted;
97
99
  if (!offered || requirementsHash(offered) !== requirementsHash(requirements))
98
100
  fail("X402_REQUIREMENTS_MISMATCH", "payment payload changed the challenged requirements");
99
- const extra = requirements.extra, nonce = String(extra.nonce ?? ""), declaredHash = String(extra.requirementsHash ?? "");
100
- const unsignedRequirements = { ...requirements, extra: { requestId: extra.requestId, operationId: extra.operationId, nonce } };
101
- if (declaredHash !== requirementsHash(unsignedRequirements))
102
- fail("X402_REQUIREMENTS_MISMATCH", "challenge requirements hash is invalid");
101
+ const extra = requirements.extra, nonce = String(extra.nonce ?? ""), declaredHash = String(extra.requirementsHash ?? ""), expiresAt = Number(extra.expiresAt);
102
+ const unsignedRequirements = { ...requirements, extra: { requestId: extra.requestId, operationId: extra.operationId, nonce, expiresAt } };
103
+ if (!Number.isSafeInteger(expiresAt) || expiresAt !== stored.createdAt + requirements.maxTimeoutSeconds * 1_000 || declaredHash !== requirementsHash(unsignedRequirements))
104
+ fail("X402_REQUIREMENTS_MISMATCH", "challenge requirements hash or expiry is invalid");
103
105
  verifyBinding(binding, artifact.requestId, nonce, unsignedRequirements, binding.transactionDigest);
104
106
  const { payerSignature: _signature, ...unsignedBinding } = binding;
105
107
  if (!(await isValidPersonalMessageSignature(paymentBindingBytes(unsignedBinding), binding.payerSignature, { address: binding.payer })))
@@ -133,20 +135,37 @@ export class X402ResourceService {
133
135
  createServer() { return createServer((request, response) => void this.handle(request, response)); }
134
136
  async facilitator(path, body) {
135
137
  const endpoint = new URL(path, `${this.#facilitator.toString().replace(/\/$/, "")}/`);
136
- const response = await this.http(endpoint, { method: "POST", headers: { "content-type": "application/json", "x-supercuts-facilitator-key": this.config.facilitatorKey }, body: JSON.stringify(body), signal: AbortSignal.timeout(60_000) });
137
- let value;
138
- try {
139
- value = await response.json();
140
- }
141
- catch (error) {
142
- return fail("X402_FACILITATOR_INVALID", "facilitator returned invalid JSON", error);
143
- }
144
- if (!response.ok || !value || typeof value !== "object" || Array.isArray(value)) {
138
+ const delays = this.config.facilitatorRetryDelaysMs ?? [0, 2_000, 5_000, 10_000];
139
+ let lastCause;
140
+ for (const delay of delays) {
141
+ if (delay > 0)
142
+ await new Promise((resolve) => setTimeout(resolve, delay));
143
+ let response;
144
+ try {
145
+ response = await this.http(endpoint, { method: "POST", headers: { "content-type": "application/json", "x-supercuts-facilitator-key": this.config.facilitatorKey }, body: JSON.stringify(body), signal: AbortSignal.timeout(60_000) });
146
+ }
147
+ catch (error) {
148
+ lastCause = error;
149
+ continue;
150
+ }
151
+ const raw = Buffer.from(await response.arrayBuffer());
152
+ let value;
153
+ try {
154
+ value = JSON.parse(raw.toString("utf8"));
155
+ }
156
+ catch (error) {
157
+ lastCause = error;
158
+ continue;
159
+ }
160
+ if (response.ok && value && typeof value === "object" && !Array.isArray(value))
161
+ return value;
145
162
  const message = value && typeof value === "object" && !Array.isArray(value) && typeof value.message === "string"
146
163
  ? `: ${value.message}` : "";
147
- fail("X402_FACILITATOR_INVALID", `facilitator ${path} failed with ${response.status}${message}`);
164
+ if (response.status < 500)
165
+ fail("X402_FACILITATOR_INVALID", `facilitator ${path} failed with ${response.status}${message}`);
166
+ lastCause = new Error(`facilitator ${path} returned ${response.status}${message}`);
148
167
  }
149
- return value;
168
+ return fail("X402_FACILITATOR_UNAVAILABLE", `facilitator ${path} remained unavailable after bounded retries`, lastCause);
150
169
  }
151
170
  send(response, status, body, headers = {}) { response.writeHead(status, { "content-length": String(body.length), ...headers }); response.end(body); }
152
171
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@supercuts-walrus/backend",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "exports": {