chainlesschain 0.47.9 → 0.51.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/bin/chainlesschain.js +0 -0
- package/package.json +1 -1
- package/src/assets/web-panel/.build-hash +1 -1
- package/src/assets/web-panel/assets/{AppLayout-6SPt_8Y_.js → AppLayout-Rvi759IS.js} +1 -1
- package/src/assets/web-panel/assets/Dashboard-BS-tzGNj.css +1 -0
- package/src/assets/web-panel/assets/{Dashboard-Br7kCwKJ.js → Dashboard-DBhFxXYQ.js} +2 -2
- package/src/assets/web-panel/assets/{index-tN-8TosE.js → index-uL0cZ8N_.js} +2 -2
- package/src/assets/web-panel/index.html +2 -2
- package/src/commands/codegen.js +303 -0
- package/src/commands/collab.js +482 -0
- package/src/commands/crosschain.js +382 -0
- package/src/commands/dbevo.js +388 -0
- package/src/commands/dev.js +411 -0
- package/src/commands/federation.js +427 -0
- package/src/commands/fusion.js +332 -0
- package/src/commands/governance.js +505 -0
- package/src/commands/hardening.js +110 -0
- package/src/commands/incentive.js +373 -0
- package/src/commands/inference.js +304 -0
- package/src/commands/infra.js +361 -0
- package/src/commands/ipfs.js +392 -0
- package/src/commands/kg.js +371 -0
- package/src/commands/marketplace.js +326 -0
- package/src/commands/mcp.js +97 -18
- package/src/commands/multimodal.js +404 -0
- package/src/commands/nlprog.js +329 -0
- package/src/commands/ops.js +408 -0
- package/src/commands/perception.js +385 -0
- package/src/commands/pqc.js +34 -0
- package/src/commands/privacy.js +345 -0
- package/src/commands/quantization.js +280 -0
- package/src/commands/recommend.js +336 -0
- package/src/commands/reputation.js +349 -0
- package/src/commands/runtime.js +500 -0
- package/src/commands/sla.js +352 -0
- package/src/commands/stress.js +252 -0
- package/src/commands/tech.js +268 -0
- package/src/commands/tenant.js +576 -0
- package/src/commands/trust.js +366 -0
- package/src/harness/mcp-client.js +330 -54
- package/src/index.js +118 -0
- package/src/lib/aiops.js +523 -0
- package/src/lib/autonomous-developer.js +524 -0
- package/src/lib/code-agent.js +442 -0
- package/src/lib/collaboration-governance.js +556 -0
- package/src/lib/community-governance.js +649 -0
- package/src/lib/content-recommendation.js +600 -0
- package/src/lib/cross-chain.js +669 -0
- package/src/lib/dbevo.js +669 -0
- package/src/lib/decentral-infra.js +445 -0
- package/src/lib/federation-hardening.js +587 -0
- package/src/lib/hardening-manager.js +409 -0
- package/src/lib/inference-network.js +407 -0
- package/src/lib/ipfs-storage.js +575 -0
- package/src/lib/knowledge-graph.js +530 -0
- package/src/lib/mcp-client.js +3 -0
- package/src/lib/multimodal.js +725 -0
- package/src/lib/nl-programming.js +595 -0
- package/src/lib/perception.js +500 -0
- package/src/lib/pqc-manager.js +141 -9
- package/src/lib/privacy-computing.js +575 -0
- package/src/lib/protocol-fusion.js +535 -0
- package/src/lib/quantization.js +362 -0
- package/src/lib/reputation-optimizer.js +509 -0
- package/src/lib/skill-marketplace.js +397 -0
- package/src/lib/sla-manager.js +484 -0
- package/src/lib/stress-tester.js +383 -0
- package/src/lib/tech-learning-engine.js +651 -0
- package/src/lib/tenant-saas.js +831 -0
- package/src/lib/token-incentive.js +513 -0
- package/src/lib/trust-security.js +473 -0
- package/src/lib/universal-runtime.js +771 -0
- package/src/assets/web-panel/assets/Dashboard-CKeMmCoT.css +0 -1
|
@@ -0,0 +1,669 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cross-Chain Interoperability — CLI port of Phase 89 跨链互操作协议
|
|
3
|
+
* (docs/design/modules/54_跨链互操作协议.md).
|
|
4
|
+
*
|
|
5
|
+
* Desktop drives cross-chain with ChainAdapterLayer (real RPC calls),
|
|
6
|
+
* CrossChainBridgePage.vue, and Pinia store. CLI port ships:
|
|
7
|
+
*
|
|
8
|
+
* - Chain catalog (5 supported chains + config)
|
|
9
|
+
* - Asset bridge lifecycle (pending → locked → minted → completed)
|
|
10
|
+
* - HTLC atomic swap lifecycle (initiated → hash_locked → claimed/refunded)
|
|
11
|
+
* - Cross-chain message tracking
|
|
12
|
+
* - Heuristic fee estimation
|
|
13
|
+
* - Bridge/swap/message stats
|
|
14
|
+
*
|
|
15
|
+
* What does NOT port: real RPC chain adapters, actual on-chain transactions,
|
|
16
|
+
* CrossChainBridgePage.vue, Pinia store.
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
import crypto from "crypto";
|
|
20
|
+
|
|
21
|
+
/* ── Constants ─────────────────────────────────────────────── */
|
|
22
|
+
|
|
23
|
+
export const SUPPORTED_CHAINS = Object.freeze({
|
|
24
|
+
ETHEREUM: Object.freeze({
|
|
25
|
+
id: "ethereum",
|
|
26
|
+
name: "Ethereum",
|
|
27
|
+
symbol: "ETH",
|
|
28
|
+
chainId: 1,
|
|
29
|
+
}),
|
|
30
|
+
POLYGON: Object.freeze({
|
|
31
|
+
id: "polygon",
|
|
32
|
+
name: "Polygon",
|
|
33
|
+
symbol: "MATIC",
|
|
34
|
+
chainId: 137,
|
|
35
|
+
}),
|
|
36
|
+
BSC: Object.freeze({
|
|
37
|
+
id: "bsc",
|
|
38
|
+
name: "BNB Smart Chain",
|
|
39
|
+
symbol: "BNB",
|
|
40
|
+
chainId: 56,
|
|
41
|
+
}),
|
|
42
|
+
ARBITRUM: Object.freeze({
|
|
43
|
+
id: "arbitrum",
|
|
44
|
+
name: "Arbitrum One",
|
|
45
|
+
symbol: "ETH",
|
|
46
|
+
chainId: 42161,
|
|
47
|
+
}),
|
|
48
|
+
SOLANA: Object.freeze({
|
|
49
|
+
id: "solana",
|
|
50
|
+
name: "Solana",
|
|
51
|
+
symbol: "SOL",
|
|
52
|
+
chainId: 0,
|
|
53
|
+
}),
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
const CHAIN_IDS = new Set(Object.values(SUPPORTED_CHAINS).map((c) => c.id));
|
|
57
|
+
|
|
58
|
+
export const BRIDGE_STATUS = Object.freeze({
|
|
59
|
+
PENDING: "pending",
|
|
60
|
+
LOCKED: "locked",
|
|
61
|
+
MINTED: "minted",
|
|
62
|
+
COMPLETED: "completed",
|
|
63
|
+
REFUNDED: "refunded",
|
|
64
|
+
FAILED: "failed",
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
export const SWAP_STATUS = Object.freeze({
|
|
68
|
+
INITIATED: "initiated",
|
|
69
|
+
HASH_LOCKED: "hash_locked",
|
|
70
|
+
CLAIMED: "claimed",
|
|
71
|
+
REFUNDED: "refunded",
|
|
72
|
+
EXPIRED: "expired",
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
export const MESSAGE_STATUS = Object.freeze({
|
|
76
|
+
PENDING: "pending",
|
|
77
|
+
SENT: "sent",
|
|
78
|
+
DELIVERED: "delivered",
|
|
79
|
+
FAILED: "failed",
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
export const DEFAULT_CONFIG = Object.freeze({
|
|
83
|
+
defaultTimeoutBlocks: 100,
|
|
84
|
+
maxBridgeAmount: 1000000,
|
|
85
|
+
feePercentage: 0.3,
|
|
86
|
+
htlcTimeoutMs: 3600000,
|
|
87
|
+
messageRetryLimit: 3,
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
/* ── State ─────────────────────────────────────────────── */
|
|
91
|
+
|
|
92
|
+
let _bridges = new Map();
|
|
93
|
+
let _swaps = new Map();
|
|
94
|
+
let _messages = new Map();
|
|
95
|
+
let _chainConfigs = new Map();
|
|
96
|
+
|
|
97
|
+
function _id() {
|
|
98
|
+
return crypto.randomUUID();
|
|
99
|
+
}
|
|
100
|
+
function _now() {
|
|
101
|
+
return Date.now();
|
|
102
|
+
}
|
|
103
|
+
function _hashLock() {
|
|
104
|
+
const secret = crypto.randomBytes(32).toString("hex");
|
|
105
|
+
const hash = crypto.createHash("sha256").update(secret).digest("hex");
|
|
106
|
+
return { secret, hashLock: hash };
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function _strip(row) {
|
|
110
|
+
if (!row) return null;
|
|
111
|
+
const out = {};
|
|
112
|
+
for (const [k, v] of Object.entries(row)) {
|
|
113
|
+
if (k !== "_rowid_" && k !== "rowid") out[k] = v;
|
|
114
|
+
}
|
|
115
|
+
return out;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/* ── Schema ────────────────────────────────────────────── */
|
|
119
|
+
|
|
120
|
+
export function ensureCrossChainTables(db) {
|
|
121
|
+
db.exec(`CREATE TABLE IF NOT EXISTS cc_bridges (
|
|
122
|
+
id TEXT PRIMARY KEY,
|
|
123
|
+
from_chain TEXT NOT NULL,
|
|
124
|
+
to_chain TEXT NOT NULL,
|
|
125
|
+
asset TEXT NOT NULL,
|
|
126
|
+
amount REAL NOT NULL,
|
|
127
|
+
sender_address TEXT,
|
|
128
|
+
recipient_address TEXT,
|
|
129
|
+
lock_tx_hash TEXT,
|
|
130
|
+
mint_tx_hash TEXT,
|
|
131
|
+
status TEXT DEFAULT 'pending',
|
|
132
|
+
fee_amount REAL DEFAULT 0,
|
|
133
|
+
fee_chain TEXT,
|
|
134
|
+
error_message TEXT,
|
|
135
|
+
created_at INTEGER,
|
|
136
|
+
completed_at INTEGER
|
|
137
|
+
)`);
|
|
138
|
+
db.exec("CREATE INDEX IF NOT EXISTS idx_ccb_status ON cc_bridges(status)");
|
|
139
|
+
db.exec("CREATE INDEX IF NOT EXISTS idx_ccb_from ON cc_bridges(from_chain)");
|
|
140
|
+
|
|
141
|
+
db.exec(`CREATE TABLE IF NOT EXISTS cc_swaps (
|
|
142
|
+
id TEXT PRIMARY KEY,
|
|
143
|
+
from_chain TEXT NOT NULL,
|
|
144
|
+
to_chain TEXT NOT NULL,
|
|
145
|
+
from_asset TEXT NOT NULL,
|
|
146
|
+
to_asset TEXT NOT NULL,
|
|
147
|
+
amount REAL NOT NULL,
|
|
148
|
+
counterparty_address TEXT,
|
|
149
|
+
hash_lock TEXT,
|
|
150
|
+
time_lock INTEGER,
|
|
151
|
+
secret TEXT,
|
|
152
|
+
status TEXT DEFAULT 'initiated',
|
|
153
|
+
claim_tx_hash TEXT,
|
|
154
|
+
refund_tx_hash TEXT,
|
|
155
|
+
created_at INTEGER,
|
|
156
|
+
expires_at INTEGER
|
|
157
|
+
)`);
|
|
158
|
+
db.exec("CREATE INDEX IF NOT EXISTS idx_ccs_status ON cc_swaps(status)");
|
|
159
|
+
|
|
160
|
+
db.exec(`CREATE TABLE IF NOT EXISTS cc_messages (
|
|
161
|
+
id TEXT PRIMARY KEY,
|
|
162
|
+
from_chain TEXT NOT NULL,
|
|
163
|
+
to_chain TEXT NOT NULL,
|
|
164
|
+
payload TEXT,
|
|
165
|
+
target_contract TEXT,
|
|
166
|
+
source_tx_hash TEXT,
|
|
167
|
+
destination_tx_hash TEXT,
|
|
168
|
+
status TEXT DEFAULT 'pending',
|
|
169
|
+
retries INTEGER DEFAULT 0,
|
|
170
|
+
created_at INTEGER,
|
|
171
|
+
delivered_at INTEGER
|
|
172
|
+
)`);
|
|
173
|
+
db.exec("CREATE INDEX IF NOT EXISTS idx_ccm_status ON cc_messages(status)");
|
|
174
|
+
|
|
175
|
+
_loadAll(db);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
function _loadAll(db) {
|
|
179
|
+
_bridges.clear();
|
|
180
|
+
_swaps.clear();
|
|
181
|
+
_messages.clear();
|
|
182
|
+
|
|
183
|
+
try {
|
|
184
|
+
for (const row of db.prepare("SELECT * FROM cc_bridges").all()) {
|
|
185
|
+
const b = _strip(row);
|
|
186
|
+
_bridges.set(b.id, b);
|
|
187
|
+
}
|
|
188
|
+
} catch (_e) {
|
|
189
|
+
/* table may not exist */
|
|
190
|
+
}
|
|
191
|
+
try {
|
|
192
|
+
for (const row of db.prepare("SELECT * FROM cc_swaps").all()) {
|
|
193
|
+
const s = _strip(row);
|
|
194
|
+
_swaps.set(s.id, s);
|
|
195
|
+
}
|
|
196
|
+
} catch (_e) {
|
|
197
|
+
/* table may not exist */
|
|
198
|
+
}
|
|
199
|
+
try {
|
|
200
|
+
for (const row of db.prepare("SELECT * FROM cc_messages").all()) {
|
|
201
|
+
const m = _strip(row);
|
|
202
|
+
_messages.set(m.id, m);
|
|
203
|
+
}
|
|
204
|
+
} catch (_e) {
|
|
205
|
+
/* table may not exist */
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
/* ── Chain validation ──────────────────────────────────── */
|
|
210
|
+
|
|
211
|
+
function _validateChain(chainId) {
|
|
212
|
+
return CHAIN_IDS.has(chainId);
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
/* ── Asset Bridge ──────────────────────────────────────── */
|
|
216
|
+
|
|
217
|
+
export function bridgeAsset(
|
|
218
|
+
db,
|
|
219
|
+
{ fromChain, toChain, asset, amount, senderAddress, recipientAddress },
|
|
220
|
+
) {
|
|
221
|
+
if (!_validateChain(fromChain))
|
|
222
|
+
return { bridgeId: null, reason: "unsupported_chain", chain: fromChain };
|
|
223
|
+
if (!_validateChain(toChain))
|
|
224
|
+
return { bridgeId: null, reason: "unsupported_chain", chain: toChain };
|
|
225
|
+
if (fromChain === toChain) return { bridgeId: null, reason: "same_chain" };
|
|
226
|
+
if (!amount || amount <= 0)
|
|
227
|
+
return { bridgeId: null, reason: "invalid_amount" };
|
|
228
|
+
if (amount > DEFAULT_CONFIG.maxBridgeAmount)
|
|
229
|
+
return { bridgeId: null, reason: "exceeds_max_amount" };
|
|
230
|
+
|
|
231
|
+
const id = _id();
|
|
232
|
+
const now = _now();
|
|
233
|
+
const fee = Math.round(amount * DEFAULT_CONFIG.feePercentage * 10) / 1000;
|
|
234
|
+
|
|
235
|
+
const bridge = {
|
|
236
|
+
id,
|
|
237
|
+
from_chain: fromChain,
|
|
238
|
+
to_chain: toChain,
|
|
239
|
+
asset: asset || "native",
|
|
240
|
+
amount,
|
|
241
|
+
sender_address: senderAddress || null,
|
|
242
|
+
recipient_address: recipientAddress || null,
|
|
243
|
+
lock_tx_hash: null,
|
|
244
|
+
mint_tx_hash: null,
|
|
245
|
+
status: "pending",
|
|
246
|
+
fee_amount: fee,
|
|
247
|
+
fee_chain: fromChain,
|
|
248
|
+
error_message: null,
|
|
249
|
+
created_at: now,
|
|
250
|
+
completed_at: null,
|
|
251
|
+
};
|
|
252
|
+
|
|
253
|
+
db.prepare(
|
|
254
|
+
`INSERT INTO cc_bridges (id, from_chain, to_chain, asset, amount, sender_address, recipient_address,
|
|
255
|
+
lock_tx_hash, mint_tx_hash, status, fee_amount, fee_chain, error_message, created_at, completed_at)
|
|
256
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
257
|
+
).run(
|
|
258
|
+
id,
|
|
259
|
+
fromChain,
|
|
260
|
+
toChain,
|
|
261
|
+
bridge.asset,
|
|
262
|
+
amount,
|
|
263
|
+
bridge.sender_address,
|
|
264
|
+
bridge.recipient_address,
|
|
265
|
+
null,
|
|
266
|
+
null,
|
|
267
|
+
"pending",
|
|
268
|
+
fee,
|
|
269
|
+
fromChain,
|
|
270
|
+
null,
|
|
271
|
+
now,
|
|
272
|
+
null,
|
|
273
|
+
);
|
|
274
|
+
|
|
275
|
+
_bridges.set(id, bridge);
|
|
276
|
+
return { bridgeId: id, fee };
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
export function updateBridgeStatus(
|
|
280
|
+
db,
|
|
281
|
+
bridgeId,
|
|
282
|
+
status,
|
|
283
|
+
{ txHash, errorMessage } = {},
|
|
284
|
+
) {
|
|
285
|
+
const b = _bridges.get(bridgeId);
|
|
286
|
+
if (!b) return { updated: false, reason: "not_found" };
|
|
287
|
+
|
|
288
|
+
const validTransitions = {
|
|
289
|
+
pending: ["locked", "failed"],
|
|
290
|
+
locked: ["minted", "refunded", "failed"],
|
|
291
|
+
minted: ["completed", "failed"],
|
|
292
|
+
};
|
|
293
|
+
const allowed = validTransitions[b.status];
|
|
294
|
+
if (!allowed || !allowed.includes(status))
|
|
295
|
+
return {
|
|
296
|
+
updated: false,
|
|
297
|
+
reason: "invalid_transition",
|
|
298
|
+
from: b.status,
|
|
299
|
+
to: status,
|
|
300
|
+
};
|
|
301
|
+
|
|
302
|
+
b.status = status;
|
|
303
|
+
if (status === "locked" && txHash) b.lock_tx_hash = txHash;
|
|
304
|
+
if (status === "minted" && txHash) b.mint_tx_hash = txHash;
|
|
305
|
+
if (status === "completed") b.completed_at = _now();
|
|
306
|
+
if (errorMessage) b.error_message = errorMessage;
|
|
307
|
+
|
|
308
|
+
db.prepare(
|
|
309
|
+
`UPDATE cc_bridges SET status = ?, lock_tx_hash = ?, mint_tx_hash = ?,
|
|
310
|
+
completed_at = ?, error_message = ? WHERE id = ?`,
|
|
311
|
+
).run(
|
|
312
|
+
b.status,
|
|
313
|
+
b.lock_tx_hash,
|
|
314
|
+
b.mint_tx_hash,
|
|
315
|
+
b.completed_at,
|
|
316
|
+
b.error_message,
|
|
317
|
+
bridgeId,
|
|
318
|
+
);
|
|
319
|
+
|
|
320
|
+
return { updated: true };
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
export function getBridge(db, bridgeId) {
|
|
324
|
+
const b = _bridges.get(bridgeId);
|
|
325
|
+
return b ? { ...b } : null;
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
export function listBridges(
|
|
329
|
+
db,
|
|
330
|
+
{ fromChain, toChain, status, limit = 50 } = {},
|
|
331
|
+
) {
|
|
332
|
+
let bridges = [..._bridges.values()];
|
|
333
|
+
if (fromChain) bridges = bridges.filter((b) => b.from_chain === fromChain);
|
|
334
|
+
if (toChain) bridges = bridges.filter((b) => b.to_chain === toChain);
|
|
335
|
+
if (status) bridges = bridges.filter((b) => b.status === status);
|
|
336
|
+
return bridges
|
|
337
|
+
.sort((a, b) => b.created_at - a.created_at)
|
|
338
|
+
.slice(0, limit)
|
|
339
|
+
.map((b) => ({ ...b }));
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
/* ── HTLC Atomic Swap ──────────────────────────────────── */
|
|
343
|
+
|
|
344
|
+
export function initiateSwap(
|
|
345
|
+
db,
|
|
346
|
+
{
|
|
347
|
+
fromChain,
|
|
348
|
+
toChain,
|
|
349
|
+
fromAsset,
|
|
350
|
+
toAsset,
|
|
351
|
+
amount,
|
|
352
|
+
counterpartyAddress,
|
|
353
|
+
timeoutMs,
|
|
354
|
+
},
|
|
355
|
+
) {
|
|
356
|
+
if (!_validateChain(fromChain))
|
|
357
|
+
return { swapId: null, reason: "unsupported_chain", chain: fromChain };
|
|
358
|
+
if (!_validateChain(toChain))
|
|
359
|
+
return { swapId: null, reason: "unsupported_chain", chain: toChain };
|
|
360
|
+
if (fromChain === toChain) return { swapId: null, reason: "same_chain" };
|
|
361
|
+
if (!amount || amount <= 0) return { swapId: null, reason: "invalid_amount" };
|
|
362
|
+
|
|
363
|
+
const id = _id();
|
|
364
|
+
const now = _now();
|
|
365
|
+
const { secret, hashLock } = _hashLock();
|
|
366
|
+
const expiresAt = now + (timeoutMs || DEFAULT_CONFIG.htlcTimeoutMs);
|
|
367
|
+
|
|
368
|
+
const swap = {
|
|
369
|
+
id,
|
|
370
|
+
from_chain: fromChain,
|
|
371
|
+
to_chain: toChain,
|
|
372
|
+
from_asset: fromAsset || "native",
|
|
373
|
+
to_asset: toAsset || "native",
|
|
374
|
+
amount,
|
|
375
|
+
counterparty_address: counterpartyAddress || null,
|
|
376
|
+
hash_lock: hashLock,
|
|
377
|
+
time_lock: expiresAt,
|
|
378
|
+
secret,
|
|
379
|
+
status: "initiated",
|
|
380
|
+
claim_tx_hash: null,
|
|
381
|
+
refund_tx_hash: null,
|
|
382
|
+
created_at: now,
|
|
383
|
+
expires_at: expiresAt,
|
|
384
|
+
};
|
|
385
|
+
|
|
386
|
+
db.prepare(
|
|
387
|
+
`INSERT INTO cc_swaps (id, from_chain, to_chain, from_asset, to_asset, amount,
|
|
388
|
+
counterparty_address, hash_lock, time_lock, secret, status, claim_tx_hash,
|
|
389
|
+
refund_tx_hash, created_at, expires_at)
|
|
390
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
391
|
+
).run(
|
|
392
|
+
id,
|
|
393
|
+
fromChain,
|
|
394
|
+
toChain,
|
|
395
|
+
swap.from_asset,
|
|
396
|
+
swap.to_asset,
|
|
397
|
+
amount,
|
|
398
|
+
swap.counterparty_address,
|
|
399
|
+
hashLock,
|
|
400
|
+
expiresAt,
|
|
401
|
+
secret,
|
|
402
|
+
"initiated",
|
|
403
|
+
null,
|
|
404
|
+
null,
|
|
405
|
+
now,
|
|
406
|
+
expiresAt,
|
|
407
|
+
);
|
|
408
|
+
|
|
409
|
+
_swaps.set(id, swap);
|
|
410
|
+
return { swapId: id, hashLock, expiresAt };
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
export function claimSwap(db, swapId, { secret, txHash } = {}) {
|
|
414
|
+
const s = _swaps.get(swapId);
|
|
415
|
+
if (!s) return { claimed: false, reason: "not_found" };
|
|
416
|
+
if (s.status !== "initiated" && s.status !== "hash_locked")
|
|
417
|
+
return { claimed: false, reason: "invalid_status", status: s.status };
|
|
418
|
+
|
|
419
|
+
// Verify secret matches hash lock
|
|
420
|
+
if (secret) {
|
|
421
|
+
const hash = crypto.createHash("sha256").update(secret).digest("hex");
|
|
422
|
+
if (hash !== s.hash_lock)
|
|
423
|
+
return { claimed: false, reason: "invalid_secret" };
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
if (_now() > s.expires_at) return { claimed: false, reason: "expired" };
|
|
427
|
+
|
|
428
|
+
s.status = "claimed";
|
|
429
|
+
if (txHash) s.claim_tx_hash = txHash;
|
|
430
|
+
|
|
431
|
+
db.prepare(
|
|
432
|
+
"UPDATE cc_swaps SET status = ?, claim_tx_hash = ? WHERE id = ?",
|
|
433
|
+
).run("claimed", s.claim_tx_hash, swapId);
|
|
434
|
+
|
|
435
|
+
return { claimed: true };
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
export function refundSwap(db, swapId, { txHash } = {}) {
|
|
439
|
+
const s = _swaps.get(swapId);
|
|
440
|
+
if (!s) return { refunded: false, reason: "not_found" };
|
|
441
|
+
if (s.status === "claimed" || s.status === "refunded")
|
|
442
|
+
return { refunded: false, reason: "invalid_status", status: s.status };
|
|
443
|
+
|
|
444
|
+
s.status = "refunded";
|
|
445
|
+
if (txHash) s.refund_tx_hash = txHash;
|
|
446
|
+
|
|
447
|
+
db.prepare(
|
|
448
|
+
"UPDATE cc_swaps SET status = ?, refund_tx_hash = ? WHERE id = ?",
|
|
449
|
+
).run("refunded", s.refund_tx_hash, swapId);
|
|
450
|
+
|
|
451
|
+
return { refunded: true };
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
export function getSwap(db, swapId) {
|
|
455
|
+
const s = _swaps.get(swapId);
|
|
456
|
+
if (!s) return null;
|
|
457
|
+
// Don't leak secret in show — only expose hashLock
|
|
458
|
+
const out = { ...s };
|
|
459
|
+
delete out.secret;
|
|
460
|
+
return out;
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
export function revealSecret(db, swapId) {
|
|
464
|
+
const s = _swaps.get(swapId);
|
|
465
|
+
if (!s) return null;
|
|
466
|
+
if (s.status !== "claimed") return null;
|
|
467
|
+
return { secret: s.secret, hashLock: s.hash_lock };
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
export function listSwaps(db, { fromChain, status, limit = 50 } = {}) {
|
|
471
|
+
let swaps = [..._swaps.values()];
|
|
472
|
+
if (fromChain) swaps = swaps.filter((s) => s.from_chain === fromChain);
|
|
473
|
+
if (status) swaps = swaps.filter((s) => s.status === status);
|
|
474
|
+
return swaps
|
|
475
|
+
.sort((a, b) => b.created_at - a.created_at)
|
|
476
|
+
.slice(0, limit)
|
|
477
|
+
.map((s) => {
|
|
478
|
+
const out = { ...s };
|
|
479
|
+
delete out.secret;
|
|
480
|
+
return out;
|
|
481
|
+
});
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
/* ── Cross-Chain Message ───────────────────────────────── */
|
|
485
|
+
|
|
486
|
+
export function sendMessage(
|
|
487
|
+
db,
|
|
488
|
+
{ fromChain, toChain, payload, targetContract },
|
|
489
|
+
) {
|
|
490
|
+
if (!_validateChain(fromChain))
|
|
491
|
+
return { messageId: null, reason: "unsupported_chain", chain: fromChain };
|
|
492
|
+
if (!_validateChain(toChain))
|
|
493
|
+
return { messageId: null, reason: "unsupported_chain", chain: toChain };
|
|
494
|
+
|
|
495
|
+
const id = _id();
|
|
496
|
+
const now = _now();
|
|
497
|
+
|
|
498
|
+
const msg = {
|
|
499
|
+
id,
|
|
500
|
+
from_chain: fromChain,
|
|
501
|
+
to_chain: toChain,
|
|
502
|
+
payload: payload || "",
|
|
503
|
+
target_contract: targetContract || null,
|
|
504
|
+
source_tx_hash: null,
|
|
505
|
+
destination_tx_hash: null,
|
|
506
|
+
status: "pending",
|
|
507
|
+
retries: 0,
|
|
508
|
+
created_at: now,
|
|
509
|
+
delivered_at: null,
|
|
510
|
+
};
|
|
511
|
+
|
|
512
|
+
db.prepare(
|
|
513
|
+
`INSERT INTO cc_messages (id, from_chain, to_chain, payload, target_contract,
|
|
514
|
+
source_tx_hash, destination_tx_hash, status, retries, created_at, delivered_at)
|
|
515
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
516
|
+
).run(
|
|
517
|
+
id,
|
|
518
|
+
fromChain,
|
|
519
|
+
toChain,
|
|
520
|
+
msg.payload,
|
|
521
|
+
msg.target_contract,
|
|
522
|
+
null,
|
|
523
|
+
null,
|
|
524
|
+
"pending",
|
|
525
|
+
0,
|
|
526
|
+
now,
|
|
527
|
+
null,
|
|
528
|
+
);
|
|
529
|
+
|
|
530
|
+
_messages.set(id, msg);
|
|
531
|
+
return { messageId: id };
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
export function updateMessageStatus(db, messageId, status, { txHash } = {}) {
|
|
535
|
+
const m = _messages.get(messageId);
|
|
536
|
+
if (!m) return { updated: false, reason: "not_found" };
|
|
537
|
+
|
|
538
|
+
const validTransitions = {
|
|
539
|
+
pending: ["sent", "failed"],
|
|
540
|
+
sent: ["delivered", "failed"],
|
|
541
|
+
failed: ["pending"], // retry
|
|
542
|
+
};
|
|
543
|
+
const allowed = validTransitions[m.status];
|
|
544
|
+
if (!allowed || !allowed.includes(status))
|
|
545
|
+
return {
|
|
546
|
+
updated: false,
|
|
547
|
+
reason: "invalid_transition",
|
|
548
|
+
from: m.status,
|
|
549
|
+
to: status,
|
|
550
|
+
};
|
|
551
|
+
|
|
552
|
+
m.status = status;
|
|
553
|
+
if (status === "sent" && txHash) m.source_tx_hash = txHash;
|
|
554
|
+
if (status === "delivered") {
|
|
555
|
+
m.delivered_at = _now();
|
|
556
|
+
if (txHash) m.destination_tx_hash = txHash;
|
|
557
|
+
}
|
|
558
|
+
if (status === "pending") m.retries += 1; // retry
|
|
559
|
+
|
|
560
|
+
db.prepare(
|
|
561
|
+
`UPDATE cc_messages SET status = ?, source_tx_hash = ?, destination_tx_hash = ?,
|
|
562
|
+
delivered_at = ?, retries = ? WHERE id = ?`,
|
|
563
|
+
).run(
|
|
564
|
+
m.status,
|
|
565
|
+
m.source_tx_hash,
|
|
566
|
+
m.destination_tx_hash,
|
|
567
|
+
m.delivered_at,
|
|
568
|
+
m.retries,
|
|
569
|
+
messageId,
|
|
570
|
+
);
|
|
571
|
+
|
|
572
|
+
return { updated: true };
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
export function getMessage(db, messageId) {
|
|
576
|
+
const m = _messages.get(messageId);
|
|
577
|
+
return m ? { ...m } : null;
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
export function listMessages(
|
|
581
|
+
db,
|
|
582
|
+
{ fromChain, toChain, status, limit = 50 } = {},
|
|
583
|
+
) {
|
|
584
|
+
let msgs = [..._messages.values()];
|
|
585
|
+
if (fromChain) msgs = msgs.filter((m) => m.from_chain === fromChain);
|
|
586
|
+
if (toChain) msgs = msgs.filter((m) => m.to_chain === toChain);
|
|
587
|
+
if (status) msgs = msgs.filter((m) => m.status === status);
|
|
588
|
+
return msgs
|
|
589
|
+
.sort((a, b) => b.created_at - a.created_at)
|
|
590
|
+
.slice(0, limit)
|
|
591
|
+
.map((m) => ({ ...m }));
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
/* ── Fee Estimation ────────────────────────────────────── */
|
|
595
|
+
|
|
596
|
+
// Heuristic base fees per chain (in USD equivalent)
|
|
597
|
+
const BASE_FEE = {
|
|
598
|
+
ethereum: 5.0,
|
|
599
|
+
polygon: 0.01,
|
|
600
|
+
bsc: 0.1,
|
|
601
|
+
arbitrum: 0.3,
|
|
602
|
+
solana: 0.005,
|
|
603
|
+
};
|
|
604
|
+
|
|
605
|
+
export function estimateFee({ fromChain, toChain, amount }) {
|
|
606
|
+
if (!_validateChain(fromChain) || !_validateChain(toChain))
|
|
607
|
+
return { fee: null, reason: "unsupported_chain" };
|
|
608
|
+
|
|
609
|
+
const baseFee = (BASE_FEE[fromChain] || 1.0) + (BASE_FEE[toChain] || 1.0);
|
|
610
|
+
const percentageFee = amount * (DEFAULT_CONFIG.feePercentage / 100);
|
|
611
|
+
const totalFee = Math.round((baseFee + percentageFee) * 1000) / 1000;
|
|
612
|
+
|
|
613
|
+
return {
|
|
614
|
+
fee: totalFee,
|
|
615
|
+
breakdown: {
|
|
616
|
+
sourceFee: BASE_FEE[fromChain] || 1.0,
|
|
617
|
+
destFee: BASE_FEE[toChain] || 1.0,
|
|
618
|
+
bridgeFee: Math.round(percentageFee * 1000) / 1000,
|
|
619
|
+
},
|
|
620
|
+
currency: "USD",
|
|
621
|
+
};
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
/* ── Stats ─────────────────────────────────────────────── */
|
|
625
|
+
|
|
626
|
+
export function getCrossChainStats(db) {
|
|
627
|
+
const bridges = [..._bridges.values()];
|
|
628
|
+
const swaps = [..._swaps.values()];
|
|
629
|
+
const msgs = [..._messages.values()];
|
|
630
|
+
|
|
631
|
+
const bridgesByStatus = {};
|
|
632
|
+
for (const b of bridges)
|
|
633
|
+
bridgesByStatus[b.status] = (bridgesByStatus[b.status] || 0) + 1;
|
|
634
|
+
|
|
635
|
+
const swapsByStatus = {};
|
|
636
|
+
for (const s of swaps)
|
|
637
|
+
swapsByStatus[s.status] = (swapsByStatus[s.status] || 0) + 1;
|
|
638
|
+
|
|
639
|
+
const msgsByStatus = {};
|
|
640
|
+
for (const m of msgs)
|
|
641
|
+
msgsByStatus[m.status] = (msgsByStatus[m.status] || 0) + 1;
|
|
642
|
+
|
|
643
|
+
let totalBridgeVolume = 0;
|
|
644
|
+
let totalFees = 0;
|
|
645
|
+
for (const b of bridges) {
|
|
646
|
+
totalBridgeVolume += b.amount;
|
|
647
|
+
totalFees += b.fee_amount;
|
|
648
|
+
}
|
|
649
|
+
|
|
650
|
+
return {
|
|
651
|
+
bridges: {
|
|
652
|
+
total: bridges.length,
|
|
653
|
+
byStatus: bridgesByStatus,
|
|
654
|
+
totalVolume: totalBridgeVolume,
|
|
655
|
+
totalFees,
|
|
656
|
+
},
|
|
657
|
+
swaps: { total: swaps.length, byStatus: swapsByStatus },
|
|
658
|
+
messages: { total: msgs.length, byStatus: msgsByStatus },
|
|
659
|
+
};
|
|
660
|
+
}
|
|
661
|
+
|
|
662
|
+
/* ── Reset (tests) ─────────────────────────────────────── */
|
|
663
|
+
|
|
664
|
+
export function _resetState() {
|
|
665
|
+
_bridges.clear();
|
|
666
|
+
_swaps.clear();
|
|
667
|
+
_messages.clear();
|
|
668
|
+
_chainConfigs.clear();
|
|
669
|
+
}
|