openttt 0.2.11 → 0.2.12

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.
@@ -1,254 +0,0 @@
1
- "use strict";
2
- // sdk/src/dynamic_fee.ts — Dynamic Fee Engine
3
- // Automatically adjusts tick cost based on TTT market price
4
- // DEX operators only set tier; the SDK handles the rest automatically
5
- Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.DynamicFeeEngine = exports.FEE_TIERS = exports.TIER_USD_MICRO = void 0;
7
- const ethers_1 = require("ethers");
8
- const logger_1 = require("./logger");
9
- // Target USD cost per tier (Scale: 1e6)
10
- //
11
- // PRICING (2026-03-14 감사 수정):
12
- // T2/T3 가격 하향 — 감사 결과 "트랜잭션당 1틱 소비" 구조에서
13
- // 매 틱 판매 가정은 비현실적. 볼륨 기반 수익 구조로 전환.
14
- // T2: $0.24 → $0.05 (4.8x 하향)
15
- // T3: $12.00 → $0.10 (120x 하향)
16
- //
17
- // YP discrepancies (code is authoritative in all cases):
18
- // YP5: TURBO entry threshold — YP says 90%, code uses 95% (more conservative).
19
- // YP6: BOOTSTRAP mintFee — YP says 3%, code uses 5% (500 basis points).
20
- // YP7: PoT min confidence — YP says 0.7, code uses 0.5 (auto_mint.ts).
21
- exports.TIER_USD_MICRO = {
22
- T0_epoch: 1000n, // $0.001 * 1e6
23
- T1_block: 10000n, // $0.01 * 1e6
24
- T2_slot: 50000n, // $0.05 * 1e6 — 감사 수정: 볼륨 기반 수익 구조
25
- T3_micro: 100000n, // $0.10 * 1e6 — 감사 수정: 트랜잭션당 1틱 소비 기준
26
- };
27
- // Helm protocol fee tiers (Scale: 1e4, e.g., 500 = 5%)
28
- exports.FEE_TIERS = {
29
- BOOTSTRAP: { mintFee: 500n, burnFee: 200n, threshold: 5000n }, // threshold: $0.005 * 1e6
30
- GROWTH: { mintFee: 1000n, burnFee: 300n, threshold: 50000n }, // threshold: $0.05 * 1e6
31
- MATURE: { mintFee: 1000n, burnFee: 500n, threshold: 500000n }, // threshold: $0.50 * 1e6
32
- PREMIUM: { mintFee: 800n, burnFee: 500n, threshold: -1n }, // Infinity replacement
33
- };
34
- class DynamicFeeEngine {
35
- priceCache = null;
36
- provider = null;
37
- rpcUrls = [];
38
- config;
39
- warnedSpotPrice = false;
40
- // P2-3: Recommended max cache duration for DEX price freshness
41
- static RECOMMENDED_MAX_CACHE_MS = 5000;
42
- constructor(config) {
43
- // R3-P0-1: Prevent division by zero in all fee calculations
44
- if (config.fallbackPriceUsd <= 0n) {
45
- throw new Error(`[DynamicFee] fallbackPriceUsd must be > 0, got: ${config.fallbackPriceUsd}`);
46
- }
47
- this.config = config;
48
- // P2-3: Warn if cache duration is too long for DEX pricing
49
- if (config.cacheDurationMs > DynamicFeeEngine.RECOMMENDED_MAX_CACHE_MS) {
50
- logger_1.logger.warn(`[DynamicFee] cacheDurationMs=${config.cacheDurationMs}ms exceeds recommended ${DynamicFeeEngine.RECOMMENDED_MAX_CACHE_MS}ms for DEX pricing accuracy`);
51
- }
52
- }
53
- /**
54
- * Connect to an RPC provider. Accepts a single URL or an array of URLs
55
- * for multi-RPC fallback. On connection failure, the next URL is tried.
56
- */
57
- async connect(rpcUrl) {
58
- const urls = Array.isArray(rpcUrl) ? rpcUrl : [rpcUrl];
59
- if (urls.length === 0 || urls.every(u => !u)) {
60
- throw new Error("[DynamicFee] At least one valid RPC URL is required");
61
- }
62
- this.rpcUrls = urls.filter(u => !!u);
63
- await this.connectToNext();
64
- }
65
- /**
66
- * Iterate through stored RPC URLs and connect to the first one that succeeds.
67
- * Throws if all URLs fail.
68
- */
69
- async connectToNext() {
70
- let lastError = null;
71
- for (const url of this.rpcUrls) {
72
- try {
73
- const provider = new ethers_1.JsonRpcProvider(url);
74
- // Verify connectivity by requesting the network
75
- await provider.getNetwork();
76
- this.provider = provider;
77
- logger_1.logger.info(`[DynamicFee] Connected to RPC: ${url}`);
78
- return;
79
- }
80
- catch (err) {
81
- lastError = err instanceof Error ? err : new Error(String(err));
82
- logger_1.logger.warn(`[DynamicFee] RPC connection failed for ${url}: ${lastError.message}`);
83
- }
84
- }
85
- // If all URLs failed, fall back to the first URL without connectivity check
86
- // so that subsequent calls can still attempt requests
87
- this.provider = new ethers_1.JsonRpcProvider(this.rpcUrls[0]);
88
- logger_1.logger.warn(`[DynamicFee] All RPC URLs failed connectivity check, using first URL as fallback`);
89
- }
90
- async getTTTPriceUsd() {
91
- const now = Date.now();
92
- if (this.priceCache && (now - this.priceCache.timestamp) < this.config.cacheDurationMs) {
93
- return this.priceCache.price;
94
- }
95
- try {
96
- let price;
97
- // R4-P1-2: Chainlink FIRST (resistant to flash loan), Uniswap spot as fallback only
98
- if (this.config.chainlinkFeed && this.provider) {
99
- price = await this.fetchChainlinkPrice();
100
- }
101
- else if (this.config.poolAddress && this.provider) {
102
- // To suppress this warning, set `chainlinkFeed` in PriceOracleConfig to a
103
- // Chainlink AggregatorV3 address (e.g. the TTT/USD feed on your target chain).
104
- // Chainlink TWAP prices are resistant to single-block flash loan manipulation.
105
- if (!this.warnedSpotPrice) {
106
- logger_1.logger.warn("[DynamicFee] Using Uniswap spot price — vulnerable to flash loan manipulation. Configure chainlinkFeed for production.");
107
- this.warnedSpotPrice = true;
108
- }
109
- price = await this.fetchUniswapPrice();
110
- }
111
- else {
112
- price = this.config.fallbackPriceUsd;
113
- }
114
- if (price <= 0n) {
115
- logger_1.logger.warn(`[DynamicFee] Invalid price ${price}, using fallback ${this.config.fallbackPriceUsd}`);
116
- price = this.config.fallbackPriceUsd;
117
- // R3-P0-1: Double-guard — fallback validated in constructor but belt-and-suspenders
118
- if (price <= 0n)
119
- throw new Error("[DynamicFee] FATAL: fallbackPriceUsd is zero — cannot calculate fees");
120
- }
121
- this.priceCache = { price, timestamp: now };
122
- return price;
123
- }
124
- catch (error) {
125
- logger_1.logger.warn(`[DynamicFee] Price fetch failed, using fallback`);
126
- return this.config.fallbackPriceUsd;
127
- }
128
- }
129
- /**
130
- * Force-invalidate price cache -- call when immediate price refresh is needed.
131
- */
132
- invalidateCache() {
133
- this.priceCache = null;
134
- logger_1.logger.info(`[DynamicFee] Price cache invalidated`);
135
- }
136
- async fetchUniswapPrice() {
137
- if (!this.provider || !this.config.poolAddress)
138
- return this.config.fallbackPriceUsd;
139
- const stateViewAbi = ["function getSlot0(bytes32 poolId) external view returns (uint160 sqrtPriceX96, int24 tick, uint24 protocolFee, uint24 lpFee)"];
140
- try {
141
- const contract = new ethers_1.Contract(this.config.poolAddress, stateViewAbi, this.provider);
142
- const poolId = ethers_1.ethers.keccak256(ethers_1.ethers.AbiCoder.defaultAbiCoder().encode(["address"], [this.config.poolAddress]));
143
- const [sqrtPriceX96] = await contract.getSlot0(poolId);
144
- // B1-7: Unified Uniswap price scaling
145
- const priceScaled = (BigInt(sqrtPriceX96) * BigInt(sqrtPriceX96) * 1000000n) >> 192n;
146
- return priceScaled;
147
- }
148
- catch (error) {
149
- logger_1.logger.warn(`[DynamicFee] Uniswap price fetch failed, using fallback: ${error instanceof Error ? error.message : error}`);
150
- return this.config.fallbackPriceUsd;
151
- }
152
- }
153
- async fetchChainlinkPrice() {
154
- if (!this.provider || !this.config.chainlinkFeed)
155
- return this.config.fallbackPriceUsd;
156
- const feedAbi = ["function latestRoundData() external view returns (uint80 roundId, int256 answer, uint256 startedAt, uint256 updatedAt, uint80 answeredInRound)"];
157
- const MAX_PRICE = 10n ** 12n; // B1-8: Max price 10^12n
158
- try {
159
- const contract = new ethers_1.Contract(this.config.chainlinkFeed, feedAbi, this.provider);
160
- const [, answer, , updatedAt] = await contract.latestRoundData();
161
- // B1-8: Check updatedAt (within 1 hour) and price <= MAX_PRICE
162
- const now = BigInt(Math.floor(Date.now() / 1000));
163
- // P2-4: Tightened staleness check from 3600s to 1800s (30 min)
164
- if (now - BigInt(updatedAt) > 1800n) {
165
- throw new Error("Chainlink price stale (>30min)");
166
- }
167
- const price = BigInt(answer) / 100n; // 8 decimals -> 6 decimals
168
- if (price > MAX_PRICE) {
169
- throw new Error("Chainlink price exceeds MAX_PRICE");
170
- }
171
- return price;
172
- }
173
- catch (error) {
174
- logger_1.logger.warn(`[DynamicFee] Chainlink price fetch failed, using fallback: ${error instanceof Error ? error.message : error}`);
175
- return this.config.fallbackPriceUsd;
176
- }
177
- }
178
- getFeeRate(tttPriceUsd) {
179
- if (tttPriceUsd < exports.FEE_TIERS.BOOTSTRAP.threshold) {
180
- return { ...exports.FEE_TIERS.BOOTSTRAP, phase: "BOOTSTRAP" };
181
- }
182
- else if (tttPriceUsd < exports.FEE_TIERS.GROWTH.threshold) {
183
- return { ...exports.FEE_TIERS.GROWTH, phase: "GROWTH" };
184
- }
185
- else if (tttPriceUsd < exports.FEE_TIERS.MATURE.threshold) {
186
- return { ...exports.FEE_TIERS.MATURE, phase: "MATURE" };
187
- }
188
- else {
189
- return { ...exports.FEE_TIERS.PREMIUM, phase: "PREMIUM" };
190
- }
191
- }
192
- async calculateMintFee(tier, tickCount = 1, feeToken = "USDC", feeTokenAddress = "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913") {
193
- // B1-4: Throw if tickCount <= 0
194
- if (tickCount <= 0)
195
- throw new Error("[DynamicFee] tickCount must be positive");
196
- if (!exports.TIER_USD_MICRO[tier])
197
- throw new Error(`[DynamicFee] Invalid tier: ${tier}`);
198
- const tttPriceUsd = await this.getTTTPriceUsd(); // 6 decimals
199
- const usdTarget = exports.TIER_USD_MICRO[tier]; // 6 decimals
200
- const feeRate = this.getFeeRate(tttPriceUsd);
201
- const totalUsdCost = usdTarget * BigInt(tickCount); // 6 decimals
202
- // tttAmount = (totalUsdCost / tttPriceUsd) * 1e18
203
- const tttAmount = (totalUsdCost * (10n ** 18n)) / tttPriceUsd;
204
- // protocolFeeUsd = totalUsdCost * feeRate / 10000
205
- const protocolFeeUsd = (totalUsdCost * feeRate.mintFee) / 10000n;
206
- return {
207
- tttAmount,
208
- protocolFeeUsd,
209
- feeToken,
210
- feeTokenAddress,
211
- clientNet: tttAmount,
212
- tttPriceUsd,
213
- usdCost: totalUsdCost + protocolFeeUsd,
214
- feeRateMint: feeRate.mintFee,
215
- feeRateBurn: feeRate.burnFee,
216
- tier,
217
- };
218
- }
219
- async calculateBurnFee(tier, tickCount = 1, feeToken = "USDC", feeTokenAddress = "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913") {
220
- // B1-4: Throw if tickCount <= 0
221
- if (tickCount <= 0)
222
- throw new Error("[DynamicFee] tickCount must be positive");
223
- if (!exports.TIER_USD_MICRO[tier])
224
- throw new Error(`[DynamicFee] Invalid tier: ${tier}`);
225
- const tttPriceUsd = await this.getTTTPriceUsd();
226
- const usdTarget = exports.TIER_USD_MICRO[tier];
227
- const feeRate = this.getFeeRate(tttPriceUsd);
228
- const totalUsdCost = usdTarget * BigInt(tickCount);
229
- const tttAmount = (totalUsdCost * (10n ** 18n)) / tttPriceUsd;
230
- const protocolFeeUsd = (totalUsdCost * feeRate.burnFee) / 10000n;
231
- return {
232
- tttAmount,
233
- protocolFeeUsd,
234
- feeToken,
235
- feeTokenAddress,
236
- clientNet: tttAmount,
237
- tttPriceUsd,
238
- usdCost: totalUsdCost + protocolFeeUsd,
239
- feeRateMint: feeRate.mintFee,
240
- feeRateBurn: feeRate.burnFee,
241
- tier,
242
- };
243
- }
244
- async calculateEmergencyMintFee(tier, tickCount = 1) {
245
- const base = await this.calculateMintFee(tier, tickCount);
246
- const emergencyFeeUsd = (base.protocolFeeUsd * 150n) / 100n;
247
- return {
248
- ...base,
249
- protocolFeeUsd: emergencyFeeUsd,
250
- usdCost: (base.usdCost * 150n) / 100n,
251
- };
252
- }
253
- }
254
- exports.DynamicFeeEngine = DynamicFeeEngine;
@@ -1,98 +0,0 @@
1
- /**
2
- * TTTClient.httpOnly() — Zero-friction Proof of Time
3
- * No ETH, no signer, no on-chain. Just verified time.
4
- *
5
- * Usage:
6
- * const client = TTTClient.httpOnly();
7
- * const pot = await client.generatePoT();
8
- * console.log(pot.timestamp, pot.confidence);
9
- *
10
- * Time sources: NIST, Apple, Google, Cloudflare HTTPS Date headers.
11
- * HMAC-SHA256 signature — no ethers.js dependency required.
12
- */
13
- import { ProofOfTime } from "./types";
14
- export interface HttpPoT {
15
- /** Synthesized timestamp in Unix nanoseconds (bigint). */
16
- timestamp: bigint;
17
- /** Fraction of configured sources that responded (0.0–1.0). */
18
- confidence: number;
19
- /** Lowest NTP stratum observed across sources (2 for HTTPS Date headers). */
20
- stratum: number;
21
- /** Number of sources that successfully responded. */
22
- sources: number;
23
- /** Per-source readings used to compute the median. */
24
- sourceReadings: {
25
- source: string;
26
- timestamp: bigint;
27
- uncertainty: number;
28
- stratum?: number;
29
- }[];
30
- /** Replay-protection nonce (hex). */
31
- nonce: string;
32
- /** Expiry timestamp in Unix milliseconds (bigint). */
33
- expiresAt: bigint;
34
- /** HMAC-SHA256 over canonical fields, hex-encoded. */
35
- hmac: string;
36
- }
37
- export interface HttpPoTVerifyResult {
38
- valid: boolean;
39
- reason?: string;
40
- }
41
- export interface HttpOnlyClientOptions {
42
- /**
43
- * Override HMAC secret for non-sandbox usage.
44
- * Defaults to a fixed sandbox key — sufficient for local verification only.
45
- */
46
- hmacSecret?: string;
47
- /**
48
- * Per-source request timeout in ms. Default: 3000.
49
- */
50
- timeoutMs?: number;
51
- /**
52
- * PoT validity window in seconds. Default: 60.
53
- */
54
- expirySeconds?: number;
55
- /**
56
- * Maximum divergence allowed between sources in nanoseconds.
57
- * Default: 2_000_000_000n (2 seconds — lenient for HTTPS Date 1s resolution).
58
- */
59
- toleranceNs?: bigint;
60
- }
61
- /**
62
- * HttpOnlyClient — zero-dependency Proof of Time over HTTPS.
63
- *
64
- * Fetches time from NIST, Apple, Google, and Cloudflare HTTPS endpoints,
65
- * computes the median, and returns a PoT with HMAC integrity protection.
66
- *
67
- * No ETH, no signer, no on-chain interaction required.
68
- */
69
- export declare class HttpOnlyClient {
70
- private readonly hmacSecret;
71
- private readonly timeoutMs;
72
- private readonly expirySeconds;
73
- private readonly toleranceNs;
74
- private readonly usedNonces;
75
- private readonly NONCE_TTL_MS;
76
- private readonly MAX_NONCE_CACHE;
77
- constructor(options?: HttpOnlyClientOptions);
78
- /**
79
- * Fetch time from all four HTTPS sources, compute median, return PoT.
80
- * Requires at least 1 source to succeed.
81
- */
82
- generatePoT(): Promise<HttpPoT>;
83
- /**
84
- * Verify an HttpPoT:
85
- * - HMAC integrity check
86
- * - Expiry check
87
- * - Nonce replay protection
88
- * - Source divergence tolerance check
89
- *
90
- * No on-chain interaction. Pure local verification.
91
- */
92
- verifyPoT(pot: HttpPoT): HttpPoTVerifyResult;
93
- /**
94
- * Convert HttpPoT to the standard ProofOfTime shape used by the rest of the SDK.
95
- * The issuerSignature field is omitted (no on-chain signer in httpOnly mode).
96
- */
97
- static toProofOfTime(pot: HttpPoT): ProofOfTime;
98
- }
@@ -1,252 +0,0 @@
1
- "use strict";
2
- /**
3
- * TTTClient.httpOnly() — Zero-friction Proof of Time
4
- * No ETH, no signer, no on-chain. Just verified time.
5
- *
6
- * Usage:
7
- * const client = TTTClient.httpOnly();
8
- * const pot = await client.generatePoT();
9
- * console.log(pot.timestamp, pot.confidence);
10
- *
11
- * Time sources: NIST, Apple, Google, Cloudflare HTTPS Date headers.
12
- * HMAC-SHA256 signature — no ethers.js dependency required.
13
- */
14
- var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
15
- if (k2 === undefined) k2 = k;
16
- var desc = Object.getOwnPropertyDescriptor(m, k);
17
- if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
18
- desc = { enumerable: true, get: function() { return m[k]; } };
19
- }
20
- Object.defineProperty(o, k2, desc);
21
- }) : (function(o, m, k, k2) {
22
- if (k2 === undefined) k2 = k;
23
- o[k2] = m[k];
24
- }));
25
- var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
26
- Object.defineProperty(o, "default", { enumerable: true, value: v });
27
- }) : function(o, v) {
28
- o["default"] = v;
29
- });
30
- var __importStar = (this && this.__importStar) || (function () {
31
- var ownKeys = function(o) {
32
- ownKeys = Object.getOwnPropertyNames || function (o) {
33
- var ar = [];
34
- for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
35
- return ar;
36
- };
37
- return ownKeys(o);
38
- };
39
- return function (mod) {
40
- if (mod && mod.__esModule) return mod;
41
- var result = {};
42
- if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
43
- __setModuleDefault(result, mod);
44
- return result;
45
- };
46
- })();
47
- Object.defineProperty(exports, "__esModule", { value: true });
48
- exports.HttpOnlyClient = void 0;
49
- const crypto = __importStar(require("crypto"));
50
- const https = __importStar(require("https"));
51
- const SOURCES = [
52
- { name: "nist", url: "https://time.nist.gov/" },
53
- { name: "apple", url: "https://time.apple.com/" },
54
- { name: "google", url: "https://time.google.com/" },
55
- { name: "cloudflare", url: "https://time.cloudflare.com/" },
56
- ];
57
- function fetchHttpsDate(name, url, timeoutMs = 3000) {
58
- return new Promise((resolve, reject) => {
59
- const t1 = BigInt(Date.now()) * 1000000n;
60
- const timer = setTimeout(() => {
61
- req.destroy();
62
- reject(new Error(`[httpOnly] Timeout: ${name} (${timeoutMs}ms)`));
63
- }, timeoutMs);
64
- const req = https.request(url, { method: "HEAD" }, (res) => {
65
- clearTimeout(timer);
66
- res.resume(); // drain so socket is freed
67
- const t4 = BigInt(Date.now()) * 1000000n;
68
- const dateHeader = res.headers["date"];
69
- if (!dateHeader) {
70
- reject(new Error(`[httpOnly] No Date header from ${name}`));
71
- return;
72
- }
73
- const parsed = new Date(dateHeader).getTime();
74
- if (isNaN(parsed)) {
75
- reject(new Error(`[httpOnly] Invalid Date header from ${name}: ${dateHeader}`));
76
- return;
77
- }
78
- const serverNs = BigInt(parsed) * 1000000n;
79
- const rttNs = t4 - t1;
80
- // Offset-corrected: server time + half RTT
81
- const corrected = serverNs + rttNs / 2n;
82
- const rttMs = Number(rttNs) / 1_000_000;
83
- resolve({
84
- source: name,
85
- timestamp: corrected,
86
- uncertainty: rttMs / 2 + 500, // 500ms base — HTTP Date has 1s resolution
87
- stratum: 2,
88
- });
89
- });
90
- req.on("error", (err) => {
91
- clearTimeout(timer);
92
- reject(new Error(`[httpOnly] Request error for ${name}: ${err.message}`));
93
- });
94
- req.end();
95
- });
96
- }
97
- // ---------------------------------------------------------------------------
98
- // HMAC helpers — default sandbox key derived from fixed chain+address strings
99
- // ---------------------------------------------------------------------------
100
- const SANDBOX_HMAC_SECRET = "openttt-sandbox:chainId=0:address=0x0000000000000000000000000000000000000000";
101
- function computeHmac(timestamp, nonce, expiresAt, sources, secret = SANDBOX_HMAC_SECRET) {
102
- // Canonical message: deterministic field concatenation
103
- const msg = `${timestamp.toString()}:${nonce}:${expiresAt.toString()}:${sources}`;
104
- return crypto.createHmac("sha256", secret).update(msg).digest("hex");
105
- }
106
- /**
107
- * HttpOnlyClient — zero-dependency Proof of Time over HTTPS.
108
- *
109
- * Fetches time from NIST, Apple, Google, and Cloudflare HTTPS endpoints,
110
- * computes the median, and returns a PoT with HMAC integrity protection.
111
- *
112
- * No ETH, no signer, no on-chain interaction required.
113
- */
114
- class HttpOnlyClient {
115
- hmacSecret;
116
- timeoutMs;
117
- expirySeconds;
118
- toleranceNs;
119
- usedNonces = new Map();
120
- NONCE_TTL_MS = 300_000; // 5 min
121
- MAX_NONCE_CACHE = 10_000;
122
- constructor(options = {}) {
123
- this.hmacSecret = options.hmacSecret ?? SANDBOX_HMAC_SECRET;
124
- this.timeoutMs = options.timeoutMs ?? 3000;
125
- this.expirySeconds = options.expirySeconds ?? 60;
126
- this.toleranceNs = options.toleranceNs ?? 2000000000n; // 2 s
127
- }
128
- /**
129
- * Fetch time from all four HTTPS sources, compute median, return PoT.
130
- * Requires at least 1 source to succeed.
131
- */
132
- async generatePoT() {
133
- const results = await Promise.allSettled(SOURCES.map((s) => fetchHttpsDate(s.name, s.url, this.timeoutMs)));
134
- const readings = [];
135
- for (const r of results) {
136
- if (r.status === "fulfilled")
137
- readings.push(r.value);
138
- }
139
- if (readings.length === 0) {
140
- throw new Error("[httpOnly] All HTTPS time sources failed. Check network connectivity.");
141
- }
142
- // Sort by timestamp for median selection
143
- readings.sort((a, b) => (a.timestamp < b.timestamp ? -1 : a.timestamp > b.timestamp ? 1 : 0));
144
- let finalTs;
145
- let finalUnc;
146
- let finalStratum;
147
- if (readings.length === 1) {
148
- finalTs = readings[0].timestamp;
149
- finalUnc = readings[0].uncertainty;
150
- finalStratum = readings[0].stratum;
151
- }
152
- else if (readings.length === 2) {
153
- finalTs = (readings[0].timestamp + readings[1].timestamp) / 2n;
154
- finalUnc = (readings[0].uncertainty + readings[1].uncertainty) / 2;
155
- finalStratum = Math.min(readings[0].stratum, readings[1].stratum);
156
- }
157
- else {
158
- const mid = Math.floor(readings.length / 2);
159
- finalTs = readings[mid].timestamp;
160
- finalUnc = readings[mid].uncertainty;
161
- finalStratum = readings[mid].stratum;
162
- }
163
- const nonce = crypto.randomBytes(16).toString("hex");
164
- const expiresAt = BigInt(Date.now()) + BigInt(this.expirySeconds * 1000);
165
- const sourceReadings = readings.map((r) => ({
166
- source: r.source,
167
- timestamp: r.timestamp,
168
- uncertainty: r.uncertainty,
169
- stratum: r.stratum,
170
- }));
171
- const hmac = computeHmac(finalTs, nonce, expiresAt, readings.length, this.hmacSecret);
172
- return {
173
- timestamp: finalTs,
174
- confidence: readings.length / SOURCES.length,
175
- stratum: finalStratum,
176
- sources: readings.length,
177
- sourceReadings,
178
- nonce,
179
- expiresAt,
180
- hmac,
181
- };
182
- }
183
- /**
184
- * Verify an HttpPoT:
185
- * - HMAC integrity check
186
- * - Expiry check
187
- * - Nonce replay protection
188
- * - Source divergence tolerance check
189
- *
190
- * No on-chain interaction. Pure local verification.
191
- */
192
- verifyPoT(pot) {
193
- // 1. Expiry
194
- if (BigInt(Date.now()) > pot.expiresAt) {
195
- return { valid: false, reason: "PoT expired" };
196
- }
197
- // 2. HMAC integrity
198
- const expected = computeHmac(pot.timestamp, pot.nonce, pot.expiresAt, pot.sources, this.hmacSecret);
199
- if (!crypto.timingSafeEqual(Buffer.from(expected, "hex"), Buffer.from(pot.hmac, "hex"))) {
200
- return { valid: false, reason: "HMAC mismatch — PoT may have been tampered" };
201
- }
202
- // 3. Nonce replay (bounded cache + TTL)
203
- const now = Date.now();
204
- if (this.usedNonces.size > this.MAX_NONCE_CACHE / 2) {
205
- for (const [k, ts] of this.usedNonces) {
206
- if (now - ts > this.NONCE_TTL_MS)
207
- this.usedNonces.delete(k);
208
- }
209
- }
210
- if (this.usedNonces.has(pot.nonce)) {
211
- return { valid: false, reason: "Duplicate nonce — replay detected" };
212
- }
213
- if (this.usedNonces.size >= this.MAX_NONCE_CACHE) {
214
- const oldest = this.usedNonces.keys().next().value;
215
- if (oldest !== undefined)
216
- this.usedNonces.delete(oldest);
217
- }
218
- this.usedNonces.set(pot.nonce, now);
219
- // 4. Source divergence
220
- for (const reading of pot.sourceReadings) {
221
- const diff = reading.timestamp > pot.timestamp
222
- ? reading.timestamp - pot.timestamp
223
- : pot.timestamp - reading.timestamp;
224
- if (diff > this.toleranceNs) {
225
- return {
226
- valid: false,
227
- reason: `Source ${reading.source} diverges by ${diff}ns (tolerance: ${this.toleranceNs}ns)`,
228
- };
229
- }
230
- }
231
- return { valid: true };
232
- }
233
- /**
234
- * Convert HttpPoT to the standard ProofOfTime shape used by the rest of the SDK.
235
- * The issuerSignature field is omitted (no on-chain signer in httpOnly mode).
236
- */
237
- static toProofOfTime(pot) {
238
- return {
239
- timestamp: pot.timestamp,
240
- uncertainty: pot.sourceReadings.length > 0
241
- ? pot.sourceReadings.reduce((sum, r) => sum + r.uncertainty, 0) / pot.sourceReadings.length
242
- : 500,
243
- sources: pot.sources,
244
- stratum: pot.stratum,
245
- confidence: pot.confidence,
246
- sourceReadings: pot.sourceReadings,
247
- nonce: pot.nonce,
248
- expiresAt: pot.expiresAt,
249
- };
250
- }
251
- }
252
- exports.HttpOnlyClient = HttpOnlyClient;
@@ -1,41 +0,0 @@
1
- /**
2
- * Integrity Client — replaces local integrity computation with server-side API call.
3
- * Core pipeline source code stays in Helm private repo. Only API calls go through npm SDK.
4
- *
5
- * Drop-in replacement interface for local encode() and verify() operations.
6
- */
7
- export interface IntegrityEncodeResult {
8
- /** Hex-encoded serialized shards (joined as JSON array of hex strings) */
9
- shards: string[];
10
- }
11
- export interface IntegrityVerifyResult {
12
- valid: boolean;
13
- }
14
- export declare class IntegrityClient {
15
- private baseUrl;
16
- private timeoutMs;
17
- private apiKey;
18
- constructor(baseUrl?: string, options?: {
19
- timeoutMs?: number;
20
- apiKey?: string;
21
- });
22
- /**
23
- * Forward pass: encode data through integrity pipeline (server-side).
24
- *
25
- * @returns Array of Uint8Array shards
26
- */
27
- encode(data: Uint8Array, chainId: number, poolAddress: string): Promise<Uint8Array[]>;
28
- /**
29
- * Verify: check data integrity (server-side).
30
- *
31
- * @returns boolean — true if data matches the original shards
32
- */
33
- verify(data: Uint8Array, originalShards: Uint8Array[], chainId: number, poolAddress: string): Promise<boolean>;
34
- /**
35
- * Health check — ping the integrity API server.
36
- * Returns true if reachable within timeoutMs.
37
- */
38
- isReachable(): Promise<boolean>;
39
- }
40
- export declare function getDefaultIntegrityClient(): IntegrityClient;
41
- export declare function setDefaultIntegrityClient(client: IntegrityClient): void;