clinch-core 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js ADDED
@@ -0,0 +1,544 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ var __importDefault = (this && this.__importDefault) || function (mod) {
36
+ return (mod && mod.__esModule) ? mod : { "default": mod };
37
+ };
38
+ Object.defineProperty(exports, "__esModule", { value: true });
39
+ exports.ClinchSeller = exports.ClinchCore = void 0;
40
+ const events_1 = require("events");
41
+ const tweetnacl_1 = __importDefault(require("tweetnacl"));
42
+ const js_sha256_1 = require("js-sha256");
43
+ const ws_1 = __importDefault(require("ws"));
44
+ // ============================================================================
45
+ // CONFIGURATION & UTILS
46
+ // ============================================================================
47
+ const PROTOCOL_VERSION = "0.1.0";
48
+ const FIREBASE_CONFIG_URL = "https://clinchprotocol.web.app/network-config.json";
49
+ function toHex(arr) {
50
+ return Array.from(arr).map(b => b.toString(16).padStart(2, '0')).join('');
51
+ }
52
+ // ============================================================================
53
+ // THE CLINCH CORE LIBRARY (Buyer)
54
+ // ============================================================================
55
+ class ClinchCore extends events_1.EventEmitter {
56
+ config;
57
+ cachedRegistryUrl = null;
58
+ status = 'OFFLINE';
59
+ reconnectAttempts = 0;
60
+ maxReconnectDelay = 30000;
61
+ jwtToken = null;
62
+ identityPrivKey;
63
+ identityPubKey;
64
+ activeSessions = new Map();
65
+ ws = null;
66
+ // Sandbox Engine
67
+ isSandboxMode = false;
68
+ sandboxContext = null;
69
+ sandboxSequence = null;
70
+ sandboxSession = null;
71
+ sandboxMaxTurns = 6;
72
+ currentTurn = 0;
73
+ lastKnownPrice = 0;
74
+ activeNegotiationId = null;
75
+ constructor(config = {}) {
76
+ super();
77
+ this.config = { timeoutMs: 5000, ...config };
78
+ const keyPair = tweetnacl_1.default.sign.keyPair();
79
+ this.identityPrivKey = keyPair.secretKey;
80
+ this.identityPubKey = toHex(keyPair.publicKey);
81
+ if (this.config.registryUrl) {
82
+ this.cachedRegistryUrl = this.config.registryUrl;
83
+ }
84
+ }
85
+ setStatus(newStatus) {
86
+ if (this.status !== newStatus) {
87
+ this.status = newStatus;
88
+ this.emit('status_changed', this.status);
89
+ if (this.status === 'IDLE')
90
+ this.emit('log', `🟢 [State] ONLINE & IDLE`);
91
+ else if (this.status === 'ERROR')
92
+ this.emit('log', `🔴 [State] ERROR`);
93
+ else if (this.status === 'NEGOTIATING')
94
+ this.emit('log', `⚡ [State] NEGOTIATING (Turn ${this.currentTurn})`);
95
+ else
96
+ this.emit('log', `🟡 [State] ${this.status}`);
97
+ }
98
+ }
99
+ async initialize(cachedToken) {
100
+ if (this.status === 'IDLE' || this.status === 'CONNECTING')
101
+ return;
102
+ this.setStatus('CONNECTING');
103
+ try {
104
+ this.emit('log', '[Network] Fetching registry configuration...');
105
+ await this.getRegistryUrl();
106
+ if (cachedToken) {
107
+ this.jwtToken = cachedToken;
108
+ this.emit('log', "[Auth] Restored session from cached JWT. Skipping PoW.");
109
+ }
110
+ else {
111
+ await this.registerNode();
112
+ }
113
+ await this.connectWebSocket();
114
+ this.emit('initialized', { pubKey: this.identityPubKey, registry: this.cachedRegistryUrl });
115
+ }
116
+ catch (error) {
117
+ this.setStatus('ERROR');
118
+ this.emit('error', new Error(`Initialization failed: ${error.message}`));
119
+ setTimeout(() => this.initialize(cachedToken), 5000);
120
+ }
121
+ }
122
+ async getRegistryUrl(forceRefresh = false) {
123
+ if (this.cachedRegistryUrl && !forceRefresh)
124
+ return this.cachedRegistryUrl;
125
+ const controller = new AbortController();
126
+ const timeout = setTimeout(() => controller.abort(), this.config.timeoutMs);
127
+ try {
128
+ const res = await fetch(FIREBASE_CONFIG_URL, { signal: controller.signal });
129
+ clearTimeout(timeout);
130
+ const text = await res.text();
131
+ const config = JSON.parse(text);
132
+ this.cachedRegistryUrl = config.registry_nodes[PROTOCOL_VERSION];
133
+ return this.cachedRegistryUrl;
134
+ }
135
+ catch (err) {
136
+ clearTimeout(timeout);
137
+ throw new Error(`Registry config fetch failed: ${err.message}`);
138
+ }
139
+ }
140
+ async networkRequest(endpoint, options = {}, requireAuth = true) {
141
+ const baseUrl = await this.getRegistryUrl();
142
+ const headers = { ...options.headers };
143
+ if (requireAuth && this.jwtToken)
144
+ headers['Authorization'] = `Bearer ${this.jwtToken}`;
145
+ const controller = new AbortController();
146
+ const timeout = setTimeout(() => controller.abort(), this.config.timeoutMs);
147
+ try {
148
+ const res = await fetch(`${baseUrl}${endpoint}`, { ...options, headers, signal: controller.signal });
149
+ clearTimeout(timeout);
150
+ const text = await res.text();
151
+ if (!res.ok)
152
+ throw new Error(`HTTP ${res.status}: ${text}`);
153
+ return JSON.parse(text);
154
+ }
155
+ catch (err) {
156
+ clearTimeout(timeout);
157
+ throw new Error(`Network request to ${endpoint} failed: ${err.message}`);
158
+ }
159
+ }
160
+ async registerNode() {
161
+ this.emit('log', "[Auth] Requesting PoW challenge from registry...");
162
+ const challenge = await this.networkRequest('/api/auth/challenge', {}, false);
163
+ const powSolution = await this.solvePoW(challenge.nonce, challenge.difficulty);
164
+ this.emit('log', "[Auth] Submitting PoW solution...");
165
+ const authRes = await this.networkRequest('/api/auth/verify', {
166
+ method: 'POST',
167
+ headers: { 'Content-Type': 'application/json' },
168
+ body: JSON.stringify({
169
+ challenge_id: challenge.challenge_id,
170
+ pow_solution: powSolution,
171
+ pubKey: this.identityPubKey
172
+ })
173
+ }, false);
174
+ this.jwtToken = authRes.token;
175
+ this.emit('token_issued', { token: this.jwtToken });
176
+ }
177
+ async connectWebSocket() {
178
+ return new Promise((resolve, reject) => {
179
+ const wsUrl = this.cachedRegistryUrl.replace(/^http/, 'ws');
180
+ this.emit('log', `[Network] Connecting to WebSocket at ${wsUrl}...`);
181
+ this.ws = new ws_1.default(wsUrl);
182
+ const connectionTimeout = setTimeout(() => {
183
+ if (this.ws?.readyState !== ws_1.default.OPEN) {
184
+ this.ws?.close();
185
+ reject(new Error("WebSocket connection timeout"));
186
+ }
187
+ }, this.config.timeoutMs);
188
+ this.ws.on('open', () => {
189
+ clearTimeout(connectionTimeout);
190
+ this.reconnectAttempts = 0;
191
+ this.emit('log', '[Network] WebSocket connected. Sending AUTH...');
192
+ this.ws.send(JSON.stringify({ type: 'AUTH', token: this.jwtToken }));
193
+ });
194
+ this.ws.on('message', (data) => {
195
+ const msg = JSON.parse(data.toString());
196
+ if (msg.type === 'AUTH_SUCCESS') {
197
+ this.setStatus('IDLE');
198
+ resolve();
199
+ }
200
+ if (msg.type === 'CALLBACK') {
201
+ this.emit('log', `🔔 [Network] Received callback for session ${msg.session_id}`);
202
+ this.emit('callback_received', { sessionId: msg.session_id, payload: msg.payload });
203
+ this.ws.send(JSON.stringify({ type: 'ACK', id: msg.id }));
204
+ }
205
+ });
206
+ this.ws.on('error', (err) => {
207
+ clearTimeout(connectionTimeout);
208
+ if (this.status === 'CONNECTING')
209
+ reject(err);
210
+ });
211
+ this.ws.on('close', () => {
212
+ clearTimeout(connectionTimeout);
213
+ if (this.status !== 'OFFLINE')
214
+ this.handleReconnect();
215
+ });
216
+ });
217
+ }
218
+ handleReconnect() {
219
+ this.setStatus('RECONNECTING');
220
+ this.reconnectAttempts++;
221
+ const delay = Math.min(1000 * Math.pow(2, this.reconnectAttempts - 1), this.maxReconnectDelay);
222
+ setTimeout(() => this.connectWebSocket().catch(() => { }), delay);
223
+ }
224
+ disconnect() {
225
+ this.setStatus('OFFLINE');
226
+ if (this.ws) {
227
+ this.ws.close();
228
+ this.ws = null;
229
+ }
230
+ }
231
+ // --------------------------------------------------------------------------
232
+ // UNIVERSAL PROMPT BUILDER
233
+ // --------------------------------------------------------------------------
234
+ /**
235
+ * Generates a universally formatted System Prompt for external LLMs (Claude, OpenAI, Gemini).
236
+ * Developers can pass this string directly to their AI to ensure protocol-compliant negotiation.
237
+ */
238
+ buildAgentPrompt(sessionId, incomingMessage) {
239
+ const session = this.activeSessions.get(sessionId);
240
+ if (!session)
241
+ throw new Error("Cannot build prompt: Session not found.");
242
+ const gap = this.lastKnownPrice - session.constraints.max_budget;
243
+ const gapText = gap > 0 ? `-$${gap} (Over budget)` : `+$${Math.abs(gap)} (Under budget)`;
244
+ return `You are a professional AI purchasing agent negotiating via the Clinch Protocol.
245
+ Your only goal is to secure the requested item below the maximum budget.
246
+
247
+ NEGOTIATION STATE:
248
+ - Item: ${session.constraints.item}
249
+ - Category: ${session.constraints.category || 'General'}
250
+ - Your absolute max budget: $${session.constraints.max_budget}
251
+ - Current turn: ${this.currentTurn}
252
+ - Last seller price: $${this.lastKnownPrice}
253
+ - Gap to budget: ${gapText}
254
+
255
+ SELLER'S LATEST MESSAGE:
256
+ "${incomingMessage}"
257
+
258
+ STRATEGY GUIDELINES:
259
+ - Turns 1-2: Open roughly 20-30% below budget to establish an anchor. Be professional but firm.
260
+ - Turns 3-5: Move in small, calculated increments (3-5%).
261
+ - Turn 6+: Issue a final, compelling offer.
262
+ - If the seller's price is at or below your max budget: You MUST choose "accept".
263
+
264
+ OUTPUT FORMAT:
265
+ You MUST respond ONLY in valid JSON matching this exact schema. Do not include markdown blocks (like \`\`\`json).
266
+ {
267
+ "action": "counter" | "accept" | "exit",
268
+ "price": <numeric value, no currency symbols>,
269
+ "message": "<One concise sentence of negotiation dialogue>"
270
+ }`;
271
+ }
272
+ // --------------------------------------------------------------------------
273
+ // PROTOCOL OPERATIONS
274
+ // --------------------------------------------------------------------------
275
+ async search(query, mode) {
276
+ let url = `/api/discover?category=${encodeURIComponent(query)}`;
277
+ if (mode)
278
+ url += `&mode=${encodeURIComponent(mode)}`;
279
+ return await this.networkRequest(url);
280
+ }
281
+ async negotiate(address, constraints) {
282
+ this.emit('log', `[Protocol] Initiating handshake with ${address}...`);
283
+ const parsed = this.parseAddress(address);
284
+ const ephemeralKeys = tweetnacl_1.default.sign.keyPair();
285
+ const ephemeralPubHex = toHex(ephemeralKeys.publicKey);
286
+ const initPayload = {
287
+ clinch_version: PROTOCOL_VERSION,
288
+ mode: parsed.mode,
289
+ constraints,
290
+ session_pub_key: ephemeralPubHex,
291
+ timestamp_utc: new Date().toISOString()
292
+ };
293
+ const msgUint8 = new TextEncoder().encode(JSON.stringify(initPayload));
294
+ const signature = toHex(tweetnacl_1.default.sign.detached(msgUint8, ephemeralKeys.secretKey));
295
+ const response = await this.networkRequest(`/api/route/${parsed.domain}/handshake`, {
296
+ method: 'POST',
297
+ headers: { 'Content-Type': 'application/json' },
298
+ body: JSON.stringify({ ...initPayload, sig: signature })
299
+ });
300
+ this.activeSessions.set(response.session_id, {
301
+ sessionId: response.session_id,
302
+ sellerId: parsed.domain,
303
+ keyPair: ephemeralKeys,
304
+ status: 'ACTIVE',
305
+ constraints
306
+ });
307
+ this.activeNegotiationId = response.session_id;
308
+ this.currentTurn = 1;
309
+ this.setStatus('NEGOTIATING');
310
+ return response.session_id;
311
+ }
312
+ async sendCounter(sessionId, price, reason) {
313
+ const session = this.activeSessions.get(sessionId);
314
+ if (!session)
315
+ throw new Error("Active session not found");
316
+ const payload = { session_id: sessionId, turn: this.currentTurn, price, reason };
317
+ const buyer_sig = toHex(tweetnacl_1.default.sign.detached(new TextEncoder().encode(JSON.stringify(payload)), session.keyPair.secretKey));
318
+ await this.networkRequest(`/api/route/${session.sellerId}/counter`, {
319
+ method: 'POST',
320
+ headers: { 'Content-Type': 'application/json' },
321
+ body: JSON.stringify({ ...payload, buyer_sig })
322
+ });
323
+ }
324
+ async exitSession(sessionId) {
325
+ const session = this.activeSessions.get(sessionId);
326
+ if (!session)
327
+ throw new Error("Session not found");
328
+ const res = await this.networkRequest(`/api/route/${sessionId}/exit`, {
329
+ method: 'POST',
330
+ headers: { 'Content-Type': 'application/json' },
331
+ body: JSON.stringify({ seller_id: session.sellerId })
332
+ });
333
+ session.status = 'EXITED';
334
+ session.exitTokenHash = res.token_hash;
335
+ return res.token_hash;
336
+ }
337
+ // --------------------------------------------------------------------------
338
+ // OUT-OF-THE-BOX SANDBOX (AUTO-TURN ENGINE)
339
+ // --------------------------------------------------------------------------
340
+ async sandbox(config = {}) {
341
+ this.isSandboxMode = true;
342
+ this.sandboxMaxTurns = config.maxTurns || 6;
343
+ await this.initialize();
344
+ await this.setupSandbox(config);
345
+ this.on('callback_received', async (event) => {
346
+ if (!this.isSandboxMode)
347
+ return;
348
+ await this.handleAutomaticSandboxTurn(event.sessionId, event.payload);
349
+ });
350
+ this.emit('log', "⚙️ [Sandbox] Auto-Negotiation engine bound and active.");
351
+ }
352
+ async setupSandbox(config = {}) {
353
+ const settings = {
354
+ downloadLLM: true,
355
+ modelUrl: "https://huggingface.co/Qwen/Qwen2.5-1.5B-Instruct-GGUF/resolve/main/qwen2.5-1.5b-instruct-q4_k_m.gguf",
356
+ modelPath: "./qwen2.5-1.5b-instruct-q4_k_m.gguf",
357
+ ...config
358
+ };
359
+ // DYNAMIC IMPORTS: Won't break Webpack/Metro unless sandbox() is actually called!
360
+ let nodeLlama;
361
+ try {
362
+ nodeLlama = await Promise.resolve().then(() => __importStar(require('node-llama-cpp')));
363
+ }
364
+ catch (e) {
365
+ throw new Error("Sandbox requires 'node-llama-cpp'. Run: npm install node-llama-cpp");
366
+ }
367
+ const fs = await Promise.resolve().then(() => __importStar(require('fs')));
368
+ const path = await Promise.resolve().then(() => __importStar(require('path')));
369
+ const os = await Promise.resolve().then(() => __importStar(require('os')));
370
+ const resolvedPath = path.resolve(settings.modelPath);
371
+ if (!fs.existsSync(resolvedPath)) {
372
+ if (!settings.downloadLLM)
373
+ throw new Error(`Model missing at ${resolvedPath}`);
374
+ this.emit('log', `[Sandbox] Downloading Qwen 1.5B Q4_K_M (1.1GB)...`);
375
+ const { pipeline } = await Promise.resolve().then(() => __importStar(require('stream/promises')));
376
+ const { Readable } = await Promise.resolve().then(() => __importStar(require('stream')));
377
+ // @ts-ignore
378
+ const response = await fetch(settings.modelUrl);
379
+ if (!response.ok)
380
+ throw new Error("Fetch failed: " + response.statusText);
381
+ const fileStream = fs.createWriteStream(resolvedPath);
382
+ await pipeline(Readable.fromWeb(response.body), fileStream);
383
+ this.emit('log', "[Sandbox] Download complete.");
384
+ }
385
+ const llama = await nodeLlama.getLlama();
386
+ const model = await llama.loadModel({ modelPath: resolvedPath });
387
+ this.sandboxContext = await model.createContext({
388
+ contextSize: 2048,
389
+ threads: Math.min(4, Math.max(1, os.cpus().length - 1)),
390
+ batchSize: 512
391
+ });
392
+ }
393
+ async handleAutomaticSandboxTurn(sessionId, payload) {
394
+ const session = this.activeSessions.get(sessionId);
395
+ if (!session)
396
+ return;
397
+ this.currentTurn++;
398
+ this.setStatus('NEGOTIATING');
399
+ const incomingPrice = this.extractPrice(payload.message || JSON.stringify(payload), 'Counter');
400
+ if (incomingPrice !== null)
401
+ this.lastKnownPrice = incomingPrice;
402
+ if (this.lastKnownPrice <= session.constraints.max_budget) {
403
+ this.setStatus('CONVERGED');
404
+ this.activeSessions.delete(sessionId);
405
+ return;
406
+ }
407
+ if (this.currentTurn > this.sandboxMaxTurns) {
408
+ this.setStatus('STALEMATE');
409
+ await this.exitSession(sessionId);
410
+ return;
411
+ }
412
+ const modelResponse = await this.sandboxEvaluate(session, payload.message || `The price is $${this.lastKnownPrice}`);
413
+ const parsedOffer = this.extractPrice(modelResponse, 'Offer');
414
+ const reasonLine = modelResponse.split('\n').find(l => l.startsWith('Reason:'));
415
+ const reason = reasonLine ? reasonLine.replace('Reason:', '').trim() : "Suggesting a fair counter-offer.";
416
+ if (parsedOffer !== null) {
417
+ const finalOffer = Math.min(parsedOffer, session.constraints.max_budget);
418
+ await this.sendCounter(sessionId, finalOffer, reason);
419
+ }
420
+ }
421
+ async sandboxEvaluate(session, incomingOffer) {
422
+ const { LlamaChatSession, ChatMLChatWrapper } = await Promise.resolve().then(() => __importStar(require('node-llama-cpp')));
423
+ const systemPrompt = this.buildAgentPrompt(session.sessionId, incomingOffer);
424
+ if (this.sandboxSequence)
425
+ this.sandboxSequence.dispose();
426
+ this.sandboxSequence = this.sandboxContext.getSequence();
427
+ this.sandboxSession = new LlamaChatSession({
428
+ contextSequence: this.sandboxSequence,
429
+ systemPrompt: systemPrompt,
430
+ chatWrapper: new ChatMLChatWrapper()
431
+ });
432
+ let responseText = "";
433
+ await this.sandboxSession.prompt(incomingOffer, {
434
+ maxTokens: 80,
435
+ onTextChunk: (chunk) => { responseText += chunk; }
436
+ });
437
+ return responseText;
438
+ }
439
+ // --------------------------------------------------------------------------
440
+ // UTILITIES
441
+ // --------------------------------------------------------------------------
442
+ extractPrice(text, prefix) {
443
+ const regex = new RegExp(`${prefix}\\s*\\:?\\s*\\$?(\\d+(?:\\.\\d{2})?)`, 'i');
444
+ const match = text.match(regex);
445
+ if (match)
446
+ return parseFloat(match[1]);
447
+ const fallbackRegex = /"price"\s*:\s*(\d+(?:\.\d{2})?)/i;
448
+ const fallbackMatch = text.match(fallbackRegex);
449
+ return fallbackMatch ? parseFloat(fallbackMatch[1]) : null;
450
+ }
451
+ parseAddress(address) {
452
+ const parts = address.split('.');
453
+ if (parts.length < 2)
454
+ throw new Error("Invalid Address Format");
455
+ return { mode: parts[0], domain: parts.slice(1).join('.').toLowerCase(), route: '/' };
456
+ }
457
+ async solvePoW(nonce, difficultyBits) {
458
+ let counter = 0;
459
+ const CHUNK_SIZE = 10000;
460
+ while (true) {
461
+ for (let i = 0; i < CHUNK_SIZE; i++) {
462
+ const attempt = counter.toString();
463
+ const hashArray = js_sha256_1.sha256.create().update(nonce + this.identityPubKey + attempt).array();
464
+ if (this.hasLeadingZeroBits(hashArray, difficultyBits))
465
+ return attempt;
466
+ counter++;
467
+ }
468
+ await new Promise(resolve => setTimeout(resolve, 0));
469
+ }
470
+ }
471
+ hasLeadingZeroBits(hash, bits) {
472
+ let zeroBits = 0;
473
+ for (const byte of hash) {
474
+ if (byte === 0)
475
+ zeroBits += 8;
476
+ else {
477
+ zeroBits += Math.clz32(byte) - 24;
478
+ break;
479
+ }
480
+ }
481
+ return zeroBits >= bits;
482
+ }
483
+ }
484
+ exports.ClinchCore = ClinchCore;
485
+ class ClinchSeller extends events_1.EventEmitter {
486
+ config;
487
+ cachedRegistryUrl = null;
488
+ sellerAuthToken = null;
489
+ identityPrivKey;
490
+ identityPubKey;
491
+ constructor(config = {}) {
492
+ super();
493
+ this.config = { timeoutMs: 5000, ...config };
494
+ const keyPair = tweetnacl_1.default.sign.keyPair();
495
+ this.identityPrivKey = keyPair.secretKey;
496
+ this.identityPubKey = toHex(keyPair.publicKey);
497
+ if (this.config.registryUrl)
498
+ this.cachedRegistryUrl = this.config.registryUrl;
499
+ }
500
+ async authenticate(authToken) {
501
+ this.sellerAuthToken = authToken;
502
+ if (!this.cachedRegistryUrl) {
503
+ const res = await fetch(FIREBASE_CONFIG_URL);
504
+ const config = JSON.parse(await res.text());
505
+ this.cachedRegistryUrl = config.registry_nodes[PROTOCOL_VERSION];
506
+ }
507
+ }
508
+ async registerEndpoint(record) {
509
+ if (!this.sellerAuthToken)
510
+ throw new Error("Must call authenticate() first.");
511
+ const payload = {
512
+ ...record,
513
+ public_key: this.identityPubKey,
514
+ record_sig: this.signData(record)
515
+ };
516
+ const res = await fetch(`${this.cachedRegistryUrl}/api/dashboard/sellers/register`, {
517
+ method: 'POST',
518
+ headers: {
519
+ 'Content-Type': 'application/json',
520
+ 'Authorization': `Bearer ${this.sellerAuthToken}`
521
+ },
522
+ body: JSON.stringify(payload)
523
+ });
524
+ if (!res.ok)
525
+ throw new Error(`Registration failed: ${await res.text()}`);
526
+ return await res.json();
527
+ }
528
+ verifyBuyerSignature(payload, signatureHex, buyerSessionPubKeyHex) {
529
+ try {
530
+ const msgUint8 = new TextEncoder().encode(JSON.stringify(payload));
531
+ const sigUint8 = new Uint8Array(signatureHex.match(/.{1,2}/g).map(byte => parseInt(byte, 16)));
532
+ const pubKeyUint8 = new Uint8Array(buyerSessionPubKeyHex.match(/.{1,2}/g).map(byte => parseInt(byte, 16)));
533
+ return tweetnacl_1.default.sign.detached.verify(msgUint8, sigUint8, pubKeyUint8);
534
+ }
535
+ catch (e) {
536
+ return false;
537
+ }
538
+ }
539
+ signData(data) {
540
+ const msgUint8 = new TextEncoder().encode(JSON.stringify(data));
541
+ return toHex(tweetnacl_1.default.sign.detached(msgUint8, this.identityPrivKey));
542
+ }
543
+ }
544
+ exports.ClinchSeller = ClinchSeller;
@@ -0,0 +1,46 @@
1
+ import { EventEmitter } from 'events';
2
+ import nacl from 'tweetnacl';
3
+ export interface ParsedAddress {
4
+ mode: string;
5
+ domain: string;
6
+ route: string;
7
+ }
8
+ export interface ClinchConfig {
9
+ registryUrl?: string;
10
+ }
11
+ export interface ConstraintVector {
12
+ intent: string;
13
+ category?: string;
14
+ max_price_usd_bracket?: string;
15
+ [key: string]: any;
16
+ }
17
+ export interface SessionState {
18
+ sessionId: string;
19
+ sellerId: string;
20
+ keyPair: nacl.SignKeyPair;
21
+ status: 'ACTIVE' | 'EXITED' | 'CLOSED';
22
+ exitTokenHash?: string;
23
+ }
24
+ export declare class ClinchCore extends EventEmitter {
25
+ private isInitialized;
26
+ private config;
27
+ private cachedRegistryUrl;
28
+ jwtToken: string | null;
29
+ private identityPrivKey;
30
+ identityPubKey: string;
31
+ private activeSessions;
32
+ private ws;
33
+ constructor(config?: ClinchConfig);
34
+ initialize(cachedToken?: string): Promise<void>;
35
+ private registerNode;
36
+ private getRegistryUrl;
37
+ private networkRequest;
38
+ private connectWebSocket;
39
+ disconnect(): void;
40
+ search(query: string, mode?: string): Promise<any>;
41
+ negotiate(address: string, constraints: ConstraintVector): Promise<string>;
42
+ exitSession(sessionId: string): Promise<string>;
43
+ parseAddress(address: string): ParsedAddress;
44
+ private solvePoW;
45
+ private hasLeadingZeroBits;
46
+ }
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,QAAQ,CAAC;AACtC,OAAO,IAAI,MAAM,WAAW,CAAC;AAiB7B,MAAM,WAAW,aAAa;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,YAAY;IACzB,WAAW,CAAC,EAAE,MAAM,CAAC;CACxB;AAED,MAAM,WAAW,gBAAgB;IAC7B,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,qBAAqB,CAAC,EAAE,MAAM,CAAC;IAC/B,CAAC,GAAG,EAAE,MAAM,GAAG,GAAG,CAAC;CACtB;AAED,MAAM,WAAW,YAAY;IACzB,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,IAAI,CAAC,WAAW,CAAC;IAC1B,MAAM,EAAE,QAAQ,GAAG,QAAQ,GAAG,QAAQ,CAAC;IACvC,aAAa,CAAC,EAAE,MAAM,CAAC;CAC1B;AAKD,qBAAa,UAAW,SAAQ,YAAY;IACxC,OAAO,CAAC,aAAa,CAAS;IAC9B,OAAO,CAAC,MAAM,CAAe;IAC7B,OAAO,CAAC,iBAAiB,CAAuB;IAEzC,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAQ;IAGtC,OAAO,CAAC,eAAe,CAAa;IAC7B,cAAc,EAAE,MAAM,CAAC;IAG9B,OAAO,CAAC,cAAc,CAAmC;IACzD,OAAO,CAAC,EAAE,CAA0B;gBAExB,MAAM,GAAE,YAAiB;IAiB/B,UAAU,CAAC,WAAW,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;YAuBvC,YAAY;YAoBZ,cAAc;YAcd,cAAc;YAkBd,gBAAgB;IA0CvB,UAAU;IAUX,MAAM,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC;IAMlD,SAAS,CAAC,OAAO,EAAE,MAAM,EAAE,WAAW,EAAE,gBAAgB,GAAG,OAAO,CAAC,MAAM,CAAC;IAsC1E,WAAW,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAoB9C,YAAY,CAAC,OAAO,EAAE,MAAM,GAAG,aAAa;YAUrC,QAAQ;IAoBtB,OAAO,CAAC,kBAAkB;CAQ7B"}