openttt 0.1.2 → 0.1.3
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/README.md +4 -4
- package/dist/adaptive_switch.d.ts +22 -7
- package/dist/adaptive_switch.js +52 -15
- package/dist/auto_mint.d.ts +22 -7
- package/dist/auto_mint.js +107 -30
- package/dist/ct_log.d.ts +47 -0
- package/dist/ct_log.js +107 -0
- package/dist/dynamic_fee.d.ts +13 -2
- package/dist/dynamic_fee.js +62 -11
- package/dist/errors.d.ts +44 -25
- package/dist/errors.js +58 -42
- package/dist/evm_connector.d.ts +28 -1
- package/dist/evm_connector.js +124 -32
- package/dist/index.d.ts +4 -5
- package/dist/index.js +4 -5
- package/dist/logger.d.ts +36 -4
- package/dist/logger.js +70 -11
- package/dist/networks.d.ts +21 -0
- package/dist/networks.js +30 -4
- package/dist/pool_registry.d.ts +9 -0
- package/dist/pool_registry.js +37 -0
- package/dist/pot_signer.d.ts +15 -0
- package/dist/pot_signer.js +28 -0
- package/dist/protocol_fee.d.ts +42 -26
- package/dist/protocol_fee.js +77 -54
- package/dist/revenue_tiers.d.ts +36 -0
- package/dist/revenue_tiers.js +83 -0
- package/dist/signer.d.ts +1 -2
- package/dist/signer.js +72 -14
- package/dist/time_synthesis.d.ts +38 -0
- package/dist/time_synthesis.js +134 -20
- package/dist/trust_store.d.ts +49 -0
- package/dist/trust_store.js +89 -0
- package/dist/ttt_builder.d.ts +1 -1
- package/dist/ttt_builder.js +2 -2
- package/dist/ttt_client.d.ts +24 -29
- package/dist/ttt_client.js +97 -28
- package/dist/types.d.ts +46 -3
- package/dist/v4_hook.d.ts +10 -2
- package/dist/v4_hook.js +10 -2
- package/dist/x402_enforcer.d.ts +17 -2
- package/dist/x402_enforcer.js +27 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -27,7 +27,7 @@ Current MEV protection relies on **trust**: builders promise fair ordering, prot
|
|
|
27
27
|
| **Mechanism** | Social contract (request) | Physical verification (proof) |
|
|
28
28
|
| **Enforcement** | Reputation, exclusion | Economic natural selection |
|
|
29
29
|
| **Bad actors** | Must be identified and removed | Naturally unprofitable, self-selecting out |
|
|
30
|
-
| **Time source** | Block timestamp (miner-controlled) | Multi-source NTP synthesis (NIST,
|
|
30
|
+
| **Time source** | Block timestamp (miner-controlled) | Multi-source NTP synthesis (NIST, Google, Apple) |
|
|
31
31
|
|
|
32
32
|
**The core insight**: Rollups generate precise timestamps and deliver them to builders with a receipt. The Adaptive GRG pipeline then verifies whether the builder respected that ordering:
|
|
33
33
|
|
|
@@ -95,7 +95,7 @@ const ttt = await TTTClient.create({
|
|
|
95
95
|
tier: "T1_block",
|
|
96
96
|
contractAddress: "0x...",
|
|
97
97
|
poolAddress: "0x...",
|
|
98
|
-
timeSources: ["nist", "
|
|
98
|
+
timeSources: ["nist", "google", "cloudflare", "apple"],
|
|
99
99
|
protocolFeeRate: 0.05,
|
|
100
100
|
enableGracefulShutdown: true,
|
|
101
101
|
});
|
|
@@ -264,7 +264,7 @@ const ttt = await TTTClient.create({
|
|
|
264
264
|
```
|
|
265
265
|
TTTClient (entry point)
|
|
266
266
|
|-- AutoMintEngine Periodic minting loop
|
|
267
|
-
| |-- TimeSynthesis NTP multi-source median synthesis (NIST,
|
|
267
|
+
| |-- TimeSynthesis NTP multi-source median synthesis (NIST, Google, Apple)
|
|
268
268
|
| |-- DynamicFeeEngine Oracle-based pricing
|
|
269
269
|
| |-- EVMConnector On-chain mint/burn/events (ethers v6)
|
|
270
270
|
| '-- ProtocolFee EIP-712 signed fee collection
|
|
@@ -311,7 +311,7 @@ This asymmetry is deliberate: it is hard to earn trust and easy to lose it.
|
|
|
311
311
|
OpenTTT queries multiple atomic clock-synchronized NTP sources in parallel and produces a median-synthesized timestamp with confidence scoring:
|
|
312
312
|
|
|
313
313
|
- **NIST** (time.nist.gov) -- US national standard
|
|
314
|
-
- **
|
|
314
|
+
- **Apple** (time.apple.com) -- Apple global time service
|
|
315
315
|
- **Google** (time.google.com) -- Leap-smeared public NTP
|
|
316
316
|
|
|
317
317
|
All readings must fall within a stratum-dependent tolerance of the synthesized median (10ms for stratum 1, 25ms for stratum 2, 50ms for stratum 3+), or the Proof of Time is rejected. Single-source operation triggers a degraded-confidence warning.
|
|
@@ -12,6 +12,8 @@ export interface Block {
|
|
|
12
12
|
txs: string[];
|
|
13
13
|
data: Uint8Array;
|
|
14
14
|
}
|
|
15
|
+
/** Tier-based dynamic tolerance (ms) — auditor-requested upgrade */
|
|
16
|
+
export declare const TIER_TOLERANCE_MS: Record<string, number>;
|
|
15
17
|
export declare class AdaptiveSwitch {
|
|
16
18
|
private windowSize;
|
|
17
19
|
private threshold;
|
|
@@ -22,23 +24,36 @@ export declare class AdaptiveSwitch {
|
|
|
22
24
|
private consecutiveFailures;
|
|
23
25
|
private turboEntryThreshold;
|
|
24
26
|
private turboMaintainThreshold;
|
|
27
|
+
private tolerance;
|
|
28
|
+
constructor(options?: {
|
|
29
|
+
tolerance?: number;
|
|
30
|
+
});
|
|
25
31
|
/**
|
|
26
|
-
* TTT
|
|
32
|
+
* Core TTT mechanism: switches between Turbo/Full mode based on timestamp ordering match rate.
|
|
27
33
|
*/
|
|
28
|
-
verifyBlock(block: Block, tttRecord: TTTRecord): AdaptiveMode;
|
|
34
|
+
verifyBlock(block: Block, tttRecord: TTTRecord, chainId: number, poolAddress: string, tier?: string): AdaptiveMode;
|
|
29
35
|
/**
|
|
30
|
-
*
|
|
31
|
-
* TURBO: 20%
|
|
32
|
-
* FULL:
|
|
36
|
+
* Return fee discount rate based on current mode.
|
|
37
|
+
* TURBO: 20% discount (incentivizes profitability).
|
|
38
|
+
* FULL: No discount.
|
|
33
39
|
*/
|
|
34
40
|
getFeeDiscount(): number;
|
|
35
41
|
/**
|
|
36
|
-
*
|
|
42
|
+
* Get current adaptive mode.
|
|
37
43
|
*/
|
|
38
44
|
getCurrentMode(): AdaptiveMode;
|
|
39
45
|
/**
|
|
40
|
-
*
|
|
46
|
+
* Reset history (for testing).
|
|
41
47
|
*/
|
|
42
48
|
reset(): void;
|
|
49
|
+
/**
|
|
50
|
+
* Serialize internal state to JSON for persistence across restarts.
|
|
51
|
+
* Allows operators to avoid re-learning over 20 blocks after a restart.
|
|
52
|
+
*/
|
|
53
|
+
serialize(): string;
|
|
54
|
+
/**
|
|
55
|
+
* Reconstruct an AdaptiveSwitch from previously serialized JSON state.
|
|
56
|
+
*/
|
|
57
|
+
static deserialize(json: string): AdaptiveSwitch;
|
|
43
58
|
private compareTransactionOrder;
|
|
44
59
|
}
|
package/dist/adaptive_switch.js
CHANGED
|
@@ -2,15 +2,22 @@
|
|
|
2
2
|
// sdk/src/adaptive_switch.ts — Adaptive Mode Switcher
|
|
3
3
|
// Turbo (50ms) vs Full (127ms)
|
|
4
4
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
5
|
-
exports.AdaptiveSwitch = exports.AdaptiveMode = void 0;
|
|
6
|
-
const
|
|
5
|
+
exports.AdaptiveSwitch = exports.TIER_TOLERANCE_MS = exports.AdaptiveMode = void 0;
|
|
6
|
+
const helm_crypto_1 = require("../vendor/helm-crypto");
|
|
7
7
|
const logger_1 = require("./logger");
|
|
8
8
|
var AdaptiveMode;
|
|
9
9
|
(function (AdaptiveMode) {
|
|
10
10
|
AdaptiveMode["TURBO"] = "TURBO";
|
|
11
11
|
AdaptiveMode["FULL"] = "FULL";
|
|
12
12
|
})(AdaptiveMode || (exports.AdaptiveMode = AdaptiveMode = {}));
|
|
13
|
-
const TOLERANCE = 100; // 100ms tolerance for KTSat sync
|
|
13
|
+
// const TOLERANCE = 100; // 100ms tolerance for KTSat sync (now configurable via constructor)
|
|
14
|
+
/** Tier-based dynamic tolerance (ms) — auditor-requested upgrade */
|
|
15
|
+
exports.TIER_TOLERANCE_MS = {
|
|
16
|
+
T0_epoch: 2000, // 6.4min tick → 2s tolerance
|
|
17
|
+
T1_block: 200, // 2s tick → 200ms
|
|
18
|
+
T2_slot: 500, // 12s tick → 500ms
|
|
19
|
+
T3_micro: 10, // 100ms tick → 10ms (10%)
|
|
20
|
+
};
|
|
14
21
|
class AdaptiveSwitch {
|
|
15
22
|
windowSize = 20; // B1-9: Updated from 10 to 20
|
|
16
23
|
threshold = 0.9; // B1-9: Updated from 0.8 to 0.9
|
|
@@ -21,17 +28,22 @@ class AdaptiveSwitch {
|
|
|
21
28
|
consecutiveFailures = 0; // P2-1: Track consecutive failures for exponential backoff
|
|
22
29
|
turboEntryThreshold = 0.95; // P2-2: Hysteresis — stricter entry
|
|
23
30
|
turboMaintainThreshold = 0.85; // P2-2: Hysteresis — relaxed maintenance
|
|
31
|
+
tolerance;
|
|
32
|
+
constructor(options) {
|
|
33
|
+
this.tolerance = options?.tolerance ?? 100;
|
|
34
|
+
}
|
|
24
35
|
/**
|
|
25
|
-
* TTT
|
|
36
|
+
* Core TTT mechanism: switches between Turbo/Full mode based on timestamp ordering match rate.
|
|
26
37
|
*/
|
|
27
|
-
verifyBlock(block, tttRecord) {
|
|
28
|
-
// 1.
|
|
38
|
+
verifyBlock(block, tttRecord, chainId, poolAddress, tier) {
|
|
39
|
+
// 1. Check timestamp ordering and time match
|
|
29
40
|
const orderMatch = this.compareTransactionOrder(block.txs, tttRecord.txOrder);
|
|
30
|
-
const
|
|
41
|
+
const tolerance = tier ? (exports.TIER_TOLERANCE_MS[tier] ?? this.tolerance) : this.tolerance;
|
|
42
|
+
const timeMatch = Math.abs(block.timestamp - tttRecord.time) < tolerance;
|
|
31
43
|
let sequenceOk = orderMatch && timeMatch;
|
|
32
44
|
// B1-1: Do not skip GrgInverse.verify() in TURBO mode
|
|
33
45
|
// We check integrity regardless of mode
|
|
34
|
-
const integrityOk =
|
|
46
|
+
const integrityOk = helm_crypto_1.GrgInverse.verify(block.data, tttRecord.grgPayload, chainId, poolAddress);
|
|
35
47
|
if (!integrityOk) {
|
|
36
48
|
logger_1.logger.error(`[AdaptiveSwitch] GRG integrity check FAILED`);
|
|
37
49
|
sequenceOk = false; // Mark as false if integrity fails
|
|
@@ -42,7 +54,7 @@ class AdaptiveSwitch {
|
|
|
42
54
|
this.penaltyCooldown = 20 * Math.pow(2, this.consecutiveFailures - 1); // 20, 40, 80, 160, 320
|
|
43
55
|
}
|
|
44
56
|
}
|
|
45
|
-
// 2.
|
|
57
|
+
// 2. Update history (Sliding Window)
|
|
46
58
|
this.history.push(sequenceOk);
|
|
47
59
|
if (this.history.length > this.windowSize) {
|
|
48
60
|
this.history.shift();
|
|
@@ -50,7 +62,7 @@ class AdaptiveSwitch {
|
|
|
50
62
|
if (this.penaltyCooldown > 0) {
|
|
51
63
|
this.penaltyCooldown--;
|
|
52
64
|
}
|
|
53
|
-
// 3.
|
|
65
|
+
// 3. Calculate match rate and switch mode
|
|
54
66
|
const matchCount = this.history.filter(h => h).length;
|
|
55
67
|
const matchRate = this.history.length > 0 ? matchCount / this.history.length : 0;
|
|
56
68
|
// P2-2: Hysteresis — different thresholds for entering vs maintaining TURBO
|
|
@@ -73,21 +85,21 @@ class AdaptiveSwitch {
|
|
|
73
85
|
return this.currentMode;
|
|
74
86
|
}
|
|
75
87
|
/**
|
|
76
|
-
*
|
|
77
|
-
* TURBO: 20%
|
|
78
|
-
* FULL:
|
|
88
|
+
* Return fee discount rate based on current mode.
|
|
89
|
+
* TURBO: 20% discount (incentivizes profitability).
|
|
90
|
+
* FULL: No discount.
|
|
79
91
|
*/
|
|
80
92
|
getFeeDiscount() {
|
|
81
93
|
return this.currentMode === AdaptiveMode.TURBO ? 0.2 : 0.0;
|
|
82
94
|
}
|
|
83
95
|
/**
|
|
84
|
-
*
|
|
96
|
+
* Get current adaptive mode.
|
|
85
97
|
*/
|
|
86
98
|
getCurrentMode() {
|
|
87
99
|
return this.currentMode;
|
|
88
100
|
}
|
|
89
101
|
/**
|
|
90
|
-
*
|
|
102
|
+
* Reset history (for testing).
|
|
91
103
|
*/
|
|
92
104
|
reset() {
|
|
93
105
|
this.history = [];
|
|
@@ -95,6 +107,31 @@ class AdaptiveSwitch {
|
|
|
95
107
|
this.penaltyCooldown = 0;
|
|
96
108
|
this.consecutiveFailures = 0;
|
|
97
109
|
}
|
|
110
|
+
/**
|
|
111
|
+
* Serialize internal state to JSON for persistence across restarts.
|
|
112
|
+
* Allows operators to avoid re-learning over 20 blocks after a restart.
|
|
113
|
+
*/
|
|
114
|
+
serialize() {
|
|
115
|
+
return JSON.stringify({
|
|
116
|
+
history: this.history,
|
|
117
|
+
currentMode: this.currentMode,
|
|
118
|
+
consecutiveFailures: this.consecutiveFailures,
|
|
119
|
+
penaltyCooldown: this.penaltyCooldown,
|
|
120
|
+
tolerance: this.tolerance,
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* Reconstruct an AdaptiveSwitch from previously serialized JSON state.
|
|
125
|
+
*/
|
|
126
|
+
static deserialize(json) {
|
|
127
|
+
const data = JSON.parse(json);
|
|
128
|
+
const instance = new AdaptiveSwitch({ tolerance: data.tolerance ?? 100 });
|
|
129
|
+
instance.history = data.history;
|
|
130
|
+
instance.currentMode = data.currentMode;
|
|
131
|
+
instance.consecutiveFailures = data.consecutiveFailures;
|
|
132
|
+
instance.penaltyCooldown = data.penaltyCooldown;
|
|
133
|
+
return instance;
|
|
134
|
+
}
|
|
98
135
|
compareTransactionOrder(blockTxs, expectedOrder) {
|
|
99
136
|
if (blockTxs.length !== expectedOrder.length)
|
|
100
137
|
return false;
|
package/dist/auto_mint.d.ts
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
|
+
import { TimeSynthesis } from "./time_synthesis";
|
|
1
2
|
import { EVMConnector } from "./evm_connector";
|
|
2
3
|
import { AutoMintConfig, MintResult } from "./types";
|
|
3
4
|
/**
|
|
4
|
-
* AutoMintEngine - TTT
|
|
5
|
-
*
|
|
5
|
+
* AutoMintEngine - Automatic TTT minting engine.
|
|
6
|
+
* Combines time synthesis, dynamic fee calculation, and EVM minting into a single loop.
|
|
6
7
|
*/
|
|
7
8
|
export declare class AutoMintEngine {
|
|
8
9
|
private config;
|
|
@@ -20,26 +21,40 @@ export declare class AutoMintEngine {
|
|
|
20
21
|
private consecutiveFailures;
|
|
21
22
|
private maxConsecutiveFailures;
|
|
22
23
|
private potSigner;
|
|
24
|
+
/** Monotonic counter appended to tokenId hash to prevent collision when two mints share the same nanosecond timestamp. */
|
|
25
|
+
private mintNonce;
|
|
26
|
+
/** Fire the GRG >50ms performance warning at most once per engine session. */
|
|
27
|
+
private warnedGrgSlow;
|
|
23
28
|
constructor(config: AutoMintConfig);
|
|
24
29
|
getEvmConnector(): EVMConnector;
|
|
30
|
+
getTimeSynthesis(): TimeSynthesis;
|
|
25
31
|
setOnMint(callback: (result: MintResult) => void): void;
|
|
26
32
|
setOnFailure(callback: (error: Error) => void): void;
|
|
27
33
|
setOnLatency(callback: (ms: number) => void): void;
|
|
28
34
|
/**
|
|
29
|
-
*
|
|
35
|
+
* Initialize the engine (RPC connection and contract setup).
|
|
30
36
|
*/
|
|
31
37
|
initialize(): Promise<void>;
|
|
32
38
|
/**
|
|
33
|
-
*
|
|
39
|
+
* Start the automatic minting loop.
|
|
34
40
|
*/
|
|
35
41
|
start(): void;
|
|
36
42
|
/**
|
|
37
|
-
*
|
|
43
|
+
* Stop the automatic minting loop.
|
|
38
44
|
*/
|
|
39
45
|
stop(): void;
|
|
40
46
|
/**
|
|
41
|
-
*
|
|
42
|
-
*
|
|
47
|
+
* Resume the minting loop after a circuit breaker trip.
|
|
48
|
+
* Resets the consecutive failure counter and restarts the loop.
|
|
49
|
+
*/
|
|
50
|
+
resume(): void;
|
|
51
|
+
/**
|
|
52
|
+
* Sleep helper for retry backoff.
|
|
53
|
+
*/
|
|
54
|
+
private sleep;
|
|
55
|
+
/**
|
|
56
|
+
* Execute a single mint tick.
|
|
57
|
+
* Time synthesis -> tokenId generation -> EVM mint call -> fee calculation/deduction.
|
|
43
58
|
*/
|
|
44
59
|
mintTick(): Promise<void>;
|
|
45
60
|
private signFeeMessage;
|
package/dist/auto_mint.js
CHANGED
|
@@ -2,18 +2,22 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.AutoMintEngine = void 0;
|
|
4
4
|
const ethers_1 = require("ethers");
|
|
5
|
-
const crypto_1 = require("crypto");
|
|
6
5
|
const time_synthesis_1 = require("./time_synthesis");
|
|
7
6
|
const dynamic_fee_1 = require("./dynamic_fee");
|
|
8
7
|
const evm_connector_1 = require("./evm_connector");
|
|
9
8
|
const protocol_fee_1 = require("./protocol_fee");
|
|
10
9
|
const pot_signer_1 = require("./pot_signer");
|
|
10
|
+
const helm_crypto_1 = require("../vendor/helm-crypto");
|
|
11
11
|
const types_1 = require("./types");
|
|
12
12
|
const logger_1 = require("./logger");
|
|
13
13
|
const errors_1 = require("./errors");
|
|
14
|
+
/** Maximum retry attempts for RPC-dependent operations within a single tick */
|
|
15
|
+
const MINT_TICK_MAX_RETRIES = 3;
|
|
16
|
+
/** Backoff durations in ms for each retry attempt (1s, 2s, 4s) */
|
|
17
|
+
const MINT_TICK_BACKOFF_MS = [1000, 2000, 4000];
|
|
14
18
|
/**
|
|
15
|
-
* AutoMintEngine - TTT
|
|
16
|
-
*
|
|
19
|
+
* AutoMintEngine - Automatic TTT minting engine.
|
|
20
|
+
* Combines time synthesis, dynamic fee calculation, and EVM minting into a single loop.
|
|
17
21
|
*/
|
|
18
22
|
class AutoMintEngine {
|
|
19
23
|
config;
|
|
@@ -31,6 +35,10 @@ class AutoMintEngine {
|
|
|
31
35
|
consecutiveFailures = 0;
|
|
32
36
|
maxConsecutiveFailures = 5;
|
|
33
37
|
potSigner = null;
|
|
38
|
+
/** Monotonic counter appended to tokenId hash to prevent collision when two mints share the same nanosecond timestamp. */
|
|
39
|
+
mintNonce = BigInt(Date.now());
|
|
40
|
+
/** Fire the GRG >50ms performance warning at most once per engine session. */
|
|
41
|
+
warnedGrgSlow = false;
|
|
34
42
|
constructor(config) {
|
|
35
43
|
this.config = config;
|
|
36
44
|
this.timeSynthesis = new time_synthesis_1.TimeSynthesis({ sources: config.timeSources });
|
|
@@ -43,11 +51,16 @@ class AutoMintEngine {
|
|
|
43
51
|
this.cachedSigner = config.signer;
|
|
44
52
|
}
|
|
45
53
|
// Initialize Ed25519 PoT signer for non-repudiation
|
|
46
|
-
this.potSigner =
|
|
54
|
+
this.potSigner = config.potSignerKeyPath
|
|
55
|
+
? pot_signer_1.PotSigner.createOrLoad(config.potSignerKeyPath)
|
|
56
|
+
: new pot_signer_1.PotSigner();
|
|
47
57
|
}
|
|
48
58
|
getEvmConnector() {
|
|
49
59
|
return this.evmConnector;
|
|
50
60
|
}
|
|
61
|
+
getTimeSynthesis() {
|
|
62
|
+
return this.timeSynthesis;
|
|
63
|
+
}
|
|
51
64
|
setOnMint(callback) {
|
|
52
65
|
this.onMintCallback = callback;
|
|
53
66
|
}
|
|
@@ -58,13 +71,13 @@ class AutoMintEngine {
|
|
|
58
71
|
this.onLatencyCallback = callback;
|
|
59
72
|
}
|
|
60
73
|
/**
|
|
61
|
-
*
|
|
74
|
+
* Initialize the engine (RPC connection and contract setup).
|
|
62
75
|
*/
|
|
63
76
|
async initialize() {
|
|
64
77
|
try {
|
|
65
78
|
const signerOrKey = this.config.signer || this.config.privateKey;
|
|
66
79
|
if (!signerOrKey)
|
|
67
|
-
throw new errors_1.TTTConfigError("[AutoMint] Signer or Private Key is required", "Missing both 'signer' and 'privateKey' in config", "Provide a valid ethers.Signer or a private key string in your configuration.");
|
|
80
|
+
throw new errors_1.TTTConfigError(errors_1.ERROR_CODES.CONFIG_MISSING_SIGNER, "[AutoMint] Signer or Private Key is required", "Missing both 'signer' and 'privateKey' in config", "Provide a valid ethers.Signer or a private key string in your configuration.");
|
|
68
81
|
await this.evmConnector.connect(this.config.rpcUrl, signerOrKey);
|
|
69
82
|
await this.feeEngine.connect(this.config.rpcUrl);
|
|
70
83
|
this.cachedSigner = this.evmConnector.getSigner();
|
|
@@ -73,7 +86,9 @@ class AutoMintEngine {
|
|
|
73
86
|
"function burn(uint256 amount, bytes32 grgHash, uint256 tier) external",
|
|
74
87
|
"function balanceOf(address account, uint256 id) external view returns (uint256)",
|
|
75
88
|
"event TTTMinted(address indexed to, uint256 indexed tokenId, uint256 amount)",
|
|
76
|
-
"event TTTBurned(address indexed from, uint256 indexed tokenId, uint256 amount, uint256 tier)"
|
|
89
|
+
"event TTTBurned(address indexed from, uint256 indexed tokenId, uint256 amount, uint256 tier)",
|
|
90
|
+
// CT Log equivalent: every PoT is publicly auditable on-chain
|
|
91
|
+
evm_connector_1.EVMConnector.POT_ANCHORED_EVENT_ABI
|
|
77
92
|
];
|
|
78
93
|
this.evmConnector.attachContract(this.config.contractAddress, tttAbi);
|
|
79
94
|
if (this.config.feeCollectorAddress) {
|
|
@@ -91,7 +106,7 @@ class AutoMintEngine {
|
|
|
91
106
|
}
|
|
92
107
|
}
|
|
93
108
|
/**
|
|
94
|
-
*
|
|
109
|
+
* Start the automatic minting loop.
|
|
95
110
|
*/
|
|
96
111
|
start() {
|
|
97
112
|
if (this.isRunning)
|
|
@@ -135,7 +150,7 @@ class AutoMintEngine {
|
|
|
135
150
|
logger_1.logger.info(`[AutoMint] Loop started for tier ${this.config.tier} (${interval}ms)`);
|
|
136
151
|
}
|
|
137
152
|
/**
|
|
138
|
-
*
|
|
153
|
+
* Stop the automatic minting loop.
|
|
139
154
|
*/
|
|
140
155
|
stop() {
|
|
141
156
|
if (this.timer) {
|
|
@@ -146,11 +161,26 @@ class AutoMintEngine {
|
|
|
146
161
|
logger_1.logger.info(`[AutoMint] Loop stopped`);
|
|
147
162
|
}
|
|
148
163
|
/**
|
|
149
|
-
*
|
|
150
|
-
*
|
|
164
|
+
* Resume the minting loop after a circuit breaker trip.
|
|
165
|
+
* Resets the consecutive failure counter and restarts the loop.
|
|
166
|
+
*/
|
|
167
|
+
resume() {
|
|
168
|
+
this.consecutiveFailures = 0;
|
|
169
|
+
logger_1.logger.info(`[AutoMint] Consecutive failures reset, resuming...`);
|
|
170
|
+
this.start();
|
|
171
|
+
}
|
|
172
|
+
/**
|
|
173
|
+
* Sleep helper for retry backoff.
|
|
174
|
+
*/
|
|
175
|
+
sleep(ms) {
|
|
176
|
+
return new Promise(resolve => setTimeout(resolve, ms));
|
|
177
|
+
}
|
|
178
|
+
/**
|
|
179
|
+
* Execute a single mint tick.
|
|
180
|
+
* Time synthesis -> tokenId generation -> EVM mint call -> fee calculation/deduction.
|
|
151
181
|
*/
|
|
152
182
|
async mintTick() {
|
|
153
|
-
// 1.
|
|
183
|
+
// 1. Time Synthesis
|
|
154
184
|
const synthesized = await this.timeSynthesis.synthesize();
|
|
155
185
|
if (!synthesized) {
|
|
156
186
|
logger_1.logger.warn(`[AutoMint] Time synthesis returned null/undefined, skipping tick`);
|
|
@@ -158,35 +188,78 @@ class AutoMintEngine {
|
|
|
158
188
|
}
|
|
159
189
|
// Fix-12: Integrity check
|
|
160
190
|
if (synthesized.confidence === 0 || synthesized.stratum >= 16) {
|
|
161
|
-
throw new errors_1.TTTTimeSynthesisError(`[AutoMint] Synthesis integrity check failed`, `confidence=${synthesized.confidence}, stratum=${synthesized.stratum}`, `Check NTP sources or network connectivity.`);
|
|
191
|
+
throw new errors_1.TTTTimeSynthesisError(errors_1.ERROR_CODES.TIME_SYNTHESIS_INTEGRITY_FAILED, `[AutoMint] Synthesis integrity check failed`, `confidence=${synthesized.confidence}, stratum=${synthesized.stratum}`, `Check NTP sources or network connectivity.`);
|
|
162
192
|
}
|
|
163
193
|
// 1-1. PoT Generation & Validation (W1-1)
|
|
164
194
|
const pot = await this.timeSynthesis.generateProofOfTime();
|
|
165
195
|
if (pot.confidence < 0.5) {
|
|
166
|
-
throw new errors_1.TTTTimeSynthesisError(`[PoT] Insufficient confidence`, `Calculated confidence ${pot.confidence} is below required 0.5`, `Ensure more NTP sources are reachable or decrease uncertainty.`);
|
|
196
|
+
throw new errors_1.TTTTimeSynthesisError(errors_1.ERROR_CODES.TIME_SYNTHESIS_INSUFFICIENT_CONFIDENCE, `[PoT] Insufficient confidence`, `Calculated confidence ${pot.confidence} is below required 0.5`, `Ensure more NTP sources are reachable or decrease uncertainty.`);
|
|
167
197
|
}
|
|
168
|
-
|
|
198
|
+
// Deterministic potHash via ABI.encode — field order is fixed,
|
|
199
|
+
// independent of JS engine key ordering. External verifiers can
|
|
200
|
+
// reproduce this hash from the same PoT fields.
|
|
201
|
+
const nonceHash = ethers_1.ethers.keccak256(ethers_1.ethers.toUtf8Bytes(pot.nonce));
|
|
202
|
+
const potHash = ethers_1.ethers.keccak256(ethers_1.ethers.AbiCoder.defaultAbiCoder().encode(["uint64", "uint64", "uint8", "uint8", "uint32", "bytes32"], [
|
|
203
|
+
pot.timestamp,
|
|
204
|
+
pot.expiresAt,
|
|
205
|
+
pot.sources,
|
|
206
|
+
pot.stratum,
|
|
207
|
+
Math.round(pot.confidence * 1_000_000),
|
|
208
|
+
nonceHash
|
|
209
|
+
]));
|
|
169
210
|
// 1-2. Ed25519 issuer signature for non-repudiation
|
|
170
211
|
if (this.potSigner) {
|
|
171
212
|
pot.issuerSignature = this.potSigner.signPot(potHash);
|
|
172
213
|
logger_1.logger.info(`[AutoMint] PoT signed by issuer ${this.potSigner.getPubKeyHex().substring(0, 16)}...`);
|
|
173
214
|
}
|
|
174
|
-
// 2. tokenId
|
|
175
|
-
// chainId, poolAddress, timestamp
|
|
176
|
-
|
|
177
|
-
|
|
215
|
+
// 2. Generate tokenId (keccak256)
|
|
216
|
+
// Unique ID based on chainId, poolAddress, timestamp, and a monotonic nonce
|
|
217
|
+
// to prevent collision if two mints occur at the same nanosecond timestamp.
|
|
218
|
+
const nonceSuffix = this.mintNonce;
|
|
219
|
+
this.mintNonce++;
|
|
220
|
+
const tokenId = ethers_1.ethers.keccak256(ethers_1.ethers.AbiCoder.defaultAbiCoder().encode(["uint256", "address", "uint64", "uint256"], [BigInt(this.config.chainId), this.config.poolAddress, synthesized.timestamp, nonceSuffix]));
|
|
221
|
+
// 3. Fee calculation
|
|
178
222
|
const feeCalculation = await this.feeEngine.calculateMintFee(this.config.tier);
|
|
179
|
-
// 4. EVM mint
|
|
180
|
-
|
|
181
|
-
const
|
|
182
|
-
|
|
223
|
+
// 4. EVM mint call — run full GRG pipeline (Golomb → Reed-Solomon → Golay+HMAC)
|
|
224
|
+
const grgPayload = ethers_1.ethers.AbiCoder.defaultAbiCoder().encode(["bytes32", "bytes32", "uint64", "uint8"], [tokenId, potHash, synthesized.timestamp, pot.sources]);
|
|
225
|
+
const grgStart = Date.now();
|
|
226
|
+
const grgShards = helm_crypto_1.GrgForward.encode(ethers_1.ethers.getBytes(grgPayload), this.config.chainId, this.config.poolAddress);
|
|
227
|
+
const grgElapsed = Date.now() - grgStart;
|
|
228
|
+
logger_1.logger.info(`[AutoMint] GRG pipeline completed in ${grgElapsed}ms`);
|
|
229
|
+
if (grgElapsed > 50 && !this.warnedGrgSlow) {
|
|
230
|
+
this.warnedGrgSlow = true;
|
|
231
|
+
logger_1.logger.warn(`[AutoMint] GRG pipeline took ${grgElapsed}ms (>50ms threshold). Consider offloading to a Worker Thread for T3_micro tiers.`);
|
|
232
|
+
}
|
|
233
|
+
// On-chain hash = keccak256 of concatenated GRG-encoded shards
|
|
234
|
+
const grgHash = ethers_1.ethers.keccak256(ethers_1.ethers.concat(grgShards));
|
|
235
|
+
// Recipient address (defaults to signer address)
|
|
183
236
|
if (!this.cachedSigner) {
|
|
184
|
-
throw new errors_1.TTTSignerError("[AutoMint] Signer not initialized", "cachedSigner is null", "Initialize the engine before calling mintTick().");
|
|
237
|
+
throw new errors_1.TTTSignerError(errors_1.ERROR_CODES.SIGNER_NOT_INITIALIZED, "[AutoMint] Signer not initialized", "cachedSigner is null", "Initialize the engine before calling mintTick().");
|
|
185
238
|
}
|
|
186
239
|
const recipient = await this.cachedSigner.getAddress();
|
|
187
240
|
logger_1.logger.info(`[AutoMint] Executing mint: tokenId=${tokenId.substring(0, 10)}... amount=${feeCalculation.tttAmount}`);
|
|
188
|
-
|
|
189
|
-
|
|
241
|
+
// Retry loop for RPC-dependent mint operation (max 3 attempts, backoff 1s/2s/4s)
|
|
242
|
+
let receipt;
|
|
243
|
+
let lastError = null;
|
|
244
|
+
for (let attempt = 0; attempt < MINT_TICK_MAX_RETRIES; attempt++) {
|
|
245
|
+
try {
|
|
246
|
+
receipt = await this.evmConnector.mintTTT(recipient, feeCalculation.tttAmount, grgHash, potHash);
|
|
247
|
+
lastError = null;
|
|
248
|
+
break;
|
|
249
|
+
}
|
|
250
|
+
catch (err) {
|
|
251
|
+
lastError = err instanceof Error ? err : new Error(String(err));
|
|
252
|
+
if (attempt < MINT_TICK_MAX_RETRIES - 1) {
|
|
253
|
+
const backoff = MINT_TICK_BACKOFF_MS[attempt];
|
|
254
|
+
logger_1.logger.warn(`[AutoMint] Mint attempt ${attempt + 1}/${MINT_TICK_MAX_RETRIES} failed, retrying in ${backoff}ms: ${lastError.message}`);
|
|
255
|
+
await this.sleep(backoff);
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
if (lastError || !receipt) {
|
|
260
|
+
throw lastError || new Error("[AutoMint] Mint failed after all retries");
|
|
261
|
+
}
|
|
262
|
+
// 5. Fee deduction/recording (handled by contract or tracked at SDK level)
|
|
190
263
|
// W2-3: Actual ProtocolFeeCollector call
|
|
191
264
|
let actualFeePaid = feeCalculation.protocolFeeUsd;
|
|
192
265
|
if (this.feeCollector && this.config.feeCollectorAddress) {
|
|
@@ -194,7 +267,9 @@ class AutoMintEngine {
|
|
|
194
267
|
// NOTE: Single-threaded JS guarantees atomicity between nonce generation,
|
|
195
268
|
// signing, and collection below. If running multiple AutoMintEngine instances
|
|
196
269
|
// (e.g., worker_threads or cluster), a separate nonce manager with locking is required.
|
|
197
|
-
|
|
270
|
+
// Sequential nonce: query contract for current nonce, matching ProtocolFee.sol's require(nonces[msg.sender] == nonce)
|
|
271
|
+
const feeContract = new ethers_1.ethers.Contract(this.config.feeCollectorAddress, ["function getNonce(address) external view returns (uint256)"], this.evmConnector.getProvider());
|
|
272
|
+
const nonce = BigInt(await feeContract.getNonce(recipient));
|
|
198
273
|
const deadline = Math.floor(Date.now() / 1000) + 3600; // 1 hour validity
|
|
199
274
|
const signature = await this.signFeeMessage(feeCalculation, nonce, deadline);
|
|
200
275
|
const user = recipient;
|
|
@@ -206,6 +281,8 @@ class AutoMintEngine {
|
|
|
206
281
|
actualFeePaid = 0n;
|
|
207
282
|
}
|
|
208
283
|
}
|
|
284
|
+
// CT Log equivalent: log PoT anchor info for subgraph indexers
|
|
285
|
+
logger_1.logger.info(`[AutoMint] PoTAnchored: timestamp=${synthesized.timestamp}, grgHash=${grgHash}, stratum=${synthesized.stratum}, potHash=${potHash}`);
|
|
209
286
|
logger_1.logger.info(`[AutoMint] Mint success: tx=${receipt.hash}, feePaid=${actualFeePaid} (USDC eq)`);
|
|
210
287
|
if (this.onMintCallback) {
|
|
211
288
|
this.onMintCallback({
|
|
@@ -220,15 +297,15 @@ class AutoMintEngine {
|
|
|
220
297
|
}
|
|
221
298
|
async signFeeMessage(feeCalc, nonce, deadline) {
|
|
222
299
|
if (!this.cachedSigner) {
|
|
223
|
-
throw new errors_1.TTTSignerError("[AutoMint] Signer not initialized", "cachedSigner is null", "Ensure initialize() was called successfully.");
|
|
300
|
+
throw new errors_1.TTTSignerError(errors_1.ERROR_CODES.SIGNER_NOT_INITIALIZED, "[AutoMint] Signer not initialized", "cachedSigner is null", "Ensure initialize() was called successfully.");
|
|
224
301
|
}
|
|
225
302
|
// Type casting to handle signTypedData if available on the signer (Wallet supports it)
|
|
226
303
|
const signer = this.cachedSigner;
|
|
227
304
|
if (typeof signer.signTypedData !== 'function') {
|
|
228
|
-
throw new errors_1.TTTSignerError("[AutoMint] Provided signer does not support signTypedData (EIP-712)", `Signer type ${signer.constructor.name} missing signTypedData`, "Use a Wallet or a signer that implements EIP-712 signTypedData.");
|
|
305
|
+
throw new errors_1.TTTSignerError(errors_1.ERROR_CODES.SIGNER_NO_EIP712, "[AutoMint] Provided signer does not support signTypedData (EIP-712)", `Signer type ${signer.constructor.name} missing signTypedData`, "Use a Wallet or a signer that implements EIP-712 signTypedData.");
|
|
229
306
|
}
|
|
230
307
|
const domain = {
|
|
231
|
-
name: "
|
|
308
|
+
name: "OpenTTT_ProtocolFee",
|
|
232
309
|
version: "1",
|
|
233
310
|
chainId: this.config.chainId,
|
|
234
311
|
verifyingContract: this.config.feeCollectorAddress
|
package/dist/ct_log.d.ts
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PoT Certificate Transparency Log Client
|
|
3
|
+
* Tracks all PoT anchoring events and provides query/audit capabilities.
|
|
4
|
+
*/
|
|
5
|
+
export interface PoTAnchorEntry {
|
|
6
|
+
id: string;
|
|
7
|
+
stratum: string;
|
|
8
|
+
grgHash: string;
|
|
9
|
+
potHash: string;
|
|
10
|
+
timestamp: string;
|
|
11
|
+
blockNumber: string;
|
|
12
|
+
txHash: string;
|
|
13
|
+
builderAddress: string;
|
|
14
|
+
}
|
|
15
|
+
export declare class PoTCTLog {
|
|
16
|
+
private subgraphUrl;
|
|
17
|
+
constructor(subgraphUrl: string);
|
|
18
|
+
/**
|
|
19
|
+
* Query PoT anchors with filters.
|
|
20
|
+
*/
|
|
21
|
+
queryAnchors(filter: {
|
|
22
|
+
startTime?: number;
|
|
23
|
+
endTime?: number;
|
|
24
|
+
stratum?: string;
|
|
25
|
+
limit?: number;
|
|
26
|
+
}): Promise<PoTAnchorEntry[]>;
|
|
27
|
+
/**
|
|
28
|
+
* Get audit trail for a specific transaction.
|
|
29
|
+
*/
|
|
30
|
+
getAuditTrail(txHash: string): Promise<PoTAnchorEntry | null>;
|
|
31
|
+
/**
|
|
32
|
+
* Calculate builder performance score.
|
|
33
|
+
*/
|
|
34
|
+
getBuilderScore(builderAddress: string): Promise<{
|
|
35
|
+
totalAnchors: number;
|
|
36
|
+
turboRate: number;
|
|
37
|
+
avgLatencyMs: number;
|
|
38
|
+
}>;
|
|
39
|
+
/**
|
|
40
|
+
* Get global network statistics.
|
|
41
|
+
*/
|
|
42
|
+
getNetworkStats(): Promise<{
|
|
43
|
+
totalAnchors: number;
|
|
44
|
+
uniqueBuilders: number;
|
|
45
|
+
avgTurboRate: number;
|
|
46
|
+
}>;
|
|
47
|
+
}
|