@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/{chunk-Z6S56VPV.js → chunk-RAZRWSJF.js} +1139 -225
- package/dist/chunk-RAZRWSJF.js.map +1 -0
- package/dist/index.d.ts +50 -6
- package/dist/index.js +385 -1
- package/dist/index.js.map +1 -1
- package/dist/swap-PFQTJZA7.d.ts +1244 -0
- package/dist/swap.d.ts +1 -1
- package/dist/swap.js +21 -1
- package/package.json +2 -2
- package/dist/chunk-Z6S56VPV.js.map +0 -1
- package/dist/swap-DF4ghKio.d.ts +0 -484
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-
|
|
8
|
-
export { D as DecryptFulfillClaimParams, E as EncryptFulfillClaimParams,
|
|
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`.
|
|
1494
|
-
*
|
|
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-
|
|
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
|
};
|