@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
package/src/config/encryption.ts
DELETED
|
@@ -1,53 +0,0 @@
|
|
|
1
|
-
import { createCipheriv, createDecipheriv, randomBytes, scryptSync } from 'node:crypto'
|
|
2
|
-
import { hostname, homedir } from 'node:os'
|
|
3
|
-
|
|
4
|
-
const ALGORITHM = 'aes-256-gcm'
|
|
5
|
-
const SALT_LENGTH = 32
|
|
6
|
-
const IV_LENGTH = 16
|
|
7
|
-
const TAG_LENGTH = 16
|
|
8
|
-
const KEY_LENGTH = 32
|
|
9
|
-
|
|
10
|
-
/**
|
|
11
|
-
* Derive an encryption key from a machine-specific seed.
|
|
12
|
-
*/
|
|
13
|
-
export function deriveMachineKey(salt: Buffer): Buffer {
|
|
14
|
-
const seed = [
|
|
15
|
-
process.env['USER'] ?? process.env['USERNAME'] ?? '',
|
|
16
|
-
hostname(),
|
|
17
|
-
homedir(),
|
|
18
|
-
].join(':')
|
|
19
|
-
return scryptSync(seed, salt, KEY_LENGTH)
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
/**
|
|
23
|
-
* Encrypt a string value. Returns base64-encoded ciphertext with IV and auth tag prepended.
|
|
24
|
-
*/
|
|
25
|
-
export function encryptValue(plaintext: string, key: Buffer): string {
|
|
26
|
-
const iv = randomBytes(IV_LENGTH)
|
|
27
|
-
const cipher = createCipheriv(ALGORITHM, key, iv)
|
|
28
|
-
const encrypted = Buffer.concat([cipher.update(plaintext, 'utf-8'), cipher.final()])
|
|
29
|
-
const tag = cipher.getAuthTag()
|
|
30
|
-
const combined = Buffer.concat([iv, tag, encrypted])
|
|
31
|
-
return combined.toString('base64')
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
/**
|
|
35
|
-
* Decrypt a base64-encoded value produced by encryptValue.
|
|
36
|
-
*/
|
|
37
|
-
export function decryptValue(encoded: string, key: Buffer): string {
|
|
38
|
-
const combined = Buffer.from(encoded, 'base64')
|
|
39
|
-
const iv = combined.subarray(0, IV_LENGTH)
|
|
40
|
-
const tag = combined.subarray(IV_LENGTH, IV_LENGTH + TAG_LENGTH)
|
|
41
|
-
const ciphertext = combined.subarray(IV_LENGTH + TAG_LENGTH)
|
|
42
|
-
const decipher = createDecipheriv(ALGORITHM, key, iv)
|
|
43
|
-
decipher.setAuthTag(tag)
|
|
44
|
-
const decrypted = Buffer.concat([decipher.update(ciphertext), decipher.final()])
|
|
45
|
-
return decrypted.toString('utf-8')
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
/**
|
|
49
|
-
* Generate a fresh random salt for key derivation.
|
|
50
|
-
*/
|
|
51
|
-
export function generateSalt(): Buffer {
|
|
52
|
-
return randomBytes(SALT_LENGTH)
|
|
53
|
-
}
|
|
@@ -1,92 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect, beforeEach } from 'vitest'
|
|
2
|
-
import { mkdtemp, rm } from 'node:fs/promises'
|
|
3
|
-
import { tmpdir } from 'node:os'
|
|
4
|
-
import { join } from 'node:path'
|
|
5
|
-
import { addInstance, removeInstance, getInstance, getInstances, updateInstanceConfig, loadPluginConfig } from './plugin-config-manager.js'
|
|
6
|
-
import type { ConfigField } from '../interfaces/plugin.js'
|
|
7
|
-
|
|
8
|
-
const schema: ConfigField[] = [
|
|
9
|
-
{ key: 'API_KEY', label: 'API Key', type: 'secret', required: true },
|
|
10
|
-
{ key: 'BASE_URL', label: 'Base URL', type: 'string', required: false },
|
|
11
|
-
]
|
|
12
|
-
|
|
13
|
-
describe('plugin-config-manager', () => {
|
|
14
|
-
let tmpDir: string
|
|
15
|
-
let configPath: string
|
|
16
|
-
|
|
17
|
-
beforeEach(async () => {
|
|
18
|
-
tmpDir = await mkdtemp(join(tmpdir(), 'antseed-test-'))
|
|
19
|
-
configPath = join(tmpDir, 'config.json')
|
|
20
|
-
})
|
|
21
|
-
|
|
22
|
-
it('should add and retrieve an instance', async () => {
|
|
23
|
-
await addInstance(configPath, {
|
|
24
|
-
id: 'test-provider',
|
|
25
|
-
package: '@antseed/provider-test',
|
|
26
|
-
type: 'provider',
|
|
27
|
-
config: { API_KEY: 'sk-secret-123', BASE_URL: 'https://api.test.com' },
|
|
28
|
-
}, schema)
|
|
29
|
-
|
|
30
|
-
const instance = await getInstance(configPath, 'test-provider')
|
|
31
|
-
expect(instance).not.toBeNull()
|
|
32
|
-
expect(instance!.id).toBe('test-provider')
|
|
33
|
-
expect(instance!.config['API_KEY']).toBe('sk-secret-123')
|
|
34
|
-
expect(instance!.config['BASE_URL']).toBe('https://api.test.com')
|
|
35
|
-
})
|
|
36
|
-
|
|
37
|
-
it('should encrypt secrets in the file', async () => {
|
|
38
|
-
await addInstance(configPath, {
|
|
39
|
-
id: 'test',
|
|
40
|
-
package: '@antseed/provider-test',
|
|
41
|
-
type: 'provider',
|
|
42
|
-
config: { API_KEY: 'sk-secret', BASE_URL: 'https://api.test.com' },
|
|
43
|
-
}, schema)
|
|
44
|
-
|
|
45
|
-
const raw = await loadPluginConfig(configPath)
|
|
46
|
-
const stored = raw.instances[0]!.config
|
|
47
|
-
// Secret should be encrypted (prefixed with enc:)
|
|
48
|
-
expect(typeof stored['API_KEY']).toBe('string')
|
|
49
|
-
expect((stored['API_KEY'] as string).startsWith('enc:')).toBe(true)
|
|
50
|
-
// Non-secret should be plain
|
|
51
|
-
expect(stored['BASE_URL']).toBe('https://api.test.com')
|
|
52
|
-
})
|
|
53
|
-
|
|
54
|
-
it('should remove an instance', async () => {
|
|
55
|
-
await addInstance(configPath, {
|
|
56
|
-
id: 'to-remove',
|
|
57
|
-
package: '@antseed/provider-test',
|
|
58
|
-
type: 'provider',
|
|
59
|
-
config: {},
|
|
60
|
-
})
|
|
61
|
-
await removeInstance(configPath, 'to-remove')
|
|
62
|
-
const instance = await getInstance(configPath, 'to-remove')
|
|
63
|
-
expect(instance).toBeNull()
|
|
64
|
-
})
|
|
65
|
-
|
|
66
|
-
it('should list all instances', async () => {
|
|
67
|
-
await addInstance(configPath, { id: 'a', package: 'pkg-a', type: 'provider', config: {} })
|
|
68
|
-
await addInstance(configPath, { id: 'b', package: 'pkg-b', type: 'router', config: {} })
|
|
69
|
-
const all = await getInstances(configPath)
|
|
70
|
-
expect(all).toHaveLength(2)
|
|
71
|
-
expect(all.map(i => i.id)).toEqual(['a', 'b'])
|
|
72
|
-
})
|
|
73
|
-
|
|
74
|
-
it('should update instance config', async () => {
|
|
75
|
-
await addInstance(configPath, {
|
|
76
|
-
id: 'updatable',
|
|
77
|
-
package: 'pkg',
|
|
78
|
-
type: 'provider',
|
|
79
|
-
config: { API_KEY: 'old-key' },
|
|
80
|
-
}, schema)
|
|
81
|
-
|
|
82
|
-
await updateInstanceConfig(configPath, 'updatable', { API_KEY: 'new-key' }, schema)
|
|
83
|
-
const updated = await getInstance(configPath, 'updatable')
|
|
84
|
-
expect(updated!.config['API_KEY']).toBe('new-key')
|
|
85
|
-
})
|
|
86
|
-
|
|
87
|
-
it('should reject duplicate instance IDs', async () => {
|
|
88
|
-
await addInstance(configPath, { id: 'dup', package: 'pkg', type: 'provider', config: {} })
|
|
89
|
-
await expect(addInstance(configPath, { id: 'dup', package: 'pkg', type: 'provider', config: {} }))
|
|
90
|
-
.rejects.toThrow('already exists')
|
|
91
|
-
})
|
|
92
|
-
})
|
|
@@ -1,153 +0,0 @@
|
|
|
1
|
-
import { readFile, writeFile, mkdir } from 'node:fs/promises'
|
|
2
|
-
import { dirname } from 'node:path'
|
|
3
|
-
import type { PluginInstanceConfig, PluginConfigFile } from '../types/plugin-config.js'
|
|
4
|
-
import type { ConfigField } from '../interfaces/plugin.js'
|
|
5
|
-
import { encryptValue, decryptValue, deriveMachineKey, generateSalt } from './encryption.js'
|
|
6
|
-
|
|
7
|
-
const ENCRYPTED_PREFIX = 'enc:'
|
|
8
|
-
|
|
9
|
-
function isEncrypted(value: unknown): value is string {
|
|
10
|
-
return typeof value === 'string' && value.startsWith(ENCRYPTED_PREFIX)
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
/**
|
|
14
|
-
* Load plugin config file. Returns empty config if file doesn't exist.
|
|
15
|
-
*/
|
|
16
|
-
export async function loadPluginConfig(configPath: string): Promise<PluginConfigFile> {
|
|
17
|
-
try {
|
|
18
|
-
const raw = await readFile(configPath, 'utf-8')
|
|
19
|
-
const parsed = JSON.parse(raw) as Record<string, unknown>
|
|
20
|
-
return {
|
|
21
|
-
instances: Array.isArray(parsed['instances']) ? parsed['instances'] as PluginInstanceConfig[] : [],
|
|
22
|
-
encryption: parsed['encryption'] as PluginConfigFile['encryption'],
|
|
23
|
-
}
|
|
24
|
-
} catch (err) {
|
|
25
|
-
if ((err as NodeJS.ErrnoException).code === 'ENOENT') {
|
|
26
|
-
return { instances: [] }
|
|
27
|
-
}
|
|
28
|
-
throw err
|
|
29
|
-
}
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
/**
|
|
33
|
-
* Save plugin config file. Preserves non-plugin fields.
|
|
34
|
-
*/
|
|
35
|
-
export async function savePluginConfig(configPath: string, pluginConfig: PluginConfigFile): Promise<void> {
|
|
36
|
-
let existing: Record<string, unknown> = {}
|
|
37
|
-
try {
|
|
38
|
-
const raw = await readFile(configPath, 'utf-8')
|
|
39
|
-
existing = JSON.parse(raw) as Record<string, unknown>
|
|
40
|
-
} catch {
|
|
41
|
-
// File doesn't exist yet
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
const merged = {
|
|
45
|
-
...existing,
|
|
46
|
-
instances: pluginConfig.instances,
|
|
47
|
-
...(pluginConfig.encryption ? { encryption: pluginConfig.encryption } : {}),
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
await mkdir(dirname(configPath), { recursive: true })
|
|
51
|
-
await writeFile(configPath, JSON.stringify(merged, null, 2), 'utf-8')
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
function getOrCreateEncryption(config: PluginConfigFile): { key: Buffer; encryption: NonNullable<PluginConfigFile['encryption']> } {
|
|
55
|
-
if (config.encryption?.salt) {
|
|
56
|
-
const salt = Buffer.from(config.encryption.salt, 'hex')
|
|
57
|
-
return { key: deriveMachineKey(salt), encryption: config.encryption }
|
|
58
|
-
}
|
|
59
|
-
const salt = generateSalt()
|
|
60
|
-
const encryption = { algorithm: 'aes-256-gcm', kdf: 'scrypt', salt: salt.toString('hex') }
|
|
61
|
-
return { key: deriveMachineKey(salt), encryption }
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
function encryptSecrets(instanceConfig: Record<string, unknown>, key: Buffer, schema?: ConfigField[]): Record<string, unknown> {
|
|
65
|
-
const result = { ...instanceConfig }
|
|
66
|
-
const secretKeys = new Set(schema?.filter(f => f.type === 'secret').map(f => f.key) ?? [])
|
|
67
|
-
for (const [k, v] of Object.entries(result)) {
|
|
68
|
-
if (secretKeys.has(k) && typeof v === 'string' && !isEncrypted(v)) {
|
|
69
|
-
result[k] = ENCRYPTED_PREFIX + encryptValue(v, key)
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
return result
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
function decryptSecrets(instanceConfig: Record<string, unknown>, key: Buffer): Record<string, unknown> {
|
|
76
|
-
const result = { ...instanceConfig }
|
|
77
|
-
for (const [k, v] of Object.entries(result)) {
|
|
78
|
-
if (isEncrypted(v)) {
|
|
79
|
-
result[k] = decryptValue(v.slice(ENCRYPTED_PREFIX.length), key)
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
return result
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
/**
|
|
86
|
-
* Add a plugin instance with encrypted secrets.
|
|
87
|
-
*/
|
|
88
|
-
export async function addInstance(
|
|
89
|
-
configPath: string,
|
|
90
|
-
instance: PluginInstanceConfig,
|
|
91
|
-
schema?: ConfigField[],
|
|
92
|
-
): Promise<void> {
|
|
93
|
-
const config = await loadPluginConfig(configPath)
|
|
94
|
-
if (config.instances.some(i => i.id === instance.id)) {
|
|
95
|
-
throw new Error(`Instance "${instance.id}" already exists`)
|
|
96
|
-
}
|
|
97
|
-
const { key, encryption } = getOrCreateEncryption(config)
|
|
98
|
-
config.encryption = encryption
|
|
99
|
-
const encrypted = { ...instance, config: encryptSecrets(instance.config, key, schema) }
|
|
100
|
-
config.instances.push(encrypted)
|
|
101
|
-
await savePluginConfig(configPath, config)
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
/**
|
|
105
|
-
* Remove an instance by ID.
|
|
106
|
-
*/
|
|
107
|
-
export async function removeInstance(configPath: string, instanceId: string): Promise<void> {
|
|
108
|
-
const config = await loadPluginConfig(configPath)
|
|
109
|
-
config.instances = config.instances.filter(i => i.id !== instanceId)
|
|
110
|
-
await savePluginConfig(configPath, config)
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
/**
|
|
114
|
-
* Get a single instance with decrypted secrets.
|
|
115
|
-
*/
|
|
116
|
-
export async function getInstance(configPath: string, instanceId: string): Promise<PluginInstanceConfig | null> {
|
|
117
|
-
const config = await loadPluginConfig(configPath)
|
|
118
|
-
const instance = config.instances.find(i => i.id === instanceId)
|
|
119
|
-
if (!instance) return null
|
|
120
|
-
if (!config.encryption?.salt) return instance
|
|
121
|
-
const salt = Buffer.from(config.encryption.salt, 'hex')
|
|
122
|
-
const key = deriveMachineKey(salt)
|
|
123
|
-
return { ...instance, config: decryptSecrets(instance.config, key) }
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
/**
|
|
127
|
-
* Get all instances with decrypted secrets.
|
|
128
|
-
*/
|
|
129
|
-
export async function getInstances(configPath: string): Promise<PluginInstanceConfig[]> {
|
|
130
|
-
const config = await loadPluginConfig(configPath)
|
|
131
|
-
if (!config.encryption?.salt) return config.instances
|
|
132
|
-
const salt = Buffer.from(config.encryption.salt, 'hex')
|
|
133
|
-
const key = deriveMachineKey(salt)
|
|
134
|
-
return config.instances.map(i => ({ ...i, config: decryptSecrets(i.config, key) }))
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
/**
|
|
138
|
-
* Update config for an existing instance.
|
|
139
|
-
*/
|
|
140
|
-
export async function updateInstanceConfig(
|
|
141
|
-
configPath: string,
|
|
142
|
-
instanceId: string,
|
|
143
|
-
newConfig: Record<string, unknown>,
|
|
144
|
-
schema?: ConfigField[],
|
|
145
|
-
): Promise<void> {
|
|
146
|
-
const config = await loadPluginConfig(configPath)
|
|
147
|
-
const idx = config.instances.findIndex(i => i.id === instanceId)
|
|
148
|
-
if (idx === -1) throw new Error(`Instance "${instanceId}" not found`)
|
|
149
|
-
const { key, encryption } = getOrCreateEncryption(config)
|
|
150
|
-
config.encryption = encryption
|
|
151
|
-
config.instances[idx] = { ...config.instances[idx]!, config: encryptSecrets(newConfig, key, schema) }
|
|
152
|
-
await savePluginConfig(configPath, config)
|
|
153
|
-
}
|
|
@@ -1,90 +0,0 @@
|
|
|
1
|
-
import type { Provider } from '../interfaces/seller-provider.js'
|
|
2
|
-
import type { Router } from '../interfaces/buyer-router.js'
|
|
3
|
-
import type { AntseedProviderPlugin, AntseedRouterPlugin, AntseedPlugin } from '../interfaces/plugin.js'
|
|
4
|
-
import { getInstances } from './plugin-config-manager.js'
|
|
5
|
-
|
|
6
|
-
export interface LoadedProvider {
|
|
7
|
-
instanceId: string
|
|
8
|
-
provider: Provider
|
|
9
|
-
pluginName: string
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
export interface LoadedRouter {
|
|
13
|
-
instanceId: string
|
|
14
|
-
router: Router
|
|
15
|
-
pluginName: string
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
/**
|
|
19
|
-
* Load a plugin's default export from its npm package.
|
|
20
|
-
*/
|
|
21
|
-
export async function loadPluginModule(packageName: string, pluginsDir: string): Promise<AntseedPlugin> {
|
|
22
|
-
const { join, resolve } = await import('node:path')
|
|
23
|
-
const pluginPath = join(pluginsDir, 'node_modules', packageName, 'dist', 'index.js')
|
|
24
|
-
const resolved = resolve(pluginPath)
|
|
25
|
-
|
|
26
|
-
if (!resolved.startsWith(resolve(pluginsDir))) {
|
|
27
|
-
throw new Error(`Invalid plugin path: ${packageName}`)
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
let mod: { default?: unknown }
|
|
31
|
-
try {
|
|
32
|
-
mod = await import(resolved) as { default?: unknown }
|
|
33
|
-
} catch (err) {
|
|
34
|
-
const cause = err instanceof Error ? err.message : String(err)
|
|
35
|
-
throw new Error(`Plugin "${packageName}" failed to load from ${resolved}: ${cause}`)
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
const plugin = mod.default
|
|
39
|
-
if (!plugin || typeof plugin !== 'object') {
|
|
40
|
-
throw new Error(`Plugin "${packageName}" does not export a valid plugin object`)
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
const typed = plugin as { type?: string }
|
|
44
|
-
if (typed.type !== 'provider' && typed.type !== 'router') {
|
|
45
|
-
throw new Error(`Plugin "${packageName}" has invalid type: ${typed.type}`)
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
return plugin as AntseedPlugin
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
function instanceConfigToRecord(config: Record<string, unknown>): Record<string, string> {
|
|
52
|
-
const result: Record<string, string> = {}
|
|
53
|
-
for (const [key, value] of Object.entries(config)) {
|
|
54
|
-
if (value !== null && value !== undefined) {
|
|
55
|
-
result[key] = String(value)
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
return result
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
/**
|
|
62
|
-
* Load all enabled plugin instances from config and instantiate them.
|
|
63
|
-
*/
|
|
64
|
-
export async function loadAllPlugins(configPath: string, pluginsDir: string): Promise<{
|
|
65
|
-
providers: LoadedProvider[]
|
|
66
|
-
routers: LoadedRouter[]
|
|
67
|
-
}> {
|
|
68
|
-
const instances = await getInstances(configPath)
|
|
69
|
-
const providers: LoadedProvider[] = []
|
|
70
|
-
const routers: LoadedRouter[] = []
|
|
71
|
-
|
|
72
|
-
for (const instance of instances) {
|
|
73
|
-
if (instance.enabled === false) continue
|
|
74
|
-
|
|
75
|
-
const plugin = await loadPluginModule(instance.package, pluginsDir)
|
|
76
|
-
const configRecord = instanceConfigToRecord(instance.config)
|
|
77
|
-
|
|
78
|
-
if (instance.type === 'provider' && plugin.type === 'provider') {
|
|
79
|
-
const providerPlugin = plugin as AntseedProviderPlugin
|
|
80
|
-
const provider = await providerPlugin.createProvider(configRecord)
|
|
81
|
-
providers.push({ instanceId: instance.id, provider, pluginName: plugin.name })
|
|
82
|
-
} else if (instance.type === 'router' && plugin.type === 'router') {
|
|
83
|
-
const routerPlugin = plugin as AntseedRouterPlugin
|
|
84
|
-
const router = await routerPlugin.createRouter(configRecord)
|
|
85
|
-
routers.push({ instanceId: instance.id, router, pluginName: plugin.name })
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
return { providers, routers }
|
|
90
|
-
}
|
|
@@ -1,169 +0,0 @@
|
|
|
1
|
-
import type { Identity } from "../p2p/identity.js";
|
|
2
|
-
import { signData } from "../p2p/identity.js";
|
|
3
|
-
import type { DHTNode } from "./dht-node.js";
|
|
4
|
-
import { providerTopic, capabilityTopic, topicToInfoHash } from "./dht-node.js";
|
|
5
|
-
import type { PeerOffering } from "../types/capability.js";
|
|
6
|
-
import type { PeerMetadata, ProviderAnnouncement } from "./peer-metadata.js";
|
|
7
|
-
import { METADATA_VERSION } from "./peer-metadata.js";
|
|
8
|
-
import { encodeMetadataForSigning } from "./metadata-codec.js";
|
|
9
|
-
import { debugWarn } from "../utils/debug.js";
|
|
10
|
-
import { bytesToHex } from "../utils/hex.js";
|
|
11
|
-
import type { BaseEscrowClient } from "../payments/evm/escrow-client.js";
|
|
12
|
-
import { identityToEvmAddress } from "../payments/evm/keypair.js";
|
|
13
|
-
|
|
14
|
-
export interface AnnouncerConfig {
|
|
15
|
-
identity: Identity;
|
|
16
|
-
dht: DHTNode;
|
|
17
|
-
providers: Array<{
|
|
18
|
-
provider: string;
|
|
19
|
-
models: string[];
|
|
20
|
-
maxConcurrency: number;
|
|
21
|
-
}>;
|
|
22
|
-
region: string;
|
|
23
|
-
pricing: Map<
|
|
24
|
-
string,
|
|
25
|
-
{
|
|
26
|
-
defaults: { inputUsdPerMillion: number; outputUsdPerMillion: number };
|
|
27
|
-
models?: Record<string, { inputUsdPerMillion: number; outputUsdPerMillion: number }>;
|
|
28
|
-
}
|
|
29
|
-
>;
|
|
30
|
-
offerings?: PeerOffering[];
|
|
31
|
-
stakeAmountUSDC?: number;
|
|
32
|
-
trustScore?: number;
|
|
33
|
-
escrowClient?: BaseEscrowClient;
|
|
34
|
-
reannounceIntervalMs: number;
|
|
35
|
-
signalingPort: number;
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
export class PeerAnnouncer {
|
|
39
|
-
private readonly config: AnnouncerConfig;
|
|
40
|
-
private intervalHandle: ReturnType<typeof setInterval> | null = null;
|
|
41
|
-
private readonly loadMap: Map<string, number> = new Map();
|
|
42
|
-
private _latestMetadata: PeerMetadata | null = null;
|
|
43
|
-
|
|
44
|
-
constructor(config: AnnouncerConfig) {
|
|
45
|
-
this.config = config;
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
async announce(): Promise<void> {
|
|
49
|
-
const providers: ProviderAnnouncement[] = this.config.providers.map((p) => {
|
|
50
|
-
const pricing = this.config.pricing.get(p.provider) ?? {
|
|
51
|
-
defaults: {
|
|
52
|
-
inputUsdPerMillion: 0,
|
|
53
|
-
outputUsdPerMillion: 0,
|
|
54
|
-
},
|
|
55
|
-
};
|
|
56
|
-
return {
|
|
57
|
-
provider: p.provider,
|
|
58
|
-
models: p.models,
|
|
59
|
-
defaultPricing: pricing.defaults,
|
|
60
|
-
...(pricing.models ? { modelPricing: pricing.models } : {}),
|
|
61
|
-
maxConcurrency: p.maxConcurrency,
|
|
62
|
-
currentLoad: this.loadMap.get(p.provider) ?? 0,
|
|
63
|
-
};
|
|
64
|
-
});
|
|
65
|
-
|
|
66
|
-
const metadata: PeerMetadata = {
|
|
67
|
-
peerId: this.config.identity.peerId,
|
|
68
|
-
version: METADATA_VERSION,
|
|
69
|
-
providers,
|
|
70
|
-
...(this.config.offerings && this.config.offerings.length > 0
|
|
71
|
-
? { offerings: this.config.offerings }
|
|
72
|
-
: {}),
|
|
73
|
-
...(this.config.stakeAmountUSDC != null
|
|
74
|
-
? { stakeAmountUSDC: this.config.stakeAmountUSDC }
|
|
75
|
-
: {}),
|
|
76
|
-
...(this.config.trustScore != null
|
|
77
|
-
? { trustScore: this.config.trustScore }
|
|
78
|
-
: {}),
|
|
79
|
-
region: this.config.region,
|
|
80
|
-
timestamp: Date.now(),
|
|
81
|
-
signature: "",
|
|
82
|
-
};
|
|
83
|
-
|
|
84
|
-
// Populate EVM address and on-chain reputation if escrow client is available
|
|
85
|
-
if (this.config.escrowClient) {
|
|
86
|
-
try {
|
|
87
|
-
const evmAddress = identityToEvmAddress(this.config.identity);
|
|
88
|
-
metadata.evmAddress = evmAddress;
|
|
89
|
-
const reputation = await this.config.escrowClient.getReputation(evmAddress);
|
|
90
|
-
metadata.onChainReputation = reputation.weightedAverage;
|
|
91
|
-
metadata.onChainSessionCount = reputation.sessionCount;
|
|
92
|
-
metadata.onChainDisputeCount = reputation.disputeCount;
|
|
93
|
-
} catch {
|
|
94
|
-
// Silently continue without reputation data
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
// Sign metadata
|
|
99
|
-
const dataToSign = encodeMetadataForSigning(metadata);
|
|
100
|
-
const signature = await signData(
|
|
101
|
-
this.config.identity.privateKey,
|
|
102
|
-
dataToSign
|
|
103
|
-
);
|
|
104
|
-
metadata.signature = bytesToHex(signature);
|
|
105
|
-
this._latestMetadata = metadata;
|
|
106
|
-
|
|
107
|
-
// Announce under each provider topic (continue on failure)
|
|
108
|
-
for (const p of providers) {
|
|
109
|
-
try {
|
|
110
|
-
const topic = providerTopic(p.provider);
|
|
111
|
-
const infoHash = topicToInfoHash(topic);
|
|
112
|
-
await this.config.dht.announce(infoHash, this.config.signalingPort);
|
|
113
|
-
} catch {
|
|
114
|
-
// DHT may not have peers yet — will retry on next cycle
|
|
115
|
-
}
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
// Also announce under the wildcard topic for generic discovery
|
|
119
|
-
try {
|
|
120
|
-
const wildcardInfoHash = topicToInfoHash(providerTopic("*"));
|
|
121
|
-
await this.config.dht.announce(wildcardInfoHash, this.config.signalingPort);
|
|
122
|
-
} catch {
|
|
123
|
-
// DHT may not have peers yet — will retry on next cycle
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
// Announce under each capability topic
|
|
127
|
-
if (this.config.offerings) {
|
|
128
|
-
for (const offering of this.config.offerings) {
|
|
129
|
-
try {
|
|
130
|
-
const topic = capabilityTopic(offering.capability, offering.name);
|
|
131
|
-
const infoHash = topicToInfoHash(topic);
|
|
132
|
-
await this.config.dht.announce(infoHash, this.config.signalingPort);
|
|
133
|
-
} catch {
|
|
134
|
-
// DHT may not have peers yet — will retry on next cycle
|
|
135
|
-
}
|
|
136
|
-
}
|
|
137
|
-
}
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
startPeriodicAnnounce(): void {
|
|
141
|
-
if (this.intervalHandle) {
|
|
142
|
-
return;
|
|
143
|
-
}
|
|
144
|
-
// Announce immediately, then on interval
|
|
145
|
-
void this.announce().catch((err) => {
|
|
146
|
-
debugWarn(`[Announcer] Initial announce failed: ${err instanceof Error ? err.message : err}`);
|
|
147
|
-
});
|
|
148
|
-
this.intervalHandle = setInterval(() => {
|
|
149
|
-
void this.announce().catch((err) => {
|
|
150
|
-
debugWarn(`[Announcer] Periodic announce failed: ${err instanceof Error ? err.message : err}`);
|
|
151
|
-
});
|
|
152
|
-
}, this.config.reannounceIntervalMs);
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
stopPeriodicAnnounce(): void {
|
|
156
|
-
if (this.intervalHandle) {
|
|
157
|
-
clearInterval(this.intervalHandle);
|
|
158
|
-
this.intervalHandle = null;
|
|
159
|
-
}
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
updateLoad(providerName: string, currentLoad: number): void {
|
|
163
|
-
this.loadMap.set(providerName, currentLoad);
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
getLatestMetadata(): PeerMetadata | null {
|
|
167
|
-
return this._latestMetadata;
|
|
168
|
-
}
|
|
169
|
-
}
|
|
@@ -1,57 +0,0 @@
|
|
|
1
|
-
export interface BootstrapNode {
|
|
2
|
-
host: string;
|
|
3
|
-
port: number;
|
|
4
|
-
label?: string;
|
|
5
|
-
}
|
|
6
|
-
|
|
7
|
-
export const OFFICIAL_BOOTSTRAP_NODES: BootstrapNode[] = [
|
|
8
|
-
{ host: "router.bittorrent.com", port: 6881, label: "BitTorrent" },
|
|
9
|
-
{ host: "dht.transmissionbt.com", port: 6881, label: "Transmission" },
|
|
10
|
-
{ host: "router.utorrent.com", port: 6881, label: "uTorrent" },
|
|
11
|
-
{ host: "dht.libtorrent.org", port: 25401, label: "libtorrent" },
|
|
12
|
-
{ host: "router.silotis.us", port: 6881, label: "Silotis" },
|
|
13
|
-
{ host: "dht.aelitis.com", port: 6881, label: "Vuze" },
|
|
14
|
-
];
|
|
15
|
-
|
|
16
|
-
export function parseBootstrapList(entries: string[]): BootstrapNode[] {
|
|
17
|
-
return entries.map((entry) => {
|
|
18
|
-
const lastColon = entry.lastIndexOf(":");
|
|
19
|
-
if (lastColon === -1) {
|
|
20
|
-
throw new Error(`Invalid bootstrap entry, missing port: "${entry}"`);
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
const host = entry.slice(0, lastColon);
|
|
24
|
-
const portStr = entry.slice(lastColon + 1);
|
|
25
|
-
const port = parseInt(portStr, 10);
|
|
26
|
-
|
|
27
|
-
if (!Number.isFinite(port) || port < 1 || port > 65535) {
|
|
28
|
-
throw new Error(`Invalid port in bootstrap entry: "${entry}"`);
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
return { host, port };
|
|
32
|
-
});
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
export function mergeBootstrapNodes(
|
|
36
|
-
official: BootstrapNode[],
|
|
37
|
-
userConfigured: BootstrapNode[]
|
|
38
|
-
): BootstrapNode[] {
|
|
39
|
-
const seen = new Set<string>();
|
|
40
|
-
const result: BootstrapNode[] = [];
|
|
41
|
-
|
|
42
|
-
for (const node of [...official, ...userConfigured]) {
|
|
43
|
-
const key = `${node.host}:${node.port}`;
|
|
44
|
-
if (!seen.has(key)) {
|
|
45
|
-
seen.add(key);
|
|
46
|
-
result.push(node);
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
return result;
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
export function toBootstrapConfig(
|
|
54
|
-
nodes: BootstrapNode[]
|
|
55
|
-
): Array<{ host: string; port: number }> {
|
|
56
|
-
return nodes.map((n) => ({ host: n.host, port: n.port }));
|
|
57
|
-
}
|
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
import type { PeerEndpoint, MetadataResolver } from "./metadata-resolver.js";
|
|
2
|
-
import type { PeerMetadata } from "./peer-metadata.js";
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* Default fail-closed metadata resolver.
|
|
6
|
-
*
|
|
7
|
-
* Always returns `null`, meaning no peer metadata can be resolved.
|
|
8
|
-
* This is intentional: without a real transport-specific resolver wired in,
|
|
9
|
-
* no peers will pass the metadata resolution step and `findSellers()` will
|
|
10
|
-
* return an empty list. Callers must supply a concrete `MetadataResolver`
|
|
11
|
-
* implementation (e.g. one that fetches metadata over HTTP or a P2P channel)
|
|
12
|
-
* to discover real peers.
|
|
13
|
-
*/
|
|
14
|
-
export class DefaultMetadataResolver implements MetadataResolver {
|
|
15
|
-
async resolve(_peer: PeerEndpoint): Promise<PeerMetadata | null> {
|
|
16
|
-
return null;
|
|
17
|
-
}
|
|
18
|
-
}
|