@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.
- package/README.md +48 -0
- package/dist/contexts/AssetsContext.d.ts +24 -0
- package/dist/contexts/AssetsContext.js +83 -0
- package/dist/contexts/BalancesContext.d.ts +28 -0
- package/dist/contexts/BalancesContext.js +533 -0
- package/dist/contexts/OrdersContext.d.ts +53 -0
- package/dist/contexts/OrdersContext.js +240 -0
- package/dist/contexts/PricesContext.d.ts +12 -0
- package/dist/contexts/PricesContext.js +109 -0
- package/dist/contexts/SilentSwapContext.d.ts +58 -0
- package/dist/contexts/SilentSwapContext.js +205 -0
- package/dist/hooks/silent/orderTrackingWebSocketManager.d.ts +48 -0
- package/dist/hooks/silent/orderTrackingWebSocketManager.js +284 -0
- package/dist/hooks/silent/solana-transaction.d.ts +60 -0
- package/dist/hooks/silent/solana-transaction.js +236 -0
- package/dist/hooks/silent/useAuth.d.ts +90 -0
- package/dist/hooks/silent/useAuth.js +269 -0
- package/dist/hooks/silent/useBridgeExecution.d.ts +39 -0
- package/dist/hooks/silent/useBridgeExecution.js +877 -0
- package/dist/hooks/silent/useOrderSigning.d.ts +34 -0
- package/dist/hooks/silent/useOrderSigning.js +133 -0
- package/dist/hooks/silent/useOrderTracking.d.ts +174 -0
- package/dist/hooks/silent/useOrderTracking.js +524 -0
- package/dist/hooks/silent/useQuoteCalculation.d.ts +50 -0
- package/dist/hooks/silent/useQuoteCalculation.js +331 -0
- package/dist/hooks/silent/useQuoteFetching.d.ts +18 -0
- package/dist/hooks/silent/useQuoteFetching.js +54 -0
- package/dist/hooks/silent/useRefund.d.ts +26 -0
- package/dist/hooks/silent/useRefund.js +134 -0
- package/dist/hooks/silent/useSilentClient.d.ts +16 -0
- package/dist/hooks/silent/useSilentClient.js +32 -0
- package/dist/hooks/silent/useSilentOrders.d.ts +174 -0
- package/dist/hooks/silent/useSilentOrders.js +73 -0
- package/dist/hooks/silent/useSilentQuote.d.ts +88 -0
- package/dist/hooks/silent/useSilentQuote.js +381 -0
- package/dist/hooks/silent/useWallet.d.ts +76 -0
- package/dist/hooks/silent/useWallet.js +203 -0
- package/dist/hooks/useAssetPrice.d.ts +8 -0
- package/dist/hooks/useAssetPrice.js +47 -0
- package/dist/hooks/useContacts.d.ts +52 -0
- package/dist/hooks/useContacts.js +259 -0
- package/dist/hooks/useEgressEstimates.d.ts +32 -0
- package/dist/hooks/useEgressEstimates.js +230 -0
- package/dist/hooks/useHiddenSwapFees.d.ts +22 -0
- package/dist/hooks/useHiddenSwapFees.js +81 -0
- package/dist/hooks/useOrderEstimates.d.ts +37 -0
- package/dist/hooks/useOrderEstimates.js +393 -0
- package/dist/hooks/useOutputAssetInfo.d.ts +12 -0
- package/dist/hooks/useOutputAssetInfo.js +38 -0
- package/dist/hooks/usePrices.d.ts +60 -0
- package/dist/hooks/usePrices.js +188 -0
- package/dist/hooks/useQuote.d.ts +73 -0
- package/dist/hooks/useQuote.js +507 -0
- package/dist/hooks/useResetSwapForm.d.ts +16 -0
- package/dist/hooks/useResetSwapForm.js +68 -0
- package/dist/hooks/useSlippageUsd.d.ts +11 -0
- package/dist/hooks/useSlippageUsd.js +19 -0
- package/dist/hooks/useSolanaAdapter.d.ts +15 -0
- package/dist/hooks/useSolanaAdapter.js +55 -0
- package/dist/hooks/useStatus.d.ts +25 -0
- package/dist/hooks/useStatus.js +60 -0
- package/dist/hooks/useSwap.d.ts +67 -0
- package/dist/hooks/useSwap.js +285 -0
- package/dist/hooks/useTransaction.d.ts +119 -0
- package/dist/hooks/useTransaction.js +353 -0
- package/dist/hooks/useTransactionAddress.d.ts +11 -0
- package/dist/hooks/useTransactionAddress.js +26 -0
- package/dist/hooks/useUsdValue.d.ts +7 -0
- package/dist/hooks/useUsdValue.js +19 -0
- package/dist/index.d.ts +54 -0
- package/dist/index.js +41 -0
- package/dist/stories/SilentSwapOverview.stories.d.ts +10 -0
- package/dist/stories/SilentSwapOverview.stories.js +364 -0
- package/dist/stories/useAuth.stories.d.ts +6 -0
- package/dist/stories/useAuth.stories.js +55 -0
- package/dist/stories/useSilentClient.stories.d.ts +9 -0
- package/dist/stories/useSilentClient.stories.js +39 -0
- package/dist/stories/useSilentOrders.stories.d.ts +1 -0
- package/dist/stories/useSilentOrders.stories.js +1 -0
- package/dist/stories/useSilentQuote.stories.d.ts +6 -0
- package/dist/stories/useSilentQuote.stories.js +267 -0
- package/dist/stories/useTransaction.stories.d.ts +6 -0
- package/dist/stories/useTransaction.stories.js +121 -0
- package/dist/utils/formatters.d.ts +33 -0
- package/dist/utils/formatters.js +82 -0
- package/package.json +67 -0
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { useMemo } from 'react';
|
|
2
|
+
import { useWallet as useSolanaWallet, useConnection } from '@solana/wallet-adapter-react';
|
|
3
|
+
import { createSolanaRpc } from '@solana/rpc';
|
|
4
|
+
/**
|
|
5
|
+
* Reusable hook for Solana wallet adapter setup
|
|
6
|
+
* Returns the Solana connector and connection adapter for use with SilentSwap hooks
|
|
7
|
+
*/
|
|
8
|
+
export function useSolanaAdapter() {
|
|
9
|
+
const solanaWallet = useSolanaWallet();
|
|
10
|
+
const solanaConnection = useConnection();
|
|
11
|
+
// Prepare Solana connector if wallet is connected
|
|
12
|
+
const solanaConnector = useMemo(() => {
|
|
13
|
+
if (!solanaWallet.publicKey || !solanaWallet.signTransaction || !solanaWallet.sendTransaction) {
|
|
14
|
+
return undefined;
|
|
15
|
+
}
|
|
16
|
+
return {
|
|
17
|
+
signTransaction: solanaWallet.signTransaction.bind(solanaWallet),
|
|
18
|
+
sendTransaction: solanaWallet.sendTransaction.bind(solanaWallet),
|
|
19
|
+
publicKey: solanaWallet.publicKey,
|
|
20
|
+
};
|
|
21
|
+
}, [solanaWallet.publicKey, solanaWallet.signTransaction, solanaWallet.sendTransaction]);
|
|
22
|
+
// Prepare Solana connection
|
|
23
|
+
const solanaConnectionAdapter = useMemo(() => {
|
|
24
|
+
if (!solanaConnection.connection) {
|
|
25
|
+
return undefined;
|
|
26
|
+
}
|
|
27
|
+
const rpc = createSolanaRpc(solanaConnection.connection.rpcEndpoint);
|
|
28
|
+
return {
|
|
29
|
+
rpc,
|
|
30
|
+
// Include the actual connection object for wallet adapter compatibility
|
|
31
|
+
connection: solanaConnection.connection,
|
|
32
|
+
getLatestBlockhash: async (commitment) => {
|
|
33
|
+
const result = await rpc.getLatestBlockhash({ commitment: commitment }).send();
|
|
34
|
+
return {
|
|
35
|
+
blockhash: result.value.blockhash,
|
|
36
|
+
lastValidBlockHeight: BigInt(result.value.lastValidBlockHeight),
|
|
37
|
+
};
|
|
38
|
+
},
|
|
39
|
+
confirmTransaction: async (config, commitment) => {
|
|
40
|
+
// Note: @solana/rpc doesn't have a direct confirmTransaction method
|
|
41
|
+
// We'll use getSignatureStatuses instead
|
|
42
|
+
const result = await rpc.getSignatureStatuses([config.signature], { searchTransactionHistory: true }).send();
|
|
43
|
+
return {
|
|
44
|
+
value: {
|
|
45
|
+
err: result.value?.[0]?.err || null,
|
|
46
|
+
},
|
|
47
|
+
};
|
|
48
|
+
},
|
|
49
|
+
};
|
|
50
|
+
}, [solanaConnection.connection]);
|
|
51
|
+
return {
|
|
52
|
+
solanaConnector,
|
|
53
|
+
solanaConnectionAdapter,
|
|
54
|
+
};
|
|
55
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
export interface StatusResponse {
|
|
2
|
+
serviceFeeRate?: number;
|
|
3
|
+
overheadUsd?: string;
|
|
4
|
+
minimumDepositUusdc?: string;
|
|
5
|
+
maximumDepositUusdc?: string;
|
|
6
|
+
platformHealth?: 'UNKNOWN' | 'ON' | 'OFF' | 'PAUSED' | 'ONLINE' | 'DEPOSITS_PAUSED' | 'MAINTENANCE';
|
|
7
|
+
outputLimit?: number;
|
|
8
|
+
volume?: number;
|
|
9
|
+
liquidity?: number;
|
|
10
|
+
identity?: string;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Hook to fetch the current platform status, fee rates, and limits
|
|
14
|
+
*/
|
|
15
|
+
export declare function useStatus(): {
|
|
16
|
+
status: StatusResponse;
|
|
17
|
+
isLoading: boolean;
|
|
18
|
+
error: Error | null;
|
|
19
|
+
serviceFeeRate: number;
|
|
20
|
+
overheadUsd: number;
|
|
21
|
+
minimumDepositUusdc: string | undefined;
|
|
22
|
+
maximumDepositUusdc: string | undefined;
|
|
23
|
+
platformHealth: "UNKNOWN" | "ON" | "OFF" | "PAUSED" | "ONLINE" | "DEPOSITS_PAUSED" | "MAINTENANCE" | undefined;
|
|
24
|
+
outputLimit: number | undefined;
|
|
25
|
+
};
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { useState, useEffect } from 'react';
|
|
2
|
+
import { useSilentSwap } from '../contexts/SilentSwapContext.js';
|
|
3
|
+
/**
|
|
4
|
+
* Hook to fetch the current platform status, fee rates, and limits
|
|
5
|
+
*/
|
|
6
|
+
export function useStatus() {
|
|
7
|
+
const { config } = useSilentSwap();
|
|
8
|
+
const baseUrl = config.baseUrl;
|
|
9
|
+
const [status, setStatus] = useState({});
|
|
10
|
+
const [isLoading, setIsLoading] = useState(true);
|
|
11
|
+
const [error, setError] = useState(null);
|
|
12
|
+
useEffect(() => {
|
|
13
|
+
let isMounted = true;
|
|
14
|
+
const fetchStatus = async () => {
|
|
15
|
+
try {
|
|
16
|
+
setIsLoading(true);
|
|
17
|
+
const response = await fetch(`${baseUrl}/status`);
|
|
18
|
+
if (!response.ok) {
|
|
19
|
+
throw new Error(`Failed to fetch status: ${response.statusText}`);
|
|
20
|
+
}
|
|
21
|
+
const data = await response.json();
|
|
22
|
+
if (isMounted) {
|
|
23
|
+
setStatus(data);
|
|
24
|
+
setError(null);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
catch (err) {
|
|
28
|
+
if (isMounted) {
|
|
29
|
+
setError(err instanceof Error ? err : new Error('Failed to fetch status'));
|
|
30
|
+
// Use defaults on error
|
|
31
|
+
setStatus({});
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
finally {
|
|
35
|
+
if (isMounted) {
|
|
36
|
+
setIsLoading(false);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
};
|
|
40
|
+
fetchStatus();
|
|
41
|
+
return () => {
|
|
42
|
+
isMounted = false;
|
|
43
|
+
};
|
|
44
|
+
}, [baseUrl]);
|
|
45
|
+
// Convert overheadUsd from string to number
|
|
46
|
+
const overheadUsd = status.overheadUsd ? parseFloat(status.overheadUsd) : 0;
|
|
47
|
+
// serviceFeeRate is a Decimal (0.01 = 1%), convert to number
|
|
48
|
+
const serviceFeeRate = status.serviceFeeRate ?? 0.01; // Default 1%
|
|
49
|
+
return {
|
|
50
|
+
status,
|
|
51
|
+
isLoading,
|
|
52
|
+
error,
|
|
53
|
+
serviceFeeRate,
|
|
54
|
+
overheadUsd,
|
|
55
|
+
minimumDepositUusdc: status.minimumDepositUusdc,
|
|
56
|
+
maximumDepositUusdc: status.maximumDepositUusdc,
|
|
57
|
+
platformHealth: status.platformHealth,
|
|
58
|
+
outputLimit: status.outputLimit,
|
|
59
|
+
};
|
|
60
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { type AssetInfo } from '@silentswap/sdk';
|
|
2
|
+
export declare const X_RANGE_SLIDER_MIN_GAP = 0.01;
|
|
3
|
+
export declare const OUTPUT_LIMIT = 5;
|
|
4
|
+
export declare const DEFAULT_SOURCE_ASSET = "eip155:1/slip44:60";
|
|
5
|
+
export declare const DEFAULT_DEST_ASSET = "eip155:1/erc20:0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599";
|
|
6
|
+
export declare const DEFAULT_OUTPUT_ASSET = "eip155:8453/erc20:0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913";
|
|
7
|
+
export declare const getSourceAssetCaip19: (tokenIn: AssetInfo | null) => string;
|
|
8
|
+
export interface Destination {
|
|
9
|
+
asset: string;
|
|
10
|
+
contact: string;
|
|
11
|
+
amount: string;
|
|
12
|
+
priceUsd?: number;
|
|
13
|
+
}
|
|
14
|
+
export interface SwapState {
|
|
15
|
+
tokenIn: AssetInfo | null;
|
|
16
|
+
inputAmount: string;
|
|
17
|
+
isSwapLoading: boolean;
|
|
18
|
+
isMultiSwapLoading: boolean;
|
|
19
|
+
srcChain: number;
|
|
20
|
+
dstChain: number;
|
|
21
|
+
slippage: number;
|
|
22
|
+
isAutoSlippage: boolean;
|
|
23
|
+
latestSuggestedAutoSlippageValue: number;
|
|
24
|
+
recipientAddress: string;
|
|
25
|
+
privacyEnabled: boolean;
|
|
26
|
+
usdInputMode: boolean;
|
|
27
|
+
destinations: Destination[];
|
|
28
|
+
splits: number[];
|
|
29
|
+
isDraggingSlider: boolean;
|
|
30
|
+
setTokenIn: (token: AssetInfo) => void;
|
|
31
|
+
setSrcChain: (chainId: number) => void;
|
|
32
|
+
setDstChain: (chainId: number) => void;
|
|
33
|
+
setInputAmount: (amount: string) => void;
|
|
34
|
+
setRecipientAddress: (recipientAddress: string) => void;
|
|
35
|
+
setSwapLoading: (value: boolean) => void;
|
|
36
|
+
swapTokens: (amount: string) => void;
|
|
37
|
+
setSlippage: (amount: number) => void;
|
|
38
|
+
setIsAutoSlippage: (value: boolean) => void;
|
|
39
|
+
setLatestSuggestedAutoSlippageValue: (value: number) => void;
|
|
40
|
+
setPrivacyEnabled: (enabled: boolean) => void;
|
|
41
|
+
setUsdInputMode: (enabled: boolean) => void;
|
|
42
|
+
toggleUsdInputMode: () => void;
|
|
43
|
+
setDestinations: (updater: Destination[] | ((prev: Destination[]) => Destination[])) => void;
|
|
44
|
+
setSplits: (updater: number[] | ((prev: number[]) => number[])) => void;
|
|
45
|
+
handleAddOutput: (caip19Asset?: string, defaultAddress?: string) => void;
|
|
46
|
+
handleDeleteOutput: (index: number) => void;
|
|
47
|
+
propagateRange: (i_split: number, x_value: number, b_hi: boolean, b_whole: boolean) => boolean;
|
|
48
|
+
updateDestinationAsset: (index: number, asset: string) => void;
|
|
49
|
+
updateDestinationContact: (index: number, contact: string) => void;
|
|
50
|
+
updateDestinationAmount: (index: number, amount: string) => void;
|
|
51
|
+
setIsDraggingSlider: (isDragging: boolean) => void;
|
|
52
|
+
getCanAddOutput: () => boolean;
|
|
53
|
+
getHasMultipleOutputs: () => boolean;
|
|
54
|
+
}
|
|
55
|
+
export declare const useSwap: import("zustand").UseBoundStore<Omit<import("zustand").StoreApi<SwapState>, "setState" | "persist"> & {
|
|
56
|
+
setState(partial: SwapState | Partial<SwapState> | ((state: SwapState) => SwapState | Partial<SwapState>), replace?: false | undefined): unknown;
|
|
57
|
+
setState(state: SwapState | ((state: SwapState) => SwapState), replace: true): unknown;
|
|
58
|
+
persist: {
|
|
59
|
+
setOptions: (options: Partial<import("zustand/middleware").PersistOptions<SwapState, unknown, unknown>>) => void;
|
|
60
|
+
clearStorage: () => void;
|
|
61
|
+
rehydrate: () => Promise<void> | void;
|
|
62
|
+
hasHydrated: () => boolean;
|
|
63
|
+
onHydrate: (fn: (state: SwapState) => void) => () => void;
|
|
64
|
+
onFinishHydration: (fn: (state: SwapState) => void) => () => void;
|
|
65
|
+
getOptions: () => Partial<import("zustand/middleware").PersistOptions<SwapState, unknown, unknown>>;
|
|
66
|
+
};
|
|
67
|
+
}>;
|
|
@@ -0,0 +1,285 @@
|
|
|
1
|
+
import { create } from 'zustand';
|
|
2
|
+
import { persist, createJSONStorage } from 'zustand/middleware';
|
|
3
|
+
import { getAssetByCaip19 } from '@silentswap/sdk';
|
|
4
|
+
const DEFAULT_SLIPPAGE = 1;
|
|
5
|
+
// Constants matching Svelte app
|
|
6
|
+
export const X_RANGE_SLIDER_MIN_GAP = 0.01;
|
|
7
|
+
export const OUTPUT_LIMIT = 5;
|
|
8
|
+
// Default assets
|
|
9
|
+
export const DEFAULT_SOURCE_ASSET = 'eip155:1/slip44:60'; // ETH on Ethereum
|
|
10
|
+
export const DEFAULT_DEST_ASSET = 'eip155:1/erc20:0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599'; // WBTC on Ethereum
|
|
11
|
+
export const DEFAULT_OUTPUT_ASSET = 'eip155:8453/erc20:0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913'; // USDC on Base
|
|
12
|
+
// Helper to get source asset CAIP19 from tokenIn
|
|
13
|
+
export const getSourceAssetCaip19 = (tokenIn) => {
|
|
14
|
+
return tokenIn?.caip19 || DEFAULT_SOURCE_ASSET;
|
|
15
|
+
};
|
|
16
|
+
export const useSwap = create()(persist((set, get) => ({
|
|
17
|
+
// Regular swap state
|
|
18
|
+
isAutoSlippage: true,
|
|
19
|
+
tokenIn: getAssetByCaip19(DEFAULT_SOURCE_ASSET) ?? null,
|
|
20
|
+
srcChain: 1,
|
|
21
|
+
dstChain: 1,
|
|
22
|
+
inputAmount: '',
|
|
23
|
+
isSwapLoading: false,
|
|
24
|
+
isMultiSwapLoading: false,
|
|
25
|
+
latestSuggestedAutoSlippageValue: 0,
|
|
26
|
+
slippage: DEFAULT_SLIPPAGE,
|
|
27
|
+
recipientAddress: '',
|
|
28
|
+
// Privacy/UI state
|
|
29
|
+
privacyEnabled: true,
|
|
30
|
+
usdInputMode: false,
|
|
31
|
+
// Hidden swap state (uses tokenIn and inputAmount)
|
|
32
|
+
// For regular swaps, destinations[0] represents the output token and recipient
|
|
33
|
+
destinations: [{ asset: DEFAULT_DEST_ASSET, contact: '', amount: '' }],
|
|
34
|
+
splits: [1],
|
|
35
|
+
isDraggingSlider: false,
|
|
36
|
+
// Regular swap setters
|
|
37
|
+
setTokenIn: (token) => {
|
|
38
|
+
set({ tokenIn: token });
|
|
39
|
+
},
|
|
40
|
+
setSrcChain: (chainId) => set({ srcChain: chainId }),
|
|
41
|
+
setDstChain: (chainId) => set({ dstChain: chainId }),
|
|
42
|
+
setInputAmount: (amount) => set({ inputAmount: amount }),
|
|
43
|
+
setRecipientAddress: (recipientAddress) => set({ recipientAddress }),
|
|
44
|
+
setSwapLoading: (value) => set({ isSwapLoading: value }),
|
|
45
|
+
swapTokens: (amount) => set((state) => {
|
|
46
|
+
if (!state.tokenIn || state.destinations.length === 0 || !state.destinations[0]?.asset) {
|
|
47
|
+
return state;
|
|
48
|
+
}
|
|
49
|
+
// Remove any commas or special characters and ensure the string is valid
|
|
50
|
+
const sanitizedAmount = amount.replace(/[',]/g, '');
|
|
51
|
+
// Check if the sanitized amount is a valid number
|
|
52
|
+
const number = !isNaN(parseFloat(sanitizedAmount)) ? parseFloat(sanitizedAmount) : null;
|
|
53
|
+
// Get current destination asset
|
|
54
|
+
const currentDestAsset = state.destinations[0]?.asset;
|
|
55
|
+
const currentDestContact = state.destinations[0]?.contact || '';
|
|
56
|
+
// Swap tokenIn with destination asset
|
|
57
|
+
const newTokenIn = getAssetByCaip19(currentDestAsset);
|
|
58
|
+
if (!newTokenIn)
|
|
59
|
+
return state;
|
|
60
|
+
// Only add inputAmount if the number is valid
|
|
61
|
+
return {
|
|
62
|
+
...state,
|
|
63
|
+
...(number !== null && { inputAmount: number.toString() }), // Only include inputAmount if number is valid
|
|
64
|
+
tokenIn: newTokenIn,
|
|
65
|
+
destinations: [{ asset: state.tokenIn.caip19, contact: currentDestContact, amount: '' }],
|
|
66
|
+
recipientAddress: '',
|
|
67
|
+
};
|
|
68
|
+
}),
|
|
69
|
+
setSlippage: (value) => {
|
|
70
|
+
return set({ slippage: value, isAutoSlippage: false });
|
|
71
|
+
},
|
|
72
|
+
setIsAutoSlippage: (value) => {
|
|
73
|
+
return set({ isAutoSlippage: value });
|
|
74
|
+
},
|
|
75
|
+
setLatestSuggestedAutoSlippageValue: (value) => {
|
|
76
|
+
return set({ latestSuggestedAutoSlippageValue: value });
|
|
77
|
+
},
|
|
78
|
+
setPrivacyEnabled: (enabled) => {
|
|
79
|
+
return set({ privacyEnabled: enabled });
|
|
80
|
+
},
|
|
81
|
+
setUsdInputMode: (enabled) => {
|
|
82
|
+
return set({ usdInputMode: enabled });
|
|
83
|
+
},
|
|
84
|
+
toggleUsdInputMode: () => {
|
|
85
|
+
return set((state) => ({ usdInputMode: !state.usdInputMode }));
|
|
86
|
+
},
|
|
87
|
+
// Hidden swap setters
|
|
88
|
+
setDestinations: (updater) => {
|
|
89
|
+
const current = get().destinations;
|
|
90
|
+
const next = typeof updater === 'function' ? updater(current) : updater;
|
|
91
|
+
set({ destinations: next });
|
|
92
|
+
},
|
|
93
|
+
setSplits: (updater) => {
|
|
94
|
+
const current = get().splits;
|
|
95
|
+
const next = typeof updater === 'function' ? updater(current) : updater;
|
|
96
|
+
set({ splits: next });
|
|
97
|
+
},
|
|
98
|
+
// Hidden swap actions
|
|
99
|
+
handleAddOutput: (_, defaultAddress) => {
|
|
100
|
+
const state = get();
|
|
101
|
+
const newAsset = DEFAULT_OUTPUT_ASSET;
|
|
102
|
+
const numOutputs = state.splits.length + 1;
|
|
103
|
+
const newSplits = Array.from({ length: numOutputs }, (_, i) => (i + 1) / numOutputs);
|
|
104
|
+
const newDestination = {
|
|
105
|
+
asset: newAsset,
|
|
106
|
+
contact: defaultAddress || '',
|
|
107
|
+
amount: '',
|
|
108
|
+
};
|
|
109
|
+
set((prev) => ({
|
|
110
|
+
destinations: [...prev.destinations, newDestination],
|
|
111
|
+
splits: newSplits,
|
|
112
|
+
}));
|
|
113
|
+
},
|
|
114
|
+
handleDeleteOutput: (index) => {
|
|
115
|
+
const state = get();
|
|
116
|
+
// Only allow deletion if there are multiple outputs
|
|
117
|
+
if (state.splits.length <= 1)
|
|
118
|
+
return;
|
|
119
|
+
set((prev) => {
|
|
120
|
+
const newDestinations = [...prev.destinations];
|
|
121
|
+
const newSplits = [...prev.splits];
|
|
122
|
+
// Update primary destination if deleting index 0
|
|
123
|
+
if (index === 0) {
|
|
124
|
+
newDestinations[0] = {
|
|
125
|
+
contact: newDestinations[0]?.contact ?? '',
|
|
126
|
+
asset: newDestinations[0]?.asset ?? '',
|
|
127
|
+
amount: newDestinations[0]?.amount ?? '',
|
|
128
|
+
priceUsd: newDestinations[0]?.priceUsd,
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
// If deleting the last split, remove the split at index-1
|
|
132
|
+
if (index === newSplits.length - 1) {
|
|
133
|
+
newSplits.splice(index - 1, 1);
|
|
134
|
+
}
|
|
135
|
+
// Otherwise, remove the split at index
|
|
136
|
+
else {
|
|
137
|
+
newSplits.splice(index, 1);
|
|
138
|
+
}
|
|
139
|
+
// Remove destination at index if destinations length > splits length
|
|
140
|
+
if (newDestinations.length > newSplits.length) {
|
|
141
|
+
newDestinations.splice(index, 1);
|
|
142
|
+
}
|
|
143
|
+
return {
|
|
144
|
+
destinations: newDestinations,
|
|
145
|
+
splits: newSplits,
|
|
146
|
+
};
|
|
147
|
+
});
|
|
148
|
+
},
|
|
149
|
+
propagateRange: (i_split, x_value, b_hi, b_whole) => {
|
|
150
|
+
const state = get();
|
|
151
|
+
const newSplits = [...state.splits];
|
|
152
|
+
if (b_hi) {
|
|
153
|
+
const x_max_safe = 1 - X_RANGE_SLIDER_MIN_GAP * (state.splits.length - i_split - 1);
|
|
154
|
+
if (x_value > x_max_safe)
|
|
155
|
+
return true;
|
|
156
|
+
const x_scale = (1 - x_value) / (1 - state.splits[i_split]);
|
|
157
|
+
for (let i_each = state.splits.length - 1; i_each > i_split; i_each--) {
|
|
158
|
+
const x_new = 1 - x_scale * (1 - state.splits[i_each]);
|
|
159
|
+
if ((state.splits[i_each + 1] ?? 1) - x_new < X_RANGE_SLIDER_MIN_GAP) {
|
|
160
|
+
newSplits[i_each] = (state.splits[i_each + 1] ?? 1) - X_RANGE_SLIDER_MIN_GAP;
|
|
161
|
+
}
|
|
162
|
+
else {
|
|
163
|
+
newSplits[i_each] = x_new;
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
else {
|
|
168
|
+
if (x_value < X_RANGE_SLIDER_MIN_GAP * i_split)
|
|
169
|
+
return true;
|
|
170
|
+
const x_scale = x_value / (state.splits[i_split - 1] ?? 0);
|
|
171
|
+
for (let i_each = 0; i_each < i_split - 1; i_each++) {
|
|
172
|
+
const x_new = state.splits[i_each] * x_scale;
|
|
173
|
+
if (x_new - (i_each < 1 ? 0 : state.splits[i_each - 1]) < X_RANGE_SLIDER_MIN_GAP) {
|
|
174
|
+
newSplits[i_each] = X_RANGE_SLIDER_MIN_GAP;
|
|
175
|
+
}
|
|
176
|
+
else {
|
|
177
|
+
newSplits[i_each] = x_new;
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
if (b_whole) {
|
|
182
|
+
for (let i_each = 0; i_each < state.splits.length - 1; i_each++) {
|
|
183
|
+
newSplits[i_each] = Math.round(newSplits[i_each] * 100) / 100;
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
if (b_hi) {
|
|
187
|
+
newSplits[i_split] = x_value;
|
|
188
|
+
}
|
|
189
|
+
else if (i_split > 0) {
|
|
190
|
+
newSplits[i_split - 1] = x_value;
|
|
191
|
+
}
|
|
192
|
+
newSplits[newSplits.length - 1] = 1.0;
|
|
193
|
+
set({ splits: newSplits });
|
|
194
|
+
return false;
|
|
195
|
+
},
|
|
196
|
+
updateDestinationAsset: (index, asset) => {
|
|
197
|
+
set((state) => {
|
|
198
|
+
return {
|
|
199
|
+
destinations: state.destinations.map((dest, idx) => idx === index
|
|
200
|
+
? {
|
|
201
|
+
asset: String(asset),
|
|
202
|
+
contact: String(dest.contact),
|
|
203
|
+
amount: '',
|
|
204
|
+
priceUsd: dest.priceUsd,
|
|
205
|
+
}
|
|
206
|
+
: {
|
|
207
|
+
asset: String(dest.asset),
|
|
208
|
+
contact: String(dest.contact),
|
|
209
|
+
amount: String(dest.amount),
|
|
210
|
+
priceUsd: dest.priceUsd,
|
|
211
|
+
}),
|
|
212
|
+
};
|
|
213
|
+
});
|
|
214
|
+
},
|
|
215
|
+
updateDestinationContact: (index, contact) => {
|
|
216
|
+
set((state) => ({
|
|
217
|
+
destinations: state.destinations.map((dest, idx) => idx === index
|
|
218
|
+
? {
|
|
219
|
+
asset: String(dest.asset),
|
|
220
|
+
contact: String(contact),
|
|
221
|
+
amount: String(dest.amount),
|
|
222
|
+
priceUsd: dest.priceUsd,
|
|
223
|
+
}
|
|
224
|
+
: {
|
|
225
|
+
asset: String(dest.asset),
|
|
226
|
+
contact: String(dest.contact),
|
|
227
|
+
amount: String(dest.amount),
|
|
228
|
+
priceUsd: dest.priceUsd,
|
|
229
|
+
}),
|
|
230
|
+
}));
|
|
231
|
+
},
|
|
232
|
+
updateDestinationAmount: (index, amount) => {
|
|
233
|
+
set((state) => ({
|
|
234
|
+
destinations: state.destinations.map((dest, idx) => idx === index
|
|
235
|
+
? {
|
|
236
|
+
asset: String(dest.asset),
|
|
237
|
+
contact: String(dest.contact),
|
|
238
|
+
amount: String(amount),
|
|
239
|
+
priceUsd: dest.priceUsd,
|
|
240
|
+
}
|
|
241
|
+
: {
|
|
242
|
+
asset: String(dest.asset),
|
|
243
|
+
contact: String(dest.contact),
|
|
244
|
+
amount: String(dest.amount),
|
|
245
|
+
priceUsd: dest.priceUsd,
|
|
246
|
+
}),
|
|
247
|
+
}));
|
|
248
|
+
},
|
|
249
|
+
setIsDraggingSlider: (isDragging) => set({ isDraggingSlider: isDragging }),
|
|
250
|
+
// Computed functions
|
|
251
|
+
getCanAddOutput: () => {
|
|
252
|
+
return get().splits.length < OUTPUT_LIMIT;
|
|
253
|
+
},
|
|
254
|
+
getHasMultipleOutputs: () => {
|
|
255
|
+
return get().splits.length > 1;
|
|
256
|
+
},
|
|
257
|
+
}), {
|
|
258
|
+
name: 'swap-store',
|
|
259
|
+
storage: createJSONStorage(() => localStorage),
|
|
260
|
+
// Persist privacy mode, tokenIn, and inputAmount
|
|
261
|
+
partialize: (state) => ({
|
|
262
|
+
privacyEnabled: state.privacyEnabled,
|
|
263
|
+
tokenInCaip19: state.tokenIn ? state.tokenIn.caip19 : null,
|
|
264
|
+
inputAmount: state.inputAmount,
|
|
265
|
+
usdInputMode: state.usdInputMode,
|
|
266
|
+
}),
|
|
267
|
+
merge: (persistedState, currentState) => {
|
|
268
|
+
// Helper to restore AssetInfo from CAIP19
|
|
269
|
+
const restoreAsset = (caip19) => {
|
|
270
|
+
if (!caip19)
|
|
271
|
+
return null;
|
|
272
|
+
return getAssetByCaip19(caip19) || null;
|
|
273
|
+
};
|
|
274
|
+
return {
|
|
275
|
+
...currentState,
|
|
276
|
+
// Restore persisted fields
|
|
277
|
+
usdInputMode: persistedState?.usdInputMode ?? currentState.usdInputMode,
|
|
278
|
+
privacyEnabled: persistedState?.privacyEnabled ?? currentState.privacyEnabled,
|
|
279
|
+
tokenIn: persistedState?.tokenInCaip19
|
|
280
|
+
? restoreAsset(persistedState.tokenInCaip19) || currentState.tokenIn
|
|
281
|
+
: currentState.tokenIn,
|
|
282
|
+
inputAmount: persistedState?.inputAmount ?? currentState.inputAmount,
|
|
283
|
+
};
|
|
284
|
+
},
|
|
285
|
+
}));
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import type { Hex, WalletClient } from 'viem';
|
|
2
|
+
import type { Connector } from 'wagmi';
|
|
3
|
+
import type { BridgeProvider, BridgeQuote, BridgeStatus, OrderResponse } from '@silentswap/sdk';
|
|
4
|
+
import type { SolanaWalletConnector, SolanaConnection } from './silent/solana-transaction.js';
|
|
5
|
+
export interface useTransactionOptions {
|
|
6
|
+
/** User's EVM address */
|
|
7
|
+
address: `0x${string}`;
|
|
8
|
+
/** Wallet client for signing operations */
|
|
9
|
+
walletClient?: WalletClient;
|
|
10
|
+
/** Wagmi connector */
|
|
11
|
+
connector?: Connector;
|
|
12
|
+
/** Solana wallet connector (required for Solana transactions) */
|
|
13
|
+
solanaConnector?: SolanaWalletConnector;
|
|
14
|
+
/** Solana RPC connection (required for Solana transactions) */
|
|
15
|
+
solanaConnection?: SolanaConnection;
|
|
16
|
+
/** Solana RPC URL (optional, will create connection if not provided) */
|
|
17
|
+
solanaRpcUrl?: string;
|
|
18
|
+
/** Optional callback to set current step (for external state management) */
|
|
19
|
+
setCurrentStep?: (step: string) => void;
|
|
20
|
+
/** Optional status update callback */
|
|
21
|
+
onStatus?: (status: string) => void;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Transaction execution result for OrderResponse transactions
|
|
25
|
+
*/
|
|
26
|
+
export interface SwapTransaction {
|
|
27
|
+
hash: Hex;
|
|
28
|
+
chainId: number;
|
|
29
|
+
status: 'pending' | 'confirmed' | 'failed';
|
|
30
|
+
confirmations?: number;
|
|
31
|
+
}
|
|
32
|
+
export interface useTransactionReturn {
|
|
33
|
+
isLoading: boolean;
|
|
34
|
+
currentStep: string;
|
|
35
|
+
error: Error | null;
|
|
36
|
+
/** Execute a bridge transaction from a BridgeQuote */
|
|
37
|
+
executeTransaction: (quote: BridgeQuote) => Promise<BridgeStatus | null>;
|
|
38
|
+
/** Execute a deposit transaction from an OrderResponse */
|
|
39
|
+
executeSwapTransaction: (orderResponse: OrderResponse) => Promise<SwapTransaction>;
|
|
40
|
+
/** Approve token spending for a given allowance target */
|
|
41
|
+
approveTokenSpending: (chainId: number, tokenAddress: `0x${string}`, allowanceTarget: `0x${string}`, amount: string, userAddress: `0x${string}`) => Promise<Hex | null>;
|
|
42
|
+
/** Get bridge status for a request */
|
|
43
|
+
getStatus: (requestId: string, provider: BridgeProvider) => Promise<BridgeStatus | null>;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* React hook for executing transactions
|
|
47
|
+
*
|
|
48
|
+
* This hook provides transaction execution for:
|
|
49
|
+
* - Bridge quotes from any provider (relay.link, deBridge)
|
|
50
|
+
* - SilentSwap deposit transactions (OrderResponse)
|
|
51
|
+
* - Token approvals for ERC-20 tokens
|
|
52
|
+
*
|
|
53
|
+
* It handles chain switching and transaction sending automatically for all transaction types.
|
|
54
|
+
*
|
|
55
|
+
* @param options - Configuration options for transaction execution
|
|
56
|
+
* @returns Object with transaction execution state and methods
|
|
57
|
+
*
|
|
58
|
+
* @example
|
|
59
|
+
* ```typescript
|
|
60
|
+
* import { useTransaction, useQuote } from '@silentswap/react';
|
|
61
|
+
* import { useWalletClient, useAccount } from 'wagmi';
|
|
62
|
+
*
|
|
63
|
+
* function BridgeComponent() {
|
|
64
|
+
* const { address, connector } = useAccount();
|
|
65
|
+
* const { data: walletClient } = useWalletClient();
|
|
66
|
+
*
|
|
67
|
+
* // Get the best quote from all providers
|
|
68
|
+
* const { getQuote: getBestQuote } = useQuote({ address });
|
|
69
|
+
*
|
|
70
|
+
* // Execute the transaction
|
|
71
|
+
* const {
|
|
72
|
+
* executeTransaction,
|
|
73
|
+
* getStatus,
|
|
74
|
+
* isLoading,
|
|
75
|
+
* currentStep,
|
|
76
|
+
* error
|
|
77
|
+
* } = useTransaction({
|
|
78
|
+
* address: address!,
|
|
79
|
+
* walletClient,
|
|
80
|
+
* connector,
|
|
81
|
+
* });
|
|
82
|
+
*
|
|
83
|
+
* const handleBridge = async () => {
|
|
84
|
+
* // Get the best quote from all providers
|
|
85
|
+
* const quote = await getBestQuote({
|
|
86
|
+
* srcChainId: 1, // Ethereum
|
|
87
|
+
* dstChainId: 43114, // Avalanche
|
|
88
|
+
* srcToken: '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48', // USDC
|
|
89
|
+
* dstToken: '0xB97EF9Ef8734C71904D8002F8b6Bc66Dd9c48a6E', // USDC.e
|
|
90
|
+
* amount: '1000000', // 1 USDC
|
|
91
|
+
* recipient: address!,
|
|
92
|
+
* });
|
|
93
|
+
*
|
|
94
|
+
* if (quote) {
|
|
95
|
+
* // Execute the bridge transaction
|
|
96
|
+
* const result = await executeTransaction(quote);
|
|
97
|
+
* console.log('Bridge completed:', result);
|
|
98
|
+
*
|
|
99
|
+
* // Check status if needed
|
|
100
|
+
* if (result?.requestId) {
|
|
101
|
+
* const status = await getStatus(result.requestId, quote.provider);
|
|
102
|
+
* console.log('Bridge status:', status);
|
|
103
|
+
* }
|
|
104
|
+
* }
|
|
105
|
+
* };
|
|
106
|
+
*
|
|
107
|
+
* return (
|
|
108
|
+
* <div>
|
|
109
|
+
* {isLoading && <div>{currentStep}...</div>}
|
|
110
|
+
* {error && <div>Error: {error.message}</div>}
|
|
111
|
+
* <button onClick={handleBridge} disabled={isLoading}>
|
|
112
|
+
* Bridge Assets
|
|
113
|
+
* </button>
|
|
114
|
+
* </div>
|
|
115
|
+
* );
|
|
116
|
+
* }
|
|
117
|
+
* ```
|
|
118
|
+
*/
|
|
119
|
+
export declare function useTransaction({ walletClient, connector, solanaConnector, solanaConnection, solanaRpcUrl, setCurrentStep: externalSetCurrentStep, onStatus: externalOnStatus, }: useTransactionOptions): useTransactionReturn;
|