@coin-voyage/swapkit 0.1.0-beta.2 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/components/connected-wallets.d.ts +1 -1
- package/dist/components/connected-wallets.js +37 -14
- package/dist/components/connected-wallets.test.d.ts +1 -0
- package/dist/components/connected-wallets.test.js +104 -0
- package/dist/components/execution-modal.js +8 -2
- package/dist/components/styles.d.ts +4 -1
- package/dist/components/styles.js +86 -16
- package/dist/components/swap.d.ts +27 -1
- package/dist/components/swap.js +41 -18
- package/dist/components/token-picker.d.ts +3 -1
- package/dist/components/token-picker.js +19 -9
- package/dist/components/token-picker.test.d.ts +1 -0
- package/dist/components/token-picker.test.js +43 -0
- package/dist/components/wallet-connector.d.ts +3 -1
- package/dist/components/wallet-connector.js +69 -20
- package/dist/components/wallet-connector.test.d.ts +1 -0
- package/dist/components/wallet-connector.test.js +89 -0
- package/dist/hooks/use-swap-execution.d.ts +1 -1
- package/dist/hooks/use-swap-execution.js +69 -35
- package/dist/hooks/use-swap-state.d.ts +4 -1
- package/dist/hooks/use-swap-state.js +3 -3
- package/dist/hooks/use-token-data.d.ts +1 -0
- package/dist/hooks/use-token-data.js +4 -3
- package/dist/lib/currency-selection.js +3 -4
- package/dist/lib/execution.d.ts +12 -5
- package/dist/lib/quote.d.ts +1 -1
- package/dist/lib/quote.js +7 -7
- package/dist/providers/swapkit-provider.d.ts +1 -1
- package/dist/providers/swapkit-provider.js +3 -1
- package/dist/providers/swapkit-provider.test.d.ts +1 -0
- package/dist/providers/swapkit-provider.test.js +20 -0
- package/dist/types.d.ts +2 -1
- package/package.json +4 -4
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare function ConnectedWallets(): import("react/jsx-runtime").JSX.Element
|
|
1
|
+
export declare function ConnectedWallets(): import("react/jsx-runtime").JSX.Element;
|
|
@@ -1,10 +1,12 @@
|
|
|
1
|
-
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
2
|
import { useAccount, useAccountDisconnect, useInstalledWallets } from "@coin-voyage/crypto/hooks";
|
|
3
3
|
import { getChainTypeName } from "@coin-voyage/shared/chain";
|
|
4
|
+
import { ChainType } from "@coin-voyage/shared/types";
|
|
4
5
|
import { useEffect, useRef, useState } from "react";
|
|
5
6
|
import { CloseIcon, UnplugIcon } from "./icons";
|
|
6
|
-
import { ConnectedWalletPanel, ConnectedWalletRow, DisconnectAllButton, IconButton, TriggerWalletIcons, ToolbarGroup, WalletDetails, WalletDisconnectButton, WalletIdentity, WalletLabel, WalletPanelHeader, } from "./styles";
|
|
7
|
+
import { ConnectedWalletPanel, ConnectedWalletRow, DisconnectAllButton, IconButton, TriggerWalletIcons, ToolbarGroup, WalletActions, WalletDetails, WalletDisconnectButton, WalletIdentity, WalletLabel, WalletPanelHeader, WalletSwitchButton, } from "./styles";
|
|
7
8
|
import { WalletIconDisplay } from "./wallet-icon";
|
|
9
|
+
import { WalletConnector } from "./wallet-connector";
|
|
8
10
|
function truncateAddress(address) {
|
|
9
11
|
return address.length <= 14 ? address : `${address.slice(0, 6)}…${address.slice(-5)}`;
|
|
10
12
|
}
|
|
@@ -17,6 +19,15 @@ function connectorId(connector) {
|
|
|
17
19
|
return connector.name;
|
|
18
20
|
return undefined;
|
|
19
21
|
}
|
|
22
|
+
function connectorName(connector) {
|
|
23
|
+
if (!connector || typeof connector !== "object")
|
|
24
|
+
return undefined;
|
|
25
|
+
if ("displayName" in connector && typeof connector.displayName === "string")
|
|
26
|
+
return connector.displayName;
|
|
27
|
+
if ("name" in connector && typeof connector.name === "string")
|
|
28
|
+
return connector.name;
|
|
29
|
+
return undefined;
|
|
30
|
+
}
|
|
20
31
|
function accountAddress(account) {
|
|
21
32
|
return account.address ?? account.addresses?.[0];
|
|
22
33
|
}
|
|
@@ -25,11 +36,20 @@ export function ConnectedWallets() {
|
|
|
25
36
|
const wallets = useInstalledWallets();
|
|
26
37
|
const disconnectAccount = useAccountDisconnect();
|
|
27
38
|
const [open, setOpen] = useState(false);
|
|
39
|
+
const [switchAccount, setSwitchAccount] = useState(null);
|
|
28
40
|
const groupRef = useRef(null);
|
|
29
41
|
const connectedAccounts = accounts.filter((account) => account.isConnected);
|
|
30
42
|
const walletForAccount = (account) => {
|
|
31
43
|
const accountConnectorId = connectorId(account.connector);
|
|
32
|
-
|
|
44
|
+
const accountConnectorName = connectorName(account.connector)?.toLowerCase();
|
|
45
|
+
return wallets.find((wallet) => wallet.connectors.some((candidate) => {
|
|
46
|
+
if (candidate.chainType !== account.chainType)
|
|
47
|
+
return false;
|
|
48
|
+
const candidateConnectorId = connectorId(candidate.connector);
|
|
49
|
+
const candidateConnectorName = connectorName(candidate.connector)?.toLowerCase();
|
|
50
|
+
return ((accountConnectorId !== undefined && candidateConnectorId === accountConnectorId) ||
|
|
51
|
+
(accountConnectorName !== undefined && candidateConnectorName === accountConnectorName));
|
|
52
|
+
}));
|
|
33
53
|
};
|
|
34
54
|
useEffect(() => {
|
|
35
55
|
if (!open)
|
|
@@ -49,18 +69,21 @@ export function ConnectedWallets() {
|
|
|
49
69
|
document.removeEventListener("keydown", dismissFromKeyboard);
|
|
50
70
|
};
|
|
51
71
|
}, [open]);
|
|
52
|
-
if (connectedAccounts.length === 0)
|
|
53
|
-
return null;
|
|
54
72
|
const triggerLabel = connectedAccounts.length === 1
|
|
55
73
|
? truncateAddress(accountAddress(connectedAccounts[0]) ?? "Wallet")
|
|
56
74
|
: `${connectedAccounts.length} connections`;
|
|
57
|
-
return (_jsxs(ToolbarGroup, { ref: groupRef, children: [_jsxs(IconButton, { type: "button", "aria-label": "Connected wallets", "aria-expanded": open, onClick: () => setOpen((current) => !current), children: [_jsx(TriggerWalletIcons, { children: connectedAccounts.slice(0, 3).map((account, index) => {
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
75
|
+
return (_jsxs(_Fragment, { children: [connectedAccounts.length > 0 && (_jsxs(ToolbarGroup, { ref: groupRef, children: [_jsxs(IconButton, { type: "button", "aria-label": "Connected wallets", "aria-expanded": open, onClick: () => setOpen((current) => !current), children: [_jsx(TriggerWalletIcons, { children: connectedAccounts.slice(0, 3).map((account, index) => {
|
|
76
|
+
const wallet = walletForAccount(account);
|
|
77
|
+
return (_jsx(WalletIconDisplay, { icon: wallet?.icon, name: wallet?.name ?? getChainTypeName(account.chainType), size: 20 }, `${account.chainType}:${accountAddress(account) ?? index}`));
|
|
78
|
+
}) }), _jsx(WalletLabel, { children: triggerLabel })] }), open && (_jsxs(ConnectedWalletPanel, { role: "group", "aria-label": "Connected wallets", children: [_jsx(WalletPanelHeader, { children: "Connected wallets" }), connectedAccounts.map((account, index) => {
|
|
79
|
+
const wallet = walletForAccount(account);
|
|
80
|
+
const chainName = getChainTypeName(account.chainType);
|
|
81
|
+
const address = accountAddress(account);
|
|
82
|
+
return (_jsxs(ConnectedWalletRow, { children: [_jsxs(WalletIdentity, { children: [_jsx(WalletIconDisplay, { icon: wallet?.icon, name: wallet?.name ?? chainName, size: 20 }), _jsxs(WalletDetails, { children: [_jsx("strong", { children: chainName }), _jsx("small", { children: address ? truncateAddress(address) : "Connected" })] })] }), _jsxs(WalletActions, { children: [_jsx(WalletSwitchButton, { type: "button", "aria-label": `Switch ${chainName} wallet`, onClick: () => {
|
|
83
|
+
setOpen(false);
|
|
84
|
+
setSwitchAccount(account);
|
|
85
|
+
}, children: "Switch" }), _jsx(WalletDisconnectButton, { type: "button", "aria-label": `Disconnect ${chainName} wallet`, onClick: () => void disconnectAccount(account), children: _jsx(CloseIcon, { size: 16 }) })] })] }, `${account.chainType}:${address ?? index}`));
|
|
86
|
+
}), _jsxs(DisconnectAllButton, { type: "button", onClick: () => void disconnectAccount(), children: [_jsx(UnplugIcon, {}), "Disconnect all"] })] }))] })), _jsx(WalletConnector, { open: switchAccount !== null, chainType: switchAccount?.chainType, intent: "switch", beforeConnect: switchAccount && switchAccount.chainType !== ChainType.SOL
|
|
87
|
+
? () => disconnectAccount(switchAccount)
|
|
88
|
+
: undefined, onClose: () => setSwitchAccount(null) })] }));
|
|
66
89
|
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
// @vitest-environment jsdom
|
|
3
|
+
import { ChainId, ChainType } from "@coin-voyage/shared/types";
|
|
4
|
+
import { cleanup, render, screen } from "@testing-library/react";
|
|
5
|
+
import userEvent from "@testing-library/user-event";
|
|
6
|
+
import { afterEach, describe, expect, it, vi } from "vitest";
|
|
7
|
+
import { ConnectedWallets } from "./connected-wallets";
|
|
8
|
+
const disconnectAccount = vi.fn();
|
|
9
|
+
let accountsConnected = true;
|
|
10
|
+
let accountChainType = ChainType.SOL;
|
|
11
|
+
let accountAddress = "current-address";
|
|
12
|
+
let accountConnector = { id: "Test wallet", name: "Test wallet" };
|
|
13
|
+
let installedWalletOverride;
|
|
14
|
+
vi.mock("@coin-voyage/crypto/hooks", () => ({
|
|
15
|
+
useAccount: () => ({
|
|
16
|
+
accounts: accountsConnected
|
|
17
|
+
? [
|
|
18
|
+
{
|
|
19
|
+
address: accountAddress,
|
|
20
|
+
chainId: ChainId.SOL,
|
|
21
|
+
chainType: accountChainType,
|
|
22
|
+
connector: accountConnector,
|
|
23
|
+
isConnected: true,
|
|
24
|
+
isConnecting: false,
|
|
25
|
+
isDisconnected: false,
|
|
26
|
+
isReconnecting: false,
|
|
27
|
+
status: "connected",
|
|
28
|
+
},
|
|
29
|
+
]
|
|
30
|
+
: [],
|
|
31
|
+
}),
|
|
32
|
+
useAccountDisconnect: () => disconnectAccount,
|
|
33
|
+
useInstalledWallets: () => [
|
|
34
|
+
installedWalletOverride ?? {
|
|
35
|
+
id: "test-wallet",
|
|
36
|
+
name: "Test wallet",
|
|
37
|
+
connectors: [{ chainType: accountChainType, connector: { id: "Test wallet", name: "Test wallet" } }],
|
|
38
|
+
},
|
|
39
|
+
],
|
|
40
|
+
}));
|
|
41
|
+
vi.mock("./wallet-connector", () => ({
|
|
42
|
+
WalletConnector: ({ open, chainType, intent }) => open ? _jsx("div", { "data-testid": "wallet-connector", children: `${intent}:${chainType}` }) : null,
|
|
43
|
+
}));
|
|
44
|
+
afterEach(() => {
|
|
45
|
+
cleanup();
|
|
46
|
+
disconnectAccount.mockClear();
|
|
47
|
+
accountsConnected = true;
|
|
48
|
+
accountChainType = ChainType.SOL;
|
|
49
|
+
accountAddress = "current-address";
|
|
50
|
+
accountConnector = { id: "Test wallet", name: "Test wallet" };
|
|
51
|
+
installedWalletOverride = undefined;
|
|
52
|
+
});
|
|
53
|
+
describe("ConnectedWallets", () => {
|
|
54
|
+
it("opens the wallet switcher for the selected account chain", async () => {
|
|
55
|
+
const user = userEvent.setup();
|
|
56
|
+
render(_jsx(ConnectedWallets, {}));
|
|
57
|
+
await user.click(screen.getByRole("button", { name: "Connected wallets" }));
|
|
58
|
+
await user.click(screen.getByRole("button", { name: "Switch Solana wallet" }));
|
|
59
|
+
expect(screen.queryByRole("group", { name: "Connected wallets" })).toBeNull();
|
|
60
|
+
expect(screen.getByTestId("wallet-connector").textContent).toBe(`switch:${ChainType.SOL}`);
|
|
61
|
+
expect(disconnectAccount).not.toHaveBeenCalled();
|
|
62
|
+
});
|
|
63
|
+
it("keeps the switcher open while the previous account disconnects", async () => {
|
|
64
|
+
const user = userEvent.setup();
|
|
65
|
+
const { rerender } = render(_jsx(ConnectedWallets, {}));
|
|
66
|
+
await user.click(screen.getByRole("button", { name: "Connected wallets" }));
|
|
67
|
+
await user.click(screen.getByRole("button", { name: "Switch Solana wallet" }));
|
|
68
|
+
accountsConnected = false;
|
|
69
|
+
rerender(_jsx(ConnectedWallets, {}));
|
|
70
|
+
expect(screen.getByTestId("wallet-connector").textContent).toBe(`switch:${ChainType.SOL}`);
|
|
71
|
+
});
|
|
72
|
+
it("updates the connected-wallet label when Phantom changes address", () => {
|
|
73
|
+
accountAddress = "old-address-123456789";
|
|
74
|
+
const { rerender } = render(_jsx(ConnectedWallets, {}));
|
|
75
|
+
expect(screen.getByRole("button", { name: "Connected wallets" }).textContent).toContain("old-ad…56789");
|
|
76
|
+
accountAddress = "new-address-987654321";
|
|
77
|
+
rerender(_jsx(ConnectedWallets, {}));
|
|
78
|
+
expect(screen.getByRole("button", { name: "Connected wallets" }).textContent).toContain("new-ad…54321");
|
|
79
|
+
});
|
|
80
|
+
it("uses the Phantom icon for its Ethereum connection in a multichain wallet", () => {
|
|
81
|
+
accountChainType = ChainType.EVM;
|
|
82
|
+
accountConnector = { id: "injected", name: "Phantom" };
|
|
83
|
+
installedWalletOverride = {
|
|
84
|
+
id: "app.phantom",
|
|
85
|
+
name: "Phantom",
|
|
86
|
+
icon: "phantom.svg",
|
|
87
|
+
connectors: [
|
|
88
|
+
{ chainType: ChainType.EVM, connector: { id: "app.phantom", name: "Phantom" } },
|
|
89
|
+
{ chainType: ChainType.SOL, connector: { id: "Phantom", name: "Phantom" } },
|
|
90
|
+
],
|
|
91
|
+
};
|
|
92
|
+
render(_jsx(ConnectedWallets, {}));
|
|
93
|
+
expect(screen.getByAltText("Phantom wallet")).not.toBeNull();
|
|
94
|
+
});
|
|
95
|
+
it("only disconnects an account from the explicit disconnect action", async () => {
|
|
96
|
+
accountChainType = ChainType.EVM;
|
|
97
|
+
const user = userEvent.setup();
|
|
98
|
+
render(_jsx(ConnectedWallets, {}));
|
|
99
|
+
await user.click(screen.getByRole("button", { name: "Connected wallets" }));
|
|
100
|
+
await user.click(screen.getByRole("button", { name: "Disconnect Ethereum wallet" }));
|
|
101
|
+
expect(disconnectAccount).toHaveBeenCalledTimes(1);
|
|
102
|
+
expect(disconnectAccount.mock.calls[0]?.[0]?.chainType).toBe(ChainType.EVM);
|
|
103
|
+
});
|
|
104
|
+
});
|
|
@@ -36,16 +36,22 @@ function outcomeState(stage) {
|
|
|
36
36
|
if (!("swapData" in stage) || !stage.swapData)
|
|
37
37
|
return { tone: "muted", label: "Awaiting" };
|
|
38
38
|
if (stage.kind === "done") {
|
|
39
|
+
const transactionHash = stage.destinationTxHash ?? stage.sourceTxHash;
|
|
40
|
+
const chainId = stage.destinationTxHash
|
|
41
|
+
? stage.swapData.data.output.currency.chain_id
|
|
42
|
+
: stage.swapData.data.input.currency.chain_id;
|
|
39
43
|
return {
|
|
40
44
|
tone: "complete",
|
|
41
|
-
explorerUrl: getChainExplorerTxUrl(
|
|
45
|
+
explorerUrl: transactionHash ? getChainExplorerTxUrl(chainId, transactionHash) : undefined,
|
|
42
46
|
};
|
|
43
47
|
}
|
|
44
48
|
if (stage.kind === "refunded") {
|
|
45
49
|
return {
|
|
46
50
|
tone: "warning",
|
|
47
51
|
label: "Refunded",
|
|
48
|
-
explorerUrl:
|
|
52
|
+
explorerUrl: stage.refundTxHash
|
|
53
|
+
? getChainExplorerTxUrl(stage.swapData.data.input.currency.chain_id, stage.refundTxHash)
|
|
54
|
+
: undefined,
|
|
49
55
|
};
|
|
50
56
|
}
|
|
51
57
|
return { tone: "muted", label: "Awaiting" };
|
|
@@ -28,6 +28,8 @@ export declare const Hint: import("styled-components").StyledComponent<"p", any,
|
|
|
28
28
|
export declare const CardWrap: import("styled-components").StyledComponent<"div", any, {}, never>;
|
|
29
29
|
export declare const CurrencyCard: import("styled-components").StyledComponent<"section", any, {}, never>;
|
|
30
30
|
export declare const CardTitle: import("styled-components").StyledComponent<"div", any, {}, never>;
|
|
31
|
+
export declare const QuickAmountActions: import("styled-components").StyledComponent<"div", any, {}, never>;
|
|
32
|
+
export declare const QuickAmountButton: import("styled-components").StyledComponent<"button", any, {}, never>;
|
|
31
33
|
export declare const AmountRow: import("styled-components").StyledComponent<"div", any, {}, never>;
|
|
32
34
|
export declare const AmountInput: import("styled-components").StyledComponent<"input", any, {}, never>;
|
|
33
35
|
export declare const AmountSkeleton: import("styled-components").StyledComponent<"span", any, {}, never>;
|
|
@@ -44,7 +46,6 @@ export declare const ChainBadge: import("styled-components").StyledComponent<"sp
|
|
|
44
46
|
}, never>;
|
|
45
47
|
export declare const CardFooter: import("styled-components").StyledComponent<"div", any, {}, never>;
|
|
46
48
|
export declare const InlineActions: import("styled-components").StyledComponent<"div", any, {}, never>;
|
|
47
|
-
export declare const TextButton: import("styled-components").StyledComponent<"button", any, {}, never>;
|
|
48
49
|
export declare const OutputWalletControl: import("styled-components").StyledComponent<"span", any, {}, never>;
|
|
49
50
|
export declare const OutputWalletTrigger: import("styled-components").StyledComponent<"button", any, {}, never>;
|
|
50
51
|
export declare const OutputWalletMenu: import("styled-components").StyledComponent<"span", any, {}, never>;
|
|
@@ -94,6 +95,8 @@ export declare const WalletPanelHeader: import("styled-components").StyledCompon
|
|
|
94
95
|
export declare const ConnectedWalletRow: import("styled-components").StyledComponent<"div", any, {}, never>;
|
|
95
96
|
export declare const WalletIdentity: import("styled-components").StyledComponent<"div", any, {}, never>;
|
|
96
97
|
export declare const WalletDetails: import("styled-components").StyledComponent<"div", any, {}, never>;
|
|
98
|
+
export declare const WalletActions: import("styled-components").StyledComponent<"div", any, {}, never>;
|
|
99
|
+
export declare const WalletSwitchButton: import("styled-components").StyledComponent<"button", any, {}, never>;
|
|
97
100
|
export declare const WalletDisconnectButton: import("styled-components").StyledComponent<"button", any, {}, never>;
|
|
98
101
|
export declare const DisconnectAllButton: import("styled-components").StyledComponent<"button", any, {}, never>;
|
|
99
102
|
export declare const TriggerWalletIcons: import("styled-components").StyledComponent<"span", any, {}, never>;
|
|
@@ -199,14 +199,30 @@ export const CardWrap = styled.div `
|
|
|
199
199
|
position: relative;
|
|
200
200
|
`;
|
|
201
201
|
export const CurrencyCard = styled.section `
|
|
202
|
+
position: relative;
|
|
202
203
|
width: 100%;
|
|
203
|
-
min-height:
|
|
204
|
-
padding: 14px;
|
|
204
|
+
min-height: 128px;
|
|
205
|
+
padding: 14px 14px 8px;
|
|
205
206
|
border: 0;
|
|
206
207
|
border-style: none;
|
|
207
208
|
border-radius: var(--sk-radius);
|
|
208
209
|
color: var(--sk-text);
|
|
209
210
|
background: var(--sk-surface);
|
|
211
|
+
|
|
212
|
+
&:hover [data-quick-amounts],
|
|
213
|
+
&:focus-within [data-quick-amounts] {
|
|
214
|
+
opacity: 1;
|
|
215
|
+
pointer-events: auto;
|
|
216
|
+
transform: translateY(0);
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
@media (hover: none) {
|
|
220
|
+
[data-quick-amounts] {
|
|
221
|
+
opacity: 1;
|
|
222
|
+
pointer-events: auto;
|
|
223
|
+
transform: translateY(0);
|
|
224
|
+
}
|
|
225
|
+
}
|
|
210
226
|
`;
|
|
211
227
|
export const CardTitle = styled.div `
|
|
212
228
|
margin-bottom: 12px;
|
|
@@ -215,6 +231,47 @@ export const CardTitle = styled.div `
|
|
|
215
231
|
font-weight: 500;
|
|
216
232
|
line-height: 16px;
|
|
217
233
|
`;
|
|
234
|
+
export const QuickAmountActions = styled.div `
|
|
235
|
+
position: absolute;
|
|
236
|
+
top: 10px;
|
|
237
|
+
right: 12px;
|
|
238
|
+
display: flex;
|
|
239
|
+
align-items: center;
|
|
240
|
+
gap: 4px;
|
|
241
|
+
opacity: 0;
|
|
242
|
+
pointer-events: none;
|
|
243
|
+
transform: translateY(-2px);
|
|
244
|
+
transition:
|
|
245
|
+
opacity 120ms ease,
|
|
246
|
+
transform 120ms ease;
|
|
247
|
+
|
|
248
|
+
@media (prefers-reduced-motion: reduce) {
|
|
249
|
+
transition: none;
|
|
250
|
+
}
|
|
251
|
+
`;
|
|
252
|
+
export const QuickAmountButton = styled.button `
|
|
253
|
+
min-width: 34px;
|
|
254
|
+
height: 24px;
|
|
255
|
+
padding: 0 7px;
|
|
256
|
+
border: 1px solid var(--sk-border);
|
|
257
|
+
border-radius: 999px;
|
|
258
|
+
color: var(--sk-text);
|
|
259
|
+
background: var(--sk-surface-elevated);
|
|
260
|
+
cursor: pointer;
|
|
261
|
+
font-size: 11px;
|
|
262
|
+
font-weight: 600;
|
|
263
|
+
line-height: 22px;
|
|
264
|
+
|
|
265
|
+
&:hover:not(:disabled) {
|
|
266
|
+
border-color: var(--sk-accent);
|
|
267
|
+
color: var(--sk-accent);
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
&:disabled {
|
|
271
|
+
cursor: not-allowed;
|
|
272
|
+
opacity: 0.5;
|
|
273
|
+
}
|
|
274
|
+
`;
|
|
218
275
|
export const AmountRow = styled.div `
|
|
219
276
|
display: grid;
|
|
220
277
|
grid-template-columns: minmax(0, 1fr) auto;
|
|
@@ -371,20 +428,6 @@ export const InlineActions = styled.div `
|
|
|
371
428
|
align-items: center;
|
|
372
429
|
gap: 8px;
|
|
373
430
|
`;
|
|
374
|
-
export const TextButton = styled.button `
|
|
375
|
-
padding: 0;
|
|
376
|
-
border: 0;
|
|
377
|
-
color: var(--sk-accent);
|
|
378
|
-
background: transparent;
|
|
379
|
-
cursor: pointer;
|
|
380
|
-
font-size: 12px;
|
|
381
|
-
font-weight: 600;
|
|
382
|
-
|
|
383
|
-
&:disabled {
|
|
384
|
-
cursor: not-allowed;
|
|
385
|
-
opacity: 0.5;
|
|
386
|
-
}
|
|
387
|
-
`;
|
|
388
431
|
export const OutputWalletControl = styled.span `
|
|
389
432
|
position: relative;
|
|
390
433
|
display: inline-flex;
|
|
@@ -912,6 +955,11 @@ export const WalletOption = styled.button `
|
|
|
912
955
|
&:hover {
|
|
913
956
|
background: var(--sk-surface);
|
|
914
957
|
}
|
|
958
|
+
|
|
959
|
+
&:disabled {
|
|
960
|
+
cursor: wait;
|
|
961
|
+
opacity: 0.6;
|
|
962
|
+
}
|
|
915
963
|
`;
|
|
916
964
|
export const WalletIconFrame = styled.span `
|
|
917
965
|
display: inline-flex;
|
|
@@ -1025,6 +1073,28 @@ export const WalletDetails = styled.div `
|
|
|
1025
1073
|
font-size: 12px;
|
|
1026
1074
|
}
|
|
1027
1075
|
`;
|
|
1076
|
+
export const WalletActions = styled.div `
|
|
1077
|
+
display: flex;
|
|
1078
|
+
flex: 0 0 auto;
|
|
1079
|
+
align-items: center;
|
|
1080
|
+
gap: 4px;
|
|
1081
|
+
`;
|
|
1082
|
+
export const WalletSwitchButton = styled.button `
|
|
1083
|
+
height: 28px;
|
|
1084
|
+
padding: 0 8px;
|
|
1085
|
+
border: 0;
|
|
1086
|
+
border-radius: 6px;
|
|
1087
|
+
color: var(--sk-text-muted);
|
|
1088
|
+
background: transparent;
|
|
1089
|
+
cursor: pointer;
|
|
1090
|
+
font-size: 12px;
|
|
1091
|
+
font-weight: 500;
|
|
1092
|
+
|
|
1093
|
+
&:hover {
|
|
1094
|
+
color: var(--sk-accent);
|
|
1095
|
+
background: color-mix(in srgb, var(--sk-accent) 12%, transparent);
|
|
1096
|
+
}
|
|
1097
|
+
`;
|
|
1028
1098
|
export const WalletDisconnectButton = styled.button `
|
|
1029
1099
|
display: inline-flex;
|
|
1030
1100
|
width: 28px;
|
|
@@ -1,2 +1,28 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import { type ReactNode } from "react";
|
|
2
|
+
import { useSwapExecution } from "../hooks/use-swap-execution";
|
|
3
|
+
import { type SwapToken } from "../hooks/use-token-data";
|
|
4
|
+
import { type SwapSide } from "../lib/quote";
|
|
5
|
+
import type { SwapCompleteResult, SwapError, SwapProps } from "../types";
|
|
6
|
+
export declare function useSwapNotifications({ stage, quoteError, tokenError, onBalancesChanged, onComplete, onError, }: {
|
|
7
|
+
stage: ReturnType<typeof useSwapExecution>["stage"];
|
|
8
|
+
quoteError?: string | null;
|
|
9
|
+
tokenError?: string | null;
|
|
10
|
+
onBalancesChanged: () => void;
|
|
11
|
+
onComplete?: (result: SwapCompleteResult) => void;
|
|
12
|
+
onError?: (error: SwapError) => void;
|
|
13
|
+
}): void;
|
|
14
|
+
interface CurrencyPanelProps {
|
|
15
|
+
side: SwapSide;
|
|
16
|
+
token?: SwapToken;
|
|
17
|
+
amount: string;
|
|
18
|
+
balance?: number;
|
|
19
|
+
quoteUsd?: number | null;
|
|
20
|
+
quoteLoading?: boolean;
|
|
21
|
+
disabled?: boolean;
|
|
22
|
+
onAmountChange: (value: string) => void;
|
|
23
|
+
onSelectToken: () => void;
|
|
24
|
+
footerAction?: ReactNode;
|
|
25
|
+
}
|
|
26
|
+
export declare function CurrencyPanel({ side, token, amount, balance, quoteUsd, quoteLoading, disabled, onAmountChange, onSelectToken, footerAction, }: CurrencyPanelProps): import("react/jsx-runtime").JSX.Element;
|
|
2
27
|
export declare function Swap({ defaultSourceCurrency, defaultDestinationCurrency, defaultSlippageBps, onComplete, onError, }?: SwapProps): import("react/jsx-runtime").JSX.Element;
|
|
28
|
+
export {};
|
package/dist/components/swap.js
CHANGED
|
@@ -16,7 +16,7 @@ import { ExecutionModal } from "./execution-modal";
|
|
|
16
16
|
import { ConnectedWallets } from "./connected-wallets";
|
|
17
17
|
import { ArrowDownIcon, ChevronDownIcon, FuelIcon, InfoIcon, PasteIcon, SettingsIcon, WalletIcon } from "./icons";
|
|
18
18
|
import { Modal } from "./modal";
|
|
19
|
-
import { AmountInput, AmountRow, AmountSkeleton, BreakdownBody, BreakdownCard, BreakdownMeta, BreakdownToggle, CardFooter, CardTitle, CardWrap, CompactInput, CurrencyCard, DetailRow, ErrorBox, FieldLabel, FormStack, FreeFeeBadge, Hint, IconButton, InlineActions, InfoButton, InfoWrap, NetworkCost, OutputWalletControl, OutputWalletMenu, OutputWalletOption, OutputWalletTrigger, PrimaryButton, QuoteValueSkeleton, SettingsLabel, SettingsPanel, SettingsRow, SettingsTooltip, SlippageControl, SlippageInput, SlippageInputWrap, SlippageModeButton, Spinner, SwitchButton,
|
|
19
|
+
import { AmountInput, AmountRow, AmountSkeleton, BreakdownBody, BreakdownCard, BreakdownMeta, BreakdownToggle, CardFooter, CardTitle, CardWrap, CompactInput, CurrencyCard, DetailRow, ErrorBox, FieldLabel, FormStack, FreeFeeBadge, Hint, IconButton, InlineActions, InfoButton, InfoWrap, NetworkCost, OutputWalletControl, OutputWalletMenu, OutputWalletOption, OutputWalletTrigger, PrimaryButton, QuoteValueSkeleton, QuickAmountActions, QuickAmountButton, SettingsLabel, SettingsPanel, SettingsRow, SettingsTooltip, SlippageControl, SlippageInput, SlippageInputWrap, SlippageModeButton, Spinner, SwitchButton, TokenButton, Toolbar, ToolbarGroup, WalletRequirementButton, } from "./styles";
|
|
20
20
|
import { TokenIcon } from "./token-icon";
|
|
21
21
|
import { TokenPicker } from "./token-picker";
|
|
22
22
|
import { WalletConnector } from "./wallet-connector";
|
|
@@ -54,24 +54,30 @@ function SlippageInfoTooltip({ id }) {
|
|
|
54
54
|
const open = hovered || focused;
|
|
55
55
|
return (_jsxs(InfoWrap, { onMouseEnter: () => setHovered(true), onMouseLeave: () => setHovered(false), children: [_jsx(InfoButton, { type: "button", "aria-label": "About max slippage", "aria-describedby": open ? id : undefined, onFocus: () => setFocused(true), onBlur: () => setFocused(false), children: _jsx(InfoIcon, {}) }), open && (_jsx(SettingsTooltip, { id: id, role: "tooltip", children: SLIPPAGE_TOOLTIP }))] }));
|
|
56
56
|
}
|
|
57
|
-
function useSwapNotifications({ stage, quoteError, tokenError, onComplete, onError, }) {
|
|
57
|
+
export function useSwapNotifications({ stage, quoteError, tokenError, onBalancesChanged, onComplete, onError, }) {
|
|
58
58
|
const onCompleteRef = useRef(onComplete);
|
|
59
59
|
const onErrorRef = useRef(onError);
|
|
60
|
+
const onBalancesChangedRef = useRef(onBalancesChanged);
|
|
60
61
|
const lastNotification = useRef(undefined);
|
|
61
62
|
useEffect(() => {
|
|
62
63
|
onCompleteRef.current = onComplete;
|
|
63
64
|
onErrorRef.current = onError;
|
|
64
|
-
|
|
65
|
+
onBalancesChangedRef.current = onBalancesChanged;
|
|
66
|
+
}, [onBalancesChanged, onComplete, onError]);
|
|
65
67
|
useEffect(() => {
|
|
66
68
|
if (stage.kind === "idle") {
|
|
67
69
|
lastNotification.current = undefined;
|
|
68
70
|
return;
|
|
69
71
|
}
|
|
70
72
|
if (stage.kind === "done") {
|
|
71
|
-
const
|
|
73
|
+
const transactionHash = stage.destinationTxHash ?? stage.sourceTxHash;
|
|
74
|
+
if (!transactionHash)
|
|
75
|
+
return;
|
|
76
|
+
const key = `complete:${stage.orderId}:${transactionHash}`;
|
|
72
77
|
if (lastNotification.current !== key) {
|
|
73
78
|
lastNotification.current = key;
|
|
74
|
-
|
|
79
|
+
onBalancesChangedRef.current();
|
|
80
|
+
onCompleteRef.current?.({ orderId: stage.orderId, transactionHash });
|
|
75
81
|
}
|
|
76
82
|
}
|
|
77
83
|
else if (stage.kind === "error") {
|
|
@@ -131,12 +137,26 @@ function tokenUsdValue(token, amount) {
|
|
|
131
137
|
return undefined;
|
|
132
138
|
return token.currency.price_usd * numericAmount;
|
|
133
139
|
}
|
|
134
|
-
|
|
140
|
+
const QUICK_AMOUNTS = [
|
|
141
|
+
{ label: "25%", fraction: 0.25 },
|
|
142
|
+
{ label: "50%", fraction: 0.5 },
|
|
143
|
+
{ label: "75%", fraction: 0.75 },
|
|
144
|
+
{ label: "Max", fraction: 1 },
|
|
145
|
+
];
|
|
146
|
+
function balanceFraction(balance, fraction, decimals) {
|
|
147
|
+
if (fraction === 1)
|
|
148
|
+
return balance.toString();
|
|
149
|
+
return Number((balance * fraction).toFixed(Math.min(Math.max(decimals, 0), 18))).toString();
|
|
150
|
+
}
|
|
151
|
+
export function CurrencyPanel({ side, token, amount, balance, quoteUsd, quoteLoading, disabled, onAmountChange, onSelectToken, footerAction, }) {
|
|
135
152
|
const label = side === "input" ? "SELL" : "BUY";
|
|
136
|
-
|
|
153
|
+
const showQuickAmounts = side === "input" && balance !== undefined && token !== undefined;
|
|
154
|
+
return (_jsxs(CurrencyCard, { children: [_jsx(CardTitle, { children: label }), showQuickAmounts && (_jsx(QuickAmountActions, { "data-quick-amounts": true, children: QUICK_AMOUNTS.map(({ label: amountLabel, fraction }) => (_jsx(QuickAmountButton, { type: "button", disabled: disabled, "aria-label": fraction === 1
|
|
155
|
+
? `Use maximum ${token.currency.symbol} balance`
|
|
156
|
+
: `Use ${amountLabel} of ${token.currency.symbol} balance`, onClick: () => onAmountChange(balanceFraction(balance, fraction, token.currency.decimals)), children: amountLabel }, amountLabel))) })), _jsxs(AmountRow, { children: [quoteLoading ? (_jsx(AmountSkeleton, { role: "status", "aria-label": "Loading quoted amount" })) : (_jsx(AmountInput, { "aria-label": side === "input" ? "Sell amount" : "Buy amount", type: "text", inputMode: "decimal", autoComplete: "off", autoCorrect: "off", spellCheck: false, placeholder: "0", value: amount, disabled: disabled, onChange: (event) => {
|
|
137
157
|
if (isValidSwapAmountInput(event.target.value))
|
|
138
158
|
onAmountChange(event.target.value);
|
|
139
|
-
} })), _jsxs(TokenButton, { type: "button", onClick: onSelectToken, "aria-label": `Select ${side} token`, children: [token ? _jsx(TokenIcon, { currency: token.currency }) : null, _jsx("span", { children: token?.currency.symbol ?? "Select" }), _jsx(ChevronDownIcon, {})] })] }), _jsxs(CardFooter, { children: [quoteLoading ? (_jsx(QuoteValueSkeleton, { role: "status", "aria-label": "Loading quoted value" })) : (_jsx("span", { children: formatUsd(quoteUsd ?? tokenUsdValue(token, amount)) })), _jsxs(InlineActions, { children: [balance !== undefined && _jsxs("span", { children: ["Balance: ", balance.toLocaleString()] }),
|
|
159
|
+
} })), _jsxs(TokenButton, { type: "button", onClick: onSelectToken, "aria-label": `Select ${side} token`, children: [token ? _jsx(TokenIcon, { currency: token.currency }) : null, _jsx("span", { children: token?.currency.symbol ?? "Select" }), _jsx(ChevronDownIcon, {})] })] }), _jsxs(CardFooter, { children: [quoteLoading ? (_jsx(QuoteValueSkeleton, { role: "status", "aria-label": "Loading quoted value" })) : (_jsx("span", { children: formatUsd(quoteUsd ?? tokenUsdValue(token, amount)) })), _jsxs(InlineActions, { children: [balance !== undefined && _jsxs("span", { children: ["Balance: ", balance.toLocaleString()] }), footerAction] })] })] }));
|
|
140
160
|
}
|
|
141
161
|
function ManualWalletModal({ open, chainId, initialValue, onClose, onSave, }) {
|
|
142
162
|
const [value, setValue] = useState(initialValue ?? "");
|
|
@@ -156,7 +176,7 @@ export function Swap({ defaultSourceCurrency, defaultDestinationCurrency, defaul
|
|
|
156
176
|
const { mode, theme } = useSwapKit();
|
|
157
177
|
const slippageInputId = useId();
|
|
158
178
|
const slippageTooltipId = useId();
|
|
159
|
-
const { tokens, accounts, isLoading, error: tokenError } = useTokenData();
|
|
179
|
+
const { tokens, accounts, isLoading, error: tokenError, refreshBalances } = useTokenData();
|
|
160
180
|
const { accounts: connectedAccounts } = useAccount();
|
|
161
181
|
const [sourcePickerOpen, setSourcePickerOpen] = useState(false);
|
|
162
182
|
const [destinationPickerOpen, setDestinationPickerOpen] = useState(false);
|
|
@@ -223,9 +243,16 @@ export function Swap({ defaultSourceCurrency, defaultDestinationCurrency, defaul
|
|
|
223
243
|
slippageBps,
|
|
224
244
|
sourceToken?.currency,
|
|
225
245
|
]);
|
|
226
|
-
const execution = useSwapExecution(sourceChainType);
|
|
246
|
+
const execution = useSwapExecution(sourceChainType, inputAccount?.address);
|
|
227
247
|
const quoteState = useSwapQuote(quoteRequest, execution.stage.kind === "idle");
|
|
228
|
-
useSwapNotifications({
|
|
248
|
+
useSwapNotifications({
|
|
249
|
+
stage: execution.stage,
|
|
250
|
+
quoteError: quoteState.error,
|
|
251
|
+
tokenError,
|
|
252
|
+
onBalancesChanged: refreshBalances,
|
|
253
|
+
onComplete,
|
|
254
|
+
onError,
|
|
255
|
+
});
|
|
229
256
|
const displayedInputAmount = activeSide === "output" && quoteState.quote ? quoteState.quote.input.amount.ui : inputAmount;
|
|
230
257
|
const displayedOutputAmount = activeSide === "input" && quoteState.quote ? quoteState.quote.output.amount.ui : outputAmount;
|
|
231
258
|
const sourceBalance = sourceToken?.balance;
|
|
@@ -271,11 +298,7 @@ export function Swap({ defaultSourceCurrency, defaultDestinationCurrency, defaul
|
|
|
271
298
|
} }), _jsx("span", { children: "%" })] })] })] }), slippageMode === "custom" &&
|
|
272
299
|
(slippage >= 40 ? (_jsx(Hint, { "$tone": "danger", children: "This slippage tolerance may cause a severely unfavorable trade." })) : slippage >= 6 ? (_jsx(Hint, { "$tone": "warning", children: "High slippage can result in a worse execution price." })) : null)] }))] })] }), _jsxs(CardWrap, { children: [_jsx(CurrencyPanel, { side: "input", token: sourceToken, amount: displayedInputAmount, balance: sourceBalance, quoteUsd: quoteState.quote?.input.amount.value_usd, quoteLoading: quoteState.loading && activeSide === "output", disabled: execution.stage.kind !== "idle", onAmountChange: (value) => {
|
|
273
300
|
swapState.setAmount("input", value);
|
|
274
|
-
}, onSelectToken: () => setSourcePickerOpen(true),
|
|
275
|
-
? undefined
|
|
276
|
-
: () => {
|
|
277
|
-
swapState.setAmount("input", sourceBalance.toString());
|
|
278
|
-
} }), _jsx(SwitchButton, { type: "button", "aria-label": "Swap tokens", onClick: reverseTokens, disabled: !sourceToken || !destinationToken, children: _jsx(ArrowDownIcon, { size: 24 }) })] }), _jsx(CurrencyPanel, { side: "output", token: destinationToken, amount: displayedOutputAmount, quoteUsd: quoteState.quote?.output.amount.value_usd, quoteLoading: quoteState.loading && activeSide === "input", disabled: execution.stage.kind !== "idle", onAmountChange: (value) => {
|
|
301
|
+
}, onSelectToken: () => setSourcePickerOpen(true) }), _jsx(SwitchButton, { type: "button", "aria-label": "Swap tokens", onClick: reverseTokens, disabled: !sourceToken || !destinationToken, children: _jsx(ArrowDownIcon, { size: 24 }) })] }), _jsx(CurrencyPanel, { side: "output", token: destinationToken, amount: displayedOutputAmount, quoteUsd: quoteState.quote?.output.amount.value_usd, quoteLoading: quoteState.loading && activeSide === "input", disabled: execution.stage.kind !== "idle", onAmountChange: (value) => {
|
|
279
302
|
swapState.setAmount("output", value);
|
|
280
303
|
}, onSelectToken: () => setDestinationPickerOpen(true), footerAction: showOutputWalletSelector ? (_jsx(OutputWalletSelector, { label: outputAddress ? truncateAddress(outputAddress) : "Connect Wallet", open: outputWalletMenuOpen, onOpenChange: setOutputWalletMenuOpen, onConnect: () => {
|
|
281
304
|
setOutputWalletMenuOpen(false);
|
|
@@ -283,13 +306,13 @@ export function Swap({ defaultSourceCurrency, defaultDestinationCurrency, defaul
|
|
|
283
306
|
}, onPaste: () => {
|
|
284
307
|
setOutputWalletMenuOpen(false);
|
|
285
308
|
setManualWalletOpen(true);
|
|
286
|
-
} })) : undefined }), quoteState.error && _jsx(ErrorBox, { children: quoteState.error }), tokenError && _jsx(ErrorBox, { children: tokenError }), insufficientBalance && _jsxs(ErrorBox, { children: ["Insufficient ", sourceToken?.currency.symbol, " balance"] }), sameSwap && _jsx(ErrorBox, { children: "Select a different token or destination wallet" }), (quoteState.loading || quoteState.quote) && (_jsxs(BreakdownCard, { children: [_jsxs(BreakdownToggle, { type: "button", "aria-expanded": breakdownOpen, onClick: () => setBreakdownOpen((open) => !open), children: [_jsx("span", { children: quoteState.loading ? "Finding the best route…" : quoteSummary }), _jsxs(BreakdownMeta, { children: [quoteState.quote && (_jsxs(NetworkCost, { children: [_jsx(FuelIcon, {}), _jsx("span", { children: formatUsd(quoteState.quote.input.breakdown?.gas?.value_usd) })] })), _jsx(ChevronDownIcon, { size: 16, up: breakdownOpen })] })] }), breakdownOpen && (_jsxs(BreakdownBody, { children: [quoteState.quote?.output.amount_out_minimum && (_jsxs(DetailRow, { children: [_jsx("span", { children: "Receive at least" }), _jsxs("strong", { children: [quoteState.quote.output.amount_out_minimum.ui, " ", quoteState.quote.output.currency.symbol] })] })), _jsxs(DetailRow, { children: [_jsx("span", { children: "Price impact" }), _jsx("strong", { children: quoteState.quote ? formatPriceImpact(quoteState.quote.price_impact) : "—" })] }), _jsxs(DetailRow, { children: [_jsx("span", { children: "Network cost" }), _jsx("strong", { children: formatUsd(quoteState.quote?.input.breakdown?.gas?.value_usd) })] }), _jsxs(DetailRow, { children: [_jsx("span", { children: "Total fees" }), quoteState.quote?.input.breakdown?.fees?.total_fee.value_usd === 0 ? (_jsx(FreeFeeBadge, { children: "Free" })) : (_jsx("strong", { children: formatUsd(quoteState.quote?.input.breakdown?.fees?.total_fee.value_usd) }))] })] }))] })), walletRequirement.target !== "none" ? (_jsx(WalletRequirementButton, { type: "button", onClick: () => setConnectTarget({ open: true, chainType: walletRequirement.chainType }), children: walletRequirement.label })) : (_jsxs(PrimaryButton, { type: "button", disabled: !canExecute || quoteState.loading, onClick: () => void executeSwap(), children: [quoteState.loading ? _jsx(Spinner, {}) : null, isLoading ? "Loading tokens…" : quoteState.loading ? "Fetching quote…" : "Swap"] })), _jsx(TokenPicker, { open: sourcePickerOpen, title: "Select Token", tokens: tokens, selectedId: resolvedSourceId, onClose: () => setSourcePickerOpen(false), onSelect: (token) => selectToken("input", token) }), _jsx(TokenPicker, { open: destinationPickerOpen, title: "Select Token", tokens: tokens, selectedId: resolvedDestinationId, onClose: () => setDestinationPickerOpen(false), onSelect: (token) => selectToken("output", token) }), _jsx(WalletConnector, { open: connectTarget.open, chainType: connectTarget.chainType, onClose: () => setConnectTarget({ open: false }) }), manualWalletOpen && (_jsx(ManualWalletModal, { open: true, chainId: destinationToken?.currency.chain_id, initialValue: manualAddress, onClose: () => setManualWalletOpen(false), onSave: (address) => {
|
|
309
|
+
} })) : undefined }), quoteState.error && _jsx(ErrorBox, { children: quoteState.error }), tokenError && _jsx(ErrorBox, { children: tokenError }), insufficientBalance && _jsxs(ErrorBox, { children: ["Insufficient ", sourceToken?.currency.symbol, " balance"] }), sameSwap && _jsx(ErrorBox, { children: "Select a different token or destination wallet" }), (quoteState.loading || quoteState.quote) && (_jsxs(BreakdownCard, { children: [_jsxs(BreakdownToggle, { type: "button", "aria-expanded": breakdownOpen, onClick: () => setBreakdownOpen((open) => !open), children: [_jsx("span", { children: quoteState.loading ? "Finding the best route…" : quoteSummary }), _jsxs(BreakdownMeta, { children: [quoteState.quote && (_jsxs(NetworkCost, { children: [_jsx(FuelIcon, {}), _jsx("span", { children: formatUsd(quoteState.quote.input.breakdown?.gas?.value_usd) })] })), _jsx(ChevronDownIcon, { size: 16, up: breakdownOpen })] })] }), breakdownOpen && (_jsxs(BreakdownBody, { children: [quoteState.quote?.output.amount_out_minimum && (_jsxs(DetailRow, { children: [_jsx("span", { children: "Receive at least" }), _jsxs("strong", { children: [quoteState.quote.output.amount_out_minimum.ui, " ", quoteState.quote.output.currency.symbol] })] })), _jsxs(DetailRow, { children: [_jsx("span", { children: "Price impact" }), _jsx("strong", { children: quoteState.quote ? formatPriceImpact(quoteState.quote.price_impact) : "—" })] }), _jsxs(DetailRow, { children: [_jsx("span", { children: "Network cost" }), _jsx("strong", { children: formatUsd(quoteState.quote?.input.breakdown?.gas?.value_usd) })] }), _jsxs(DetailRow, { children: [_jsx("span", { children: "Total fees" }), quoteState.quote?.input.breakdown?.fees?.total_fee.value_usd === 0 ? (_jsx(FreeFeeBadge, { children: "Free" })) : (_jsx("strong", { children: formatUsd(quoteState.quote?.input.breakdown?.fees?.total_fee.value_usd) }))] })] }))] })), walletRequirement.target !== "none" ? (_jsx(WalletRequirementButton, { type: "button", onClick: () => setConnectTarget({ open: true, chainType: walletRequirement.chainType }), children: walletRequirement.label })) : (_jsxs(PrimaryButton, { type: "button", disabled: !canExecute || quoteState.loading, onClick: () => void executeSwap(), children: [quoteState.loading ? _jsx(Spinner, {}) : null, isLoading ? "Loading tokens…" : quoteState.loading ? "Fetching quote…" : "Swap"] })), _jsx(TokenPicker, { open: sourcePickerOpen, title: "Select Token", tokens: tokens, selectedId: resolvedSourceId, onClose: () => setSourcePickerOpen(false), onSelect: (token) => selectToken("input", token) }), _jsx(TokenPicker, { open: destinationPickerOpen, title: "Select Token", tokens: tokens, selectedId: resolvedDestinationId, preferredChainId: sourceToken?.currency.chain_id, onClose: () => setDestinationPickerOpen(false), onSelect: (token) => selectToken("output", token) }), _jsx(WalletConnector, { open: connectTarget.open, chainType: connectTarget.chainType, onClose: () => setConnectTarget({ open: false }) }), manualWalletOpen && (_jsx(ManualWalletModal, { open: true, chainId: destinationToken?.currency.chain_id, initialValue: manualAddress, onClose: () => setManualWalletOpen(false), onSave: (address) => {
|
|
287
310
|
if (destinationToken)
|
|
288
311
|
swapState.setManualOutputWallet({ chainId: destinationToken.currency.chain_id, address });
|
|
289
312
|
} })), _jsx(ExecutionModal, { stage: execution.stage, onClose: () => {
|
|
290
313
|
const terminal = ["done", "refunded"].includes(execution.stage.kind);
|
|
291
314
|
execution.reset();
|
|
292
315
|
if (terminal)
|
|
293
|
-
swapState.reset();
|
|
316
|
+
swapState.reset({ preserveCurrencies: execution.stage.kind === "done" });
|
|
294
317
|
} })] }) }));
|
|
295
318
|
}
|
|
@@ -1,11 +1,13 @@
|
|
|
1
|
+
import { ChainId } from "@coin-voyage/shared/types";
|
|
1
2
|
import type { SwapToken } from "../hooks/use-token-data";
|
|
2
3
|
interface TokenPickerProps {
|
|
3
4
|
open: boolean;
|
|
4
5
|
title: string;
|
|
5
6
|
tokens: SwapToken[];
|
|
6
7
|
selectedId?: string;
|
|
8
|
+
preferredChainId?: ChainId;
|
|
7
9
|
onClose: () => void;
|
|
8
10
|
onSelect: (token: SwapToken) => void;
|
|
9
11
|
}
|
|
10
|
-
export declare function TokenPicker({ open, title, tokens, selectedId, onClose, onSelect }: TokenPickerProps): import("react/jsx-runtime").JSX.Element;
|
|
12
|
+
export declare function TokenPicker({ open, title, tokens, selectedId, preferredChainId, onClose, onSelect, }: TokenPickerProps): import("react/jsx-runtime").JSX.Element;
|
|
11
13
|
export {};
|