@weftlabs/sdk 0.25.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 +499 -0
- package/dist/client-C9_zh3Qg.d.mts +11 -0
- package/dist/client-C9_zh3Qg.d.ts +11 -0
- package/dist/facilitator/index.d.mts +18 -0
- package/dist/facilitator/index.d.ts +18 -0
- package/dist/facilitator/index.js +180 -0
- package/dist/facilitator/index.js.map +1 -0
- package/dist/facilitator/index.mjs +149 -0
- package/dist/facilitator/index.mjs.map +1 -0
- package/dist/facilitator/middleware/index.d.mts +539 -0
- package/dist/facilitator/middleware/index.d.ts +539 -0
- package/dist/facilitator/middleware/index.js +1522 -0
- package/dist/facilitator/middleware/index.js.map +1 -0
- package/dist/facilitator/middleware/index.mjs +1493 -0
- package/dist/facilitator/middleware/index.mjs.map +1 -0
- package/dist/index.d.mts +5667 -0
- package/dist/index.d.ts +5667 -0
- package/dist/index.js +5555 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +5553 -0
- package/dist/index.mjs.map +1 -0
- package/dist/server.d.mts +8 -0
- package/dist/server.d.ts +8 -0
- package/dist/server.js +1587 -0
- package/dist/server.js.map +1 -0
- package/dist/server.mjs +1574 -0
- package/dist/server.mjs.map +1 -0
- package/docs/APIKeysApi.md +219 -0
- package/docs/AccountApi.md +72 -0
- package/docs/AccountBootstrapApproval.md +36 -0
- package/docs/AccountBootstrapCreated.md +44 -0
- package/docs/AccountBootstrapCreatedApproval.md +38 -0
- package/docs/AccountBootstrapCreatedResponse.md +32 -0
- package/docs/AccountBootstrapRequest.md +42 -0
- package/docs/AccountBootstrapStatus.md +40 -0
- package/docs/AccountBootstrapStatusResponse.md +32 -0
- package/docs/AccountDetails.md +43 -0
- package/docs/AgentBootstrapApi.md +223 -0
- package/docs/ApiKey.md +42 -0
- package/docs/ApiKeyCreated.md +40 -0
- package/docs/ApiKeyCreatedResponse.md +32 -0
- package/docs/ApiKeyListResponse.md +34 -0
- package/docs/AuthApi.md +408 -0
- package/docs/AuthResponse.md +32 -0
- package/docs/AuthResponseData.md +36 -0
- package/docs/BalanceApi.md +73 -0
- package/docs/BalanceResponse.md +45 -0
- package/docs/ConfirmRequest.md +32 -0
- package/docs/CreateApiKeyRequest.md +32 -0
- package/docs/DocumentationApi.md +65 -0
- package/docs/EnrolledResource.md +59 -0
- package/docs/ErrorResponse.md +33 -0
- package/docs/Fetch403Response.md +44 -0
- package/docs/FetchApi.md +91 -0
- package/docs/FetchBalanceSnapshot.md +41 -0
- package/docs/FetchErrorResponse.md +41 -0
- package/docs/FetchRequest.md +46 -0
- package/docs/FetchRequestBody.md +31 -0
- package/docs/FetchResponse.md +49 -0
- package/docs/InsufficientScopeResponse.md +37 -0
- package/docs/MeApiKey.md +40 -0
- package/docs/MeApiKeyCreator.md +37 -0
- package/docs/MeResponse.md +32 -0
- package/docs/MeResponseData.md +56 -0
- package/docs/MessageResponse.md +32 -0
- package/docs/MessageResponseData.md +32 -0
- package/docs/ModelError.md +39 -0
- package/docs/Pagination.md +38 -0
- package/docs/PasswordResetRequest.md +32 -0
- package/docs/PasswordUpdateRequest.md +36 -0
- package/docs/Payment.md +56 -0
- package/docs/PaymentListResponse.md +34 -0
- package/docs/PaymentResponse.md +32 -0
- package/docs/PaymentsApi.md +154 -0
- package/docs/PrincipalWallet.md +34 -0
- package/docs/PromoBalance.md +37 -0
- package/docs/Purchase.md +58 -0
- package/docs/PurchaseArtifact.md +38 -0
- package/docs/PurchaseListResponse.md +34 -0
- package/docs/PurchaseResponse.md +32 -0
- package/docs/PurchasesApi.md +157 -0
- package/docs/RateLimitResponse.md +34 -0
- package/docs/ResendConfirmationRequest.md +32 -0
- package/docs/ResourceEnrollmentRequest.md +39 -0
- package/docs/ResourceEnrollmentResponse.md +32 -0
- package/docs/ResourceStats.md +45 -0
- package/docs/ResourcesApi.md +77 -0
- package/docs/SearchAccessMethod.md +52 -0
- package/docs/SearchApi.md +86 -0
- package/docs/SearchCapabilityRef.md +34 -0
- package/docs/SearchCuratedCallability.md +34 -0
- package/docs/SearchCuratedExecution.md +32 -0
- package/docs/SearchCuratedOperation.md +36 -0
- package/docs/SearchCuratedService.md +34 -0
- package/docs/SearchCuratedSource.md +36 -0
- package/docs/SearchEndpointCall.md +39 -0
- package/docs/SearchEndpointHit.md +74 -0
- package/docs/SearchEndpointPrice.md +41 -0
- package/docs/SearchErrorResponse.md +35 -0
- package/docs/SearchExecutionModeFilter.md +34 -0
- package/docs/SearchFilterSpec.md +49 -0
- package/docs/SearchMethodFilter.md +34 -0
- package/docs/SearchPaymentOffer.md +47 -0
- package/docs/SearchPriceAtomicFilter.md +41 -0
- package/docs/SearchPriceUsdFilter.md +41 -0
- package/docs/SearchProtocolFilter.md +35 -0
- package/docs/SearchProviderRef.md +36 -0
- package/docs/SearchRequest.md +36 -0
- package/docs/SearchResourceTypeFilter.md +35 -0
- package/docs/SearchResponse.md +67 -0
- package/docs/SearchResponseWarningsInner.md +38 -0
- package/docs/SearchResult.md +38 -0
- package/docs/SearchStringSetFilter.md +34 -0
- package/docs/SearchWeftFetchCompatibility.md +36 -0
- package/docs/SignInRequest.md +34 -0
- package/docs/SignUpRequest.md +36 -0
- package/docs/SpendingPolicy.md +36 -0
- package/docs/User.md +36 -0
- package/docs/UserPrincipal.md +49 -0
- package/docs/Wallet.md +42 -0
- package/examples/charge-api-hono.mjs +40 -0
- package/examples/charge-api.mjs +43 -0
- package/examples/quickstart.mjs +16 -0
- package/package.json +105 -0
package/README.md
ADDED
|
@@ -0,0 +1,499 @@
|
|
|
1
|
+
# `@weftlabs/sdk`
|
|
2
|
+
|
|
3
|
+
The supported TypeScript client for building buyer applications on Weft.
|
|
4
|
+
|
|
5
|
+
## Get a buyer API key
|
|
6
|
+
|
|
7
|
+
Sign in at [weft.network](https://weft.network), then create a key in
|
|
8
|
+
[Dashboard → API keys](https://weft.network/dashboard/buyer/api_keys). Copy the
|
|
9
|
+
one-time `wk_*` value and store it as `WEFT_API_KEY` in your secret manager or
|
|
10
|
+
shell. Do not put it in source code, command arguments, or logs.
|
|
11
|
+
|
|
12
|
+
```sh
|
|
13
|
+
export WEFT_API_KEY="wk_..."
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
## Install
|
|
17
|
+
|
|
18
|
+
```sh
|
|
19
|
+
npm install @weftlabs/sdk @x402/core
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
Node.js 18 or newer is required. The client uses
|
|
23
|
+
`https://weft.network` unless `baseUrl` is explicitly supplied.
|
|
24
|
+
|
|
25
|
+
## OAuth access tokens
|
|
26
|
+
|
|
27
|
+
User-facing applications can pass an OAuth bearer token instead of an API key:
|
|
28
|
+
|
|
29
|
+
```js
|
|
30
|
+
const weft = new WeftClient({ accessToken });
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
Provide exactly one of `apiKey` or `accessToken`. The SDK sends either value as
|
|
34
|
+
bearer authentication. Your application owns OAuth registration, PKCE,
|
|
35
|
+
redirects, refresh, and secure session storage; never expose the token to
|
|
36
|
+
browser JavaScript.
|
|
37
|
+
|
|
38
|
+
## First authenticated search
|
|
39
|
+
|
|
40
|
+
```js
|
|
41
|
+
import { WeftClient } from "@weftlabs/sdk";
|
|
42
|
+
|
|
43
|
+
const apiKey = process.env.WEFT_API_KEY;
|
|
44
|
+
if (!apiKey) throw new Error("Set WEFT_API_KEY to a buyer wk_* API key");
|
|
45
|
+
|
|
46
|
+
const weft = new WeftClient({ apiKey });
|
|
47
|
+
|
|
48
|
+
const account = await weft.me();
|
|
49
|
+
const search = await weft.search({ query: "weather data API" });
|
|
50
|
+
|
|
51
|
+
console.log({ account: account.data, results: search.results });
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
The complete example is shipped as [`examples/quickstart.mjs`](examples/quickstart.mjs)
|
|
55
|
+
and is executed from the packed npm artifact in CI.
|
|
56
|
+
|
|
57
|
+
## Bounded paid fetch
|
|
58
|
+
|
|
59
|
+
Every paid fetch needs an explicit spending ceiling. Supply an idempotency key
|
|
60
|
+
and reuse that same key when retrying after a timeout or uncertain response.
|
|
61
|
+
|
|
62
|
+
```js
|
|
63
|
+
import { randomUUID } from "node:crypto";
|
|
64
|
+
import { WeftClient } from "@weftlabs/sdk";
|
|
65
|
+
|
|
66
|
+
const apiKey = process.env.WEFT_API_KEY;
|
|
67
|
+
if (!apiKey) throw new Error("Set WEFT_API_KEY to a buyer wk_* API key");
|
|
68
|
+
|
|
69
|
+
const weft = new WeftClient({ apiKey });
|
|
70
|
+
const idempotencyKey = randomUUID();
|
|
71
|
+
|
|
72
|
+
const artifact = await weft.fetch(
|
|
73
|
+
{
|
|
74
|
+
url: "https://merchant.example/data",
|
|
75
|
+
maxCostUsd: "0.05",
|
|
76
|
+
},
|
|
77
|
+
{ idempotencyKey },
|
|
78
|
+
);
|
|
79
|
+
|
|
80
|
+
console.log({ idempotencyKey, artifact });
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
Do not create a new key for a retry of the same logical purchase. The CLI
|
|
84
|
+
generates a key automatically and returns it in its success envelope.
|
|
85
|
+
|
|
86
|
+
## CLI
|
|
87
|
+
|
|
88
|
+
The `weft` executable is published separately as `@weftlabs/cli`.
|
|
89
|
+
|
|
90
|
+
```sh
|
|
91
|
+
npx --package @weftlabs/cli weft me
|
|
92
|
+
npx --package @weftlabs/cli weft balance
|
|
93
|
+
npx --package @weftlabs/cli weft search "weather data API" --max-results 5
|
|
94
|
+
npx --package @weftlabs/cli weft fetch "https://merchant.example/data" \
|
|
95
|
+
--max-cost-usd 0.05
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
The CLI accepts credentials from `--api-key-stdin`, `WEFT_API_KEY`, or its
|
|
99
|
+
protected local stored OAuth/bootstrap credentials. It never accepts a key in
|
|
100
|
+
process arguments. Every response is a versioned JSON envelope suitable for
|
|
101
|
+
scripts. See the [CLI guide](../cli/README.md) for credential-free bootstrap,
|
|
102
|
+
human claim, and automatic Skill installation.
|
|
103
|
+
|
|
104
|
+
## Error handling
|
|
105
|
+
|
|
106
|
+
The generated transport throws `ResponseError` for non-2xx responses. Inspect
|
|
107
|
+
the status and the structured response body, and retain the server request ID
|
|
108
|
+
when asking for support.
|
|
109
|
+
|
|
110
|
+
```js
|
|
111
|
+
import { WeftClient, WeftError } from "@weftlabs/sdk";
|
|
112
|
+
|
|
113
|
+
const apiKey = process.env.WEFT_API_KEY;
|
|
114
|
+
if (!apiKey) throw new Error("Set WEFT_API_KEY to a buyer wk_* API key");
|
|
115
|
+
|
|
116
|
+
const weft = new WeftClient({ apiKey });
|
|
117
|
+
|
|
118
|
+
try {
|
|
119
|
+
await weft.search({ query: "weather data API" });
|
|
120
|
+
} catch (error) {
|
|
121
|
+
if (error instanceof WeftError) {
|
|
122
|
+
console.error({
|
|
123
|
+
status: error.status,
|
|
124
|
+
code: error.code,
|
|
125
|
+
requestId: error.requestId,
|
|
126
|
+
retryable: error.retryable,
|
|
127
|
+
details: error.details,
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
throw error;
|
|
131
|
+
}
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
- `401`: confirm that `WEFT_API_KEY` contains a current buyer `wk_*` key.
|
|
135
|
+
- `403`: inspect `code` and `details` for an insufficient balance or spending
|
|
136
|
+
policy denial before changing the request.
|
|
137
|
+
- `409` (`IDEMPOTENCY_CONFLICT`): this buyer already used the supplied
|
|
138
|
+
idempotency key for a different fetch request. Generate a new key for the
|
|
139
|
+
new operation; the original operation retries unchanged with its own key.
|
|
140
|
+
`retryable` is `false` — resending the conflicting request will conflict
|
|
141
|
+
again.
|
|
142
|
+
- `429`: honor `Retry-After` and back off.
|
|
143
|
+
- `5xx`: retry transient failures with backoff; reuse the idempotency key for a
|
|
144
|
+
paid fetch.
|
|
145
|
+
- `status: 0` (`NETWORK_ERROR`): the request failed before any Weft response,
|
|
146
|
+
so the outcome is uncertain. `retryable` is `true`; retry with backoff and
|
|
147
|
+
reuse the idempotency key for a paid fetch.
|
|
148
|
+
|
|
149
|
+
## Advanced generated APIs
|
|
150
|
+
|
|
151
|
+
`WeftClient` is the stable application entrypoint. For operations it does not
|
|
152
|
+
yet wrap, the generated OpenAPI classes remain exported:
|
|
153
|
+
|
|
154
|
+
```js
|
|
155
|
+
import { Configuration, SearchApi } from "@weftlabs/sdk";
|
|
156
|
+
|
|
157
|
+
const configuration = new Configuration({
|
|
158
|
+
accessToken: process.env.WEFT_API_KEY,
|
|
159
|
+
basePath: "https://weft.network",
|
|
160
|
+
});
|
|
161
|
+
const searchApi = new SearchApi(configuration);
|
|
162
|
+
const result = await searchApi.search({
|
|
163
|
+
searchRequest: { query: "weather data API" },
|
|
164
|
+
});
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
See the [API reference](https://weft.network/docs) and
|
|
168
|
+
[OpenAPI document](https://weft.network/docs/openapi.yaml) for the full
|
|
169
|
+
contract.
|
|
170
|
+
|
|
171
|
+
## Facilitator integration
|
|
172
|
+
|
|
173
|
+
Seller infrastructure can import the separately exported facilitator helpers:
|
|
174
|
+
|
|
175
|
+
```js
|
|
176
|
+
import { createFacilitatorClient, getFeeInfo } from "@weftlabs/sdk/facilitator";
|
|
177
|
+
|
|
178
|
+
const facilitator = createFacilitatorClient();
|
|
179
|
+
const fee = await getFeeInfo();
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
The default facilitator URL is `https://x402.weft.network`; override it through
|
|
183
|
+
the helper configuration or `X402_FACILITATOR_URL`.
|
|
184
|
+
|
|
185
|
+
## Charging for your own API
|
|
186
|
+
|
|
187
|
+
The payment middleware asks unpaid callers to pay and lets paid callers
|
|
188
|
+
through. The money settles to your wallet; Weft never holds it.
|
|
189
|
+
|
|
190
|
+
Selling needs a **seller** key, not the buyer `wk_*` key above. Create one in
|
|
191
|
+
[Dashboard → Seller → API keys](https://weft.network/dashboard/seller/api_keys).
|
|
192
|
+
It starts with `ax_live_` and is shown once. Store it as
|
|
193
|
+
`WEFT_SELLER_API_KEY`, and put the wallet that gets paid in `WEFT_PAY_TO`.
|
|
194
|
+
|
|
195
|
+
A seller installs a scheme package too. The middleware carries the x402
|
|
196
|
+
plumbing; the scheme prices the route and shapes the payment:
|
|
197
|
+
|
|
198
|
+
```sh
|
|
199
|
+
npm install @weftlabs/sdk @x402/core @x402/evm express
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
<!-- example:charge-api.mjs:start -->
|
|
203
|
+
|
|
204
|
+
```js
|
|
205
|
+
import express from "express";
|
|
206
|
+
import { ExactEvmScheme } from "@x402/evm/exact/server";
|
|
207
|
+
import { weftPaymentMiddleware } from "@weftlabs/sdk/facilitator/middleware";
|
|
208
|
+
|
|
209
|
+
const apiKey = process.env.WEFT_SELLER_API_KEY;
|
|
210
|
+
if (!apiKey) {
|
|
211
|
+
throw new Error("Set WEFT_SELLER_API_KEY to a seller ax_live_* API key");
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
const payTo = process.env.WEFT_PAY_TO;
|
|
215
|
+
if (!payTo) {
|
|
216
|
+
throw new Error("Set WEFT_PAY_TO to the wallet address that gets paid");
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
const network = process.env.WEFT_NETWORK ?? "eip155:8453";
|
|
220
|
+
|
|
221
|
+
const app = express();
|
|
222
|
+
|
|
223
|
+
app.use(
|
|
224
|
+
weftPaymentMiddleware(
|
|
225
|
+
{
|
|
226
|
+
"GET /v1/quote": {
|
|
227
|
+
accepts: { scheme: "exact", network, payTo, price: "$0.01" },
|
|
228
|
+
},
|
|
229
|
+
},
|
|
230
|
+
{
|
|
231
|
+
apiKey,
|
|
232
|
+
name: "Acme Pricing API",
|
|
233
|
+
type: "api",
|
|
234
|
+
tags: ["finance", "pricing"],
|
|
235
|
+
schemes: [{ network, server: new ExactEvmScheme() }],
|
|
236
|
+
},
|
|
237
|
+
),
|
|
238
|
+
);
|
|
239
|
+
|
|
240
|
+
app.get("/v1/quote", (_req, res) => {
|
|
241
|
+
res.json({ symbol: "ACME", price: "12.34", currency: "USD" });
|
|
242
|
+
});
|
|
243
|
+
|
|
244
|
+
const port = Number(process.env.PORT ?? 3000);
|
|
245
|
+
app.listen(port, () => {
|
|
246
|
+
console.log(JSON.stringify({ listening: port }));
|
|
247
|
+
});
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
<!-- example:charge-api.mjs:end -->
|
|
251
|
+
|
|
252
|
+
`weftPaymentMiddlewareHono` is the Hono equivalent and takes the same
|
|
253
|
+
configuration:
|
|
254
|
+
|
|
255
|
+
<!-- example:charge-api-hono.mjs:start -->
|
|
256
|
+
|
|
257
|
+
```js
|
|
258
|
+
import { Hono } from "hono";
|
|
259
|
+
import { ExactEvmScheme } from "@x402/evm/exact/server";
|
|
260
|
+
import { weftPaymentMiddlewareHono } from "@weftlabs/sdk/facilitator/middleware";
|
|
261
|
+
|
|
262
|
+
const apiKey = process.env.WEFT_SELLER_API_KEY;
|
|
263
|
+
if (!apiKey) {
|
|
264
|
+
throw new Error("Set WEFT_SELLER_API_KEY to a seller ax_live_* API key");
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
const payTo = process.env.WEFT_PAY_TO;
|
|
268
|
+
if (!payTo) {
|
|
269
|
+
throw new Error("Set WEFT_PAY_TO to the wallet address that gets paid");
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
const network = process.env.WEFT_NETWORK ?? "eip155:8453";
|
|
273
|
+
|
|
274
|
+
const app = new Hono();
|
|
275
|
+
|
|
276
|
+
app.use(
|
|
277
|
+
weftPaymentMiddlewareHono(
|
|
278
|
+
{
|
|
279
|
+
"GET /v1/quote": {
|
|
280
|
+
accepts: { scheme: "exact", network, payTo, price: "$0.01" },
|
|
281
|
+
},
|
|
282
|
+
},
|
|
283
|
+
{
|
|
284
|
+
apiKey,
|
|
285
|
+
name: "Acme Pricing API",
|
|
286
|
+
type: "api",
|
|
287
|
+
tags: ["finance", "pricing"],
|
|
288
|
+
schemes: [{ network, server: new ExactEvmScheme() }],
|
|
289
|
+
},
|
|
290
|
+
),
|
|
291
|
+
);
|
|
292
|
+
|
|
293
|
+
app.get("/v1/quote", (c) =>
|
|
294
|
+
c.json({ symbol: "ACME", price: "12.34", currency: "USD" }),
|
|
295
|
+
);
|
|
296
|
+
|
|
297
|
+
export default app;
|
|
298
|
+
```
|
|
299
|
+
|
|
300
|
+
<!-- example:charge-api-hono.mjs:end -->
|
|
301
|
+
|
|
302
|
+
Both blocks are the shipped `examples/charge-api.mjs` and
|
|
303
|
+
`examples/charge-api-hono.mjs` verbatim. `tests/readme-examples.test.ts` fails
|
|
304
|
+
if they drift. The root artifact test runs both from the packed package against
|
|
305
|
+
a stub facilitator.
|
|
306
|
+
|
|
307
|
+
### What the facilitator does per paid request
|
|
308
|
+
|
|
309
|
+
The middleware calls the facilitator twice. `/verify` checks the buyer's
|
|
310
|
+
payment before your handler runs. `/settle` moves the money after your handler
|
|
311
|
+
returns success.
|
|
312
|
+
|
|
313
|
+
`/settle` rejects a call without your seller key — every settlement 401s and
|
|
314
|
+
you are never paid. `/verify` works without the key but reads it when present,
|
|
315
|
+
and the facilitator uses it to attribute verification attempts to your
|
|
316
|
+
product. Set `apiKey` once and both are covered.
|
|
317
|
+
|
|
318
|
+
The direct REST contract for `/supported`, `/verify` and `/settle` is the
|
|
319
|
+
advanced path. Sellers do not need it to charge for a route.
|
|
320
|
+
|
|
321
|
+
### Declaring your product
|
|
322
|
+
|
|
323
|
+
`name`, `type`, `tags` and `iconUrl` describe the product once and apply to
|
|
324
|
+
every protected route. They travel on the 402 challenge, are copied onto the
|
|
325
|
+
buyer's payment, and arrive with the settlement — so your product appears in
|
|
326
|
+
the Weft dashboard already named and categorised, with no form to fill in.
|
|
327
|
+
|
|
328
|
+
| Field | Meaning |
|
|
329
|
+
|---|---|
|
|
330
|
+
| `name` | Display name, e.g. `"Acme Pricing API"`. |
|
|
331
|
+
| `type` | `"api"`, `"agent"` or `"mcp"`. |
|
|
332
|
+
| `tags` | Up to four free-text tags, or five if you omit `type`. |
|
|
333
|
+
| `iconUrl` | Absolute `http`/`https` URL of an icon. |
|
|
334
|
+
| `productId` | Identifier of the dashboard product this deployment claims to be. |
|
|
335
|
+
| `manifestHash` | Hash of the product manifest this deployment was built from. |
|
|
336
|
+
|
|
337
|
+
`type`, `productId` and `manifestHash` additionally travel in the x402
|
|
338
|
+
extensions channel as `extensions["weft.product"]` — `{info, schema}`, with
|
|
339
|
+
`info` holding `kind`, `product_id` and `manifest_hash` and `schema` the JSON
|
|
340
|
+
Schema describing it. Buyers on `@x402/core` echo the declaration onto their
|
|
341
|
+
payment, so it arrives with the settlement; fields you do not declare are
|
|
342
|
+
omitted, never sent empty. A route that declares its own
|
|
343
|
+
`extensions["weft.product"]` keeps it.
|
|
344
|
+
|
|
345
|
+
`apiKey` is the one secret in the config: the API key minted with your product
|
|
346
|
+
in the Weft dashboard. It does two jobs. It **authenticates settlement** — the
|
|
347
|
+
facilitator requires it on every settle call that credits your wallet, so
|
|
348
|
+
without it (and without your own `facilitator.createAuthHeaders`) paid requests
|
|
349
|
+
fail at the money step. And it **announces the product** at boot — one
|
|
350
|
+
authenticated call carrying the identity above, so the dashboard shows your
|
|
351
|
+
product connected, named and typed before the first payment arrives. If you
|
|
352
|
+
supply your own `createAuthHeaders`, yours wins; `apiKey` never overrides it.
|
|
353
|
+
The announcement can never block or break your server: a facilitator that is
|
|
354
|
+
down or slow at boot costs you nothing, and payment-protected routes recover on
|
|
355
|
+
their own once it is reachable again.
|
|
356
|
+
|
|
357
|
+
Setting any of these on an individual route overrides the product-level value
|
|
358
|
+
for that route only — `type` included, so an API with an MCP endpoint beside it
|
|
359
|
+
can say so per route:
|
|
360
|
+
|
|
361
|
+
```js
|
|
362
|
+
weftPaymentMiddleware(
|
|
363
|
+
{
|
|
364
|
+
"GET /v1/search": { accepts },
|
|
365
|
+
"POST /mcp": { accepts, type: "mcp" },
|
|
366
|
+
},
|
|
367
|
+
{ name: "Acme Pricing API", type: "api" },
|
|
368
|
+
);
|
|
369
|
+
```
|
|
370
|
+
|
|
371
|
+
`type` has no field of its own in the x402 protocol, so the SDK sends it as one
|
|
372
|
+
reserved tag — `weft:type:api`, `weft:type:agent`, `weft:type:mcp`. That is why
|
|
373
|
+
`tags` carries four of your own values rather than five: the protocol allows
|
|
374
|
+
five in total. Any `weft:type:*` value you put in `tags` yourself is dropped
|
|
375
|
+
with a warning — declare `type` instead.
|
|
376
|
+
|
|
377
|
+
#### Stamping the request on the payment
|
|
378
|
+
|
|
379
|
+
`extensions` on a route carries structured metadata to the buyer and on to the
|
|
380
|
+
settlement, alongside the `weft.product` declaration above. Any key in it may
|
|
381
|
+
be a **callback instead of a static value**, evaluated where `price` is
|
|
382
|
+
evaluated — while the challenge for that request is built:
|
|
383
|
+
|
|
384
|
+
```js
|
|
385
|
+
weftPaymentMiddleware(
|
|
386
|
+
{
|
|
387
|
+
"POST /v1/generate": {
|
|
388
|
+
accepts: [{ scheme: "exact", network, payTo, price: quoteFromBody }],
|
|
389
|
+
extensions: {
|
|
390
|
+
"weft.request": async (context) => {
|
|
391
|
+
const { model, max_tokens } = await context.adapter.getBody();
|
|
392
|
+
return { model, max_tokens };
|
|
393
|
+
},
|
|
394
|
+
},
|
|
395
|
+
},
|
|
396
|
+
},
|
|
397
|
+
{ apiKey, name: "Acme Image API", type: "api" },
|
|
398
|
+
);
|
|
399
|
+
```
|
|
400
|
+
|
|
401
|
+
That is how a seller who prices per request — from a model name, a token
|
|
402
|
+
budget, a page count — can also say what the buyer asked for. The buyer's
|
|
403
|
+
client echoes the resolved value onto the payment, and the facilitator relays
|
|
404
|
+
it onto the settlement, so it is there to display next to the amount.
|
|
405
|
+
|
|
406
|
+
`weft.request` is the key to reach for. Under it the SDK owns the envelope: the
|
|
407
|
+
callback returns the `info` payload alone and a published JSON Schema is
|
|
408
|
+
stamped beside it, the same way `weft.product` is assembled from the fields you
|
|
409
|
+
declare. That schema is deliberately open — what a request asked for is your
|
|
410
|
+
vocabulary, not ours — so what it publishes is a posture rather than a field
|
|
411
|
+
list: seller-authored, display-only, key nothing on it.
|
|
412
|
+
|
|
413
|
+
`weft.product` says what is being sold and is fixed at boot. `weft.request`
|
|
414
|
+
says what this one call asked for. Any *other* key works too and ships your
|
|
415
|
+
value verbatim, envelope and all — the escape hatch if you need your own
|
|
416
|
+
namespace on the wire. Prefer the named key: x402 extensions are named
|
|
417
|
+
capabilities with published contracts, and a key each seller invents is
|
|
418
|
+
readable by nobody but Weft.
|
|
419
|
+
|
|
420
|
+
`await context.adapter.getBody()` rather than `context.adapter.getBody()`: the
|
|
421
|
+
body is a promise on Hono and a plain value on Express.
|
|
422
|
+
|
|
423
|
+
Two rules:
|
|
424
|
+
|
|
425
|
+
- **Be deterministic for one request.** The challenge is built twice — once for
|
|
426
|
+
the unpaid request, once for the buyer's paid retry — and the buyer's echo is
|
|
427
|
+
checked against the second one. A clock, a counter or a random value in here
|
|
428
|
+
fails that check and costs you the sale, not just the blob. Derive the value
|
|
429
|
+
from the request.
|
|
430
|
+
- **It is display-only.** By the time it reaches a consumer it is
|
|
431
|
+
unauthenticated buyer input, like every echoed extension. Attribution keys on
|
|
432
|
+
the API key that settled the payment, never on this.
|
|
433
|
+
|
|
434
|
+
Return plain JSON data. The value travels as JSON and is advertised exactly as
|
|
435
|
+
JSON round-trips it, so a `NaN` arrives as `null` and a function-valued field
|
|
436
|
+
does not arrive at all — the challenge and the buyer's echo always agree.
|
|
437
|
+
|
|
438
|
+
Anything the callback cannot deliver costs the blob and never the payment: a
|
|
439
|
+
callback that throws, returns a non-object, returns something JSON cannot carry,
|
|
440
|
+
or pushes the challenge's whole extensions object past the facilitator's 16 KiB
|
|
441
|
+
relay cap has its key dropped from the challenge, with one `[weft]` line saying
|
|
442
|
+
which and why. The rest of the declaration, `weft.product` included, ships as
|
|
443
|
+
normal. Return `undefined` to skip a request deliberately; that one is silent.
|
|
444
|
+
|
|
445
|
+
#### Declaring what revenue can be sliced by
|
|
446
|
+
|
|
447
|
+
`dimensions` names the fields inside `weft.request` that revenue may be
|
|
448
|
+
broken down by:
|
|
449
|
+
|
|
450
|
+
```js
|
|
451
|
+
weftPaymentMiddleware(routes, {
|
|
452
|
+
apiKey,
|
|
453
|
+
name: "Acme Image API",
|
|
454
|
+
dimensions: ["model", "tier"],
|
|
455
|
+
});
|
|
456
|
+
```
|
|
457
|
+
|
|
458
|
+
Declare that, and "how much did this endpoint earn this month with
|
|
459
|
+
`model = gpt-5.6`?" becomes a question the dashboard can answer.
|
|
460
|
+
|
|
461
|
+
It travels on the boot handshake and nowhere else — never on the 402
|
|
462
|
+
challenge. That is the whole point. The values on a payment are echoed back
|
|
463
|
+
by the buyer, so an aggregate over a field you never declared would be
|
|
464
|
+
summing input the buyer controlled. This call is authenticated with your API
|
|
465
|
+
key, so a field named here is one you vouched for.
|
|
466
|
+
|
|
467
|
+
**Declare dimensions, not payloads.** Low cardinality, enumerable: `model`,
|
|
468
|
+
`tier`, `size`, `region`. Never prompt text, a user id, or a document body —
|
|
469
|
+
those cannot be indexed, are useless as a breakdown, and put your users'
|
|
470
|
+
content somewhere it does not belong.
|
|
471
|
+
|
|
472
|
+
At most eight travel, each at most 64 characters and shaped like a field name.
|
|
473
|
+
Anything else is dropped with a `[weft]` line, and the handshake still goes.
|
|
474
|
+
|
|
475
|
+
#### What the SDK trims, and why
|
|
476
|
+
|
|
477
|
+
The x402 protocol's `ResourceInfo` is narrow, and a buyer that validates the
|
|
478
|
+
challenge rejects **the whole challenge** over one out-of-bounds field: you
|
|
479
|
+
would not lose your product name, you would lose the sale. So the SDK keeps
|
|
480
|
+
what it emits inside the protocol's bounds, and tells you at startup — one
|
|
481
|
+
`[weft]` line per problem — what changed. Nothing is reported per request, and
|
|
482
|
+
nothing throws; a payment server should not refuse to boot over a display name.
|
|
483
|
+
|
|
484
|
+
| Limit | What the SDK does |
|
|
485
|
+
|---|---|
|
|
486
|
+
| `name` over 32 characters | Truncates it to 32. `"Acme Real Estate Property Records API"` ships as `"Acme Real Estate Property Record"`. 32 is tight for real product names, so check what yours becomes. |
|
|
487
|
+
| A tag over 32 characters | Drops that tag, keeps the rest. |
|
|
488
|
+
| More tags than the protocol carries | Drops the extras; a declared `type` always survives. |
|
|
489
|
+
| A `name` or tag that is not printable ASCII | Drops it. `"Acme Café"` does not travel. |
|
|
490
|
+
| An `iconUrl` that is not an absolute `http`/`https` URL, or is over 2048 characters | Drops it. A dashboard renders this URL, so no other scheme is relayed. |
|
|
491
|
+
| A `type` outside `api`/`agent`/`mcp`, or any field of the wrong type | Ignores it and says so. Your other tags are unaffected. |
|
|
492
|
+
|
|
493
|
+
The ASCII restriction is the protocol's, not Weft's — the Weft facilitator
|
|
494
|
+
relays a name in any script. The SDK enforces it anyway, because the challenge
|
|
495
|
+
reaches buyers before it reaches any facilitator and a buyer running the
|
|
496
|
+
published schema throws the whole challenge out. That cost falls on sellers
|
|
497
|
+
whose names are not expressible in ASCII. The fix belongs in the x402 schema;
|
|
498
|
+
until it lands, spell `name` in ASCII and put the rest in the route's
|
|
499
|
+
`description`.
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { FacilitatorConfig, HTTPFacilitatorClient } from '@x402/core/server';
|
|
2
|
+
|
|
3
|
+
declare const X402_FACILITATOR_URL = "https://x402.weft.network";
|
|
4
|
+
interface WeftFacilitatorConfig {
|
|
5
|
+
url?: string;
|
|
6
|
+
createAuthHeaders?: FacilitatorConfig["createAuthHeaders"];
|
|
7
|
+
}
|
|
8
|
+
type AuthHeadersByPath = Awaited<ReturnType<NonNullable<FacilitatorConfig["createAuthHeaders"]>>>;
|
|
9
|
+
declare function createFacilitatorClient(config?: WeftFacilitatorConfig, weftAuthHeaders?: AuthHeadersByPath): HTTPFacilitatorClient;
|
|
10
|
+
|
|
11
|
+
export { type WeftFacilitatorConfig as W, X402_FACILITATOR_URL as X, createFacilitatorClient as c };
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { FacilitatorConfig, HTTPFacilitatorClient } from '@x402/core/server';
|
|
2
|
+
|
|
3
|
+
declare const X402_FACILITATOR_URL = "https://x402.weft.network";
|
|
4
|
+
interface WeftFacilitatorConfig {
|
|
5
|
+
url?: string;
|
|
6
|
+
createAuthHeaders?: FacilitatorConfig["createAuthHeaders"];
|
|
7
|
+
}
|
|
8
|
+
type AuthHeadersByPath = Awaited<ReturnType<NonNullable<FacilitatorConfig["createAuthHeaders"]>>>;
|
|
9
|
+
declare function createFacilitatorClient(config?: WeftFacilitatorConfig, weftAuthHeaders?: AuthHeadersByPath): HTTPFacilitatorClient;
|
|
10
|
+
|
|
11
|
+
export { type WeftFacilitatorConfig as W, X402_FACILITATOR_URL as X, createFacilitatorClient as c };
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export * from '@x402/core';
|
|
2
|
+
export * from '@x402/core/types';
|
|
3
|
+
import { W as WeftFacilitatorConfig } from '../client-C9_zh3Qg.mjs';
|
|
4
|
+
export { X as X402_FACILITATOR_URL, c as createFacilitatorClient } from '../client-C9_zh3Qg.mjs';
|
|
5
|
+
import '@x402/core/server';
|
|
6
|
+
|
|
7
|
+
interface FeeInfo {
|
|
8
|
+
amount: string;
|
|
9
|
+
asset: string;
|
|
10
|
+
network: string;
|
|
11
|
+
}
|
|
12
|
+
interface FeeCacheConfig {
|
|
13
|
+
ttl?: number;
|
|
14
|
+
}
|
|
15
|
+
declare function invalidateFeeCache(): void;
|
|
16
|
+
declare function getFeeInfo(config?: WeftFacilitatorConfig, cacheConfig?: FeeCacheConfig): Promise<FeeInfo>;
|
|
17
|
+
|
|
18
|
+
export { type FeeCacheConfig, type FeeInfo, WeftFacilitatorConfig, getFeeInfo, invalidateFeeCache };
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export * from '@x402/core';
|
|
2
|
+
export * from '@x402/core/types';
|
|
3
|
+
import { W as WeftFacilitatorConfig } from '../client-C9_zh3Qg.js';
|
|
4
|
+
export { X as X402_FACILITATOR_URL, c as createFacilitatorClient } from '../client-C9_zh3Qg.js';
|
|
5
|
+
import '@x402/core/server';
|
|
6
|
+
|
|
7
|
+
interface FeeInfo {
|
|
8
|
+
amount: string;
|
|
9
|
+
asset: string;
|
|
10
|
+
network: string;
|
|
11
|
+
}
|
|
12
|
+
interface FeeCacheConfig {
|
|
13
|
+
ttl?: number;
|
|
14
|
+
}
|
|
15
|
+
declare function invalidateFeeCache(): void;
|
|
16
|
+
declare function getFeeInfo(config?: WeftFacilitatorConfig, cacheConfig?: FeeCacheConfig): Promise<FeeInfo>;
|
|
17
|
+
|
|
18
|
+
export { type FeeCacheConfig, type FeeInfo, WeftFacilitatorConfig, getFeeInfo, invalidateFeeCache };
|