@toon-protocol/sdk 2.0.1 → 2.2.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/dist/index.d.ts CHANGED
@@ -4,8 +4,8 @@ import { NostrEvent, UnsignedEvent } from 'nostr-tools/pure';
4
4
  import { ToonRoutingMeta } from '@toon-protocol/core/toon';
5
5
  import { TransportConfig } from '@toon-protocol/connector';
6
6
  export { TransportConfig } from '@toon-protocol/connector';
7
- import { A as AccumulatedClaim } from './swap-DF4ghKio.js';
8
- export { D as DecryptFulfillClaimParams, E as EncryptFulfillClaimParams, a as EncryptFulfillClaimResult, P as PacketProgress, R as RateMonitorCallback, S as StreamSwapController, b as StreamSwapParams, c as StreamSwapResult, U as UnwrapSwapPacketFromToonParams, d as UnwrapSwapPacketParams, e as UnwrapSwapPacketResult, W as WrapSwapPacketParams, f as WrapSwapPacketResult, g as WrapSwapPacketToToonParams, h as WrapSwapPacketToToonResult, _ as __streamSwapTesting, i as decryptFulfillClaim, j as encryptFulfillClaim, s as streamSwap, k as streamSwapControlled, u as unwrapSwapPacket, l as unwrapSwapPacketFromToon, w as wrapSwapPacket, m as wrapSwapPacketToToon } from './swap-DF4ghKio.js';
7
+ import { R as ReceiptSessionStoreLike, A as AccumulatedClaim } from './swap-PFQTJZA7.js';
8
+ export { a as AdaptiveDeltaController, b as AdaptiveDeltaControllerConfig, B as BoundedReceiptSessions, D as DEFAULT_RECEIPT_SESSIONS_CAP, c as DecryptFulfillClaimParams, E as EncryptFulfillClaimParams, d as EncryptFulfillClaimResult, I as InMemorySwapControllerStateStore, J as JsonFileSwapControllerStateStore, P as PacketObservation, e as PacketProgress, f as PacketResolution, g as RateMonitorCallback, h as ReceiptAddResult, i as ReceiptChainTracker, j as ReceiptSessionState, S as STREAM_RECEIPT_SIGNING_TAG, k as STREAM_RECEIPT_VERSION, l as StreamReceipt, m as StreamReceiptChain, n as StreamReceiptFields, o as StreamSwapAdaptiveController, p as StreamSwapController, q as StreamSwapParams, r as StreamSwapResult, s as SwapControllerError, t as SwapControllerState, u as SwapControllerStateStore, U as UnwrapSwapPacketFromToonParams, v as UnwrapSwapPacketParams, w as UnwrapSwapPacketResult, W as WrapSwapPacketParams, x as WrapSwapPacketResult, y as WrapSwapPacketToToonParams, z as WrapSwapPacketToToonResult, _ as __streamSwapTesting, C as decryptFulfillClaim, F as encodeReceiptSigningPayload, G as encryptFulfillClaim, H as isSwapControllerState, K as isValidStreamNonce, L as issueSessionReceipt, M as parseStreamReceipt, N as serializeReceiptChain, O as signStreamReceipt, Q as streamSwap, T as streamSwapControlled, V as swapControllerStateKey, X as unwrapSwapPacket, Y as unwrapSwapPacketFromToon, Z as verifyStreamReceipt, $ as wrapSwapPacket, a0 as wrapSwapPacketToToon } from './swap-PFQTJZA7.js';
9
9
 
10
10
  /**
11
11
  * Unified identity module for @toon-protocol/sdk.
@@ -1462,6 +1462,21 @@ interface ApplyRateParams {
1462
1462
  /** Decimal-string rate (target whole-units per source whole-unit). */
1463
1463
  rate: string;
1464
1464
  }
1465
+ /**
1466
+ * A fresh quote returned by a {@link CreateSwapHandlerConfig.rateProvider}
1467
+ * hook (issue #82, rolling-swap quote tape).
1468
+ *
1469
+ * `rateTimestamp` is the unix-ms time at which the maker's rate SOURCE
1470
+ * produced this quote (its feed tick), not the time the handler resolved it.
1471
+ * Both fields are echoed verbatim into the FULFILL accept-metadata so the
1472
+ * sender can read the quote tape `(R_1, t_1), (R_2, t_2), …` off the fills.
1473
+ */
1474
+ interface RateQuote {
1475
+ /** Decimal-string rate matching `SwapPair.rate` format: /^(0|[1-9]\d*)(\.\d+)?$/. */
1476
+ rate: string;
1477
+ /** Unix ms timestamp when the rate source produced this quote. Positive integer. */
1478
+ rateTimestamp: number;
1479
+ }
1465
1480
  /** Minimal pino-compatible logger interface. */
1466
1481
  interface SwapHandlerLogger {
1467
1482
  debug: (...args: unknown[]) => void;
@@ -1490,10 +1505,18 @@ interface CreateSwapHandlerConfig {
1490
1505
  claimIssuer: ClaimIssuer;
1491
1506
  /**
1492
1507
  * Optional live-rate override hook. When provided, the handler calls this per
1493
- * packet instead of reading `pair.rate`. MUST return a decimal string matching
1494
- * `SwapPair.rate` format: /^(0|[1-9]\d*)(\.\d+)?$/.
1508
+ * packet instead of reading `pair.rate`. This is the rolling-swap fresh-quote
1509
+ * seam (issue #82): the resolved rate `R_i` and its quote timestamp are
1510
+ * emitted on every FULFILL's accept-metadata as the quote tape.
1511
+ *
1512
+ * MAY return either:
1513
+ * - a decimal string matching `SwapPair.rate` format
1514
+ * /^(0|[1-9]\d*)(\.\d+)?$/ (legacy shape; the handler stamps
1515
+ * `rateTimestamp` with its own resolution time), or
1516
+ * - a {@link RateQuote} `{ rate, rateTimestamp }` so the rate source's own
1517
+ * tick time travels on the tape.
1495
1518
  */
1496
- rateProvider?: (pair: SwapPair) => string | Promise<string>;
1519
+ rateProvider?: (pair: SwapPair) => string | RateQuote | Promise<string | RateQuote>;
1497
1520
  /**
1498
1521
  * Optional replay-protection set. When provided, the handler uses it
1499
1522
  * VERBATIM (operator-owned — bounding/persistence is the operator's
@@ -1504,6 +1527,27 @@ interface CreateSwapHandlerConfig {
1504
1527
  * satisfies the contract.
1505
1528
  */
1506
1529
  seenPacketIds?: SeenPacketIdsLike;
1530
+ /**
1531
+ * Optional dedicated receipt signing key (issue #84, rfc-0039 stream
1532
+ * receipts; rolling-swap spec §7.2). 32-byte secp256k1 secret key used to
1533
+ * BIP-340-sign the per-fulfill {@link StreamReceipt}. When OMITTED,
1534
+ * receipts are signed with `recipientSecretKey` — the maker's Nostr
1535
+ * identity key — so senders verify against the `swapPubkey` they already
1536
+ * discovered via kind:10032. Provide a separate key when the receipt
1537
+ * signer should be provisioned independently of the identity key (e.g.
1538
+ * the swap#47 coupled engine binding receipts to the chain-B claim
1539
+ * signer); senders then verify via `StreamSwapParams.receiptPubkey`.
1540
+ */
1541
+ receiptSecretKey?: Uint8Array;
1542
+ /**
1543
+ * Optional receipt session store (issue #84). Tracks per-`streamNonce`
1544
+ * `{seq, cumulativeDelivered}` so receipt totals are monotone within a
1545
+ * session. Defaults to a {@link BoundedReceiptSessions} in-memory LRU.
1546
+ * Operators that persist claims should back this with the same storage so
1547
+ * receipt state survives restarts alongside the claim stream (a lost
1548
+ * session restarts at seq 1, which senders reject as a forked chain).
1549
+ */
1550
+ receiptSessions?: ReceiptSessionStoreLike;
1507
1551
  /** Optional pino-compatible logger. Defaults to a no-op logger. */
1508
1552
  logger?: SwapHandlerLogger;
1509
1553
  }
@@ -1957,4 +2001,4 @@ interface StoreHealthResponse {
1957
2001
  jobsRecent: StoreJobsRecent;
1958
2002
  }
1959
2003
 
1960
- export { AccumulatedClaim, type AddChunkResult, type ApplyRateParams, type ArweaveDvmConfig, type ArweaveUploadAdapter, type BuildSettlementTxParams, type BuildSettlementTxResult, type BuildSkillDescriptorConfig, ChunkManager, type ChunkManagerConfig, type ClaimIssuer, type CreateHandlerContextOptions, type CreateSwapHandlerConfig, type FromMnemonicOptions, GiftWrapError, type Handler, type HandlerContext, HandlerError, HandlerRegistry, type HandlerResponse, IdentityError, type IssueClaimParams, type IssueClaimResult, type MinaIdentity, type MinaSignerClientLike, type NodeConfig, NodeError, type NodeIdentity, type PaymentHandlerBridgeConfig, type PaymentRequest, type PaymentResponse, type PrefixClaimHandlerOptions, PricingError, type PricingValidationResult, type PricingValidatorConfig, type PublishEventResult, type PublishableNode, SWAP_HANDLER_REJECT_CODES, SWAP_HANDLER_REJECT_MESSAGES, type ServiceNode, type SettlementBundle, SettlementTxError, type SolanaIdentity, type StartResult, type StoreHealthResponse, type StoreJobsByKindEntry, type StoreJobsByStatus, type StoreJobsRecent, StreamSwapError, SwapHandlerError, type SwapHandlerLogger, type SwapSignerConfig, SwarmCoordinator, type SwarmCoordinatorOptions, type SwarmState, type ToonIdentity, TurboUploadAdapter, type UploadBlobChunkedOptions, type UploadBlobOptions, VerificationError, type VerificationPipelineConfig, type VerificationResult, type WorkflowEventStore, WorkflowOrchestrator, type WorkflowOrchestratorOptions, type WorkflowState, applyRate, base58Decode, base58Encode, buildSettlementTx, buildSkillDescriptor, createArweaveDvmHandler, createEventStorageHandler, createHandlerContext, createNode, createPaymentHandlerBridge, createPrefixClaimHandler, createPricingValidator, createSwapHandler, createVerificationPipeline, fillEvmSettlementTxGas, findSwapPair, fromMnemonic, fromMnemonicFull, fromSecretKey, generateMnemonic, generateSolanaKeypair, loadMinaSignerClient, uploadBlob, uploadBlobChunked, verifyAccumulatedClaim, verifyEd25519Signature, verifyMinaSignature };
2004
+ export { AccumulatedClaim, type AddChunkResult, type ApplyRateParams, type ArweaveDvmConfig, type ArweaveUploadAdapter, type BuildSettlementTxParams, type BuildSettlementTxResult, type BuildSkillDescriptorConfig, ChunkManager, type ChunkManagerConfig, type ClaimIssuer, type CreateHandlerContextOptions, type CreateSwapHandlerConfig, type FromMnemonicOptions, GiftWrapError, type Handler, type HandlerContext, HandlerError, HandlerRegistry, type HandlerResponse, IdentityError, type IssueClaimParams, type IssueClaimResult, type MinaIdentity, type MinaSignerClientLike, type NodeConfig, NodeError, type NodeIdentity, type PaymentHandlerBridgeConfig, type PaymentRequest, type PaymentResponse, type PrefixClaimHandlerOptions, PricingError, type PricingValidationResult, type PricingValidatorConfig, type PublishEventResult, type PublishableNode, type RateQuote, ReceiptSessionStoreLike, SWAP_HANDLER_REJECT_CODES, SWAP_HANDLER_REJECT_MESSAGES, type ServiceNode, type SettlementBundle, SettlementTxError, type SolanaIdentity, type StartResult, type StoreHealthResponse, type StoreJobsByKindEntry, type StoreJobsByStatus, type StoreJobsRecent, StreamSwapError, SwapHandlerError, type SwapHandlerLogger, type SwapSignerConfig, SwarmCoordinator, type SwarmCoordinatorOptions, type SwarmState, type ToonIdentity, TurboUploadAdapter, type UploadBlobChunkedOptions, type UploadBlobOptions, VerificationError, type VerificationPipelineConfig, type VerificationResult, type WorkflowEventStore, WorkflowOrchestrator, type WorkflowOrchestratorOptions, type WorkflowState, applyRate, base58Decode, base58Encode, buildSettlementTx, buildSkillDescriptor, createArweaveDvmHandler, createEventStorageHandler, createHandlerContext, createNode, createPaymentHandlerBridge, createPrefixClaimHandler, createPricingValidator, createSwapHandler, createVerificationPipeline, fillEvmSettlementTxGas, findSwapPair, fromMnemonic, fromMnemonicFull, fromSecretKey, generateMnemonic, generateSolanaKeypair, loadMinaSignerClient, uploadBlob, uploadBlobChunked, verifyAccumulatedClaim, verifyEd25519Signature, verifyMinaSignature };
package/dist/index.js CHANGED
@@ -1,9 +1,14 @@
1
1
  import {
2
+ BoundedReceiptSessions,
3
+ DEFAULT_RECEIPT_SESSIONS_CAP,
2
4
  GiftWrapError,
3
5
  HandlerError,
4
6
  IdentityError,
5
7
  NodeError,
6
8
  PricingError,
9
+ ReceiptChainTracker,
10
+ STREAM_RECEIPT_SIGNING_TAG,
11
+ STREAM_RECEIPT_VERSION,
7
12
  SWAP_HANDLER_REJECT_CODES,
8
13
  SWAP_HANDLER_REJECT_MESSAGES,
9
14
  SettlementTxError,
@@ -16,6 +21,7 @@ import {
16
21
  base58Encode,
17
22
  createSwapHandler,
18
23
  decryptFulfillClaim,
24
+ encodeReceiptSigningPayload,
19
25
  encryptFulfillClaim,
20
26
  findSwapPair,
21
27
  fromMnemonic,
@@ -23,13 +29,19 @@ import {
23
29
  fromSecretKey,
24
30
  generateMnemonic,
25
31
  generateSolanaKeypair,
32
+ isValidStreamNonce,
33
+ issueSessionReceipt,
34
+ parseStreamReceipt,
35
+ serializeReceiptChain,
36
+ signStreamReceipt,
26
37
  streamSwap,
27
38
  streamSwapControlled,
28
39
  unwrapSwapPacket,
29
40
  unwrapSwapPacketFromToon,
41
+ verifyStreamReceipt,
30
42
  wrapSwapPacket,
31
43
  wrapSwapPacketToToon
32
- } from "./chunk-Z6S56VPV.js";
44
+ } from "./chunk-RAZRWSJF.js";
33
45
  import "./chunk-UP2VWCW5.js";
34
46
 
35
47
  // src/handler-context.ts
@@ -1997,6 +2009,360 @@ async function uploadBlobChunked(node, blob, destination, options) {
1997
2009
  return lastResult.eventId;
1998
2010
  }
1999
2011
 
2012
+ // src/adaptive-controller.ts
2013
+ import { ToonError as ToonError3 } from "@toon-protocol/core";
2014
+ var SwapControllerError = class extends ToonError3 {
2015
+ constructor(message, cause) {
2016
+ super(message, "SWAP_CONTROLLER_ERROR", cause);
2017
+ this.name = "SwapControllerError";
2018
+ }
2019
+ };
2020
+ var DECIMAL_UINT_REGEX = /^(0|[1-9]\d*)$/;
2021
+ function isSwapControllerState(value) {
2022
+ if (!value || typeof value !== "object") return false;
2023
+ const s = value;
2024
+ return s["v"] === 1 && typeof s["delta"] === "string" && DECIMAL_UINT_REGEX.test(s["delta"]) && typeof s["W"] === "number" && Number.isInteger(s["W"]) && s["W"] >= 1 && typeof s["vEwma"] === "number" && Number.isFinite(s["vEwma"]) && s["vEwma"] >= 0 && typeof s["tauEwma"] === "number" && Number.isFinite(s["tauEwma"]) && s["tauEwma"] >= 0 && typeof s["cleanStreak"] === "number" && Number.isInteger(s["cleanStreak"]) && s["cleanStreak"] >= 0 && typeof s["everShrunk"] === "boolean" && (s["lastWidened"] === "delta" || s["lastWidened"] === "window") && typeof s["updatedAt"] === "number";
2025
+ }
2026
+ function swapControllerStateKey(params) {
2027
+ const { makerPubkey, pair } = params;
2028
+ const from = `${pair.from.assetCode}@${pair.from.chain}`;
2029
+ const to = `${pair.to.assetCode}@${pair.to.chain}`;
2030
+ return `${pair.from.chain}:${makerPubkey}:${from}:${to}`;
2031
+ }
2032
+ var InMemorySwapControllerStateStore = class {
2033
+ states = /* @__PURE__ */ new Map();
2034
+ async load(key) {
2035
+ const hit = this.states.get(key);
2036
+ return hit === void 0 ? void 0 : { ...hit };
2037
+ }
2038
+ async save(key, state) {
2039
+ this.states.set(key, { ...state });
2040
+ }
2041
+ };
2042
+ var JsonFileSwapControllerStateStore = class {
2043
+ constructor(filePath) {
2044
+ this.filePath = filePath;
2045
+ if (typeof filePath !== "string" || filePath.length === 0) {
2046
+ throw new SwapControllerError(
2047
+ "JsonFileSwapControllerStateStore requires a non-empty filePath"
2048
+ );
2049
+ }
2050
+ }
2051
+ async readAll() {
2052
+ const fs = await import("fs/promises");
2053
+ let raw;
2054
+ try {
2055
+ raw = await fs.readFile(this.filePath, "utf8");
2056
+ } catch (err) {
2057
+ if (err.code === "ENOENT") return {};
2058
+ throw new SwapControllerError(
2059
+ `Failed to read controller state file ${this.filePath}`,
2060
+ err instanceof Error ? err : void 0
2061
+ );
2062
+ }
2063
+ try {
2064
+ const parsed = JSON.parse(raw);
2065
+ if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
2066
+ return parsed;
2067
+ }
2068
+ } catch {
2069
+ }
2070
+ return {};
2071
+ }
2072
+ async load(key) {
2073
+ const all = await this.readAll();
2074
+ const candidate = all[key];
2075
+ return isSwapControllerState(candidate) ? { ...candidate } : void 0;
2076
+ }
2077
+ async save(key, state) {
2078
+ const fs = await import("fs/promises");
2079
+ const path = await import("path");
2080
+ const all = await this.readAll();
2081
+ all[key] = { ...state };
2082
+ const dir = path.dirname(this.filePath);
2083
+ await fs.mkdir(dir, { recursive: true });
2084
+ const tmp = `${this.filePath}.${process.pid}.${Date.now()}.tmp`;
2085
+ await fs.writeFile(tmp, JSON.stringify(all, null, 2), "utf8");
2086
+ await fs.rename(tmp, this.filePath);
2087
+ }
2088
+ };
2089
+ var DEFAULT_EPSILON_HALF_SPREAD_FRACTION = 0.5;
2090
+ var DEFAULT_MAX_WINDOW = 8;
2091
+ var DEFAULT_CLEAN_STREAK_LENGTH = 16;
2092
+ var DEFAULT_COLD_START_DIVISOR = 256;
2093
+ var DEFAULT_EWMA_ALPHA = 0.2;
2094
+ var CAP_SCALE = 1000000000000n;
2095
+ var CAP_SCALE_NUM = 1e12;
2096
+ function rateToNumber(rate) {
2097
+ const n = Number(rate);
2098
+ return Number.isFinite(n) && n > 0 ? n : NaN;
2099
+ }
2100
+ var AdaptiveDeltaController = class _AdaptiveDeltaController {
2101
+ /** Persistence key for this tuple (see {@link swapControllerStateKey}). */
2102
+ key;
2103
+ store;
2104
+ pair;
2105
+ epsilon;
2106
+ maxPacketAmount;
2107
+ minPacketAmount;
2108
+ maxWindow;
2109
+ cleanStreakLength;
2110
+ coldStartDivisor;
2111
+ alpha;
2112
+ now;
2113
+ stateInternal;
2114
+ /** δ in-memory as bigint (mirrors `stateInternal.delta`). */
2115
+ delta;
2116
+ /** Cold-start δ_0 — also the additive widen increment. Set on first nextDelta(). */
2117
+ delta0 = null;
2118
+ /** Previous tape entry for the per-second volatility measurement. */
2119
+ lastRate = null;
2120
+ lastRateTimestamp = null;
2121
+ constructor(config, key, store, state) {
2122
+ this.key = key;
2123
+ this.store = store;
2124
+ this.pair = config.pair;
2125
+ const epsilonFraction = config.epsilonHalfSpreadFraction ?? DEFAULT_EPSILON_HALF_SPREAD_FRACTION;
2126
+ this.epsilon = epsilonFraction * (config.advertisedSpread / 2);
2127
+ this.maxPacketAmount = config.maxPacketAmount;
2128
+ this.minPacketAmount = config.minPacketAmount ?? 1n;
2129
+ this.maxWindow = config.maxWindow ?? DEFAULT_MAX_WINDOW;
2130
+ this.cleanStreakLength = config.cleanStreakLength ?? DEFAULT_CLEAN_STREAK_LENGTH;
2131
+ this.coldStartDivisor = BigInt(
2132
+ config.coldStartDivisor ?? DEFAULT_COLD_START_DIVISOR
2133
+ );
2134
+ this.alpha = config.ewmaAlpha ?? DEFAULT_EWMA_ALPHA;
2135
+ this.now = config.now ?? Date.now;
2136
+ this.stateInternal = state;
2137
+ this.delta = BigInt(state.delta);
2138
+ }
2139
+ /**
2140
+ * Create a controller for one swap session: loads the persisted state for
2141
+ * the tuple from the store, or starts cold (`δ` unset until the first
2142
+ * `nextDelta()`, `W = 1`).
2143
+ */
2144
+ static async create(config) {
2145
+ validateConfig(config);
2146
+ const key = swapControllerStateKey({
2147
+ makerPubkey: config.makerPubkey,
2148
+ pair: config.pair
2149
+ });
2150
+ const store = config.store ?? new InMemorySwapControllerStateStore();
2151
+ const now = config.now ?? Date.now;
2152
+ const loaded = await store.load(key);
2153
+ const state = isSwapControllerState(loaded) ? loaded : {
2154
+ v: 1,
2155
+ delta: "0",
2156
+ W: 1,
2157
+ vEwma: 0,
2158
+ tauEwma: 0,
2159
+ cleanStreak: 0,
2160
+ everShrunk: false,
2161
+ // 'window' so the FIRST widen step hits δ (alternation starts on δ).
2162
+ lastWidened: "window",
2163
+ updatedAt: now()
2164
+ };
2165
+ return new _AdaptiveDeltaController(config, key, store, state);
2166
+ }
2167
+ /** Defensive snapshot of the current controller state. */
2168
+ get state() {
2169
+ return { ...this.stateInternal, delta: this.delta.toString() };
2170
+ }
2171
+ /** Current in-flight window W. */
2172
+ get window() {
2173
+ return this.stateInternal.W;
2174
+ }
2175
+ /**
2176
+ * Current `delta_cap = ε/(v·τ)` as a fraction of remaining notional.
2177
+ * `Infinity` while `v·τ` has no measurement yet (cold tape) — the
2178
+ * cold-start `δ_0` and absolute caps bound δ in that regime.
2179
+ */
2180
+ get deltaCapFraction() {
2181
+ const vTau = this.stateInternal.vEwma * this.stateInternal.tauEwma;
2182
+ if (!(vTau > 0)) return Infinity;
2183
+ return this.epsilon / vTau;
2184
+ }
2185
+ /** `delta_cap` in absolute source micro-units for a given remaining notional. */
2186
+ deltaCapAbsolute(remaining) {
2187
+ const frac = this.deltaCapFraction;
2188
+ if (!Number.isFinite(frac) || frac >= 1) return remaining;
2189
+ if (frac <= 0) return this.minPacketAmount;
2190
+ const scaled = BigInt(Math.floor(frac * CAP_SCALE_NUM));
2191
+ return remaining * scaled / CAP_SCALE;
2192
+ }
2193
+ /**
2194
+ * Decide the next packet size (source micro-units) for a session with
2195
+ * `remaining` notional left. Enforces, in order: the measured
2196
+ * `delta_cap = ε/(v·τ)` bound, the absolute `maxPacketAmount` cap, the
2197
+ * remaining notional, and the `minPacketAmount` floor. Seeds the
2198
+ * cold-start `δ_0 = min(delta_cap, notional/256, maxPacketAmount)` on
2199
+ * first call.
2200
+ */
2201
+ nextDelta(remaining) {
2202
+ if (typeof remaining !== "bigint" || remaining <= 0n) return 0n;
2203
+ if (this.delta0 === null) {
2204
+ let d0 = remaining / this.coldStartDivisor;
2205
+ const capAbs2 = this.deltaCapAbsolute(remaining);
2206
+ if (d0 > capAbs2) d0 = capAbs2;
2207
+ if (this.maxPacketAmount !== void 0 && d0 > this.maxPacketAmount) {
2208
+ d0 = this.maxPacketAmount;
2209
+ }
2210
+ if (d0 < this.minPacketAmount) d0 = this.minPacketAmount;
2211
+ this.delta0 = d0;
2212
+ if (this.delta <= 0n) {
2213
+ this.delta = d0;
2214
+ this.stateInternal.delta = d0.toString();
2215
+ }
2216
+ }
2217
+ let d = this.delta;
2218
+ const capAbs = this.deltaCapAbsolute(remaining);
2219
+ if (d > capAbs) d = capAbs;
2220
+ if (this.maxPacketAmount !== void 0 && d > this.maxPacketAmount) {
2221
+ d = this.maxPacketAmount;
2222
+ }
2223
+ if (d > remaining) d = remaining;
2224
+ const floor = this.minPacketAmount < remaining ? this.minPacketAmount : remaining;
2225
+ if (d < floor) d = floor;
2226
+ return d;
2227
+ }
2228
+ /**
2229
+ * Feed one packet observation: updates the measured EWMAs (`v`, `τ`),
2230
+ * applies at most ONE knob adjustment (asymmetric: multiplicative shrink /
2231
+ * additive widen), and persists the state.
2232
+ */
2233
+ async observe(observation) {
2234
+ const s = this.stateInternal;
2235
+ if (typeof observation.rttMs === "number" && Number.isFinite(observation.rttMs) && observation.rttMs > 0) {
2236
+ const tauSec = observation.rttMs / 1e3;
2237
+ s.tauEwma = s.tauEwma === 0 ? tauSec : this.alpha * tauSec + (1 - this.alpha) * s.tauEwma;
2238
+ }
2239
+ if (typeof observation.rate === "string" && typeof observation.rateTimestamp === "number" && Number.isFinite(observation.rateTimestamp)) {
2240
+ const r = rateToNumber(observation.rate);
2241
+ if (!Number.isNaN(r)) {
2242
+ if (this.lastRate !== null && this.lastRateTimestamp !== null && observation.rateTimestamp > this.lastRateTimestamp) {
2243
+ const dtSec = (observation.rateTimestamp - this.lastRateTimestamp) / 1e3;
2244
+ const inst = Math.abs(r - this.lastRate) / this.lastRate / dtSec;
2245
+ if (Number.isFinite(inst)) {
2246
+ s.vEwma = s.vEwma === 0 ? inst : this.alpha * inst + (1 - this.alpha) * s.vEwma;
2247
+ }
2248
+ }
2249
+ if (this.lastRateTimestamp === null || observation.rateTimestamp >= this.lastRateTimestamp) {
2250
+ this.lastRate = r;
2251
+ this.lastRateTimestamp = observation.rateTimestamp;
2252
+ }
2253
+ }
2254
+ }
2255
+ const slip = this.realizedSlip(observation);
2256
+ const isShrink = observation.resolution !== "fulfill" || slip > this.epsilon;
2257
+ if (isShrink) {
2258
+ if (observation.resolution === "timeout") {
2259
+ s.W = Math.max(1, Math.ceil(s.W / 2));
2260
+ } else {
2261
+ let d = this.delta / 2n;
2262
+ if (d < this.minPacketAmount) d = this.minPacketAmount;
2263
+ this.delta = d;
2264
+ s.delta = d.toString();
2265
+ }
2266
+ s.cleanStreak = 0;
2267
+ s.everShrunk = true;
2268
+ } else {
2269
+ s.cleanStreak += 1;
2270
+ if (s.cleanStreak >= this.cleanStreakLength) {
2271
+ s.cleanStreak = 0;
2272
+ const knob = s.lastWidened === "delta" ? "window" : "delta";
2273
+ if (knob === "window") {
2274
+ s.W = Math.min(this.maxWindow, s.W + 1);
2275
+ } else {
2276
+ const increment = this.delta0 ?? this.minPacketAmount;
2277
+ let d = s.everShrunk ? this.delta + increment : this.delta * 2n;
2278
+ if (this.maxPacketAmount !== void 0 && d > this.maxPacketAmount) {
2279
+ d = this.maxPacketAmount;
2280
+ }
2281
+ if (observation.remaining !== void 0 && observation.remaining > 0n) {
2282
+ const capAbs = this.deltaCapAbsolute(observation.remaining);
2283
+ if (d > capAbs) d = capAbs;
2284
+ }
2285
+ if (d < this.minPacketAmount) d = this.minPacketAmount;
2286
+ if (d < this.delta) d = this.delta;
2287
+ this.delta = d;
2288
+ s.delta = d.toString();
2289
+ }
2290
+ s.lastWidened = knob;
2291
+ }
2292
+ }
2293
+ s.updatedAt = this.now();
2294
+ await this.store.save(this.key, { ...s });
2295
+ }
2296
+ /**
2297
+ * Realized per-packet slip as a fraction: shortfall of the delivered
2298
+ * target amount vs the amount implied by the packet's own tape rate `R_i`
2299
+ * (spec §7.1 `Δcumulative` vs `⌊δ · R_i⌋` cross-check). `0` when the
2300
+ * inputs are unavailable or delivery met the tape.
2301
+ */
2302
+ realizedSlip(observation) {
2303
+ if (observation.resolution !== "fulfill" || observation.rate === void 0 || observation.sourceAmount === void 0 || observation.targetAmount === void 0 || observation.sourceAmount <= 0n) {
2304
+ return 0;
2305
+ }
2306
+ let expected;
2307
+ try {
2308
+ expected = applyRate({
2309
+ sourceAmount: observation.sourceAmount,
2310
+ fromScale: this.pair.from.assetScale,
2311
+ toScale: this.pair.to.assetScale,
2312
+ rate: observation.rate
2313
+ });
2314
+ } catch {
2315
+ return 0;
2316
+ }
2317
+ if (expected <= 0n || observation.targetAmount >= expected) return 0;
2318
+ const shortfall = expected - observation.targetAmount;
2319
+ return Number(shortfall * 1000000n / expected) / 1e6;
2320
+ }
2321
+ };
2322
+ function validateConfig(config) {
2323
+ if (typeof config.makerPubkey !== "string" || config.makerPubkey.length === 0) {
2324
+ throw new SwapControllerError("makerPubkey must be a non-empty string");
2325
+ }
2326
+ if (!config.pair || typeof config.pair !== "object" || !config.pair.from || !config.pair.to || typeof config.pair.from.chain !== "string" || typeof config.pair.to.chain !== "string") {
2327
+ throw new SwapControllerError(
2328
+ "pair must be a SwapPair with from/to chain descriptors"
2329
+ );
2330
+ }
2331
+ if (typeof config.advertisedSpread !== "number" || !Number.isFinite(config.advertisedSpread) || config.advertisedSpread <= 0) {
2332
+ throw new SwapControllerError(
2333
+ `advertisedSpread must be a positive finite fraction, got ${String(
2334
+ config.advertisedSpread
2335
+ )}`
2336
+ );
2337
+ }
2338
+ if (config.epsilonHalfSpreadFraction !== void 0 && (typeof config.epsilonHalfSpreadFraction !== "number" || !Number.isFinite(config.epsilonHalfSpreadFraction) || config.epsilonHalfSpreadFraction <= 0)) {
2339
+ throw new SwapControllerError(
2340
+ "epsilonHalfSpreadFraction must be a positive finite number"
2341
+ );
2342
+ }
2343
+ if (config.maxPacketAmount !== void 0 && (typeof config.maxPacketAmount !== "bigint" || config.maxPacketAmount <= 0n)) {
2344
+ throw new SwapControllerError("maxPacketAmount must be a positive bigint");
2345
+ }
2346
+ if (config.minPacketAmount !== void 0 && (typeof config.minPacketAmount !== "bigint" || config.minPacketAmount <= 0n)) {
2347
+ throw new SwapControllerError("minPacketAmount must be a positive bigint");
2348
+ }
2349
+ if (config.minPacketAmount !== void 0 && config.maxPacketAmount !== void 0 && config.minPacketAmount > config.maxPacketAmount) {
2350
+ throw new SwapControllerError("minPacketAmount must be <= maxPacketAmount");
2351
+ }
2352
+ if (config.maxWindow !== void 0 && (!Number.isInteger(config.maxWindow) || config.maxWindow < 1)) {
2353
+ throw new SwapControllerError("maxWindow must be an integer >= 1");
2354
+ }
2355
+ if (config.cleanStreakLength !== void 0 && (!Number.isInteger(config.cleanStreakLength) || config.cleanStreakLength < 1)) {
2356
+ throw new SwapControllerError("cleanStreakLength must be an integer >= 1");
2357
+ }
2358
+ if (config.coldStartDivisor !== void 0 && (!Number.isInteger(config.coldStartDivisor) || config.coldStartDivisor < 1)) {
2359
+ throw new SwapControllerError("coldStartDivisor must be an integer >= 1");
2360
+ }
2361
+ if (config.ewmaAlpha !== void 0 && (typeof config.ewmaAlpha !== "number" || !(config.ewmaAlpha > 0) || config.ewmaAlpha > 1)) {
2362
+ throw new SwapControllerError("ewmaAlpha must be in (0, 1]");
2363
+ }
2364
+ }
2365
+
2000
2366
  // src/settlement/evm.ts
2001
2367
  import { secp256k1 } from "@noble/curves/secp256k1.js";
2002
2368
  import { keccak_256 } from "@noble/hashes/sha3.js";
@@ -3039,17 +3405,26 @@ function verifyAccumulatedClaim(claim, signer, minaSignerClient) {
3039
3405
  };
3040
3406
  }
3041
3407
  export {
3408
+ AdaptiveDeltaController,
3409
+ BoundedReceiptSessions,
3042
3410
  ChunkManager,
3411
+ DEFAULT_RECEIPT_SESSIONS_CAP,
3043
3412
  GiftWrapError,
3044
3413
  HandlerError,
3045
3414
  HandlerRegistry,
3046
3415
  IdentityError,
3416
+ InMemorySwapControllerStateStore,
3417
+ JsonFileSwapControllerStateStore,
3047
3418
  NodeError,
3048
3419
  PricingError,
3420
+ ReceiptChainTracker,
3421
+ STREAM_RECEIPT_SIGNING_TAG,
3422
+ STREAM_RECEIPT_VERSION,
3049
3423
  SWAP_HANDLER_REJECT_CODES,
3050
3424
  SWAP_HANDLER_REJECT_MESSAGES,
3051
3425
  SettlementTxError,
3052
3426
  StreamSwapError,
3427
+ SwapControllerError,
3053
3428
  SwapHandlerError,
3054
3429
  SwarmCoordinator,
3055
3430
  TurboUploadAdapter,
@@ -3076,6 +3451,7 @@ export {
3076
3451
  createSwapHandler,
3077
3452
  createVerificationPipeline,
3078
3453
  decryptFulfillClaim,
3454
+ encodeReceiptSigningPayload,
3079
3455
  encryptFulfillClaim,
3080
3456
  fillEvmSettlementTxGas,
3081
3457
  findSwapPair,
@@ -3085,10 +3461,17 @@ export {
3085
3461
  generateMnemonic,
3086
3462
  generateSolanaKeypair,
3087
3463
  hexToBytes2 as hexToBytes,
3464
+ isSwapControllerState,
3465
+ isValidStreamNonce,
3466
+ issueSessionReceipt,
3088
3467
  loadMinaSignerClient,
3089
3468
  minaHashToField,
3469
+ parseStreamReceipt,
3470
+ serializeReceiptChain,
3471
+ signStreamReceipt,
3090
3472
  streamSwap,
3091
3473
  streamSwapControlled,
3474
+ swapControllerStateKey,
3092
3475
  unwrapSwapPacket,
3093
3476
  unwrapSwapPacketFromToon,
3094
3477
  uploadBlob,
@@ -3096,6 +3479,7 @@ export {
3096
3479
  verifyAccumulatedClaim,
3097
3480
  verifyEd25519Signature,
3098
3481
  verifyMinaSignature,
3482
+ verifyStreamReceipt,
3099
3483
  wrapSwapPacket,
3100
3484
  wrapSwapPacketToToon
3101
3485
  };