@silentswap/react 0.0.41

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.
Files changed (86) hide show
  1. package/README.md +48 -0
  2. package/dist/contexts/AssetsContext.d.ts +24 -0
  3. package/dist/contexts/AssetsContext.js +83 -0
  4. package/dist/contexts/BalancesContext.d.ts +28 -0
  5. package/dist/contexts/BalancesContext.js +533 -0
  6. package/dist/contexts/OrdersContext.d.ts +53 -0
  7. package/dist/contexts/OrdersContext.js +240 -0
  8. package/dist/contexts/PricesContext.d.ts +12 -0
  9. package/dist/contexts/PricesContext.js +109 -0
  10. package/dist/contexts/SilentSwapContext.d.ts +58 -0
  11. package/dist/contexts/SilentSwapContext.js +205 -0
  12. package/dist/hooks/silent/orderTrackingWebSocketManager.d.ts +48 -0
  13. package/dist/hooks/silent/orderTrackingWebSocketManager.js +284 -0
  14. package/dist/hooks/silent/solana-transaction.d.ts +60 -0
  15. package/dist/hooks/silent/solana-transaction.js +236 -0
  16. package/dist/hooks/silent/useAuth.d.ts +90 -0
  17. package/dist/hooks/silent/useAuth.js +269 -0
  18. package/dist/hooks/silent/useBridgeExecution.d.ts +39 -0
  19. package/dist/hooks/silent/useBridgeExecution.js +877 -0
  20. package/dist/hooks/silent/useOrderSigning.d.ts +34 -0
  21. package/dist/hooks/silent/useOrderSigning.js +133 -0
  22. package/dist/hooks/silent/useOrderTracking.d.ts +174 -0
  23. package/dist/hooks/silent/useOrderTracking.js +524 -0
  24. package/dist/hooks/silent/useQuoteCalculation.d.ts +50 -0
  25. package/dist/hooks/silent/useQuoteCalculation.js +331 -0
  26. package/dist/hooks/silent/useQuoteFetching.d.ts +18 -0
  27. package/dist/hooks/silent/useQuoteFetching.js +54 -0
  28. package/dist/hooks/silent/useRefund.d.ts +26 -0
  29. package/dist/hooks/silent/useRefund.js +134 -0
  30. package/dist/hooks/silent/useSilentClient.d.ts +16 -0
  31. package/dist/hooks/silent/useSilentClient.js +32 -0
  32. package/dist/hooks/silent/useSilentOrders.d.ts +174 -0
  33. package/dist/hooks/silent/useSilentOrders.js +73 -0
  34. package/dist/hooks/silent/useSilentQuote.d.ts +88 -0
  35. package/dist/hooks/silent/useSilentQuote.js +381 -0
  36. package/dist/hooks/silent/useWallet.d.ts +76 -0
  37. package/dist/hooks/silent/useWallet.js +203 -0
  38. package/dist/hooks/useAssetPrice.d.ts +8 -0
  39. package/dist/hooks/useAssetPrice.js +47 -0
  40. package/dist/hooks/useContacts.d.ts +52 -0
  41. package/dist/hooks/useContacts.js +259 -0
  42. package/dist/hooks/useEgressEstimates.d.ts +32 -0
  43. package/dist/hooks/useEgressEstimates.js +230 -0
  44. package/dist/hooks/useHiddenSwapFees.d.ts +22 -0
  45. package/dist/hooks/useHiddenSwapFees.js +81 -0
  46. package/dist/hooks/useOrderEstimates.d.ts +37 -0
  47. package/dist/hooks/useOrderEstimates.js +393 -0
  48. package/dist/hooks/useOutputAssetInfo.d.ts +12 -0
  49. package/dist/hooks/useOutputAssetInfo.js +38 -0
  50. package/dist/hooks/usePrices.d.ts +60 -0
  51. package/dist/hooks/usePrices.js +188 -0
  52. package/dist/hooks/useQuote.d.ts +73 -0
  53. package/dist/hooks/useQuote.js +507 -0
  54. package/dist/hooks/useResetSwapForm.d.ts +16 -0
  55. package/dist/hooks/useResetSwapForm.js +68 -0
  56. package/dist/hooks/useSlippageUsd.d.ts +11 -0
  57. package/dist/hooks/useSlippageUsd.js +19 -0
  58. package/dist/hooks/useSolanaAdapter.d.ts +15 -0
  59. package/dist/hooks/useSolanaAdapter.js +55 -0
  60. package/dist/hooks/useStatus.d.ts +25 -0
  61. package/dist/hooks/useStatus.js +60 -0
  62. package/dist/hooks/useSwap.d.ts +67 -0
  63. package/dist/hooks/useSwap.js +285 -0
  64. package/dist/hooks/useTransaction.d.ts +119 -0
  65. package/dist/hooks/useTransaction.js +353 -0
  66. package/dist/hooks/useTransactionAddress.d.ts +11 -0
  67. package/dist/hooks/useTransactionAddress.js +26 -0
  68. package/dist/hooks/useUsdValue.d.ts +7 -0
  69. package/dist/hooks/useUsdValue.js +19 -0
  70. package/dist/index.d.ts +54 -0
  71. package/dist/index.js +41 -0
  72. package/dist/stories/SilentSwapOverview.stories.d.ts +10 -0
  73. package/dist/stories/SilentSwapOverview.stories.js +364 -0
  74. package/dist/stories/useAuth.stories.d.ts +6 -0
  75. package/dist/stories/useAuth.stories.js +55 -0
  76. package/dist/stories/useSilentClient.stories.d.ts +9 -0
  77. package/dist/stories/useSilentClient.stories.js +39 -0
  78. package/dist/stories/useSilentOrders.stories.d.ts +1 -0
  79. package/dist/stories/useSilentOrders.stories.js +1 -0
  80. package/dist/stories/useSilentQuote.stories.d.ts +6 -0
  81. package/dist/stories/useSilentQuote.stories.js +267 -0
  82. package/dist/stories/useTransaction.stories.d.ts +6 -0
  83. package/dist/stories/useTransaction.stories.js +121 -0
  84. package/dist/utils/formatters.d.ts +33 -0
  85. package/dist/utils/formatters.js +82 -0
  86. package/package.json +67 -0
@@ -0,0 +1,284 @@
1
+ /**
2
+ * Shared WebSocket connection manager for order tracking
3
+ * Maintains one connection per client/baseUrl and handles multiple order subscriptions
4
+ */
5
+ class OrderTrackingWebSocketManager {
6
+ connections = new Map();
7
+ /**
8
+ * Get connection key from client
9
+ */
10
+ getConnectionKey(client) {
11
+ if (client) {
12
+ return client.baseUrl;
13
+ }
14
+ if (typeof window !== 'undefined') {
15
+ return window.location.origin;
16
+ }
17
+ return 'default';
18
+ }
19
+ /**
20
+ * Get WebSocket URL from client
21
+ */
22
+ getWebSocketUrl(client) {
23
+ let wsUrl;
24
+ if (typeof window !== 'undefined' && window.__WEBSOCKET_URL__) {
25
+ wsUrl = window.__WEBSOCKET_URL__;
26
+ }
27
+ else if (client) {
28
+ const baseUrl = client.baseUrl;
29
+ try {
30
+ const url = new URL(baseUrl);
31
+ const protocol = url.protocol === 'https:' ? 'wss:' : 'ws:';
32
+ wsUrl = `${protocol}//${url.host}/websocket`;
33
+ }
34
+ catch {
35
+ wsUrl = baseUrl.replace(/^https?:\/\//, 'wss://').replace(/^http:\/\//, 'ws://') + '/websocket';
36
+ }
37
+ }
38
+ else if (typeof window !== 'undefined') {
39
+ wsUrl = `${window.location.protocol === 'https:' ? 'wss' : 'ws'}://${window.location.host}/websocket`;
40
+ }
41
+ else {
42
+ wsUrl = 'ws://localhost/websocket';
43
+ }
44
+ // Ensure URL doesn't have any path or query params
45
+ try {
46
+ const url = new URL(wsUrl);
47
+ const protocol = url.protocol === 'https:' || url.protocol === 'wss:' ? 'wss:' : 'ws:';
48
+ wsUrl = `${protocol}//${url.host}/websocket`;
49
+ }
50
+ catch {
51
+ wsUrl = wsUrl.split('?')[0].split('#')[0].replace(/\/+$/, '') + '/websocket';
52
+ }
53
+ return wsUrl;
54
+ }
55
+ /**
56
+ * Get or create connection state
57
+ */
58
+ getConnectionState(client) {
59
+ const key = this.getConnectionKey(client);
60
+ let state = this.connections.get(key);
61
+ if (!state) {
62
+ state = {
63
+ ws: null,
64
+ url: this.getWebSocketUrl(client),
65
+ isConnected: false,
66
+ isConnecting: false,
67
+ subscriptions: new Map(),
68
+ requestHandlers: new Map(),
69
+ requestCounter: 0,
70
+ reconnectTimeout: null,
71
+ reconnectAttempts: 0,
72
+ reconnectDelay: 1000,
73
+ lastReceived: 0,
74
+ isReconnecting: false,
75
+ maxReconnectAttempts: 5,
76
+ };
77
+ this.connections.set(key, state);
78
+ }
79
+ return state;
80
+ }
81
+ /**
82
+ * Connect WebSocket if not already connected
83
+ */
84
+ connectWebSocket(state) {
85
+ if (state.ws && state.ws.readyState === WebSocket.OPEN) {
86
+ return; // Already connected
87
+ }
88
+ if (state.isConnecting || state.isReconnecting) {
89
+ return; // Already connecting
90
+ }
91
+ // Close existing connection if any
92
+ if (state.ws) {
93
+ state.ws.close();
94
+ state.ws = null;
95
+ }
96
+ state.isConnecting = true;
97
+ try {
98
+ const ws = new WebSocket(state.url);
99
+ state.ws = ws;
100
+ ws.onopen = () => {
101
+ state.lastReceived = Date.now();
102
+ state.isConnected = true;
103
+ state.isConnecting = false;
104
+ state.isReconnecting = false;
105
+ state.reconnectAttempts = 0;
106
+ state.reconnectDelay = 1000;
107
+ // Subscribe all existing subscriptions
108
+ state.subscriptions.forEach((handler) => {
109
+ this.subscribeOrder(state, handler.orderId, handler.auth, handler);
110
+ });
111
+ };
112
+ ws.onmessage = (event) => {
113
+ state.lastReceived = Date.now();
114
+ try {
115
+ const response = JSON.parse(event.data);
116
+ if (response.jsonrpc !== '2.0' || typeof response.id !== 'number') {
117
+ return;
118
+ }
119
+ const handler = state.requestHandlers.get(response.id);
120
+ if (handler) {
121
+ handler(response.error, response.result);
122
+ }
123
+ }
124
+ catch (parseError) {
125
+ console.warn('Failed to parse WebSocket message:', parseError);
126
+ }
127
+ };
128
+ ws.onerror = () => {
129
+ state.isConnecting = false;
130
+ // Error will be handled by onclose
131
+ };
132
+ ws.onclose = () => {
133
+ state.isConnected = false;
134
+ state.isConnecting = false;
135
+ state.requestHandlers.clear();
136
+ // Reconnect if we have active subscriptions
137
+ if (state.subscriptions.size > 0 && !state.isReconnecting) {
138
+ this.scheduleReconnect(state);
139
+ }
140
+ };
141
+ }
142
+ catch (err) {
143
+ state.isConnecting = false;
144
+ console.error('Failed to create WebSocket connection:', err);
145
+ }
146
+ }
147
+ /**
148
+ * Schedule reconnection with exponential backoff
149
+ */
150
+ scheduleReconnect(state) {
151
+ if (state.reconnectAttempts >= state.maxReconnectAttempts) {
152
+ // Notify all subscriptions of error
153
+ const error = new Error(`WebSocket connection failed after ${state.maxReconnectAttempts} attempts`);
154
+ state.subscriptions.forEach((handler) => {
155
+ handler.onError(error);
156
+ });
157
+ // Reset after delay to allow retry later
158
+ state.reconnectTimeout = setTimeout(() => {
159
+ state.reconnectAttempts = 0;
160
+ state.reconnectDelay = 1000;
161
+ }, 30000);
162
+ return;
163
+ }
164
+ state.isReconnecting = true;
165
+ state.reconnectAttempts += 1;
166
+ const currentDelay = state.reconnectDelay;
167
+ state.reconnectDelay = Math.min(currentDelay * 2, 30000);
168
+ const timeSinceLastReceived = Date.now() - state.lastReceived;
169
+ const extraDelay = timeSinceLastReceived > 90000 ? 5000 : 0;
170
+ const totalDelay = currentDelay + extraDelay;
171
+ state.reconnectTimeout = setTimeout(() => {
172
+ if (state.subscriptions.size > 0) {
173
+ state.isReconnecting = false;
174
+ this.connectWebSocket(state);
175
+ }
176
+ else {
177
+ state.isReconnecting = false;
178
+ state.reconnectAttempts = 0;
179
+ state.reconnectDelay = 1000;
180
+ }
181
+ }, totalDelay);
182
+ }
183
+ /**
184
+ * Subscribe to an order
185
+ */
186
+ subscribeOrder(state, orderId, auth, handler) {
187
+ if (!state.ws || state.ws.readyState !== WebSocket.OPEN) {
188
+ return;
189
+ }
190
+ const requestId = state.requestCounter++;
191
+ const request = {
192
+ jsonrpc: '2.0',
193
+ id: requestId,
194
+ method: 'connect',
195
+ params: {
196
+ auth: {
197
+ orderId,
198
+ viewingAuth: auth,
199
+ },
200
+ },
201
+ };
202
+ // Set up handler for initial response and subsequent updates
203
+ // Note: The server sends multiple responses with the same request ID for status updates
204
+ state.requestHandlers.set(requestId, (error, result) => {
205
+ if (error) {
206
+ const err = new Error(error.message || 'Connection error');
207
+ handler.onError(err);
208
+ return;
209
+ }
210
+ // Check if this is an initial OrderStatus or a StatusUpdate
211
+ // OrderStatus has a 'deposit' field, StatusUpdate has a 'type' field
212
+ const data = result;
213
+ if ('type' in data) {
214
+ // This is a StatusUpdate
215
+ handler.onStatusChange(data);
216
+ }
217
+ else {
218
+ // This is an initial OrderStatus
219
+ handler.onStatusUpdate(data);
220
+ // Replace handler for subsequent status updates (reuse same request ID)
221
+ state.requestHandlers.set(requestId, (statusError, statusResult) => {
222
+ if (statusError) {
223
+ const err = new Error(statusError.message || 'Status error');
224
+ handler.onError(err);
225
+ return;
226
+ }
227
+ // Handle status update
228
+ const update = statusResult;
229
+ handler.onStatusChange(update);
230
+ });
231
+ }
232
+ });
233
+ state.ws.send(JSON.stringify(request));
234
+ }
235
+ /**
236
+ * Subscribe to order tracking
237
+ */
238
+ subscribe(client, orderId, auth, handlers) {
239
+ const state = this.getConnectionState(client);
240
+ const subscriptionKey = `${orderId}:${auth}`;
241
+ // Create subscription handler
242
+ const subscriptionHandler = {
243
+ orderId,
244
+ auth,
245
+ ...handlers,
246
+ };
247
+ // Add subscription
248
+ state.subscriptions.set(subscriptionKey, subscriptionHandler);
249
+ // Connect if not connected
250
+ if (!state.isConnected && !state.isConnecting) {
251
+ this.connectWebSocket(state);
252
+ }
253
+ else if (state.isConnected) {
254
+ // Already connected, subscribe immediately
255
+ this.subscribeOrder(state, orderId, auth, subscriptionHandler);
256
+ }
257
+ // Return unsubscribe function
258
+ return () => {
259
+ state.subscriptions.delete(subscriptionKey);
260
+ // If no more subscriptions, close connection
261
+ if (state.subscriptions.size === 0) {
262
+ if (state.reconnectTimeout) {
263
+ clearTimeout(state.reconnectTimeout);
264
+ state.reconnectTimeout = null;
265
+ }
266
+ if (state.ws) {
267
+ state.ws.close();
268
+ state.ws = null;
269
+ }
270
+ state.isConnected = false;
271
+ state.isReconnecting = false;
272
+ }
273
+ };
274
+ }
275
+ /**
276
+ * Get connection status
277
+ */
278
+ isConnected(client) {
279
+ const state = this.getConnectionState(client);
280
+ return state.isConnected;
281
+ }
282
+ }
283
+ // Singleton instance
284
+ export const orderTrackingWebSocketManager = new OrderTrackingWebSocketManager();
@@ -0,0 +1,60 @@
1
+ import type { BridgeTransaction } from '@silentswap/sdk';
2
+ import type { SolanaTransactionExecutor } from '@silentswap/sdk';
3
+ import { type Signature, type Blockhash } from '@solana/kit';
4
+ import { createSolanaRpc } from '@solana/rpc';
5
+ /**
6
+ * Solana wallet connector interface
7
+ * Compatible with @solana/wallet-adapter-react and other Solana wallet adapters
8
+ */
9
+ export interface SolanaWalletConnector {
10
+ signTransaction: (transaction: any) => Promise<any>;
11
+ sendTransaction: (transaction: any, connection: any) => Promise<string>;
12
+ publicKey: {
13
+ toString: () => string;
14
+ } | null;
15
+ }
16
+ /**
17
+ * Solana connection interface
18
+ * Compatible with @solana/rpc RPC client
19
+ */
20
+ export interface SolanaConnection {
21
+ getLatestBlockhash: (commitment?: string) => Promise<{
22
+ blockhash: Blockhash;
23
+ lastValidBlockHeight: bigint;
24
+ }>;
25
+ confirmTransaction: (config: {
26
+ signature: Signature;
27
+ blockhash: Blockhash;
28
+ lastValidBlockHeight: bigint;
29
+ }, commitment?: string) => Promise<{
30
+ value: {
31
+ err: any;
32
+ };
33
+ }>;
34
+ rpc?: ReturnType<typeof createSolanaRpc>;
35
+ connection?: any;
36
+ }
37
+ /**
38
+ * Create a Solana transaction executor
39
+ * This function creates a transaction executor that can handle Solana transactions
40
+ * from relay.link bridge quotes
41
+ *
42
+ * @param connector - Solana wallet connector (from @solana/wallet-adapter-react or similar)
43
+ * @param connection - Solana RPC connection (from @solana/rpc)
44
+ * @returns Solana transaction executor function
45
+ */
46
+ export declare function createSolanaTransactionExecutor(connector: SolanaWalletConnector, connection: SolanaConnection): SolanaTransactionExecutor;
47
+ /**
48
+ * Helper to convert relay.link Solana step data to BridgeTransaction
49
+ */
50
+ export declare function convertRelaySolanaStepToTransaction(stepData: {
51
+ instructions: Array<{
52
+ programId: string;
53
+ keys: Array<{
54
+ pubkey: string;
55
+ isSigner: boolean;
56
+ isWritable: boolean;
57
+ }>;
58
+ data: string;
59
+ }>;
60
+ }, feePayer: string, chainId: number): BridgeTransaction;
@@ -0,0 +1,236 @@
1
+ import { address, appendTransactionMessageInstructions, createTransactionMessage, setTransactionMessageFeePayer, setTransactionMessageLifetimeUsingBlockhash, compileTransaction, getBase64EncodedWireTransaction, getTransactionDecoder, getCompiledTransactionMessageDecoder, decompileTransactionMessage, pipe, AccountRole, } from '@solana/kit';
2
+ import { createSolanaRpc } from '@solana/rpc';
3
+ import { VersionedTransaction } from '@solana/web3.js';
4
+ /**
5
+ * Create a Solana transaction executor
6
+ * This function creates a transaction executor that can handle Solana transactions
7
+ * from relay.link bridge quotes
8
+ *
9
+ * @param connector - Solana wallet connector (from @solana/wallet-adapter-react or similar)
10
+ * @param connection - Solana RPC connection (from @solana/rpc)
11
+ * @returns Solana transaction executor function
12
+ */
13
+ export function createSolanaTransactionExecutor(connector, connection) {
14
+ return async (tx) => {
15
+ try {
16
+ if (!connector.publicKey) {
17
+ throw new Error('Solana wallet not connected');
18
+ }
19
+ console.log('Creating Solana transaction:', {
20
+ hasData: !!tx.data,
21
+ hasInstructions: !!tx.instructions,
22
+ instructionsCount: tx.instructions?.length,
23
+ feePayer: tx.feePayer,
24
+ publicKey: connector.publicKey.toString(),
25
+ });
26
+ // Get RPC client from connection or create a default one
27
+ const rpc = connection.rpc || createSolanaRpc('https://api.mainnet-beta.solana.com');
28
+ let transactionMessage;
29
+ let blockhash;
30
+ let lastValidBlockHeight;
31
+ // Check if transaction has serialized data (from deBridge)
32
+ // deBridge returns Solana transactions as serialized hex strings in tx.data
33
+ if (tx.data && !tx.instructions && !tx.to) {
34
+ // Deserialize transaction from hex data
35
+ // Remove 0x prefix if present
36
+ const hexData = tx.data.replace(/^0x/, '');
37
+ const buffer = Buffer.from(hexData, 'hex');
38
+ // Decode transaction using @solana/kit
39
+ const decoded = getTransactionDecoder().decode(new Uint8Array(buffer));
40
+ // Extract blockhash from decoded transaction
41
+ const compiledMessage = getCompiledTransactionMessageDecoder().decode(decoded.messageBytes);
42
+ transactionMessage = decompileTransactionMessage(compiledMessage);
43
+ // Get blockhash from the decoded transaction's lifetime token
44
+ // In @solana/kit v5, lifetime is stored as lifetimeToken (blockhash string)
45
+ if ('lifetimeToken' in compiledMessage) {
46
+ blockhash = compiledMessage.lifetimeToken;
47
+ // For deserialized transactions, we don't have lastValidBlockHeight
48
+ // We'll need to fetch it from RPC
49
+ const blockhashResponse = await rpc.getLatestBlockhash({ commitment: 'finalized' }).send();
50
+ lastValidBlockHeight = blockhashResponse.value.lastValidBlockHeight;
51
+ }
52
+ }
53
+ else if (tx.instructions) {
54
+ // Transaction has instructions (from relay.link)
55
+ // Get fee payer from transaction or use connector's public key
56
+ const feePayer = tx.feePayer || connector.publicKey.toString();
57
+ const feePayerAddress = address(feePayer);
58
+ // Get latest blockhash using RPC
59
+ const blockhashResponse = await rpc.getLatestBlockhash({ commitment: 'finalized' }).send();
60
+ const latestBlockhash = {
61
+ blockhash: blockhashResponse.value.blockhash,
62
+ lastValidBlockHeight: blockhashResponse.value.lastValidBlockHeight,
63
+ };
64
+ blockhash = latestBlockhash.blockhash;
65
+ lastValidBlockHeight = latestBlockhash.lastValidBlockHeight;
66
+ console.log('Got blockhash:', {
67
+ blockhash,
68
+ lastValidBlockHeight: lastValidBlockHeight.toString(),
69
+ });
70
+ // Convert relay.link instructions to @solana/kit Instruction format
71
+ // @solana/kit expects: { programAddress, accounts, data }
72
+ // where accounts is array of { address, role: AccountRole }
73
+ console.log('Converting instructions:', tx.instructions.length);
74
+ const instructions = tx.instructions.map((inst, instIndex) => {
75
+ try {
76
+ // Log original values for debugging
77
+ console.log(`Instruction ${instIndex}:`, {
78
+ programId: inst.programId,
79
+ programIdLength: inst.programId?.length,
80
+ keysCount: inst.keys?.length,
81
+ });
82
+ // Validate and convert programId to base58 address
83
+ if (!inst.programId || typeof inst.programId !== 'string') {
84
+ throw new Error(`Invalid programId in instruction ${instIndex}: ${inst.programId}`);
85
+ }
86
+ const programIdAddress = address(inst.programId);
87
+ // Validate and convert all pubkey addresses in keys
88
+ // @solana/kit expects AccountMeta with { address, role: AccountRole }
89
+ const accounts = inst.keys.map((key, keyIndex) => {
90
+ console.log('Key:', key);
91
+ if (!key.pubkey || typeof key.pubkey !== 'string') {
92
+ throw new Error(`Invalid pubkey in instruction ${instIndex}, key ${keyIndex}: ${key.pubkey}`);
93
+ }
94
+ // Log original pubkey for debugging
95
+ if (key.pubkey.length < 32 || key.pubkey.length > 44) {
96
+ console.warn(`Suspicious pubkey length in instruction ${instIndex}, key ${keyIndex}:`, {
97
+ pubkey: key.pubkey,
98
+ length: key.pubkey.length,
99
+ isSigner: key.isSigner,
100
+ isWritable: key.isWritable,
101
+ });
102
+ }
103
+ try {
104
+ const validatedAddress = address(key.pubkey);
105
+ // @solana/kit AccountRole enum:
106
+ // WRITABLE_SIGNER = 3, READONLY_SIGNER = 2, WRITABLE = 1, READONLY = 0
107
+ let role;
108
+ if (key.isSigner && key.isWritable) {
109
+ role = AccountRole.WRITABLE_SIGNER;
110
+ }
111
+ else if (key.isSigner && !key.isWritable) {
112
+ role = AccountRole.READONLY_SIGNER;
113
+ }
114
+ else if (!key.isSigner && key.isWritable) {
115
+ role = AccountRole.WRITABLE;
116
+ }
117
+ else {
118
+ role = AccountRole.READONLY;
119
+ }
120
+ return {
121
+ address: validatedAddress,
122
+ role,
123
+ };
124
+ }
125
+ catch (error) {
126
+ throw new Error(`Failed to validate pubkey in instruction ${instIndex}, key ${keyIndex}: ${key.pubkey}. Error: ${error instanceof Error ? error.message : String(error)}`);
127
+ }
128
+ });
129
+ // Convert data to Uint8Array
130
+ const instructionData = inst.data.startsWith('0x')
131
+ ? new Uint8Array(Buffer.from(inst.data.slice(2), 'hex'))
132
+ : new Uint8Array(Buffer.from(inst.data, 'hex'));
133
+ // @solana/kit Instruction format: { programAddress, accounts, data }
134
+ return {
135
+ programAddress: programIdAddress,
136
+ accounts,
137
+ data: instructionData,
138
+ };
139
+ }
140
+ catch (error) {
141
+ console.error(`Error converting instruction ${instIndex}:`, {
142
+ instruction: inst,
143
+ error: error instanceof Error ? error.message : String(error),
144
+ });
145
+ throw error;
146
+ }
147
+ });
148
+ // Build transaction message using pipe pattern (following @solana/kit best practices)
149
+ transactionMessage = pipe(createTransactionMessage({ version: 0 }), (tx) => setTransactionMessageFeePayer(feePayerAddress, tx), (tx) => setTransactionMessageLifetimeUsingBlockhash(latestBlockhash, tx), (tx) => appendTransactionMessageInstructions(instructions, tx));
150
+ console.log('Transaction message built successfully');
151
+ }
152
+ else {
153
+ throw new Error('Solana transaction must have either instructions or serialized data');
154
+ }
155
+ // Compile transaction message to get wire format
156
+ console.log('Compiling transaction...');
157
+ const compiledTransaction = compileTransaction(transactionMessage);
158
+ // Convert to VersionedTransaction for wallet adapter signing
159
+ const base64WireTransaction = getBase64EncodedWireTransaction(compiledTransaction);
160
+ const transactionBytes = Buffer.from(base64WireTransaction, 'base64');
161
+ console.log('Transaction bytes length:', transactionBytes.length);
162
+ // Create VersionedTransaction from wire bytes
163
+ const versionedTransaction = VersionedTransaction.deserialize(transactionBytes);
164
+ console.log('Requesting signature...');
165
+ // Sign transaction with wallet adapter
166
+ const signedTransaction = await connector.signTransaction(versionedTransaction);
167
+ console.log('Transaction signed');
168
+ // Send transaction using RPC directly (matching reference implementation pattern)
169
+ // Convert signed transaction to base64 for RPC
170
+ const signedTransactionBytes = signedTransaction.serialize();
171
+ const signedTransactionBase64 = Buffer.from(signedTransactionBytes).toString('base64'); // Type assertion for Base64EncodedWireTransaction
172
+ console.log('Sending transaction via RPC...');
173
+ // Use RPC sendTransaction with base64 encoding (matching reference implementation)
174
+ const signatureResponse = await rpc
175
+ .sendTransaction(signedTransactionBase64, {
176
+ encoding: 'base64',
177
+ skipPreflight: true,
178
+ preflightCommitment: 'confirmed',
179
+ })
180
+ .send();
181
+ const signatureString = signatureResponse;
182
+ console.log('Transaction sent:', signatureString);
183
+ // Wait for confirmation
184
+ // Use the adapter's confirmTransaction method
185
+ // Only confirm if we have blockhash and lastValidBlockHeight
186
+ let result;
187
+ if (blockhash && lastValidBlockHeight) {
188
+ console.log('Waiting for confirmation...');
189
+ result = await connection.confirmTransaction({
190
+ signature: signatureString,
191
+ blockhash,
192
+ lastValidBlockHeight,
193
+ }, 'confirmed');
194
+ console.log('Transaction confirmed');
195
+ }
196
+ else {
197
+ // For transactions without blockhash info, return success
198
+ // The transaction will be confirmed by the network
199
+ console.log('No blockhash/lastValidBlockHeight, skipping confirmation');
200
+ result = {
201
+ value: {
202
+ err: null,
203
+ },
204
+ };
205
+ }
206
+ // Check for errors
207
+ if (result.value.err) {
208
+ console.error('Transaction failed:', result.value.err);
209
+ throw new Error(`Solana transaction failed: ${result.value.err.toString()}`);
210
+ }
211
+ console.log('Transaction completed successfully:', signatureString);
212
+ return signatureString;
213
+ }
214
+ catch (error) {
215
+ console.error('Solana transaction execution failed:', {
216
+ error: error instanceof Error ? error.message : String(error),
217
+ stack: error instanceof Error ? error.stack : undefined,
218
+ });
219
+ throw error;
220
+ }
221
+ };
222
+ }
223
+ /**
224
+ * Helper to convert relay.link Solana step data to BridgeTransaction
225
+ */
226
+ export function convertRelaySolanaStepToTransaction(stepData, feePayer, chainId) {
227
+ return {
228
+ chainId,
229
+ feePayer,
230
+ instructions: stepData.instructions.map((inst) => ({
231
+ programId: inst.programId,
232
+ keys: inst.keys,
233
+ data: inst.data.startsWith('0x') ? inst.data : `0x${inst.data}`,
234
+ })),
235
+ };
236
+ }
@@ -0,0 +1,90 @@
1
+ import { createEip712DocForOrder, createEip712DocForWalletGeneration, type QuoteResponse, type SignInMessage, type AuthResponse } from '@silentswap/sdk';
2
+ import type { SilentSwapClient } from '@silentswap/sdk';
3
+ import type { WalletClient } from 'viem';
4
+ import type { Connector } from 'wagmi';
5
+ export interface useAuthOptions {
6
+ /** SilentSwap client instance */
7
+ client?: SilentSwapClient;
8
+ /** User's EVM address */
9
+ address?: `0x${string}`;
10
+ /** Wallet client for signing operations */
11
+ walletClient?: WalletClient;
12
+ /** Wagmi connector */
13
+ connector?: Connector;
14
+ /** Domain for SIWE message (defaults to window.location.host) */
15
+ domain?: string;
16
+ /** Whether to auto-authenticate when dependencies are available */
17
+ autoAuthenticate?: boolean;
18
+ }
19
+ export interface useAuthReturn {
20
+ auth: AuthResponse | null;
21
+ nonce: string | null;
22
+ isLoading: boolean;
23
+ error: Error | null;
24
+ createSignInMessage: (address: `0x${string}`, nonce: string, domain?: string) => SignInMessage;
25
+ createEip712DocForOrder: (quoteResponse: QuoteResponse) => ReturnType<typeof createEip712DocForOrder>;
26
+ createEip712DocForWalletGeneration: (scope: string, token: string) => ReturnType<typeof createEip712DocForWalletGeneration>;
27
+ getNonce: () => Promise<string>;
28
+ authenticate: () => Promise<AuthResponse | null>;
29
+ signIn: () => Promise<AuthResponse | null>;
30
+ signOut: () => void;
31
+ isAuthenticated: () => boolean;
32
+ }
33
+ /**
34
+ * React hook for SilentSwap authentication with full SIWE flow
35
+ *
36
+ * This hook provides:
37
+ * - SIWE authentication with nonce management
38
+ * - Automatic auth caching and restoration
39
+ * - Sign-in message creation and signing
40
+ * - Auth state management
41
+ * - Auto-authentication when dependencies are available
42
+ *
43
+ * @param options - Configuration options for authentication
44
+ * @returns Object with authentication state and methods
45
+ *
46
+ * @example
47
+ * ```typescript
48
+ * import { useAuth, useSilentClient } from '@silentswap/react';
49
+ * import { useWalletClient, useAccount } from 'wagmi';
50
+ *
51
+ * function MyComponent() {
52
+ * const { address } = useAccount();
53
+ * const { data: walletClient } = useWalletClient();
54
+ * const client = useSilentClient({ config: { /* ... *\/ } }).client;
55
+ *
56
+ * const {
57
+ * auth,
58
+ * isAuthenticated,
59
+ * signIn,
60
+ * signOut,
61
+ * isLoading,
62
+ * error
63
+ * } = useAuth({
64
+ * client,
65
+ * address,
66
+ * walletClient: walletClient as any,
67
+ * autoAuthenticate: true,
68
+ * });
69
+ *
70
+ * return (
71
+ * <div>
72
+ * {isLoading && <div>Authenticating...</div>}
73
+ * {error && <div>Error: {error.message}</div>}
74
+ *
75
+ * {isAuthenticated() ? (
76
+ * <div>
77
+ * <p>Authenticated as {auth?.address}</p>
78
+ * <button onClick={signOut}>Sign Out</button>
79
+ * </div>
80
+ * ) : (
81
+ * <button onClick={signIn} disabled={isLoading}>
82
+ * Sign In with Ethereum
83
+ * </button>
84
+ * )}
85
+ * </div>
86
+ * );
87
+ * }
88
+ * ```
89
+ */
90
+ export declare function useAuth({ client, address, walletClient, domain, autoAuthenticate, }?: useAuthOptions): useAuthReturn;