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/.github/workflows/publish.yml +29 -0
- package/LICENSE +21 -0
- package/README.md +245 -0
- package/dist/index.d.ts +101 -0
- package/dist/index.js +544 -0
- package/dist/src/index.d.ts +46 -0
- package/dist/src/index.d.ts.map +1 -0
- package/dist/src/index.js +247 -0
- package/dist/src/index.js.map +1 -0
- package/dist/test.js +70 -0
- package/package.json +28 -0
- package/src/index.d.ts +46 -0
- package/src/index.d.ts.map +1 -0
- package/src/index.js +247 -0
- package/src/index.js.map +1 -0
- package/src/index.ts +612 -0
- package/test.ts +16 -0
- package/tsconfig.json +14 -0
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.ClinchCore = void 0;
|
|
7
|
+
const events_1 = require("events");
|
|
8
|
+
const tweetnacl_1 = __importDefault(require("tweetnacl"));
|
|
9
|
+
const js_sha256_1 = require("js-sha256");
|
|
10
|
+
const ws_1 = __importDefault(require("ws"));
|
|
11
|
+
// ============================================================================
|
|
12
|
+
// CONFIGURATION & UTILS
|
|
13
|
+
// ============================================================================
|
|
14
|
+
const PROTOCOL_VERSION = "0.1.0";
|
|
15
|
+
const FIREBASE_CONFIG_URL = "https://clinchprotocol.web.app/network-config.json";
|
|
16
|
+
function toHex(arr) {
|
|
17
|
+
return Array.from(arr).map(b => b.toString(16).padStart(2, '0')).join('');
|
|
18
|
+
}
|
|
19
|
+
// ============================================================================
|
|
20
|
+
// THE CLINCH CORE LIBRARY
|
|
21
|
+
// ============================================================================
|
|
22
|
+
class ClinchCore extends events_1.EventEmitter {
|
|
23
|
+
isInitialized = false;
|
|
24
|
+
config;
|
|
25
|
+
cachedRegistryUrl = null;
|
|
26
|
+
jwtToken = null;
|
|
27
|
+
// Agent Identity Keypair (Long-lived, used for Auth, NEVER for session messages)
|
|
28
|
+
identityPrivKey;
|
|
29
|
+
identityPubKey;
|
|
30
|
+
// Active Sessions & Ephemeral Keys
|
|
31
|
+
activeSessions = new Map();
|
|
32
|
+
ws = null;
|
|
33
|
+
constructor(config = {}) {
|
|
34
|
+
super();
|
|
35
|
+
this.config = config;
|
|
36
|
+
// Generate Identity Key
|
|
37
|
+
const keyPair = tweetnacl_1.default.sign.keyPair();
|
|
38
|
+
this.identityPrivKey = keyPair.secretKey;
|
|
39
|
+
this.identityPubKey = toHex(keyPair.publicKey);
|
|
40
|
+
if (this.config.registryUrl) {
|
|
41
|
+
this.cachedRegistryUrl = this.config.registryUrl;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
// --------------------------------------------------------------------------
|
|
45
|
+
// INITIALIZATION & AUTH
|
|
46
|
+
// --------------------------------------------------------------------------
|
|
47
|
+
async initialize(cachedToken) {
|
|
48
|
+
if (this.isInitialized)
|
|
49
|
+
return;
|
|
50
|
+
try {
|
|
51
|
+
await this.getRegistryUrl();
|
|
52
|
+
if (cachedToken) {
|
|
53
|
+
this.jwtToken = cachedToken;
|
|
54
|
+
this.emit('log', "[Core] Restored session from cached JWT. Skipping PoW.");
|
|
55
|
+
}
|
|
56
|
+
else {
|
|
57
|
+
await this.registerNode();
|
|
58
|
+
}
|
|
59
|
+
await this.connectWebSocket();
|
|
60
|
+
this.isInitialized = true;
|
|
61
|
+
this.emit('initialized', { pubKey: this.identityPubKey, registry: this.cachedRegistryUrl });
|
|
62
|
+
}
|
|
63
|
+
catch (error) {
|
|
64
|
+
this.emit('error', error);
|
|
65
|
+
throw error;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
async registerNode() {
|
|
69
|
+
this.emit('log', "[Core] No cached token found. Requesting PoW challenge...");
|
|
70
|
+
const challenge = await this.networkRequest('/api/auth/challenge', {}, false);
|
|
71
|
+
const powSolution = await this.solvePoW(challenge.nonce, challenge.difficulty);
|
|
72
|
+
const authRes = await this.networkRequest('/api/auth/verify', {
|
|
73
|
+
method: 'POST',
|
|
74
|
+
headers: { 'Content-Type': 'application/json' },
|
|
75
|
+
body: JSON.stringify({
|
|
76
|
+
challenge_id: challenge.challenge_id,
|
|
77
|
+
pow_solution: powSolution,
|
|
78
|
+
pubKey: this.identityPubKey
|
|
79
|
+
})
|
|
80
|
+
}, false);
|
|
81
|
+
this.jwtToken = authRes.token;
|
|
82
|
+
this.emit('token_issued', { token: this.jwtToken });
|
|
83
|
+
}
|
|
84
|
+
async getRegistryUrl(forceRefresh = false) {
|
|
85
|
+
if (this.cachedRegistryUrl && !forceRefresh)
|
|
86
|
+
return this.cachedRegistryUrl;
|
|
87
|
+
const res = await fetch(FIREBASE_CONFIG_URL);
|
|
88
|
+
const text = await res.text();
|
|
89
|
+
try {
|
|
90
|
+
const config = JSON.parse(text);
|
|
91
|
+
this.cachedRegistryUrl = config.registry_nodes[PROTOCOL_VERSION];
|
|
92
|
+
return this.cachedRegistryUrl;
|
|
93
|
+
}
|
|
94
|
+
catch (err) {
|
|
95
|
+
throw new Error(`Failed to parse Firebase config.`);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
async networkRequest(endpoint, options = {}, requireAuth = true) {
|
|
99
|
+
const baseUrl = await this.getRegistryUrl();
|
|
100
|
+
const headers = { ...options.headers };
|
|
101
|
+
if (requireAuth && this.jwtToken) {
|
|
102
|
+
headers['Authorization'] = `Bearer ${this.jwtToken}`;
|
|
103
|
+
}
|
|
104
|
+
const res = await fetch(`${baseUrl}${endpoint}`, { ...options, headers });
|
|
105
|
+
const text = await res.text();
|
|
106
|
+
if (!res.ok)
|
|
107
|
+
throw new Error(`HTTP ${res.status} on ${endpoint}: ${text}`);
|
|
108
|
+
return JSON.parse(text);
|
|
109
|
+
}
|
|
110
|
+
// --------------------------------------------------------------------------
|
|
111
|
+
// WEBSOCKET CONNECTION
|
|
112
|
+
// --------------------------------------------------------------------------
|
|
113
|
+
async connectWebSocket() {
|
|
114
|
+
return new Promise((resolve, reject) => {
|
|
115
|
+
const wsUrl = this.cachedRegistryUrl.replace(/^http/, 'ws');
|
|
116
|
+
this.ws = new ws_1.default(wsUrl);
|
|
117
|
+
this.ws.on('open', () => {
|
|
118
|
+
this.ws.send(JSON.stringify({ type: 'AUTH', token: this.jwtToken }));
|
|
119
|
+
});
|
|
120
|
+
this.ws.on('message', (data) => {
|
|
121
|
+
const msg = JSON.parse(data.toString());
|
|
122
|
+
if (msg.type === 'AUTH_SUCCESS') {
|
|
123
|
+
this.emit('log', '[Core] WebSocket authenticated & listening.');
|
|
124
|
+
resolve();
|
|
125
|
+
}
|
|
126
|
+
if (msg.type === 'CALLBACK') {
|
|
127
|
+
this.emit('callback_received', {
|
|
128
|
+
sessionId: msg.session_id,
|
|
129
|
+
payload: msg.payload
|
|
130
|
+
});
|
|
131
|
+
this.ws.send(JSON.stringify({ type: 'ACK', id: msg.id }));
|
|
132
|
+
}
|
|
133
|
+
if (msg.type === 'ERROR') {
|
|
134
|
+
this.emit('error', new Error(msg.message));
|
|
135
|
+
}
|
|
136
|
+
});
|
|
137
|
+
this.ws.on('error', (err) => {
|
|
138
|
+
this.emit('error', err);
|
|
139
|
+
reject(err);
|
|
140
|
+
});
|
|
141
|
+
this.ws.on('close', () => {
|
|
142
|
+
this.emit('log', '[Core] WebSocket disconnected.');
|
|
143
|
+
});
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
disconnect() {
|
|
147
|
+
if (this.ws) {
|
|
148
|
+
this.ws.close();
|
|
149
|
+
this.ws = null;
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
// --------------------------------------------------------------------------
|
|
153
|
+
// PROTOCOL OPERATIONS
|
|
154
|
+
// --------------------------------------------------------------------------
|
|
155
|
+
async search(query, mode) {
|
|
156
|
+
let url = `/api/discover?category=${encodeURIComponent(query)}`;
|
|
157
|
+
if (mode)
|
|
158
|
+
url += `&mode=${encodeURIComponent(mode)}`;
|
|
159
|
+
return await this.networkRequest(url);
|
|
160
|
+
}
|
|
161
|
+
async negotiate(address, constraints) {
|
|
162
|
+
const parsed = this.parseAddress(address);
|
|
163
|
+
const ephemeralKeys = tweetnacl_1.default.sign.keyPair();
|
|
164
|
+
const ephemeralPubHex = toHex(ephemeralKeys.publicKey);
|
|
165
|
+
const initPayload = {
|
|
166
|
+
clinch_version: PROTOCOL_VERSION,
|
|
167
|
+
mode: parsed.mode,
|
|
168
|
+
constraints,
|
|
169
|
+
session_pub_key: ephemeralPubHex,
|
|
170
|
+
timestamp_utc: new Date().toISOString()
|
|
171
|
+
};
|
|
172
|
+
const msgUint8 = new TextEncoder().encode(JSON.stringify(initPayload));
|
|
173
|
+
const signature = toHex(tweetnacl_1.default.sign.detached(msgUint8, ephemeralKeys.secretKey));
|
|
174
|
+
const signedPayload = { ...initPayload, sig: signature };
|
|
175
|
+
const response = await this.networkRequest(`/api/route/${parsed.domain}/handshake`, {
|
|
176
|
+
method: 'POST',
|
|
177
|
+
headers: { 'Content-Type': 'application/json' },
|
|
178
|
+
body: JSON.stringify(signedPayload)
|
|
179
|
+
});
|
|
180
|
+
const sessionId = response.session_id;
|
|
181
|
+
this.activeSessions.set(sessionId, {
|
|
182
|
+
sessionId,
|
|
183
|
+
sellerId: parsed.domain,
|
|
184
|
+
keyPair: ephemeralKeys,
|
|
185
|
+
status: 'ACTIVE'
|
|
186
|
+
});
|
|
187
|
+
this.emit('session_started', { sessionId, sellerId: parsed.domain });
|
|
188
|
+
return sessionId;
|
|
189
|
+
}
|
|
190
|
+
async exitSession(sessionId) {
|
|
191
|
+
const session = this.activeSessions.get(sessionId);
|
|
192
|
+
if (!session)
|
|
193
|
+
throw new Error("Session not found or not active");
|
|
194
|
+
const res = await this.networkRequest(`/api/route/${sessionId}/exit`, {
|
|
195
|
+
method: 'POST',
|
|
196
|
+
headers: { 'Content-Type': 'application/json' },
|
|
197
|
+
body: JSON.stringify({ seller_id: session.sellerId })
|
|
198
|
+
});
|
|
199
|
+
session.status = 'EXITED';
|
|
200
|
+
session.exitTokenHash = res.token_hash;
|
|
201
|
+
this.emit('session_exited', { sessionId, token_hash: res.token_hash });
|
|
202
|
+
return res.token_hash;
|
|
203
|
+
}
|
|
204
|
+
// --------------------------------------------------------------------------
|
|
205
|
+
// UTILITIES
|
|
206
|
+
// --------------------------------------------------------------------------
|
|
207
|
+
parseAddress(address) {
|
|
208
|
+
const parts = address.split('.');
|
|
209
|
+
if (parts.length < 2)
|
|
210
|
+
throw new Error("Invalid Address Format. Expected MODE.DOMAIN.anp");
|
|
211
|
+
return {
|
|
212
|
+
mode: parts[0],
|
|
213
|
+
domain: parts.slice(1).join('.').toLowerCase(),
|
|
214
|
+
route: '/'
|
|
215
|
+
};
|
|
216
|
+
}
|
|
217
|
+
async solvePoW(nonce, difficultyBits) {
|
|
218
|
+
this.emit('pow_started', { difficulty: difficultyBits });
|
|
219
|
+
let counter = 0;
|
|
220
|
+
const CHUNK_SIZE = 5000;
|
|
221
|
+
while (true) {
|
|
222
|
+
for (let i = 0; i < CHUNK_SIZE; i++) {
|
|
223
|
+
const attempt = counter.toString();
|
|
224
|
+
const hashArray = js_sha256_1.sha256.create().update(nonce + this.identityPubKey + attempt).array();
|
|
225
|
+
if (this.hasLeadingZeroBits(hashArray, difficultyBits)) {
|
|
226
|
+
this.emit('pow_solved', { attempts: counter });
|
|
227
|
+
return attempt;
|
|
228
|
+
}
|
|
229
|
+
counter++;
|
|
230
|
+
}
|
|
231
|
+
await new Promise(resolve => setTimeout(resolve, 0));
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
hasLeadingZeroBits(hash, bits) {
|
|
235
|
+
let zeroBits = 0;
|
|
236
|
+
for (const byte of hash) {
|
|
237
|
+
if (byte === 0)
|
|
238
|
+
zeroBits += 8;
|
|
239
|
+
else {
|
|
240
|
+
zeroBits += Math.clz32(byte) - 24;
|
|
241
|
+
break;
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
return zeroBits >= bits;
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
exports.ClinchCore = ClinchCore;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"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"}
|
package/dist/test.js
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
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
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
const index_1 = require("./src/index");
|
|
37
|
+
const fs = __importStar(require("fs"));
|
|
38
|
+
const TOKEN_FILE = './clinch-identity.txt';
|
|
39
|
+
const myHostAi = {
|
|
40
|
+
async evaluateOffer() { return { action: 'counter' }; }
|
|
41
|
+
};
|
|
42
|
+
async function run() {
|
|
43
|
+
console.log("=== CLINCH CORE: CACHED AUTH RUN ===\n");
|
|
44
|
+
const clinch = new index_1.ClinchCore();
|
|
45
|
+
let savedToken;
|
|
46
|
+
if (fs.existsSync(TOKEN_FILE)) {
|
|
47
|
+
savedToken = fs.readFileSync(TOKEN_FILE, 'utf-8');
|
|
48
|
+
console.log("📂 Found saved JWT on disk!");
|
|
49
|
+
}
|
|
50
|
+
clinch.on('pow_started', (d) => console.log(`⚙️ Hard PoW Started (Difficulty: ${d.difficulty})...`));
|
|
51
|
+
clinch.on('pow_solved', (d) => console.log(`🔓 PoW Solved in ${d.attempts} attempts!`));
|
|
52
|
+
clinch.on('token_issued', (data) => {
|
|
53
|
+
console.log(`💾 Core issued new JWT. Saving to disk...`);
|
|
54
|
+
fs.writeFileSync(TOKEN_FILE, data.token);
|
|
55
|
+
});
|
|
56
|
+
try {
|
|
57
|
+
await clinch.initialize(myHostAi, savedToken);
|
|
58
|
+
console.log('✅ Core Initialized!');
|
|
59
|
+
console.log('\n--- Searching (Requires Auth) ---');
|
|
60
|
+
const searchRes = await clinch.search('iphone');
|
|
61
|
+
console.log('🔍 Results:', searchRes.results);
|
|
62
|
+
console.log('\n--- Negotiating (Requires Auth) ---');
|
|
63
|
+
const sessionId = await clinch.negotiate('ANP-A.AMAZON.DEALS', { max_price: 500 });
|
|
64
|
+
console.log('🤝 Session ID:', sessionId);
|
|
65
|
+
}
|
|
66
|
+
catch (e) {
|
|
67
|
+
console.error("Test Failed:", e.message || e);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
run();
|
package/package.json
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "clinch-core",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
|
|
5
|
+
"description": "Clinch Protocol Edge Client",
|
|
6
|
+
"main": "dist/index.js",
|
|
7
|
+
"types": "dist/index.d.ts",
|
|
8
|
+
"scripts": {
|
|
9
|
+
"build": "tsc"
|
|
10
|
+
},
|
|
11
|
+
"keywords": [],
|
|
12
|
+
"author": "Clinch Protocol Project",
|
|
13
|
+
"license": "ISC",
|
|
14
|
+
"devDependencies": {
|
|
15
|
+
"@types/node": "^25.9.1",
|
|
16
|
+
"@types/ws": "^8.18.1",
|
|
17
|
+
"tsup": "^8.5.1",
|
|
18
|
+
"typescript": "^6.0.3"
|
|
19
|
+
},
|
|
20
|
+
"dependencies": {
|
|
21
|
+
"js-sha256": "^0.11.1",
|
|
22
|
+
"tweetnacl": "^1.0.3",
|
|
23
|
+
"ws": "^8.20.1"
|
|
24
|
+
},
|
|
25
|
+
"peerDependencies":{
|
|
26
|
+
"node-llama-cpp": "^3.18.1"
|
|
27
|
+
}
|
|
28
|
+
}
|
package/src/index.d.ts
ADDED
|
@@ -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":["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"}
|
package/src/index.js
ADDED
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.ClinchCore = void 0;
|
|
7
|
+
const events_1 = require("events");
|
|
8
|
+
const tweetnacl_1 = __importDefault(require("tweetnacl"));
|
|
9
|
+
const js_sha256_1 = require("js-sha256");
|
|
10
|
+
const ws_1 = __importDefault(require("ws"));
|
|
11
|
+
// ============================================================================
|
|
12
|
+
// CONFIGURATION & UTILS
|
|
13
|
+
// ============================================================================
|
|
14
|
+
const PROTOCOL_VERSION = "0.1.0";
|
|
15
|
+
const FIREBASE_CONFIG_URL = "https://clinchprotocol.web.app/network-config.json";
|
|
16
|
+
function toHex(arr) {
|
|
17
|
+
return Array.from(arr).map(b => b.toString(16).padStart(2, '0')).join('');
|
|
18
|
+
}
|
|
19
|
+
// ============================================================================
|
|
20
|
+
// THE CLINCH CORE LIBRARY
|
|
21
|
+
// ============================================================================
|
|
22
|
+
class ClinchCore extends events_1.EventEmitter {
|
|
23
|
+
isInitialized = false;
|
|
24
|
+
config;
|
|
25
|
+
cachedRegistryUrl = null;
|
|
26
|
+
jwtToken = null;
|
|
27
|
+
// Agent Identity Keypair (Long-lived, used for Auth, NEVER for session messages)
|
|
28
|
+
identityPrivKey;
|
|
29
|
+
identityPubKey;
|
|
30
|
+
// Active Sessions & Ephemeral Keys
|
|
31
|
+
activeSessions = new Map();
|
|
32
|
+
ws = null;
|
|
33
|
+
constructor(config = {}) {
|
|
34
|
+
super();
|
|
35
|
+
this.config = config;
|
|
36
|
+
// Generate Identity Key
|
|
37
|
+
const keyPair = tweetnacl_1.default.sign.keyPair();
|
|
38
|
+
this.identityPrivKey = keyPair.secretKey;
|
|
39
|
+
this.identityPubKey = toHex(keyPair.publicKey);
|
|
40
|
+
if (this.config.registryUrl) {
|
|
41
|
+
this.cachedRegistryUrl = this.config.registryUrl;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
// --------------------------------------------------------------------------
|
|
45
|
+
// INITIALIZATION & AUTH
|
|
46
|
+
// --------------------------------------------------------------------------
|
|
47
|
+
async initialize(cachedToken) {
|
|
48
|
+
if (this.isInitialized)
|
|
49
|
+
return;
|
|
50
|
+
try {
|
|
51
|
+
await this.getRegistryUrl();
|
|
52
|
+
if (cachedToken) {
|
|
53
|
+
this.jwtToken = cachedToken;
|
|
54
|
+
this.emit('log', "[Core] Restored session from cached JWT. Skipping PoW.");
|
|
55
|
+
}
|
|
56
|
+
else {
|
|
57
|
+
await this.registerNode();
|
|
58
|
+
}
|
|
59
|
+
await this.connectWebSocket();
|
|
60
|
+
this.isInitialized = true;
|
|
61
|
+
this.emit('initialized', { pubKey: this.identityPubKey, registry: this.cachedRegistryUrl });
|
|
62
|
+
}
|
|
63
|
+
catch (error) {
|
|
64
|
+
this.emit('error', error);
|
|
65
|
+
throw error;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
async registerNode() {
|
|
69
|
+
this.emit('log', "[Core] No cached token found. Requesting PoW challenge...");
|
|
70
|
+
const challenge = await this.networkRequest('/api/auth/challenge', {}, false);
|
|
71
|
+
const powSolution = await this.solvePoW(challenge.nonce, challenge.difficulty);
|
|
72
|
+
const authRes = await this.networkRequest('/api/auth/verify', {
|
|
73
|
+
method: 'POST',
|
|
74
|
+
headers: { 'Content-Type': 'application/json' },
|
|
75
|
+
body: JSON.stringify({
|
|
76
|
+
challenge_id: challenge.challenge_id,
|
|
77
|
+
pow_solution: powSolution,
|
|
78
|
+
pubKey: this.identityPubKey
|
|
79
|
+
})
|
|
80
|
+
}, false);
|
|
81
|
+
this.jwtToken = authRes.token;
|
|
82
|
+
this.emit('token_issued', { token: this.jwtToken });
|
|
83
|
+
}
|
|
84
|
+
async getRegistryUrl(forceRefresh = false) {
|
|
85
|
+
if (this.cachedRegistryUrl && !forceRefresh)
|
|
86
|
+
return this.cachedRegistryUrl;
|
|
87
|
+
const res = await fetch(FIREBASE_CONFIG_URL);
|
|
88
|
+
const text = await res.text();
|
|
89
|
+
try {
|
|
90
|
+
const config = JSON.parse(text);
|
|
91
|
+
this.cachedRegistryUrl = config.registry_nodes[PROTOCOL_VERSION];
|
|
92
|
+
return this.cachedRegistryUrl;
|
|
93
|
+
}
|
|
94
|
+
catch (err) {
|
|
95
|
+
throw new Error(`Failed to parse Firebase config.`);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
async networkRequest(endpoint, options = {}, requireAuth = true) {
|
|
99
|
+
const baseUrl = await this.getRegistryUrl();
|
|
100
|
+
const headers = { ...options.headers };
|
|
101
|
+
if (requireAuth && this.jwtToken) {
|
|
102
|
+
headers['Authorization'] = `Bearer ${this.jwtToken}`;
|
|
103
|
+
}
|
|
104
|
+
const res = await fetch(`${baseUrl}${endpoint}`, { ...options, headers });
|
|
105
|
+
const text = await res.text();
|
|
106
|
+
if (!res.ok)
|
|
107
|
+
throw new Error(`HTTP ${res.status} on ${endpoint}: ${text}`);
|
|
108
|
+
return JSON.parse(text);
|
|
109
|
+
}
|
|
110
|
+
// --------------------------------------------------------------------------
|
|
111
|
+
// WEBSOCKET CONNECTION
|
|
112
|
+
// --------------------------------------------------------------------------
|
|
113
|
+
async connectWebSocket() {
|
|
114
|
+
return new Promise((resolve, reject) => {
|
|
115
|
+
const wsUrl = this.cachedRegistryUrl.replace(/^http/, 'ws');
|
|
116
|
+
this.ws = new ws_1.default(wsUrl);
|
|
117
|
+
this.ws.on('open', () => {
|
|
118
|
+
this.ws.send(JSON.stringify({ type: 'AUTH', token: this.jwtToken }));
|
|
119
|
+
});
|
|
120
|
+
this.ws.on('message', (data) => {
|
|
121
|
+
const msg = JSON.parse(data.toString());
|
|
122
|
+
if (msg.type === 'AUTH_SUCCESS') {
|
|
123
|
+
this.emit('log', '[Core] WebSocket authenticated & listening.');
|
|
124
|
+
resolve();
|
|
125
|
+
}
|
|
126
|
+
if (msg.type === 'CALLBACK') {
|
|
127
|
+
this.emit('callback_received', {
|
|
128
|
+
sessionId: msg.session_id,
|
|
129
|
+
payload: msg.payload
|
|
130
|
+
});
|
|
131
|
+
this.ws.send(JSON.stringify({ type: 'ACK', id: msg.id }));
|
|
132
|
+
}
|
|
133
|
+
if (msg.type === 'ERROR') {
|
|
134
|
+
this.emit('error', new Error(msg.message));
|
|
135
|
+
}
|
|
136
|
+
});
|
|
137
|
+
this.ws.on('error', (err) => {
|
|
138
|
+
this.emit('error', err);
|
|
139
|
+
reject(err);
|
|
140
|
+
});
|
|
141
|
+
this.ws.on('close', () => {
|
|
142
|
+
this.emit('log', '[Core] WebSocket disconnected.');
|
|
143
|
+
});
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
disconnect() {
|
|
147
|
+
if (this.ws) {
|
|
148
|
+
this.ws.close();
|
|
149
|
+
this.ws = null;
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
// --------------------------------------------------------------------------
|
|
153
|
+
// PROTOCOL OPERATIONS
|
|
154
|
+
// --------------------------------------------------------------------------
|
|
155
|
+
async search(query, mode) {
|
|
156
|
+
let url = `/api/discover?category=${encodeURIComponent(query)}`;
|
|
157
|
+
if (mode)
|
|
158
|
+
url += `&mode=${encodeURIComponent(mode)}`;
|
|
159
|
+
return await this.networkRequest(url);
|
|
160
|
+
}
|
|
161
|
+
async negotiate(address, constraints) {
|
|
162
|
+
const parsed = this.parseAddress(address);
|
|
163
|
+
const ephemeralKeys = tweetnacl_1.default.sign.keyPair();
|
|
164
|
+
const ephemeralPubHex = toHex(ephemeralKeys.publicKey);
|
|
165
|
+
const initPayload = {
|
|
166
|
+
clinch_version: PROTOCOL_VERSION,
|
|
167
|
+
mode: parsed.mode,
|
|
168
|
+
constraints,
|
|
169
|
+
session_pub_key: ephemeralPubHex,
|
|
170
|
+
timestamp_utc: new Date().toISOString()
|
|
171
|
+
};
|
|
172
|
+
const msgUint8 = new TextEncoder().encode(JSON.stringify(initPayload));
|
|
173
|
+
const signature = toHex(tweetnacl_1.default.sign.detached(msgUint8, ephemeralKeys.secretKey));
|
|
174
|
+
const signedPayload = { ...initPayload, sig: signature };
|
|
175
|
+
const response = await this.networkRequest(`/api/route/${parsed.domain}/handshake`, {
|
|
176
|
+
method: 'POST',
|
|
177
|
+
headers: { 'Content-Type': 'application/json' },
|
|
178
|
+
body: JSON.stringify(signedPayload)
|
|
179
|
+
});
|
|
180
|
+
const sessionId = response.session_id;
|
|
181
|
+
this.activeSessions.set(sessionId, {
|
|
182
|
+
sessionId,
|
|
183
|
+
sellerId: parsed.domain,
|
|
184
|
+
keyPair: ephemeralKeys,
|
|
185
|
+
status: 'ACTIVE'
|
|
186
|
+
});
|
|
187
|
+
this.emit('session_started', { sessionId, sellerId: parsed.domain });
|
|
188
|
+
return sessionId;
|
|
189
|
+
}
|
|
190
|
+
async exitSession(sessionId) {
|
|
191
|
+
const session = this.activeSessions.get(sessionId);
|
|
192
|
+
if (!session)
|
|
193
|
+
throw new Error("Session not found or not active");
|
|
194
|
+
const res = await this.networkRequest(`/api/route/${sessionId}/exit`, {
|
|
195
|
+
method: 'POST',
|
|
196
|
+
headers: { 'Content-Type': 'application/json' },
|
|
197
|
+
body: JSON.stringify({ seller_id: session.sellerId })
|
|
198
|
+
});
|
|
199
|
+
session.status = 'EXITED';
|
|
200
|
+
session.exitTokenHash = res.token_hash;
|
|
201
|
+
this.emit('session_exited', { sessionId, token_hash: res.token_hash });
|
|
202
|
+
return res.token_hash;
|
|
203
|
+
}
|
|
204
|
+
// --------------------------------------------------------------------------
|
|
205
|
+
// UTILITIES
|
|
206
|
+
// --------------------------------------------------------------------------
|
|
207
|
+
parseAddress(address) {
|
|
208
|
+
const parts = address.split('.');
|
|
209
|
+
if (parts.length < 2)
|
|
210
|
+
throw new Error("Invalid Address Format. Expected MODE.DOMAIN.anp");
|
|
211
|
+
return {
|
|
212
|
+
mode: parts[0],
|
|
213
|
+
domain: parts.slice(1).join('.').toLowerCase(),
|
|
214
|
+
route: '/'
|
|
215
|
+
};
|
|
216
|
+
}
|
|
217
|
+
async solvePoW(nonce, difficultyBits) {
|
|
218
|
+
this.emit('pow_started', { difficulty: difficultyBits });
|
|
219
|
+
let counter = 0;
|
|
220
|
+
const CHUNK_SIZE = 5000;
|
|
221
|
+
while (true) {
|
|
222
|
+
for (let i = 0; i < CHUNK_SIZE; i++) {
|
|
223
|
+
const attempt = counter.toString();
|
|
224
|
+
const hashArray = js_sha256_1.sha256.create().update(nonce + this.identityPubKey + attempt).array();
|
|
225
|
+
if (this.hasLeadingZeroBits(hashArray, difficultyBits)) {
|
|
226
|
+
this.emit('pow_solved', { attempts: counter });
|
|
227
|
+
return attempt;
|
|
228
|
+
}
|
|
229
|
+
counter++;
|
|
230
|
+
}
|
|
231
|
+
await new Promise(resolve => setTimeout(resolve, 0));
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
hasLeadingZeroBits(hash, bits) {
|
|
235
|
+
let zeroBits = 0;
|
|
236
|
+
for (const byte of hash) {
|
|
237
|
+
if (byte === 0)
|
|
238
|
+
zeroBits += 8;
|
|
239
|
+
else {
|
|
240
|
+
zeroBits += Math.clz32(byte) - 24;
|
|
241
|
+
break;
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
return zeroBits >= bits;
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
exports.ClinchCore = ClinchCore;
|