@naculus/connect-appkit-react 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/api-extractor.json +21 -0
- package/dist/client.cjs +2 -0
- package/dist/client.cjs.map +1 -0
- package/dist/client.js +2 -0
- package/dist/client.js.map +1 -0
- package/dist/index.cjs +2 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -0
- package/package.json +39 -0
- package/src/chain-abstraction/index.ts +8 -0
- package/src/chain-abstraction/useCompareCosts.ts +137 -0
- package/src/chain-abstraction/useExecuteRoute.ts +129 -0
- package/src/chain-abstraction/useRouteQuote.ts +168 -0
- package/src/client.test.ts +242 -0
- package/src/client.ts +265 -0
- package/src/hooks/useAccount.test.ts +67 -0
- package/src/hooks/useAccount.ts +37 -0
- package/src/hooks/useBalance.test.ts +459 -0
- package/src/hooks/useBalance.ts +170 -0
- package/src/hooks/useChain.test.ts +160 -0
- package/src/hooks/useChain.ts +53 -0
- package/src/hooks/useConnect.test.ts +112 -0
- package/src/hooks/useConnect.ts +29 -0
- package/src/hooks/useDisconnect.test.ts +75 -0
- package/src/hooks/useDisconnect.ts +32 -0
- package/src/hooks/useERC20Allowance.ts +93 -0
- package/src/hooks/useERC20Approve.ts +220 -0
- package/src/hooks/useERC20Transfer.ts +134 -0
- package/src/hooks/useERC20TransferSimulation.ts +147 -0
- package/src/hooks/useEmbeddedWallet.test.ts +266 -0
- package/src/hooks/useEmbeddedWallet.ts +181 -0
- package/src/hooks/useLastTx.test.ts +117 -0
- package/src/hooks/useLastTx.ts +73 -0
- package/src/hooks/useLookupAddress.ts +131 -0
- package/src/hooks/useNotification.ts +308 -0
- package/src/hooks/useResolveName.ts +128 -0
- package/src/hooks/useSIWxLogin.test.ts +307 -0
- package/src/hooks/useSIWxLogin.ts +163 -0
- package/src/hooks/useSIWxSession.test.ts +481 -0
- package/src/hooks/useSIWxSession.ts +290 -0
- package/src/hooks/useSendCalls.ts +103 -0
- package/src/hooks/useSendTransaction.test.ts +175 -0
- package/src/hooks/useSendTransaction.ts +80 -0
- package/src/hooks/useSendUserOperation.ts +313 -0
- package/src/hooks/useSession.ts +96 -0
- package/src/hooks/useSessionKeys.ts +356 -0
- package/src/hooks/useSignInWithEthereum.test.ts +224 -0
- package/src/hooks/useSignInWithEthereum.ts +46 -0
- package/src/hooks/useSignInWithX.test.ts +210 -0
- package/src/hooks/useSignInWithX.ts +141 -0
- package/src/hooks/useSignMessage.test.ts +151 -0
- package/src/hooks/useSignMessage.ts +65 -0
- package/src/hooks/useSimulateTransfer.ts +165 -0
- package/src/hooks/useSiwxAuthSession.test.ts +456 -0
- package/src/hooks/useSiwxAuthSession.ts +163 -0
- package/src/hooks/useSmartAccount.ts +194 -0
- package/src/hooks/useSwitchChain.ts +68 -0
- package/src/hooks/useTokenBalance.test.ts +337 -0
- package/src/hooks/useTokenBalance.ts +210 -0
- package/src/hooks/useTransactionSimulation.ts +401 -0
- package/src/hooks/useTxHistory.test.ts +108 -0
- package/src/hooks/useTxHistory.ts +83 -0
- package/src/hooks/useTxMonitor.test.ts +127 -0
- package/src/hooks/useTxMonitor.ts +178 -0
- package/src/hooks/useUserOpStatus.ts +245 -0
- package/src/hooks/useValidateDestination.test.ts +96 -0
- package/src/hooks/useValidateDestination.ts +72 -0
- package/src/hooks/useViemClient.test.ts +155 -0
- package/src/hooks/useViemClient.ts +65 -0
- package/src/hooks/useWallet.test.ts +133 -0
- package/src/hooks/useWallet.ts +39 -0
- package/src/hooks/useWeb3ErrorHandler.ts +102 -0
- package/src/index.ts +186 -0
- package/src/provider/Web3ConnectProvider.test.tsx +178 -0
- package/src/provider/Web3ConnectProvider.tsx +530 -0
- package/src/token-list/TokenSelector.tsx +351 -0
- package/src/token-list/defaults.ts +39 -0
- package/src/token-list/index.ts +14 -0
- package/src/token-list/useTokenList.ts +119 -0
- package/src/token-list/useTokenSearch.ts +99 -0
- package/src/types/index.ts +99 -0
- package/src/utils/chains.ts +115 -0
- package/src/utils/errorMessages.ts +142 -0
- package/src/utils/i18n.ts +90 -0
- package/src/wc-generated/components.ts +377 -0
- package/test/setup.ts +17 -0
- package/test/test-utils.tsx +78 -0
- package/tsup.config.ts +18 -0
package/package.json
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@naculus/connect-appkit-react",
|
|
3
|
+
"version": "0.1.2",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"main": "./dist/index.js",
|
|
6
|
+
"types": "./dist/index.d.ts",
|
|
7
|
+
"dependencies": {
|
|
8
|
+
"@stencil/react-output-target": "^1.6.1",
|
|
9
|
+
"viem": "^2.54.6",
|
|
10
|
+
"@naculus/connect-core": "^0.1.2",
|
|
11
|
+
"@naculus/connector-walletconnect": "^0.1.2",
|
|
12
|
+
"@naculus/connector-evm-injected": "^0.1.2",
|
|
13
|
+
"@naculus/connector-embedded": "^0.1.2",
|
|
14
|
+
"@naculus/connector-passkeys": "^0.1.2",
|
|
15
|
+
"@naculus/connector-solana": "^0.1.2",
|
|
16
|
+
"@naculus/siwx": "^0.1.2",
|
|
17
|
+
"@naculus/wallet-engine": "^0.1.2",
|
|
18
|
+
"@naculus/connect-appkit-wc": "0.1.2"
|
|
19
|
+
},
|
|
20
|
+
"peerDependencies": {
|
|
21
|
+
"react": "^18.0.0 || ^19.0.0"
|
|
22
|
+
},
|
|
23
|
+
"publishConfig": {
|
|
24
|
+
"access": "public"
|
|
25
|
+
},
|
|
26
|
+
"devDependencies": {
|
|
27
|
+
"@testing-library/react": "^16.3.0",
|
|
28
|
+
"@types/react": "^19.0.0",
|
|
29
|
+
"jsdom": "^25.0.0 || ^26.0.0 || ^28.0.0",
|
|
30
|
+
"react": "^19.0.0",
|
|
31
|
+
"react-dom": "^19.0.0",
|
|
32
|
+
"tsup": "^8.3.6",
|
|
33
|
+
"typescript": "^5.9.3"
|
|
34
|
+
},
|
|
35
|
+
"scripts": {
|
|
36
|
+
"build": "tsup",
|
|
37
|
+
"test": "vitest run"
|
|
38
|
+
}
|
|
39
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export { useRouteQuote } from "./useRouteQuote";
|
|
2
|
+
export type { UseRouteQuoteInput, UseRouteQuoteReturn, Quote, QuoteOptions } from "./useRouteQuote";
|
|
3
|
+
|
|
4
|
+
export { useExecuteRoute } from "./useExecuteRoute";
|
|
5
|
+
export type { UseExecuteRouteReturn, ExecuteError, ExecuteRouteResult, ExecuteOptions } from "./useExecuteRoute";
|
|
6
|
+
|
|
7
|
+
export { useCompareCosts } from "./useCompareCosts";
|
|
8
|
+
export type { UseCompareCostsInput, UseCompareCostsReturn, CostComparison, CostComparisonOperation, CostComparisonOptions } from "./useCompareCosts";
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* useCompareCosts — React hook for comparing operation costs across chains.
|
|
3
|
+
*
|
|
4
|
+
* Provides reactive state for comparing gas costs, fees, and estimated
|
|
5
|
+
* times for a given operation across multiple chains.
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* ```tsx
|
|
9
|
+
* import { useCompareCosts } from "@naculus/connect-appkit-react";
|
|
10
|
+
*
|
|
11
|
+
* function CostComparisonPanel() {
|
|
12
|
+
* const { comparisons, loading, error, refresh } = useCompareCosts({
|
|
13
|
+
* operation: "send_erc20",
|
|
14
|
+
* chains: ["eip155:1", "eip155:137", "eip155:10"],
|
|
15
|
+
* });
|
|
16
|
+
*
|
|
17
|
+
* if (loading) return <div>Loading...</div>;
|
|
18
|
+
*
|
|
19
|
+
* return (
|
|
20
|
+
* <div>
|
|
21
|
+
* {comparisons.map(c => (
|
|
22
|
+
* <div key={c.chain}>
|
|
23
|
+
* {c.chainName}: ${c.totalCost} (gas: ${c.gasCost})
|
|
24
|
+
* </div>
|
|
25
|
+
* ))}
|
|
26
|
+
* </div>
|
|
27
|
+
* );
|
|
28
|
+
* }
|
|
29
|
+
* ```
|
|
30
|
+
*/
|
|
31
|
+
|
|
32
|
+
import { useState, useEffect, useCallback, useRef } from "react";
|
|
33
|
+
// ── Local type definitions (hook domain types, not core exports) ──────
|
|
34
|
+
|
|
35
|
+
export interface CostComparison {
|
|
36
|
+
chain: string;
|
|
37
|
+
chainName: string;
|
|
38
|
+
totalCost: string;
|
|
39
|
+
gasCost: string;
|
|
40
|
+
estimatedTimeMs: number;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export type CostComparisonOperation = "send_erc20" | "swap" | "bridge" | (string & {});
|
|
44
|
+
|
|
45
|
+
export interface CostComparisonOptions {
|
|
46
|
+
amount?: string;
|
|
47
|
+
token?: string;
|
|
48
|
+
[key: string]: unknown;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
export interface UseCompareCostsInput {
|
|
53
|
+
operation: CostComparisonOperation;
|
|
54
|
+
chains: string[];
|
|
55
|
+
options?: CostComparisonOptions;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export interface UseCompareCostsReturn {
|
|
59
|
+
/** Sorted cost comparisons (cheapest first) */
|
|
60
|
+
comparisons: CostComparison[];
|
|
61
|
+
/** Whether a comparison request is in flight */
|
|
62
|
+
loading: boolean;
|
|
63
|
+
/** Error that occurred during the last request */
|
|
64
|
+
error: Error | null;
|
|
65
|
+
/** Manually trigger a refresh */
|
|
66
|
+
refresh: () => Promise<void>;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Hook for comparing operation costs across multiple chains.
|
|
71
|
+
*
|
|
72
|
+
* Automatically re-fetches when input parameters change.
|
|
73
|
+
* Helpful for chain selection UIs ("show me the cheapest chain").
|
|
74
|
+
*
|
|
75
|
+
* @param input - Comparison parameters
|
|
76
|
+
* @param compareCostsFn - Function that performs the actual comparison
|
|
77
|
+
*/
|
|
78
|
+
export function useCompareCosts(
|
|
79
|
+
input: UseCompareCostsInput,
|
|
80
|
+
compareCostsFn?: (
|
|
81
|
+
operation: string,
|
|
82
|
+
chains: string[],
|
|
83
|
+
options?: CostComparisonOptions,
|
|
84
|
+
) => Promise<CostComparison[]>,
|
|
85
|
+
): UseCompareCostsReturn {
|
|
86
|
+
const { operation, chains, options } = input;
|
|
87
|
+
const [comparisons, setComparisons] = useState<CostComparison[]>([]);
|
|
88
|
+
const [loading, setLoading] = useState(false);
|
|
89
|
+
const [error, setError] = useState<Error | null>(null);
|
|
90
|
+
const mountedRef = useRef(true);
|
|
91
|
+
|
|
92
|
+
const fetchComparisons = useCallback(async () => {
|
|
93
|
+
if (!chains || chains.length === 0) {
|
|
94
|
+
setComparisons([]);
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
if (!compareCostsFn) return;
|
|
99
|
+
|
|
100
|
+
setLoading(true);
|
|
101
|
+
setError(null);
|
|
102
|
+
|
|
103
|
+
try {
|
|
104
|
+
const result = await compareCostsFn(operation, chains, options);
|
|
105
|
+
|
|
106
|
+
if (mountedRef.current) {
|
|
107
|
+
setComparisons(result);
|
|
108
|
+
}
|
|
109
|
+
} catch (err) {
|
|
110
|
+
if (mountedRef.current) {
|
|
111
|
+
setError(err instanceof Error ? err : new Error(String(err)));
|
|
112
|
+
setComparisons([]);
|
|
113
|
+
}
|
|
114
|
+
} finally {
|
|
115
|
+
if (mountedRef.current) {
|
|
116
|
+
setLoading(false);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}, [operation, chains, options, compareCostsFn]);
|
|
120
|
+
|
|
121
|
+
useEffect(() => {
|
|
122
|
+
fetchComparisons();
|
|
123
|
+
}, [fetchComparisons]);
|
|
124
|
+
|
|
125
|
+
useEffect(() => {
|
|
126
|
+
mountedRef.current = true;
|
|
127
|
+
return () => {
|
|
128
|
+
mountedRef.current = false;
|
|
129
|
+
};
|
|
130
|
+
}, []);
|
|
131
|
+
|
|
132
|
+
const refresh = useCallback(async () => {
|
|
133
|
+
await fetchComparisons();
|
|
134
|
+
}, [fetchComparisons]);
|
|
135
|
+
|
|
136
|
+
return { comparisons, loading, error, refresh };
|
|
137
|
+
}
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* useExecuteRoute — React hook for executing cross-chain routes.
|
|
3
|
+
*
|
|
4
|
+
* Manages the execution lifecycle of a cross-chain transfer.
|
|
5
|
+
* Tracks execution status, bridge reference, and completion state.
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* ```tsx
|
|
9
|
+
* import { useExecuteRoute } from "@naculus/connect-appkit-react";
|
|
10
|
+
*
|
|
11
|
+
* function RouteExecutionPanel({ quote }) {
|
|
12
|
+
* const { execute, executing, result, error } = useExecuteRoute();
|
|
13
|
+
*
|
|
14
|
+
* return (
|
|
15
|
+
* <div>
|
|
16
|
+
* <button onClick={() => execute(quote, "0xRecipient")} disabled={executing}>
|
|
17
|
+
* {executing ? "Executing..." : "Execute Route"}
|
|
18
|
+
* </button>
|
|
19
|
+
* {result && <div>Tx: {result.fromTxHash}</div>}
|
|
20
|
+
* {error && <div>Error: {error.message}</div>}
|
|
21
|
+
* </div>
|
|
22
|
+
* );
|
|
23
|
+
* }
|
|
24
|
+
* ```
|
|
25
|
+
*/
|
|
26
|
+
|
|
27
|
+
import { useState, useCallback, useRef } from "react";
|
|
28
|
+
// ── Local type definitions (hook domain types, not core exports) ──────
|
|
29
|
+
|
|
30
|
+
export interface Quote {
|
|
31
|
+
routeId: string;
|
|
32
|
+
provider: string;
|
|
33
|
+
totalCost?: bigint;
|
|
34
|
+
estimatedTimeMs?: number;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export interface ExecuteRouteResult {
|
|
38
|
+
fromTxHash: string;
|
|
39
|
+
toTxHash?: string;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export interface ExecuteOptions {
|
|
43
|
+
timeoutMs?: number;
|
|
44
|
+
gasLimit?: bigint;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export interface UseExecuteRouteReturn {
|
|
48
|
+
/** Execute a route (call this with the selected quote) */
|
|
49
|
+
execute: (quote: Quote, recipient: string, options?: ExecuteOptions) => Promise<void>;
|
|
50
|
+
/** Whether execution is in progress */
|
|
51
|
+
executing: boolean;
|
|
52
|
+
/** The execution result (populated after successful execution) */
|
|
53
|
+
result: ExecuteRouteResult | null;
|
|
54
|
+
/** Error that occurred during execution */
|
|
55
|
+
error: ExecuteError | null;
|
|
56
|
+
/** Reset execution state back to initial */
|
|
57
|
+
reset: () => void;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export interface ExecuteError {
|
|
61
|
+
code: string;
|
|
62
|
+
message: string;
|
|
63
|
+
details?: Record<string, unknown>;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Hook for executing a cross-chain route.
|
|
68
|
+
*
|
|
69
|
+
* Manages the full lifecycle from calling executeRoute to
|
|
70
|
+
* tracking the result. For auto-approve flows, handles the
|
|
71
|
+
* approve → cross-chain sequence.
|
|
72
|
+
*
|
|
73
|
+
* @param executeRouteFn - Function that performs the actual route execution
|
|
74
|
+
*/
|
|
75
|
+
export function useExecuteRoute(
|
|
76
|
+
executeRouteFn?: (quote: Quote, recipient: string, options?: ExecuteOptions) => Promise<ExecuteRouteResult>,
|
|
77
|
+
): UseExecuteRouteReturn {
|
|
78
|
+
const [executing, setExecuting] = useState(false);
|
|
79
|
+
const [result, setResult] = useState<ExecuteRouteResult | null>(null);
|
|
80
|
+
const [error, setError] = useState<ExecuteError | null>(null);
|
|
81
|
+
const mountedRef = useRef(true);
|
|
82
|
+
|
|
83
|
+
const execute = useCallback(async (
|
|
84
|
+
quote: Quote,
|
|
85
|
+
recipient: string,
|
|
86
|
+
options?: ExecuteOptions,
|
|
87
|
+
) => {
|
|
88
|
+
if (!executeRouteFn) {
|
|
89
|
+
setError({
|
|
90
|
+
code: "no_executor",
|
|
91
|
+
message: "No executeRoute function provided",
|
|
92
|
+
});
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
setExecuting(true);
|
|
97
|
+
setError(null);
|
|
98
|
+
setResult(null);
|
|
99
|
+
|
|
100
|
+
try {
|
|
101
|
+
const execResult = await executeRouteFn(quote, recipient, options);
|
|
102
|
+
|
|
103
|
+
if (mountedRef.current) {
|
|
104
|
+
setResult(execResult);
|
|
105
|
+
}
|
|
106
|
+
} catch (err) {
|
|
107
|
+
if (mountedRef.current) {
|
|
108
|
+
const executionError = err as Error;
|
|
109
|
+
setError({
|
|
110
|
+
code: (executionError as any).code ?? "execution_failed",
|
|
111
|
+
message: executionError.message,
|
|
112
|
+
details: (executionError as any).details,
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
} finally {
|
|
116
|
+
if (mountedRef.current) {
|
|
117
|
+
setExecuting(false);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
}, [executeRouteFn]);
|
|
121
|
+
|
|
122
|
+
const reset = useCallback(() => {
|
|
123
|
+
setResult(null);
|
|
124
|
+
setError(null);
|
|
125
|
+
setExecuting(false);
|
|
126
|
+
}, []);
|
|
127
|
+
|
|
128
|
+
return { execute, executing, result, error, reset };
|
|
129
|
+
}
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* useRouteQuote — React hook for cross-chain route quoting.
|
|
3
|
+
*
|
|
4
|
+
* Provides reactive state for discovering routes and getting quotes
|
|
5
|
+
* from the RouteEngine. Returns loading, error, and result states.
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* ```tsx
|
|
9
|
+
* import { useRouteQuote } from "@naculus/connect-appkit-react";
|
|
10
|
+
*
|
|
11
|
+
* function QuotePanel() {
|
|
12
|
+
* const { quotes, loading, error, refresh } = useRouteQuote({
|
|
13
|
+
* fromChain: "eip155:1",
|
|
14
|
+
* toChain: "eip155:137",
|
|
15
|
+
* fromToken: "USDC",
|
|
16
|
+
* amount: "1000000",
|
|
17
|
+
* });
|
|
18
|
+
*
|
|
19
|
+
* if (loading) return <div>Loading quotes...</div>;
|
|
20
|
+
* if (error) return <div>Error: {error.message}</div>;
|
|
21
|
+
*
|
|
22
|
+
* return (
|
|
23
|
+
* <div>
|
|
24
|
+
* {quotes.map(q => (
|
|
25
|
+
* <div key={q.routeId}>
|
|
26
|
+
* {q.provider} — {q.netReceiveFormatted} {q.toTokenSymbol}
|
|
27
|
+
* </div>
|
|
28
|
+
* ))}
|
|
29
|
+
* </div>
|
|
30
|
+
* );
|
|
31
|
+
* }
|
|
32
|
+
* ```
|
|
33
|
+
*/
|
|
34
|
+
|
|
35
|
+
import { useState, useEffect, useCallback, useRef } from "react";
|
|
36
|
+
// ── Local type definitions (hook domain types, not core exports) ──────
|
|
37
|
+
|
|
38
|
+
export interface Quote {
|
|
39
|
+
routeId: string;
|
|
40
|
+
provider: string;
|
|
41
|
+
netReceiveFormatted: string;
|
|
42
|
+
toTokenSymbol: string;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export interface QuoteOptions {
|
|
46
|
+
sortBy?: "cost" | "time" | "balance";
|
|
47
|
+
maxSlippage?: number;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export interface UseRouteQuoteInput {
|
|
51
|
+
fromChain: string;
|
|
52
|
+
toChain: string;
|
|
53
|
+
fromToken: string;
|
|
54
|
+
toToken?: string;
|
|
55
|
+
amount: string;
|
|
56
|
+
options?: QuoteOptions;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export interface UseRouteQuoteReturn {
|
|
60
|
+
/** Current list of quotes (sorted by options.sortBy) */
|
|
61
|
+
quotes: Quote[];
|
|
62
|
+
/** Whether a quote request is in flight */
|
|
63
|
+
loading: boolean;
|
|
64
|
+
/** Error that occurred during the last request */
|
|
65
|
+
error: Error | null;
|
|
66
|
+
/** Manually trigger a refresh (re-fetches quotes) */
|
|
67
|
+
refresh: () => Promise<void>;
|
|
68
|
+
/** Clear the current quotes and error state */
|
|
69
|
+
clear: () => void;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Hook for fetching cross-chain route quotes.
|
|
74
|
+
*
|
|
75
|
+
* Automatically re-fetches when input parameters change.
|
|
76
|
+
* Debounces rapid changes to avoid excessive API calls.
|
|
77
|
+
*
|
|
78
|
+
* @param input - Quote request parameters
|
|
79
|
+
* @param getQuoteFn - Function that performs the actual quote fetch
|
|
80
|
+
*/
|
|
81
|
+
export function useRouteQuote(
|
|
82
|
+
input: UseRouteQuoteInput,
|
|
83
|
+
getQuoteFn?: (from: string, to: string, token: string, amount: string, options?: QuoteOptions) => Promise<Quote[]>,
|
|
84
|
+
): UseRouteQuoteReturn {
|
|
85
|
+
const { fromChain, toChain, fromToken, toToken, amount, options } = input;
|
|
86
|
+
const [quotes, setQuotes] = useState<Quote[]>([]);
|
|
87
|
+
const [loading, setLoading] = useState(false);
|
|
88
|
+
const [error, setError] = useState<Error | null>(null);
|
|
89
|
+
const debounceTimer = useRef<ReturnType<typeof setTimeout> | null>(null);
|
|
90
|
+
const mountedRef = useRef(true);
|
|
91
|
+
|
|
92
|
+
const fetchQuotes = useCallback(async () => {
|
|
93
|
+
if (!fromChain || !toChain || !fromToken || !amount || BigInt(amount) <= 0n) {
|
|
94
|
+
setQuotes([]);
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
if (!getQuoteFn) return;
|
|
99
|
+
|
|
100
|
+
setLoading(true);
|
|
101
|
+
setError(null);
|
|
102
|
+
|
|
103
|
+
try {
|
|
104
|
+
const result = await getQuoteFn(
|
|
105
|
+
fromChain,
|
|
106
|
+
toChain,
|
|
107
|
+
fromToken,
|
|
108
|
+
amount,
|
|
109
|
+
options,
|
|
110
|
+
);
|
|
111
|
+
|
|
112
|
+
if (mountedRef.current) {
|
|
113
|
+
setQuotes(result);
|
|
114
|
+
}
|
|
115
|
+
} catch (err) {
|
|
116
|
+
if (mountedRef.current) {
|
|
117
|
+
setError(err instanceof Error ? err : new Error(String(err)));
|
|
118
|
+
setQuotes([]);
|
|
119
|
+
}
|
|
120
|
+
} finally {
|
|
121
|
+
if (mountedRef.current) {
|
|
122
|
+
setLoading(false);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
}, [fromChain, toChain, fromToken, amount, options, getQuoteFn]);
|
|
126
|
+
|
|
127
|
+
// Debounced auto-fetch on input change
|
|
128
|
+
useEffect(() => {
|
|
129
|
+
if (debounceTimer.current) {
|
|
130
|
+
clearTimeout(debounceTimer.current);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// Don't fetch if parameters are incomplete
|
|
134
|
+
if (!fromChain || !toChain || !fromToken || !amount) {
|
|
135
|
+
setQuotes([]);
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
debounceTimer.current = setTimeout(() => {
|
|
140
|
+
fetchQuotes();
|
|
141
|
+
}, 300); // 300ms debounce
|
|
142
|
+
|
|
143
|
+
return () => {
|
|
144
|
+
if (debounceTimer.current) {
|
|
145
|
+
clearTimeout(debounceTimer.current);
|
|
146
|
+
}
|
|
147
|
+
};
|
|
148
|
+
}, [fetchQuotes, fromChain, toChain, fromToken, amount]);
|
|
149
|
+
|
|
150
|
+
useEffect(() => {
|
|
151
|
+
mountedRef.current = true;
|
|
152
|
+
return () => {
|
|
153
|
+
mountedRef.current = false;
|
|
154
|
+
};
|
|
155
|
+
}, []);
|
|
156
|
+
|
|
157
|
+
const refresh = useCallback(async () => {
|
|
158
|
+
await fetchQuotes();
|
|
159
|
+
}, [fetchQuotes]);
|
|
160
|
+
|
|
161
|
+
const clear = useCallback(() => {
|
|
162
|
+
setQuotes([]);
|
|
163
|
+
setError(null);
|
|
164
|
+
setLoading(false);
|
|
165
|
+
}, []);
|
|
166
|
+
|
|
167
|
+
return { quotes, loading, error, refresh, clear };
|
|
168
|
+
}
|