@sip-protocol/sdk 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/dist/index.d.mts +3640 -0
- package/dist/index.d.ts +3640 -0
- package/dist/index.js +5725 -0
- package/dist/index.mjs +5606 -0
- package/package.json +61 -0
- package/src/adapters/index.ts +19 -0
- package/src/adapters/near-intents.ts +475 -0
- package/src/adapters/oneclick-client.ts +367 -0
- package/src/commitment.ts +470 -0
- package/src/crypto.ts +93 -0
- package/src/errors.ts +471 -0
- package/src/index.ts +369 -0
- package/src/intent.ts +488 -0
- package/src/privacy.ts +382 -0
- package/src/proofs/index.ts +52 -0
- package/src/proofs/interface.ts +228 -0
- package/src/proofs/mock.ts +258 -0
- package/src/proofs/noir.ts +233 -0
- package/src/sip.ts +299 -0
- package/src/solver/index.ts +25 -0
- package/src/solver/mock-solver.ts +278 -0
- package/src/stealth.ts +414 -0
- package/src/validation.ts +401 -0
- package/src/wallet/base-adapter.ts +407 -0
- package/src/wallet/errors.ts +106 -0
- package/src/wallet/ethereum/adapter.ts +655 -0
- package/src/wallet/ethereum/index.ts +48 -0
- package/src/wallet/ethereum/mock.ts +505 -0
- package/src/wallet/ethereum/types.ts +364 -0
- package/src/wallet/index.ts +116 -0
- package/src/wallet/registry.ts +207 -0
- package/src/wallet/solana/adapter.ts +533 -0
- package/src/wallet/solana/index.ts +40 -0
- package/src/wallet/solana/mock.ts +522 -0
- package/src/wallet/solana/types.ts +253 -0
- package/src/zcash/index.ts +53 -0
- package/src/zcash/rpc-client.ts +623 -0
- package/src/zcash/shielded-service.ts +641 -0
package/src/sip.ts
ADDED
|
@@ -0,0 +1,299 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SIP SDK Main Client
|
|
3
|
+
*
|
|
4
|
+
* High-level interface for interacting with the Shielded Intents Protocol.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import {
|
|
8
|
+
PrivacyLevel,
|
|
9
|
+
IntentStatus,
|
|
10
|
+
type ShieldedIntent,
|
|
11
|
+
type CreateIntentParams,
|
|
12
|
+
type TrackedIntent,
|
|
13
|
+
type Quote,
|
|
14
|
+
type FulfillmentResult,
|
|
15
|
+
type StealthMetaAddress,
|
|
16
|
+
type ViewingKey,
|
|
17
|
+
} from '@sip-protocol/types'
|
|
18
|
+
import { IntentBuilder, createShieldedIntent, trackIntent, hasRequiredProofs } from './intent'
|
|
19
|
+
import {
|
|
20
|
+
generateStealthMetaAddress,
|
|
21
|
+
encodeStealthMetaAddress,
|
|
22
|
+
decodeStealthMetaAddress,
|
|
23
|
+
} from './stealth'
|
|
24
|
+
import { generateViewingKey, deriveViewingKey } from './privacy'
|
|
25
|
+
import type { ChainId, HexString } from '@sip-protocol/types'
|
|
26
|
+
import type { ProofProvider } from './proofs'
|
|
27
|
+
import { ValidationError } from './errors'
|
|
28
|
+
import { isValidChainId } from './validation'
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* SIP SDK configuration
|
|
32
|
+
*/
|
|
33
|
+
export interface SIPConfig {
|
|
34
|
+
/** Network: mainnet or testnet */
|
|
35
|
+
network: 'mainnet' | 'testnet'
|
|
36
|
+
/** Default privacy level */
|
|
37
|
+
defaultPrivacy?: PrivacyLevel
|
|
38
|
+
/** RPC endpoints for chains */
|
|
39
|
+
rpcEndpoints?: Partial<Record<ChainId, string>>
|
|
40
|
+
/**
|
|
41
|
+
* Proof provider for ZK proof generation
|
|
42
|
+
*
|
|
43
|
+
* If not provided, proof generation will not be available.
|
|
44
|
+
* Use MockProofProvider for testing, NoirProofProvider for production.
|
|
45
|
+
*
|
|
46
|
+
* @example
|
|
47
|
+
* ```typescript
|
|
48
|
+
* import { MockProofProvider } from '@sip-protocol/sdk'
|
|
49
|
+
*
|
|
50
|
+
* const sip = new SIP({
|
|
51
|
+
* network: 'testnet',
|
|
52
|
+
* proofProvider: new MockProofProvider(),
|
|
53
|
+
* })
|
|
54
|
+
* ```
|
|
55
|
+
*/
|
|
56
|
+
proofProvider?: ProofProvider
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Wallet adapter interface
|
|
61
|
+
*/
|
|
62
|
+
export interface WalletAdapter {
|
|
63
|
+
/** Connected chain */
|
|
64
|
+
chain: ChainId
|
|
65
|
+
/** Wallet address */
|
|
66
|
+
address: string
|
|
67
|
+
/** Sign a message */
|
|
68
|
+
signMessage(message: string): Promise<string>
|
|
69
|
+
/** Sign a transaction */
|
|
70
|
+
signTransaction(tx: unknown): Promise<unknown>
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Main SIP SDK class
|
|
75
|
+
*/
|
|
76
|
+
export class SIP {
|
|
77
|
+
private config: SIPConfig
|
|
78
|
+
private wallet?: WalletAdapter
|
|
79
|
+
private stealthKeys?: {
|
|
80
|
+
metaAddress: StealthMetaAddress
|
|
81
|
+
spendingPrivateKey: HexString
|
|
82
|
+
viewingPrivateKey: HexString
|
|
83
|
+
}
|
|
84
|
+
private proofProvider?: ProofProvider
|
|
85
|
+
|
|
86
|
+
constructor(config: SIPConfig) {
|
|
87
|
+
// Validate config
|
|
88
|
+
if (!config || typeof config !== 'object') {
|
|
89
|
+
throw new ValidationError('config must be an object')
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
if (config.network !== 'mainnet' && config.network !== 'testnet') {
|
|
93
|
+
throw new ValidationError(
|
|
94
|
+
`network must be 'mainnet' or 'testnet'`,
|
|
95
|
+
'config.network',
|
|
96
|
+
{ received: config.network }
|
|
97
|
+
)
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
if (config.defaultPrivacy !== undefined) {
|
|
101
|
+
const validLevels = ['transparent', 'shielded', 'compliant']
|
|
102
|
+
if (!validLevels.includes(config.defaultPrivacy)) {
|
|
103
|
+
throw new ValidationError(
|
|
104
|
+
`defaultPrivacy must be one of: ${validLevels.join(', ')}`,
|
|
105
|
+
'config.defaultPrivacy',
|
|
106
|
+
{ received: config.defaultPrivacy }
|
|
107
|
+
)
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
this.config = {
|
|
112
|
+
...config,
|
|
113
|
+
defaultPrivacy: config.defaultPrivacy ?? PrivacyLevel.SHIELDED,
|
|
114
|
+
}
|
|
115
|
+
this.proofProvider = config.proofProvider
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Get the configured proof provider
|
|
120
|
+
*/
|
|
121
|
+
getProofProvider(): ProofProvider | undefined {
|
|
122
|
+
return this.proofProvider
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Set or update the proof provider
|
|
127
|
+
*/
|
|
128
|
+
setProofProvider(provider: ProofProvider): void {
|
|
129
|
+
this.proofProvider = provider
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Check if proof provider is available and ready
|
|
134
|
+
*/
|
|
135
|
+
hasProofProvider(): boolean {
|
|
136
|
+
return !!(this.proofProvider && this.proofProvider.isReady)
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Connect a wallet
|
|
141
|
+
*/
|
|
142
|
+
connect(wallet: WalletAdapter): void {
|
|
143
|
+
this.wallet = wallet
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Disconnect wallet
|
|
148
|
+
*/
|
|
149
|
+
disconnect(): void {
|
|
150
|
+
this.wallet = undefined
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Check if wallet is connected
|
|
155
|
+
*/
|
|
156
|
+
isConnected(): boolean {
|
|
157
|
+
return !!this.wallet
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* Get connected wallet
|
|
162
|
+
*/
|
|
163
|
+
getWallet(): WalletAdapter | undefined {
|
|
164
|
+
return this.wallet
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Generate and store stealth keys for this session
|
|
169
|
+
*
|
|
170
|
+
* @throws {ValidationError} If chain is invalid
|
|
171
|
+
*/
|
|
172
|
+
generateStealthKeys(chain: ChainId, label?: string): StealthMetaAddress {
|
|
173
|
+
// Validation delegated to generateStealthMetaAddress
|
|
174
|
+
const keys = generateStealthMetaAddress(chain, label)
|
|
175
|
+
this.stealthKeys = keys
|
|
176
|
+
return keys.metaAddress
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* Get the encoded stealth meta-address for receiving
|
|
181
|
+
*/
|
|
182
|
+
getStealthAddress(): string | undefined {
|
|
183
|
+
if (!this.stealthKeys) return undefined
|
|
184
|
+
return encodeStealthMetaAddress(this.stealthKeys.metaAddress)
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* Create a new intent builder
|
|
189
|
+
*
|
|
190
|
+
* The builder is automatically configured with the SIP client's proof provider
|
|
191
|
+
* (if one is set), so proofs will be generated automatically when `.build()` is called.
|
|
192
|
+
*
|
|
193
|
+
* @example
|
|
194
|
+
* ```typescript
|
|
195
|
+
* const intent = await sip.intent()
|
|
196
|
+
* .input('near', 'NEAR', 100n)
|
|
197
|
+
* .output('zcash', 'ZEC', 95n)
|
|
198
|
+
* .privacy(PrivacyLevel.SHIELDED)
|
|
199
|
+
* .build()
|
|
200
|
+
* ```
|
|
201
|
+
*/
|
|
202
|
+
intent(): IntentBuilder {
|
|
203
|
+
const builder = new IntentBuilder()
|
|
204
|
+
if (this.proofProvider) {
|
|
205
|
+
builder.withProvider(this.proofProvider)
|
|
206
|
+
}
|
|
207
|
+
return builder
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
/**
|
|
211
|
+
* Create a shielded intent directly
|
|
212
|
+
*
|
|
213
|
+
* Uses the SIP client's configured proof provider (if any) to generate proofs
|
|
214
|
+
* automatically for SHIELDED and COMPLIANT privacy levels.
|
|
215
|
+
*/
|
|
216
|
+
async createIntent(params: CreateIntentParams): Promise<TrackedIntent> {
|
|
217
|
+
const intent = await createShieldedIntent(params, {
|
|
218
|
+
senderAddress: this.wallet?.address,
|
|
219
|
+
proofProvider: this.proofProvider,
|
|
220
|
+
})
|
|
221
|
+
return trackIntent(intent)
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
/**
|
|
225
|
+
* Get quotes for an intent (mock implementation)
|
|
226
|
+
*/
|
|
227
|
+
async getQuotes(intent: ShieldedIntent): Promise<Quote[]> {
|
|
228
|
+
// Mock quotes for demo
|
|
229
|
+
const baseAmount = intent.minOutputAmount
|
|
230
|
+
|
|
231
|
+
return [
|
|
232
|
+
{
|
|
233
|
+
quoteId: `quote-${Date.now()}-1`,
|
|
234
|
+
intentId: intent.intentId,
|
|
235
|
+
solverId: 'solver-1',
|
|
236
|
+
outputAmount: baseAmount + (baseAmount * 2n) / 100n, // +2%
|
|
237
|
+
estimatedTime: 30,
|
|
238
|
+
expiry: Math.floor(Date.now() / 1000) + 60,
|
|
239
|
+
fee: baseAmount / 200n, // 0.5%
|
|
240
|
+
},
|
|
241
|
+
{
|
|
242
|
+
quoteId: `quote-${Date.now()}-2`,
|
|
243
|
+
intentId: intent.intentId,
|
|
244
|
+
solverId: 'solver-2',
|
|
245
|
+
outputAmount: baseAmount + (baseAmount * 1n) / 100n, // +1%
|
|
246
|
+
estimatedTime: 15,
|
|
247
|
+
expiry: Math.floor(Date.now() / 1000) + 60,
|
|
248
|
+
fee: baseAmount / 100n, // 1%
|
|
249
|
+
},
|
|
250
|
+
]
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
/**
|
|
254
|
+
* Execute an intent with a selected quote (mock implementation)
|
|
255
|
+
*/
|
|
256
|
+
async execute(
|
|
257
|
+
intent: TrackedIntent,
|
|
258
|
+
quote: Quote,
|
|
259
|
+
): Promise<FulfillmentResult> {
|
|
260
|
+
// Mock execution
|
|
261
|
+
await new Promise((resolve) => setTimeout(resolve, 2000))
|
|
262
|
+
|
|
263
|
+
return {
|
|
264
|
+
intentId: intent.intentId,
|
|
265
|
+
status: IntentStatus.FULFILLED,
|
|
266
|
+
outputAmount: quote.outputAmount,
|
|
267
|
+
txHash: intent.privacyLevel === PrivacyLevel.TRANSPARENT ? `0x${Date.now().toString(16)}` : undefined,
|
|
268
|
+
fulfilledAt: Math.floor(Date.now() / 1000),
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
/**
|
|
273
|
+
* Generate a viewing key for compliant mode
|
|
274
|
+
*/
|
|
275
|
+
generateViewingKey(path?: string): ViewingKey {
|
|
276
|
+
return generateViewingKey(path)
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
/**
|
|
280
|
+
* Derive a child viewing key
|
|
281
|
+
*/
|
|
282
|
+
deriveViewingKey(masterKey: ViewingKey, childPath: string): ViewingKey {
|
|
283
|
+
return deriveViewingKey(masterKey, childPath)
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
/**
|
|
287
|
+
* Get network configuration
|
|
288
|
+
*/
|
|
289
|
+
getNetwork(): 'mainnet' | 'testnet' {
|
|
290
|
+
return this.config.network
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
/**
|
|
295
|
+
* Create a new SIP instance with default testnet config
|
|
296
|
+
*/
|
|
297
|
+
export function createSIP(network: 'mainnet' | 'testnet' = 'testnet'): SIP {
|
|
298
|
+
return new SIP({ network })
|
|
299
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Solver Module
|
|
3
|
+
*
|
|
4
|
+
* Provides solver implementations and utilities for SIP Protocol.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
export { MockSolver, createMockSolver } from './mock-solver'
|
|
8
|
+
export type { MockSolverConfig } from './mock-solver'
|
|
9
|
+
|
|
10
|
+
// Re-export solver types from types package
|
|
11
|
+
export type {
|
|
12
|
+
Solver,
|
|
13
|
+
SolverCapabilities,
|
|
14
|
+
SolverVisibleIntent,
|
|
15
|
+
SolverQuote,
|
|
16
|
+
SIPSolver,
|
|
17
|
+
FulfillmentStatus,
|
|
18
|
+
FulfillmentRequest,
|
|
19
|
+
FulfillmentCommitment,
|
|
20
|
+
FulfillmentProof,
|
|
21
|
+
SwapRoute,
|
|
22
|
+
SwapRouteStep,
|
|
23
|
+
SolverEvent,
|
|
24
|
+
SolverEventListener,
|
|
25
|
+
} from '@sip-protocol/types'
|
|
@@ -0,0 +1,278 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Mock Solver Implementation
|
|
3
|
+
*
|
|
4
|
+
* Reference implementation of the SIPSolver interface for testing
|
|
5
|
+
* and development. Demonstrates how solvers should interact with
|
|
6
|
+
* shielded intents while preserving privacy.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import {
|
|
10
|
+
type SIPSolver,
|
|
11
|
+
type Solver,
|
|
12
|
+
type SolverCapabilities,
|
|
13
|
+
type SolverVisibleIntent,
|
|
14
|
+
type SolverQuote,
|
|
15
|
+
type ShieldedIntent,
|
|
16
|
+
type FulfillmentResult,
|
|
17
|
+
type FulfillmentStatus,
|
|
18
|
+
type ChainId,
|
|
19
|
+
IntentStatus,
|
|
20
|
+
} from '@sip-protocol/types'
|
|
21
|
+
import { bytesToHex, randomBytes } from '@noble/hashes/utils'
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Configuration for MockSolver
|
|
25
|
+
*/
|
|
26
|
+
export interface MockSolverConfig {
|
|
27
|
+
/** Solver name */
|
|
28
|
+
name?: string
|
|
29
|
+
/** Supported chains */
|
|
30
|
+
supportedChains?: ChainId[]
|
|
31
|
+
/** Base fee percentage (0-1) */
|
|
32
|
+
feePercent?: number
|
|
33
|
+
/** Simulated execution time in ms */
|
|
34
|
+
executionDelay?: number
|
|
35
|
+
/** Failure rate for testing (0-1) */
|
|
36
|
+
failureRate?: number
|
|
37
|
+
/** Quote spread percentage (0-1) */
|
|
38
|
+
spreadPercent?: number
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Mock implementation of SIPSolver for testing
|
|
43
|
+
*
|
|
44
|
+
* This solver demonstrates the privacy-preserving interaction pattern:
|
|
45
|
+
* - Only accesses visible fields of intents
|
|
46
|
+
* - Cannot see sender identity or exact input amounts
|
|
47
|
+
* - Generates valid quotes based on output requirements
|
|
48
|
+
*
|
|
49
|
+
* @example
|
|
50
|
+
* ```typescript
|
|
51
|
+
* const solver = new MockSolver({ name: 'Test Solver' })
|
|
52
|
+
*
|
|
53
|
+
* // Check if solver can handle intent
|
|
54
|
+
* if (await solver.canHandle(visibleIntent)) {
|
|
55
|
+
* const quote = await solver.generateQuote(visibleIntent)
|
|
56
|
+
* if (quote) {
|
|
57
|
+
* const result = await solver.fulfill(intent, quote)
|
|
58
|
+
* }
|
|
59
|
+
* }
|
|
60
|
+
* ```
|
|
61
|
+
*/
|
|
62
|
+
export class MockSolver implements SIPSolver {
|
|
63
|
+
readonly info: Solver
|
|
64
|
+
readonly capabilities: SolverCapabilities
|
|
65
|
+
|
|
66
|
+
private readonly feePercent: number
|
|
67
|
+
private readonly executionDelay: number
|
|
68
|
+
private readonly failureRate: number
|
|
69
|
+
private readonly spreadPercent: number
|
|
70
|
+
private readonly pendingFulfillments: Map<string, FulfillmentStatus> = new Map()
|
|
71
|
+
|
|
72
|
+
constructor(config: MockSolverConfig = {}) {
|
|
73
|
+
const supportedChains = config.supportedChains ?? [
|
|
74
|
+
'near', 'ethereum', 'solana', 'zcash', 'polygon', 'arbitrum', 'base'
|
|
75
|
+
] as ChainId[]
|
|
76
|
+
|
|
77
|
+
this.info = {
|
|
78
|
+
id: `mock-solver-${Date.now()}`,
|
|
79
|
+
name: config.name ?? 'Mock SIP Solver',
|
|
80
|
+
supportedChains,
|
|
81
|
+
reputation: 95,
|
|
82
|
+
totalVolume: 1000000n,
|
|
83
|
+
successRate: 0.99,
|
|
84
|
+
minOrderSize: 1n,
|
|
85
|
+
maxOrderSize: 1000000000n,
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// Build supported pairs (all combinations)
|
|
89
|
+
const supportedPairs = new Map<string, string[]>()
|
|
90
|
+
for (const inputChain of supportedChains) {
|
|
91
|
+
const outputs = supportedChains.filter(c => c !== inputChain)
|
|
92
|
+
supportedPairs.set(inputChain, outputs)
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
this.capabilities = {
|
|
96
|
+
inputChains: supportedChains,
|
|
97
|
+
outputChains: supportedChains,
|
|
98
|
+
supportedPairs,
|
|
99
|
+
supportsShielded: true,
|
|
100
|
+
supportsCompliant: true,
|
|
101
|
+
supportsPartialFill: false,
|
|
102
|
+
avgFulfillmentTime: 30,
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
this.feePercent = config.feePercent ?? 0.005 // 0.5% default fee
|
|
106
|
+
this.executionDelay = config.executionDelay ?? 1000
|
|
107
|
+
this.failureRate = config.failureRate ?? 0
|
|
108
|
+
this.spreadPercent = config.spreadPercent ?? 0.01 // 1% spread
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Check if this solver can handle the given intent
|
|
113
|
+
*
|
|
114
|
+
* Privacy-preserving: Only accesses visible fields
|
|
115
|
+
*/
|
|
116
|
+
async canHandle(intent: SolverVisibleIntent): Promise<boolean> {
|
|
117
|
+
// Check if output chain is supported
|
|
118
|
+
const outputChain = intent.outputAsset.chain
|
|
119
|
+
if (!this.capabilities.outputChains.includes(outputChain)) {
|
|
120
|
+
return false
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// Check expiry
|
|
124
|
+
if (intent.expiry < Date.now() / 1000) {
|
|
125
|
+
return false
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// Check minimum amount (solver may have minimums)
|
|
129
|
+
if (intent.minOutputAmount < (this.info.minOrderSize ?? 0n)) {
|
|
130
|
+
return false
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
return true
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Generate a quote for the intent
|
|
138
|
+
*
|
|
139
|
+
* Privacy-preserving:
|
|
140
|
+
* - Does NOT access sender identity (only senderCommitment visible)
|
|
141
|
+
* - Does NOT know exact input amount (only inputCommitment visible)
|
|
142
|
+
* - Quotes based solely on output requirements
|
|
143
|
+
*/
|
|
144
|
+
async generateQuote(intent: SolverVisibleIntent): Promise<SolverQuote | null> {
|
|
145
|
+
// First check if we can handle this
|
|
146
|
+
if (!await this.canHandle(intent)) {
|
|
147
|
+
return null
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// Calculate output amount with spread
|
|
151
|
+
// Real solvers would query liquidity pools, order books, etc.
|
|
152
|
+
const baseOutput = intent.minOutputAmount
|
|
153
|
+
const spreadAmount = (baseOutput * BigInt(Math.floor(this.spreadPercent * 10000))) / 10000n
|
|
154
|
+
const outputAmount = baseOutput + spreadAmount
|
|
155
|
+
|
|
156
|
+
// Calculate fee
|
|
157
|
+
const feeAmount = (outputAmount * BigInt(Math.floor(this.feePercent * 10000))) / 10000n
|
|
158
|
+
|
|
159
|
+
// Generate quote
|
|
160
|
+
const quoteId = `quote-${bytesToHex(randomBytes(8))}`
|
|
161
|
+
const now = Math.floor(Date.now() / 1000)
|
|
162
|
+
|
|
163
|
+
const quote: SolverQuote = {
|
|
164
|
+
quoteId,
|
|
165
|
+
intentId: intent.intentId,
|
|
166
|
+
solverId: this.info.id,
|
|
167
|
+
outputAmount,
|
|
168
|
+
estimatedTime: this.capabilities.avgFulfillmentTime,
|
|
169
|
+
expiry: now + 60, // Quote valid for 1 minute
|
|
170
|
+
fee: feeAmount,
|
|
171
|
+
signature: `0x${bytesToHex(randomBytes(64))}`, // Mock signature
|
|
172
|
+
validUntil: now + 60,
|
|
173
|
+
estimatedGas: 200000n,
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
return quote
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* Fulfill an intent with the given quote
|
|
181
|
+
*
|
|
182
|
+
* In production, this would:
|
|
183
|
+
* 1. Lock collateral
|
|
184
|
+
* 2. Execute the swap on destination chain
|
|
185
|
+
* 3. Generate fulfillment proof
|
|
186
|
+
* 4. Release collateral after verification
|
|
187
|
+
*
|
|
188
|
+
* Privacy preserved:
|
|
189
|
+
* - Funds go to stealth address (unlinkable)
|
|
190
|
+
* - Solver never learns recipient's real identity
|
|
191
|
+
*/
|
|
192
|
+
async fulfill(
|
|
193
|
+
intent: ShieldedIntent,
|
|
194
|
+
quote: SolverQuote,
|
|
195
|
+
): Promise<FulfillmentResult> {
|
|
196
|
+
// Track fulfillment status
|
|
197
|
+
const status: FulfillmentStatus = {
|
|
198
|
+
intentId: intent.intentId,
|
|
199
|
+
status: 'executing',
|
|
200
|
+
}
|
|
201
|
+
this.pendingFulfillments.set(intent.intentId, status)
|
|
202
|
+
|
|
203
|
+
// Simulate execution delay
|
|
204
|
+
await this.delay(this.executionDelay)
|
|
205
|
+
|
|
206
|
+
// Simulate random failures for testing
|
|
207
|
+
if (Math.random() < this.failureRate) {
|
|
208
|
+
status.status = 'failed'
|
|
209
|
+
status.error = 'Simulated failure for testing'
|
|
210
|
+
|
|
211
|
+
return {
|
|
212
|
+
intentId: intent.intentId,
|
|
213
|
+
status: IntentStatus.FAILED,
|
|
214
|
+
fulfilledAt: Math.floor(Date.now() / 1000),
|
|
215
|
+
error: status.error,
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
// Generate mock transaction hash
|
|
220
|
+
const txHash = `0x${bytesToHex(randomBytes(32))}`
|
|
221
|
+
status.status = 'completed'
|
|
222
|
+
status.txHash = txHash
|
|
223
|
+
|
|
224
|
+
return {
|
|
225
|
+
intentId: intent.intentId,
|
|
226
|
+
status: IntentStatus.FULFILLED,
|
|
227
|
+
outputAmount: quote.outputAmount,
|
|
228
|
+
txHash: intent.privacyLevel === 'transparent' ? txHash : undefined,
|
|
229
|
+
fulfillmentProof: {
|
|
230
|
+
type: 'fulfillment',
|
|
231
|
+
proof: `0x${bytesToHex(randomBytes(128))}` as const,
|
|
232
|
+
publicInputs: [
|
|
233
|
+
`0x${bytesToHex(new TextEncoder().encode(intent.intentId))}` as const,
|
|
234
|
+
`0x${bytesToHex(new TextEncoder().encode(quote.quoteId))}` as const,
|
|
235
|
+
],
|
|
236
|
+
},
|
|
237
|
+
fulfilledAt: Math.floor(Date.now() / 1000),
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
/**
|
|
242
|
+
* Cancel a pending fulfillment
|
|
243
|
+
*/
|
|
244
|
+
async cancel(intentId: string): Promise<boolean> {
|
|
245
|
+
const status = this.pendingFulfillments.get(intentId)
|
|
246
|
+
if (!status || status.status !== 'pending') {
|
|
247
|
+
return false
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
status.status = 'cancelled'
|
|
251
|
+
return true
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
/**
|
|
255
|
+
* Get fulfillment status
|
|
256
|
+
*/
|
|
257
|
+
async getStatus(intentId: string): Promise<FulfillmentStatus | null> {
|
|
258
|
+
return this.pendingFulfillments.get(intentId) ?? null
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
/**
|
|
262
|
+
* Reset solver state (for testing)
|
|
263
|
+
*/
|
|
264
|
+
reset(): void {
|
|
265
|
+
this.pendingFulfillments.clear()
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
private delay(ms: number): Promise<void> {
|
|
269
|
+
return new Promise(resolve => setTimeout(resolve, ms))
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
/**
|
|
274
|
+
* Create a mock solver with default configuration
|
|
275
|
+
*/
|
|
276
|
+
export function createMockSolver(config?: MockSolverConfig): MockSolver {
|
|
277
|
+
return new MockSolver(config)
|
|
278
|
+
}
|