@catena/sdk 0.0.0-alpha-20260818193756 → 0.1.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 +300 -28
- package/dist/{client-DOzVVmm_.mjs → client-CHZMO00P.mjs} +287 -77
- package/dist/client-Cm2wNUuB.d.mts +1096 -0
- package/dist/client.d.mts +2 -2
- package/dist/client.mjs +2 -2
- package/dist/mpp.d.mts +315 -0
- package/dist/mpp.mjs +564 -0
- package/dist/settlement-report-CT3EbtLL.mjs +36 -0
- package/dist/viem.d.mts +135 -0
- package/dist/viem.mjs +301 -0
- package/dist/x402.d.mts +17 -4
- package/dist/x402.mjs +20 -34
- package/package.json +28 -6
- package/dist/client-DC_AWoCZ.d.mts +0 -619
package/README.md
CHANGED
|
@@ -1,33 +1,34 @@
|
|
|
1
1
|
# @catena/sdk
|
|
2
2
|
|
|
3
3
|
Typed client for the Catena agent API: read accounts, balances, and policy, move
|
|
4
|
-
money through policy-checked intents, and pay x402
|
|
4
|
+
money through policy-checked intents, and pay MPP or x402 HTTP 402 challenges.
|
|
5
5
|
Node >= 20; no framework dependencies.
|
|
6
6
|
|
|
7
|
-
## Status
|
|
7
|
+
## Status
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
snapshot versions (`0.0.0-alpha-<timestamp>`) never match ordinary semver ranges
|
|
13
|
-
— only an exact version, an explicit prerelease range, or a dist-tag like
|
|
14
|
-
`alpha` selects one.
|
|
9
|
+
The SDK is in initial development. Patch releases within a `0.x` minor are
|
|
10
|
+
intended to remain compatible; a new minor may change the public API. Test
|
|
11
|
+
upgrades before moving to a new `0.x` minor.
|
|
15
12
|
|
|
16
13
|
```sh
|
|
17
|
-
npm install @catena/sdk
|
|
14
|
+
npm install @catena/sdk
|
|
18
15
|
```
|
|
19
16
|
|
|
20
|
-
Pin the exact version you tested against; do not depend on the `alpha` tag
|
|
21
|
-
resolving to anything compatible over time.
|
|
22
|
-
|
|
23
17
|
## Entry points
|
|
24
18
|
|
|
25
19
|
- `@catena/sdk` — the client: `createCatenaClient`, intents, accounts,
|
|
26
20
|
counterparties, error types.
|
|
27
21
|
- `@catena/sdk/keypair` — generate or derive the P-256 credential and compute
|
|
28
22
|
its thumbprint.
|
|
23
|
+
- `@catena/sdk/mpp` — a managed MPP `fetch` wrapper plus Catena-backed
|
|
24
|
+
`evm/charge` and `usdc/charge` methods for custom mppx integrations. Requires
|
|
25
|
+
`mppx` and its viem peer dependencies (both optional SDK peer dependencies;
|
|
26
|
+
install `mppx@0.9.0` and `viem@^2.54.0`).
|
|
29
27
|
- `@catena/sdk/x402` — a `fetch` wrapper that pays x402 402 challenges through
|
|
30
28
|
Catena.
|
|
29
|
+
- `@catena/sdk/viem` — a viem `Account` backed by a Catena wallet, for x402
|
|
30
|
+
client tooling that expects an account or `{ address, signTypedData }`.
|
|
31
|
+
Requires `viem` (an optional peer dependency — install it yourself, `^2.21`).
|
|
31
32
|
|
|
32
33
|
## Authentication
|
|
33
34
|
|
|
@@ -81,6 +82,18 @@ await client.submitIntent({
|
|
|
81
82
|
// Email-only creation saves a counterparty without sending an email. A completed
|
|
82
83
|
// intent returns data.counterparty with status "awaiting_details" and no rails.
|
|
83
84
|
|
|
85
|
+
await client.submitIntent({
|
|
86
|
+
action: {
|
|
87
|
+
type: "request_counterparty_details",
|
|
88
|
+
counterpartyId: counterparties[0].id,
|
|
89
|
+
methods: { bank: true, wallet: false },
|
|
90
|
+
},
|
|
91
|
+
})
|
|
92
|
+
|
|
93
|
+
// The request follows policy and may remain pending for human approval. While
|
|
94
|
+
// an invitation is outstanding, another request returns ApiError status 409
|
|
95
|
+
// with code "counterparty_payment_request_conflict" and details.invite.
|
|
96
|
+
|
|
84
97
|
// The rail must match the method: "ach" and "wire" need a bank rail,
|
|
85
98
|
// "on-chain" needs a wallet rail. Pass the rail id, not the counterparty id.
|
|
86
99
|
const bankRail = counterparties
|
|
@@ -90,7 +103,6 @@ const bankRail = counterparties
|
|
|
90
103
|
const intent = await client.submitIntent({
|
|
91
104
|
action: {
|
|
92
105
|
type: "send",
|
|
93
|
-
accountId: accounts[0].id,
|
|
94
106
|
counterpartyRailId: bankRail.id,
|
|
95
107
|
// Decimal USD string: "12.50" is $12.50. Never cents or atomic units.
|
|
96
108
|
amount: "12.50",
|
|
@@ -99,18 +111,37 @@ const intent = await client.submitIntent({
|
|
|
99
111
|
})
|
|
100
112
|
```
|
|
101
113
|
|
|
114
|
+
For `send`, omit `accountId` when the agent's current effective policy allows
|
|
115
|
+
sends from exactly one account. Other readable or transfer accounts do not
|
|
116
|
+
count. The server rejects zero send accounts with HTTP 403
|
|
117
|
+
(`policy_send_account_unavailable`) and multiple send accounts with HTTP 400
|
|
118
|
+
(`policy_send_account_required`). An explicit account always stays explicit; it
|
|
119
|
+
is never replaced with another account. Transfers still require `accountId`.
|
|
120
|
+
|
|
121
|
+
The result's `accountId` is the resolved source, pinned at creation and returned
|
|
122
|
+
by `getIntent` too, including for pending or blocked intents. Approval checks
|
|
123
|
+
the pinned account against the current policy; it never chooses a new source.
|
|
124
|
+
|
|
125
|
+
Each new accountless operation resolves the current policy. Reusing an explicit
|
|
126
|
+
idempotency key keeps the original intent's source, even if the policy now
|
|
127
|
+
selects another account or no longer has a unique send account. Changed payment
|
|
128
|
+
details or an explicitly different source return HTTP 409. Policy-blocked
|
|
129
|
+
attempts are reevaluated against the current policy with the source still
|
|
130
|
+
pinned. Use a new key for a new operation. If the outcome of a submission is
|
|
131
|
+
unknown, check that intent before submitting again.
|
|
132
|
+
|
|
102
133
|
`submitIntent` returns a disposition, not a guarantee of execution — branch on
|
|
103
134
|
`intent.status`:
|
|
104
135
|
|
|
105
136
|
- `"completed"` — the action succeeded: money moved for send, transfer, and
|
|
106
|
-
wallet_send; for x402 the payment authorization was delivered
|
|
107
|
-
settlement is verified separately); the counterparty exists for
|
|
137
|
+
wallet_send; for MPP and x402 the payment authorization was delivered
|
|
138
|
+
(on-chain settlement is verified separately); the counterparty exists for
|
|
108
139
|
create_counterparty.
|
|
109
140
|
- `"pending"` — parked, typically awaiting a human approval; `reasons` says why,
|
|
110
141
|
`expiresAt` says when the approval request lapses.
|
|
111
142
|
- `"processing"` — accepted and in progress. Executing intents advance on their
|
|
112
|
-
own; poll `getIntent(intent.id)`. An approved x402 payment instead
|
|
113
|
-
until the paid request is re-run — polling never advances it.
|
|
143
|
+
own; poll `getIntent(intent.id)`. An approved MPP or x402 payment instead
|
|
144
|
+
rests here until the paid request is re-run — polling never advances it.
|
|
114
145
|
- `"blocked"` — declined by policy or denied by an operator; `reasons` explains
|
|
115
146
|
why approval was required, not necessarily why it was denied. Terminal.
|
|
116
147
|
- `"failed"` — failed, expired, or reversed. Terminal.
|
|
@@ -144,12 +175,13 @@ deadline, so a multi-exchange operation may take several timeout periods.
|
|
|
144
175
|
directly; a submit-stamp timeout is the `cause` of an `IntentSubmitError`
|
|
145
176
|
whose `outcome` is `"unknown"`.
|
|
146
177
|
- A timeout means the SDK stopped waiting, not that the server did no work.
|
|
147
|
-
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
178
|
+
- Each `submitIntent` invocation generates a fresh UUID when `idempotencyKey`
|
|
179
|
+
is omitted. Matching actions alone are not deduplicated.
|
|
180
|
+
- To recover an exact submission after a create-intent timeout, choose and
|
|
181
|
+
persist an explicit `idempotencyKey` before the first call, then retry the
|
|
182
|
+
unchanged action with that key. Reusing it with a different action
|
|
183
|
+
returns 409. If the first call omitted the key, do not treat a second call
|
|
184
|
+
as its retry: it is a new logical operation.
|
|
153
185
|
- If that retry throws an `ApiError` with status `409` and code
|
|
154
186
|
`wallet_send_intent_not_executing` or `x402_intent_state_mismatch`, the SDK
|
|
155
187
|
cannot reissue the co-signing body and the response carries no intent ID.
|
|
@@ -160,11 +192,16 @@ deadline, so a multi-exchange operation may take several timeout periods.
|
|
|
160
192
|
awaiting expiry. A payment pending or granted approval is instead matched by
|
|
161
193
|
its canonical requirements and reused; a lapsed approval may park again.
|
|
162
194
|
- `IntentSubmitError` — `submitIntent` failed after the intent was created.
|
|
163
|
-
Branch on `outcome`:
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
195
|
+
Branch on `outcome`:
|
|
196
|
+
|
|
197
|
+
- `"not-submitted"` means no money moved. Recover with a new `submitIntent`
|
|
198
|
+
invocation and a new key, typically after fixing the signing credential.
|
|
199
|
+
Omit `idempotencyKey` to generate one, or supply a different value. Reusing
|
|
200
|
+
the original key finds the stranded intent and can return a 409 rather than
|
|
201
|
+
restart signing.
|
|
202
|
+
- `"unknown"` means the server may have acted — poll `getIntent(err.intentId)`
|
|
203
|
+
to a terminal status and resubmit only after blocked or failed, never
|
|
204
|
+
blindly.
|
|
168
205
|
|
|
169
206
|
## Paying x402 challenges
|
|
170
207
|
|
|
@@ -204,6 +241,241 @@ re-run the same request after approval; `X402RetryFailedError` and
|
|
|
204
241
|
`X402SubmitInterruptedError` mean money may have already moved — follow the
|
|
205
242
|
recovery steps in the error message instead of paying again.
|
|
206
243
|
|
|
244
|
+
## Paying MPP challenges
|
|
245
|
+
|
|
246
|
+
Install mppx and its compatible viem version alongside the SDK. Catena supports
|
|
247
|
+
the `evm/charge` and `usdc/charge` wire pairs for native USDC on Base and Base
|
|
248
|
+
Sepolia:
|
|
249
|
+
|
|
250
|
+
```sh
|
|
251
|
+
npm install @catena/sdk mppx@0.9.0 viem@^2.54.0
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
```ts
|
|
255
|
+
import { createCatenaClient } from "@catena/sdk"
|
|
256
|
+
import { wrapFetchWithMppPayment } from "@catena/sdk/mpp"
|
|
257
|
+
|
|
258
|
+
const client = createCatenaClient()
|
|
259
|
+
const fetchWithPayment = wrapFetchWithMppPayment(client, {
|
|
260
|
+
accountId: walletAccountId,
|
|
261
|
+
// Refuse a price above 1 USDC before creating an intent.
|
|
262
|
+
maxAtomicAmount: 1_000_000n,
|
|
263
|
+
onPayment: (receipt) => {
|
|
264
|
+
recordPayment(receipt)
|
|
265
|
+
},
|
|
266
|
+
})
|
|
267
|
+
|
|
268
|
+
const response = await fetchWithPayment("https://api.example.com/paid-thing")
|
|
269
|
+
```
|
|
270
|
+
|
|
271
|
+
The managed wrapper passes ordinary responses and undecodable 402s through
|
|
272
|
+
untouched. For an eligible challenge, mppx parses and orders the candidates,
|
|
273
|
+
chooses `Authorization` or `Payment-Authorization`, and attaches the credential.
|
|
274
|
+
The wrapper tries at most the first five supported candidates and moves to the
|
|
275
|
+
next only when Catena returns `mpp_challenge_not_payable`,
|
|
276
|
+
`mpp_network_mismatch`, or `mpp_counterparty_rail_not_found`. It creates at most
|
|
277
|
+
one credential and makes exactly one paid retry.
|
|
278
|
+
|
|
279
|
+
The payment is bound to the response's final canonical URL. A cross-origin
|
|
280
|
+
redirect is rejected before an intent is created. The credential-bearing retry
|
|
281
|
+
preserves the caller's redirect mode and delegates redirect handling to the
|
|
282
|
+
configured Fetch implementation, consistent with the x402 wrapper. Request
|
|
283
|
+
bodies must be replayable: use a string, `URLSearchParams`, Blob, ArrayBuffer,
|
|
284
|
+
or typed array in the `init` argument; a body-bearing `Request`,
|
|
285
|
+
`ReadableStream`, or raw `FormData` is refused before payment. The wrapper does
|
|
286
|
+
not pre-verify an optional request digest—the seller verifies it when accepting
|
|
287
|
+
the credential.
|
|
288
|
+
|
|
289
|
+
`onPayment` runs once after Catena completes and validates the credential, but
|
|
290
|
+
before the paid retry. Its `MppPaymentReceipt` contains the intent ID, validated
|
|
291
|
+
method and terms, and canonical resource URL; it never contains the credential.
|
|
292
|
+
If the callback, retry, or a second 402 fails, `MppRetryFailedError.receipt`
|
|
293
|
+
identifies the completed intent so the caller does not pay twice.
|
|
294
|
+
|
|
295
|
+
Recovery errors are explicit: retry the original request after resolving
|
|
296
|
+
`MppApprovalPendingError`; treat `MppPaymentDeclinedError` as terminal; fund one
|
|
297
|
+
of `MppNetworkMismatchError.requiredNetworks`; and add the rail named by
|
|
298
|
+
`MppCounterpartyNotFoundError`. For `MppSubmitInterruptedError`, first reconcile
|
|
299
|
+
`intentId` with `client.getIntent(...)`, because submission may have succeeded.
|
|
300
|
+
A create-phase `TimeoutError`, non-fallback `ApiError`, and `IntentSubmitError`
|
|
301
|
+
with outcome `"not-submitted"` propagate unchanged.
|
|
302
|
+
|
|
303
|
+
### Custom mppx integration
|
|
304
|
+
|
|
305
|
+
Use the lower-level Catena methods when the host needs its own candidate policy,
|
|
306
|
+
retry count, event hooks, or transport behavior:
|
|
307
|
+
|
|
308
|
+
```ts
|
|
309
|
+
import { createCatenaClient } from "@catena/sdk"
|
|
310
|
+
import { catena } from "@catena/sdk/mpp"
|
|
311
|
+
import { Mppx } from "mppx/client"
|
|
312
|
+
|
|
313
|
+
const client = createCatenaClient()
|
|
314
|
+
const mppx = Mppx.create({
|
|
315
|
+
methods: [
|
|
316
|
+
catena({
|
|
317
|
+
client,
|
|
318
|
+
accountId: walletAccountId,
|
|
319
|
+
maxAtomicAmount: 1_000_000n,
|
|
320
|
+
}),
|
|
321
|
+
],
|
|
322
|
+
maxPaymentRetries: 1,
|
|
323
|
+
polyfill: false,
|
|
324
|
+
})
|
|
325
|
+
|
|
326
|
+
const response = await mppx.fetch("https://api.example.com/paid-thing")
|
|
327
|
+
```
|
|
328
|
+
|
|
329
|
+
`catena.charge(...)` is an equivalent alias for `catena(...)`. mppx parses and
|
|
330
|
+
selects challenges, chooses `Authorization` or `Payment-Authorization`, retries
|
|
331
|
+
the request, and runs its response hooks. The Catena method submits the selected
|
|
332
|
+
challenge through the normal policy and approval lifecycle, then returns the
|
|
333
|
+
serialized credential to mppx.
|
|
334
|
+
|
|
335
|
+
By default, `mppx@0.9.0` allows up to three automatic payment attempts per
|
|
336
|
+
fetch. If the seller keeps returning payable challenges with fresh IDs, one
|
|
337
|
+
fetch can produce multiple payment authorizations that may be redeemed
|
|
338
|
+
separately. Each attempt still goes through Catena policy and approval checks;
|
|
339
|
+
these checks do not guarantee one payment per fetch.
|
|
340
|
+
|
|
341
|
+
Set `maxAtomicAmount` when creating the Catena methods to refuse an individual
|
|
342
|
+
challenge above that price before creating an intent. The value uses atomic USDC
|
|
343
|
+
units (6 decimals, so `1_000_000n` is 1 USDC). The server's policy limits still
|
|
344
|
+
apply. This ceiling is checked separately for every challenge and does not limit
|
|
345
|
+
the number of attempts.
|
|
346
|
+
|
|
347
|
+
To allow only one automatic payment attempt per fetch, set
|
|
348
|
+
`maxPaymentRetries: 1` in `Mppx.create(...)`. This stops automatic negotiation
|
|
349
|
+
of fresh challenges after that attempt, but does not prevent duplicate purchases
|
|
350
|
+
across separate fetch calls.
|
|
351
|
+
|
|
352
|
+
With `mppx@0.9.0`, pass the HTTP method and body in the fetch `init` argument,
|
|
353
|
+
not only inside a `Request` object. mppx rebuilds the paid retry from `init`, so
|
|
354
|
+
a method or body stored only in `Request` can be lost: a POST may retry as a
|
|
355
|
+
bodiless GET, and even a bodiless DELETE may retry as GET. For example:
|
|
356
|
+
|
|
357
|
+
```ts
|
|
358
|
+
const response = await mppx.fetch("https://api.example.com/paid-thing", {
|
|
359
|
+
method: "POST",
|
|
360
|
+
headers: { "Content-Type": "application/json" },
|
|
361
|
+
body: JSON.stringify({ query: "example" }),
|
|
362
|
+
})
|
|
363
|
+
```
|
|
364
|
+
|
|
365
|
+
The body must also be reusable and byte-stable: every attempt must send the same
|
|
366
|
+
bytes, as with the JSON string above. Raw `FormData` can generate a new
|
|
367
|
+
multipart boundary each time it is sent, so identical fields can still fail a
|
|
368
|
+
seller's request-body digest check. Serialize multipart bodies once, then reuse
|
|
369
|
+
the resulting bytes and matching `Content-Type` on every attempt. Do not rely on
|
|
370
|
+
`ReadableStream` bodies being replayed: the first send consumes the stream, and
|
|
371
|
+
the paid retry can fail after authorization. Buffer the body before the first
|
|
372
|
+
request instead; this uses memory and sacrifices streaming uploads.
|
|
373
|
+
|
|
374
|
+
The method relays a challenge digest unchanged; it does not read or hash the
|
|
375
|
+
HTTP request body. The seller verifies any body binding when it accepts the
|
|
376
|
+
credential. Unsupported methods, intents, currencies, networks, and credential
|
|
377
|
+
types are rejected before a Catena intent is submitted.
|
|
378
|
+
|
|
379
|
+
Additional selection requirements apply: the UTC challenge expiry must end in
|
|
380
|
+
`Z`, use no more than 3 fractional-second digits, and leave at least 5 seconds
|
|
381
|
+
and no more than 1 hour. The optional credential header must be omitted or be
|
|
382
|
+
exactly `Payment-Authorization`. An optional digest must use
|
|
383
|
+
`sha-256=:<base64>:` or the mppx 0.9 spelling `sha-256=<base64>`, with a
|
|
384
|
+
canonical, padded standard-base64 SHA-256 value. Optional `opaque` data must be
|
|
385
|
+
nonempty, unpadded base64url. Addresses must be lowercase or have a valid EIP-55
|
|
386
|
+
checksum. Request `description` and `externalId` text must contain valid Unicode
|
|
387
|
+
without unpaired surrogates. When `maxAtomicAmount` is configured, the request
|
|
388
|
+
amount must not exceed it.
|
|
389
|
+
|
|
390
|
+
When Catena declines a challenge during selection, mppx can report
|
|
391
|
+
`No method found for challenges` even when it lists `evm.charge` or
|
|
392
|
+
`usdc.charge` as available. Check these requirements as well as the supported
|
|
393
|
+
payment types when diagnosing that error.
|
|
394
|
+
|
|
395
|
+
An intent result that does not yield a usable credential throws
|
|
396
|
+
`MppPaymentError`, which exposes `intentId`, `status`, `reasons`, and
|
|
397
|
+
`expiresAt`. A pending payment needs human approval. Inspect an in-flight intent
|
|
398
|
+
with `client.getIntent(error.intentId)`. After approval, re-run the original
|
|
399
|
+
paid request so its fresh challenge can consume the approved grant; polling
|
|
400
|
+
reports the status but never advances an approved MPP payment. Blocked and
|
|
401
|
+
failed payments include the server's reasons in the thrown error.
|
|
402
|
+
|
|
403
|
+
Submission can also throw `IntentSubmitError` from `@catena/sdk`. An `outcome`
|
|
404
|
+
of `"unknown"` means the server may already have signed an authorization. Poll
|
|
405
|
+
`client.getIntent(error.intentId)` to determine the outcome before making
|
|
406
|
+
another payment attempt.
|
|
407
|
+
|
|
408
|
+
An HTTP failure (including another 402), network error, or later
|
|
409
|
+
`MppPaymentError` does not prove that earlier authorizations were unused. An
|
|
410
|
+
`MppPaymentError` describes only its own intent; earlier attempts in the same
|
|
411
|
+
fetch may already have produced redeemable credentials. Before retrying the
|
|
412
|
+
original request, including after approval, reconcile all payment attempts, not
|
|
413
|
+
just the intent named in the last error. Use `client.getIntent(intentId)` for
|
|
414
|
+
known intents; for MPP, `completed` means an authorization was produced, not
|
|
415
|
+
proof of settlement or delivery of the purchased resource. If earlier outcomes
|
|
416
|
+
are unclear, reconcile with the operator and seller before making another
|
|
417
|
+
payment attempt. Do not automatically retry an ambiguous payment.
|
|
418
|
+
|
|
419
|
+
## Using the wallet as a viem account
|
|
420
|
+
|
|
421
|
+
A Catena wallet is custodial: your credential authenticates requests and
|
|
422
|
+
co-signs intents, and the wallet's own key never leaves the custodian. So the
|
|
423
|
+
account `@catena/sdk/viem` returns signs exactly one thing — x402 EIP-3009
|
|
424
|
+
`TransferWithAuthorization` typed data for USDC on Base or Base Sepolia, routed
|
|
425
|
+
through a policy-checked payment intent. That is the payload x402 client
|
|
426
|
+
libraries build, which makes the account a drop-in signer for tooling that knows
|
|
427
|
+
nothing about Catena:
|
|
428
|
+
|
|
429
|
+
```ts
|
|
430
|
+
import { createCatenaClient } from "@catena/sdk"
|
|
431
|
+
import { createCatenaAccount } from "@catena/sdk/viem"
|
|
432
|
+
|
|
433
|
+
const client = createCatenaClient()
|
|
434
|
+
const account = await createCatenaAccount(client, {
|
|
435
|
+
accountId: walletAccountId,
|
|
436
|
+
// Must match the network the wallet account is configured for; defaults to
|
|
437
|
+
// "base". A testnet wallet needs "base-sepolia" here.
|
|
438
|
+
network: "base",
|
|
439
|
+
})
|
|
440
|
+
|
|
441
|
+
// Hand `account` to any x402 client that accepts a viem account or
|
|
442
|
+
// an { address, signTypedData } signer, e.g.:
|
|
443
|
+
// wrapFetchWithPayment(fetch, clientWithScheme(account))
|
|
444
|
+
```
|
|
445
|
+
|
|
446
|
+
The contract, in brief:
|
|
447
|
+
|
|
448
|
+
- `signTypedData` accepts only `TransferWithAuthorization` for USDC on Base
|
|
449
|
+
(8453) or Base Sepolia (84532). Anything else throws
|
|
450
|
+
`UnsupportedTypedDataError` client-side, with a machine-readable `reason` —
|
|
451
|
+
nothing is submitted.
|
|
452
|
+
- The returned signature is verified to recover to the wallet address for the
|
|
453
|
+
exact typed data you passed before it is returned; a mismatch throws
|
|
454
|
+
`SignatureVerificationFailedError` instead of surfacing a bad signature. That
|
|
455
|
+
check runs after the payment completed, so it belongs to the
|
|
456
|
+
money-may-have-moved group below, not to the client-side refusals above.
|
|
457
|
+
- `signMessage` and `signTransaction` throw `UnsupportedAccountCapabilityError`;
|
|
458
|
+
there is no raw-hash `sign`.
|
|
459
|
+
- A payment over a policy approval threshold throws `X402ApprovalPendingError`
|
|
460
|
+
(the same class `@catena/sdk/x402` throws). Approve it in the Catena console,
|
|
461
|
+
then let the tooling retry — a retry with a fresh nonce still consumes the
|
|
462
|
+
approval.
|
|
463
|
+
- A policy block or terminal failure throws `X402PaymentDeclinedError` with the
|
|
464
|
+
server's reasons; request-level failures (for example a reused nonce)
|
|
465
|
+
propagate as `ApiError` with a stable `code`.
|
|
466
|
+
- Once the payment may have moved, every failure carries the intent id:
|
|
467
|
+
`X402SubmitInterruptedError` (the submission was interrupted after the payment
|
|
468
|
+
may have been claimed), `X402PaymentSignatureUnusableError` (it completed
|
|
469
|
+
carrying a signature this SDK cannot use), `X402PaymentInFlightError` (still
|
|
470
|
+
in flight — note that polling does not advance an approved x402 payment
|
|
471
|
+
resting there; it waits for the paid request to be re-run), and
|
|
472
|
+
`SignatureVerificationFailedError`. Reconcile with `getIntent(intentId)`
|
|
473
|
+
instead of paying again. Note the shape difference: the intent's
|
|
474
|
+
`data.paymentCredential.value` is the base64 x402 envelope used as the
|
|
475
|
+
`PAYMENT-SIGNATURE` header, whereas `signTypedData` returns the inner hex
|
|
476
|
+
signature decoded out of it — passing the envelope to a viem-based signer path
|
|
477
|
+
will fail on shape.
|
|
478
|
+
|
|
207
479
|
## License
|
|
208
480
|
|
|
209
481
|
[Apache-2.0](./LICENSE)
|