@coin-voyage/swapkit 0.1.0-beta.0 → 0.1.0-beta.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/dist/components/connected-wallets.d.ts +1 -0
- package/dist/components/connected-wallets.js +66 -0
- package/dist/components/execution-modal.d.ts +5 -0
- package/dist/components/execution-modal.js +79 -0
- package/dist/components/icons.d.ts +23 -0
- package/dist/components/icons.js +49 -0
- package/dist/components/modal.d.ts +11 -0
- package/dist/components/modal.js +40 -0
- package/dist/components/styles.d.ts +124 -0
- package/dist/components/styles.js +1239 -0
- package/dist/components/swap.d.ts +2 -0
- package/dist/components/swap.js +295 -0
- package/dist/components/token-icon.d.ts +5 -0
- package/dist/components/token-icon.js +9 -0
- package/dist/components/token-picker.d.ts +11 -0
- package/dist/components/token-picker.js +69 -0
- package/dist/components/wallet-connector.d.ts +8 -0
- package/dist/components/wallet-connector.js +33 -0
- package/dist/components/wallet-icon.d.ts +5 -0
- package/dist/components/wallet-icon.js +6 -0
- package/dist/hooks/use-order-status.d.ts +9 -0
- package/dist/hooks/use-order-status.js +36 -0
- package/dist/hooks/use-swap-execution.d.ts +7 -0
- package/dist/hooks/use-swap-execution.js +105 -0
- package/dist/hooks/use-swap-quote.d.ts +6 -0
- package/dist/hooks/use-swap-quote.js +63 -0
- package/dist/hooks/use-swap-state.d.ts +70 -0
- package/dist/hooks/use-swap-state.js +147 -0
- package/dist/hooks/use-token-data.d.ts +12 -0
- package/dist/hooks/use-token-data.js +80 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +3 -0
- package/dist/lib/amount-input.d.ts +3 -0
- package/dist/lib/amount-input.js +11 -0
- package/dist/lib/currency-selection.d.ts +13 -0
- package/dist/lib/currency-selection.js +26 -0
- package/dist/lib/execution.d.ts +38 -0
- package/dist/lib/execution.js +26 -0
- package/dist/lib/output-wallet.d.ts +24 -0
- package/dist/lib/output-wallet.js +24 -0
- package/dist/lib/quote.d.ts +13 -0
- package/dist/lib/quote.js +24 -0
- package/dist/lib/version.d.ts +1 -0
- package/dist/lib/version.js +2 -0
- package/dist/providers/swapkit-context.d.ts +10 -0
- package/dist/providers/swapkit-context.js +8 -0
- package/dist/providers/swapkit-provider.d.ts +2 -0
- package/dist/providers/swapkit-provider.js +18 -0
- package/dist/styles/root.d.ts +5 -0
- package/dist/styles/root.js +75 -0
- package/dist/styles/styled.d.ts +2 -0
- package/dist/styles/styled.js +3 -0
- package/dist/styles/theme.d.ts +3 -0
- package/dist/styles/theme.js +36 -0
- package/dist/types.d.ts +44 -0
- package/dist/types.js +1 -0
- package/package.json +4 -4
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { useEffect, useRef, useState } from "react";
|
|
2
|
+
import { useSwapKit } from "../providers/swapkit-context";
|
|
3
|
+
const QUOTE_DEBOUNCE_MS = 400;
|
|
4
|
+
const QUOTE_REFRESH_MS = 10000;
|
|
5
|
+
export function useSwapQuote(request, enabled = true) {
|
|
6
|
+
const { api } = useSwapKit();
|
|
7
|
+
const [quote, setQuote] = useState(null);
|
|
8
|
+
const [loading, setLoading] = useState(false);
|
|
9
|
+
const [error, setError] = useState(null);
|
|
10
|
+
const requestId = useRef(0);
|
|
11
|
+
useEffect(() => {
|
|
12
|
+
const id = ++requestId.current;
|
|
13
|
+
if (!request || !enabled) {
|
|
14
|
+
setLoading(false);
|
|
15
|
+
if (!request) {
|
|
16
|
+
setQuote(null);
|
|
17
|
+
setError(null);
|
|
18
|
+
}
|
|
19
|
+
return;
|
|
20
|
+
}
|
|
21
|
+
setQuote(null);
|
|
22
|
+
setError(null);
|
|
23
|
+
setLoading(true);
|
|
24
|
+
const controller = new AbortController();
|
|
25
|
+
let interval;
|
|
26
|
+
const load = async (initial) => {
|
|
27
|
+
try {
|
|
28
|
+
const response = await api.swapQuote(request, { signal: controller.signal });
|
|
29
|
+
if (requestId.current !== id)
|
|
30
|
+
return;
|
|
31
|
+
if (response.error || !response.data?.quote) {
|
|
32
|
+
setQuote(null);
|
|
33
|
+
setError(response.error?.message || "No swap route is available");
|
|
34
|
+
}
|
|
35
|
+
else {
|
|
36
|
+
setQuote(response.data.quote);
|
|
37
|
+
setError(null);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
catch (requestError) {
|
|
41
|
+
if (requestId.current !== id || requestError.name === "AbortError")
|
|
42
|
+
return;
|
|
43
|
+
setQuote(null);
|
|
44
|
+
setError("Unable to fetch a swap quote");
|
|
45
|
+
}
|
|
46
|
+
finally {
|
|
47
|
+
if (initial && requestId.current === id)
|
|
48
|
+
setLoading(false);
|
|
49
|
+
}
|
|
50
|
+
};
|
|
51
|
+
const timeout = setTimeout(() => {
|
|
52
|
+
void load(true);
|
|
53
|
+
interval = setInterval(() => void load(false), QUOTE_REFRESH_MS);
|
|
54
|
+
}, QUOTE_DEBOUNCE_MS);
|
|
55
|
+
return () => {
|
|
56
|
+
clearTimeout(timeout);
|
|
57
|
+
if (interval)
|
|
58
|
+
clearInterval(interval);
|
|
59
|
+
controller.abort();
|
|
60
|
+
};
|
|
61
|
+
}, [api, request, enabled]);
|
|
62
|
+
return { quote, loading, error };
|
|
63
|
+
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import type { ChainType, Currency, CurrencyBase } from "@coin-voyage/shared/types";
|
|
2
|
+
import type { SwapSide } from "../lib/quote";
|
|
3
|
+
export type SlippageMode = "auto" | "custom";
|
|
4
|
+
export interface ManualOutputWallet {
|
|
5
|
+
chainId: number;
|
|
6
|
+
address: string;
|
|
7
|
+
}
|
|
8
|
+
export interface SwapState {
|
|
9
|
+
sourceId?: string;
|
|
10
|
+
destinationId?: string;
|
|
11
|
+
inputAmount: string;
|
|
12
|
+
outputAmount: string;
|
|
13
|
+
activeSide: SwapSide;
|
|
14
|
+
slippageMode: SlippageMode;
|
|
15
|
+
slippagePercent: string;
|
|
16
|
+
manualOutputWallet?: ManualOutputWallet;
|
|
17
|
+
currenciesInitialized: boolean;
|
|
18
|
+
initialSourceId?: string;
|
|
19
|
+
initialDestinationId?: string;
|
|
20
|
+
initialSlippageMode: SlippageMode;
|
|
21
|
+
initialSlippagePercent: string;
|
|
22
|
+
}
|
|
23
|
+
export type SwapStateAction = {
|
|
24
|
+
type: "initialize_currencies";
|
|
25
|
+
sourceId?: string;
|
|
26
|
+
destinationId?: string;
|
|
27
|
+
} | {
|
|
28
|
+
type: "select_currency";
|
|
29
|
+
side: SwapSide;
|
|
30
|
+
id: string;
|
|
31
|
+
} | {
|
|
32
|
+
type: "set_amount";
|
|
33
|
+
side: SwapSide;
|
|
34
|
+
value: string;
|
|
35
|
+
} | {
|
|
36
|
+
type: "reverse";
|
|
37
|
+
inputAmount: string;
|
|
38
|
+
outputAmount: string;
|
|
39
|
+
} | {
|
|
40
|
+
type: "set_slippage_mode";
|
|
41
|
+
mode: SlippageMode;
|
|
42
|
+
} | {
|
|
43
|
+
type: "set_slippage";
|
|
44
|
+
value: string;
|
|
45
|
+
} | {
|
|
46
|
+
type: "set_manual_output_wallet";
|
|
47
|
+
wallet?: ManualOutputWallet;
|
|
48
|
+
} | {
|
|
49
|
+
type: "reset";
|
|
50
|
+
};
|
|
51
|
+
export declare function createSwapState(defaultSlippageBps?: number): SwapState;
|
|
52
|
+
export declare function swapStateReducer(state: SwapState, action: SwapStateAction): SwapState;
|
|
53
|
+
interface UseSwapStateOptions {
|
|
54
|
+
currencies: Currency[];
|
|
55
|
+
connectedChainTypes: ReadonlySet<ChainType>;
|
|
56
|
+
defaultSourceCurrency?: CurrencyBase;
|
|
57
|
+
defaultDestinationCurrency?: CurrencyBase;
|
|
58
|
+
defaultSlippageBps?: number;
|
|
59
|
+
}
|
|
60
|
+
export declare function useSwapState({ currencies, connectedChainTypes, defaultSourceCurrency, defaultDestinationCurrency, defaultSlippageBps, }: UseSwapStateOptions): {
|
|
61
|
+
state: SwapState;
|
|
62
|
+
setAmount: (side: SwapSide, value: string) => void;
|
|
63
|
+
selectCurrency: (side: SwapSide, id: string) => void;
|
|
64
|
+
reverse: (inputAmount: string, outputAmount: string) => void;
|
|
65
|
+
setSlippageMode: (mode: SlippageMode) => void;
|
|
66
|
+
setSlippagePercent: (value: string) => void;
|
|
67
|
+
setManualOutputWallet: (wallet?: ManualOutputWallet) => void;
|
|
68
|
+
reset: () => void;
|
|
69
|
+
};
|
|
70
|
+
export {};
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
import { useCallback, useEffect, useReducer, useState } from "react";
|
|
2
|
+
import { resolveCurrencySelection } from "../lib/currency-selection";
|
|
3
|
+
const DEFAULT_CUSTOM_SLIPPAGE_BPS = 100;
|
|
4
|
+
function slippagePercent(defaultSlippageBps) {
|
|
5
|
+
if (!Number.isFinite(defaultSlippageBps) ||
|
|
6
|
+
!Number.isInteger(defaultSlippageBps) ||
|
|
7
|
+
defaultSlippageBps < 0 ||
|
|
8
|
+
defaultSlippageBps > 10000) {
|
|
9
|
+
throw new RangeError("defaultSlippageBps must be an integer between 0 and 10000");
|
|
10
|
+
}
|
|
11
|
+
return String(defaultSlippageBps / 100);
|
|
12
|
+
}
|
|
13
|
+
export function createSwapState(defaultSlippageBps) {
|
|
14
|
+
const initialSlippageMode = defaultSlippageBps === undefined ? "auto" : "custom";
|
|
15
|
+
const initialSlippagePercent = slippagePercent(defaultSlippageBps ?? DEFAULT_CUSTOM_SLIPPAGE_BPS);
|
|
16
|
+
return {
|
|
17
|
+
inputAmount: "",
|
|
18
|
+
outputAmount: "",
|
|
19
|
+
activeSide: "input",
|
|
20
|
+
slippageMode: initialSlippageMode,
|
|
21
|
+
slippagePercent: initialSlippagePercent,
|
|
22
|
+
currenciesInitialized: false,
|
|
23
|
+
initialSlippageMode,
|
|
24
|
+
initialSlippagePercent,
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
export function swapStateReducer(state, action) {
|
|
28
|
+
switch (action.type) {
|
|
29
|
+
case "initialize_currencies":
|
|
30
|
+
if (state.currenciesInitialized)
|
|
31
|
+
return state;
|
|
32
|
+
return {
|
|
33
|
+
...state,
|
|
34
|
+
sourceId: action.sourceId,
|
|
35
|
+
destinationId: action.destinationId,
|
|
36
|
+
initialSourceId: action.sourceId,
|
|
37
|
+
initialDestinationId: action.destinationId,
|
|
38
|
+
currenciesInitialized: true,
|
|
39
|
+
};
|
|
40
|
+
case "select_currency":
|
|
41
|
+
return {
|
|
42
|
+
...state,
|
|
43
|
+
sourceId: action.side === "input" ? action.id : state.sourceId,
|
|
44
|
+
destinationId: action.side === "output" ? action.id : state.destinationId,
|
|
45
|
+
inputAmount: "",
|
|
46
|
+
outputAmount: "",
|
|
47
|
+
manualOutputWallet: action.side === "output" ? undefined : state.manualOutputWallet,
|
|
48
|
+
};
|
|
49
|
+
case "set_amount":
|
|
50
|
+
return {
|
|
51
|
+
...state,
|
|
52
|
+
activeSide: action.side,
|
|
53
|
+
inputAmount: action.side === "input" ? action.value : state.inputAmount,
|
|
54
|
+
outputAmount: action.side === "output" ? action.value : state.outputAmount,
|
|
55
|
+
};
|
|
56
|
+
case "reverse":
|
|
57
|
+
return {
|
|
58
|
+
...state,
|
|
59
|
+
sourceId: state.destinationId,
|
|
60
|
+
destinationId: state.sourceId,
|
|
61
|
+
inputAmount: action.outputAmount,
|
|
62
|
+
outputAmount: action.inputAmount,
|
|
63
|
+
activeSide: state.activeSide === "input" ? "output" : "input",
|
|
64
|
+
manualOutputWallet: undefined,
|
|
65
|
+
};
|
|
66
|
+
case "set_slippage_mode":
|
|
67
|
+
return { ...state, slippageMode: action.mode };
|
|
68
|
+
case "set_slippage":
|
|
69
|
+
return { ...state, slippageMode: "custom", slippagePercent: action.value };
|
|
70
|
+
case "set_manual_output_wallet":
|
|
71
|
+
return { ...state, manualOutputWallet: action.wallet };
|
|
72
|
+
case "reset":
|
|
73
|
+
return {
|
|
74
|
+
...state,
|
|
75
|
+
sourceId: state.initialSourceId,
|
|
76
|
+
destinationId: state.initialDestinationId,
|
|
77
|
+
inputAmount: "",
|
|
78
|
+
outputAmount: "",
|
|
79
|
+
activeSide: "input",
|
|
80
|
+
slippageMode: state.initialSlippageMode,
|
|
81
|
+
slippagePercent: state.initialSlippagePercent,
|
|
82
|
+
manualOutputWallet: undefined,
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
export function useSwapState({ currencies, connectedChainTypes, defaultSourceCurrency, defaultDestinationCurrency, defaultSlippageBps, }) {
|
|
87
|
+
const [defaults] = useState(() => ({ source: defaultSourceCurrency, destination: defaultDestinationCurrency }));
|
|
88
|
+
const [state, dispatch] = useReducer(swapStateReducer, undefined, () => {
|
|
89
|
+
const initialState = createSwapState(defaultSlippageBps);
|
|
90
|
+
if (currencies.length === 0)
|
|
91
|
+
return initialState;
|
|
92
|
+
return swapStateReducer(initialState, {
|
|
93
|
+
type: "initialize_currencies",
|
|
94
|
+
...resolveCurrencySelection({
|
|
95
|
+
currencies,
|
|
96
|
+
connectedChainTypes,
|
|
97
|
+
defaultSourceCurrency: defaults.source,
|
|
98
|
+
defaultDestinationCurrency: defaults.destination,
|
|
99
|
+
}),
|
|
100
|
+
});
|
|
101
|
+
});
|
|
102
|
+
useEffect(() => {
|
|
103
|
+
if (state.currenciesInitialized || currencies.length === 0)
|
|
104
|
+
return;
|
|
105
|
+
const selection = resolveCurrencySelection({
|
|
106
|
+
currencies,
|
|
107
|
+
connectedChainTypes,
|
|
108
|
+
defaultSourceCurrency: defaults.source,
|
|
109
|
+
defaultDestinationCurrency: defaults.destination,
|
|
110
|
+
});
|
|
111
|
+
dispatch({ type: "initialize_currencies", ...selection });
|
|
112
|
+
}, [connectedChainTypes, currencies, defaults.destination, defaults.source, state.currenciesInitialized]);
|
|
113
|
+
const setAmount = useCallback((side, value) => {
|
|
114
|
+
dispatch({ type: "set_amount", side, value });
|
|
115
|
+
}, []);
|
|
116
|
+
const selectCurrency = useCallback((side, id) => {
|
|
117
|
+
dispatch({ type: "select_currency", side, id });
|
|
118
|
+
}, []);
|
|
119
|
+
const reverse = useCallback((inputAmount, outputAmount) => {
|
|
120
|
+
dispatch({ type: "reverse", inputAmount, outputAmount });
|
|
121
|
+
}, []);
|
|
122
|
+
const setSlippagePercent = useCallback((value) => dispatch({ type: "set_slippage", value }), []);
|
|
123
|
+
const setSlippageMode = useCallback((mode) => dispatch({ type: "set_slippage_mode", mode }), []);
|
|
124
|
+
const setManualOutputWallet = useCallback((wallet) => dispatch({ type: "set_manual_output_wallet", wallet }), []);
|
|
125
|
+
const reset = useCallback(() => dispatch({ type: "reset" }), []);
|
|
126
|
+
const resolvedState = !state.currenciesInitialized && currencies.length > 0
|
|
127
|
+
? swapStateReducer(state, {
|
|
128
|
+
type: "initialize_currencies",
|
|
129
|
+
...resolveCurrencySelection({
|
|
130
|
+
currencies,
|
|
131
|
+
connectedChainTypes,
|
|
132
|
+
defaultSourceCurrency: defaults.source,
|
|
133
|
+
defaultDestinationCurrency: defaults.destination,
|
|
134
|
+
}),
|
|
135
|
+
})
|
|
136
|
+
: state;
|
|
137
|
+
return {
|
|
138
|
+
state: resolvedState,
|
|
139
|
+
setAmount,
|
|
140
|
+
selectCurrency,
|
|
141
|
+
reverse,
|
|
142
|
+
setSlippageMode,
|
|
143
|
+
setSlippagePercent,
|
|
144
|
+
setManualOutputWallet,
|
|
145
|
+
reset,
|
|
146
|
+
};
|
|
147
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { type Currency } from "@coin-voyage/shared/types";
|
|
2
|
+
export interface SwapToken {
|
|
3
|
+
currency: Currency;
|
|
4
|
+
balance?: number;
|
|
5
|
+
balanceUsd?: number;
|
|
6
|
+
}
|
|
7
|
+
export declare function useTokenData(): {
|
|
8
|
+
tokens: SwapToken[];
|
|
9
|
+
accounts: import("@coin-voyage/crypto/hooks").Account[];
|
|
10
|
+
isLoading: boolean;
|
|
11
|
+
error: string | null;
|
|
12
|
+
};
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { useAccount } from "@coin-voyage/crypto/hooks";
|
|
2
|
+
import { fetchTokenList, tokenToCurrency } from "@coin-voyage/shared/currency";
|
|
3
|
+
import { currencyID } from "@coin-voyage/shared/types";
|
|
4
|
+
import { useEffect, useState } from "react";
|
|
5
|
+
import { useSwapKit } from "../providers/swapkit-context";
|
|
6
|
+
function mergeTokens(tokenList, portfolio) {
|
|
7
|
+
const portfolioById = new Map(portfolio.map((token) => [currencyID(token.currency), token]));
|
|
8
|
+
const tokenListIds = new Set(tokenList.map(currencyID));
|
|
9
|
+
const listed = tokenList.map((currency) => portfolioById.get(currencyID(currency)) ?? { currency });
|
|
10
|
+
const unlisted = portfolio.filter((token) => !tokenListIds.has(currencyID(token.currency)));
|
|
11
|
+
return [...listed, ...unlisted].sort((left, right) => (right.balanceUsd ?? 0) - (left.balanceUsd ?? 0));
|
|
12
|
+
}
|
|
13
|
+
export function useTokenData() {
|
|
14
|
+
const { api } = useSwapKit();
|
|
15
|
+
const { accounts } = useAccount();
|
|
16
|
+
const [listedCurrencies, setListedCurrencies] = useState([]);
|
|
17
|
+
const [portfolioState, setPortfolioState] = useState({
|
|
18
|
+
walletKey: "[]",
|
|
19
|
+
tokens: [],
|
|
20
|
+
});
|
|
21
|
+
const [isLoading, setIsLoading] = useState(true);
|
|
22
|
+
const [tokenError, setTokenError] = useState(null);
|
|
23
|
+
const [portfolioError, setPortfolioError] = useState(null);
|
|
24
|
+
useEffect(() => {
|
|
25
|
+
let active = true;
|
|
26
|
+
fetchTokenList()
|
|
27
|
+
.then((data) => {
|
|
28
|
+
if (!active)
|
|
29
|
+
return;
|
|
30
|
+
setListedCurrencies(data.chains.flatMap((chain) => chain.tokens.map(tokenToCurrency)));
|
|
31
|
+
setTokenError(null);
|
|
32
|
+
})
|
|
33
|
+
.catch(() => {
|
|
34
|
+
if (active)
|
|
35
|
+
setTokenError("Unable to load supported tokens");
|
|
36
|
+
})
|
|
37
|
+
.finally(() => {
|
|
38
|
+
if (active)
|
|
39
|
+
setIsLoading(false);
|
|
40
|
+
});
|
|
41
|
+
return () => {
|
|
42
|
+
active = false;
|
|
43
|
+
};
|
|
44
|
+
}, []);
|
|
45
|
+
const walletKey = JSON.stringify(accounts
|
|
46
|
+
.flatMap((account) => (account.address ? [{ address: account.address, chain_type: account.chainType }] : []))
|
|
47
|
+
.sort((left, right) => `${left.chain_type}:${left.address}`.localeCompare(`${right.chain_type}:${right.address}`)));
|
|
48
|
+
useEffect(() => {
|
|
49
|
+
const wallets = JSON.parse(walletKey);
|
|
50
|
+
if (wallets.length === 0)
|
|
51
|
+
return;
|
|
52
|
+
const controller = new AbortController();
|
|
53
|
+
api
|
|
54
|
+
.portfolio({ wallets }, { signal: controller.signal })
|
|
55
|
+
.then(({ data, error: responseError }) => {
|
|
56
|
+
if (responseError || !data) {
|
|
57
|
+
setPortfolioError(responseError?.message || "Unable to load wallet balances");
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
setPortfolioError(null);
|
|
61
|
+
setPortfolioState({
|
|
62
|
+
walletKey,
|
|
63
|
+
tokens: data.portfolio.flatMap((result) => result.currencies.map(({ balance, currency }) => ({
|
|
64
|
+
currency,
|
|
65
|
+
balance: Number(balance.ui),
|
|
66
|
+
balanceUsd: balance.value_usd ?? undefined,
|
|
67
|
+
}))),
|
|
68
|
+
});
|
|
69
|
+
})
|
|
70
|
+
.catch((requestError) => {
|
|
71
|
+
if (requestError.name !== "AbortError") {
|
|
72
|
+
setPortfolioError("Unable to load wallet balances");
|
|
73
|
+
}
|
|
74
|
+
});
|
|
75
|
+
return () => controller.abort();
|
|
76
|
+
}, [api, walletKey]);
|
|
77
|
+
const activePortfolioTokens = portfolioState.walletKey === walletKey ? portfolioState.tokens : [];
|
|
78
|
+
const tokens = mergeTokens(listedCurrencies, activePortfolioTokens);
|
|
79
|
+
return { tokens, accounts, isLoading, error: tokenError ?? portfolioError };
|
|
80
|
+
}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export { WalletProvider } from "@coin-voyage/crypto/wallets";
|
|
2
|
+
export { Swap } from "./components/swap";
|
|
3
|
+
export { SwapKitProvider } from "./providers/swapkit-provider";
|
|
4
|
+
export type { SwapCompleteResult, SwapCurrency, SwapError, SwapKitMode, SwapKitProviderProps, SwapKitTheme, SwapProps, } from "./types";
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
const DECIMAL_INPUT = /^(?:\d+(?:\.\d*)?|\.\d+)?$/;
|
|
2
|
+
export function isValidSwapAmountInput(value) {
|
|
3
|
+
return DECIMAL_INPUT.test(value);
|
|
4
|
+
}
|
|
5
|
+
export function toSwapAmountInputValue(amount) {
|
|
6
|
+
return amount && isValidSwapAmountInput(amount) ? amount : "";
|
|
7
|
+
}
|
|
8
|
+
export function shouldValidateSwapBalance(amount) {
|
|
9
|
+
const numericAmount = Number(amount);
|
|
10
|
+
return Number.isFinite(numericAmount) && numericAmount > 0;
|
|
11
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { type ChainType, type Currency, type CurrencyBase } from "@coin-voyage/shared/types";
|
|
2
|
+
export declare function currenciesMatch(left: CurrencyBase, right: CurrencyBase): boolean;
|
|
3
|
+
interface ResolveCurrencySelectionOptions {
|
|
4
|
+
currencies: Currency[];
|
|
5
|
+
connectedChainTypes: ReadonlySet<ChainType>;
|
|
6
|
+
defaultSourceCurrency?: CurrencyBase;
|
|
7
|
+
defaultDestinationCurrency?: CurrencyBase;
|
|
8
|
+
}
|
|
9
|
+
export declare function resolveCurrencySelection({ currencies, connectedChainTypes, defaultSourceCurrency, defaultDestinationCurrency, }: ResolveCurrencySelectionOptions): {
|
|
10
|
+
sourceId?: string;
|
|
11
|
+
destinationId?: string;
|
|
12
|
+
};
|
|
13
|
+
export {};
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { getChainTypeByChainId } from "@coin-voyage/shared/chain";
|
|
2
|
+
import { currencyID } from "@coin-voyage/shared/types";
|
|
3
|
+
function normalizedAddress(currency) {
|
|
4
|
+
return currency.address?.toLowerCase() ?? null;
|
|
5
|
+
}
|
|
6
|
+
export function currenciesMatch(left, right) {
|
|
7
|
+
return left.chain_id === right.chain_id && normalizedAddress(left) === normalizedAddress(right);
|
|
8
|
+
}
|
|
9
|
+
export function resolveCurrencySelection({ currencies, connectedChainTypes, defaultSourceCurrency, defaultDestinationCurrency, }) {
|
|
10
|
+
const preferredSource = defaultSourceCurrency
|
|
11
|
+
? currencies.find((currency) => currenciesMatch(currency, defaultSourceCurrency))
|
|
12
|
+
: undefined;
|
|
13
|
+
const source = preferredSource ??
|
|
14
|
+
currencies.find((currency) => connectedChainTypes.has(getChainTypeByChainId(currency.chain_id))) ??
|
|
15
|
+
currencies[0];
|
|
16
|
+
const preferredDestination = defaultDestinationCurrency
|
|
17
|
+
? currencies.find((currency) => currenciesMatch(currency, defaultDestinationCurrency))
|
|
18
|
+
: undefined;
|
|
19
|
+
const destination = preferredDestination && source && !currenciesMatch(preferredDestination, source)
|
|
20
|
+
? preferredDestination
|
|
21
|
+
: currencies.find((currency) => !source || !currenciesMatch(currency, source));
|
|
22
|
+
return {
|
|
23
|
+
sourceId: source ? currencyID(source) : undefined,
|
|
24
|
+
destinationId: destination ? currencyID(destination) : undefined,
|
|
25
|
+
};
|
|
26
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import type { ExecuteTransaction } from "@coin-voyage/crypto/types";
|
|
2
|
+
import type { SwapExecuteResponse } from "@coin-voyage/shared/types";
|
|
3
|
+
export type SwapStage = {
|
|
4
|
+
kind: "idle";
|
|
5
|
+
} | {
|
|
6
|
+
kind: "confirming";
|
|
7
|
+
} | {
|
|
8
|
+
kind: "awaiting_payment";
|
|
9
|
+
swapData: SwapExecuteResponse;
|
|
10
|
+
paymentStepIndex: number;
|
|
11
|
+
} | {
|
|
12
|
+
kind: "source_confirming";
|
|
13
|
+
swapData: SwapExecuteResponse;
|
|
14
|
+
orderId: string;
|
|
15
|
+
sourceTxHash?: string;
|
|
16
|
+
} | {
|
|
17
|
+
kind: "executing";
|
|
18
|
+
swapData: SwapExecuteResponse;
|
|
19
|
+
orderId: string;
|
|
20
|
+
sourceTxHash?: string;
|
|
21
|
+
} | {
|
|
22
|
+
kind: "done";
|
|
23
|
+
swapData: SwapExecuteResponse;
|
|
24
|
+
orderId: string;
|
|
25
|
+
sourceTxHash?: string;
|
|
26
|
+
txHash: string;
|
|
27
|
+
} | {
|
|
28
|
+
kind: "refunded";
|
|
29
|
+
swapData: SwapExecuteResponse;
|
|
30
|
+
orderId: string;
|
|
31
|
+
sourceTxHash?: string;
|
|
32
|
+
refundTxHash: string;
|
|
33
|
+
} | {
|
|
34
|
+
kind: "error";
|
|
35
|
+
swapData: SwapExecuteResponse | null;
|
|
36
|
+
message: string;
|
|
37
|
+
};
|
|
38
|
+
export declare function buildSwapExecuteTransactions(swapData: SwapExecuteResponse, fromAddress: string): ExecuteTransaction[];
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { isChainPaymentData, isDepositStepData } from "@coin-voyage/shared/payment";
|
|
2
|
+
export function buildSwapExecuteTransactions(swapData, fromAddress) {
|
|
3
|
+
const steps = swapData.data.steps;
|
|
4
|
+
if (steps.length === 0)
|
|
5
|
+
throw new Error("No step found");
|
|
6
|
+
return steps.map((step) => {
|
|
7
|
+
const base = { from: fromAddress };
|
|
8
|
+
if (isChainPaymentData(step.data))
|
|
9
|
+
return { ...base, paymentData: step.data };
|
|
10
|
+
if (isDepositStepData(step.data)) {
|
|
11
|
+
const tokenAddress = step.data.currency.address;
|
|
12
|
+
const decimals = swapData.data.input.currency.decimals;
|
|
13
|
+
if (tokenAddress && decimals === undefined) {
|
|
14
|
+
throw new Error("Unsupported swap step: missing token decimals");
|
|
15
|
+
}
|
|
16
|
+
return {
|
|
17
|
+
...base,
|
|
18
|
+
to: step.data.deposit_address,
|
|
19
|
+
chainId: step.data.currency.chain_id,
|
|
20
|
+
amount: BigInt(step.data.amount),
|
|
21
|
+
...(tokenAddress ? { token: { address: tokenAddress, decimals } } : {}),
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
throw new Error(`Unsupported swap step kind: ${step.kind}`);
|
|
25
|
+
});
|
|
26
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { ChainType } from "@coin-voyage/shared/types";
|
|
2
|
+
export type SwapWalletRequirement = {
|
|
3
|
+
target: "none";
|
|
4
|
+
} | {
|
|
5
|
+
target: "input" | "output";
|
|
6
|
+
chainType?: ChainType;
|
|
7
|
+
label: string;
|
|
8
|
+
};
|
|
9
|
+
interface GetSwapWalletRequirementProps {
|
|
10
|
+
inputWalletAddress?: string;
|
|
11
|
+
inputChainType?: ChainType;
|
|
12
|
+
shouldRestrictInputChainType?: boolean;
|
|
13
|
+
outputWalletAddress?: string;
|
|
14
|
+
outputChainType?: ChainType;
|
|
15
|
+
}
|
|
16
|
+
export declare function shouldUseSeparateOutputWallet(sourceChainType: ChainType | undefined, destinationChainType: ChainType | undefined): boolean;
|
|
17
|
+
interface ShouldShowOutputWalletSelectorProps {
|
|
18
|
+
requiresSeparateOutputWallet: boolean;
|
|
19
|
+
outputChainType?: ChainType;
|
|
20
|
+
connectionRequirement: SwapWalletRequirement;
|
|
21
|
+
}
|
|
22
|
+
export declare function shouldShowOutputWalletSelector({ requiresSeparateOutputWallet, outputChainType, connectionRequirement, }: ShouldShowOutputWalletSelectorProps): boolean;
|
|
23
|
+
export declare function getSwapWalletRequirement({ inputWalletAddress, inputChainType, shouldRestrictInputChainType, outputWalletAddress, outputChainType, }: GetSwapWalletRequirementProps): SwapWalletRequirement;
|
|
24
|
+
export {};
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { getChainTypeName } from "@coin-voyage/shared/chain";
|
|
2
|
+
export function shouldUseSeparateOutputWallet(sourceChainType, destinationChainType) {
|
|
3
|
+
return Boolean(sourceChainType && destinationChainType && sourceChainType !== destinationChainType);
|
|
4
|
+
}
|
|
5
|
+
export function shouldShowOutputWalletSelector({ requiresSeparateOutputWallet, outputChainType, connectionRequirement, }) {
|
|
6
|
+
return Boolean(outputChainType && requiresSeparateOutputWallet && connectionRequirement.target !== "input");
|
|
7
|
+
}
|
|
8
|
+
export function getSwapWalletRequirement({ inputWalletAddress, inputChainType, shouldRestrictInputChainType = true, outputWalletAddress, outputChainType, }) {
|
|
9
|
+
if (!inputWalletAddress) {
|
|
10
|
+
return {
|
|
11
|
+
target: "input",
|
|
12
|
+
chainType: shouldRestrictInputChainType ? inputChainType : undefined,
|
|
13
|
+
label: "Connect Wallet",
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
if (!outputWalletAddress) {
|
|
17
|
+
return {
|
|
18
|
+
target: "output",
|
|
19
|
+
chainType: outputChainType,
|
|
20
|
+
label: `Connect ${getChainTypeName(outputChainType)} Wallet`,
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
return { target: "none" };
|
|
24
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { type CurrencyBase, type SwapQuoteRequest } from "@coin-voyage/shared/types";
|
|
2
|
+
export type SwapSide = "input" | "output";
|
|
3
|
+
export declare function toCurrencyBase(currency: CurrencyBase): CurrencyBase;
|
|
4
|
+
interface BuildSwapQuoteRequestProps {
|
|
5
|
+
amount: string;
|
|
6
|
+
activeSide: SwapSide;
|
|
7
|
+
source?: CurrencyBase;
|
|
8
|
+
destination?: CurrencyBase;
|
|
9
|
+
slippageBps?: number;
|
|
10
|
+
}
|
|
11
|
+
export declare function buildSwapQuoteRequest({ amount, activeSide, source, destination, slippageBps, }: BuildSwapQuoteRequestProps): SwapQuoteRequest | null;
|
|
12
|
+
export declare function isSameSwap(source: CurrencyBase | undefined, destination: CurrencyBase | undefined, inputWalletAddress?: string, outputWalletAddress?: string): boolean;
|
|
13
|
+
export {};
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { currencyID } from "@coin-voyage/shared/types";
|
|
2
|
+
export function toCurrencyBase(currency) {
|
|
3
|
+
return currency.address ? { chain_id: currency.chain_id, address: currency.address } : { chain_id: currency.chain_id };
|
|
4
|
+
}
|
|
5
|
+
export function buildSwapQuoteRequest({ amount, activeSide, source, destination, slippageBps, }) {
|
|
6
|
+
const numericAmount = Number(amount);
|
|
7
|
+
if (!source || !destination || !Number.isFinite(numericAmount) || numericAmount <= 0)
|
|
8
|
+
return null;
|
|
9
|
+
return {
|
|
10
|
+
trade_type: activeSide === "output" ? "EXACT_OUTPUT" : "EXACT_INPUT",
|
|
11
|
+
amount,
|
|
12
|
+
source: toCurrencyBase(source),
|
|
13
|
+
destination: toCurrencyBase(destination),
|
|
14
|
+
...(slippageBps === undefined ? {} : { slippage_bps: slippageBps }),
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
export function isSameSwap(source, destination, inputWalletAddress, outputWalletAddress) {
|
|
18
|
+
return Boolean(source &&
|
|
19
|
+
destination &&
|
|
20
|
+
currencyID(source) === currencyID(destination) &&
|
|
21
|
+
inputWalletAddress &&
|
|
22
|
+
outputWalletAddress &&
|
|
23
|
+
inputWalletAddress.toLowerCase() === outputWalletAddress.toLowerCase());
|
|
24
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const swapkitVersion: string;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { ApiClient, APIEnvironment } from "@coin-voyage/shared/api";
|
|
2
|
+
import type { SwapKitMode, SwapKitTheme } from "../types";
|
|
3
|
+
export interface SwapKitContextValue {
|
|
4
|
+
api: ApiClient;
|
|
5
|
+
environment: APIEnvironment;
|
|
6
|
+
mode: SwapKitMode;
|
|
7
|
+
theme: Partial<SwapKitTheme>;
|
|
8
|
+
}
|
|
9
|
+
export declare const SwapKitContext: import("react").Context<SwapKitContextValue | null>;
|
|
10
|
+
export declare function useSwapKit(): SwapKitContextValue;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { createContext, useContext } from "react";
|
|
2
|
+
export const SwapKitContext = createContext(null);
|
|
3
|
+
export function useSwapKit() {
|
|
4
|
+
const context = useContext(SwapKitContext);
|
|
5
|
+
if (!context)
|
|
6
|
+
throw new Error("Swap must be used within SwapKitProvider");
|
|
7
|
+
return context;
|
|
8
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { useInWagmiContext } from "@coin-voyage/crypto/evm";
|
|
3
|
+
import { ApiClient } from "@coin-voyage/shared/api";
|
|
4
|
+
import { generateSessionId } from "@coin-voyage/shared/utils";
|
|
5
|
+
import { useMemo, useState } from "react";
|
|
6
|
+
import { SwapKitContext } from "./swapkit-context";
|
|
7
|
+
import { swapkitVersion } from "../lib/version";
|
|
8
|
+
export function SwapKitProvider({ apiKey, environment = "production", mode = "auto", theme = {}, children, }) {
|
|
9
|
+
const inWalletContext = useInWagmiContext();
|
|
10
|
+
if (!inWalletContext)
|
|
11
|
+
throw new Error("SwapKitProvider must be used within a WalletProvider");
|
|
12
|
+
if (!apiKey.trim())
|
|
13
|
+
throw new Error("SwapKitProvider requires a public API key");
|
|
14
|
+
const [sessionId] = useState(generateSessionId);
|
|
15
|
+
const api = useMemo(() => new ApiClient({ apiKey, environment, sessionId, version: `swapkit/${swapkitVersion}` }), [apiKey, environment, sessionId]);
|
|
16
|
+
const context = useMemo(() => ({ api, environment, mode, theme }), [api, environment, mode, theme]);
|
|
17
|
+
return _jsx(SwapKitContext.Provider, { value: context, children: children });
|
|
18
|
+
}
|