@antseed/node 0.1.0 → 0.1.1
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.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/interfaces/seller-provider.d.ts +13 -1
- package/dist/interfaces/seller-provider.d.ts.map +1 -1
- package/dist/node.d.ts +13 -3
- package/dist/node.d.ts.map +1 -1
- package/dist/node.js +123 -15
- package/dist/node.js.map +1 -1
- package/dist/proxy/proxy-mux.d.ts +3 -1
- package/dist/proxy/proxy-mux.d.ts.map +1 -1
- package/dist/proxy/proxy-mux.js +9 -5
- package/dist/proxy/proxy-mux.js.map +1 -1
- package/dist/types/http.d.ts +1 -0
- package/dist/types/http.d.ts.map +1 -1
- package/dist/types/http.js +1 -1
- package/dist/types/http.js.map +1 -1
- package/package.json +14 -10
- package/contracts/AntseedEscrow.sol +0 -310
- package/contracts/MockUSDC.sol +0 -64
- package/contracts/README.md +0 -102
- package/src/config/encryption.test.ts +0 -49
- package/src/config/encryption.ts +0 -53
- package/src/config/plugin-config-manager.test.ts +0 -92
- package/src/config/plugin-config-manager.ts +0 -153
- package/src/config/plugin-loader.ts +0 -90
- package/src/discovery/announcer.ts +0 -169
- package/src/discovery/bootstrap.ts +0 -57
- package/src/discovery/default-metadata-resolver.ts +0 -18
- package/src/discovery/dht-health.ts +0 -136
- package/src/discovery/dht-node.ts +0 -191
- package/src/discovery/http-metadata-resolver.ts +0 -47
- package/src/discovery/index.ts +0 -15
- package/src/discovery/metadata-codec.ts +0 -453
- package/src/discovery/metadata-resolver.ts +0 -7
- package/src/discovery/metadata-server.ts +0 -73
- package/src/discovery/metadata-validator.ts +0 -172
- package/src/discovery/peer-lookup.ts +0 -122
- package/src/discovery/peer-metadata.ts +0 -34
- package/src/discovery/peer-selector.ts +0 -134
- package/src/discovery/profile-manager.ts +0 -131
- package/src/discovery/profile-search.ts +0 -100
- package/src/discovery/reputation-verifier.ts +0 -54
- package/src/index.ts +0 -61
- package/src/interfaces/buyer-router.ts +0 -21
- package/src/interfaces/plugin.ts +0 -36
- package/src/interfaces/seller-provider.ts +0 -81
- package/src/metering/index.ts +0 -6
- package/src/metering/receipt-generator.ts +0 -105
- package/src/metering/receipt-verifier.ts +0 -102
- package/src/metering/session-tracker.ts +0 -145
- package/src/metering/storage.ts +0 -600
- package/src/metering/token-counter.ts +0 -127
- package/src/metering/usage-aggregator.ts +0 -236
- package/src/node.ts +0 -1698
- package/src/p2p/connection-auth.ts +0 -152
- package/src/p2p/connection-manager.ts +0 -916
- package/src/p2p/handshake.ts +0 -162
- package/src/p2p/ice-config.ts +0 -59
- package/src/p2p/identity.ts +0 -110
- package/src/p2p/index.ts +0 -11
- package/src/p2p/keepalive.ts +0 -118
- package/src/p2p/message-protocol.ts +0 -171
- package/src/p2p/nat-traversal.ts +0 -169
- package/src/p2p/payment-codec.ts +0 -165
- package/src/p2p/payment-mux.ts +0 -153
- package/src/p2p/reconnect.ts +0 -117
- package/src/payments/balance-manager.ts +0 -77
- package/src/payments/buyer-payment-manager.ts +0 -414
- package/src/payments/disputes.ts +0 -72
- package/src/payments/evm/escrow-client.ts +0 -263
- package/src/payments/evm/keypair.ts +0 -31
- package/src/payments/evm/signatures.ts +0 -103
- package/src/payments/evm/wallet.ts +0 -42
- package/src/payments/index.ts +0 -50
- package/src/payments/settlement.ts +0 -40
- package/src/payments/types.ts +0 -79
- package/src/proxy/index.ts +0 -3
- package/src/proxy/provider-detection.ts +0 -78
- package/src/proxy/proxy-mux.ts +0 -173
- package/src/proxy/request-codec.ts +0 -294
- package/src/reputation/index.ts +0 -6
- package/src/reputation/rating-manager.ts +0 -118
- package/src/reputation/report-manager.ts +0 -91
- package/src/reputation/trust-engine.ts +0 -120
- package/src/reputation/trust-score.ts +0 -74
- package/src/reputation/uptime-tracker.ts +0 -155
- package/src/routing/default-router.ts +0 -75
- package/src/types/bittorrent-dht.d.ts +0 -19
- package/src/types/buyer.ts +0 -37
- package/src/types/capability.ts +0 -34
- package/src/types/connection.ts +0 -29
- package/src/types/http.ts +0 -20
- package/src/types/index.ts +0 -14
- package/src/types/metering.ts +0 -175
- package/src/types/nat-api.d.ts +0 -29
- package/src/types/peer-profile.ts +0 -25
- package/src/types/peer.ts +0 -62
- package/src/types/plugin-config.ts +0 -31
- package/src/types/protocol.ts +0 -162
- package/src/types/provider.ts +0 -40
- package/src/types/rating.ts +0 -23
- package/src/types/report.ts +0 -30
- package/src/types/seller.ts +0 -38
- package/src/types/staking.ts +0 -23
- package/src/utils/debug.ts +0 -30
- package/src/utils/hex.ts +0 -14
- package/tests/balance-manager.test.ts +0 -156
- package/tests/bootstrap.test.ts +0 -108
- package/tests/buyer-payment-manager.test.ts +0 -358
- package/tests/connection-auth.test.ts +0 -87
- package/tests/default-router.test.ts +0 -148
- package/tests/evm-keypair.test.ts +0 -173
- package/tests/identity.test.ts +0 -133
- package/tests/message-protocol.test.ts +0 -212
- package/tests/metadata-codec.test.ts +0 -165
- package/tests/metadata-validator.test.ts +0 -261
- package/tests/metering-storage.test.ts +0 -244
- package/tests/payment-codec.test.ts +0 -95
- package/tests/payment-mux.test.ts +0 -191
- package/tests/peer-selector.test.ts +0 -184
- package/tests/provider-detection.test.ts +0 -107
- package/tests/proxy-mux-security.test.ts +0 -38
- package/tests/receipt.test.ts +0 -215
- package/tests/reputation-integration.test.ts +0 -195
- package/tests/request-codec.test.ts +0 -144
- package/tests/token-counter.test.ts +0 -122
- package/tsconfig.json +0 -9
- package/vitest.config.ts +0 -7
|
@@ -1,453 +0,0 @@
|
|
|
1
|
-
import type { PeerMetadata } from "./peer-metadata.js";
|
|
2
|
-
import type { PeerOffering } from "../types/capability.js";
|
|
3
|
-
import { hexToBytes, bytesToHex } from "../utils/hex.js";
|
|
4
|
-
import { toPeerId } from "../types/peer.js";
|
|
5
|
-
|
|
6
|
-
/**
|
|
7
|
-
* Encode metadata into binary format:
|
|
8
|
-
* [version:1][peerId:32][regionLen:1][region:N][timestamp:8 BigUint64][providerCount:1]
|
|
9
|
-
* for each provider:
|
|
10
|
-
* [providerLen:1][provider:N][modelCount:1][models...]
|
|
11
|
-
* [defaultInputPrice:4][defaultOutputPrice:4]
|
|
12
|
-
* [modelPricingCount:1][modelPricingEntries...]
|
|
13
|
-
* [maxConcurrency:2][currentLoad:2]
|
|
14
|
-
* modelPricingEntry: [modelLen:1][model:N][inputPrice:4][outputPrice:4]
|
|
15
|
-
* [signature:64]
|
|
16
|
-
*/
|
|
17
|
-
export function encodeMetadata(metadata: PeerMetadata): Uint8Array {
|
|
18
|
-
const bodyBytes = encodeBody(metadata);
|
|
19
|
-
const signatureBytes = hexToBytes(metadata.signature);
|
|
20
|
-
|
|
21
|
-
const result = new Uint8Array(bodyBytes.length + signatureBytes.length);
|
|
22
|
-
result.set(bodyBytes, 0);
|
|
23
|
-
result.set(signatureBytes, bodyBytes.length);
|
|
24
|
-
return result;
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
/**
|
|
28
|
-
* Encode metadata without signature, for signing purposes.
|
|
29
|
-
*/
|
|
30
|
-
export function encodeMetadataForSigning(metadata: PeerMetadata): Uint8Array {
|
|
31
|
-
return encodeBody(metadata);
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
function encodeBody(metadata: PeerMetadata): Uint8Array {
|
|
35
|
-
const parts: Uint8Array[] = [];
|
|
36
|
-
|
|
37
|
-
// version: 1 byte
|
|
38
|
-
parts.push(new Uint8Array([metadata.version]));
|
|
39
|
-
|
|
40
|
-
// peerId: 32 bytes
|
|
41
|
-
parts.push(hexToBytes(metadata.peerId));
|
|
42
|
-
|
|
43
|
-
// region: length-prefixed
|
|
44
|
-
const regionBytes = new TextEncoder().encode(metadata.region);
|
|
45
|
-
parts.push(new Uint8Array([regionBytes.length]));
|
|
46
|
-
parts.push(regionBytes);
|
|
47
|
-
|
|
48
|
-
// timestamp: 8 bytes BigUint64
|
|
49
|
-
const timestampBuf = new ArrayBuffer(8);
|
|
50
|
-
const timestampView = new DataView(timestampBuf);
|
|
51
|
-
timestampView.setBigUint64(0, BigInt(metadata.timestamp), false);
|
|
52
|
-
parts.push(new Uint8Array(timestampBuf));
|
|
53
|
-
|
|
54
|
-
// providerCount: 1 byte
|
|
55
|
-
parts.push(new Uint8Array([metadata.providers.length]));
|
|
56
|
-
|
|
57
|
-
// each provider
|
|
58
|
-
for (const p of metadata.providers) {
|
|
59
|
-
const providerNameBytes = new TextEncoder().encode(p.provider);
|
|
60
|
-
parts.push(new Uint8Array([providerNameBytes.length]));
|
|
61
|
-
parts.push(providerNameBytes);
|
|
62
|
-
|
|
63
|
-
// modelCount: 1 byte
|
|
64
|
-
parts.push(new Uint8Array([p.models.length]));
|
|
65
|
-
|
|
66
|
-
// each model: length-prefixed
|
|
67
|
-
for (const model of p.models) {
|
|
68
|
-
const modelBytes = new TextEncoder().encode(model);
|
|
69
|
-
parts.push(new Uint8Array([modelBytes.length]));
|
|
70
|
-
parts.push(modelBytes);
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
// default input price: 4 bytes (float32)
|
|
74
|
-
const inputPriceBuf = new ArrayBuffer(4);
|
|
75
|
-
new DataView(inputPriceBuf).setFloat32(0, p.defaultPricing.inputUsdPerMillion, false);
|
|
76
|
-
parts.push(new Uint8Array(inputPriceBuf));
|
|
77
|
-
|
|
78
|
-
// default output price: 4 bytes (float32)
|
|
79
|
-
const outputPriceBuf = new ArrayBuffer(4);
|
|
80
|
-
new DataView(outputPriceBuf).setFloat32(0, p.defaultPricing.outputUsdPerMillion, false);
|
|
81
|
-
parts.push(new Uint8Array(outputPriceBuf));
|
|
82
|
-
|
|
83
|
-
// modelPricing entries
|
|
84
|
-
const modelPricingEntries = Object.entries(p.modelPricing ?? {}).sort(([a], [b]) =>
|
|
85
|
-
a.localeCompare(b),
|
|
86
|
-
);
|
|
87
|
-
parts.push(new Uint8Array([modelPricingEntries.length]));
|
|
88
|
-
for (const [modelName, pricing] of modelPricingEntries) {
|
|
89
|
-
const modelNameBytes = new TextEncoder().encode(modelName);
|
|
90
|
-
parts.push(new Uint8Array([modelNameBytes.length]));
|
|
91
|
-
parts.push(modelNameBytes);
|
|
92
|
-
|
|
93
|
-
const modelInputBuf = new ArrayBuffer(4);
|
|
94
|
-
new DataView(modelInputBuf).setFloat32(0, pricing.inputUsdPerMillion, false);
|
|
95
|
-
parts.push(new Uint8Array(modelInputBuf));
|
|
96
|
-
|
|
97
|
-
const modelOutputBuf = new ArrayBuffer(4);
|
|
98
|
-
new DataView(modelOutputBuf).setFloat32(0, pricing.outputUsdPerMillion, false);
|
|
99
|
-
parts.push(new Uint8Array(modelOutputBuf));
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
// maxConcurrency: 2 bytes (uint16)
|
|
103
|
-
const maxConcBuf = new ArrayBuffer(2);
|
|
104
|
-
new DataView(maxConcBuf).setUint16(0, p.maxConcurrency, false);
|
|
105
|
-
parts.push(new Uint8Array(maxConcBuf));
|
|
106
|
-
|
|
107
|
-
// currentLoad: 2 bytes (uint16)
|
|
108
|
-
const loadBuf = new ArrayBuffer(2);
|
|
109
|
-
new DataView(loadBuf).setUint16(0, p.currentLoad, false);
|
|
110
|
-
parts.push(new Uint8Array(loadBuf));
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
// offerings
|
|
114
|
-
const offerings = metadata.offerings ?? [];
|
|
115
|
-
const offeringCountBuf = new ArrayBuffer(2);
|
|
116
|
-
new DataView(offeringCountBuf).setUint16(0, offerings.length, false);
|
|
117
|
-
parts.push(new Uint8Array(offeringCountBuf));
|
|
118
|
-
|
|
119
|
-
const PRICING_UNIT_MAP: Record<string, number> = { token: 0, request: 1, minute: 2, task: 3 };
|
|
120
|
-
|
|
121
|
-
for (const o of offerings) {
|
|
122
|
-
// capability: length-prefixed (1 byte len)
|
|
123
|
-
const capBytes = new TextEncoder().encode(o.capability);
|
|
124
|
-
parts.push(new Uint8Array([capBytes.length]));
|
|
125
|
-
parts.push(capBytes);
|
|
126
|
-
|
|
127
|
-
// name: length-prefixed (1 byte len)
|
|
128
|
-
const nameBytes = new TextEncoder().encode(o.name);
|
|
129
|
-
parts.push(new Uint8Array([nameBytes.length]));
|
|
130
|
-
parts.push(nameBytes);
|
|
131
|
-
|
|
132
|
-
// description: length-prefixed (2 byte uint16 len)
|
|
133
|
-
const descBytes = new TextEncoder().encode(o.description);
|
|
134
|
-
const descLenBuf = new ArrayBuffer(2);
|
|
135
|
-
new DataView(descLenBuf).setUint16(0, descBytes.length, false);
|
|
136
|
-
parts.push(new Uint8Array(descLenBuf));
|
|
137
|
-
parts.push(descBytes);
|
|
138
|
-
|
|
139
|
-
// pricingUnit: 1 byte
|
|
140
|
-
parts.push(new Uint8Array([PRICING_UNIT_MAP[o.pricing.unit] ?? 0]));
|
|
141
|
-
|
|
142
|
-
// pricePerUnit: 4 bytes float32
|
|
143
|
-
const priceBuf = new ArrayBuffer(4);
|
|
144
|
-
new DataView(priceBuf).setFloat32(0, o.pricing.pricePerUnit, false);
|
|
145
|
-
parts.push(new Uint8Array(priceBuf));
|
|
146
|
-
|
|
147
|
-
// modelCount: 1 byte, then each model
|
|
148
|
-
const models = o.models ?? [];
|
|
149
|
-
parts.push(new Uint8Array([models.length]));
|
|
150
|
-
for (const model of models) {
|
|
151
|
-
const modelBytes = new TextEncoder().encode(model);
|
|
152
|
-
parts.push(new Uint8Array([modelBytes.length]));
|
|
153
|
-
parts.push(modelBytes);
|
|
154
|
-
}
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
// EVM address: 1 flag byte + 20 address bytes if present
|
|
158
|
-
if (metadata.evmAddress) {
|
|
159
|
-
parts.push(new Uint8Array([1])); // flag: present
|
|
160
|
-
// Strip 0x prefix if present, then decode 20 bytes
|
|
161
|
-
const addrHex = metadata.evmAddress.startsWith('0x')
|
|
162
|
-
? metadata.evmAddress.slice(2)
|
|
163
|
-
: metadata.evmAddress;
|
|
164
|
-
parts.push(hexToBytes(addrHex.toLowerCase().padStart(40, '0')));
|
|
165
|
-
} else {
|
|
166
|
-
parts.push(new Uint8Array([0])); // flag: absent
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
// On-chain reputation: 1 flag byte + 10 data bytes (1 reputation + 4 sessionCount + 4 disputeCount + 1 reserved)
|
|
170
|
-
if (metadata.onChainReputation !== undefined) {
|
|
171
|
-
parts.push(new Uint8Array([1])); // flag: present
|
|
172
|
-
const repBuf = new ArrayBuffer(10);
|
|
173
|
-
const repView = new DataView(repBuf);
|
|
174
|
-
repView.setUint8(0, Math.min(255, Math.max(0, metadata.onChainReputation)));
|
|
175
|
-
repView.setUint32(1, metadata.onChainSessionCount ?? 0, false);
|
|
176
|
-
repView.setUint32(5, metadata.onChainDisputeCount ?? 0, false);
|
|
177
|
-
repView.setUint8(9, 0); // reserved
|
|
178
|
-
parts.push(new Uint8Array(repBuf));
|
|
179
|
-
} else {
|
|
180
|
-
parts.push(new Uint8Array([0])); // flag: absent
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
// Combine all parts
|
|
184
|
-
const totalLength = parts.reduce((sum, p) => sum + p.length, 0);
|
|
185
|
-
const result = new Uint8Array(totalLength);
|
|
186
|
-
let offset = 0;
|
|
187
|
-
for (const part of parts) {
|
|
188
|
-
result.set(part, offset);
|
|
189
|
-
offset += part.length;
|
|
190
|
-
}
|
|
191
|
-
return result;
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
/**
|
|
195
|
-
* Decode binary metadata back into PeerMetadata.
|
|
196
|
-
*/
|
|
197
|
-
export function decodeMetadata(data: Uint8Array): PeerMetadata {
|
|
198
|
-
function checkBounds(offset: number, needed: number, total: number): void {
|
|
199
|
-
if (offset + needed > total) throw new Error('Truncated metadata buffer');
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
let offset = 0;
|
|
203
|
-
|
|
204
|
-
// version: 1 byte
|
|
205
|
-
checkBounds(offset, 1, data.length);
|
|
206
|
-
const version = data[offset]!;
|
|
207
|
-
offset += 1;
|
|
208
|
-
|
|
209
|
-
// peerId: 32 bytes
|
|
210
|
-
checkBounds(offset, 32, data.length);
|
|
211
|
-
const peerIdBytes = data.slice(offset, offset + 32);
|
|
212
|
-
const peerId = bytesToHex(peerIdBytes);
|
|
213
|
-
offset += 32;
|
|
214
|
-
|
|
215
|
-
// region: length-prefixed
|
|
216
|
-
checkBounds(offset, 1, data.length);
|
|
217
|
-
const regionLen = data[offset]!;
|
|
218
|
-
offset += 1;
|
|
219
|
-
checkBounds(offset, regionLen, data.length);
|
|
220
|
-
const region = new TextDecoder().decode(data.slice(offset, offset + regionLen));
|
|
221
|
-
offset += regionLen;
|
|
222
|
-
|
|
223
|
-
// timestamp: 8 bytes BigUint64
|
|
224
|
-
checkBounds(offset, 8, data.length);
|
|
225
|
-
const timestampView = new DataView(data.buffer, data.byteOffset + offset, 8);
|
|
226
|
-
const timestamp = Number(timestampView.getBigUint64(0, false));
|
|
227
|
-
offset += 8;
|
|
228
|
-
|
|
229
|
-
// providerCount: 1 byte
|
|
230
|
-
checkBounds(offset, 1, data.length);
|
|
231
|
-
const providerCount = data[offset]!;
|
|
232
|
-
offset += 1;
|
|
233
|
-
|
|
234
|
-
const providers = [];
|
|
235
|
-
for (let i = 0; i < providerCount; i++) {
|
|
236
|
-
// provider name: length-prefixed
|
|
237
|
-
checkBounds(offset, 1, data.length);
|
|
238
|
-
const providerLen = data[offset]!;
|
|
239
|
-
offset += 1;
|
|
240
|
-
checkBounds(offset, providerLen, data.length);
|
|
241
|
-
const provider = new TextDecoder().decode(data.slice(offset, offset + providerLen));
|
|
242
|
-
offset += providerLen;
|
|
243
|
-
|
|
244
|
-
// modelCount: 1 byte
|
|
245
|
-
checkBounds(offset, 1, data.length);
|
|
246
|
-
const modelCount = data[offset]!;
|
|
247
|
-
offset += 1;
|
|
248
|
-
|
|
249
|
-
const models: string[] = [];
|
|
250
|
-
for (let j = 0; j < modelCount; j++) {
|
|
251
|
-
checkBounds(offset, 1, data.length);
|
|
252
|
-
const modelLen = data[offset]!;
|
|
253
|
-
offset += 1;
|
|
254
|
-
checkBounds(offset, modelLen, data.length);
|
|
255
|
-
const model = new TextDecoder().decode(data.slice(offset, offset + modelLen));
|
|
256
|
-
offset += modelLen;
|
|
257
|
-
models.push(model);
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
// default input price: 4 bytes float32
|
|
261
|
-
checkBounds(offset, 4, data.length);
|
|
262
|
-
const inputPriceView = new DataView(data.buffer, data.byteOffset + offset, 4);
|
|
263
|
-
const defaultInputUsdPerMillion = inputPriceView.getFloat32(0, false);
|
|
264
|
-
offset += 4;
|
|
265
|
-
|
|
266
|
-
// default output price: 4 bytes float32
|
|
267
|
-
checkBounds(offset, 4, data.length);
|
|
268
|
-
const outputPriceView = new DataView(data.buffer, data.byteOffset + offset, 4);
|
|
269
|
-
const defaultOutputUsdPerMillion = outputPriceView.getFloat32(0, false);
|
|
270
|
-
offset += 4;
|
|
271
|
-
|
|
272
|
-
// modelPricing entries
|
|
273
|
-
checkBounds(offset, 1, data.length);
|
|
274
|
-
const modelPricingCount = data[offset]!;
|
|
275
|
-
offset += 1;
|
|
276
|
-
|
|
277
|
-
const modelPricing: Record<string, { inputUsdPerMillion: number; outputUsdPerMillion: number }> = {};
|
|
278
|
-
for (let j = 0; j < modelPricingCount; j++) {
|
|
279
|
-
checkBounds(offset, 1, data.length);
|
|
280
|
-
const pricedModelLen = data[offset]!;
|
|
281
|
-
offset += 1;
|
|
282
|
-
checkBounds(offset, pricedModelLen, data.length);
|
|
283
|
-
const pricedModelName = new TextDecoder().decode(data.slice(offset, offset + pricedModelLen));
|
|
284
|
-
offset += pricedModelLen;
|
|
285
|
-
|
|
286
|
-
checkBounds(offset, 4, data.length);
|
|
287
|
-
const pricedInputView = new DataView(data.buffer, data.byteOffset + offset, 4);
|
|
288
|
-
const inputUsdPerMillion = pricedInputView.getFloat32(0, false);
|
|
289
|
-
offset += 4;
|
|
290
|
-
|
|
291
|
-
checkBounds(offset, 4, data.length);
|
|
292
|
-
const pricedOutputView = new DataView(data.buffer, data.byteOffset + offset, 4);
|
|
293
|
-
const outputUsdPerMillion = pricedOutputView.getFloat32(0, false);
|
|
294
|
-
offset += 4;
|
|
295
|
-
|
|
296
|
-
modelPricing[pricedModelName] = {
|
|
297
|
-
inputUsdPerMillion,
|
|
298
|
-
outputUsdPerMillion,
|
|
299
|
-
};
|
|
300
|
-
}
|
|
301
|
-
|
|
302
|
-
// maxConcurrency: 2 bytes uint16
|
|
303
|
-
checkBounds(offset, 2, data.length);
|
|
304
|
-
const maxConcView = new DataView(data.buffer, data.byteOffset + offset, 2);
|
|
305
|
-
const maxConcurrency = maxConcView.getUint16(0, false);
|
|
306
|
-
offset += 2;
|
|
307
|
-
|
|
308
|
-
// currentLoad: 2 bytes uint16
|
|
309
|
-
checkBounds(offset, 2, data.length);
|
|
310
|
-
const loadView = new DataView(data.buffer, data.byteOffset + offset, 2);
|
|
311
|
-
const currentLoad = loadView.getUint16(0, false);
|
|
312
|
-
offset += 2;
|
|
313
|
-
|
|
314
|
-
providers.push({
|
|
315
|
-
provider,
|
|
316
|
-
models,
|
|
317
|
-
defaultPricing: {
|
|
318
|
-
inputUsdPerMillion: defaultInputUsdPerMillion,
|
|
319
|
-
outputUsdPerMillion: defaultOutputUsdPerMillion,
|
|
320
|
-
},
|
|
321
|
-
...(modelPricingCount > 0 ? { modelPricing } : {}),
|
|
322
|
-
maxConcurrency,
|
|
323
|
-
currentLoad,
|
|
324
|
-
});
|
|
325
|
-
}
|
|
326
|
-
|
|
327
|
-
// offerings (optional — present if there are remaining bytes before the 64-byte signature)
|
|
328
|
-
const PRICING_UNIT_REVERSE: Array<'token' | 'request' | 'minute' | 'task'> = ['token', 'request', 'minute', 'task'];
|
|
329
|
-
let offerings: PeerOffering[] | undefined;
|
|
330
|
-
|
|
331
|
-
const remainingBeforeSignature = data.length - offset - 64;
|
|
332
|
-
if (remainingBeforeSignature >= 2) {
|
|
333
|
-
offerings = [];
|
|
334
|
-
checkBounds(offset, 2, data.length - 64);
|
|
335
|
-
const offeringCountView = new DataView(data.buffer, data.byteOffset + offset, 2);
|
|
336
|
-
const offeringCount = offeringCountView.getUint16(0, false);
|
|
337
|
-
offset += 2;
|
|
338
|
-
|
|
339
|
-
for (let i = 0; i < offeringCount; i++) {
|
|
340
|
-
// capability
|
|
341
|
-
checkBounds(offset, 1, data.length - 64);
|
|
342
|
-
const capLen = data[offset]!;
|
|
343
|
-
offset += 1;
|
|
344
|
-
checkBounds(offset, capLen, data.length - 64);
|
|
345
|
-
const capability = new TextDecoder().decode(data.slice(offset, offset + capLen));
|
|
346
|
-
offset += capLen;
|
|
347
|
-
|
|
348
|
-
// name
|
|
349
|
-
checkBounds(offset, 1, data.length - 64);
|
|
350
|
-
const nameLen = data[offset]!;
|
|
351
|
-
offset += 1;
|
|
352
|
-
checkBounds(offset, nameLen, data.length - 64);
|
|
353
|
-
const name = new TextDecoder().decode(data.slice(offset, offset + nameLen));
|
|
354
|
-
offset += nameLen;
|
|
355
|
-
|
|
356
|
-
// description (uint16 length)
|
|
357
|
-
checkBounds(offset, 2, data.length - 64);
|
|
358
|
-
const descLenView = new DataView(data.buffer, data.byteOffset + offset, 2);
|
|
359
|
-
const descLen = descLenView.getUint16(0, false);
|
|
360
|
-
offset += 2;
|
|
361
|
-
checkBounds(offset, descLen, data.length - 64);
|
|
362
|
-
const description = new TextDecoder().decode(data.slice(offset, offset + descLen));
|
|
363
|
-
offset += descLen;
|
|
364
|
-
|
|
365
|
-
// pricingUnit: 1 byte
|
|
366
|
-
checkBounds(offset, 1, data.length - 64);
|
|
367
|
-
const pricingUnitIdx = data[offset]!;
|
|
368
|
-
offset += 1;
|
|
369
|
-
const unit = PRICING_UNIT_REVERSE[pricingUnitIdx] ?? 'token';
|
|
370
|
-
|
|
371
|
-
// pricePerUnit: 4 bytes float32
|
|
372
|
-
checkBounds(offset, 4, data.length - 64);
|
|
373
|
-
const priceView = new DataView(data.buffer, data.byteOffset + offset, 4);
|
|
374
|
-
const pricePerUnit = priceView.getFloat32(0, false);
|
|
375
|
-
offset += 4;
|
|
376
|
-
|
|
377
|
-
// models
|
|
378
|
-
checkBounds(offset, 1, data.length - 64);
|
|
379
|
-
const modelCount = data[offset]!;
|
|
380
|
-
offset += 1;
|
|
381
|
-
const models: string[] = [];
|
|
382
|
-
for (let j = 0; j < modelCount; j++) {
|
|
383
|
-
checkBounds(offset, 1, data.length - 64);
|
|
384
|
-
const modelLen = data[offset]!;
|
|
385
|
-
offset += 1;
|
|
386
|
-
checkBounds(offset, modelLen, data.length - 64);
|
|
387
|
-
const model = new TextDecoder().decode(data.slice(offset, offset + modelLen));
|
|
388
|
-
offset += modelLen;
|
|
389
|
-
models.push(model);
|
|
390
|
-
}
|
|
391
|
-
|
|
392
|
-
offerings.push({
|
|
393
|
-
capability: capability as PeerOffering['capability'],
|
|
394
|
-
name,
|
|
395
|
-
description,
|
|
396
|
-
models: models.length > 0 ? models : undefined,
|
|
397
|
-
pricing: { unit, pricePerUnit, currency: 'USD' },
|
|
398
|
-
});
|
|
399
|
-
}
|
|
400
|
-
}
|
|
401
|
-
|
|
402
|
-
// Optional EVM address (flag + 20 bytes) — present if there are enough remaining bytes before signature
|
|
403
|
-
let evmAddress: string | undefined;
|
|
404
|
-
const remainingBeforeEvmSig = data.length - offset - 64;
|
|
405
|
-
if (remainingBeforeEvmSig >= 1) {
|
|
406
|
-
const evmFlag = data[offset]!;
|
|
407
|
-
offset += 1;
|
|
408
|
-
if (evmFlag === 1) {
|
|
409
|
-
checkBounds(offset, 20, data.length - 64);
|
|
410
|
-
const addrBytes = data.slice(offset, offset + 20);
|
|
411
|
-
evmAddress = '0x' + bytesToHex(addrBytes);
|
|
412
|
-
offset += 20;
|
|
413
|
-
}
|
|
414
|
-
}
|
|
415
|
-
|
|
416
|
-
// Optional on-chain reputation (flag + 10 bytes)
|
|
417
|
-
let onChainReputation: number | undefined;
|
|
418
|
-
let onChainSessionCount: number | undefined;
|
|
419
|
-
let onChainDisputeCount: number | undefined;
|
|
420
|
-
const remainingBeforeRepSig = data.length - offset - 64;
|
|
421
|
-
if (remainingBeforeRepSig >= 1) {
|
|
422
|
-
const repFlag = data[offset]!;
|
|
423
|
-
offset += 1;
|
|
424
|
-
if (repFlag === 1) {
|
|
425
|
-
checkBounds(offset, 10, data.length - 64);
|
|
426
|
-
const repView = new DataView(data.buffer, data.byteOffset + offset, 10);
|
|
427
|
-
onChainReputation = repView.getUint8(0);
|
|
428
|
-
onChainSessionCount = repView.getUint32(1, false);
|
|
429
|
-
onChainDisputeCount = repView.getUint32(5, false);
|
|
430
|
-
// byte 9 is reserved
|
|
431
|
-
offset += 10;
|
|
432
|
-
}
|
|
433
|
-
}
|
|
434
|
-
|
|
435
|
-
// signature: 64 bytes
|
|
436
|
-
checkBounds(offset, 64, data.length);
|
|
437
|
-
const signatureBytes = data.slice(offset, offset + 64);
|
|
438
|
-
const signature = bytesToHex(signatureBytes);
|
|
439
|
-
|
|
440
|
-
return {
|
|
441
|
-
peerId: toPeerId(peerId),
|
|
442
|
-
version,
|
|
443
|
-
providers,
|
|
444
|
-
...(offerings && offerings.length > 0 ? { offerings } : {}),
|
|
445
|
-
...(evmAddress !== undefined ? { evmAddress } : {}),
|
|
446
|
-
...(onChainReputation !== undefined ? { onChainReputation } : {}),
|
|
447
|
-
...(onChainSessionCount !== undefined ? { onChainSessionCount } : {}),
|
|
448
|
-
...(onChainDisputeCount !== undefined ? { onChainDisputeCount } : {}),
|
|
449
|
-
region,
|
|
450
|
-
timestamp,
|
|
451
|
-
signature,
|
|
452
|
-
};
|
|
453
|
-
}
|
|
@@ -1,73 +0,0 @@
|
|
|
1
|
-
import { createServer, type Server, type IncomingMessage, type ServerResponse } from 'node:http';
|
|
2
|
-
import type { PeerMetadata } from './peer-metadata.js';
|
|
3
|
-
|
|
4
|
-
export interface MetadataServerConfig {
|
|
5
|
-
port: number;
|
|
6
|
-
host?: string;
|
|
7
|
-
getMetadata: () => PeerMetadata | null;
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
/** @deprecated Standalone MetadataServer is unused; ConnectionManager._serveHttpMetadata is preferred. */
|
|
11
|
-
export class MetadataServer {
|
|
12
|
-
private readonly _config: MetadataServerConfig;
|
|
13
|
-
private _server: Server | null = null;
|
|
14
|
-
|
|
15
|
-
constructor(config: MetadataServerConfig) {
|
|
16
|
-
this._config = config;
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
async start(): Promise<void> {
|
|
20
|
-
return new Promise((resolve, reject) => {
|
|
21
|
-
this._server = createServer((req: IncomingMessage, res: ServerResponse) => {
|
|
22
|
-
if (req.method !== 'GET') {
|
|
23
|
-
res.writeHead(405, { 'content-type': 'application/json' });
|
|
24
|
-
res.end(JSON.stringify({ error: 'method not allowed' }));
|
|
25
|
-
return;
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
if (req.url !== '/metadata') {
|
|
29
|
-
res.writeHead(404, { 'content-type': 'application/json' });
|
|
30
|
-
res.end(JSON.stringify({ error: 'not found' }));
|
|
31
|
-
return;
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
const metadata = this._config.getMetadata();
|
|
35
|
-
if (!metadata) {
|
|
36
|
-
res.writeHead(503, { 'content-type': 'application/json' });
|
|
37
|
-
res.end(JSON.stringify({ error: 'metadata not available' }));
|
|
38
|
-
return;
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
res.writeHead(200, { 'content-type': 'application/json' });
|
|
42
|
-
res.end(JSON.stringify(metadata));
|
|
43
|
-
});
|
|
44
|
-
|
|
45
|
-
this._server.on('error', reject);
|
|
46
|
-
this._server.listen(this._config.port, this._config.host ?? '0.0.0.0', () => {
|
|
47
|
-
resolve();
|
|
48
|
-
});
|
|
49
|
-
});
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
async stop(): Promise<void> {
|
|
53
|
-
return new Promise((resolve) => {
|
|
54
|
-
if (!this._server) {
|
|
55
|
-
resolve();
|
|
56
|
-
return;
|
|
57
|
-
}
|
|
58
|
-
this._server.close(() => {
|
|
59
|
-
this._server = null;
|
|
60
|
-
resolve();
|
|
61
|
-
});
|
|
62
|
-
});
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
getPort(): number {
|
|
66
|
-
if (!this._server) return this._config.port;
|
|
67
|
-
const addr = this._server.address();
|
|
68
|
-
if (addr && typeof addr !== 'string') {
|
|
69
|
-
return addr.port;
|
|
70
|
-
}
|
|
71
|
-
return this._config.port;
|
|
72
|
-
}
|
|
73
|
-
}
|
|
@@ -1,172 +0,0 @@
|
|
|
1
|
-
import type { PeerMetadata } from "./peer-metadata.js";
|
|
2
|
-
import { METADATA_VERSION } from "./peer-metadata.js";
|
|
3
|
-
import { encodeMetadata } from "./metadata-codec.js";
|
|
4
|
-
|
|
5
|
-
export const MAX_METADATA_SIZE = 1000;
|
|
6
|
-
export const MAX_PROVIDERS = 10;
|
|
7
|
-
export const MAX_MODELS_PER_PROVIDER = 20;
|
|
8
|
-
export const MAX_MODEL_NAME_LENGTH = 64;
|
|
9
|
-
export const MAX_REGION_LENGTH = 32;
|
|
10
|
-
|
|
11
|
-
export interface ValidationError {
|
|
12
|
-
field: string;
|
|
13
|
-
message: string;
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
export function validateMetadata(metadata: PeerMetadata): ValidationError[] {
|
|
17
|
-
const errors: ValidationError[] = [];
|
|
18
|
-
|
|
19
|
-
// version
|
|
20
|
-
if (metadata.version !== METADATA_VERSION) {
|
|
21
|
-
errors.push({
|
|
22
|
-
field: "version",
|
|
23
|
-
message: `Expected version ${METADATA_VERSION}, got ${metadata.version}`,
|
|
24
|
-
});
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
// peerId length (64 hex chars = 32 bytes)
|
|
28
|
-
if (!/^[0-9a-f]{64}$/.test(metadata.peerId)) {
|
|
29
|
-
errors.push({
|
|
30
|
-
field: "peerId",
|
|
31
|
-
message: "PeerId must be exactly 64 lowercase hex characters",
|
|
32
|
-
});
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
// region
|
|
36
|
-
if (!metadata.region || metadata.region.length === 0) {
|
|
37
|
-
errors.push({
|
|
38
|
-
field: "region",
|
|
39
|
-
message: "Region must not be empty",
|
|
40
|
-
});
|
|
41
|
-
} else if (metadata.region.length > MAX_REGION_LENGTH) {
|
|
42
|
-
errors.push({
|
|
43
|
-
field: "region",
|
|
44
|
-
message: `Region length ${metadata.region.length} exceeds max ${MAX_REGION_LENGTH}`,
|
|
45
|
-
});
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
// timestamp
|
|
49
|
-
if (metadata.timestamp <= 0 || !Number.isFinite(metadata.timestamp)) {
|
|
50
|
-
errors.push({
|
|
51
|
-
field: "timestamp",
|
|
52
|
-
message: "Timestamp must be a positive finite number",
|
|
53
|
-
});
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
// providers count
|
|
57
|
-
if (metadata.providers.length === 0) {
|
|
58
|
-
errors.push({
|
|
59
|
-
field: "providers",
|
|
60
|
-
message: "Must have at least one provider",
|
|
61
|
-
});
|
|
62
|
-
} else if (metadata.providers.length > MAX_PROVIDERS) {
|
|
63
|
-
errors.push({
|
|
64
|
-
field: "providers",
|
|
65
|
-
message: `Provider count ${metadata.providers.length} exceeds max ${MAX_PROVIDERS}`,
|
|
66
|
-
});
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
// each provider
|
|
70
|
-
for (let i = 0; i < metadata.providers.length; i++) {
|
|
71
|
-
const p = metadata.providers[i]!;
|
|
72
|
-
|
|
73
|
-
// models count
|
|
74
|
-
if (p.models.length > MAX_MODELS_PER_PROVIDER) {
|
|
75
|
-
errors.push({
|
|
76
|
-
field: `providers[${i}].models`,
|
|
77
|
-
message: `Model count ${p.models.length} exceeds max ${MAX_MODELS_PER_PROVIDER}`,
|
|
78
|
-
});
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
// model name length
|
|
82
|
-
for (let j = 0; j < p.models.length; j++) {
|
|
83
|
-
const model = p.models[j]!;
|
|
84
|
-
if (model.length > MAX_MODEL_NAME_LENGTH) {
|
|
85
|
-
errors.push({
|
|
86
|
-
field: `providers[${i}].models[${j}]`,
|
|
87
|
-
message: `Model name length ${model.length} exceeds max ${MAX_MODEL_NAME_LENGTH}`,
|
|
88
|
-
});
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
// default pricing
|
|
93
|
-
if (!Number.isFinite(p.defaultPricing?.inputUsdPerMillion) || p.defaultPricing.inputUsdPerMillion < 0) {
|
|
94
|
-
errors.push({
|
|
95
|
-
field: `providers[${i}].defaultPricing.inputUsdPerMillion`,
|
|
96
|
-
message: "Default input price must be a non-negative finite number",
|
|
97
|
-
});
|
|
98
|
-
}
|
|
99
|
-
if (!Number.isFinite(p.defaultPricing?.outputUsdPerMillion) || p.defaultPricing.outputUsdPerMillion < 0) {
|
|
100
|
-
errors.push({
|
|
101
|
-
field: `providers[${i}].defaultPricing.outputUsdPerMillion`,
|
|
102
|
-
message: "Default output price must be a non-negative finite number",
|
|
103
|
-
});
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
// model pricing (optional)
|
|
107
|
-
if (p.modelPricing !== undefined) {
|
|
108
|
-
for (const [modelName, modelPricing] of Object.entries(p.modelPricing)) {
|
|
109
|
-
if (!modelPricing || !Number.isFinite(modelPricing.inputUsdPerMillion) || modelPricing.inputUsdPerMillion < 0) {
|
|
110
|
-
errors.push({
|
|
111
|
-
field: `providers[${i}].modelPricing.${modelName}.inputUsdPerMillion`,
|
|
112
|
-
message: "Model input price must be a non-negative finite number",
|
|
113
|
-
});
|
|
114
|
-
}
|
|
115
|
-
if (!modelPricing || !Number.isFinite(modelPricing.outputUsdPerMillion) || modelPricing.outputUsdPerMillion < 0) {
|
|
116
|
-
errors.push({
|
|
117
|
-
field: `providers[${i}].modelPricing.${modelName}.outputUsdPerMillion`,
|
|
118
|
-
message: "Model output price must be a non-negative finite number",
|
|
119
|
-
});
|
|
120
|
-
}
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
// concurrency
|
|
125
|
-
if (p.maxConcurrency < 1) {
|
|
126
|
-
errors.push({
|
|
127
|
-
field: `providers[${i}].maxConcurrency`,
|
|
128
|
-
message: "Max concurrency must be at least 1",
|
|
129
|
-
});
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
// currentLoad
|
|
133
|
-
if (p.currentLoad < 0) {
|
|
134
|
-
errors.push({
|
|
135
|
-
field: `providers[${i}].currentLoad`,
|
|
136
|
-
message: "Current load must be non-negative",
|
|
137
|
-
});
|
|
138
|
-
}
|
|
139
|
-
if (p.currentLoad > p.maxConcurrency) {
|
|
140
|
-
errors.push({
|
|
141
|
-
field: `providers[${i}].currentLoad`,
|
|
142
|
-
message: "Current load must not exceed max concurrency",
|
|
143
|
-
});
|
|
144
|
-
}
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
// signature length (128 hex chars = 64 bytes)
|
|
148
|
-
if (!/^[0-9a-f]{128}$/.test(metadata.signature)) {
|
|
149
|
-
errors.push({
|
|
150
|
-
field: "signature",
|
|
151
|
-
message: "Signature must be exactly 128 lowercase hex characters (64 bytes)",
|
|
152
|
-
});
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
// encoded size
|
|
156
|
-
try {
|
|
157
|
-
const encoded = encodeMetadata(metadata);
|
|
158
|
-
if (encoded.length > MAX_METADATA_SIZE) {
|
|
159
|
-
errors.push({
|
|
160
|
-
field: "encoded",
|
|
161
|
-
message: `Encoded size ${encoded.length} exceeds max ${MAX_METADATA_SIZE}`,
|
|
162
|
-
});
|
|
163
|
-
}
|
|
164
|
-
} catch {
|
|
165
|
-
errors.push({
|
|
166
|
-
field: "encoded",
|
|
167
|
-
message: "Failed to encode metadata for size check",
|
|
168
|
-
});
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
return errors;
|
|
172
|
-
}
|