@toon-protocol/sdk 2.0.1 → 2.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,911 @@
1
+ import { NostrEvent, UnsignedEvent } from 'nostr-tools/pure';
2
+ import { IlpPreparePacket, SwapPair, ToonError } from '@toon-protocol/core';
3
+
4
+ /**
5
+ * NIP-59 Gift Wrap Integration for ILP Packets (Story 12.2)
6
+ *
7
+ * Provides privacy-preserving encoding/decoding of swap packets using the
8
+ * NIP-59 three-layer gift wrap construction (rumor -> seal -> gift wrap).
9
+ * Intermediary peers routing ILP packets see only opaque TOON-encoded binary
10
+ * in the data field -- they cannot determine the event kind, sender identity,
11
+ * or swap intent. Only the destination Swap can unwrap and process.
12
+ *
13
+ * Also provides NIP-44 encryption/decryption for FULFILL claim return path
14
+ * (D12-008), using ephemeral keys so intermediaries on the return path see
15
+ * only opaque ciphertext.
16
+ *
17
+ * @module
18
+ */
19
+
20
+ /** Parameters for {@link wrapSwapPacket}. */
21
+ interface WrapSwapPacketParams {
22
+ /** Unsigned inner event containing swap metadata. */
23
+ rumor: UnsignedEvent;
24
+ /** Sender's secp256k1 secret key. */
25
+ senderSecretKey: Uint8Array;
26
+ /** Swap's compressed hex pubkey (64 chars). */
27
+ recipientPubkey: string;
28
+ }
29
+ /** Result of {@link wrapSwapPacket}. */
30
+ interface WrapSwapPacketResult {
31
+ /** Fully-formed kind:1059 gift wrap event signed by a fresh ephemeral key. */
32
+ giftWrap: NostrEvent;
33
+ /** The ephemeral pubkey used for the outer gift wrap layer. */
34
+ ephemeralPubkey: string;
35
+ }
36
+ /** Parameters for {@link unwrapSwapPacket}. */
37
+ interface UnwrapSwapPacketParams {
38
+ /** A kind:1059 gift wrap event. */
39
+ giftWrap: NostrEvent;
40
+ /** Recipient's (Swap's) secret key. */
41
+ recipientSecretKey: Uint8Array;
42
+ }
43
+ /** Result of {@link unwrapSwapPacket}. */
44
+ interface UnwrapSwapPacketResult {
45
+ /** The decrypted inner rumor (unsigned). */
46
+ rumor: UnsignedEvent;
47
+ /** The sender's real pubkey (extracted from the seal layer). */
48
+ senderPubkey: string;
49
+ }
50
+ /** Parameters for {@link wrapSwapPacketToToon}. */
51
+ interface WrapSwapPacketToToonParams {
52
+ /** Unsigned inner event containing swap metadata. */
53
+ rumor: UnsignedEvent;
54
+ /** Sender's secp256k1 secret key. */
55
+ senderSecretKey: Uint8Array;
56
+ /** Swap's compressed hex pubkey (64 chars). */
57
+ recipientPubkey: string;
58
+ /** ILP destination address. */
59
+ destination: string;
60
+ /** Payment amount in ILP units (bigint). */
61
+ amount: bigint;
62
+ /**
63
+ * Per-packet expiry. Propagated onto the produced PREPARE as an ISO 8601
64
+ * `expiresAt` string (rolling-swap R7 leg ordering). When omitted, the
65
+ * transport applies its default (timeout-derived, ~30s).
66
+ */
67
+ expiresAt?: Date;
68
+ }
69
+ /** Result of {@link wrapSwapPacketToToon}. */
70
+ interface WrapSwapPacketToToonResult {
71
+ /** Ready-to-send ILP PREPARE packet with TOON-encoded gift wrap as data. */
72
+ ilpPrepare: IlpPreparePacket;
73
+ /** The ephemeral pubkey used for the outer gift wrap layer. */
74
+ ephemeralPubkey: string;
75
+ }
76
+ /** Parameters for {@link unwrapSwapPacketFromToon}. */
77
+ interface UnwrapSwapPacketFromToonParams {
78
+ /** The data field from an incoming ILP PREPARE (TOON-encoded gift wrap). */
79
+ toonData: Uint8Array;
80
+ /** Recipient's (Swap's) secret key. */
81
+ recipientSecretKey: Uint8Array;
82
+ }
83
+ /** Parameters for {@link encryptFulfillClaim}. */
84
+ interface EncryptFulfillClaimParams {
85
+ /** The signed claim bytes to encrypt. */
86
+ claimData: Uint8Array;
87
+ /** The original sender's pubkey (recovered from unwrap). */
88
+ senderPubkey: string;
89
+ }
90
+ /** Result of {@link encryptFulfillClaim}. */
91
+ interface EncryptFulfillClaimResult {
92
+ /** NIP-44 encrypted claim bytes. */
93
+ ciphertext: Uint8Array;
94
+ /** The Swap's ephemeral pubkey (included in FULFILL so sender can decrypt). */
95
+ ephemeralPubkey: string;
96
+ }
97
+ /** Parameters for {@link decryptFulfillClaim}. */
98
+ interface DecryptFulfillClaimParams {
99
+ /** The encrypted claim bytes from the FULFILL data field. */
100
+ ciphertext: Uint8Array;
101
+ /** The Swap's ephemeral pubkey from the FULFILL data field. */
102
+ ephemeralPubkey: string;
103
+ /** The sender's (recipient of FULFILL) secret key. */
104
+ recipientSecretKey: Uint8Array;
105
+ }
106
+ /**
107
+ * NIP-59 three-layer gift wrap a swap packet.
108
+ *
109
+ * Constructs a kind:1059 gift wrap event using a fresh ephemeral keypair.
110
+ * The rumor is sealed with the sender's real key, then wrapped with the
111
+ * ephemeral key. Intermediaries see only the ephemeral pubkey.
112
+ *
113
+ * Each invocation generates a fresh ephemeral keypair for forward secrecy
114
+ * and message unlinkability (risk R-006).
115
+ *
116
+ * @throws {GiftWrapError} If the wrap operation fails (invalid keys, crypto failure).
117
+ */
118
+ declare function wrapSwapPacket(params: WrapSwapPacketParams): WrapSwapPacketResult;
119
+ /**
120
+ * Unwrap a NIP-59 gift-wrapped swap packet, recovering the inner rumor
121
+ * and the sender's real pubkey.
122
+ *
123
+ * Performs two-layer decryption: gift wrap -> seal -> rumor.
124
+ * The sender pubkey is extracted from the seal's pubkey field.
125
+ *
126
+ * @throws {GiftWrapError} If kind is not 1059, decryption fails, or structure is malformed.
127
+ */
128
+ declare function unwrapSwapPacket(params: UnwrapSwapPacketParams): UnwrapSwapPacketResult;
129
+ /**
130
+ * Convenience: gift wrap a swap packet, encode to TOON binary, and build
131
+ * an ILP PREPARE packet.
132
+ *
133
+ * This is the path that `streamSwap()` (Story 12.5) will use in its
134
+ * per-packet loop.
135
+ */
136
+ declare function wrapSwapPacketToToon(params: WrapSwapPacketToToonParams): WrapSwapPacketToToonResult;
137
+ /**
138
+ * Convenience: decode TOON binary from an incoming ILP PREPARE data field
139
+ * and unwrap the gift-wrapped swap packet.
140
+ *
141
+ * This is the path that the Swap handler (Story 12.3) will use to process
142
+ * incoming swap packets.
143
+ */
144
+ declare function unwrapSwapPacketFromToon(params: UnwrapSwapPacketFromToonParams): UnwrapSwapPacketResult;
145
+ /**
146
+ * Encrypt a FULFILL claim for the return path (D12-008).
147
+ *
148
+ * Generates a fresh ephemeral keypair and NIP-44 encrypts the claim data.
149
+ * The ephemeral pubkey is included alongside the ciphertext so the sender
150
+ * can decrypt. The ephemeral privkey is discarded after encryption.
151
+ *
152
+ * @throws {GiftWrapError} If claimData is empty or encryption fails.
153
+ */
154
+ declare function encryptFulfillClaim(params: EncryptFulfillClaimParams): EncryptFulfillClaimResult;
155
+ /**
156
+ * Decrypt a FULFILL claim from the return path.
157
+ *
158
+ * Uses the sender's secret key and the Swap's ephemeral pubkey (from the
159
+ * FULFILL data field) to NIP-44 decrypt the claim bytes.
160
+ *
161
+ * @throws {GiftWrapError} If decryption fails (wrong key, malformed ciphertext).
162
+ */
163
+ declare function decryptFulfillClaim(params: DecryptFulfillClaimParams): Uint8Array;
164
+
165
+ /**
166
+ * Adaptive δ/W controller for rolling swaps (issue #83, rolling-swap spec §6).
167
+ *
168
+ * Two knobs, managed separately:
169
+ * - `δ` (delta) — packet size in source micro-units — bounds *per-packet
170
+ * pick-off risk*.
171
+ * - `W` (window) — max unfulfilled packets in flight — bounds *timing /
172
+ * liveness risk* and the worst-case unrecovered exposure `δ·W`.
173
+ *
174
+ * Normative behavior implemented here (spec §6):
175
+ * - **The cap.** `delta_cap = ε / (v·τ)`, recomputed per packet from measured
176
+ * state, and `δ ≤ delta_cap` always. δ and `delta_cap` are fractions of the
177
+ * session's remaining notional; `v·τ` is the expected fractional rate drift
178
+ * while one packet is in flight; ε is the per-packet slippage budget as a
179
+ * fraction of the maker's advertised half-spread (default `ε = 0.5 ×
180
+ * halfSpread`). ε is spread-denominated, never an absolute rate.
181
+ * - **Inputs are measured, not trusted.** `v` is an EWMA of
182
+ * `abs(R_i − R_{i−1})/R_{i−1}` per second read off the quote tape
183
+ * (`PacketProgress.rate` / `rateTimestamp`, issue #82); `τ` is an EWMA of
184
+ * observed round-trip times. Both update on every observed packet.
185
+ * - **Asymmetric adjustment, one knob per step.** On a shrink signal (a
186
+ * `stale_rate` reject, any other reject/verification failure, or realized
187
+ * per-packet slip `> ε`): multiplicative — `δ ← max(δ_min, δ/2)`; if the
188
+ * signal was a timeout/expiry, `W ← max(1, ⌈W/2⌉)` instead. On a clean
189
+ * streak of `K = 16` consecutive fulfills: additive — `δ ← min(caps, δ +
190
+ * δ_0)` or `W ← min(W_max, W + 1)`, alternating, never both in one step.
191
+ * - **Cold start ramps.** With no persisted state for the tuple:
192
+ * `δ_0 = min(delta_cap, notional/256, maker maxAmount)`, `W_0 = 1`. Until
193
+ * the first shrink signal ever observed for the tuple, the δ widen step is
194
+ * multiplicative (`δ ← min(caps, 2δ)` per clean streak) — slow-start —
195
+ * dropping to additive permanently after the first loss event.
196
+ * - **State is per-(chain, maker, pair) and persisted** via a pluggable
197
+ * {@link SwapControllerStateStore} (the SDK is isomorphic; the JSON-file
198
+ * implementation is Node-only and lazily imports `node:fs`).
199
+ *
200
+ * INVARIANT (spec §5): the controller is an *efficiency* mechanism only. It
201
+ * never sees, computes, or relaxes the `minExchangeRate` floor — the floor
202
+ * check in `stream-swap.ts` runs before the controller observes anything and
203
+ * consults nothing but the floor itself. A calm (or adversarially painted)
204
+ * tape can widen δ up to the caps, but can never worsen the sender's declared
205
+ * worst case. NOTE: the adversarial-quote-tape question (toon-meta#146 — a
206
+ * maker quoting calm to coax δ wide, then gapping one large packet) is open;
207
+ * its resolution may tighten the cap logic here. The floor plus the absolute
208
+ * `maxPacketAmount` cap are the uncompromising backstops in the meantime.
209
+ *
210
+ * @module
211
+ */
212
+
213
+ /**
214
+ * Error thrown for invalid adaptive-controller configuration or a
215
+ * persistence-layer failure surfaced through {@link SwapControllerStateStore}.
216
+ */
217
+ declare class SwapControllerError extends ToonError {
218
+ constructor(message: string, cause?: Error);
219
+ }
220
+ /**
221
+ * Persisted per-(chain, maker, pair) controller state (spec §6 value shape
222
+ * `{delta, W, vEwma, tauEwma, cleanStreak, everShrunk, updatedAt}` plus the
223
+ * additive `lastWidened` alternation marker).
224
+ *
225
+ * JSON-serializable by construction: `delta` is a decimal string because
226
+ * `bigint` does not survive `JSON.stringify`.
227
+ */
228
+ interface SwapControllerState {
229
+ /** Schema version for forward migration. Currently `1`. */
230
+ v: 1;
231
+ /**
232
+ * Current ramp value of δ in source micro-units (decimal string).
233
+ * `'0'` means "not yet initialized" — the first `nextDelta()` call seeds it
234
+ * with the cold-start `δ_0`.
235
+ */
236
+ delta: string;
237
+ /** In-flight window W: max unfulfilled packets outstanding. Always ≥ 1. */
238
+ W: number;
239
+ /** EWMA of `abs(ΔR)/R` per second, measured from the quote tape. */
240
+ vEwma: number;
241
+ /** EWMA of observed packet round-trip time, in seconds. */
242
+ tauEwma: number;
243
+ /** Consecutive clean fulfills since the last knob adjustment. */
244
+ cleanStreak: number;
245
+ /**
246
+ * True once ANY shrink signal has ever been observed for this tuple.
247
+ * Ends the multiplicative slow-start widen ramp permanently (spec §6).
248
+ */
249
+ everShrunk: boolean;
250
+ /** Which knob the most recent widen step adjusted (alternation marker). */
251
+ lastWidened: 'delta' | 'window';
252
+ /** Unix ms of the last state mutation. */
253
+ updatedAt: number;
254
+ }
255
+ /** Runtime shape check for a (possibly foreign) persisted state blob. */
256
+ declare function isSwapControllerState(value: unknown): value is SwapControllerState;
257
+ /**
258
+ * Canonical persistence key for a controller tuple (spec §6):
259
+ * `${chain}:${makerPubkey}:${from}:${to}` with `from`/`to` as
260
+ * `assetCode@chain` so cross-chain pairs sharing an asset code stay distinct.
261
+ *
262
+ * The leading `chain` segment is the SOURCE chain — per-tuple state is how
263
+ * the same code runs fast on Base and cautious on Mina.
264
+ */
265
+ declare function swapControllerStateKey(params: {
266
+ makerPubkey: string;
267
+ pair: SwapPair;
268
+ }): string;
269
+ /**
270
+ * Pluggable persistence seam for controller state, keyed by
271
+ * {@link swapControllerStateKey}. The SDK is isomorphic, so the interface is
272
+ * environment-neutral (same pattern as `WorkflowEventStore`); pick
273
+ * {@link JsonFileSwapControllerStateStore} on Node or supply your own
274
+ * (e.g. IndexedDB / daemon-side store in toon-client).
275
+ */
276
+ interface SwapControllerStateStore {
277
+ load(key: string): Promise<SwapControllerState | undefined>;
278
+ save(key: string, state: SwapControllerState): Promise<void>;
279
+ }
280
+ /** Volatile in-memory store — the default when no store is configured. */
281
+ declare class InMemorySwapControllerStateStore implements SwapControllerStateStore {
282
+ private readonly states;
283
+ load(key: string): Promise<SwapControllerState | undefined>;
284
+ save(key: string, state: SwapControllerState): Promise<void>;
285
+ }
286
+ /**
287
+ * Node-only JSON-file store (the toon-client `JsonFileChannelStore` pattern —
288
+ * controller state persists beside the channel store). One JSON file holds a
289
+ * `{ [key]: SwapControllerState }` map; writes are atomic
290
+ * (temp-file + rename). `node:fs` / `node:path` are imported lazily so
291
+ * bundling this module for the browser stays safe as long as the class is
292
+ * not instantiated there.
293
+ */
294
+ declare class JsonFileSwapControllerStateStore implements SwapControllerStateStore {
295
+ private readonly filePath;
296
+ constructor(filePath: string);
297
+ private readAll;
298
+ load(key: string): Promise<SwapControllerState | undefined>;
299
+ save(key: string, state: SwapControllerState): Promise<void>;
300
+ }
301
+ /**
302
+ * Resolution class of one packet, as measured by the sender (spec §6):
303
+ *
304
+ * - `'fulfill'` — packet fulfilled and its claim accepted. Clean unless the
305
+ * realized slip exceeds ε (computed internally from `rate` +
306
+ * `sourceAmount` + `targetAmount`), in which case it is a shrink signal.
307
+ * - `'reject-stale'` — maker staleness reject (`T99 stale_rate`, spec §4).
308
+ * Shrink signal → δ halves.
309
+ * - `'reject'` — any other reject or verification failure (R5-class: floor
310
+ * breach, recipient substitution, bad claim). Shrink signal → δ halves.
311
+ * - `'timeout'` — packet expiry / transport timeout. Shrink signal → W
312
+ * halves (timing risk, not pricing risk).
313
+ * - `'error'` — transport or decode error. Shrink signal → δ halves.
314
+ */
315
+ type PacketResolution = 'fulfill' | 'reject-stale' | 'reject' | 'timeout' | 'error';
316
+ /** One per-packet observation fed to {@link AdaptiveDeltaController.observe}. */
317
+ interface PacketObservation {
318
+ /** Resolution class for this packet (see {@link PacketResolution}). */
319
+ resolution: PacketResolution;
320
+ /** Measured round-trip time for this packet in ms (send → resolve). */
321
+ rttMs?: number;
322
+ /** Quote-tape rate `R_i` for this packet (decimal string, issue #82). */
323
+ rate?: string;
324
+ /** Unix ms when the maker's rate source produced {@link rate}. */
325
+ rateTimestamp?: number;
326
+ /** Source amount sent for this packet (micro-units). Used for slip. */
327
+ sourceAmount?: bigint;
328
+ /** Delivered target amount (micro-units). Used for slip. */
329
+ targetAmount?: bigint;
330
+ /**
331
+ * Remaining session notional AFTER this packet (source micro-units).
332
+ * When provided, widen steps are additionally clamped to the current
333
+ * `delta_cap` fraction of it (spec §6 `δ ← min(delta_cap, δ + δ_0)`).
334
+ * `nextDelta()` re-enforces the cap at issuance regardless.
335
+ */
336
+ remaining?: bigint;
337
+ }
338
+ /**
339
+ * The seam `streamSwap` consumes (kept structural so alternative controller
340
+ * implementations — or a test double — can be plugged in).
341
+ */
342
+ interface StreamSwapAdaptiveController {
343
+ /**
344
+ * Decide the size of the next packet, in source micro-units, given the
345
+ * session's remaining notional. MUST return a value in `[1, remaining]`
346
+ * (callers clamp defensively). Enforces `δ ≤ delta_cap` at issuance.
347
+ */
348
+ nextDelta(remaining: bigint): bigint;
349
+ /** Current in-flight window W (max unfulfilled packets outstanding, ≥ 1). */
350
+ readonly window: number;
351
+ /** Feed one packet observation. May persist state (hence async). */
352
+ observe(observation: PacketObservation): void | Promise<void>;
353
+ }
354
+ /** Configuration for {@link AdaptiveDeltaController.create}. */
355
+ interface AdaptiveDeltaControllerConfig {
356
+ /** Maker's 64-char hex pubkey — part of the persistence key. */
357
+ makerPubkey: string;
358
+ /** The pair being executed — chain + asset parts of the persistence key. */
359
+ pair: SwapPair;
360
+ /**
361
+ * Maker's advertised two-sided spread as a fraction (e.g. `0.004` = 40 bps).
362
+ * ε is denominated off this — NEVER an absolute rate (spec §6): the
363
+ * half-spread self-calibrates ε per chain and per maker. Today the spread
364
+ * is caller-supplied (from the maker's board / RFQ response once toon#145's
365
+ * RFQ lands); there is deliberately no default — an invented spread would
366
+ * rot and silently mis-size ε.
367
+ */
368
+ advertisedSpread: number;
369
+ /**
370
+ * ε as a fraction of the advertised HALF-spread. Default `0.5`
371
+ * (spec §6 default `ε = 0.5 × halfSpread`).
372
+ */
373
+ epsilonHalfSpreadFraction?: number;
374
+ /**
375
+ * Absolute per-packet ceiling in source micro-units (the maker's advertised
376
+ * `maxAmount`). Applied to δ at issuance AND to every widen step — the
377
+ * uncompromising absolute cap alongside the measured `v·τ` bound.
378
+ */
379
+ maxPacketAmount?: bigint;
380
+ /** Floor on δ in source micro-units (`δ_min`). Default `1n`. */
381
+ minPacketAmount?: bigint;
382
+ /** Ceiling on W (`W_max`). Default `8`. */
383
+ maxWindow?: number;
384
+ /** Clean-fulfill streak length K per widen step. Default `16` (spec §6). */
385
+ cleanStreakLength?: number;
386
+ /**
387
+ * Cold-start divisor: `δ_0 = notional / coldStartDivisor` (further clamped
388
+ * by `delta_cap` and `maxPacketAmount`). Default `256` (spec §6).
389
+ */
390
+ coldStartDivisor?: number;
391
+ /** EWMA smoothing factor α for both `v` and `τ`, in (0, 1]. Default `0.2`. */
392
+ ewmaAlpha?: number;
393
+ /**
394
+ * Pluggable persistence. Default: a fresh volatile
395
+ * {@link InMemorySwapControllerStateStore} (state survives the session
396
+ * only). Pass {@link JsonFileSwapControllerStateStore} (Node) or a custom
397
+ * store to persist ramp/trust across swaps.
398
+ */
399
+ store?: SwapControllerStateStore;
400
+ /** Injectable clock for deterministic tests. Default `Date.now`. */
401
+ now?: () => number;
402
+ }
403
+ /**
404
+ * Per-(chain, maker, pair) adaptive δ/W controller (spec §6). Construct via
405
+ * {@link AdaptiveDeltaController.create} (async: loads persisted state).
406
+ *
407
+ * Concurrency note: one instance drives one swap session. Running two
408
+ * concurrent sessions against the same tuple is last-write-wins on the
409
+ * persisted state (same as concurrent channel use).
410
+ */
411
+ declare class AdaptiveDeltaController implements StreamSwapAdaptiveController {
412
+ /** Persistence key for this tuple (see {@link swapControllerStateKey}). */
413
+ readonly key: string;
414
+ private readonly store;
415
+ private readonly pair;
416
+ private readonly epsilon;
417
+ private readonly maxPacketAmount;
418
+ private readonly minPacketAmount;
419
+ private readonly maxWindow;
420
+ private readonly cleanStreakLength;
421
+ private readonly coldStartDivisor;
422
+ private readonly alpha;
423
+ private readonly now;
424
+ private readonly stateInternal;
425
+ /** δ in-memory as bigint (mirrors `stateInternal.delta`). */
426
+ private delta;
427
+ /** Cold-start δ_0 — also the additive widen increment. Set on first nextDelta(). */
428
+ private delta0;
429
+ /** Previous tape entry for the per-second volatility measurement. */
430
+ private lastRate;
431
+ private lastRateTimestamp;
432
+ private constructor();
433
+ /**
434
+ * Create a controller for one swap session: loads the persisted state for
435
+ * the tuple from the store, or starts cold (`δ` unset until the first
436
+ * `nextDelta()`, `W = 1`).
437
+ */
438
+ static create(config: AdaptiveDeltaControllerConfig): Promise<AdaptiveDeltaController>;
439
+ /** Defensive snapshot of the current controller state. */
440
+ get state(): SwapControllerState;
441
+ /** Current in-flight window W. */
442
+ get window(): number;
443
+ /**
444
+ * Current `delta_cap = ε/(v·τ)` as a fraction of remaining notional.
445
+ * `Infinity` while `v·τ` has no measurement yet (cold tape) — the
446
+ * cold-start `δ_0` and absolute caps bound δ in that regime.
447
+ */
448
+ get deltaCapFraction(): number;
449
+ /** `delta_cap` in absolute source micro-units for a given remaining notional. */
450
+ private deltaCapAbsolute;
451
+ /**
452
+ * Decide the next packet size (source micro-units) for a session with
453
+ * `remaining` notional left. Enforces, in order: the measured
454
+ * `delta_cap = ε/(v·τ)` bound, the absolute `maxPacketAmount` cap, the
455
+ * remaining notional, and the `minPacketAmount` floor. Seeds the
456
+ * cold-start `δ_0 = min(delta_cap, notional/256, maxPacketAmount)` on
457
+ * first call.
458
+ */
459
+ nextDelta(remaining: bigint): bigint;
460
+ /**
461
+ * Feed one packet observation: updates the measured EWMAs (`v`, `τ`),
462
+ * applies at most ONE knob adjustment (asymmetric: multiplicative shrink /
463
+ * additive widen), and persists the state.
464
+ */
465
+ observe(observation: PacketObservation): Promise<void>;
466
+ /**
467
+ * Realized per-packet slip as a fraction: shortfall of the delivered
468
+ * target amount vs the amount implied by the packet's own tape rate `R_i`
469
+ * (spec §7.1 `Δcumulative` vs `⌊δ · R_i⌋` cross-check). `0` when the
470
+ * inputs are unavailable or delivery met the tape.
471
+ */
472
+ private realizedSlip;
473
+ }
474
+
475
+ /**
476
+ * Sender-side `streamSwap()` API (Story 12.5).
477
+ *
478
+ * Drives the sender side of the Token Swap Primitive: chunks a total source
479
+ * amount into N sender-chosen packets, wraps each with `wrapSwapPacketToToon()`
480
+ * using a fresh ephemeral gift-wrap key per packet (D12-003 / risk R-006),
481
+ * sends each via `ToonClient.sendSwapPacket(...)`, decrypts each FULFILL's
482
+ * NIP-44-encrypted claim with `decryptFulfillClaim()`, accumulates the
483
+ * decrypted claims into an ordered array, invokes a per-packet rate monitoring
484
+ * callback, and supports pause / resume / stop / AbortSignal so the sender
485
+ * can abort when rate drifts past tolerance.
486
+ *
487
+ * Composition story: consumes Stories 12.1 (SwapPair type), 12.2 (gift wrap
488
+ * primitives), 12.3 (handler wire contract). Produces `AccumulatedClaim[]`
489
+ * consumed by Story 12.6 (`buildSettlementTx()`).
490
+ *
491
+ * Design decisions:
492
+ * - `streamSwap()` does NOT throw on mid-stream failure. Caller gets
493
+ * `StreamSwapResult` with `state` and `abortReason`. Only construction-time
494
+ * validation throws synchronously.
495
+ * - `streamSwap()` does NOT retry individual packets. BTP-level connection
496
+ * retries are handled inside `BtpRuntimeClient`; application-layer retry
497
+ * (e.g., T04 insufficient inventory) is a future-story concern.
498
+ * - Rate deviation math stays BigInt throughout. The `effectiveRate` on
499
+ * `PacketProgress` is a display-only `number` (Epic 11 retro MAX_SAFE_INTEGER
500
+ * guard).
501
+ *
502
+ * @module
503
+ */
504
+
505
+ /** Minimal `IlpSendResult` shape — mirrors `packages/client/src/types.ts`. */
506
+ interface IlpSendResultLike {
507
+ accepted: boolean;
508
+ data?: string;
509
+ code?: string;
510
+ message?: string;
511
+ }
512
+ /**
513
+ * Minimal `SignedBalanceProof` structural type.
514
+ *
515
+ * We avoid importing from `@toon-protocol/client` so the SDK package does not
516
+ * gain a runtime dep on the client package. The sender layer only forwards
517
+ * this opaquely to `ToonClient.sendSwapPacket`.
518
+ */
519
+ type SignedBalanceProofLike = any;
520
+ /**
521
+ * The subset of `ToonClient` that `streamSwap()` requires. This narrow shape
522
+ * exists so that consumers can pass a real `ToonClient` OR a mock without
523
+ * importing the whole class.
524
+ */
525
+ interface StreamSwapClient {
526
+ sendSwapPacket(params: {
527
+ destination: string;
528
+ amount: bigint;
529
+ toonData: Uint8Array;
530
+ timeout?: number;
531
+ claim?: SignedBalanceProofLike;
532
+ /**
533
+ * Explicit per-packet PREPARE expiry. When set, the transport MUST use
534
+ * exactly this expiry on the wire instead of deriving one from the
535
+ * request timeout. Optional so existing `ToonClient` implementations
536
+ * remain structurally compatible (they ignore the extra field until
537
+ * the client-transport work lands).
538
+ */
539
+ expiresAt?: Date;
540
+ }): Promise<IlpSendResultLike>;
541
+ getPublicKey(): string;
542
+ }
543
+ /**
544
+ * Parameters for {@link streamSwap} / {@link streamSwapControlled}.
545
+ *
546
+ * @stable — downstream Stories 12.6 and 12.8 depend on this shape.
547
+ */
548
+ interface StreamSwapParams {
549
+ /** SDK client with BTP wiring (see {@link StreamSwapClient}). */
550
+ client: StreamSwapClient;
551
+ /** Swap's 64-char lowercase hex pubkey (recipient of gift wrap). */
552
+ swapPubkey: string;
553
+ /** Swap's ILP destination address (e.g., 'g.toon.swap1'). */
554
+ swapIlpAddress: string;
555
+ /** The `SwapPair` being executed (from kind:10032 discovery, Story 12.1). */
556
+ pair: SwapPair;
557
+ /** Sender's 32-byte secp256k1 secret key. Used for seal signing AND FULFILL decryption. */
558
+ senderSecretKey: Uint8Array;
559
+ /**
560
+ * Sender's chain-specific payout address for `pair.to.chain` (Story 12.9 AC-4).
561
+ *
562
+ * REQUIRED. The Nostr `senderPubkey` (identity layer) cannot be used as the
563
+ * on-chain `recipient` in a balance-proof because they are cryptographically
564
+ * independent keys (D12-011). For `evm:*` chains this must be a 20-byte
565
+ * lowercased `0x`-prefixed hex address; for `solana:*` a base58 pubkey that
566
+ * decodes to 32 bytes; for `mina:*` a base58 public-key string. The value
567
+ * is validated at `streamSwapControlled()` entry via `validateChainAddress`
568
+ * and echoed on every rumor as the `chain-recipient` tag (AC-6).
569
+ */
570
+ chainRecipient: string;
571
+ /** Total source-asset amount to swap (source micro-units). */
572
+ totalAmount: bigint;
573
+ /** Even-split packet count. EXACTLY ONE of this, `packetAmounts`, or `controller` is required. */
574
+ packetCount?: number;
575
+ /** Explicit per-packet amounts. EXACTLY ONE of this, `packetCount`, or `controller` is required. */
576
+ packetAmounts?: readonly bigint[];
577
+ /**
578
+ * Adaptive δ/W controller (issue #83, rolling-swap spec §6). EXACTLY ONE
579
+ * of this, `packetCount`, or `packetAmounts` is required.
580
+ *
581
+ * When set, packetization is DYNAMIC: the static even split is replaced by
582
+ * per-packet sizing from `controller.nextDelta(remaining)` (δ, capped by
583
+ * the measured `ε/(v·τ)` bound), and up to `controller.window` (W) packets
584
+ * are kept in flight concurrently. After every packet resolves, the
585
+ * controller receives a {@link PacketObservation} (realized rate, tape
586
+ * entry, RTT, resolution class) so δ and W adapt across the stream —
587
+ * multiplicative shrink on stale/slip/reject, additive widen on clean
588
+ * streaks.
589
+ *
590
+ * Adaptive-mode contract differences (each N/A to the legacy paths):
591
+ * - `PacketProgress.total` is the number of packets scheduled SO FAR (the
592
+ * final count is unknown upfront).
593
+ * - With `W > 1`, `onPacket` fires in packet COMPLETION order, not strict
594
+ * index order.
595
+ * - On a mid-stream halt (floor breach, stop, abort), already-sent
596
+ * in-flight packets are drained and their claims harvested before the
597
+ * result resolves.
598
+ *
599
+ * INVARIANT: the controller only tightens/loosens δ and W. It is consulted
600
+ * strictly AFTER the `minExchangeRate` floor check and can never relax it.
601
+ */
602
+ controller?: StreamSwapAdaptiveController;
603
+ /** Source-asset balance proof claim. Required unless ChannelManager is wired. */
604
+ claim?: SignedBalanceProofLike;
605
+ /** Rate monitoring callback (fires after each accepted FULFILL). */
606
+ onPacket?: RateMonitorCallback;
607
+ /** Rate deviation threshold (decimal, e.g., 0.02 = 2%). */
608
+ rateDeviationThreshold?: number;
609
+ /**
610
+ * Hard floor on the per-packet exchange rate (issue #82, rfc-0029
611
+ * `minExchangeRate` semantics; rolling-swap spec §5). Decimal string in
612
+ * `SwapPair.rate` format (target whole-units per source whole-unit),
613
+ * strictly positive.
614
+ *
615
+ * When set:
616
+ * - The quote tape (`rate` + `rateTimestamp` on each FULFILL's metadata)
617
+ * becomes REQUIRED: a fulfilled packet whose metadata is missing the
618
+ * tape is a loud per-packet `FULFILL_DECODE_FAILED` error, never a
619
+ * silent drop.
620
+ * - Each fulfilled packet is checked BEFORE its claim is decrypted or
621
+ * accumulated: if the maker's tape rate `R_i` is below the floor, OR
622
+ * the delivered `targetAmount` is below `applyRate(sourceAmount,
623
+ * minExchangeRate)`, the packet is recorded as a rejection with code
624
+ * `BELOW_FLOOR` and the stream halts with `abortReason: 'below-floor'`.
625
+ * A violating packet NEVER accumulates into `claims[]`.
626
+ *
627
+ * This is the safety mechanism, deliberately independent of (and never
628
+ * relaxed by) the soft `rateDeviationThreshold` monitor, the `onPacket`
629
+ * callback, or any future adaptive-controller signal: a calm tape must
630
+ * not be able to talk the sender into a worse worst case.
631
+ *
632
+ * When omitted, behavior is unchanged (legacy back-compat): the tape is
633
+ * optional and only the soft deviation monitor applies.
634
+ */
635
+ minExchangeRate?: string;
636
+ /** Per-packet timeout in ms. Default 30000. */
637
+ packetTimeoutMs?: number;
638
+ /**
639
+ * Per-packet PREPARE expiry window in ms. When set, each packet is sent
640
+ * with `expiresAt = now + packetExpiryMs` (computed at send time) so a
641
+ * stalled packet expires deterministically and releases its in-flight
642
+ * slot (rolling-swap R7). When omitted, behavior is unchanged: the
643
+ * transport derives the expiry from its timeout (back-compat default).
644
+ */
645
+ packetExpiryMs?: number;
646
+ /** Optional AbortSignal. */
647
+ signal?: AbortSignal;
648
+ /**
649
+ * Optional pino-compatible logger. Defaults to a no-op.
650
+ *
651
+ * Note: `streamSwap` calls each method with a single structured-event object
652
+ * (e.g., `logger.warn({ event: 'stream_swap.packet_rejected', code, ... })`).
653
+ * Pino accepts this form (the object is logged as top-level fields and the
654
+ * message is left empty); other loggers that expect `(msg, meta)` should
655
+ * wrap accordingly.
656
+ */
657
+ logger?: {
658
+ debug: (...a: unknown[]) => void;
659
+ info: (...a: unknown[]) => void;
660
+ warn: (...a: unknown[]) => void;
661
+ error: (...a: unknown[]) => void;
662
+ };
663
+ }
664
+ /**
665
+ * Rate monitoring callback. Fires after each successful FULFILL decryption,
666
+ * before the next packet is sent. If the callback throws or rejects, the
667
+ * stream is treated as stopped.
668
+ */
669
+ type RateMonitorCallback = (progress: PacketProgress) => void | Promise<void>;
670
+ /**
671
+ * Progress payload delivered to {@link RateMonitorCallback}. Frozen to
672
+ * prevent callback mutation.
673
+ */
674
+ interface PacketProgress {
675
+ /** 0-indexed packet number within this streamSwap invocation. */
676
+ index: number;
677
+ /**
678
+ * Total number of packets scheduled. In adaptive mode (`controller` set)
679
+ * packet sizing is dynamic, so this is the number scheduled SO FAR.
680
+ */
681
+ total: number;
682
+ /** Source-asset amount sent for this packet (micro-units). */
683
+ sourceAmount: bigint;
684
+ /** Target-asset claim amount derived for this packet (micro-units). */
685
+ targetAmount: bigint;
686
+ /** Rate advertised on the `SwapPair` at swap start (decimal string). */
687
+ advertisedRate: string;
688
+ /** Effective rate for this packet as JS Number (target / source in whole units). Display-only. */
689
+ effectiveRate: number;
690
+ /** Absolute deviation from advertisedRate as a decimal (e.g., 0.0125 = 1.25%). */
691
+ rateDeviation: number;
692
+ /** Cumulative source sent across accepted packets so far (including this one). */
693
+ cumulativeSource: bigint;
694
+ /** Cumulative target received so far (including this one). */
695
+ cumulativeTarget: bigint;
696
+ /**
697
+ * Quote-tape entry (issue #82, rolling-swap spec §7.1): the maker's fresh
698
+ * rate `R_i` actually applied to THIS packet, as reported on the FULFILL
699
+ * metadata. Present iff the maker emitted the tape. The sequence of these
700
+ * values across packets, in `index` order, IS the price tape the adaptive
701
+ * controller (toon#83) consumes.
702
+ */
703
+ rate?: string;
704
+ /** Unix ms timestamp when the maker's rate source produced {@link rate}. Present iff `rate` is. */
705
+ rateTimestamp?: number;
706
+ /** Controller state at callback time. */
707
+ state: 'running' | 'paused' | 'stopped';
708
+ }
709
+ /**
710
+ * An accumulated claim successfully harvested from a single packet.
711
+ *
712
+ * @stable — Story 12.6 (`buildSettlementTx()`) depends on this shape.
713
+ * Breaking changes require a coordinated migration.
714
+ *
715
+ * Story 12.6 ADDITIVE extension: the settlement-context fields
716
+ * `channelId`, `nonce`, `cumulativeAmount`, `recipient`, and
717
+ * `swapSignerAddress` are marked optional (`?:`) for one story-cycle of
718
+ * backward compat but are REQUIRED in practice: Story 12.6's
719
+ * `buildSettlementTx()` throws `MISSING_SETTLEMENT_METADATA` when any of
720
+ * these are absent.
721
+ */
722
+ interface AccumulatedClaim {
723
+ /** 0-indexed position in the swap's packet stream. */
724
+ packetIndex: number;
725
+ /** Source-asset amount sent for this packet (micro-units). */
726
+ sourceAmount: bigint;
727
+ /**
728
+ * Target-asset amount claimed (micro-units).
729
+ *
730
+ * **Source of truth caveat:** This is the expected target amount computed
731
+ * by `applyRate(pair.rate)`. The actual signed-claim amount lives inside
732
+ * `claimBytes`; Story 12.6 is responsible for parsing `claimBytes` per
733
+ * chain and verifying the on-wire signed amount equals this expected amount.
734
+ */
735
+ targetAmount: bigint;
736
+ /** Decrypted signed claim bytes. Chain-specific encoding per Story 12.4. */
737
+ claimBytes: Uint8Array;
738
+ /** Swap's ephemeral pubkey from the FULFILL (64-char lowercase hex). */
739
+ swapEphemeralPubkey: string;
740
+ /** Optional Swap-side claim ID (passed through from handler metadata). */
741
+ claimId?: string;
742
+ /** Swap pair this claim was priced against (copy of `pair` for settlement-time routing). */
743
+ pair: SwapPair;
744
+ /** Unix ms timestamp when this claim was accepted. */
745
+ receivedAt: number;
746
+ /** Channel identifier on the target chain (lowercase hex with 0x prefix for EVM; base58 for Solana). */
747
+ channelId?: string;
748
+ /** Balance-proof nonce (decimal string). Monotonically increasing within a channel. */
749
+ nonce?: string;
750
+ /** Cumulative transferred amount on the channel (target micro-units, decimal string). */
751
+ cumulativeAmount?: string;
752
+ /** Recipient address (the sender's target-asset address). */
753
+ recipient?: string;
754
+ /** Swap's on-chain signer address. */
755
+ swapSignerAddress?: string;
756
+ /** Maker's fresh rate `R_i` applied to this packet (decimal string), from the FULFILL quote tape. */
757
+ rate?: string;
758
+ /** Unix ms timestamp when the maker's rate source produced `rate`. Present iff `rate` is. */
759
+ rateTimestamp?: number;
760
+ }
761
+ /**
762
+ * Aggregate result of a `streamSwap()` invocation.
763
+ *
764
+ * @stable — Stories 12.6 and 12.8 depend on this shape.
765
+ */
766
+ interface StreamSwapResult {
767
+ state: 'completed' | 'failed' | 'stopped';
768
+ claims: AccumulatedClaim[];
769
+ rejections: {
770
+ packetIndex: number;
771
+ sourceAmount: bigint;
772
+ code: string;
773
+ message: string;
774
+ }[];
775
+ errors: {
776
+ packetIndex: number;
777
+ cause: Error;
778
+ }[];
779
+ abortReason: 'complete' | 'aborted' | 'stopped' | 'callback-stop' | 'callback-throw' | 'rate-deviation' | 'below-floor' | 'all-rejected';
780
+ cumulativeSource: bigint;
781
+ cumulativeTarget: bigint;
782
+ packetsSent: number;
783
+ packetsScheduled: number;
784
+ }
785
+ /** External controller returned by {@link streamSwapControlled}. */
786
+ interface StreamSwapController {
787
+ pause(): void;
788
+ resume(): void;
789
+ stop(): void;
790
+ readonly state: 'running' | 'paused' | 'stopped' | 'completed' | 'failed';
791
+ }
792
+ /** Length-split `total` into `count` bigints. Remainder absorbed on last element. */
793
+ declare function chunkAmount(total: bigint, count: number): bigint[];
794
+ /** Build the kind:20032 unsigned "swap rumor" event per AC-4.
795
+ *
796
+ * Story 12.9 AC-1/AC-6: the returned rumor MUST include a `chain-recipient`
797
+ * tag carrying the sender-supplied chain-format payout address for
798
+ * `pair.to.chain`. The value is echoed verbatim per packet — no case-folding
799
+ * or transformation beyond what the sender-side `validateChainAddress`
800
+ * accepts.
801
+ */
802
+ declare function buildSwapRumor(input: {
803
+ senderPubkey: string;
804
+ pair: SwapPair;
805
+ sourceAmount: bigint;
806
+ packetIndex: number;
807
+ totalPackets: number;
808
+ nonce: Uint8Array;
809
+ createdAt: number;
810
+ chainRecipient: string;
811
+ }): UnsignedEvent;
812
+ /**
813
+ * Decode the FULFILL response `data` (base64-encoded JSON metadata) into
814
+ * the `{ claim, ephemeralPubkey, claimId?, targetAmount?, ...settlement }`
815
+ * shape.
816
+ *
817
+ * Story 12.6 extension: also parses the settlement-context fields
818
+ * (`channelId`, `nonce`, `cumulativeAmount`, `recipient`, `swapSignerAddress`).
819
+ * These are OPTIONAL and best-effort (#153): each is threaded through only
820
+ * when it is a well-formed string for the target chain; an absent or malformed
821
+ * settlement field is silently dropped rather than failing the whole decode,
822
+ * so a fulfilled swap still surfaces its signed `claim` + `ephemeralPubkey`.
823
+ * Only `claim` and `ephemeralPubkey` are strictly required.
824
+ *
825
+ * Issue #82 extension (quote tape, rolling-swap spec §7.1): parses the
826
+ * per-packet quote-tape fields `rate` (decimal string, the maker's fresh
827
+ * `R_i`) and `rateTimestamp` (unix ms). Unlike the best-effort settlement
828
+ * fields, a MALFORMED tape is always a loud `FULFILL_DECODE_FAILED` — a
829
+ * present-but-garbled `rate`/`rateTimestamp`, or one field without the
830
+ * other, fails the decode rather than silently dropping, so a rolling
831
+ * sender can never run blind on a corrupt tape. A wholly ABSENT tape is
832
+ * permitted for legacy makers unless `opts.requireQuoteTape` is set (which
833
+ * `runLoop` does whenever `minExchangeRate` is armed).
834
+ *
835
+ * @param chain Optional `pair.to.chain` string for per-chain format validation
836
+ * of channelId / recipient / swapSignerAddress. When omitted, format checks
837
+ * fall back to a length-only sanity check.
838
+ * @param opts.requireQuoteTape When true, a missing `rate`/`rateTimestamp`
839
+ * pair is a `FULFILL_DECODE_FAILED` error instead of being tolerated.
840
+ */
841
+ declare function decodeFulfillMetadata(data: string | undefined, chain?: string, opts?: {
842
+ requireQuoteTape?: boolean;
843
+ }): {
844
+ claim: string;
845
+ ephemeralPubkey: string;
846
+ claimId?: string;
847
+ /** Optional Swap-reported actual target amount (decimal string). Used for rate deviation when present. */
848
+ targetAmount?: string;
849
+ /** Quote-tape rate `R_i` (decimal string). Present iff the maker emitted the tape. */
850
+ rate?: string;
851
+ /** Quote-tape timestamp (unix ms). Present iff `rate` is. */
852
+ rateTimestamp?: number;
853
+ channelId?: string;
854
+ nonce?: string;
855
+ cumulativeAmount?: string;
856
+ recipient?: string;
857
+ swapSignerAddress?: string;
858
+ };
859
+ /**
860
+ * Compare two decimal-string rates (RATE_REGEX format) exactly, in BigInt —
861
+ * no float coercion. Returns -1 if `a < b`, 0 if equal, 1 if `a > b`.
862
+ *
863
+ * Used by the issue #82 `minExchangeRate` floor to compare the maker's tape
864
+ * rate `R_i` against the floor without precision loss on long fractions.
865
+ */
866
+ declare function compareDecimalRates(a: string, b: string): -1 | 0 | 1;
867
+ /**
868
+ * Drive a multi-packet swap against a Swap and return a `StreamSwapResult`.
869
+ *
870
+ * `streamSwap()` does NOT throw on mid-stream failure — inspect the result's
871
+ * `state`, `abortReason`, `rejections[]`, and `errors[]` to diagnose.
872
+ *
873
+ * @example
874
+ * ```ts
875
+ * // Discover the SwapPair from the Swap's kind:10032 peer-info event (Story 12.1).
876
+ * const result = await streamSwap({
877
+ * client: toonClient,
878
+ * swapPubkey: swap.pubkey,
879
+ * swapIlpAddress: 'g.toon.swap1',
880
+ * pair,
881
+ * senderSecretKey,
882
+ * totalAmount: 1_000_000n,
883
+ * packetCount: 10,
884
+ * onPacket: (p) => console.log('packet', p.index, 'rate', p.effectiveRate),
885
+ * rateDeviationThreshold: 0.02,
886
+ * });
887
+ * // Feed result.claims into buildSettlementTx() from Story 12.6.
888
+ * ```
889
+ *
890
+ * @throws {StreamSwapError} Only for construction-time validation failures
891
+ * (INVALID_AMOUNT, INVALID_CHUNKING, INVALID_PAIR, INVALID_STATE).
892
+ */
893
+ declare function streamSwap(params: StreamSwapParams): Promise<StreamSwapResult>;
894
+ /**
895
+ * Two-form variant of {@link streamSwap} that additionally returns a
896
+ * {@link StreamSwapController} with `pause()` / `resume()` / `stop()`.
897
+ *
898
+ * @throws {StreamSwapError} Synchronously for construction-time validation.
899
+ */
900
+ declare function streamSwapControlled(params: StreamSwapParams): {
901
+ result: Promise<StreamSwapResult>;
902
+ controller: StreamSwapController;
903
+ };
904
+ declare const __testing: {
905
+ chunkAmount: typeof chunkAmount;
906
+ decodeFulfillMetadata: typeof decodeFulfillMetadata;
907
+ buildSwapRumor: typeof buildSwapRumor;
908
+ compareDecimalRates: typeof compareDecimalRates;
909
+ };
910
+
911
+ export { type AccumulatedClaim as A, type StreamSwapClient as B, type DecryptFulfillClaimParams as D, type EncryptFulfillClaimParams as E, InMemorySwapControllerStateStore as I, JsonFileSwapControllerStateStore as J, type PacketObservation as P, type RateMonitorCallback as R, type StreamSwapAdaptiveController as S, type UnwrapSwapPacketFromToonParams as U, type WrapSwapPacketParams as W, __testing as _, AdaptiveDeltaController as a, type AdaptiveDeltaControllerConfig as b, type EncryptFulfillClaimResult as c, type PacketProgress as d, type PacketResolution as e, type StreamSwapController as f, type StreamSwapParams as g, type StreamSwapResult as h, SwapControllerError as i, type SwapControllerState as j, type SwapControllerStateStore as k, type UnwrapSwapPacketParams as l, type UnwrapSwapPacketResult as m, type WrapSwapPacketResult as n, type WrapSwapPacketToToonParams as o, type WrapSwapPacketToToonResult as p, decryptFulfillClaim as q, encryptFulfillClaim as r, isSwapControllerState as s, streamSwap as t, streamSwapControlled as u, swapControllerStateKey as v, unwrapSwapPacket as w, unwrapSwapPacketFromToon as x, wrapSwapPacket as y, wrapSwapPacketToToon as z };