@coin-voyage/swapkit 0.3.0 → 0.4.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.
Files changed (38) hide show
  1. package/LICENSE +8 -8
  2. package/README.md +68 -68
  3. package/dist/components/execution-modal.d.ts +1 -1
  4. package/dist/components/execution-modal.js +6 -2
  5. package/dist/components/styles.d.ts +1 -1
  6. package/dist/components/styles.js +1282 -1281
  7. package/dist/components/swap-recipient.test.d.ts +1 -0
  8. package/dist/components/swap-recipient.test.js +136 -0
  9. package/dist/components/swap.d.ts +3 -3
  10. package/dist/components/swap.js +35 -23
  11. package/dist/components/swap.test.d.ts +1 -0
  12. package/dist/components/swap.test.js +76 -0
  13. package/dist/components/token-picker.js +1 -1
  14. package/dist/components/wallet-connector.d.ts +1 -1
  15. package/dist/components/wallet-connector.js +24 -4
  16. package/dist/components/wallet-connector.test.js +55 -1
  17. package/dist/hooks/use-order-status.d.ts +1 -1
  18. package/dist/hooks/use-token-data.d.ts +3 -2
  19. package/dist/hooks/use-token-data.js +41 -12
  20. package/dist/hooks/use-token-data.test.d.ts +1 -0
  21. package/dist/hooks/use-token-data.test.js +81 -0
  22. package/dist/lib/amount-input.d.ts +1 -0
  23. package/dist/lib/amount-input.js +7 -0
  24. package/dist/lib/amount-input.test.d.ts +1 -0
  25. package/dist/lib/amount-input.test.js +17 -0
  26. package/dist/lib/execution.d.ts +2 -0
  27. package/dist/lib/execution.js +6 -0
  28. package/dist/lib/execution.test.d.ts +1 -0
  29. package/dist/lib/execution.test.js +24 -0
  30. package/dist/lib/output-wallet.d.ts +1 -2
  31. package/dist/lib/output-wallet.js +2 -2
  32. package/dist/lib/output-wallet.test.d.ts +1 -0
  33. package/dist/lib/output-wallet.test.js +26 -0
  34. package/dist/providers/swapkit-context.d.ts +1 -1
  35. package/dist/providers/swapkit-provider.js +1 -1
  36. package/dist/styles/root.js +59 -59
  37. package/dist/styles/theme.js +1 -1
  38. package/package.json +8 -8
@@ -1,13 +1,25 @@
1
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, useReducer, useState } from "react";
2
+ import { fetchTokenList, formatCurrencyAmount, tokenToCurrency } from "@coin-voyage/shared/currency";
3
+ import { getChainTypeByChainId } from "@coin-voyage/shared/chain";
4
+ import { ChainType, currencyID } from "@coin-voyage/shared/types";
5
+ import { useCallback, useEffect, useMemo, useReducer, useRef, useState } from "react";
5
6
  import { useSwapKit } from "../providers/swapkit-context";
7
+ const BALANCE_REFRESH_DELAYS_MS = [4000, 12000];
8
+ // The token list and the portfolio can report the same address in different casing.
9
+ function currencyKey(currency) {
10
+ const chainType = getChainTypeByChainId(currency.chain_id);
11
+ const caseInsensitive = chainType === ChainType.EVM || chainType === ChainType.SUI;
12
+ return currencyID(caseInsensitive ? { ...currency, address: currency.address?.toLowerCase() } : currency);
13
+ }
6
14
  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)));
15
+ const portfolioByKey = new Map(portfolio.map((token) => [currencyKey(token.currency), token]));
16
+ const tokenListKeys = new Set(tokenList.map(currencyKey));
17
+ const listed = tokenList.map((currency) => {
18
+ const held = portfolioByKey.get(currencyKey(currency));
19
+ // Keep the listed address so the token's id stays stable once balances load.
20
+ return held ? { ...held, currency: { ...held.currency, address: currency.address } } : { currency };
21
+ });
22
+ const unlisted = portfolio.filter((token) => !tokenListKeys.has(currencyKey(token.currency)));
11
23
  return [...listed, ...unlisted].sort((left, right) => (right.balanceUsd ?? 0) - (left.balanceUsd ?? 0));
12
24
  }
13
25
  export function useTokenData() {
@@ -17,11 +29,24 @@ export function useTokenData() {
17
29
  const [portfolioState, setPortfolioState] = useState({
18
30
  walletKey: "[]",
19
31
  tokens: [],
32
+ complete: false,
20
33
  });
21
34
  const [isLoading, setIsLoading] = useState(true);
22
35
  const [tokenError, setTokenError] = useState(null);
23
36
  const [portfolioError, setPortfolioError] = useState(null);
24
- const [portfolioRevision, refreshBalances] = useReducer((revision) => revision + 1, 0);
37
+ const [portfolioRevision, bumpPortfolioRevision] = useReducer((revision) => revision + 1, 0);
38
+ const refreshTimers = useRef([]);
39
+ const clearRefreshTimers = useCallback(() => {
40
+ refreshTimers.current.forEach(clearTimeout);
41
+ refreshTimers.current = [];
42
+ }, []);
43
+ // Balances can lag behind a settled swap, so refetch now and again once the chain data has caught up.
44
+ const refreshBalances = useCallback(() => {
45
+ clearRefreshTimers();
46
+ bumpPortfolioRevision();
47
+ refreshTimers.current = BALANCE_REFRESH_DELAYS_MS.map((delay) => setTimeout(bumpPortfolioRevision, delay));
48
+ }, [clearRefreshTimers]);
49
+ useEffect(() => clearRefreshTimers, [clearRefreshTimers]);
25
50
  useEffect(() => {
26
51
  let active = true;
27
52
  fetchTokenList()
@@ -61,9 +86,10 @@ export function useTokenData() {
61
86
  setPortfolioError(null);
62
87
  setPortfolioState({
63
88
  walletKey,
89
+ complete: !data.errors?.length,
64
90
  tokens: data.portfolio.flatMap((result) => result.currencies.map(({ balance, currency }) => ({
65
91
  currency,
66
- balance: Number(balance.ui),
92
+ balance: formatCurrencyAmount(balance.raw, currency.decimals),
67
93
  balanceUsd: balance.value_usd ?? undefined,
68
94
  }))),
69
95
  });
@@ -75,7 +101,10 @@ export function useTokenData() {
75
101
  });
76
102
  return () => controller.abort();
77
103
  }, [api, portfolioRevision, walletKey]);
78
- const activePortfolioTokens = portfolioState.walletKey === walletKey ? portfolioState.tokens : [];
79
- const tokens = mergeTokens(listedCurrencies, activePortfolioTokens);
80
- return { tokens, accounts, isLoading, error: tokenError ?? portfolioError, refreshBalances };
104
+ const portfolioActive = portfolioState.walletKey === walletKey;
105
+ // True once every connected wallet's balances are known, so a token without a balance is a zero balance.
106
+ const balancesLoaded = portfolioActive && portfolioState.complete;
107
+ // Token identities must stay stable across renders: the quote request is memoized on them.
108
+ const tokens = useMemo(() => mergeTokens(listedCurrencies, portfolioActive ? portfolioState.tokens : []), [listedCurrencies, portfolioActive, portfolioState.tokens]);
109
+ return { tokens, accounts, isLoading, error: tokenError ?? portfolioError, balancesLoaded, refreshBalances };
81
110
  }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,81 @@
1
+ // @vitest-environment jsdom
2
+ import { fetchTokenList } from "@coin-voyage/shared/currency";
3
+ import { ChainId, ChainType } from "@coin-voyage/shared/types";
4
+ import { act, cleanup, renderHook, waitFor } from "@testing-library/react";
5
+ import { afterEach, describe, expect, it, vi } from "vitest";
6
+ import { useTokenData } from "./use-token-data";
7
+ const { api } = vi.hoisted(() => ({ api: { portfolio: vi.fn() } }));
8
+ vi.mock("@coin-voyage/crypto/hooks", () => ({
9
+ useAccount: () => ({ accounts: [{ address: "0x123", chainType: ChainType.EVM }] }),
10
+ }));
11
+ vi.mock("@coin-voyage/shared/currency", async (importOriginal) => ({
12
+ ...(await importOriginal()),
13
+ fetchTokenList: vi.fn().mockResolvedValue({ chains: [] }),
14
+ }));
15
+ vi.mock("../providers/swapkit-context", () => ({
16
+ useSwapKit: () => ({ api }),
17
+ }));
18
+ afterEach(() => {
19
+ cleanup();
20
+ vi.clearAllMocks();
21
+ });
22
+ describe("useTokenData", () => {
23
+ it.each([
24
+ { decimals: 6, raw: "1234567", ui: "1.23", expected: "1.234567" },
25
+ { decimals: 9, raw: "1239999999", ui: "1.24", expected: "1.239999999" },
26
+ { decimals: 18, raw: "1123456789012345678", ui: "1.12", expected: "1.123456789012345678" },
27
+ { decimals: 18, raw: "1", ui: "0.00", expected: "0.000000000000000001" },
28
+ ])("preserves the raw balance at $decimals decimals ($raw)", async ({ decimals, raw, ui, expected }) => {
29
+ const currency = { id: "eth", chain_id: ChainId.ETH, name: "Ether", symbol: "ETH", decimals };
30
+ api.portfolio.mockResolvedValue({
31
+ data: { portfolio: [{ currencies: [{ currency, balance: { raw, ui, value_usd: 12.34 } }] }] },
32
+ });
33
+ const { result } = renderHook(() => useTokenData());
34
+ await waitFor(() => expect(result.current.tokens).toHaveLength(1));
35
+ expect(result.current.tokens[0]).toEqual({ currency, balance: expected, balanceUsd: 12.34 });
36
+ });
37
+ it("attaches balances to listed tokens when the portfolio address casing differs", async () => {
38
+ const address = "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48";
39
+ const listed = { name: "USD Coin", ticker: "USDC", decimals: 6, chainId: ChainId.ETH, address };
40
+ vi.mocked(fetchTokenList).mockResolvedValueOnce({ chains: [{ tokens: [listed] }] });
41
+ const held = {
42
+ id: "usdc",
43
+ chain_id: ChainId.ETH,
44
+ address: address.toLowerCase(),
45
+ name: "USD Coin",
46
+ symbol: "USDC",
47
+ decimals: 6,
48
+ };
49
+ api.portfolio.mockResolvedValue({
50
+ data: { portfolio: [{ currencies: [{ currency: held, balance: { raw: "1500000", ui: "1.5", value_usd: 1.5 } }] }] },
51
+ });
52
+ const { result, rerender } = renderHook(() => useTokenData());
53
+ await waitFor(() => expect(result.current.tokens[0]?.balance).toBe("1.5"));
54
+ expect(result.current.tokens).toHaveLength(1);
55
+ expect(result.current.tokens[0].currency.address).toBe(address);
56
+ const { currency } = result.current.tokens[0];
57
+ rerender();
58
+ expect(result.current.tokens[0].currency).toBe(currency);
59
+ });
60
+ it("refetches the portfolio immediately and again after a refresh", async () => {
61
+ vi.useFakeTimers();
62
+ try {
63
+ api.portfolio.mockResolvedValue({ data: { portfolio: [] } });
64
+ const { result, unmount } = renderHook(() => useTokenData());
65
+ expect(api.portfolio).toHaveBeenCalledTimes(1);
66
+ act(() => result.current.refreshBalances());
67
+ expect(api.portfolio).toHaveBeenCalledTimes(2);
68
+ await act(() => vi.advanceTimersByTimeAsync(4000));
69
+ expect(api.portfolio).toHaveBeenCalledTimes(3);
70
+ await act(() => vi.advanceTimersByTimeAsync(8000));
71
+ expect(api.portfolio).toHaveBeenCalledTimes(4);
72
+ act(() => result.current.refreshBalances());
73
+ unmount();
74
+ await vi.advanceTimersByTimeAsync(20000);
75
+ expect(api.portfolio).toHaveBeenCalledTimes(5);
76
+ }
77
+ finally {
78
+ vi.useRealTimers();
79
+ }
80
+ });
81
+ });
@@ -1,3 +1,4 @@
1
+ export declare function balanceFraction(balance: string, fraction: number, decimals: number): string;
1
2
  export declare function isValidSwapAmountInput(value: string): boolean;
2
3
  export declare function toSwapAmountInputValue(amount?: string | null): string;
3
4
  export declare function shouldValidateSwapBalance(amount: string): boolean;
@@ -1,4 +1,11 @@
1
+ import { formatCurrencyAmount, parseCurrencyAmount } from "@coin-voyage/shared/currency";
1
2
  const DECIMAL_INPUT = /^(?:\d+(?:\.\d*)?|\.\d+)?$/;
3
+ export function balanceFraction(balance, fraction, decimals) {
4
+ if (fraction === 1)
5
+ return balance;
6
+ const raw = BigInt(parseCurrencyAmount(balance, decimals));
7
+ return formatCurrencyAmount((raw * BigInt(fraction * 100)) / 100n, decimals);
8
+ }
2
9
  export function isValidSwapAmountInput(value) {
3
10
  return DECIMAL_INPUT.test(value);
4
11
  }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,17 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { balanceFraction } from "./amount-input";
3
+ describe("balanceFraction", () => {
4
+ it.each(["1.239999999999999999", "0.000000000000000001", "9007199254740993.123456789012345678", "0"])("preserves the full MAX balance %s", (balance) => {
5
+ expect(balanceFraction(balance, 1, 18)).toBe(balance);
6
+ });
7
+ it.each([
8
+ [0.25, "0.309999999999999999"],
9
+ [0.5, "0.619999999999999999"],
10
+ [0.75, "0.929999999999999999"],
11
+ ])("floors a %s fraction to spendable base units", (fraction, expected) => {
12
+ expect(balanceFraction("1.239999999999999999", fraction, 18)).toBe(expected);
13
+ });
14
+ it("does not round a fraction up for indivisible tokens", () => {
15
+ expect(balanceFraction("3", 0.5, 0)).toBe("1");
16
+ });
17
+ });
@@ -41,5 +41,7 @@ export type SwapStage = {
41
41
  swapData: SwapExecuteResponse | null;
42
42
  message: string;
43
43
  };
44
+ /** Whether the user's own swap transaction delivers the output, leaving no separate receive leg to track. */
45
+ export declare function deliversInSwapTransaction(swapData: SwapExecuteResponse): boolean;
44
46
  export declare function buildSwapExecuteTransactions(swapData: SwapExecuteResponse, fromAddress: string): ExecuteTransaction[];
45
47
  export {};
@@ -1,4 +1,10 @@
1
1
  import { isChainPaymentData, isDepositStepData } from "@coin-voyage/shared/payment";
2
+ /** Whether the user's own swap transaction delivers the output, leaving no separate receive leg to track. */
3
+ export function deliversInSwapTransaction(swapData) {
4
+ const { input, output, steps } = swapData.data;
5
+ const lastStep = steps[steps.length - 1];
6
+ return (input.currency.chain_id === output.currency.chain_id && lastStep !== undefined && !isDepositStepData(lastStep.data));
7
+ }
2
8
  export function buildSwapExecuteTransactions(swapData, fromAddress) {
3
9
  const steps = swapData.data.steps;
4
10
  if (steps.length === 0)
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,24 @@
1
+ import { ChainId } from "@coin-voyage/shared/types";
2
+ import { describe, expect, it } from "vitest";
3
+ import { deliversInSwapTransaction } from "./execution";
4
+ const swapStep = { data: { type: "EVM" } };
5
+ const depositStep = { data: { amount: "1", currency: {}, deposit_address: "0x1" } };
6
+ function swapData(inputChain, outputChain, steps) {
7
+ return {
8
+ data: { input: { currency: { chain_id: inputChain } }, output: { currency: { chain_id: outputChain } }, steps },
9
+ };
10
+ }
11
+ describe("deliversInSwapTransaction", () => {
12
+ it("is true for a same-chain swap executed by the user's transaction", () => {
13
+ expect(deliversInSwapTransaction(swapData(ChainId.ETH, ChainId.ETH, [swapStep]))).toBe(true);
14
+ });
15
+ it("is false for a cross-chain swap", () => {
16
+ expect(deliversInSwapTransaction(swapData(ChainId.ETH, ChainId.BASE, [swapStep]))).toBe(false);
17
+ });
18
+ it("is false when the user deposits and the output is delivered later", () => {
19
+ expect(deliversInSwapTransaction(swapData(ChainId.ETH, ChainId.ETH, [swapStep, depositStep]))).toBe(false);
20
+ });
21
+ it("is false without steps", () => {
22
+ expect(deliversInSwapTransaction(swapData(ChainId.ETH, ChainId.ETH, []))).toBe(false);
23
+ });
24
+ });
@@ -15,10 +15,9 @@ interface GetSwapWalletRequirementProps {
15
15
  }
16
16
  export declare function shouldUseSeparateOutputWallet(sourceChainType: ChainType | undefined, destinationChainType: ChainType | undefined): boolean;
17
17
  interface ShouldShowOutputWalletSelectorProps {
18
- requiresSeparateOutputWallet: boolean;
19
18
  outputChainType?: ChainType;
20
19
  connectionRequirement: SwapWalletRequirement;
21
20
  }
22
- export declare function shouldShowOutputWalletSelector({ requiresSeparateOutputWallet, outputChainType, connectionRequirement, }: ShouldShowOutputWalletSelectorProps): boolean;
21
+ export declare function shouldShowOutputWalletSelector({ outputChainType, connectionRequirement, }: ShouldShowOutputWalletSelectorProps): boolean;
23
22
  export declare function getSwapWalletRequirement({ inputWalletAddress, inputChainType, shouldRestrictInputChainType, outputWalletAddress, outputChainType, }: GetSwapWalletRequirementProps): SwapWalletRequirement;
24
23
  export {};
@@ -2,8 +2,8 @@ import { getChainTypeName } from "@coin-voyage/shared/chain";
2
2
  export function shouldUseSeparateOutputWallet(sourceChainType, destinationChainType) {
3
3
  return Boolean(sourceChainType && destinationChainType && sourceChainType !== destinationChainType);
4
4
  }
5
- export function shouldShowOutputWalletSelector({ requiresSeparateOutputWallet, outputChainType, connectionRequirement, }) {
6
- return Boolean(outputChainType && requiresSeparateOutputWallet && connectionRequirement.target !== "input");
5
+ export function shouldShowOutputWalletSelector({ outputChainType, connectionRequirement, }) {
6
+ return Boolean(outputChainType && connectionRequirement.target !== "input");
7
7
  }
8
8
  export function getSwapWalletRequirement({ inputWalletAddress, inputChainType, shouldRestrictInputChainType = true, outputWalletAddress, outputChainType, }) {
9
9
  if (!inputWalletAddress) {
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,26 @@
1
+ import { ChainType } from "@coin-voyage/shared/types";
2
+ import { describe, expect, it } from "vitest";
3
+ import { shouldShowOutputWalletSelector } from "./output-wallet";
4
+ describe("shouldShowOutputWalletSelector", () => {
5
+ it("allows changing the recipient when the input wallet is connected", () => {
6
+ expect(shouldShowOutputWalletSelector({
7
+ outputChainType: ChainType.EVM,
8
+ connectionRequirement: { target: "none" },
9
+ })).toBe(true);
10
+ });
11
+ it("allows choosing an address when an output wallet is required", () => {
12
+ expect(shouldShowOutputWalletSelector({
13
+ outputChainType: ChainType.SOL,
14
+ connectionRequirement: { target: "output", label: "Connect Wallet" },
15
+ })).toBe(true);
16
+ });
17
+ it("hides the selector until the input wallet is connected", () => {
18
+ expect(shouldShowOutputWalletSelector({
19
+ outputChainType: ChainType.EVM,
20
+ connectionRequirement: { target: "input", label: "Connect Wallet" },
21
+ })).toBe(false);
22
+ });
23
+ it("hides the selector without a destination chain", () => {
24
+ expect(shouldShowOutputWalletSelector({ connectionRequirement: { target: "none" } })).toBe(false);
25
+ });
26
+ });
@@ -1,4 +1,4 @@
1
- import type { ApiClient, APIEnvironment } from "@coin-voyage/shared/api";
1
+ import type { ApiClient, APIEnvironment } from "@coin-voyage/shared/api/browser";
2
2
  import type { SwapKitMode, SwapKitTheme } from "../types";
3
3
  export interface SwapKitContextValue {
4
4
  api: ApiClient;
@@ -1,7 +1,7 @@
1
1
  import { jsx as _jsx } from "react/jsx-runtime";
2
2
  import { useInWagmiContext } from "@coin-voyage/crypto/evm";
3
3
  import { useConnectCallback } from "@coin-voyage/crypto/hooks";
4
- import { ApiClient } from "@coin-voyage/shared/api";
4
+ import { ApiClient } from "@coin-voyage/shared/api/browser";
5
5
  import { generateSessionId } from "@coin-voyage/shared/utils";
6
6
  import { useMemo, useState } from "react";
7
7
  import { SwapKitContext } from "./swapkit-context";
@@ -2,23 +2,23 @@ import { css } from "styled-components";
2
2
  import styled from "./styled";
3
3
  import { darkTheme, lightTheme } from "./theme";
4
4
  function variables(theme) {
5
- return css `
6
- --sk-font-family: ${theme.fontFamily};
7
- --sk-background: ${theme.background};
8
- --sk-surface: ${theme.surface};
9
- --sk-surface-elevated: ${theme.surfaceElevated};
10
- --sk-border: ${theme.border};
11
- --sk-text: ${theme.text};
12
- --sk-text-muted: ${theme.textMuted};
13
- --sk-accent: ${theme.accent};
14
- --sk-accent-hover: ${theme.accentHover};
15
- --sk-accent-text: ${theme.accentText};
16
- --sk-danger: ${theme.danger};
17
- --sk-warning: ${theme.warning};
18
- --sk-success: ${theme.success};
19
- --sk-overlay: ${theme.overlay};
20
- --sk-shadow: ${theme.shadow};
21
- --sk-radius: ${theme.radius};
5
+ return css `
6
+ --sk-font-family: ${theme.fontFamily};
7
+ --sk-background: ${theme.background};
8
+ --sk-surface: ${theme.surface};
9
+ --sk-surface-elevated: ${theme.surfaceElevated};
10
+ --sk-border: ${theme.border};
11
+ --sk-text: ${theme.text};
12
+ --sk-text-muted: ${theme.textMuted};
13
+ --sk-accent: ${theme.accent};
14
+ --sk-accent-hover: ${theme.accentHover};
15
+ --sk-accent-text: ${theme.accentText};
16
+ --sk-danger: ${theme.danger};
17
+ --sk-warning: ${theme.warning};
18
+ --sk-success: ${theme.success};
19
+ --sk-overlay: ${theme.overlay};
20
+ --sk-shadow: ${theme.shadow};
21
+ --sk-radius: ${theme.radius};
22
22
  `;
23
23
  }
24
24
  function modeVariables(mode, customTheme) {
@@ -28,48 +28,48 @@ function modeVariables(mode, customTheme) {
28
28
  return variables(light);
29
29
  if (mode === "dark")
30
30
  return variables(dark);
31
- return css `
32
- ${variables(light)}
33
- @media (prefers-color-scheme: dark) {
34
- ${variables(dark)}
35
- }
31
+ return css `
32
+ ${variables(light)}
33
+ @media (prefers-color-scheme: dark) {
34
+ ${variables(dark)}
35
+ }
36
36
  `;
37
37
  }
38
- export const SwapKitRoot = styled.div `
39
- ${({ $mode, $customTheme }) => modeVariables($mode, $customTheme)}
40
-
41
- all: initial;
42
- display: block;
43
- width: 100%;
44
- color: var(--sk-text);
45
- background: var(--sk-background);
46
- font-family: var(--sk-font-family);
47
- font-size: 16px;
48
- text-align: left;
49
- text-rendering: optimizeLegibility;
50
- -webkit-font-smoothing: antialiased;
51
-
52
- &,
53
- * {
54
- box-sizing: border-box;
55
- font-family: var(--sk-font-family);
56
- }
57
-
58
- button,
59
- input {
60
- font-family: var(--sk-font-family);
61
- }
62
-
63
- button:focus-visible,
64
- input:focus-visible,
65
- a:focus-visible {
66
- outline: 2px solid var(--sk-accent);
67
- outline-offset: 2px;
68
- }
69
-
70
- img,
71
- svg {
72
- display: block;
73
- max-width: 100%;
74
- }
38
+ export const SwapKitRoot = styled.div `
39
+ ${({ $mode, $customTheme }) => modeVariables($mode, $customTheme)}
40
+
41
+ all: initial;
42
+ display: block;
43
+ width: 100%;
44
+ color: var(--sk-text);
45
+ background: ${({ $customTheme }) => $customTheme.background ?? "transparent"};
46
+ font-family: var(--sk-font-family);
47
+ font-size: 16px;
48
+ text-align: left;
49
+ text-rendering: optimizeLegibility;
50
+ -webkit-font-smoothing: antialiased;
51
+
52
+ &,
53
+ * {
54
+ box-sizing: border-box;
55
+ font-family: var(--sk-font-family);
56
+ }
57
+
58
+ button,
59
+ input {
60
+ font-family: var(--sk-font-family);
61
+ }
62
+
63
+ button:focus-visible,
64
+ input:focus-visible,
65
+ a:focus-visible {
66
+ outline: 2px solid var(--sk-accent);
67
+ outline-offset: 2px;
68
+ }
69
+
70
+ img,
71
+ svg {
72
+ display: block;
73
+ max-width: 100%;
74
+ }
75
75
  `;
@@ -1,7 +1,7 @@
1
1
  export const lightTheme = {
2
2
  fontFamily: `Roboto, Arial, Helvetica, sans-serif`,
3
3
  background: "#f4f5f7",
4
- surface: "#ffffff",
4
+ surface: "#f4f5f7",
5
5
  surfaceElevated: "#f7f8fa",
6
6
  border: "#e4e7ec",
7
7
  text: "#15181e",
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@coin-voyage/swapkit",
3
3
  "description": "A reusable cross-chain swap component for CoinVoyage.",
4
- "version": "0.3.0",
4
+ "version": "0.4.0",
5
5
  "private": false,
6
6
  "sideEffects": false,
7
7
  "author": "Lars <lars@coinvoyage.io>",
@@ -22,8 +22,8 @@
22
22
  "LICENSE"
23
23
  ],
24
24
  "dependencies": {
25
- "@coin-voyage/crypto": "3.5.0",
26
- "@coin-voyage/shared": "3.5.0"
25
+ "@coin-voyage/crypto": "3.6.0",
26
+ "@coin-voyage/shared": "3.6.0"
27
27
  },
28
28
  "devDependencies": {
29
29
  "@testing-library/react": "^16.3.0",
@@ -36,7 +36,7 @@
36
36
  "typescript-plugin-styled-components": "^3.0.0"
37
37
  },
38
38
  "peerDependencies": {
39
- "@bigmi/react": "^0.9.1",
39
+ "@bigmi/react": "^0.9.4",
40
40
  "@mysten/dapp-kit-react": "^2.1.4",
41
41
  "@solana/wallet-adapter-react": "^0.15.39",
42
42
  "@tanstack/react-query": "^5.90.6",
@@ -56,14 +56,14 @@
56
56
  "scripts": {
57
57
  "watch": "tsc -w -p ./tsconfig.json",
58
58
  "build": "tsc --build --force",
59
- "test": "vitest run --config ./vitest.config.ts",
60
- "test:coverage": "vitest run --coverage --maxWorkers=1 --no-file-parallelism --config ./vitest.config.ts",
61
59
  "clean": "rm -rf dist tsconfig.tsbuildinfo coverage",
60
+ "release:build": "pnpm clean && pnpm build",
62
61
  "pre:release": "pnpm version prerelease --preid=beta",
63
62
  "pre:publish": "pnpm publish --access public --tag beta --no-git-checks",
64
63
  "pre:steps": "pnpm pre:release && pnpm pre:publish",
65
- "release:build": "pnpm clean && pnpm build",
66
64
  "release": "pnpm publish --access public --no-git-checks",
67
- "type-check": "tsc --noEmit"
65
+ "type-check": "tsc --noEmit",
66
+ "test": "vitest run --config ./vitest.config.ts",
67
+ "test:coverage": "vitest run --coverage --maxWorkers=1 --no-file-parallelism --config ./vitest.config.ts"
68
68
  }
69
69
  }