@sip-protocol/sdk 0.7.1 → 0.7.3
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/browser.d.mts +1 -1
- package/dist/browser.d.ts +1 -1
- package/dist/browser.js +2926 -341
- package/dist/browser.mjs +48 -2
- package/dist/chunk-2XIVXWHA.mjs +1930 -0
- package/dist/chunk-3M3HNQCW.mjs +18253 -0
- package/dist/chunk-7RFRWDCW.mjs +1504 -0
- package/dist/chunk-F6F73W35.mjs +16166 -0
- package/dist/chunk-OFDBEIEK.mjs +16166 -0
- package/dist/chunk-SF7YSLF5.mjs +1515 -0
- package/dist/chunk-WWUSGOXE.mjs +17129 -0
- package/dist/index-8MQz13eJ.d.mts +13746 -0
- package/dist/index-B71aXVzk.d.ts +13264 -0
- package/dist/index-DIBZHOOQ.d.ts +13746 -0
- package/dist/index-pOIIuwfV.d.mts +13264 -0
- package/dist/index.d.mts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +2911 -326
- package/dist/index.mjs +48 -2
- package/dist/solana-4O4K45VU.mjs +46 -0
- package/dist/solana-NDABAZ6P.mjs +56 -0
- package/dist/solana-ZYO63LY5.mjs +46 -0
- package/package.json +3 -3
- package/src/chains/solana/index.ts +24 -0
- package/src/chains/solana/providers/generic.ts +160 -0
- package/src/chains/solana/providers/helius.ts +249 -0
- package/src/chains/solana/providers/index.ts +54 -0
- package/src/chains/solana/providers/interface.ts +178 -0
- package/src/chains/solana/providers/webhook.ts +519 -0
- package/src/chains/solana/scan.ts +88 -8
- package/src/chains/solana/types.ts +20 -1
- package/src/compliance/index.ts +14 -0
- package/src/compliance/range-sas.ts +591 -0
- package/src/index.ts +99 -0
- package/src/privacy-backends/index.ts +86 -0
- package/src/privacy-backends/interface.ts +263 -0
- package/src/privacy-backends/privacycash-types.ts +278 -0
- package/src/privacy-backends/privacycash.ts +460 -0
- package/src/privacy-backends/registry.ts +278 -0
- package/src/privacy-backends/router.ts +346 -0
- package/src/privacy-backends/sip-native.ts +253 -0
- package/src/proofs/noir.ts +1 -1
- package/src/surveillance/algorithms/address-reuse.ts +143 -0
- package/src/surveillance/algorithms/cluster.ts +247 -0
- package/src/surveillance/algorithms/exchange.ts +295 -0
- package/src/surveillance/algorithms/temporal.ts +337 -0
- package/src/surveillance/analyzer.ts +442 -0
- package/src/surveillance/index.ts +64 -0
- package/src/surveillance/scoring.ts +372 -0
- package/src/surveillance/types.ts +264 -0
|
@@ -0,0 +1,278 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Privacy Backend Registry
|
|
3
|
+
*
|
|
4
|
+
* Manages registration and discovery of privacy backends.
|
|
5
|
+
*
|
|
6
|
+
* @example
|
|
7
|
+
* ```typescript
|
|
8
|
+
* const registry = new PrivacyBackendRegistry()
|
|
9
|
+
*
|
|
10
|
+
* // Register backends
|
|
11
|
+
* registry.register(new SIPNativeBackend())
|
|
12
|
+
* registry.register(new PrivacyCashBackend(), { priority: 10 })
|
|
13
|
+
*
|
|
14
|
+
* // Get backends
|
|
15
|
+
* const all = registry.getAll()
|
|
16
|
+
* const byName = registry.get('sip-native')
|
|
17
|
+
* const forChain = registry.getByChain('solana')
|
|
18
|
+
* const forType = registry.getByType('transaction')
|
|
19
|
+
* ```
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
import type { ChainType } from '@sip-protocol/types'
|
|
23
|
+
import type {
|
|
24
|
+
PrivacyBackend,
|
|
25
|
+
BackendType,
|
|
26
|
+
BackendRegistrationOptions,
|
|
27
|
+
RegisteredBackend,
|
|
28
|
+
TransferParams,
|
|
29
|
+
AvailabilityResult,
|
|
30
|
+
} from './interface'
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Default priority for registered backends
|
|
34
|
+
*/
|
|
35
|
+
const DEFAULT_PRIORITY = 50
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Registry for managing privacy backends
|
|
39
|
+
*
|
|
40
|
+
* Provides a centralized way to register, discover, and manage
|
|
41
|
+
* different privacy backend implementations.
|
|
42
|
+
*/
|
|
43
|
+
export class PrivacyBackendRegistry {
|
|
44
|
+
private backends: Map<string, RegisteredBackend> = new Map()
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Register a privacy backend
|
|
48
|
+
*
|
|
49
|
+
* @param backend - Backend instance to register
|
|
50
|
+
* @param options - Registration options
|
|
51
|
+
* @throws Error if backend with same name exists and override is false
|
|
52
|
+
*
|
|
53
|
+
* @example
|
|
54
|
+
* ```typescript
|
|
55
|
+
* registry.register(new SIPNativeBackend())
|
|
56
|
+
* registry.register(new PrivacyCashBackend(), { priority: 100 })
|
|
57
|
+
* ```
|
|
58
|
+
*/
|
|
59
|
+
register(
|
|
60
|
+
backend: PrivacyBackend,
|
|
61
|
+
options: BackendRegistrationOptions = {}
|
|
62
|
+
): void {
|
|
63
|
+
const { override = false, priority = DEFAULT_PRIORITY, enabled = true } = options
|
|
64
|
+
|
|
65
|
+
if (this.backends.has(backend.name) && !override) {
|
|
66
|
+
throw new Error(
|
|
67
|
+
`Backend '${backend.name}' is already registered. ` +
|
|
68
|
+
`Use { override: true } to replace it.`
|
|
69
|
+
)
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
this.backends.set(backend.name, {
|
|
73
|
+
backend,
|
|
74
|
+
priority,
|
|
75
|
+
enabled,
|
|
76
|
+
registeredAt: Date.now(),
|
|
77
|
+
})
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Unregister a backend by name
|
|
82
|
+
*
|
|
83
|
+
* @param name - Backend name to unregister
|
|
84
|
+
* @returns true if backend was removed, false if not found
|
|
85
|
+
*/
|
|
86
|
+
unregister(name: string): boolean {
|
|
87
|
+
return this.backends.delete(name)
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Get a backend by name
|
|
92
|
+
*
|
|
93
|
+
* @param name - Backend name
|
|
94
|
+
* @returns Backend instance or undefined if not found
|
|
95
|
+
*/
|
|
96
|
+
get(name: string): PrivacyBackend | undefined {
|
|
97
|
+
const entry = this.backends.get(name)
|
|
98
|
+
return entry?.enabled ? entry.backend : undefined
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Check if a backend is registered
|
|
103
|
+
*
|
|
104
|
+
* @param name - Backend name
|
|
105
|
+
* @returns true if registered (regardless of enabled state)
|
|
106
|
+
*/
|
|
107
|
+
has(name: string): boolean {
|
|
108
|
+
return this.backends.has(name)
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Get all enabled backends sorted by priority
|
|
113
|
+
*
|
|
114
|
+
* @returns Array of backends (highest priority first)
|
|
115
|
+
*/
|
|
116
|
+
getAll(): PrivacyBackend[] {
|
|
117
|
+
return Array.from(this.backends.values())
|
|
118
|
+
.filter(entry => entry.enabled)
|
|
119
|
+
.sort((a, b) => b.priority - a.priority)
|
|
120
|
+
.map(entry => entry.backend)
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Get all registered entries (including disabled)
|
|
125
|
+
*
|
|
126
|
+
* @returns Array of registered backend entries
|
|
127
|
+
*/
|
|
128
|
+
getAllEntries(): RegisteredBackend[] {
|
|
129
|
+
return Array.from(this.backends.values())
|
|
130
|
+
.sort((a, b) => b.priority - a.priority)
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Get backends supporting a specific chain
|
|
135
|
+
*
|
|
136
|
+
* @param chain - Chain type to filter by
|
|
137
|
+
* @returns Array of backends supporting the chain
|
|
138
|
+
*/
|
|
139
|
+
getByChain(chain: ChainType): PrivacyBackend[] {
|
|
140
|
+
return this.getAll().filter(backend =>
|
|
141
|
+
backend.chains.includes(chain)
|
|
142
|
+
)
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Get backends of a specific type
|
|
147
|
+
*
|
|
148
|
+
* @param type - Backend type to filter by
|
|
149
|
+
* @returns Array of backends of the specified type
|
|
150
|
+
*/
|
|
151
|
+
getByType(type: BackendType): PrivacyBackend[] {
|
|
152
|
+
return this.getAll().filter(backend =>
|
|
153
|
+
backend.type === type || backend.type === 'both'
|
|
154
|
+
)
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Get backends that support compliance (viewing keys)
|
|
159
|
+
*
|
|
160
|
+
* @returns Array of compliance-supporting backends
|
|
161
|
+
*/
|
|
162
|
+
getCompliant(): PrivacyBackend[] {
|
|
163
|
+
return this.getAll().filter(backend =>
|
|
164
|
+
backend.getCapabilities().complianceSupport
|
|
165
|
+
)
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* Find available backends for a transfer
|
|
170
|
+
*
|
|
171
|
+
* @param params - Transfer parameters
|
|
172
|
+
* @returns Array of available backends with availability info
|
|
173
|
+
*/
|
|
174
|
+
async findAvailable(
|
|
175
|
+
params: TransferParams
|
|
176
|
+
): Promise<Array<{ backend: PrivacyBackend; availability: AvailabilityResult }>> {
|
|
177
|
+
const chainBackends = this.getByChain(params.chain)
|
|
178
|
+
const results: Array<{ backend: PrivacyBackend; availability: AvailabilityResult }> = []
|
|
179
|
+
|
|
180
|
+
for (const backend of chainBackends) {
|
|
181
|
+
const availability = await backend.checkAvailability(params)
|
|
182
|
+
if (availability.available) {
|
|
183
|
+
results.push({ backend, availability })
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
return results
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* Enable a backend
|
|
192
|
+
*
|
|
193
|
+
* @param name - Backend name
|
|
194
|
+
* @returns true if backend was enabled, false if not found
|
|
195
|
+
*/
|
|
196
|
+
enable(name: string): boolean {
|
|
197
|
+
const entry = this.backends.get(name)
|
|
198
|
+
if (entry) {
|
|
199
|
+
entry.enabled = true
|
|
200
|
+
return true
|
|
201
|
+
}
|
|
202
|
+
return false
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
/**
|
|
206
|
+
* Disable a backend
|
|
207
|
+
*
|
|
208
|
+
* @param name - Backend name
|
|
209
|
+
* @returns true if backend was disabled, false if not found
|
|
210
|
+
*/
|
|
211
|
+
disable(name: string): boolean {
|
|
212
|
+
const entry = this.backends.get(name)
|
|
213
|
+
if (entry) {
|
|
214
|
+
entry.enabled = false
|
|
215
|
+
return true
|
|
216
|
+
}
|
|
217
|
+
return false
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
/**
|
|
221
|
+
* Set backend priority
|
|
222
|
+
*
|
|
223
|
+
* @param name - Backend name
|
|
224
|
+
* @param priority - New priority value
|
|
225
|
+
* @returns true if priority was set, false if not found
|
|
226
|
+
*/
|
|
227
|
+
setPriority(name: string, priority: number): boolean {
|
|
228
|
+
const entry = this.backends.get(name)
|
|
229
|
+
if (entry) {
|
|
230
|
+
entry.priority = priority
|
|
231
|
+
return true
|
|
232
|
+
}
|
|
233
|
+
return false
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
/**
|
|
237
|
+
* Get count of registered backends
|
|
238
|
+
*
|
|
239
|
+
* @param enabledOnly - If true, only count enabled backends
|
|
240
|
+
* @returns Number of backends
|
|
241
|
+
*/
|
|
242
|
+
count(enabledOnly: boolean = false): number {
|
|
243
|
+
if (enabledOnly) {
|
|
244
|
+
return Array.from(this.backends.values()).filter(e => e.enabled).length
|
|
245
|
+
}
|
|
246
|
+
return this.backends.size
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
/**
|
|
250
|
+
* Clear all registered backends
|
|
251
|
+
*/
|
|
252
|
+
clear(): void {
|
|
253
|
+
this.backends.clear()
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
/**
|
|
257
|
+
* Get backend names
|
|
258
|
+
*
|
|
259
|
+
* @param enabledOnly - If true, only return enabled backend names
|
|
260
|
+
* @returns Array of backend names
|
|
261
|
+
*/
|
|
262
|
+
getNames(enabledOnly: boolean = false): string[] {
|
|
263
|
+
if (enabledOnly) {
|
|
264
|
+
return Array.from(this.backends.entries())
|
|
265
|
+
.filter(([, entry]) => entry.enabled)
|
|
266
|
+
.map(([name]) => name)
|
|
267
|
+
}
|
|
268
|
+
return Array.from(this.backends.keys())
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
/**
|
|
273
|
+
* Global default registry instance
|
|
274
|
+
*
|
|
275
|
+
* Use this for simple applications, or create your own instance
|
|
276
|
+
* for more control.
|
|
277
|
+
*/
|
|
278
|
+
export const defaultRegistry = new PrivacyBackendRegistry()
|
|
@@ -0,0 +1,346 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SmartRouter - Privacy Backend Selection
|
|
3
|
+
*
|
|
4
|
+
* Automatically selects the optimal privacy backend based on:
|
|
5
|
+
* - User preferences (privacy, speed, cost, compliance)
|
|
6
|
+
* - Backend capabilities and availability
|
|
7
|
+
* - Transfer parameters
|
|
8
|
+
*
|
|
9
|
+
* @example
|
|
10
|
+
* ```typescript
|
|
11
|
+
* import { SmartRouter, PrivacyBackendRegistry, SIPNativeBackend } from '@sip-protocol/sdk'
|
|
12
|
+
*
|
|
13
|
+
* const registry = new PrivacyBackendRegistry()
|
|
14
|
+
* registry.register(new SIPNativeBackend())
|
|
15
|
+
*
|
|
16
|
+
* const router = new SmartRouter(registry)
|
|
17
|
+
*
|
|
18
|
+
* // Auto-select best backend
|
|
19
|
+
* const result = await router.execute(params, {
|
|
20
|
+
* prioritize: 'compliance',
|
|
21
|
+
* requireViewingKeys: true,
|
|
22
|
+
* })
|
|
23
|
+
*
|
|
24
|
+
* // Or just select without executing
|
|
25
|
+
* const selection = await router.selectBackend(params, config)
|
|
26
|
+
* console.log(`Selected: ${selection.backend.name}`)
|
|
27
|
+
* ```
|
|
28
|
+
*/
|
|
29
|
+
|
|
30
|
+
import type {
|
|
31
|
+
PrivacyBackend,
|
|
32
|
+
TransferParams,
|
|
33
|
+
TransactionResult,
|
|
34
|
+
SmartRouterConfig,
|
|
35
|
+
BackendSelectionResult,
|
|
36
|
+
AvailabilityResult,
|
|
37
|
+
} from './interface'
|
|
38
|
+
import { PrivacyBackendRegistry } from './registry'
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Default router configuration
|
|
42
|
+
*/
|
|
43
|
+
const DEFAULT_CONFIG: SmartRouterConfig = {
|
|
44
|
+
prioritize: 'privacy',
|
|
45
|
+
requireViewingKeys: false,
|
|
46
|
+
allowComputePrivacy: true,
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Scoring weights for different priorities
|
|
51
|
+
*/
|
|
52
|
+
const PRIORITY_WEIGHTS = {
|
|
53
|
+
privacy: {
|
|
54
|
+
hiddenAmount: 25,
|
|
55
|
+
hiddenSender: 25,
|
|
56
|
+
hiddenRecipient: 25,
|
|
57
|
+
hiddenCompute: 10,
|
|
58
|
+
anonymitySet: 15,
|
|
59
|
+
},
|
|
60
|
+
speed: {
|
|
61
|
+
fast: 40,
|
|
62
|
+
medium: 25,
|
|
63
|
+
slow: 10,
|
|
64
|
+
setupRequired: -20,
|
|
65
|
+
},
|
|
66
|
+
cost: {
|
|
67
|
+
baseCost: 50,
|
|
68
|
+
estimatedCost: 50,
|
|
69
|
+
},
|
|
70
|
+
compliance: {
|
|
71
|
+
complianceSupport: 60,
|
|
72
|
+
hiddenAmount: 15,
|
|
73
|
+
hiddenSender: 15,
|
|
74
|
+
hiddenRecipient: 10,
|
|
75
|
+
},
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* SmartRouter for automatic backend selection
|
|
80
|
+
*
|
|
81
|
+
* Analyzes available backends and selects the optimal one
|
|
82
|
+
* based on user preferences and transfer requirements.
|
|
83
|
+
*/
|
|
84
|
+
export class SmartRouter {
|
|
85
|
+
private registry: PrivacyBackendRegistry
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Create a new SmartRouter
|
|
89
|
+
*
|
|
90
|
+
* @param registry - Backend registry to use for selection
|
|
91
|
+
*/
|
|
92
|
+
constructor(registry: PrivacyBackendRegistry) {
|
|
93
|
+
this.registry = registry
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Select the best backend for a transfer
|
|
98
|
+
*
|
|
99
|
+
* @param params - Transfer parameters
|
|
100
|
+
* @param config - Router configuration
|
|
101
|
+
* @returns Selection result with backend and reasoning
|
|
102
|
+
* @throws Error if no suitable backend is found
|
|
103
|
+
*/
|
|
104
|
+
async selectBackend(
|
|
105
|
+
params: TransferParams,
|
|
106
|
+
config: Partial<SmartRouterConfig> = {}
|
|
107
|
+
): Promise<BackendSelectionResult> {
|
|
108
|
+
const fullConfig = { ...DEFAULT_CONFIG, ...config }
|
|
109
|
+
|
|
110
|
+
// Get backends for the chain
|
|
111
|
+
const chainBackends = this.registry.getByChain(params.chain)
|
|
112
|
+
|
|
113
|
+
if (chainBackends.length === 0) {
|
|
114
|
+
throw new Error(
|
|
115
|
+
`No backends available for chain '${params.chain}'. ` +
|
|
116
|
+
`Register a backend that supports this chain.`
|
|
117
|
+
)
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// Filter and score backends
|
|
121
|
+
const scoredBackends: Array<{
|
|
122
|
+
backend: PrivacyBackend
|
|
123
|
+
availability: AvailabilityResult
|
|
124
|
+
score: number
|
|
125
|
+
reason: string
|
|
126
|
+
}> = []
|
|
127
|
+
|
|
128
|
+
for (const backend of chainBackends) {
|
|
129
|
+
// Check exclusions
|
|
130
|
+
if (fullConfig.excludeBackends?.includes(backend.name)) {
|
|
131
|
+
continue
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// Check availability
|
|
135
|
+
const availability = await backend.checkAvailability(params)
|
|
136
|
+
if (!availability.available) {
|
|
137
|
+
continue
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// Check hard requirements
|
|
141
|
+
const capabilities = backend.getCapabilities()
|
|
142
|
+
|
|
143
|
+
// Viewing key requirement
|
|
144
|
+
if (fullConfig.requireViewingKeys && !capabilities.complianceSupport) {
|
|
145
|
+
continue
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// Anonymity set requirement
|
|
149
|
+
if (
|
|
150
|
+
fullConfig.minAnonymitySet &&
|
|
151
|
+
capabilities.anonymitySet !== undefined &&
|
|
152
|
+
capabilities.anonymitySet < fullConfig.minAnonymitySet
|
|
153
|
+
) {
|
|
154
|
+
continue
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// Compute privacy filter
|
|
158
|
+
if (!fullConfig.allowComputePrivacy && backend.type === 'compute') {
|
|
159
|
+
continue
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// Cost limit
|
|
163
|
+
if (fullConfig.maxCost && availability.estimatedCost) {
|
|
164
|
+
if (availability.estimatedCost > fullConfig.maxCost) {
|
|
165
|
+
continue
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// Latency limit
|
|
170
|
+
if (fullConfig.maxLatency && availability.estimatedTime) {
|
|
171
|
+
if (availability.estimatedTime > fullConfig.maxLatency) {
|
|
172
|
+
continue
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// Score this backend
|
|
177
|
+
const { score, reason } = this.scoreBackend(
|
|
178
|
+
backend,
|
|
179
|
+
availability,
|
|
180
|
+
fullConfig
|
|
181
|
+
)
|
|
182
|
+
|
|
183
|
+
scoredBackends.push({
|
|
184
|
+
backend,
|
|
185
|
+
availability,
|
|
186
|
+
score,
|
|
187
|
+
reason,
|
|
188
|
+
})
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
if (scoredBackends.length === 0) {
|
|
192
|
+
throw new Error(
|
|
193
|
+
`No backends meet the requirements for this transfer. ` +
|
|
194
|
+
`Check your router configuration and registered backends.`
|
|
195
|
+
)
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
// Sort by score (descending)
|
|
199
|
+
scoredBackends.sort((a, b) => b.score - a.score)
|
|
200
|
+
|
|
201
|
+
// Preferred backend bonus
|
|
202
|
+
if (fullConfig.preferredBackend) {
|
|
203
|
+
const preferredIndex = scoredBackends.findIndex(
|
|
204
|
+
s => s.backend.name === fullConfig.preferredBackend
|
|
205
|
+
)
|
|
206
|
+
if (preferredIndex > 0) {
|
|
207
|
+
// Move preferred to top if within 10 points of leader
|
|
208
|
+
const preferred = scoredBackends[preferredIndex]
|
|
209
|
+
const leader = scoredBackends[0]
|
|
210
|
+
if (leader.score - preferred.score <= 10) {
|
|
211
|
+
scoredBackends.splice(preferredIndex, 1)
|
|
212
|
+
scoredBackends.unshift(preferred)
|
|
213
|
+
preferred.reason = `Preferred backend (within 10pts of optimal)`
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
const selected = scoredBackends[0]
|
|
219
|
+
const alternatives = scoredBackends.slice(1).map(s => ({
|
|
220
|
+
backend: s.backend,
|
|
221
|
+
score: s.score,
|
|
222
|
+
reason: s.reason,
|
|
223
|
+
}))
|
|
224
|
+
|
|
225
|
+
return {
|
|
226
|
+
backend: selected.backend,
|
|
227
|
+
reason: selected.reason,
|
|
228
|
+
alternatives,
|
|
229
|
+
score: selected.score,
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
/**
|
|
234
|
+
* Execute a transfer using the best available backend
|
|
235
|
+
*
|
|
236
|
+
* @param params - Transfer parameters
|
|
237
|
+
* @param config - Router configuration
|
|
238
|
+
* @returns Transaction result
|
|
239
|
+
*/
|
|
240
|
+
async execute(
|
|
241
|
+
params: TransferParams,
|
|
242
|
+
config: Partial<SmartRouterConfig> = {}
|
|
243
|
+
): Promise<TransactionResult> {
|
|
244
|
+
const selection = await this.selectBackend(params, config)
|
|
245
|
+
return selection.backend.execute(params)
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
/**
|
|
249
|
+
* Get available backends for a transfer (without selecting)
|
|
250
|
+
*
|
|
251
|
+
* @param params - Transfer parameters
|
|
252
|
+
* @returns Array of available backends with scores
|
|
253
|
+
*/
|
|
254
|
+
async getAvailableBackends(
|
|
255
|
+
params: TransferParams
|
|
256
|
+
): Promise<Array<{ backend: PrivacyBackend; availability: AvailabilityResult }>> {
|
|
257
|
+
return this.registry.findAvailable(params)
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
/**
|
|
261
|
+
* Score a backend based on configuration priority
|
|
262
|
+
*/
|
|
263
|
+
private scoreBackend(
|
|
264
|
+
backend: PrivacyBackend,
|
|
265
|
+
availability: AvailabilityResult,
|
|
266
|
+
config: SmartRouterConfig
|
|
267
|
+
): { score: number; reason: string } {
|
|
268
|
+
const capabilities = backend.getCapabilities()
|
|
269
|
+
let score = 0
|
|
270
|
+
const reasons: string[] = []
|
|
271
|
+
|
|
272
|
+
switch (config.prioritize) {
|
|
273
|
+
case 'privacy':
|
|
274
|
+
if (capabilities.hiddenAmount) {
|
|
275
|
+
score += PRIORITY_WEIGHTS.privacy.hiddenAmount
|
|
276
|
+
reasons.push('hidden amounts')
|
|
277
|
+
}
|
|
278
|
+
if (capabilities.hiddenSender) {
|
|
279
|
+
score += PRIORITY_WEIGHTS.privacy.hiddenSender
|
|
280
|
+
reasons.push('hidden sender')
|
|
281
|
+
}
|
|
282
|
+
if (capabilities.hiddenRecipient) {
|
|
283
|
+
score += PRIORITY_WEIGHTS.privacy.hiddenRecipient
|
|
284
|
+
reasons.push('hidden recipient')
|
|
285
|
+
}
|
|
286
|
+
if (capabilities.hiddenCompute) {
|
|
287
|
+
score += PRIORITY_WEIGHTS.privacy.hiddenCompute
|
|
288
|
+
reasons.push('private compute')
|
|
289
|
+
}
|
|
290
|
+
if (capabilities.anonymitySet && capabilities.anonymitySet >= 100) {
|
|
291
|
+
score += PRIORITY_WEIGHTS.privacy.anonymitySet
|
|
292
|
+
reasons.push(`anonymity set: ${capabilities.anonymitySet}`)
|
|
293
|
+
}
|
|
294
|
+
break
|
|
295
|
+
|
|
296
|
+
case 'speed':
|
|
297
|
+
score += PRIORITY_WEIGHTS.speed[capabilities.latencyEstimate]
|
|
298
|
+
reasons.push(`${capabilities.latencyEstimate} latency`)
|
|
299
|
+
if (capabilities.setupRequired) {
|
|
300
|
+
score += PRIORITY_WEIGHTS.speed.setupRequired
|
|
301
|
+
reasons.push('setup required')
|
|
302
|
+
}
|
|
303
|
+
break
|
|
304
|
+
|
|
305
|
+
case 'cost':
|
|
306
|
+
// Lower cost = higher score (invert with log scale)
|
|
307
|
+
if (availability.estimatedCost !== undefined) {
|
|
308
|
+
// Use log scale to handle wide range of costs
|
|
309
|
+
// log10(100) ≈ 2, log10(100000) ≈ 5, log10(1000000) ≈ 6
|
|
310
|
+
const logCost = availability.estimatedCost > BigInt(0)
|
|
311
|
+
? Math.log10(Number(availability.estimatedCost))
|
|
312
|
+
: 0
|
|
313
|
+
// Max cost assumed around 10^14 (log = 14), gives score 0
|
|
314
|
+
// Min cost around 10^2 (log = 2), gives score ~60
|
|
315
|
+
const costScore = Math.max(0, 70 - logCost * 5)
|
|
316
|
+
score += costScore
|
|
317
|
+
reasons.push(`low cost`)
|
|
318
|
+
}
|
|
319
|
+
break
|
|
320
|
+
|
|
321
|
+
case 'compliance':
|
|
322
|
+
if (capabilities.complianceSupport) {
|
|
323
|
+
score += PRIORITY_WEIGHTS.compliance.complianceSupport
|
|
324
|
+
reasons.push('viewing key support')
|
|
325
|
+
}
|
|
326
|
+
if (capabilities.hiddenAmount) {
|
|
327
|
+
score += PRIORITY_WEIGHTS.compliance.hiddenAmount
|
|
328
|
+
}
|
|
329
|
+
if (capabilities.hiddenSender) {
|
|
330
|
+
score += PRIORITY_WEIGHTS.compliance.hiddenSender
|
|
331
|
+
}
|
|
332
|
+
if (capabilities.hiddenRecipient) {
|
|
333
|
+
score += PRIORITY_WEIGHTS.compliance.hiddenRecipient
|
|
334
|
+
}
|
|
335
|
+
break
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
// Normalize score to 0-100
|
|
339
|
+
score = Math.min(100, Math.max(0, score))
|
|
340
|
+
|
|
341
|
+
return {
|
|
342
|
+
score,
|
|
343
|
+
reason: reasons.length > 0 ? reasons.join(', ') : 'default selection',
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
}
|