@luxfi/exchange 0.1.0 → 0.2.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/dist/bridge/use-private-teleport.d.ts +1 -1
- package/dist/bridge/use-private-teleport.d.ts.map +1 -1
- package/dist/bridge/use-private-teleport.js +73 -71
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -2
- package/package.json +5 -1
- package/src/bridge/cross-chain-store.ts +210 -0
- package/src/bridge/index.ts +81 -0
- package/src/bridge/private-teleport-types.ts +578 -0
- package/src/bridge/types.ts +125 -0
- package/src/bridge/use-cross-chain-mint.ts +299 -0
- package/src/bridge/use-private-teleport.ts +951 -0
- package/src/index.ts +2 -2
- package/dist/bridge/__tests__/use-private-teleport.test.d.ts +0 -2
- package/dist/bridge/__tests__/use-private-teleport.test.d.ts.map +0 -1
- package/dist/bridge/__tests__/use-private-teleport.test.js +0 -272
|
@@ -0,0 +1,951 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Private Teleport Hook
|
|
3
|
+
*
|
|
4
|
+
* React hook for cross-chain private teleportation
|
|
5
|
+
* Enables: XVM UTXO → ZNote (shielded) → Z-Chain AMM → destination
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { useCallback, useEffect, useState } from 'react'
|
|
9
|
+
import { usePublicClient, useWalletClient } from 'wagmi'
|
|
10
|
+
import { encodeFunctionData, keccak256, toHex } from 'viem'
|
|
11
|
+
|
|
12
|
+
import {
|
|
13
|
+
type PrivateTeleportConfig,
|
|
14
|
+
type PrivateTeleportRequest,
|
|
15
|
+
type TeleportRecord,
|
|
16
|
+
TeleportState,
|
|
17
|
+
type TeleportStateString,
|
|
18
|
+
type PedersenCommitment,
|
|
19
|
+
type EncryptedValue,
|
|
20
|
+
type RangeProof,
|
|
21
|
+
type MerkleProof,
|
|
22
|
+
DEFAULT_PRIVATE_TELEPORT_CONFIG,
|
|
23
|
+
PRIVATE_TELEPORT_ABI,
|
|
24
|
+
ZNOTE_ABI,
|
|
25
|
+
ZCHAIN_AMM_ABI,
|
|
26
|
+
} from './private-teleport-types'
|
|
27
|
+
|
|
28
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
29
|
+
// HOOK OPTIONS & RETURN TYPE
|
|
30
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
31
|
+
|
|
32
|
+
export interface UsePrivateTeleportOptions {
|
|
33
|
+
/** Custom config */
|
|
34
|
+
config?: Partial<PrivateTeleportConfig>
|
|
35
|
+
/** Enable auto-polling for teleport status */
|
|
36
|
+
pollInterval?: number
|
|
37
|
+
/** Callback when teleport state changes */
|
|
38
|
+
onStateChange?: (teleportId: string, state: TeleportState) => void
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/** Private transfer request to another recipient */
|
|
42
|
+
export interface PrivateTransferRequest {
|
|
43
|
+
teleportId: string
|
|
44
|
+
/** Recipient's viewing public key (for note encryption) */
|
|
45
|
+
recipientViewKey: `0x${string}`
|
|
46
|
+
/** Amount to send (will generate new commitment) */
|
|
47
|
+
amount: bigint
|
|
48
|
+
/** Optional: change amount back to sender */
|
|
49
|
+
changeAmount?: bigint
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/** Unshield request to X-Chain */
|
|
53
|
+
export interface UnshieldToXChainRequest {
|
|
54
|
+
teleportId: string
|
|
55
|
+
/** X-Chain destination address (bech32 format) */
|
|
56
|
+
destinationAddress: string
|
|
57
|
+
/** Amount to unshield (must match commitment) */
|
|
58
|
+
amount: bigint
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export interface UsePrivateTeleportReturn {
|
|
62
|
+
/** Initiate a private teleport */
|
|
63
|
+
teleport: (request: PrivateTeleportRequest) => Promise<string>
|
|
64
|
+
/** Execute private swap on Z-Chain AMM */
|
|
65
|
+
executeSwap: (teleportId: string, poolId: string, minOutput: bigint) => Promise<void>
|
|
66
|
+
/** Export to C-Chain (unshield) */
|
|
67
|
+
exportToDestination: (teleportId: string) => Promise<void>
|
|
68
|
+
/** Unshield back to X-Chain UTXO */
|
|
69
|
+
unshieldToXChain: (request: UnshieldToXChainRequest) => Promise<string>
|
|
70
|
+
/** Private transfer to another recipient (stays shielded) */
|
|
71
|
+
privateTransfer: (request: PrivateTransferRequest) => Promise<number>
|
|
72
|
+
/** Split note into payment + change */
|
|
73
|
+
splitAndTransfer: (teleportId: string, outputs: Array<{recipient: `0x${string}`, amount: bigint}>) => Promise<number[]>
|
|
74
|
+
/** Complete teleport after confirmation */
|
|
75
|
+
completeTeleport: (teleportId: string, warpConfirmation: `0x${string}`) => Promise<void>
|
|
76
|
+
/** Cancel teleport */
|
|
77
|
+
cancelTeleport: (teleportId: string) => Promise<void>
|
|
78
|
+
/** Get teleport record */
|
|
79
|
+
getTeleport: (teleportId: string) => Promise<TeleportRecord | null>
|
|
80
|
+
/** Check if teleport is complete */
|
|
81
|
+
isComplete: (teleportId: string) => Promise<boolean>
|
|
82
|
+
/** Current teleport ID (if any) */
|
|
83
|
+
currentTeleportId: string | null
|
|
84
|
+
/** Current teleport state */
|
|
85
|
+
currentState: TeleportState | null
|
|
86
|
+
/** Loading state */
|
|
87
|
+
isLoading: boolean
|
|
88
|
+
/** Error state */
|
|
89
|
+
error: Error | null
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
93
|
+
// CRYPTO UTILITIES (STUB - REAL IMPL USES @luxfi/crypto)
|
|
94
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Generate Pedersen commitment to an amount
|
|
98
|
+
* In production: Uses @luxfi/crypto Pedersen commitment scheme
|
|
99
|
+
*/
|
|
100
|
+
async function generateCommitment(
|
|
101
|
+
amount: bigint,
|
|
102
|
+
blindingFactor?: `0x${string}`
|
|
103
|
+
): Promise<PedersenCommitment> {
|
|
104
|
+
// Generate random blinding factor if not provided
|
|
105
|
+
const bf = blindingFactor ?? (toHex(BigInt(Math.random() * Number.MAX_SAFE_INTEGER)) as `0x${string}`)
|
|
106
|
+
|
|
107
|
+
// Pedersen: C = g^amount * h^blinding
|
|
108
|
+
// Simplified for now - real implementation uses elliptic curve ops
|
|
109
|
+
const commitment = keccak256(
|
|
110
|
+
`${toHex(amount)}${bf.slice(2)}` as `0x${string}`
|
|
111
|
+
)
|
|
112
|
+
|
|
113
|
+
return {
|
|
114
|
+
commitment,
|
|
115
|
+
blindingFactor: bf,
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* FHE-encrypt a value
|
|
121
|
+
* In production: Uses @luxfi/fhe TFHE encryption
|
|
122
|
+
*/
|
|
123
|
+
async function fheEncrypt(
|
|
124
|
+
value: bigint,
|
|
125
|
+
publicKey: `0x${string}`
|
|
126
|
+
): Promise<EncryptedValue> {
|
|
127
|
+
// Simplified - real implementation uses TFHE
|
|
128
|
+
const ciphertext = keccak256(
|
|
129
|
+
`${toHex(value)}${publicKey.slice(2)}` as `0x${string}`
|
|
130
|
+
)
|
|
131
|
+
|
|
132
|
+
return {
|
|
133
|
+
ciphertext,
|
|
134
|
+
publicKey,
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Generate Bulletproof range proof
|
|
140
|
+
* In production: Uses @luxfi/crypto Bulletproof implementation
|
|
141
|
+
*/
|
|
142
|
+
async function generateRangeProof(
|
|
143
|
+
amount: bigint,
|
|
144
|
+
commitment: `0x${string}`,
|
|
145
|
+
blindingFactor: `0x${string}`,
|
|
146
|
+
rangeBits: number = 64
|
|
147
|
+
): Promise<RangeProof> {
|
|
148
|
+
// Simplified - real implementation generates actual Bulletproof
|
|
149
|
+
const proof = keccak256(
|
|
150
|
+
`${commitment}${blindingFactor.slice(2)}${toHex(amount).slice(2)}` as `0x${string}`
|
|
151
|
+
)
|
|
152
|
+
|
|
153
|
+
return {
|
|
154
|
+
proof,
|
|
155
|
+
commitment,
|
|
156
|
+
rangeBits,
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* Generate nullifier for spending a note
|
|
162
|
+
*/
|
|
163
|
+
function generateNullifier(
|
|
164
|
+
commitment: `0x${string}`,
|
|
165
|
+
spendingKey: `0x${string}`,
|
|
166
|
+
noteIndex: number
|
|
167
|
+
): `0x${string}` {
|
|
168
|
+
return keccak256(
|
|
169
|
+
`${commitment}${spendingKey.slice(2)}${toHex(noteIndex).slice(2)}` as `0x${string}`
|
|
170
|
+
)
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
174
|
+
// STATE MAPPING
|
|
175
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
176
|
+
|
|
177
|
+
const stateMap: Record<number, TeleportState> = {
|
|
178
|
+
0: TeleportState.INITIATED,
|
|
179
|
+
1: TeleportState.SHIELDED,
|
|
180
|
+
2: TeleportState.SWAP_COMPLETE,
|
|
181
|
+
3: TeleportState.EXPORTED,
|
|
182
|
+
4: TeleportState.COMPLETED,
|
|
183
|
+
5: TeleportState.CANCELLED,
|
|
184
|
+
6: TeleportState.EXPIRED,
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
188
|
+
// HOOK IMPLEMENTATION
|
|
189
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
190
|
+
|
|
191
|
+
export function usePrivateTeleport(
|
|
192
|
+
options: UsePrivateTeleportOptions = {}
|
|
193
|
+
): UsePrivateTeleportReturn {
|
|
194
|
+
const {
|
|
195
|
+
config: configOverrides,
|
|
196
|
+
pollInterval = 5000,
|
|
197
|
+
onStateChange,
|
|
198
|
+
} = options
|
|
199
|
+
|
|
200
|
+
const config: PrivateTeleportConfig = {
|
|
201
|
+
...DEFAULT_PRIVATE_TELEPORT_CONFIG,
|
|
202
|
+
...configOverrides,
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
const publicClient = usePublicClient()
|
|
206
|
+
const { data: walletClient } = useWalletClient()
|
|
207
|
+
|
|
208
|
+
const [currentTeleportId, setCurrentTeleportId] = useState<string | null>(null)
|
|
209
|
+
const [currentState, setCurrentState] = useState<TeleportState | null>(null)
|
|
210
|
+
const [isLoading, setIsLoading] = useState(false)
|
|
211
|
+
const [error, setError] = useState<Error | null>(null)
|
|
212
|
+
|
|
213
|
+
// Store commitment secrets for later use
|
|
214
|
+
const [secrets, setSecrets] = useState<Map<string, {
|
|
215
|
+
blindingFactor: `0x${string}`
|
|
216
|
+
spendingKey: `0x${string}`
|
|
217
|
+
noteIndex: number
|
|
218
|
+
}>>(new Map())
|
|
219
|
+
|
|
220
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
221
|
+
// INITIATE TELEPORT
|
|
222
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
223
|
+
|
|
224
|
+
const teleport = useCallback(async (request: PrivateTeleportRequest): Promise<string> => {
|
|
225
|
+
if (!walletClient || !publicClient) {
|
|
226
|
+
throw new Error('Wallet not connected')
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
setIsLoading(true)
|
|
230
|
+
setError(null)
|
|
231
|
+
|
|
232
|
+
try {
|
|
233
|
+
// Generate Pedersen commitment
|
|
234
|
+
const commitment = await generateCommitment(request.amount)
|
|
235
|
+
|
|
236
|
+
// FHE-encrypt the amount
|
|
237
|
+
const fhePublicKey = '0x' + '00'.repeat(32) as `0x${string}` // Would fetch from ZNote
|
|
238
|
+
const encrypted = await fheEncrypt(request.amount, fhePublicKey)
|
|
239
|
+
|
|
240
|
+
// Build Warp message (would be signed by validators in production)
|
|
241
|
+
const warpMessage = encodeFunctionData({
|
|
242
|
+
abi: [{ name: 'teleport', type: 'function', inputs: [
|
|
243
|
+
{ name: 'sourceChain', type: 'bytes32' },
|
|
244
|
+
{ name: 'sourceAsset', type: 'bytes32' },
|
|
245
|
+
{ name: 'sender', type: 'address' },
|
|
246
|
+
{ name: 'deadline', type: 'uint256' },
|
|
247
|
+
], outputs: [] }],
|
|
248
|
+
functionName: 'teleport',
|
|
249
|
+
args: [
|
|
250
|
+
request.sourceChain,
|
|
251
|
+
request.sourceAsset,
|
|
252
|
+
walletClient.account.address,
|
|
253
|
+
BigInt(request.deadline),
|
|
254
|
+
],
|
|
255
|
+
})
|
|
256
|
+
|
|
257
|
+
// Call initiateTeleport
|
|
258
|
+
const txData = encodeFunctionData({
|
|
259
|
+
abi: PRIVATE_TELEPORT_ABI,
|
|
260
|
+
functionName: 'initiateTeleport',
|
|
261
|
+
args: [
|
|
262
|
+
warpMessage,
|
|
263
|
+
commitment.commitment,
|
|
264
|
+
encrypted.ciphertext,
|
|
265
|
+
request.recipient,
|
|
266
|
+
request.destChain,
|
|
267
|
+
request.destAsset ?? request.sourceAsset,
|
|
268
|
+
request.privateSwap,
|
|
269
|
+
],
|
|
270
|
+
})
|
|
271
|
+
|
|
272
|
+
const hash = await walletClient.sendTransaction({
|
|
273
|
+
to: config.teleportContract,
|
|
274
|
+
data: txData,
|
|
275
|
+
value: BigInt(0),
|
|
276
|
+
})
|
|
277
|
+
|
|
278
|
+
// Wait for confirmation
|
|
279
|
+
const receipt = await publicClient.waitForTransactionReceipt({ hash })
|
|
280
|
+
|
|
281
|
+
// Extract teleportId from logs (simplified)
|
|
282
|
+
const teleportId = receipt.logs[0]?.topics[1] ?? hash
|
|
283
|
+
|
|
284
|
+
// Store secrets for later
|
|
285
|
+
const spendingKey = keccak256(toHex(Date.now())) as `0x${string}`
|
|
286
|
+
setSecrets(prev => new Map(prev).set(teleportId, {
|
|
287
|
+
blindingFactor: commitment.blindingFactor!,
|
|
288
|
+
spendingKey,
|
|
289
|
+
noteIndex: 0, // Would be extracted from event
|
|
290
|
+
}))
|
|
291
|
+
|
|
292
|
+
setCurrentTeleportId(teleportId)
|
|
293
|
+
setCurrentState(TeleportState.INITIATED)
|
|
294
|
+
|
|
295
|
+
return teleportId
|
|
296
|
+
} catch (err) {
|
|
297
|
+
const e = err instanceof Error ? err : new Error(String(err))
|
|
298
|
+
setError(e)
|
|
299
|
+
throw e
|
|
300
|
+
} finally {
|
|
301
|
+
setIsLoading(false)
|
|
302
|
+
}
|
|
303
|
+
}, [walletClient, publicClient, config])
|
|
304
|
+
|
|
305
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
306
|
+
// EXECUTE PRIVATE SWAP
|
|
307
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
308
|
+
|
|
309
|
+
const executeSwap = useCallback(async (
|
|
310
|
+
teleportId: string,
|
|
311
|
+
poolId: string,
|
|
312
|
+
minOutput: bigint
|
|
313
|
+
): Promise<void> => {
|
|
314
|
+
if (!walletClient || !publicClient) {
|
|
315
|
+
throw new Error('Wallet not connected')
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
setIsLoading(true)
|
|
319
|
+
setError(null)
|
|
320
|
+
|
|
321
|
+
try {
|
|
322
|
+
// Encrypt minimum output
|
|
323
|
+
const fhePublicKey = '0x' + '00'.repeat(32) as `0x${string}`
|
|
324
|
+
const encryptedMinOutput = await fheEncrypt(minOutput, fhePublicKey)
|
|
325
|
+
|
|
326
|
+
// Generate swap proof (stub)
|
|
327
|
+
const swapProof = keccak256(teleportId as `0x${string}`) as `0x${string}`
|
|
328
|
+
|
|
329
|
+
const txData = encodeFunctionData({
|
|
330
|
+
abi: PRIVATE_TELEPORT_ABI,
|
|
331
|
+
functionName: 'executePrivateSwap',
|
|
332
|
+
args: [
|
|
333
|
+
teleportId as `0x${string}`,
|
|
334
|
+
poolId as `0x${string}`,
|
|
335
|
+
encryptedMinOutput.ciphertext,
|
|
336
|
+
swapProof,
|
|
337
|
+
],
|
|
338
|
+
})
|
|
339
|
+
|
|
340
|
+
const hash = await walletClient.sendTransaction({
|
|
341
|
+
to: config.teleportContract,
|
|
342
|
+
data: txData,
|
|
343
|
+
value: BigInt(0),
|
|
344
|
+
})
|
|
345
|
+
|
|
346
|
+
await publicClient.waitForTransactionReceipt({ hash })
|
|
347
|
+
setCurrentState(TeleportState.SWAP_COMPLETE)
|
|
348
|
+
onStateChange?.(teleportId, TeleportState.SWAP_COMPLETE)
|
|
349
|
+
} catch (err) {
|
|
350
|
+
const e = err instanceof Error ? err : new Error(String(err))
|
|
351
|
+
setError(e)
|
|
352
|
+
throw e
|
|
353
|
+
} finally {
|
|
354
|
+
setIsLoading(false)
|
|
355
|
+
}
|
|
356
|
+
}, [walletClient, publicClient, config, onStateChange])
|
|
357
|
+
|
|
358
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
359
|
+
// GET TELEPORT RECORD (defined early for use by other callbacks)
|
|
360
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
361
|
+
|
|
362
|
+
const getTeleportRecord = useCallback(async (teleportId: string): Promise<TeleportRecord | null> => {
|
|
363
|
+
if (!publicClient) {
|
|
364
|
+
throw new Error('Client not connected')
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
try {
|
|
368
|
+
const result = await publicClient.readContract({
|
|
369
|
+
address: config.teleportContract,
|
|
370
|
+
abi: PRIVATE_TELEPORT_ABI,
|
|
371
|
+
functionName: 'getTeleport',
|
|
372
|
+
args: [teleportId as `0x${string}`],
|
|
373
|
+
})
|
|
374
|
+
|
|
375
|
+
const tuple = result as unknown as [
|
|
376
|
+
`0x${string}`, number, `0x${string}`, `0x${string}`, `0x${string}`, `0x${string}`,
|
|
377
|
+
`0x${string}`, `0x${string}`, `0x${string}`, `0x${string}`, `0x${string}`,
|
|
378
|
+
bigint, bigint, boolean
|
|
379
|
+
]
|
|
380
|
+
const [
|
|
381
|
+
id, state, sourceChain, destChain, sourceAsset, destAsset,
|
|
382
|
+
noteCommitment, encryptedAmount, nullifierHash, sender, recipient,
|
|
383
|
+
deadline, createdBlock, privateSwap
|
|
384
|
+
] = tuple
|
|
385
|
+
|
|
386
|
+
if (id === '0x' + '00'.repeat(32)) {
|
|
387
|
+
return null
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
return {
|
|
391
|
+
teleportId: id,
|
|
392
|
+
state: stateMap[state] ?? TeleportState.INITIATED,
|
|
393
|
+
sourceChain,
|
|
394
|
+
destChain,
|
|
395
|
+
sourceAsset,
|
|
396
|
+
destAsset,
|
|
397
|
+
noteCommitment,
|
|
398
|
+
encryptedAmount,
|
|
399
|
+
nullifierHash: nullifierHash !== '0x' + '00'.repeat(32) ? nullifierHash : undefined,
|
|
400
|
+
sender,
|
|
401
|
+
recipient,
|
|
402
|
+
deadline: Number(deadline),
|
|
403
|
+
createdBlock: Number(createdBlock),
|
|
404
|
+
privateSwap,
|
|
405
|
+
}
|
|
406
|
+
} catch {
|
|
407
|
+
return null
|
|
408
|
+
}
|
|
409
|
+
}, [publicClient, config])
|
|
410
|
+
|
|
411
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
412
|
+
// EXPORT TO DESTINATION
|
|
413
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
414
|
+
|
|
415
|
+
const exportToDestination = useCallback(async (teleportId: string): Promise<void> => {
|
|
416
|
+
if (!walletClient || !publicClient) {
|
|
417
|
+
throw new Error('Wallet not connected')
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
const secret = secrets.get(teleportId)
|
|
421
|
+
if (!secret) {
|
|
422
|
+
throw new Error('Secrets not found for teleport')
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
setIsLoading(true)
|
|
426
|
+
setError(null)
|
|
427
|
+
|
|
428
|
+
try {
|
|
429
|
+
// Get teleport record to get commitment
|
|
430
|
+
const record = await getTeleportRecord(teleportId)
|
|
431
|
+
if (!record) {
|
|
432
|
+
throw new Error('Teleport not found')
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
// Generate range proof
|
|
436
|
+
const rangeProof = await generateRangeProof(
|
|
437
|
+
BigInt(0), // Would get actual amount from decrypted value
|
|
438
|
+
record.noteCommitment,
|
|
439
|
+
secret.blindingFactor
|
|
440
|
+
)
|
|
441
|
+
|
|
442
|
+
// Generate nullifier
|
|
443
|
+
const nullifier = generateNullifier(
|
|
444
|
+
record.noteCommitment,
|
|
445
|
+
secret.spendingKey,
|
|
446
|
+
secret.noteIndex
|
|
447
|
+
)
|
|
448
|
+
|
|
449
|
+
// Get Merkle proof from ZNote
|
|
450
|
+
const merkleProofData = await publicClient.readContract({
|
|
451
|
+
address: config.zNoteContract,
|
|
452
|
+
abi: ZNOTE_ABI,
|
|
453
|
+
functionName: 'getMerkleProof',
|
|
454
|
+
args: [BigInt(secret.noteIndex)],
|
|
455
|
+
}) as `0x${string}`[]
|
|
456
|
+
|
|
457
|
+
const txData = encodeFunctionData({
|
|
458
|
+
abi: PRIVATE_TELEPORT_ABI,
|
|
459
|
+
functionName: 'exportToDestination',
|
|
460
|
+
args: [
|
|
461
|
+
teleportId as `0x${string}`,
|
|
462
|
+
rangeProof.proof,
|
|
463
|
+
nullifier,
|
|
464
|
+
merkleProofData,
|
|
465
|
+
],
|
|
466
|
+
})
|
|
467
|
+
|
|
468
|
+
const hash = await walletClient.sendTransaction({
|
|
469
|
+
to: config.teleportContract,
|
|
470
|
+
data: txData,
|
|
471
|
+
value: BigInt(0),
|
|
472
|
+
})
|
|
473
|
+
|
|
474
|
+
await publicClient.waitForTransactionReceipt({ hash })
|
|
475
|
+
setCurrentState(TeleportState.EXPORTED)
|
|
476
|
+
onStateChange?.(teleportId, TeleportState.EXPORTED)
|
|
477
|
+
} catch (err) {
|
|
478
|
+
const e = err instanceof Error ? err : new Error(String(err))
|
|
479
|
+
setError(e)
|
|
480
|
+
throw e
|
|
481
|
+
} finally {
|
|
482
|
+
setIsLoading(false)
|
|
483
|
+
}
|
|
484
|
+
}, [walletClient, publicClient, config, secrets, onStateChange])
|
|
485
|
+
|
|
486
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
487
|
+
// COMPLETE TELEPORT
|
|
488
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
489
|
+
|
|
490
|
+
const completeTeleport = useCallback(async (
|
|
491
|
+
teleportId: string,
|
|
492
|
+
warpConfirmation: `0x${string}`
|
|
493
|
+
): Promise<void> => {
|
|
494
|
+
if (!walletClient || !publicClient) {
|
|
495
|
+
throw new Error('Wallet not connected')
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
setIsLoading(true)
|
|
499
|
+
setError(null)
|
|
500
|
+
|
|
501
|
+
try {
|
|
502
|
+
const txData = encodeFunctionData({
|
|
503
|
+
abi: PRIVATE_TELEPORT_ABI,
|
|
504
|
+
functionName: 'completeTeleport',
|
|
505
|
+
args: [teleportId as `0x${string}`, warpConfirmation],
|
|
506
|
+
})
|
|
507
|
+
|
|
508
|
+
const hash = await walletClient.sendTransaction({
|
|
509
|
+
to: config.teleportContract,
|
|
510
|
+
data: txData,
|
|
511
|
+
value: BigInt(0),
|
|
512
|
+
})
|
|
513
|
+
|
|
514
|
+
await publicClient.waitForTransactionReceipt({ hash })
|
|
515
|
+
setCurrentState(TeleportState.COMPLETED)
|
|
516
|
+
onStateChange?.(teleportId, TeleportState.COMPLETED)
|
|
517
|
+
|
|
518
|
+
// Clean up secrets
|
|
519
|
+
setSecrets(prev => {
|
|
520
|
+
const next = new Map(prev)
|
|
521
|
+
next.delete(teleportId)
|
|
522
|
+
return next
|
|
523
|
+
})
|
|
524
|
+
} catch (err) {
|
|
525
|
+
const e = err instanceof Error ? err : new Error(String(err))
|
|
526
|
+
setError(e)
|
|
527
|
+
throw e
|
|
528
|
+
} finally {
|
|
529
|
+
setIsLoading(false)
|
|
530
|
+
}
|
|
531
|
+
}, [walletClient, publicClient, config, onStateChange])
|
|
532
|
+
|
|
533
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
534
|
+
// UNSHIELD TO X-CHAIN
|
|
535
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
536
|
+
|
|
537
|
+
const unshieldToXChain = useCallback(async (request: UnshieldToXChainRequest): Promise<string> => {
|
|
538
|
+
if (!walletClient || !publicClient) {
|
|
539
|
+
throw new Error('Wallet not connected')
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
const secret = secrets.get(request.teleportId)
|
|
543
|
+
if (!secret) {
|
|
544
|
+
throw new Error('Secrets not found for teleport')
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
setIsLoading(true)
|
|
548
|
+
setError(null)
|
|
549
|
+
|
|
550
|
+
try {
|
|
551
|
+
const record = await getTeleportRecord(request.teleportId)
|
|
552
|
+
if (!record) {
|
|
553
|
+
throw new Error('Teleport not found')
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
// Generate range proof (proves amount matches commitment)
|
|
557
|
+
const rangeProof = await generateRangeProof(
|
|
558
|
+
request.amount,
|
|
559
|
+
record.noteCommitment,
|
|
560
|
+
secret.blindingFactor
|
|
561
|
+
)
|
|
562
|
+
|
|
563
|
+
// Generate nullifier
|
|
564
|
+
const nullifier = generateNullifier(
|
|
565
|
+
record.noteCommitment,
|
|
566
|
+
secret.spendingKey,
|
|
567
|
+
secret.noteIndex
|
|
568
|
+
)
|
|
569
|
+
|
|
570
|
+
// Get Merkle proof
|
|
571
|
+
const merkleProofData = await publicClient.readContract({
|
|
572
|
+
address: config.zNoteContract,
|
|
573
|
+
abi: ZNOTE_ABI,
|
|
574
|
+
functionName: 'getMerkleProof',
|
|
575
|
+
args: [BigInt(secret.noteIndex)],
|
|
576
|
+
}) as `0x${string}`[]
|
|
577
|
+
|
|
578
|
+
// Encode destination address
|
|
579
|
+
const destinationBytes = toHex(new TextEncoder().encode(request.destinationAddress))
|
|
580
|
+
|
|
581
|
+
const txData = encodeFunctionData({
|
|
582
|
+
abi: PRIVATE_TELEPORT_ABI,
|
|
583
|
+
functionName: 'unshieldToXChain',
|
|
584
|
+
args: [
|
|
585
|
+
request.teleportId as `0x${string}`,
|
|
586
|
+
destinationBytes as `0x${string}`,
|
|
587
|
+
BigInt(request.amount),
|
|
588
|
+
nullifier,
|
|
589
|
+
merkleProofData,
|
|
590
|
+
rangeProof.proof,
|
|
591
|
+
],
|
|
592
|
+
})
|
|
593
|
+
|
|
594
|
+
const hash = await walletClient.sendTransaction({
|
|
595
|
+
to: config.teleportContract,
|
|
596
|
+
data: txData,
|
|
597
|
+
value: BigInt(0),
|
|
598
|
+
})
|
|
599
|
+
|
|
600
|
+
const receipt = await publicClient.waitForTransactionReceipt({ hash })
|
|
601
|
+
|
|
602
|
+
// Extract exportTxId from logs
|
|
603
|
+
const exportTxId = receipt.logs[0]?.topics[1] ?? hash
|
|
604
|
+
|
|
605
|
+
setCurrentState(TeleportState.COMPLETED)
|
|
606
|
+
onStateChange?.(request.teleportId, TeleportState.COMPLETED)
|
|
607
|
+
|
|
608
|
+
// Clean up secrets
|
|
609
|
+
setSecrets(prev => {
|
|
610
|
+
const next = new Map(prev)
|
|
611
|
+
next.delete(request.teleportId)
|
|
612
|
+
return next
|
|
613
|
+
})
|
|
614
|
+
|
|
615
|
+
return exportTxId
|
|
616
|
+
} catch (err) {
|
|
617
|
+
const e = err instanceof Error ? err : new Error(String(err))
|
|
618
|
+
setError(e)
|
|
619
|
+
throw e
|
|
620
|
+
} finally {
|
|
621
|
+
setIsLoading(false)
|
|
622
|
+
}
|
|
623
|
+
}, [walletClient, publicClient, config, secrets, onStateChange, getTeleportRecord])
|
|
624
|
+
|
|
625
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
626
|
+
// PRIVATE TRANSFER TO RECIPIENT
|
|
627
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
628
|
+
|
|
629
|
+
const privateTransfer = useCallback(async (request: PrivateTransferRequest): Promise<number> => {
|
|
630
|
+
if (!walletClient || !publicClient) {
|
|
631
|
+
throw new Error('Wallet not connected')
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
const secret = secrets.get(request.teleportId)
|
|
635
|
+
if (!secret) {
|
|
636
|
+
throw new Error('Secrets not found for teleport')
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
setIsLoading(true)
|
|
640
|
+
setError(null)
|
|
641
|
+
|
|
642
|
+
try {
|
|
643
|
+
const record = await getTeleportRecord(request.teleportId)
|
|
644
|
+
if (!record) {
|
|
645
|
+
throw new Error('Teleport not found')
|
|
646
|
+
}
|
|
647
|
+
|
|
648
|
+
// Generate new commitment for recipient
|
|
649
|
+
const recipientCommitment = await generateCommitment(request.amount)
|
|
650
|
+
|
|
651
|
+
// Encrypt note to recipient's viewing key
|
|
652
|
+
const encryptedNote = keccak256(
|
|
653
|
+
`${request.recipientViewKey}${recipientCommitment.commitment.slice(2)}` as `0x${string}`
|
|
654
|
+
)
|
|
655
|
+
|
|
656
|
+
// Generate nullifier
|
|
657
|
+
const nullifier = generateNullifier(
|
|
658
|
+
record.noteCommitment,
|
|
659
|
+
secret.spendingKey,
|
|
660
|
+
secret.noteIndex
|
|
661
|
+
)
|
|
662
|
+
|
|
663
|
+
// Get Merkle proof
|
|
664
|
+
const merkleProofData = await publicClient.readContract({
|
|
665
|
+
address: config.zNoteContract,
|
|
666
|
+
abi: ZNOTE_ABI,
|
|
667
|
+
functionName: 'getMerkleProof',
|
|
668
|
+
args: [BigInt(secret.noteIndex)],
|
|
669
|
+
}) as `0x${string}`[]
|
|
670
|
+
|
|
671
|
+
// Generate transfer proof (proves amount conservation)
|
|
672
|
+
const transferProof = keccak256(
|
|
673
|
+
`${record.noteCommitment}${recipientCommitment.commitment.slice(2)}` as `0x${string}`
|
|
674
|
+
)
|
|
675
|
+
|
|
676
|
+
const txData = encodeFunctionData({
|
|
677
|
+
abi: PRIVATE_TELEPORT_ABI,
|
|
678
|
+
functionName: 'privateTransferToRecipient',
|
|
679
|
+
args: [
|
|
680
|
+
request.teleportId as `0x${string}`,
|
|
681
|
+
recipientCommitment.commitment,
|
|
682
|
+
encryptedNote,
|
|
683
|
+
nullifier,
|
|
684
|
+
merkleProofData,
|
|
685
|
+
transferProof,
|
|
686
|
+
],
|
|
687
|
+
})
|
|
688
|
+
|
|
689
|
+
const hash = await walletClient.sendTransaction({
|
|
690
|
+
to: config.teleportContract,
|
|
691
|
+
data: txData,
|
|
692
|
+
value: BigInt(0),
|
|
693
|
+
})
|
|
694
|
+
|
|
695
|
+
const receipt = await publicClient.waitForTransactionReceipt({ hash })
|
|
696
|
+
|
|
697
|
+
// Extract note index from logs (simplified)
|
|
698
|
+
const newNoteIndex = Number(receipt.logs[0]?.data?.slice(0, 66) ?? '0')
|
|
699
|
+
|
|
700
|
+
setCurrentState(TeleportState.COMPLETED)
|
|
701
|
+
onStateChange?.(request.teleportId, TeleportState.COMPLETED)
|
|
702
|
+
|
|
703
|
+
// Clean up secrets for this teleport
|
|
704
|
+
setSecrets(prev => {
|
|
705
|
+
const next = new Map(prev)
|
|
706
|
+
next.delete(request.teleportId)
|
|
707
|
+
return next
|
|
708
|
+
})
|
|
709
|
+
|
|
710
|
+
return newNoteIndex
|
|
711
|
+
} catch (err) {
|
|
712
|
+
const e = err instanceof Error ? err : new Error(String(err))
|
|
713
|
+
setError(e)
|
|
714
|
+
throw e
|
|
715
|
+
} finally {
|
|
716
|
+
setIsLoading(false)
|
|
717
|
+
}
|
|
718
|
+
}, [walletClient, publicClient, config, secrets, onStateChange, getTeleportRecord])
|
|
719
|
+
|
|
720
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
721
|
+
// SPLIT AND TRANSFER
|
|
722
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
723
|
+
|
|
724
|
+
const splitAndTransfer = useCallback(async (
|
|
725
|
+
teleportId: string,
|
|
726
|
+
outputs: Array<{recipient: `0x${string}`, amount: bigint}>
|
|
727
|
+
): Promise<number[]> => {
|
|
728
|
+
if (!walletClient || !publicClient) {
|
|
729
|
+
throw new Error('Wallet not connected')
|
|
730
|
+
}
|
|
731
|
+
|
|
732
|
+
const secret = secrets.get(teleportId)
|
|
733
|
+
if (!secret) {
|
|
734
|
+
throw new Error('Secrets not found for teleport')
|
|
735
|
+
}
|
|
736
|
+
|
|
737
|
+
if (outputs.length === 0 || outputs.length > 16) {
|
|
738
|
+
throw new Error('Invalid number of outputs (1-16)')
|
|
739
|
+
}
|
|
740
|
+
|
|
741
|
+
setIsLoading(true)
|
|
742
|
+
setError(null)
|
|
743
|
+
|
|
744
|
+
try {
|
|
745
|
+
const record = await getTeleportRecord(teleportId)
|
|
746
|
+
if (!record) {
|
|
747
|
+
throw new Error('Teleport not found')
|
|
748
|
+
}
|
|
749
|
+
|
|
750
|
+
// Generate commitments for each output
|
|
751
|
+
const outputNotes = await Promise.all(
|
|
752
|
+
outputs.map(async (output) => {
|
|
753
|
+
const commitment = await generateCommitment(output.amount)
|
|
754
|
+
const encryptedNote = keccak256(
|
|
755
|
+
`${output.recipient}${commitment.commitment.slice(2)}` as `0x${string}`
|
|
756
|
+
)
|
|
757
|
+
|
|
758
|
+
return {
|
|
759
|
+
commitment: commitment.commitment,
|
|
760
|
+
encryptedNote,
|
|
761
|
+
encryptedMemo: '0x' as `0x${string}`,
|
|
762
|
+
}
|
|
763
|
+
})
|
|
764
|
+
)
|
|
765
|
+
|
|
766
|
+
// Generate nullifier
|
|
767
|
+
const nullifier = generateNullifier(
|
|
768
|
+
record.noteCommitment,
|
|
769
|
+
secret.spendingKey,
|
|
770
|
+
secret.noteIndex
|
|
771
|
+
)
|
|
772
|
+
|
|
773
|
+
// Get Merkle proof
|
|
774
|
+
const merkleProofData = await publicClient.readContract({
|
|
775
|
+
address: config.zNoteContract,
|
|
776
|
+
abi: ZNOTE_ABI,
|
|
777
|
+
functionName: 'getMerkleProof',
|
|
778
|
+
args: [BigInt(secret.noteIndex)],
|
|
779
|
+
}) as `0x${string}`[]
|
|
780
|
+
|
|
781
|
+
// Generate split proof (proves sum of outputs = input)
|
|
782
|
+
const splitProof = keccak256(
|
|
783
|
+
`${record.noteCommitment}${outputNotes.map(n => n.commitment.slice(2)).join('')}` as `0x${string}`
|
|
784
|
+
)
|
|
785
|
+
|
|
786
|
+
const txData = encodeFunctionData({
|
|
787
|
+
abi: PRIVATE_TELEPORT_ABI,
|
|
788
|
+
functionName: 'splitAndTransfer',
|
|
789
|
+
args: [
|
|
790
|
+
teleportId as `0x${string}`,
|
|
791
|
+
outputNotes,
|
|
792
|
+
nullifier,
|
|
793
|
+
merkleProofData,
|
|
794
|
+
splitProof,
|
|
795
|
+
],
|
|
796
|
+
})
|
|
797
|
+
|
|
798
|
+
const hash = await walletClient.sendTransaction({
|
|
799
|
+
to: config.teleportContract,
|
|
800
|
+
data: txData,
|
|
801
|
+
value: BigInt(0),
|
|
802
|
+
})
|
|
803
|
+
|
|
804
|
+
const receipt = await publicClient.waitForTransactionReceipt({ hash })
|
|
805
|
+
|
|
806
|
+
// Extract note indices from logs (simplified)
|
|
807
|
+
const noteIndices = outputs.map((_, i) => i)
|
|
808
|
+
|
|
809
|
+
setCurrentState(TeleportState.COMPLETED)
|
|
810
|
+
onStateChange?.(teleportId, TeleportState.COMPLETED)
|
|
811
|
+
|
|
812
|
+
// Clean up secrets for this teleport
|
|
813
|
+
setSecrets(prev => {
|
|
814
|
+
const next = new Map(prev)
|
|
815
|
+
next.delete(teleportId)
|
|
816
|
+
return next
|
|
817
|
+
})
|
|
818
|
+
|
|
819
|
+
return noteIndices
|
|
820
|
+
} catch (err) {
|
|
821
|
+
const e = err instanceof Error ? err : new Error(String(err))
|
|
822
|
+
setError(e)
|
|
823
|
+
throw e
|
|
824
|
+
} finally {
|
|
825
|
+
setIsLoading(false)
|
|
826
|
+
}
|
|
827
|
+
}, [walletClient, publicClient, config, secrets, onStateChange, getTeleportRecord])
|
|
828
|
+
|
|
829
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
830
|
+
// CANCEL TELEPORT
|
|
831
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
832
|
+
|
|
833
|
+
const cancelTeleport = useCallback(async (teleportId: string): Promise<void> => {
|
|
834
|
+
if (!walletClient || !publicClient) {
|
|
835
|
+
throw new Error('Wallet not connected')
|
|
836
|
+
}
|
|
837
|
+
|
|
838
|
+
setIsLoading(true)
|
|
839
|
+
setError(null)
|
|
840
|
+
|
|
841
|
+
try {
|
|
842
|
+
const txData = encodeFunctionData({
|
|
843
|
+
abi: PRIVATE_TELEPORT_ABI,
|
|
844
|
+
functionName: 'cancelTeleport',
|
|
845
|
+
args: [teleportId as `0x${string}`],
|
|
846
|
+
})
|
|
847
|
+
|
|
848
|
+
const hash = await walletClient.sendTransaction({
|
|
849
|
+
to: config.teleportContract,
|
|
850
|
+
data: txData,
|
|
851
|
+
value: BigInt(0),
|
|
852
|
+
})
|
|
853
|
+
|
|
854
|
+
await publicClient.waitForTransactionReceipt({ hash })
|
|
855
|
+
setCurrentState(TeleportState.CANCELLED)
|
|
856
|
+
onStateChange?.(teleportId, TeleportState.CANCELLED)
|
|
857
|
+
|
|
858
|
+
// Clean up secrets
|
|
859
|
+
setSecrets(prev => {
|
|
860
|
+
const next = new Map(prev)
|
|
861
|
+
next.delete(teleportId)
|
|
862
|
+
return next
|
|
863
|
+
})
|
|
864
|
+
} catch (err) {
|
|
865
|
+
const e = err instanceof Error ? err : new Error(String(err))
|
|
866
|
+
setError(e)
|
|
867
|
+
throw e
|
|
868
|
+
} finally {
|
|
869
|
+
setIsLoading(false)
|
|
870
|
+
}
|
|
871
|
+
}, [walletClient, publicClient, config, onStateChange])
|
|
872
|
+
|
|
873
|
+
// Wrapper for external API
|
|
874
|
+
const getTeleport = useCallback(async (teleportId: string): Promise<TeleportRecord | null> => {
|
|
875
|
+
return getTeleportRecord(teleportId)
|
|
876
|
+
}, [getTeleportRecord])
|
|
877
|
+
|
|
878
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
879
|
+
// CHECK COMPLETION
|
|
880
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
881
|
+
|
|
882
|
+
const isComplete = useCallback(async (teleportId: string): Promise<boolean> => {
|
|
883
|
+
if (!publicClient) {
|
|
884
|
+
throw new Error('Client not connected')
|
|
885
|
+
}
|
|
886
|
+
|
|
887
|
+
try {
|
|
888
|
+
const result = await publicClient.readContract({
|
|
889
|
+
address: config.teleportContract,
|
|
890
|
+
abi: PRIVATE_TELEPORT_ABI,
|
|
891
|
+
functionName: 'isComplete',
|
|
892
|
+
args: [teleportId as `0x${string}`],
|
|
893
|
+
})
|
|
894
|
+
return result as boolean
|
|
895
|
+
} catch {
|
|
896
|
+
return false
|
|
897
|
+
}
|
|
898
|
+
}, [publicClient, config])
|
|
899
|
+
|
|
900
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
901
|
+
// POLLING
|
|
902
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
903
|
+
|
|
904
|
+
useEffect(() => {
|
|
905
|
+
if (!currentTeleportId || !publicClient || currentState === TeleportState.COMPLETED || currentState === TeleportState.CANCELLED) {
|
|
906
|
+
return
|
|
907
|
+
}
|
|
908
|
+
|
|
909
|
+
const poll = async () => {
|
|
910
|
+
const record = await getTeleportRecord(currentTeleportId)
|
|
911
|
+
if (record && record.state !== currentState) {
|
|
912
|
+
setCurrentState(record.state)
|
|
913
|
+
onStateChange?.(currentTeleportId, record.state)
|
|
914
|
+
}
|
|
915
|
+
}
|
|
916
|
+
|
|
917
|
+
const interval = setInterval(poll, pollInterval)
|
|
918
|
+
return () => clearInterval(interval)
|
|
919
|
+
}, [currentTeleportId, currentState, publicClient, pollInterval, onStateChange, getTeleportRecord])
|
|
920
|
+
|
|
921
|
+
return {
|
|
922
|
+
// Initiate
|
|
923
|
+
teleport,
|
|
924
|
+
executeSwap,
|
|
925
|
+
// Export (unshield)
|
|
926
|
+
exportToDestination,
|
|
927
|
+
unshieldToXChain,
|
|
928
|
+
// Private transfers (stay shielded)
|
|
929
|
+
privateTransfer,
|
|
930
|
+
splitAndTransfer,
|
|
931
|
+
// Complete/cancel
|
|
932
|
+
completeTeleport,
|
|
933
|
+
cancelTeleport,
|
|
934
|
+
// Queries
|
|
935
|
+
getTeleport,
|
|
936
|
+
isComplete,
|
|
937
|
+
// State
|
|
938
|
+
currentTeleportId,
|
|
939
|
+
currentState,
|
|
940
|
+
isLoading,
|
|
941
|
+
error,
|
|
942
|
+
}
|
|
943
|
+
}
|
|
944
|
+
|
|
945
|
+
// Re-export types for convenience
|
|
946
|
+
export type {
|
|
947
|
+
PrivateTeleportRequest,
|
|
948
|
+
TeleportRecord,
|
|
949
|
+
TeleportState,
|
|
950
|
+
PrivateTeleportConfig,
|
|
951
|
+
} from './private-teleport-types'
|