@agentkv/client 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.
@@ -0,0 +1,584 @@
1
+ import { Signer, UsageBlock } from '@agentx402-ai/core';
2
+ export { AgentKVError, Signer, SpendCapError, UsageBlock } from '@agentx402-ai/core';
3
+
4
+ /** Request passed to `topoffPayer` when the client needs an account-key top-off. */
5
+ interface TopoffPayerRequest {
6
+ /** `${endpoint}/account/deposit` — the ONLY URL a hook should ever pay. */
7
+ depositUrl: string;
8
+ /** The `ak_…` bearer that must authorize the deposit (`Authorization: Bearer <ak>`). */
9
+ accountKey: string;
10
+ /** Requested top-off in USD (= `prepay.topoff`, >= $1 — the server minimum). */
11
+ amountUsd: number;
12
+ /** Hard payment ceiling in atomic USDC units (1e6 = $1) the hook MUST enforce (e.g. awal `--max-amount`). */
13
+ maxAmountAtomic: number;
14
+ }
15
+ /**
16
+ * Request handed to `opInlinePayer`: the WHOLE encrypted account-key `set`/`get`
17
+ * op, ready to send as-is. The hook drives its OWN 402→pay→retry (e.g. via
18
+ * `awal x402 pay`) and returns the final response — the client never sees the
19
+ * intermediate 402 on this path.
20
+ */
21
+ interface OpInlineRequest {
22
+ /** `${endpoint}/kv/<digest>` — the ONLY URL a hook should ever request. */
23
+ url: string;
24
+ method: "POST" | "GET";
25
+ /** Ciphertext JSON body (POST only; omitted on GET). */
26
+ body?: string;
27
+ /** Full request headers: bearer `Authorization`, `Idempotency-Key`, `content-type` (POST). */
28
+ headers: Record<string, string>;
29
+ /** Hard payment ceiling in atomic USDC units (1e6 = $1) the hook MUST enforce (e.g. awal `--max-amount`). */
30
+ maxAmountAtomic: number;
31
+ }
32
+ /** Final response from `opInlinePayer`, after its own 402→pay→retry has settled. */
33
+ interface OpInlineResponse {
34
+ status: number;
35
+ body: string;
36
+ headers: Record<string, string>;
37
+ }
38
+ interface AgentKVCommon {
39
+ endpoint: string;
40
+ network?: string;
41
+ /** Per-paying-call USD cap; throws SpendCapError if exceeded. */
42
+ maxSpendUsd?: number;
43
+ /**
44
+ * Cumulative USD cap across this client instance. **Best-effort**: the running
45
+ * total is a plain in-memory counter, so concurrent paying calls can race
46
+ * (both pass `assertSpend` before either `recordSpend`s) and modestly overshoot
47
+ * the cap. It is a guardrail, not a hard ledger — serialize paying calls if you
48
+ * need a strict bound.
49
+ */
50
+ maxSessionSpendUsd?: number;
51
+ /**
52
+ * Bounded internal retries on TRANSIENT failures (a thrown fetch / lost
53
+ * response, or a 5xx). Default 2 (3 attempts total). Retries reuse the op's
54
+ * stable Idempotency-Key (and pinned EIP-3009 nonce on paid ops) so a
55
+ * processed-but-unacked request dedupes server-side instead of double-charging.
56
+ * Kept small so a re-sent paid authorization stays within validBefore. Set 0 to disable.
57
+ */
58
+ retries?: number;
59
+ /** Per-attempt request timeout in ms (aborts a hung-open connection so an op can't wedge forever). Default 30000. Pass 0 to disable. */
60
+ timeoutMs?: number;
61
+ /** Injectable `fetch` for proxies / instrumentation / testing. Defaults to the global `fetch`. */
62
+ fetch?: typeof fetch;
63
+ /** Opt-in Discounted Prepay. When set, the client keeps a credit balance topped up. */
64
+ prepay?: {
65
+ /** Top off when the tracked balance (USD) falls below this. */
66
+ watermark: number;
67
+ /** Top-off amount in USD (>= 1). */
68
+ topoff: number;
69
+ /** Opt-in background top-off instead of synchronous single-shot. Default false. */
70
+ async?: boolean;
71
+ };
72
+ /**
73
+ * ACCOUNT-KEY MODE ONLY: pays a prepaid-credit top-off to
74
+ * `${endpoint}/account/deposit` on this client's behalf (a managed account has
75
+ * no signing wallet). Called single-flight when the tracked balance falls below
76
+ * `prepay.watermark`, and as a fallback when an op hits an insufficient-credits
77
+ * 402. Resolve only once the deposit has SETTLED; a rejection surfaces as
78
+ * `account_topoff_failed` (fatal on the hard-402 path, swallowed on the
79
+ * proactive path). REQUIRED alongside `prepay` in account-key mode; rejected
80
+ * (`invalid_config`) in wallet mode or without `prepay` (it could never fire).
81
+ */
82
+ topoffPayer?: (req: TopoffPayerRequest) => Promise<void>;
83
+ /**
84
+ * ACCOUNT-KEY MODE ONLY: routes a WHOLE `set`/`get` op through an external inline
85
+ * x402 transport (e.g. `awal x402 pay`) instead of the stage-1 deposit top-off —
86
+ * opt-in, pay-per-op, no `prepay` required. Called on a hard 402 (insufficient
87
+ * credits): the client hands the hook the complete encrypted request
88
+ * (`{url, method, body, headers}`, already bearer-authenticated and
89
+ * Idempotency-Keyed) and the hook drives its OWN 402→pay→retry, returning the
90
+ * final `{status, body, headers}` for the client to parse/decrypt as usual.
91
+ *
92
+ * Mutually exclusive with `topoffPayer` PER OP: when BOTH are configured,
93
+ * `topoffPayer` (deposit top-off) always takes precedence and `opInlinePayer`
94
+ * is used only when `topoffPayer` is absent — see client/README.md. Rejected
95
+ * (`invalid_config`) in wallet mode (a signing wallet pays its own challenges).
96
+ */
97
+ opInlinePayer?: (req: OpInlineRequest) => Promise<OpInlineResponse>;
98
+ }
99
+ /**
100
+ * Construct with one of four auth shapes:
101
+ * - `{ privateKey }` — a raw wallet key (HKDF-derives the AES key from the KEY BYTES;
102
+ * wallet signs).
103
+ * - `{ signer, encryptionKey }` — a wallet signer + an explicit AES key.
104
+ * - `{ signer }` — a wallet signer; the AES key is SIGN-TO-DERIVED (HKDF over the wallet's
105
+ * signature of a fixed message).
106
+ * - `{ accountKey, encryptionKey }` — account-key mode: a managed account with
107
+ * NO signing wallet. Auth is an opaque `ak_…` bearer token (server hashes it to
108
+ * name storage + debits prepaid credits — no x402/EIP-712). There is no wallet
109
+ * to derive an AES key from, so an explicit `encryptionKey` is REQUIRED.
110
+ *
111
+ * ⚠️ Encryption-key stability — the shapes are NOT interchangeable for the same wallet:
112
+ * - `{ privateKey: k }` and `{ signer: accountFrom(k) }` (sign-to-derive) derive DIFFERENT
113
+ * encryption keys (raw key bytes vs a signature over them). Switching a wallet between
114
+ * these two shapes changes the value/key-name/blind-index keys, so previously written
115
+ * data becomes unreadable AND unlisted (its lookup digests no longer match) with no error
116
+ * — reads simply return null. To move between them, or to use a KMS/hardware/MPC signer,
117
+ * pin an explicit `encryptionKey` so the AES key never depends on the signer shape.
118
+ * - Sign-to-derive requires a DETERMINISTIC signer whose signTypedData returns the standard
119
+ * 65-byte ECDSA form. Non-deterministic (some MPC/threshold) or alternate-encoding
120
+ * signers (EIP-2098 compact, ERC-1271/6492 smart accounts) either rotate the key per call
121
+ * or are rejected — those MUST pass an explicit `encryptionKey`.
122
+ * - The sign-to-derive signature IS secret-grade material: whoever obtains it can derive all
123
+ * of this wallet's AgentKV keys. Treat it like a private key (do not log / expose it).
124
+ */
125
+ type AgentKVOptions = AgentKVCommon & ({
126
+ privateKey: `0x${string}`;
127
+ } | {
128
+ signer: Signer;
129
+ encryptionKey: Uint8Array | `0x${string}`;
130
+ } | {
131
+ signer: Signer;
132
+ } | {
133
+ accountKey: string;
134
+ encryptionKey: Uint8Array | `0x${string}`;
135
+ });
136
+ /** Per-write options. */
137
+ interface SetOptions {
138
+ /** Time-to-live in days (server default 90). */
139
+ ttlDays?: number;
140
+ /** If true, reads do not extend expiry (server default false). */
141
+ strictTtl?: boolean;
142
+ /**
143
+ * Stable key identifying this logical write, reused across retries so a
144
+ * retried set() (after a crash/timeout) is exactly-once: the server hits its
145
+ * idempotency record instead of charging again. Defaults to a fresh value
146
+ * (each call is a distinct write).
147
+ */
148
+ idempotencyKey?: string;
149
+ }
150
+ /** Per-read options. */
151
+ interface GetOptions {
152
+ /**
153
+ * Stable key making a retried get() exactly-once: the same key hits the
154
+ * server's read idempotency record (credit path) and pins the EIP-3009 nonce
155
+ * (paid path) instead of charging again. Defaults to a fresh value per call
156
+ * (each get() is a distinct, separately-charged read).
157
+ */
158
+ idempotencyKey?: string;
159
+ }
160
+ /** Result of a successful write. */
161
+ interface SetResult {
162
+ ok: true;
163
+ expires_at: string;
164
+ /** Machine-readable usage envelope for this write. Absent only if the server predates it. */
165
+ usage?: UsageBlock;
166
+ }
167
+ /** Result of a successful delete. */
168
+ interface DeleteResult {
169
+ ok: true;
170
+ }
171
+ /** Result of a successful credit deposit. */
172
+ interface DepositResult {
173
+ credits_added: number;
174
+ balance: number;
175
+ }
176
+ /** Standard AgentKV error response body. */
177
+ interface ErrorBody {
178
+ error: string;
179
+ code: string;
180
+ hint?: string;
181
+ }
182
+
183
+ /**
184
+ * Mint a fresh account key: `ak_` followed by the hex of AK_RANDOM_BYTES
185
+ * cryptographically-random bytes. This is the bearer secret — knowing it IS the
186
+ * capability to read/write the account. Persist it securely (e.g. a keystore);
187
+ * the worker stores only its hash, so a lost key is unrecoverable.
188
+ */
189
+ declare function generateAccountKey(): string;
190
+ /** True iff `s` is a string in the canonical `ak_<64 lowercase hex>` shape. */
191
+ declare function isAccountKeyFormat(s: unknown): s is string;
192
+
193
+ /**
194
+ * All keys derived from one per-wallet `ikm` with domain-separated HKDF labels — so the
195
+ * value key, the key-name key, and the blind-index MAC key are independent.
196
+ */
197
+ interface KeyMaterial {
198
+ value: Uint8Array;
199
+ keyName: Uint8Array;
200
+ mac: Uint8Array;
201
+ }
202
+ declare function deriveKeyMaterial(ikm: Uint8Array): KeyMaterial;
203
+ /**
204
+ * Blind-index digest for a key NAME: `scheme_tag ‖ HMAC-SHA256(macKey, NFC(name))`,
205
+ * url-safe-base64. Deterministic and per-wallet (macKey is per-wallet), so the server
206
+ * looks up by an opaque token it cannot invert or correlate across wallets. NFC is the
207
+ * frozen normalization (case-sensitive, whitespace-preserving). Used by set/get/delete/list
208
+ * to look a value up under an opaque per-wallet token the server cannot invert.
209
+ */
210
+ declare function hashKey(macKey: Uint8Array, name: string): string;
211
+ /**
212
+ * Encrypt plaintext with AES-256-GCM, emitting the versioned self-describing envelope
213
+ * `base64( magic ‖ ver ‖ suite ‖ kdf_id ‖ IV ‖ ct+tag )`. The header (and, when `aad` is
214
+ * supplied, the key's digest) is bound into the AAD.
215
+ */
216
+ declare function encrypt(key: Uint8Array, plaintext: string, aad?: string): Promise<string>;
217
+ /**
218
+ * Decrypt a value produced by encrypt(). Requires the versioned envelope (magic + known
219
+ * ver/suite/kdf) and verifies the AAD-bound header (and, when `aad` is supplied, the key's
220
+ * digest — so a value the server serves for the WRONG key fails the tag instead of decrypting).
221
+ */
222
+ declare function decrypt(key: Uint8Array, packed: string, aad?: string): Promise<string>;
223
+
224
+ declare const VERSION = "0.1.0";
225
+
226
+ /** USD value of one prepaid credit ($1 mints 10,000 credits). */
227
+ declare const CREDIT_VALUE_USD = 0.0001;
228
+ declare const ACCOUNT_READ_USD = 0.0003;
229
+ declare const ACCOUNT_WRITE_USD = 0.0005;
230
+ /**
231
+ * Built-in ceiling on a SERVER-QUOTED per-op price when no `maxSpendUsd` is configured.
232
+ * The advertised op price is ~$0.005; without this, a compromised or spoofed worker could
233
+ * answer a routine read with a 402 challenge for the wallet's entire balance and the client
234
+ * would sign the EIP-3009 authorization (the default config has no per-op cap). Callers who
235
+ * legitimately need a pricier op opt in explicitly via `maxSpendUsd`.
236
+ */
237
+ declare const DEFAULT_MAX_OP_USD = 0.05;
238
+ declare class AgentKV {
239
+ /**
240
+ * The signing wallet. `undefined` in account-key mode (a managed account has no
241
+ * wallet that can sign) — the `ak_…` bearer token is the identity instead.
242
+ */
243
+ readonly signer?: Signer;
244
+ /**
245
+ * The wallet address, the per-wallet namespace. In account-key mode there is no
246
+ * wallet, so this is the zero address (a documented sentinel: the account key —
247
+ * not an address — is the identity; the server names storage by the key's hash).
248
+ */
249
+ /** The wallet address (its namespace) in wallet/signer mode; `undefined` in account-key mode. */
250
+ readonly address: `0x${string}` | undefined;
251
+ /** The raw `ak_…` bearer token in account-key mode; `undefined` otherwise. */
252
+ readonly accountKey?: string;
253
+ readonly endpoint: string;
254
+ readonly network: string;
255
+ readonly maxSpendUsd?: number;
256
+ readonly maxSessionSpendUsd?: number;
257
+ /** Bounded internal retries on transient failures (network error / 5xx). */
258
+ readonly maxRetries: number;
259
+ private readonly timeoutMs?;
260
+ private readonly fetchImpl?;
261
+ private _ikm?;
262
+ private _km?;
263
+ private _kmPromise?;
264
+ private sessionSpentUsd;
265
+ private readonly prepay?;
266
+ /** Top-off amount in atomic USDC units (1e6), computed once in the constructor. */
267
+ private readonly topoffAtomic;
268
+ /** Account-key top-off hook (account-key mode only; validated in the constructor). */
269
+ private readonly topoffPayer?;
270
+ /**
271
+ * Account-key inline x402 transport hook (account-key mode only; validated in
272
+ * the constructor). Mutually exclusive with `topoffPayer` PER OP — `topoffPayer`
273
+ * always takes precedence when both are configured (see the call sites in
274
+ * set()/get(), which gate on `!this.topoffPayer`).
275
+ */
276
+ private readonly opInlinePayer?;
277
+ /** Last-known credit balance as an EXACT integer credit count (never USD floats). */
278
+ private knownCredits?;
279
+ /** Synchronous single-flight guard: at most one in-flight top-off at a time. */
280
+ private topoffInFlight;
281
+ /**
282
+ * The in-flight SYNCHRONOUS top-off deposit (account mode), published so a concurrent op
283
+ * that hits a hard 402 but can't claim the single-flight can await it and retry.
284
+ */
285
+ private topoffPromise;
286
+ /** Cached last `PAYMENT-REQUIRED` header — the template for a proactive single-shot. */
287
+ private challengeTemplate?;
288
+ constructor(opts: AgentKVOptions);
289
+ /**
290
+ * Resolve (and memoize) the AES key. Async only for the sign-to-derive shape
291
+ * (`{signer}` with no encryptionKey): the key is `HKDF` over a fixed-message
292
+ * signature, which is stable ONLY for deterministic ECDSA signers (local keys /
293
+ * RFC-6979). Non-deterministic signers (some MPC/threshold backends) would
294
+ * derive a different key each run and fail to decrypt — those must pass an
295
+ * explicit `encryptionKey`.
296
+ */
297
+ private getKeyMaterial;
298
+ /**
299
+ * Decrypt a value envelope with the current value key, binding the key's blind-index
300
+ * digest into the AAD so a value the server serves for the wrong key fails the auth tag.
301
+ */
302
+ private decryptValue;
303
+ private assertSpend;
304
+ private recordSpend;
305
+ /**
306
+ * Reject a SERVER-QUOTED per-op price above a sane ceiling in the DEFAULT (cap-less) config.
307
+ * When `maxSpendUsd` is set, `assertSpend` already bounds the op price; when it is NOT set,
308
+ * a compromised or spoofed worker could otherwise answer a routine $0.005 read with a 402
309
+ * challenge for the wallet's whole balance and the client would sign the EIP-3009
310
+ * authorization. Callers who genuinely need a pricier op opt in via `maxSpendUsd`.
311
+ */
312
+ private assertOpPriceCeiling;
313
+ /**
314
+ * The effective per-op ceiling (USD) for an inline-payer op: the caller's `maxSpendUsd`
315
+ * when set (they opted into that bound), else the built-in default ceiling. Handed to the
316
+ * hook as its hard `maxAmountAtomic`, and pre-reserved against the session cap before paying.
317
+ */
318
+ private inlineOpCeilingUsd;
319
+ /**
320
+ * The on-chain settlement txHash from a response's PAYMENT-RESPONSE header, or ""
321
+ * when the server served the op from existing credits (so the attached top-off
322
+ * authorization was NEVER settled — it just expires) or the header is absent. The
323
+ * worker emits PAYMENT-RESPONSE = base64(JSON `{ success, payer, amount, txHash }`)
324
+ * on any paid 200, with `txHash: ""` on the credit hot path. A proactive single-shot
325
+ * top-off must only count toward session spend when this is non-empty — otherwise no
326
+ * USDC moved and recording it inflates sessionSpentUsd, prematurely tripping the cap.
327
+ *
328
+ * Accepted trade-off: in a doubly-rare crash window (the server's settle mined on-chain
329
+ * but its ledger row was lost, AND the response was lost so the client retries), the
330
+ * worker's already-used-authorization recovery returns success with txHash "" even
331
+ * though USDC did move. The client then under-counts that top-off by one, making the
332
+ * local session cap marginally lenient — no funds are lost (the amount is still minted
333
+ * as credits). This is unavoidable (the worker cannot distinguish that case from a
334
+ * plain credit-served op) and far cheaper than the systematic L3 over-count it replaces.
335
+ */
336
+ private settledTxHash;
337
+ /**
338
+ * Issue a request with bounded internal retry on TRANSIENT failures only: a
339
+ * thrown fetch (network error / lost response) or a 5xx. `build()` is re-invoked
340
+ * per attempt so the credit path can re-sign identity with a FRESH nonce each
341
+ * time, while the op's stable Idempotency-Key (and pinned EIP-3009 nonce on paid
342
+ * ops) makes a retry of an already-processed request dedupe server-side — so a
343
+ * lost response that the server already charged is recovered without a second
344
+ * charge. NOT retried: any 2xx/4xx (incl. the 402 credit->pay handoff, 401, 404)
345
+ * — those are returned as-is for the caller's normal handling. The bound is kept
346
+ * small so a re-sent paid authorization cannot outlive its validBefore.
347
+ *
348
+ * The retry MECHANICS (transient-status detection, backoff,
349
+ * Retry-After honoring) were extracted to `@agentx402-ai/core`'s `fetchWithRetry`
350
+ * as a pure function parameterized by `maxRetries` — this method is now a
351
+ * thin delegating wrapper so every existing `this.fetchWithRetry(...)` call
352
+ * site above is unchanged.
353
+ */
354
+ private fetchWithRetry;
355
+ /**
356
+ * Update prepay tracking from any server response. Reads the exact integer
357
+ * credit balance (`X-AgentKV-Credits-Remaining`) and caches the most recent
358
+ * `PAYMENT-REQUIRED` challenge as the proactive single-shot template (so a
359
+ * top-off needs no preflight request). Safe to call when prepay is disabled.
360
+ */
361
+ private trackBalance;
362
+ /** Watermark (USD) expressed in EXACT integer credits (1 credit = CREDIT_VALUE_USD = $0.0001,
363
+ * so $1 = 10,000 credits — matching the worker's mint rate). */
364
+ private watermarkCredits;
365
+ /**
366
+ * Synchronous single-flight claim for a proactive top-off. CRITICAL: there is
367
+ * NO `await` between the watermark check and setting `topoffInFlight = true`,
368
+ * and this must be called at the very top of set/get before any await.
369
+ * Otherwise two concurrent ops both pass the check and each fire a separate
370
+ * top-off with distinct fresh nonces the server can't dedupe (double charge).
371
+ * Returns true for exactly one concurrent op below the watermark; the caller
372
+ * MUST clear `topoffInFlight` in a `finally`. Losers take the identity path.
373
+ */
374
+ private tryClaimTopoff;
375
+ /**
376
+ * Single-flight claim at a hard 402 (insufficient credits). Unlike
377
+ * `tryClaimTopoff` this ignores the watermark — a 402 already proves credits
378
+ * are short — but still claims the flag synchronously so concurrent 402s don't
379
+ * each fire a top-off. Returns true only if the flag was free; the caller MUST
380
+ * clear it in a `finally`. In `async` mode we leave the 402 to be paid at the
381
+ * op price (the background deposit replenishes credits separately).
382
+ */
383
+ private tryClaimTopoffOnFault;
384
+ /**
385
+ * Run a synchronous account-mode top-off while PUBLISHING its in-flight promise, so a
386
+ * concurrent op that hits a hard 402 (and can't claim the single-flight) can await THIS
387
+ * deposit and retry rather than surfacing the 402. The caller holds the single-flight claim.
388
+ */
389
+ private runSharedTopoff;
390
+ /**
391
+ * Detached async top-off (opt-in via `prepay.async`). When below the watermark
392
+ * and no top-off is in flight, fire a deposit WITHOUT awaiting it in the op
393
+ * path. Documented races: the balance read is point-in-time so the trigger can
394
+ * be stale, and a deposit settling between an op's read and this check can make
395
+ * the top-off redundant; the single-flight flag bounds these to at most one
396
+ * outstanding deposit, but cannot serialize against an op already past its own
397
+ * check. Use the (default) synchronous single-shot for exactly-bounded spend.
398
+ *
399
+ * Account-key mode: there is no signing wallet, so the detached
400
+ * deposit is dispatched through `runAccountTopoff()` (the `topoffPayer` hook)
401
+ * instead of `runDeposit` (which throws `no_signer` in account mode). Wallet
402
+ * mode is completely unchanged below.
403
+ */
404
+ private maybeAsyncTopoff;
405
+ /**
406
+ * Whether a top-off of `prepay.topoff` fits under the cumulative SESSION cap.
407
+ * Top-offs are deliberately NOT gated on the per-op `maxSpendUsd` (which bounds
408
+ * individual pay-per-op charges); when over the session cap we downgrade to
409
+ * pay-per-op rather than throwing.
410
+ */
411
+ private topoffFitsSessionCap;
412
+ /**
413
+ * Single source of truth for BOTH the EIP-712-signed pathname (`path`) and the
414
+ * URL to fetch (`url`), so they can never diverge — a divergence silently
415
+ * breaks identity auth: the worker verifies over the RECEIVED path and
416
+ * recovers a phantom address if it differs from what the client signed.
417
+ * `base` is the un-prefixed pathname; the fetched/signed path is `/v1` + `base`
418
+ * (list-keys overrides it to `/v1/kv`). `query` is appended to `url`
419
+ * ONLY — EIP-712 binds the pathname, never the query string.
420
+ */
421
+ private route;
422
+ private kvRoute;
423
+ /**
424
+ * Per-op auth headers. In account-key mode this is the `Authorization: Bearer
425
+ * ak_…` header (server hashes it to name storage + debit credits); in wallet
426
+ * mode it is the EIP-712 identity signature. Used by every op so the same call
427
+ * site picks the right scheme. Async to share the signature of `identityHeaders`.
428
+ */
429
+ private authHeaders;
430
+ /**
431
+ * The signing wallet, asserted present. Every x402 path (set/get top-off + 402
432
+ * fallback, deposit) is gated behind `!this.accountKey` and so always has a
433
+ * signer; this narrows the optional type at those call sites (and fails loudly
434
+ * if that invariant is ever broken).
435
+ */
436
+ private requireSigner;
437
+ /** EIP-712 identity headers with the deployment host bound into the signature (prevents cross-deployment signature replay). */
438
+ private identityHeaders;
439
+ /**
440
+ * Shared money/transport orchestrator behind set() and getInternal() — the single copy of
441
+ * the flow both share: account-key (bearer) mode with proactive + hard-402 top-off and the
442
+ * inline-payer path; wallet mode with the proactive single-shot, credit path, and 402
443
+ * pay-and-retry — including the single-flight top-off accounting and settled-txHash spend
444
+ * gating. Per-op differences (method/body, credit cost, 404 handling, success/inline
445
+ * parsing) come from `spec`. The CALLER must claim the single-flight top-off SYNCHRONOUSLY
446
+ * (before any await) and pass it in `flight`; it may be re-claimed here on a cold-start hard
447
+ * 402, and the caller's `finally` releases it.
448
+ */
449
+ private performOp;
450
+ /**
451
+ * Write an encrypted value. The value is JSON-stringified and AES-256-GCM
452
+ * encrypted client-side; the server only ever stores ciphertext. `null` and
453
+ * `undefined` are rejected (`invalid_value`) so a `null` from get() unambiguously
454
+ * means "missing key" — use delete() to remove a key. Tries the credit path first
455
+ * (EIP-712 identity signature); if credits are insufficient the server returns a 402
456
+ * x402 challenge and the client pays. A stable Idempotency-Key is reused across that
457
+ * retry so the write is exactly-once.
458
+ */
459
+ set(key: string, value: unknown, opts?: SetOptions): Promise<SetResult>;
460
+ /**
461
+ * Read and decrypt a value. Tries the credit path first (EIP-712 identity);
462
+ * if credits are insufficient the server returns a 402 x402 challenge and the
463
+ * client pays, then retries. Returns null if the key is missing or expired (404);
464
+ * stored values are never null (set rejects it), so null unambiguously means absent.
465
+ */
466
+ get<T = unknown>(key: string, opts?: GetOptions): Promise<T | null>;
467
+ /**
468
+ * Like `get`, but ALSO surfaces the machine-readable usage envelope the server
469
+ * attaches to a paid read's success body — a separate, additive
470
+ * accessor so `get()` keeps its narrower `T | null` signature (no existing
471
+ * caller breaks). `usage` is absent when the key was missing/expired (404 —
472
+ * the read op itself is never charged on a miss, see the DO's 404-before-charge
473
+ * precheck — though a proactive top-off, if one was triggered for this call,
474
+ * is a separate deposit charge and can still happen alongside a miss) or when
475
+ * talking to a server that predates the usage envelope.
476
+ */
477
+ getWithUsage<T = unknown>(key: string, opts?: GetOptions): Promise<{
478
+ value: T | null;
479
+ usage?: UsageBlock;
480
+ }>;
481
+ /**
482
+ * Shared implementation behind `get`/`getWithUsage`. Tries the credit path
483
+ * first (EIP-712 identity); if credits are insufficient the server returns a
484
+ * 402 x402 challenge and the client pays, then retries. `value` is null if
485
+ * the key is missing or expired (404); stored values are never null (set
486
+ * rejects it), so null unambiguously means absent.
487
+ */
488
+ private getInternal;
489
+ /**
490
+ * Delete a key. Free operation. Authenticated with the account-key bearer in
491
+ * account mode, else an EIP-712 identity signature (fresh nonce + timestamp).
492
+ * The digest is computed from the local key material either way.
493
+ */
494
+ delete(key: string): Promise<DeleteResult>;
495
+ /**
496
+ * List the wallet's keys. The server returns opaque per-wallet digests plus each key's
497
+ * ENCRYPTED name; this decrypts the names locally and returns them — the server never
498
+ * sees a plaintext key name. Free (EIP-712 identity signed). Paginated: pass the returned
499
+ * `cursor` to fetch the next page; `cursor` is null once exhausted.
500
+ */
501
+ listKeys(opts?: {
502
+ cursor?: string | null;
503
+ limit?: number;
504
+ }): Promise<{
505
+ keys: string[];
506
+ cursor: string | null;
507
+ }>;
508
+ /**
509
+ * Read the pre-paid credit balance. Free. Account-key bearer in account mode,
510
+ * else an EIP-712 identity signature.
511
+ */
512
+ balance(): Promise<number>;
513
+ /**
514
+ * Buy credits with an x402 payment. `amountUsd` must be at least $1; any
515
+ * amount is accepted (no fixed tiers). This settles on-chain once via the
516
+ * facilitator; the returned credits are then spendable by set/get with no
517
+ * further payment.
518
+ */
519
+ deposit(amountUsd: number, opts?: {
520
+ idempotencyKey?: string;
521
+ expectedPayTo?: string;
522
+ }): Promise<DepositResult>;
523
+ /**
524
+ * Account-key top-off: delegate payment of `${endpoint}/account/deposit` to the
525
+ * configured `topoffPayer` (a managed account has no signing wallet to sign an
526
+ * x402 payment). The caller must hold the single-flight claim and have checked
527
+ * `topoffFitsSessionCap()`. On resolve (= the deposit SETTLED) the top-off is
528
+ * recorded against the session budget only — top-offs are credit purchases, not
529
+ * per-op charges, so the per-op cap is deliberately not consulted (mirrors the
530
+ * wallet-mode top-off budget rules). A rejection is wrapped as
531
+ * `account_topoff_failed`; the ak_ bearer is never included in the message.
532
+ */
533
+ /**
534
+ * `amountUsd` generalizes this beyond the fixed `prepay.topoff` amount
535
+ * so `deposit()` can reuse it for a caller-chosen amount. OMITTED (the no-arg
536
+ * call from the proactive/hard-402/async paths above), it defaults to
537
+ * `prepay.topoff` and its precomputed `topoffAtomic` ceiling — BYTE-FOR-BYTE the
538
+ * stage-1 behavior. An EXPLICIT `amountUsd` (from `deposit()`, which may be
539
+ * called with no `prepay` configured at all) is validated here the same way
540
+ * `runDeposit` validates its wallet-mode amount, since it never passed through
541
+ * the constructor's `prepay.topoff` guard.
542
+ */
543
+ private runAccountTopoff;
544
+ private runDeposit;
545
+ /**
546
+ * Fund an ACCOUNT-KEY namespace — "payer funds, bearer owns". A CALLER-supplied
547
+ * `signer` pays via x402 to add prepaid credits to THIS client's account (the
548
+ * one named by its `ak_…` bearer). The payer and the owner are deliberately
549
+ * DECOUPLED: the payer wallet signs the on-chain EIP-3009 authorization, while
550
+ * the bearer — not the payer's address — owns the credited namespace. This is the
551
+ * SDK counterpart of the server's `/account/deposit` route.
552
+ *
553
+ * Account-key mode ONLY. In WALLET mode the paying wallet IS the namespace, so
554
+ * use `deposit()` instead — calling this throws `wrong_mode` before any network.
555
+ *
556
+ * `signer` is the PAYER: a viem account (must expose `address` + `signTypedData`)
557
+ * or a raw `0x` private key (built into a viem account internally, mirroring the
558
+ * constructor). `amountUsd` must be a whole number of dollars >= $1. UNLIKE
559
+ * `deposit()` (which IS gated by both spend caps and counts toward session spend),
560
+ * this explicit funding call is NOT gated by `maxSpendUsd`/`maxSessionSpendUsd` and
561
+ * does not count toward session spend — the payer is an EXTERNAL wallet, not this
562
+ * client's tracked budget. The local encryption key is never touched (funding does not encrypt).
563
+ */
564
+ fundAccount(signer: Signer | `0x${string}`, amountUsd: number, opts?: {
565
+ idempotencyKey?: string;
566
+ expectedPayTo?: string;
567
+ }): Promise<DepositResult>;
568
+ /** Shared by `asError` (a real `Response`) and the `opInlinePayer` path (a plain `{status,body}`). */
569
+ private errorFromBody;
570
+ private asError;
571
+ /**
572
+ * The settled amount (USD) from an `opInlinePayer` response's PAYMENT-RESPONSE
573
+ * header — the inline-path mirror of `settledTxHash()` above, but reading a
574
+ * plain `Record<string,string>` (the hook's own headers, not a `Response`) and
575
+ * returning the `amount` field instead of the `txHash`. Case-insensitive header
576
+ * lookup: an external transport (e.g. awal) is not guaranteed to preserve the
577
+ * worker's exact `PAYMENT-RESPONSE` casing. Returns `undefined` when the header
578
+ * is absent/unparsable OR the op settled nothing (served from existing credits,
579
+ * `txHash: ""`) — callers fall back to the credit-equivalent op price.
580
+ */
581
+ private inlineSettledAmountUsd;
582
+ }
583
+
584
+ export { ACCOUNT_READ_USD, ACCOUNT_WRITE_USD, AgentKV, type AgentKVOptions, CREDIT_VALUE_USD, DEFAULT_MAX_OP_USD, type DeleteResult, type DepositResult, type ErrorBody, type GetOptions, type KeyMaterial, type OpInlineRequest, type OpInlineResponse, type SetOptions, type SetResult, type TopoffPayerRequest, VERSION, decrypt, deriveKeyMaterial, encrypt, generateAccountKey, hashKey, isAccountKeyFormat };