@nullclear/x402-arbiter 0.0.0-stage → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +36 -2
- package/dist/index.cjs +320 -0
- package/dist/index.d.cts +104 -0
- package/dist/index.d.mts +104 -0
- package/dist/index.d.ts +104 -0
- package/dist/index.js +295 -0
- package/dist/index.mjs +295 -0
- package/package.json +43 -3
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Nullclear-Protocol
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,3 +1,37 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @nullclear/x402-arbiter
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Autonomous Agent SDK for zero-risk HTTP 402 calls via NullClear Deferred Non-Custodial Schema Escrow.
|
|
4
|
+
|
|
5
|
+
## Key Features
|
|
6
|
+
- **Automatic 402 Interception:** Intercepts standard `402 Payment Required` headers and signs EIP-3009 authorizations.
|
|
7
|
+
- **Zod & JSON Schema Enforcement:** Define expected response structures with standard Zod or JSON schemas.
|
|
8
|
+
- **$0 Spend Protection:** If the seller crashes, returns 500, or hallucinates bad types, settlement is voided and $0 is spent.
|
|
9
|
+
|
|
10
|
+
## Quickstart
|
|
11
|
+
|
|
12
|
+
```typescript
|
|
13
|
+
import { createNullClearFetch } from "@nullclear/x402-arbiter";
|
|
14
|
+
import { z } from "zod";
|
|
15
|
+
|
|
16
|
+
const nullclearFetch = createNullClearFetch({
|
|
17
|
+
accountOrPrivateKey: "0x...", // Buyer private key or viem account
|
|
18
|
+
chainId: 8453, // Base Mainnet (or 84532 for Base Sepolia)
|
|
19
|
+
arbiterUrl: "https://arbiter.nullclear.dev"
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
// Make requests normally - 402 will be intercepted and escrow-routed
|
|
23
|
+
const response = await nullclearFetch("https://seller-agent.com/v1/alpha", {
|
|
24
|
+
nullclear: {
|
|
25
|
+
sellerAgentId: "42",
|
|
26
|
+
minTrustScore: 60,
|
|
27
|
+
schema: z.object({
|
|
28
|
+
ticker: z.string(),
|
|
29
|
+
price: z.number(),
|
|
30
|
+
confidence: z.number()
|
|
31
|
+
})
|
|
32
|
+
}
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
const data = await response.json();
|
|
36
|
+
console.log("Verified data:", data);
|
|
37
|
+
```
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,320 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/index.ts
|
|
21
|
+
var index_exports = {};
|
|
22
|
+
__export(index_exports, {
|
|
23
|
+
NullClearClient: () => NullClearClient,
|
|
24
|
+
NullClearEscrowAbortedError: () => NullClearEscrowAbortedError,
|
|
25
|
+
createNullClearFetch: () => createNullClearFetch,
|
|
26
|
+
signX402Payment: () => signX402Payment,
|
|
27
|
+
zodToJsonSchema: () => zodToJsonSchema
|
|
28
|
+
});
|
|
29
|
+
module.exports = __toCommonJS(index_exports);
|
|
30
|
+
|
|
31
|
+
// src/signer.ts
|
|
32
|
+
var import_accounts = require("viem/accounts");
|
|
33
|
+
var import_shared = require("@nullclear/shared");
|
|
34
|
+
async function signX402Payment(signer, option, resourceUrl) {
|
|
35
|
+
let accountAddress;
|
|
36
|
+
let signTypedDataFn;
|
|
37
|
+
if (typeof signer === "string" && signer.startsWith("0x")) {
|
|
38
|
+
const account = (0, import_accounts.privateKeyToAccount)(signer);
|
|
39
|
+
accountAddress = account.address;
|
|
40
|
+
signTypedDataFn = (params) => account.signTypedData(params);
|
|
41
|
+
} else if (typeof signer === "object" && signer !== null && "signTypedData" in signer && "account" in signer && signer.account) {
|
|
42
|
+
const rawAccount = signer.account;
|
|
43
|
+
accountAddress = typeof rawAccount === "string" ? rawAccount : rawAccount.address;
|
|
44
|
+
signTypedDataFn = (params) => signer.signTypedData(params);
|
|
45
|
+
} else {
|
|
46
|
+
throw new Error("Invalid signer provided to signX402Payment: must be private key or viem account/wallet client");
|
|
47
|
+
}
|
|
48
|
+
const randomBytes = new Uint8Array(32);
|
|
49
|
+
crypto.getRandomValues(randomBytes);
|
|
50
|
+
const nonce = `0x` + Array.from(randomBytes).map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
51
|
+
const validAfter = "0";
|
|
52
|
+
const timeoutSeconds = option.maxTimeoutSeconds && option.maxTimeoutSeconds > 0 ? option.maxTimeoutSeconds : 300;
|
|
53
|
+
const validBefore = String(Math.floor(Date.now() / 1e3) + timeoutSeconds);
|
|
54
|
+
const chainConfig = (0, import_shared.getChainConfig)(option.network);
|
|
55
|
+
const domain = {
|
|
56
|
+
name: chainConfig.usdcDomain.name,
|
|
57
|
+
version: chainConfig.usdcDomain.version,
|
|
58
|
+
chainId: chainConfig.chainId,
|
|
59
|
+
verifyingContract: chainConfig.usdc
|
|
60
|
+
};
|
|
61
|
+
const authorization = {
|
|
62
|
+
from: accountAddress,
|
|
63
|
+
to: option.payTo,
|
|
64
|
+
value: option.amount,
|
|
65
|
+
validAfter,
|
|
66
|
+
validBefore,
|
|
67
|
+
nonce
|
|
68
|
+
};
|
|
69
|
+
const signature = await signTypedDataFn({
|
|
70
|
+
domain,
|
|
71
|
+
types: import_shared.EIP3009_TYPES,
|
|
72
|
+
primaryType: "TransferWithAuthorization",
|
|
73
|
+
message: {
|
|
74
|
+
from: authorization.from,
|
|
75
|
+
to: authorization.to,
|
|
76
|
+
value: BigInt(authorization.value),
|
|
77
|
+
validAfter: BigInt(authorization.validAfter),
|
|
78
|
+
validBefore: BigInt(authorization.validBefore),
|
|
79
|
+
nonce: authorization.nonce
|
|
80
|
+
}
|
|
81
|
+
});
|
|
82
|
+
const payload = {
|
|
83
|
+
x402Version: 2,
|
|
84
|
+
resource: resourceUrl ? { url: resourceUrl } : void 0,
|
|
85
|
+
accepted: option,
|
|
86
|
+
payload: {
|
|
87
|
+
signature,
|
|
88
|
+
authorization
|
|
89
|
+
}
|
|
90
|
+
};
|
|
91
|
+
return (0, import_shared.encodeBase64Json)(payload);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// src/client.ts
|
|
95
|
+
var NullClearClient = class {
|
|
96
|
+
arbiterUrl;
|
|
97
|
+
defaultChainId;
|
|
98
|
+
customFetch;
|
|
99
|
+
constructor(config = {}) {
|
|
100
|
+
this.arbiterUrl = (config.arbiterUrl || "https://arbiter.nullclear.dev").replace(/\/$/, "");
|
|
101
|
+
this.defaultChainId = config.defaultChainId || 8453;
|
|
102
|
+
this.customFetch = config.fetch || fetch;
|
|
103
|
+
}
|
|
104
|
+
async getScore(agentId, chainId) {
|
|
105
|
+
const targetChainId = chainId || this.defaultChainId;
|
|
106
|
+
const res = await this.customFetch(`${this.arbiterUrl}/v1/score/${targetChainId}/${agentId}`);
|
|
107
|
+
if (!res.ok) {
|
|
108
|
+
const err = await res.text();
|
|
109
|
+
throw new Error(`Failed to fetch score for agent ${agentId}: ${res.status} ${err}`);
|
|
110
|
+
}
|
|
111
|
+
const data = await res.json();
|
|
112
|
+
return data.data;
|
|
113
|
+
}
|
|
114
|
+
async clear(request) {
|
|
115
|
+
const res = await this.customFetch(`${this.arbiterUrl}/v1/clear`, {
|
|
116
|
+
method: "POST",
|
|
117
|
+
headers: { "Content-Type": "application/json" },
|
|
118
|
+
body: JSON.stringify(request)
|
|
119
|
+
});
|
|
120
|
+
const data = await res.json();
|
|
121
|
+
return data;
|
|
122
|
+
}
|
|
123
|
+
async getKya(agentId, chainId) {
|
|
124
|
+
const targetChainId = chainId || this.defaultChainId;
|
|
125
|
+
const res = await this.customFetch(`${this.arbiterUrl}/v1/kya/${targetChainId}/${agentId}`);
|
|
126
|
+
if (!res.ok) {
|
|
127
|
+
if (res.status === 404) return null;
|
|
128
|
+
const err = await res.text();
|
|
129
|
+
throw new Error(`Failed to fetch KYA mandate for agent ${agentId}: ${res.status} ${err}`);
|
|
130
|
+
}
|
|
131
|
+
const data = await res.json();
|
|
132
|
+
return data.data;
|
|
133
|
+
}
|
|
134
|
+
async attestKya(mandate, chainId, signature, signerAddress) {
|
|
135
|
+
const targetChainId = chainId || this.defaultChainId;
|
|
136
|
+
const res = await this.customFetch(`${this.arbiterUrl}/v1/kya/attest`, {
|
|
137
|
+
method: "POST",
|
|
138
|
+
headers: { "Content-Type": "application/json" },
|
|
139
|
+
body: JSON.stringify({
|
|
140
|
+
mandate,
|
|
141
|
+
chainId: targetChainId,
|
|
142
|
+
signature,
|
|
143
|
+
signerAddress
|
|
144
|
+
})
|
|
145
|
+
});
|
|
146
|
+
if (!res.ok) {
|
|
147
|
+
const err = await res.text();
|
|
148
|
+
throw new Error(`Failed to attest KYA mandate: ${res.status} ${err}`);
|
|
149
|
+
}
|
|
150
|
+
const data = await res.json();
|
|
151
|
+
return data.data;
|
|
152
|
+
}
|
|
153
|
+
};
|
|
154
|
+
|
|
155
|
+
// src/fetch-wrapper.ts
|
|
156
|
+
var import_zod = require("zod");
|
|
157
|
+
var import_shared2 = require("@nullclear/shared");
|
|
158
|
+
var NullClearEscrowAbortedError = class extends Error {
|
|
159
|
+
stage;
|
|
160
|
+
buyerCharged;
|
|
161
|
+
validationErrors;
|
|
162
|
+
score;
|
|
163
|
+
upstreamStatus;
|
|
164
|
+
reason;
|
|
165
|
+
constructor(message, details) {
|
|
166
|
+
super(message);
|
|
167
|
+
this.name = "NullClearEscrowAbortedError";
|
|
168
|
+
this.stage = details.stage;
|
|
169
|
+
this.buyerCharged = details.buyerCharged || "0";
|
|
170
|
+
this.validationErrors = details.validationErrors || [];
|
|
171
|
+
this.score = details.score;
|
|
172
|
+
this.upstreamStatus = details.upstreamStatus;
|
|
173
|
+
this.reason = details.reason;
|
|
174
|
+
}
|
|
175
|
+
};
|
|
176
|
+
function zodToJsonSchema(schema) {
|
|
177
|
+
const def = schema._def;
|
|
178
|
+
if (!def) return {};
|
|
179
|
+
const typeName = def.typeName;
|
|
180
|
+
switch (typeName) {
|
|
181
|
+
case "ZodString":
|
|
182
|
+
return { type: "string" };
|
|
183
|
+
case "ZodNumber":
|
|
184
|
+
return { type: "number" };
|
|
185
|
+
case "ZodBoolean":
|
|
186
|
+
return { type: "boolean" };
|
|
187
|
+
case "ZodArray":
|
|
188
|
+
return {
|
|
189
|
+
type: "array",
|
|
190
|
+
items: def.type ? zodToJsonSchema(def.type) : {}
|
|
191
|
+
};
|
|
192
|
+
case "ZodObject": {
|
|
193
|
+
const shape = def.shape();
|
|
194
|
+
const properties = {};
|
|
195
|
+
const required = [];
|
|
196
|
+
for (const [key, value] of Object.entries(shape)) {
|
|
197
|
+
properties[key] = zodToJsonSchema(value);
|
|
198
|
+
const subDef = value._def;
|
|
199
|
+
if (subDef.typeName !== "ZodOptional" && subDef.typeName !== "ZodNullable") {
|
|
200
|
+
required.push(key);
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
return {
|
|
204
|
+
type: "object",
|
|
205
|
+
properties,
|
|
206
|
+
required: required.length > 0 ? required : void 0
|
|
207
|
+
};
|
|
208
|
+
}
|
|
209
|
+
case "ZodEnum":
|
|
210
|
+
return {
|
|
211
|
+
type: "string",
|
|
212
|
+
enum: def.values
|
|
213
|
+
};
|
|
214
|
+
case "ZodOptional":
|
|
215
|
+
case "ZodNullable":
|
|
216
|
+
return def.innerType ? zodToJsonSchema(def.innerType) : {};
|
|
217
|
+
default:
|
|
218
|
+
return {};
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
function createNullClearFetch(config) {
|
|
222
|
+
const chainId = config.chainId || 8453;
|
|
223
|
+
const arbiterUrl = (config.arbiterUrl || "https://arbiter.nullclear.dev").replace(/\/$/, "");
|
|
224
|
+
const defaultMinTrustScore = config.defaultMinTrustScore || 50;
|
|
225
|
+
const customFetch = config.customFetch || fetch;
|
|
226
|
+
return async function nullclearFetch(input, init) {
|
|
227
|
+
const targetUrl = typeof input === "string" ? input : input instanceof URL ? input.toString() : input.url;
|
|
228
|
+
const fetchInit = { ...init };
|
|
229
|
+
delete fetchInit.nullclear;
|
|
230
|
+
const initialRes = await customFetch(input, fetchInit);
|
|
231
|
+
if (initialRes.status !== 402) {
|
|
232
|
+
return initialRes;
|
|
233
|
+
}
|
|
234
|
+
const paymentRequiredHeader = initialRes.headers.get("PAYMENT-REQUIRED") || initialRes.headers.get("payment-required");
|
|
235
|
+
if (!paymentRequiredHeader) {
|
|
236
|
+
return initialRes;
|
|
237
|
+
}
|
|
238
|
+
let paymentRequired;
|
|
239
|
+
try {
|
|
240
|
+
paymentRequired = (0, import_shared2.decodeBase64Json)(paymentRequiredHeader);
|
|
241
|
+
} catch {
|
|
242
|
+
return initialRes;
|
|
243
|
+
}
|
|
244
|
+
const targetNetwork = `eip155:${chainId}`;
|
|
245
|
+
const selectedOption = paymentRequired.accepts.find(
|
|
246
|
+
(opt) => opt.network === targetNetwork && opt.scheme === "exact"
|
|
247
|
+
);
|
|
248
|
+
if (!selectedOption) {
|
|
249
|
+
throw new Error(
|
|
250
|
+
`Seller does not accept payments on network ${targetNetwork}. Accepted: ${paymentRequired.accepts.map((a) => a.network).join(", ")}`
|
|
251
|
+
);
|
|
252
|
+
}
|
|
253
|
+
const paymentSignatureHeader = await signX402Payment(
|
|
254
|
+
config.accountOrPrivateKey,
|
|
255
|
+
selectedOption,
|
|
256
|
+
targetUrl
|
|
257
|
+
);
|
|
258
|
+
let jsonSchema;
|
|
259
|
+
if (init?.nullclear?.schema) {
|
|
260
|
+
if (init.nullclear.schema instanceof import_zod.z.ZodType) {
|
|
261
|
+
jsonSchema = zodToJsonSchema(init.nullclear.schema);
|
|
262
|
+
} else if (typeof init.nullclear.schema === "object") {
|
|
263
|
+
jsonSchema = init.nullclear.schema;
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
const clearPayload = {
|
|
267
|
+
targetUrl,
|
|
268
|
+
method: init?.method || "GET",
|
|
269
|
+
headers: init?.headers || {},
|
|
270
|
+
body: init?.body,
|
|
271
|
+
paymentSignatureHeader,
|
|
272
|
+
sellerAgentId: init?.nullclear?.sellerAgentId,
|
|
273
|
+
minTrustScore: init?.nullclear?.minTrustScore ?? defaultMinTrustScore,
|
|
274
|
+
validation: {
|
|
275
|
+
expectedStatus: [200],
|
|
276
|
+
jsonSchema,
|
|
277
|
+
requiredFields: init?.nullclear?.requiredFields,
|
|
278
|
+
sha256Match: init?.nullclear?.sha256Match
|
|
279
|
+
}
|
|
280
|
+
};
|
|
281
|
+
const arbiterRes = await customFetch(`${arbiterUrl}/v1/clear`, {
|
|
282
|
+
method: "POST",
|
|
283
|
+
headers: { "Content-Type": "application/json" },
|
|
284
|
+
body: JSON.stringify(clearPayload)
|
|
285
|
+
});
|
|
286
|
+
const arbiterData = await arbiterRes.json();
|
|
287
|
+
if (arbiterRes.status === 200 && arbiterData.cleared) {
|
|
288
|
+
const resBody = typeof arbiterData.data === "string" ? arbiterData.data : JSON.stringify(arbiterData.data);
|
|
289
|
+
const resHeaders = new Headers();
|
|
290
|
+
resHeaders.set("Content-Type", "application/json");
|
|
291
|
+
const paymentResponseHdr = arbiterRes.headers.get("PAYMENT-RESPONSE");
|
|
292
|
+
if (paymentResponseHdr) resHeaders.set("PAYMENT-RESPONSE", paymentResponseHdr);
|
|
293
|
+
const attestationHdr = arbiterRes.headers.get("X-NullClear-Attestation");
|
|
294
|
+
if (attestationHdr) resHeaders.set("X-NullClear-Attestation", attestationHdr);
|
|
295
|
+
return new Response(resBody, {
|
|
296
|
+
status: 200,
|
|
297
|
+
headers: resHeaders
|
|
298
|
+
});
|
|
299
|
+
}
|
|
300
|
+
throw new NullClearEscrowAbortedError(
|
|
301
|
+
`NullClear Escrow Aborted: ${arbiterData.stage || "FAILED"} - ${arbiterData.reason || arbiterData.error || (arbiterData.validationErrors || []).join("; ")}`,
|
|
302
|
+
{
|
|
303
|
+
stage: arbiterData.stage || "UNKNOWN_FAILURE",
|
|
304
|
+
buyerCharged: arbiterData.buyerCharged || "0",
|
|
305
|
+
validationErrors: arbiterData.validationErrors || [],
|
|
306
|
+
score: arbiterData.score,
|
|
307
|
+
upstreamStatus: arbiterData.upstreamStatus,
|
|
308
|
+
reason: arbiterData.reason || arbiterData.error
|
|
309
|
+
}
|
|
310
|
+
);
|
|
311
|
+
};
|
|
312
|
+
}
|
|
313
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
314
|
+
0 && (module.exports = {
|
|
315
|
+
NullClearClient,
|
|
316
|
+
NullClearEscrowAbortedError,
|
|
317
|
+
createNullClearFetch,
|
|
318
|
+
signX402Payment,
|
|
319
|
+
zodToJsonSchema
|
|
320
|
+
});
|
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import { PrivateKeyAccount } from 'viem/accounts';
|
|
2
|
+
import { WalletClient } from 'viem';
|
|
3
|
+
import { X402PaymentOption, KYAMandate } from '@nullclear/shared';
|
|
4
|
+
import { z } from 'zod';
|
|
5
|
+
|
|
6
|
+
type SignerInput = `0x${string}` | PrivateKeyAccount | {
|
|
7
|
+
account: {
|
|
8
|
+
address: `0x${string}`;
|
|
9
|
+
};
|
|
10
|
+
signTypedData: (params: any) => Promise<`0x${string}`>;
|
|
11
|
+
} | WalletClient;
|
|
12
|
+
/**
|
|
13
|
+
* Signs an EIP-3009 TransferWithAuthorization and returns a Base64 encoded X402PaymentSignaturePayload
|
|
14
|
+
*/
|
|
15
|
+
declare function signX402Payment(signer: SignerInput, option: X402PaymentOption, resourceUrl?: string): Promise<string>;
|
|
16
|
+
|
|
17
|
+
interface NullClearClientConfig {
|
|
18
|
+
arbiterUrl?: string;
|
|
19
|
+
defaultChainId?: number;
|
|
20
|
+
fetch?: typeof fetch;
|
|
21
|
+
}
|
|
22
|
+
interface ClearRequest {
|
|
23
|
+
targetUrl: string;
|
|
24
|
+
method?: "GET" | "POST" | "PUT" | "DELETE";
|
|
25
|
+
headers?: Record<string, string>;
|
|
26
|
+
body?: unknown;
|
|
27
|
+
paymentSignatureHeader: string;
|
|
28
|
+
sellerAgentId?: string;
|
|
29
|
+
minTrustScore?: number;
|
|
30
|
+
validation?: {
|
|
31
|
+
expectedStatus?: number[];
|
|
32
|
+
jsonSchema?: Record<string, unknown>;
|
|
33
|
+
requiredFields?: string[];
|
|
34
|
+
sha256Match?: string;
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
interface ClearResult<T = unknown> {
|
|
38
|
+
cleared: boolean;
|
|
39
|
+
settled: boolean;
|
|
40
|
+
clearingId?: string;
|
|
41
|
+
buyerCharged: string;
|
|
42
|
+
clearingFee?: string;
|
|
43
|
+
transaction?: `0x${string}`;
|
|
44
|
+
attestation?: unknown;
|
|
45
|
+
data?: T;
|
|
46
|
+
stage?: string;
|
|
47
|
+
error?: string;
|
|
48
|
+
validationErrors?: string[];
|
|
49
|
+
}
|
|
50
|
+
declare class NullClearClient {
|
|
51
|
+
private arbiterUrl;
|
|
52
|
+
private defaultChainId;
|
|
53
|
+
private customFetch;
|
|
54
|
+
constructor(config?: NullClearClientConfig);
|
|
55
|
+
getScore(agentId: string, chainId?: number): Promise<any>;
|
|
56
|
+
clear<T = unknown>(request: ClearRequest): Promise<ClearResult<T>>;
|
|
57
|
+
getKya(agentId: string, chainId?: number): Promise<any>;
|
|
58
|
+
attestKya(mandate: KYAMandate, chainId?: number, signature?: string, signerAddress?: `0x${string}`): Promise<any>;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
declare class NullClearEscrowAbortedError extends Error {
|
|
62
|
+
stage: string;
|
|
63
|
+
buyerCharged: string;
|
|
64
|
+
validationErrors: string[];
|
|
65
|
+
score?: number;
|
|
66
|
+
upstreamStatus?: number;
|
|
67
|
+
reason?: string;
|
|
68
|
+
constructor(message: string, details: {
|
|
69
|
+
stage: string;
|
|
70
|
+
buyerCharged?: string;
|
|
71
|
+
validationErrors?: string[];
|
|
72
|
+
score?: number;
|
|
73
|
+
upstreamStatus?: number;
|
|
74
|
+
reason?: string;
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Lightweight deterministic Zod-to-JSON-Schema converter
|
|
79
|
+
*/
|
|
80
|
+
declare function zodToJsonSchema(schema: z.ZodTypeAny): Record<string, unknown>;
|
|
81
|
+
interface NullClearOptions {
|
|
82
|
+
sellerAgentId?: string;
|
|
83
|
+
minTrustScore?: number;
|
|
84
|
+
schema?: z.ZodTypeAny | Record<string, unknown>;
|
|
85
|
+
requiredFields?: string[];
|
|
86
|
+
sha256Match?: string;
|
|
87
|
+
}
|
|
88
|
+
interface NullClearFetchConfig {
|
|
89
|
+
accountOrPrivateKey: SignerInput;
|
|
90
|
+
chainId?: number;
|
|
91
|
+
arbiterUrl?: string;
|
|
92
|
+
defaultMinTrustScore?: number;
|
|
93
|
+
customFetch?: typeof fetch;
|
|
94
|
+
}
|
|
95
|
+
type NullClearRequestInit = RequestInit & {
|
|
96
|
+
nullclear?: NullClearOptions;
|
|
97
|
+
};
|
|
98
|
+
/**
|
|
99
|
+
* Creates an enhanced fetch client that autonomously intercepts HTTP 402 Payment Required
|
|
100
|
+
* and safely wraps the call in NullClear Deferred Non-Custodial Schema Escrow
|
|
101
|
+
*/
|
|
102
|
+
declare function createNullClearFetch(config: NullClearFetchConfig): (input: RequestInfo | URL, init?: NullClearRequestInit) => Promise<Response>;
|
|
103
|
+
|
|
104
|
+
export { type ClearRequest, type ClearResult, NullClearClient, type NullClearClientConfig, NullClearEscrowAbortedError, type NullClearFetchConfig, type NullClearOptions, type NullClearRequestInit, type SignerInput, createNullClearFetch, signX402Payment, zodToJsonSchema };
|
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import { PrivateKeyAccount } from 'viem/accounts';
|
|
2
|
+
import { WalletClient } from 'viem';
|
|
3
|
+
import { X402PaymentOption, KYAMandate } from '@nullclear/shared';
|
|
4
|
+
import { z } from 'zod';
|
|
5
|
+
|
|
6
|
+
type SignerInput = `0x${string}` | PrivateKeyAccount | {
|
|
7
|
+
account: {
|
|
8
|
+
address: `0x${string}`;
|
|
9
|
+
};
|
|
10
|
+
signTypedData: (params: any) => Promise<`0x${string}`>;
|
|
11
|
+
} | WalletClient;
|
|
12
|
+
/**
|
|
13
|
+
* Signs an EIP-3009 TransferWithAuthorization and returns a Base64 encoded X402PaymentSignaturePayload
|
|
14
|
+
*/
|
|
15
|
+
declare function signX402Payment(signer: SignerInput, option: X402PaymentOption, resourceUrl?: string): Promise<string>;
|
|
16
|
+
|
|
17
|
+
interface NullClearClientConfig {
|
|
18
|
+
arbiterUrl?: string;
|
|
19
|
+
defaultChainId?: number;
|
|
20
|
+
fetch?: typeof fetch;
|
|
21
|
+
}
|
|
22
|
+
interface ClearRequest {
|
|
23
|
+
targetUrl: string;
|
|
24
|
+
method?: "GET" | "POST" | "PUT" | "DELETE";
|
|
25
|
+
headers?: Record<string, string>;
|
|
26
|
+
body?: unknown;
|
|
27
|
+
paymentSignatureHeader: string;
|
|
28
|
+
sellerAgentId?: string;
|
|
29
|
+
minTrustScore?: number;
|
|
30
|
+
validation?: {
|
|
31
|
+
expectedStatus?: number[];
|
|
32
|
+
jsonSchema?: Record<string, unknown>;
|
|
33
|
+
requiredFields?: string[];
|
|
34
|
+
sha256Match?: string;
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
interface ClearResult<T = unknown> {
|
|
38
|
+
cleared: boolean;
|
|
39
|
+
settled: boolean;
|
|
40
|
+
clearingId?: string;
|
|
41
|
+
buyerCharged: string;
|
|
42
|
+
clearingFee?: string;
|
|
43
|
+
transaction?: `0x${string}`;
|
|
44
|
+
attestation?: unknown;
|
|
45
|
+
data?: T;
|
|
46
|
+
stage?: string;
|
|
47
|
+
error?: string;
|
|
48
|
+
validationErrors?: string[];
|
|
49
|
+
}
|
|
50
|
+
declare class NullClearClient {
|
|
51
|
+
private arbiterUrl;
|
|
52
|
+
private defaultChainId;
|
|
53
|
+
private customFetch;
|
|
54
|
+
constructor(config?: NullClearClientConfig);
|
|
55
|
+
getScore(agentId: string, chainId?: number): Promise<any>;
|
|
56
|
+
clear<T = unknown>(request: ClearRequest): Promise<ClearResult<T>>;
|
|
57
|
+
getKya(agentId: string, chainId?: number): Promise<any>;
|
|
58
|
+
attestKya(mandate: KYAMandate, chainId?: number, signature?: string, signerAddress?: `0x${string}`): Promise<any>;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
declare class NullClearEscrowAbortedError extends Error {
|
|
62
|
+
stage: string;
|
|
63
|
+
buyerCharged: string;
|
|
64
|
+
validationErrors: string[];
|
|
65
|
+
score?: number;
|
|
66
|
+
upstreamStatus?: number;
|
|
67
|
+
reason?: string;
|
|
68
|
+
constructor(message: string, details: {
|
|
69
|
+
stage: string;
|
|
70
|
+
buyerCharged?: string;
|
|
71
|
+
validationErrors?: string[];
|
|
72
|
+
score?: number;
|
|
73
|
+
upstreamStatus?: number;
|
|
74
|
+
reason?: string;
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Lightweight deterministic Zod-to-JSON-Schema converter
|
|
79
|
+
*/
|
|
80
|
+
declare function zodToJsonSchema(schema: z.ZodTypeAny): Record<string, unknown>;
|
|
81
|
+
interface NullClearOptions {
|
|
82
|
+
sellerAgentId?: string;
|
|
83
|
+
minTrustScore?: number;
|
|
84
|
+
schema?: z.ZodTypeAny | Record<string, unknown>;
|
|
85
|
+
requiredFields?: string[];
|
|
86
|
+
sha256Match?: string;
|
|
87
|
+
}
|
|
88
|
+
interface NullClearFetchConfig {
|
|
89
|
+
accountOrPrivateKey: SignerInput;
|
|
90
|
+
chainId?: number;
|
|
91
|
+
arbiterUrl?: string;
|
|
92
|
+
defaultMinTrustScore?: number;
|
|
93
|
+
customFetch?: typeof fetch;
|
|
94
|
+
}
|
|
95
|
+
type NullClearRequestInit = RequestInit & {
|
|
96
|
+
nullclear?: NullClearOptions;
|
|
97
|
+
};
|
|
98
|
+
/**
|
|
99
|
+
* Creates an enhanced fetch client that autonomously intercepts HTTP 402 Payment Required
|
|
100
|
+
* and safely wraps the call in NullClear Deferred Non-Custodial Schema Escrow
|
|
101
|
+
*/
|
|
102
|
+
declare function createNullClearFetch(config: NullClearFetchConfig): (input: RequestInfo | URL, init?: NullClearRequestInit) => Promise<Response>;
|
|
103
|
+
|
|
104
|
+
export { type ClearRequest, type ClearResult, NullClearClient, type NullClearClientConfig, NullClearEscrowAbortedError, type NullClearFetchConfig, type NullClearOptions, type NullClearRequestInit, type SignerInput, createNullClearFetch, signX402Payment, zodToJsonSchema };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import { PrivateKeyAccount } from 'viem/accounts';
|
|
2
|
+
import { WalletClient } from 'viem';
|
|
3
|
+
import { X402PaymentOption, KYAMandate } from '@nullclear/shared';
|
|
4
|
+
import { z } from 'zod';
|
|
5
|
+
|
|
6
|
+
type SignerInput = `0x${string}` | PrivateKeyAccount | {
|
|
7
|
+
account: {
|
|
8
|
+
address: `0x${string}`;
|
|
9
|
+
};
|
|
10
|
+
signTypedData: (params: any) => Promise<`0x${string}`>;
|
|
11
|
+
} | WalletClient;
|
|
12
|
+
/**
|
|
13
|
+
* Signs an EIP-3009 TransferWithAuthorization and returns a Base64 encoded X402PaymentSignaturePayload
|
|
14
|
+
*/
|
|
15
|
+
declare function signX402Payment(signer: SignerInput, option: X402PaymentOption, resourceUrl?: string): Promise<string>;
|
|
16
|
+
|
|
17
|
+
interface NullClearClientConfig {
|
|
18
|
+
arbiterUrl?: string;
|
|
19
|
+
defaultChainId?: number;
|
|
20
|
+
fetch?: typeof fetch;
|
|
21
|
+
}
|
|
22
|
+
interface ClearRequest {
|
|
23
|
+
targetUrl: string;
|
|
24
|
+
method?: "GET" | "POST" | "PUT" | "DELETE";
|
|
25
|
+
headers?: Record<string, string>;
|
|
26
|
+
body?: unknown;
|
|
27
|
+
paymentSignatureHeader: string;
|
|
28
|
+
sellerAgentId?: string;
|
|
29
|
+
minTrustScore?: number;
|
|
30
|
+
validation?: {
|
|
31
|
+
expectedStatus?: number[];
|
|
32
|
+
jsonSchema?: Record<string, unknown>;
|
|
33
|
+
requiredFields?: string[];
|
|
34
|
+
sha256Match?: string;
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
interface ClearResult<T = unknown> {
|
|
38
|
+
cleared: boolean;
|
|
39
|
+
settled: boolean;
|
|
40
|
+
clearingId?: string;
|
|
41
|
+
buyerCharged: string;
|
|
42
|
+
clearingFee?: string;
|
|
43
|
+
transaction?: `0x${string}`;
|
|
44
|
+
attestation?: unknown;
|
|
45
|
+
data?: T;
|
|
46
|
+
stage?: string;
|
|
47
|
+
error?: string;
|
|
48
|
+
validationErrors?: string[];
|
|
49
|
+
}
|
|
50
|
+
declare class NullClearClient {
|
|
51
|
+
private arbiterUrl;
|
|
52
|
+
private defaultChainId;
|
|
53
|
+
private customFetch;
|
|
54
|
+
constructor(config?: NullClearClientConfig);
|
|
55
|
+
getScore(agentId: string, chainId?: number): Promise<any>;
|
|
56
|
+
clear<T = unknown>(request: ClearRequest): Promise<ClearResult<T>>;
|
|
57
|
+
getKya(agentId: string, chainId?: number): Promise<any>;
|
|
58
|
+
attestKya(mandate: KYAMandate, chainId?: number, signature?: string, signerAddress?: `0x${string}`): Promise<any>;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
declare class NullClearEscrowAbortedError extends Error {
|
|
62
|
+
stage: string;
|
|
63
|
+
buyerCharged: string;
|
|
64
|
+
validationErrors: string[];
|
|
65
|
+
score?: number;
|
|
66
|
+
upstreamStatus?: number;
|
|
67
|
+
reason?: string;
|
|
68
|
+
constructor(message: string, details: {
|
|
69
|
+
stage: string;
|
|
70
|
+
buyerCharged?: string;
|
|
71
|
+
validationErrors?: string[];
|
|
72
|
+
score?: number;
|
|
73
|
+
upstreamStatus?: number;
|
|
74
|
+
reason?: string;
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Lightweight deterministic Zod-to-JSON-Schema converter
|
|
79
|
+
*/
|
|
80
|
+
declare function zodToJsonSchema(schema: z.ZodTypeAny): Record<string, unknown>;
|
|
81
|
+
interface NullClearOptions {
|
|
82
|
+
sellerAgentId?: string;
|
|
83
|
+
minTrustScore?: number;
|
|
84
|
+
schema?: z.ZodTypeAny | Record<string, unknown>;
|
|
85
|
+
requiredFields?: string[];
|
|
86
|
+
sha256Match?: string;
|
|
87
|
+
}
|
|
88
|
+
interface NullClearFetchConfig {
|
|
89
|
+
accountOrPrivateKey: SignerInput;
|
|
90
|
+
chainId?: number;
|
|
91
|
+
arbiterUrl?: string;
|
|
92
|
+
defaultMinTrustScore?: number;
|
|
93
|
+
customFetch?: typeof fetch;
|
|
94
|
+
}
|
|
95
|
+
type NullClearRequestInit = RequestInit & {
|
|
96
|
+
nullclear?: NullClearOptions;
|
|
97
|
+
};
|
|
98
|
+
/**
|
|
99
|
+
* Creates an enhanced fetch client that autonomously intercepts HTTP 402 Payment Required
|
|
100
|
+
* and safely wraps the call in NullClear Deferred Non-Custodial Schema Escrow
|
|
101
|
+
*/
|
|
102
|
+
declare function createNullClearFetch(config: NullClearFetchConfig): (input: RequestInfo | URL, init?: NullClearRequestInit) => Promise<Response>;
|
|
103
|
+
|
|
104
|
+
export { type ClearRequest, type ClearResult, NullClearClient, type NullClearClientConfig, NullClearEscrowAbortedError, type NullClearFetchConfig, type NullClearOptions, type NullClearRequestInit, type SignerInput, createNullClearFetch, signX402Payment, zodToJsonSchema };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,295 @@
|
|
|
1
|
+
// src/signer.ts
|
|
2
|
+
import { privateKeyToAccount } from "viem/accounts";
|
|
3
|
+
import {
|
|
4
|
+
EIP3009_TYPES,
|
|
5
|
+
getChainConfig,
|
|
6
|
+
encodeBase64Json
|
|
7
|
+
} from "@nullclear/shared";
|
|
8
|
+
async function signX402Payment(signer, option, resourceUrl) {
|
|
9
|
+
let accountAddress;
|
|
10
|
+
let signTypedDataFn;
|
|
11
|
+
if (typeof signer === "string" && signer.startsWith("0x")) {
|
|
12
|
+
const account = privateKeyToAccount(signer);
|
|
13
|
+
accountAddress = account.address;
|
|
14
|
+
signTypedDataFn = (params) => account.signTypedData(params);
|
|
15
|
+
} else if (typeof signer === "object" && signer !== null && "signTypedData" in signer && "account" in signer && signer.account) {
|
|
16
|
+
const rawAccount = signer.account;
|
|
17
|
+
accountAddress = typeof rawAccount === "string" ? rawAccount : rawAccount.address;
|
|
18
|
+
signTypedDataFn = (params) => signer.signTypedData(params);
|
|
19
|
+
} else {
|
|
20
|
+
throw new Error("Invalid signer provided to signX402Payment: must be private key or viem account/wallet client");
|
|
21
|
+
}
|
|
22
|
+
const randomBytes = new Uint8Array(32);
|
|
23
|
+
crypto.getRandomValues(randomBytes);
|
|
24
|
+
const nonce = `0x` + Array.from(randomBytes).map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
25
|
+
const validAfter = "0";
|
|
26
|
+
const timeoutSeconds = option.maxTimeoutSeconds && option.maxTimeoutSeconds > 0 ? option.maxTimeoutSeconds : 300;
|
|
27
|
+
const validBefore = String(Math.floor(Date.now() / 1e3) + timeoutSeconds);
|
|
28
|
+
const chainConfig = getChainConfig(option.network);
|
|
29
|
+
const domain = {
|
|
30
|
+
name: chainConfig.usdcDomain.name,
|
|
31
|
+
version: chainConfig.usdcDomain.version,
|
|
32
|
+
chainId: chainConfig.chainId,
|
|
33
|
+
verifyingContract: chainConfig.usdc
|
|
34
|
+
};
|
|
35
|
+
const authorization = {
|
|
36
|
+
from: accountAddress,
|
|
37
|
+
to: option.payTo,
|
|
38
|
+
value: option.amount,
|
|
39
|
+
validAfter,
|
|
40
|
+
validBefore,
|
|
41
|
+
nonce
|
|
42
|
+
};
|
|
43
|
+
const signature = await signTypedDataFn({
|
|
44
|
+
domain,
|
|
45
|
+
types: EIP3009_TYPES,
|
|
46
|
+
primaryType: "TransferWithAuthorization",
|
|
47
|
+
message: {
|
|
48
|
+
from: authorization.from,
|
|
49
|
+
to: authorization.to,
|
|
50
|
+
value: BigInt(authorization.value),
|
|
51
|
+
validAfter: BigInt(authorization.validAfter),
|
|
52
|
+
validBefore: BigInt(authorization.validBefore),
|
|
53
|
+
nonce: authorization.nonce
|
|
54
|
+
}
|
|
55
|
+
});
|
|
56
|
+
const payload = {
|
|
57
|
+
x402Version: 2,
|
|
58
|
+
resource: resourceUrl ? { url: resourceUrl } : void 0,
|
|
59
|
+
accepted: option,
|
|
60
|
+
payload: {
|
|
61
|
+
signature,
|
|
62
|
+
authorization
|
|
63
|
+
}
|
|
64
|
+
};
|
|
65
|
+
return encodeBase64Json(payload);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// src/client.ts
|
|
69
|
+
var NullClearClient = class {
|
|
70
|
+
arbiterUrl;
|
|
71
|
+
defaultChainId;
|
|
72
|
+
customFetch;
|
|
73
|
+
constructor(config = {}) {
|
|
74
|
+
this.arbiterUrl = (config.arbiterUrl || "https://arbiter.nullclear.dev").replace(/\/$/, "");
|
|
75
|
+
this.defaultChainId = config.defaultChainId || 8453;
|
|
76
|
+
this.customFetch = config.fetch || fetch;
|
|
77
|
+
}
|
|
78
|
+
async getScore(agentId, chainId) {
|
|
79
|
+
const targetChainId = chainId || this.defaultChainId;
|
|
80
|
+
const res = await this.customFetch(`${this.arbiterUrl}/v1/score/${targetChainId}/${agentId}`);
|
|
81
|
+
if (!res.ok) {
|
|
82
|
+
const err = await res.text();
|
|
83
|
+
throw new Error(`Failed to fetch score for agent ${agentId}: ${res.status} ${err}`);
|
|
84
|
+
}
|
|
85
|
+
const data = await res.json();
|
|
86
|
+
return data.data;
|
|
87
|
+
}
|
|
88
|
+
async clear(request) {
|
|
89
|
+
const res = await this.customFetch(`${this.arbiterUrl}/v1/clear`, {
|
|
90
|
+
method: "POST",
|
|
91
|
+
headers: { "Content-Type": "application/json" },
|
|
92
|
+
body: JSON.stringify(request)
|
|
93
|
+
});
|
|
94
|
+
const data = await res.json();
|
|
95
|
+
return data;
|
|
96
|
+
}
|
|
97
|
+
async getKya(agentId, chainId) {
|
|
98
|
+
const targetChainId = chainId || this.defaultChainId;
|
|
99
|
+
const res = await this.customFetch(`${this.arbiterUrl}/v1/kya/${targetChainId}/${agentId}`);
|
|
100
|
+
if (!res.ok) {
|
|
101
|
+
if (res.status === 404) return null;
|
|
102
|
+
const err = await res.text();
|
|
103
|
+
throw new Error(`Failed to fetch KYA mandate for agent ${agentId}: ${res.status} ${err}`);
|
|
104
|
+
}
|
|
105
|
+
const data = await res.json();
|
|
106
|
+
return data.data;
|
|
107
|
+
}
|
|
108
|
+
async attestKya(mandate, chainId, signature, signerAddress) {
|
|
109
|
+
const targetChainId = chainId || this.defaultChainId;
|
|
110
|
+
const res = await this.customFetch(`${this.arbiterUrl}/v1/kya/attest`, {
|
|
111
|
+
method: "POST",
|
|
112
|
+
headers: { "Content-Type": "application/json" },
|
|
113
|
+
body: JSON.stringify({
|
|
114
|
+
mandate,
|
|
115
|
+
chainId: targetChainId,
|
|
116
|
+
signature,
|
|
117
|
+
signerAddress
|
|
118
|
+
})
|
|
119
|
+
});
|
|
120
|
+
if (!res.ok) {
|
|
121
|
+
const err = await res.text();
|
|
122
|
+
throw new Error(`Failed to attest KYA mandate: ${res.status} ${err}`);
|
|
123
|
+
}
|
|
124
|
+
const data = await res.json();
|
|
125
|
+
return data.data;
|
|
126
|
+
}
|
|
127
|
+
};
|
|
128
|
+
|
|
129
|
+
// src/fetch-wrapper.ts
|
|
130
|
+
import { z } from "zod";
|
|
131
|
+
import {
|
|
132
|
+
decodeBase64Json
|
|
133
|
+
} from "@nullclear/shared";
|
|
134
|
+
var NullClearEscrowAbortedError = class extends Error {
|
|
135
|
+
stage;
|
|
136
|
+
buyerCharged;
|
|
137
|
+
validationErrors;
|
|
138
|
+
score;
|
|
139
|
+
upstreamStatus;
|
|
140
|
+
reason;
|
|
141
|
+
constructor(message, details) {
|
|
142
|
+
super(message);
|
|
143
|
+
this.name = "NullClearEscrowAbortedError";
|
|
144
|
+
this.stage = details.stage;
|
|
145
|
+
this.buyerCharged = details.buyerCharged || "0";
|
|
146
|
+
this.validationErrors = details.validationErrors || [];
|
|
147
|
+
this.score = details.score;
|
|
148
|
+
this.upstreamStatus = details.upstreamStatus;
|
|
149
|
+
this.reason = details.reason;
|
|
150
|
+
}
|
|
151
|
+
};
|
|
152
|
+
function zodToJsonSchema(schema) {
|
|
153
|
+
const def = schema._def;
|
|
154
|
+
if (!def) return {};
|
|
155
|
+
const typeName = def.typeName;
|
|
156
|
+
switch (typeName) {
|
|
157
|
+
case "ZodString":
|
|
158
|
+
return { type: "string" };
|
|
159
|
+
case "ZodNumber":
|
|
160
|
+
return { type: "number" };
|
|
161
|
+
case "ZodBoolean":
|
|
162
|
+
return { type: "boolean" };
|
|
163
|
+
case "ZodArray":
|
|
164
|
+
return {
|
|
165
|
+
type: "array",
|
|
166
|
+
items: def.type ? zodToJsonSchema(def.type) : {}
|
|
167
|
+
};
|
|
168
|
+
case "ZodObject": {
|
|
169
|
+
const shape = def.shape();
|
|
170
|
+
const properties = {};
|
|
171
|
+
const required = [];
|
|
172
|
+
for (const [key, value] of Object.entries(shape)) {
|
|
173
|
+
properties[key] = zodToJsonSchema(value);
|
|
174
|
+
const subDef = value._def;
|
|
175
|
+
if (subDef.typeName !== "ZodOptional" && subDef.typeName !== "ZodNullable") {
|
|
176
|
+
required.push(key);
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
return {
|
|
180
|
+
type: "object",
|
|
181
|
+
properties,
|
|
182
|
+
required: required.length > 0 ? required : void 0
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
case "ZodEnum":
|
|
186
|
+
return {
|
|
187
|
+
type: "string",
|
|
188
|
+
enum: def.values
|
|
189
|
+
};
|
|
190
|
+
case "ZodOptional":
|
|
191
|
+
case "ZodNullable":
|
|
192
|
+
return def.innerType ? zodToJsonSchema(def.innerType) : {};
|
|
193
|
+
default:
|
|
194
|
+
return {};
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
function createNullClearFetch(config) {
|
|
198
|
+
const chainId = config.chainId || 8453;
|
|
199
|
+
const arbiterUrl = (config.arbiterUrl || "https://arbiter.nullclear.dev").replace(/\/$/, "");
|
|
200
|
+
const defaultMinTrustScore = config.defaultMinTrustScore || 50;
|
|
201
|
+
const customFetch = config.customFetch || fetch;
|
|
202
|
+
return async function nullclearFetch(input, init) {
|
|
203
|
+
const targetUrl = typeof input === "string" ? input : input instanceof URL ? input.toString() : input.url;
|
|
204
|
+
const fetchInit = { ...init };
|
|
205
|
+
delete fetchInit.nullclear;
|
|
206
|
+
const initialRes = await customFetch(input, fetchInit);
|
|
207
|
+
if (initialRes.status !== 402) {
|
|
208
|
+
return initialRes;
|
|
209
|
+
}
|
|
210
|
+
const paymentRequiredHeader = initialRes.headers.get("PAYMENT-REQUIRED") || initialRes.headers.get("payment-required");
|
|
211
|
+
if (!paymentRequiredHeader) {
|
|
212
|
+
return initialRes;
|
|
213
|
+
}
|
|
214
|
+
let paymentRequired;
|
|
215
|
+
try {
|
|
216
|
+
paymentRequired = decodeBase64Json(paymentRequiredHeader);
|
|
217
|
+
} catch {
|
|
218
|
+
return initialRes;
|
|
219
|
+
}
|
|
220
|
+
const targetNetwork = `eip155:${chainId}`;
|
|
221
|
+
const selectedOption = paymentRequired.accepts.find(
|
|
222
|
+
(opt) => opt.network === targetNetwork && opt.scheme === "exact"
|
|
223
|
+
);
|
|
224
|
+
if (!selectedOption) {
|
|
225
|
+
throw new Error(
|
|
226
|
+
`Seller does not accept payments on network ${targetNetwork}. Accepted: ${paymentRequired.accepts.map((a) => a.network).join(", ")}`
|
|
227
|
+
);
|
|
228
|
+
}
|
|
229
|
+
const paymentSignatureHeader = await signX402Payment(
|
|
230
|
+
config.accountOrPrivateKey,
|
|
231
|
+
selectedOption,
|
|
232
|
+
targetUrl
|
|
233
|
+
);
|
|
234
|
+
let jsonSchema;
|
|
235
|
+
if (init?.nullclear?.schema) {
|
|
236
|
+
if (init.nullclear.schema instanceof z.ZodType) {
|
|
237
|
+
jsonSchema = zodToJsonSchema(init.nullclear.schema);
|
|
238
|
+
} else if (typeof init.nullclear.schema === "object") {
|
|
239
|
+
jsonSchema = init.nullclear.schema;
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
const clearPayload = {
|
|
243
|
+
targetUrl,
|
|
244
|
+
method: init?.method || "GET",
|
|
245
|
+
headers: init?.headers || {},
|
|
246
|
+
body: init?.body,
|
|
247
|
+
paymentSignatureHeader,
|
|
248
|
+
sellerAgentId: init?.nullclear?.sellerAgentId,
|
|
249
|
+
minTrustScore: init?.nullclear?.minTrustScore ?? defaultMinTrustScore,
|
|
250
|
+
validation: {
|
|
251
|
+
expectedStatus: [200],
|
|
252
|
+
jsonSchema,
|
|
253
|
+
requiredFields: init?.nullclear?.requiredFields,
|
|
254
|
+
sha256Match: init?.nullclear?.sha256Match
|
|
255
|
+
}
|
|
256
|
+
};
|
|
257
|
+
const arbiterRes = await customFetch(`${arbiterUrl}/v1/clear`, {
|
|
258
|
+
method: "POST",
|
|
259
|
+
headers: { "Content-Type": "application/json" },
|
|
260
|
+
body: JSON.stringify(clearPayload)
|
|
261
|
+
});
|
|
262
|
+
const arbiterData = await arbiterRes.json();
|
|
263
|
+
if (arbiterRes.status === 200 && arbiterData.cleared) {
|
|
264
|
+
const resBody = typeof arbiterData.data === "string" ? arbiterData.data : JSON.stringify(arbiterData.data);
|
|
265
|
+
const resHeaders = new Headers();
|
|
266
|
+
resHeaders.set("Content-Type", "application/json");
|
|
267
|
+
const paymentResponseHdr = arbiterRes.headers.get("PAYMENT-RESPONSE");
|
|
268
|
+
if (paymentResponseHdr) resHeaders.set("PAYMENT-RESPONSE", paymentResponseHdr);
|
|
269
|
+
const attestationHdr = arbiterRes.headers.get("X-NullClear-Attestation");
|
|
270
|
+
if (attestationHdr) resHeaders.set("X-NullClear-Attestation", attestationHdr);
|
|
271
|
+
return new Response(resBody, {
|
|
272
|
+
status: 200,
|
|
273
|
+
headers: resHeaders
|
|
274
|
+
});
|
|
275
|
+
}
|
|
276
|
+
throw new NullClearEscrowAbortedError(
|
|
277
|
+
`NullClear Escrow Aborted: ${arbiterData.stage || "FAILED"} - ${arbiterData.reason || arbiterData.error || (arbiterData.validationErrors || []).join("; ")}`,
|
|
278
|
+
{
|
|
279
|
+
stage: arbiterData.stage || "UNKNOWN_FAILURE",
|
|
280
|
+
buyerCharged: arbiterData.buyerCharged || "0",
|
|
281
|
+
validationErrors: arbiterData.validationErrors || [],
|
|
282
|
+
score: arbiterData.score,
|
|
283
|
+
upstreamStatus: arbiterData.upstreamStatus,
|
|
284
|
+
reason: arbiterData.reason || arbiterData.error
|
|
285
|
+
}
|
|
286
|
+
);
|
|
287
|
+
};
|
|
288
|
+
}
|
|
289
|
+
export {
|
|
290
|
+
NullClearClient,
|
|
291
|
+
NullClearEscrowAbortedError,
|
|
292
|
+
createNullClearFetch,
|
|
293
|
+
signX402Payment,
|
|
294
|
+
zodToJsonSchema
|
|
295
|
+
};
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,295 @@
|
|
|
1
|
+
// src/signer.ts
|
|
2
|
+
import { privateKeyToAccount } from "viem/accounts";
|
|
3
|
+
import {
|
|
4
|
+
EIP3009_TYPES,
|
|
5
|
+
getChainConfig,
|
|
6
|
+
encodeBase64Json
|
|
7
|
+
} from "@nullclear/shared";
|
|
8
|
+
async function signX402Payment(signer, option, resourceUrl) {
|
|
9
|
+
let accountAddress;
|
|
10
|
+
let signTypedDataFn;
|
|
11
|
+
if (typeof signer === "string" && signer.startsWith("0x")) {
|
|
12
|
+
const account = privateKeyToAccount(signer);
|
|
13
|
+
accountAddress = account.address;
|
|
14
|
+
signTypedDataFn = (params) => account.signTypedData(params);
|
|
15
|
+
} else if (typeof signer === "object" && signer !== null && "signTypedData" in signer && "account" in signer && signer.account) {
|
|
16
|
+
const rawAccount = signer.account;
|
|
17
|
+
accountAddress = typeof rawAccount === "string" ? rawAccount : rawAccount.address;
|
|
18
|
+
signTypedDataFn = (params) => signer.signTypedData(params);
|
|
19
|
+
} else {
|
|
20
|
+
throw new Error("Invalid signer provided to signX402Payment: must be private key or viem account/wallet client");
|
|
21
|
+
}
|
|
22
|
+
const randomBytes = new Uint8Array(32);
|
|
23
|
+
crypto.getRandomValues(randomBytes);
|
|
24
|
+
const nonce = `0x` + Array.from(randomBytes).map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
25
|
+
const validAfter = "0";
|
|
26
|
+
const timeoutSeconds = option.maxTimeoutSeconds && option.maxTimeoutSeconds > 0 ? option.maxTimeoutSeconds : 300;
|
|
27
|
+
const validBefore = String(Math.floor(Date.now() / 1e3) + timeoutSeconds);
|
|
28
|
+
const chainConfig = getChainConfig(option.network);
|
|
29
|
+
const domain = {
|
|
30
|
+
name: chainConfig.usdcDomain.name,
|
|
31
|
+
version: chainConfig.usdcDomain.version,
|
|
32
|
+
chainId: chainConfig.chainId,
|
|
33
|
+
verifyingContract: chainConfig.usdc
|
|
34
|
+
};
|
|
35
|
+
const authorization = {
|
|
36
|
+
from: accountAddress,
|
|
37
|
+
to: option.payTo,
|
|
38
|
+
value: option.amount,
|
|
39
|
+
validAfter,
|
|
40
|
+
validBefore,
|
|
41
|
+
nonce
|
|
42
|
+
};
|
|
43
|
+
const signature = await signTypedDataFn({
|
|
44
|
+
domain,
|
|
45
|
+
types: EIP3009_TYPES,
|
|
46
|
+
primaryType: "TransferWithAuthorization",
|
|
47
|
+
message: {
|
|
48
|
+
from: authorization.from,
|
|
49
|
+
to: authorization.to,
|
|
50
|
+
value: BigInt(authorization.value),
|
|
51
|
+
validAfter: BigInt(authorization.validAfter),
|
|
52
|
+
validBefore: BigInt(authorization.validBefore),
|
|
53
|
+
nonce: authorization.nonce
|
|
54
|
+
}
|
|
55
|
+
});
|
|
56
|
+
const payload = {
|
|
57
|
+
x402Version: 2,
|
|
58
|
+
resource: resourceUrl ? { url: resourceUrl } : void 0,
|
|
59
|
+
accepted: option,
|
|
60
|
+
payload: {
|
|
61
|
+
signature,
|
|
62
|
+
authorization
|
|
63
|
+
}
|
|
64
|
+
};
|
|
65
|
+
return encodeBase64Json(payload);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// src/client.ts
|
|
69
|
+
var NullClearClient = class {
|
|
70
|
+
arbiterUrl;
|
|
71
|
+
defaultChainId;
|
|
72
|
+
customFetch;
|
|
73
|
+
constructor(config = {}) {
|
|
74
|
+
this.arbiterUrl = (config.arbiterUrl || "https://arbiter.nullclear.dev").replace(/\/$/, "");
|
|
75
|
+
this.defaultChainId = config.defaultChainId || 8453;
|
|
76
|
+
this.customFetch = config.fetch || fetch;
|
|
77
|
+
}
|
|
78
|
+
async getScore(agentId, chainId) {
|
|
79
|
+
const targetChainId = chainId || this.defaultChainId;
|
|
80
|
+
const res = await this.customFetch(`${this.arbiterUrl}/v1/score/${targetChainId}/${agentId}`);
|
|
81
|
+
if (!res.ok) {
|
|
82
|
+
const err = await res.text();
|
|
83
|
+
throw new Error(`Failed to fetch score for agent ${agentId}: ${res.status} ${err}`);
|
|
84
|
+
}
|
|
85
|
+
const data = await res.json();
|
|
86
|
+
return data.data;
|
|
87
|
+
}
|
|
88
|
+
async clear(request) {
|
|
89
|
+
const res = await this.customFetch(`${this.arbiterUrl}/v1/clear`, {
|
|
90
|
+
method: "POST",
|
|
91
|
+
headers: { "Content-Type": "application/json" },
|
|
92
|
+
body: JSON.stringify(request)
|
|
93
|
+
});
|
|
94
|
+
const data = await res.json();
|
|
95
|
+
return data;
|
|
96
|
+
}
|
|
97
|
+
async getKya(agentId, chainId) {
|
|
98
|
+
const targetChainId = chainId || this.defaultChainId;
|
|
99
|
+
const res = await this.customFetch(`${this.arbiterUrl}/v1/kya/${targetChainId}/${agentId}`);
|
|
100
|
+
if (!res.ok) {
|
|
101
|
+
if (res.status === 404) return null;
|
|
102
|
+
const err = await res.text();
|
|
103
|
+
throw new Error(`Failed to fetch KYA mandate for agent ${agentId}: ${res.status} ${err}`);
|
|
104
|
+
}
|
|
105
|
+
const data = await res.json();
|
|
106
|
+
return data.data;
|
|
107
|
+
}
|
|
108
|
+
async attestKya(mandate, chainId, signature, signerAddress) {
|
|
109
|
+
const targetChainId = chainId || this.defaultChainId;
|
|
110
|
+
const res = await this.customFetch(`${this.arbiterUrl}/v1/kya/attest`, {
|
|
111
|
+
method: "POST",
|
|
112
|
+
headers: { "Content-Type": "application/json" },
|
|
113
|
+
body: JSON.stringify({
|
|
114
|
+
mandate,
|
|
115
|
+
chainId: targetChainId,
|
|
116
|
+
signature,
|
|
117
|
+
signerAddress
|
|
118
|
+
})
|
|
119
|
+
});
|
|
120
|
+
if (!res.ok) {
|
|
121
|
+
const err = await res.text();
|
|
122
|
+
throw new Error(`Failed to attest KYA mandate: ${res.status} ${err}`);
|
|
123
|
+
}
|
|
124
|
+
const data = await res.json();
|
|
125
|
+
return data.data;
|
|
126
|
+
}
|
|
127
|
+
};
|
|
128
|
+
|
|
129
|
+
// src/fetch-wrapper.ts
|
|
130
|
+
import { z } from "zod";
|
|
131
|
+
import {
|
|
132
|
+
decodeBase64Json
|
|
133
|
+
} from "@nullclear/shared";
|
|
134
|
+
var NullClearEscrowAbortedError = class extends Error {
|
|
135
|
+
stage;
|
|
136
|
+
buyerCharged;
|
|
137
|
+
validationErrors;
|
|
138
|
+
score;
|
|
139
|
+
upstreamStatus;
|
|
140
|
+
reason;
|
|
141
|
+
constructor(message, details) {
|
|
142
|
+
super(message);
|
|
143
|
+
this.name = "NullClearEscrowAbortedError";
|
|
144
|
+
this.stage = details.stage;
|
|
145
|
+
this.buyerCharged = details.buyerCharged || "0";
|
|
146
|
+
this.validationErrors = details.validationErrors || [];
|
|
147
|
+
this.score = details.score;
|
|
148
|
+
this.upstreamStatus = details.upstreamStatus;
|
|
149
|
+
this.reason = details.reason;
|
|
150
|
+
}
|
|
151
|
+
};
|
|
152
|
+
function zodToJsonSchema(schema) {
|
|
153
|
+
const def = schema._def;
|
|
154
|
+
if (!def) return {};
|
|
155
|
+
const typeName = def.typeName;
|
|
156
|
+
switch (typeName) {
|
|
157
|
+
case "ZodString":
|
|
158
|
+
return { type: "string" };
|
|
159
|
+
case "ZodNumber":
|
|
160
|
+
return { type: "number" };
|
|
161
|
+
case "ZodBoolean":
|
|
162
|
+
return { type: "boolean" };
|
|
163
|
+
case "ZodArray":
|
|
164
|
+
return {
|
|
165
|
+
type: "array",
|
|
166
|
+
items: def.type ? zodToJsonSchema(def.type) : {}
|
|
167
|
+
};
|
|
168
|
+
case "ZodObject": {
|
|
169
|
+
const shape = def.shape();
|
|
170
|
+
const properties = {};
|
|
171
|
+
const required = [];
|
|
172
|
+
for (const [key, value] of Object.entries(shape)) {
|
|
173
|
+
properties[key] = zodToJsonSchema(value);
|
|
174
|
+
const subDef = value._def;
|
|
175
|
+
if (subDef.typeName !== "ZodOptional" && subDef.typeName !== "ZodNullable") {
|
|
176
|
+
required.push(key);
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
return {
|
|
180
|
+
type: "object",
|
|
181
|
+
properties,
|
|
182
|
+
required: required.length > 0 ? required : void 0
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
case "ZodEnum":
|
|
186
|
+
return {
|
|
187
|
+
type: "string",
|
|
188
|
+
enum: def.values
|
|
189
|
+
};
|
|
190
|
+
case "ZodOptional":
|
|
191
|
+
case "ZodNullable":
|
|
192
|
+
return def.innerType ? zodToJsonSchema(def.innerType) : {};
|
|
193
|
+
default:
|
|
194
|
+
return {};
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
function createNullClearFetch(config) {
|
|
198
|
+
const chainId = config.chainId || 8453;
|
|
199
|
+
const arbiterUrl = (config.arbiterUrl || "https://arbiter.nullclear.dev").replace(/\/$/, "");
|
|
200
|
+
const defaultMinTrustScore = config.defaultMinTrustScore || 50;
|
|
201
|
+
const customFetch = config.customFetch || fetch;
|
|
202
|
+
return async function nullclearFetch(input, init) {
|
|
203
|
+
const targetUrl = typeof input === "string" ? input : input instanceof URL ? input.toString() : input.url;
|
|
204
|
+
const fetchInit = { ...init };
|
|
205
|
+
delete fetchInit.nullclear;
|
|
206
|
+
const initialRes = await customFetch(input, fetchInit);
|
|
207
|
+
if (initialRes.status !== 402) {
|
|
208
|
+
return initialRes;
|
|
209
|
+
}
|
|
210
|
+
const paymentRequiredHeader = initialRes.headers.get("PAYMENT-REQUIRED") || initialRes.headers.get("payment-required");
|
|
211
|
+
if (!paymentRequiredHeader) {
|
|
212
|
+
return initialRes;
|
|
213
|
+
}
|
|
214
|
+
let paymentRequired;
|
|
215
|
+
try {
|
|
216
|
+
paymentRequired = decodeBase64Json(paymentRequiredHeader);
|
|
217
|
+
} catch {
|
|
218
|
+
return initialRes;
|
|
219
|
+
}
|
|
220
|
+
const targetNetwork = `eip155:${chainId}`;
|
|
221
|
+
const selectedOption = paymentRequired.accepts.find(
|
|
222
|
+
(opt) => opt.network === targetNetwork && opt.scheme === "exact"
|
|
223
|
+
);
|
|
224
|
+
if (!selectedOption) {
|
|
225
|
+
throw new Error(
|
|
226
|
+
`Seller does not accept payments on network ${targetNetwork}. Accepted: ${paymentRequired.accepts.map((a) => a.network).join(", ")}`
|
|
227
|
+
);
|
|
228
|
+
}
|
|
229
|
+
const paymentSignatureHeader = await signX402Payment(
|
|
230
|
+
config.accountOrPrivateKey,
|
|
231
|
+
selectedOption,
|
|
232
|
+
targetUrl
|
|
233
|
+
);
|
|
234
|
+
let jsonSchema;
|
|
235
|
+
if (init?.nullclear?.schema) {
|
|
236
|
+
if (init.nullclear.schema instanceof z.ZodType) {
|
|
237
|
+
jsonSchema = zodToJsonSchema(init.nullclear.schema);
|
|
238
|
+
} else if (typeof init.nullclear.schema === "object") {
|
|
239
|
+
jsonSchema = init.nullclear.schema;
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
const clearPayload = {
|
|
243
|
+
targetUrl,
|
|
244
|
+
method: init?.method || "GET",
|
|
245
|
+
headers: init?.headers || {},
|
|
246
|
+
body: init?.body,
|
|
247
|
+
paymentSignatureHeader,
|
|
248
|
+
sellerAgentId: init?.nullclear?.sellerAgentId,
|
|
249
|
+
minTrustScore: init?.nullclear?.minTrustScore ?? defaultMinTrustScore,
|
|
250
|
+
validation: {
|
|
251
|
+
expectedStatus: [200],
|
|
252
|
+
jsonSchema,
|
|
253
|
+
requiredFields: init?.nullclear?.requiredFields,
|
|
254
|
+
sha256Match: init?.nullclear?.sha256Match
|
|
255
|
+
}
|
|
256
|
+
};
|
|
257
|
+
const arbiterRes = await customFetch(`${arbiterUrl}/v1/clear`, {
|
|
258
|
+
method: "POST",
|
|
259
|
+
headers: { "Content-Type": "application/json" },
|
|
260
|
+
body: JSON.stringify(clearPayload)
|
|
261
|
+
});
|
|
262
|
+
const arbiterData = await arbiterRes.json();
|
|
263
|
+
if (arbiterRes.status === 200 && arbiterData.cleared) {
|
|
264
|
+
const resBody = typeof arbiterData.data === "string" ? arbiterData.data : JSON.stringify(arbiterData.data);
|
|
265
|
+
const resHeaders = new Headers();
|
|
266
|
+
resHeaders.set("Content-Type", "application/json");
|
|
267
|
+
const paymentResponseHdr = arbiterRes.headers.get("PAYMENT-RESPONSE");
|
|
268
|
+
if (paymentResponseHdr) resHeaders.set("PAYMENT-RESPONSE", paymentResponseHdr);
|
|
269
|
+
const attestationHdr = arbiterRes.headers.get("X-NullClear-Attestation");
|
|
270
|
+
if (attestationHdr) resHeaders.set("X-NullClear-Attestation", attestationHdr);
|
|
271
|
+
return new Response(resBody, {
|
|
272
|
+
status: 200,
|
|
273
|
+
headers: resHeaders
|
|
274
|
+
});
|
|
275
|
+
}
|
|
276
|
+
throw new NullClearEscrowAbortedError(
|
|
277
|
+
`NullClear Escrow Aborted: ${arbiterData.stage || "FAILED"} - ${arbiterData.reason || arbiterData.error || (arbiterData.validationErrors || []).join("; ")}`,
|
|
278
|
+
{
|
|
279
|
+
stage: arbiterData.stage || "UNKNOWN_FAILURE",
|
|
280
|
+
buyerCharged: arbiterData.buyerCharged || "0",
|
|
281
|
+
validationErrors: arbiterData.validationErrors || [],
|
|
282
|
+
score: arbiterData.score,
|
|
283
|
+
upstreamStatus: arbiterData.upstreamStatus,
|
|
284
|
+
reason: arbiterData.reason || arbiterData.error
|
|
285
|
+
}
|
|
286
|
+
);
|
|
287
|
+
};
|
|
288
|
+
}
|
|
289
|
+
export {
|
|
290
|
+
NullClearClient,
|
|
291
|
+
NullClearEscrowAbortedError,
|
|
292
|
+
createNullClearFetch,
|
|
293
|
+
signX402Payment,
|
|
294
|
+
zodToJsonSchema
|
|
295
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,46 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nullclear/x402-arbiter",
|
|
3
|
-
"version": "
|
|
4
|
-
"
|
|
5
|
-
"
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "NullClear Developer and Agent SDK for safe x402 calls with deferred schema escrow",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"homepage": "https://nullclear.dev",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "https://github.com/Nullclear-Protocol/nullclear.git"
|
|
10
|
+
},
|
|
11
|
+
"type": "module",
|
|
12
|
+
"main": "./dist/index.cjs",
|
|
13
|
+
"module": "./dist/index.js",
|
|
14
|
+
"types": "./dist/index.d.ts",
|
|
15
|
+
"files": [
|
|
16
|
+
"dist",
|
|
17
|
+
"README.md"
|
|
18
|
+
],
|
|
19
|
+
"publishConfig": {
|
|
20
|
+
"access": "public"
|
|
21
|
+
},
|
|
22
|
+
"exports": {
|
|
23
|
+
".": {
|
|
24
|
+
"types": "./dist/index.d.ts",
|
|
25
|
+
"import": "./dist/index.js",
|
|
26
|
+
"require": "./dist/index.cjs"
|
|
27
|
+
}
|
|
28
|
+
},
|
|
29
|
+
"dependencies": {
|
|
30
|
+
"@nullclear/shared": "1.0.0",
|
|
31
|
+
"viem": "^2.23.0",
|
|
32
|
+
"zod": "^3.24.2"
|
|
33
|
+
},
|
|
34
|
+
"devDependencies": {
|
|
35
|
+
"@nullclear/worker-arbiter": "1.0.0",
|
|
36
|
+
"tsup": "^8.3.6",
|
|
37
|
+
"typescript": "^5.7.3",
|
|
38
|
+
"vitest": "^3.0.5"
|
|
39
|
+
},
|
|
40
|
+
"scripts": {
|
|
41
|
+
"build": "tsup src/index.ts --format esm,cjs --dts",
|
|
42
|
+
"typecheck": "tsc --noEmit",
|
|
43
|
+
"test": "vitest run",
|
|
44
|
+
"lint": "echo \"@nullclear/x402-arbiter clean\""
|
|
45
|
+
}
|
|
6
46
|
}
|