@coin-voyage/swapkit 0.4.0-beta.0 → 0.5.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/LICENSE +8 -8
- package/README.md +68 -68
- package/dist/components/currency-panel.d.ts +17 -0
- package/dist/components/currency-panel.js +22 -0
- package/dist/components/execution-modal.d.ts +1 -1
- package/dist/components/execution-modal.js +33 -6
- package/dist/components/execution-modal.test.d.ts +1 -0
- package/dist/components/execution-modal.test.js +92 -0
- package/dist/components/manual-wallet-modal.d.ts +8 -0
- package/dist/components/manual-wallet-modal.js +19 -0
- package/dist/components/output-wallet-selector.d.ts +7 -0
- package/dist/components/output-wallet-selector.js +10 -0
- package/dist/components/quote-breakdown.d.ts +5 -0
- package/dist/components/quote-breakdown.js +13 -0
- package/dist/components/styles.d.ts +2 -1
- package/dist/components/styles.js +1294 -1281
- package/dist/components/swap-recipient.test.js +61 -2
- package/dist/components/swap-settings.d.ts +9 -0
- package/dist/components/swap-settings.js +24 -0
- package/dist/components/swap.d.ts +2 -28
- package/dist/components/swap.js +27 -301
- package/dist/components/token-picker.test.js +11 -0
- package/dist/hooks/use-dismiss.d.ts +3 -0
- package/dist/hooks/use-dismiss.js +26 -0
- package/dist/hooks/use-swap-notifications.d.ts +10 -0
- package/dist/hooks/use-swap-notifications.js +53 -0
- package/dist/hooks/use-swap-quote.js +49 -51
- package/dist/hooks/use-swap.d.ts +47 -0
- package/dist/hooks/use-swap.js +189 -0
- package/dist/hooks/use-token-data.d.ts +2 -1
- package/dist/hooks/use-token-data.js +42 -13
- package/dist/hooks/use-token-data.test.js +78 -3
- package/dist/lib/currency-selection.js +6 -4
- package/dist/lib/currency-selection.test.d.ts +1 -0
- package/dist/lib/currency-selection.test.js +16 -0
- package/dist/lib/execution.d.ts +2 -0
- package/dist/lib/execution.js +6 -0
- package/dist/lib/execution.test.d.ts +1 -0
- package/dist/lib/execution.test.js +24 -0
- package/dist/lib/format.d.ts +5 -0
- package/dist/lib/format.js +20 -0
- package/dist/lib/quote.d.ts +10 -1
- package/dist/lib/quote.js +12 -0
- package/dist/lib/slippage.d.ts +11 -0
- package/dist/lib/slippage.js +17 -0
- package/dist/styles/root.js +59 -59
- package/package.json +4 -4
package/dist/lib/execution.d.ts
CHANGED
|
@@ -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 {};
|
package/dist/lib/execution.js
CHANGED
|
@@ -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
|
+
});
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { SwapQuote } from "@coin-voyage/shared/types";
|
|
2
|
+
export declare function formatUsd(value?: number | null): string;
|
|
3
|
+
export declare function formatPriceImpact(value: number): string;
|
|
4
|
+
export declare function usdValue(priceUsd: number | null | undefined, amount: string): number | undefined;
|
|
5
|
+
export declare function formatQuoteRate(quote: SwapQuote): string;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
export function formatUsd(value) {
|
|
2
|
+
if (value === undefined || value === null || !Number.isFinite(value))
|
|
3
|
+
return "$0.00";
|
|
4
|
+
return new Intl.NumberFormat("en-US", { style: "currency", currency: "USD", maximumFractionDigits: 2 }).format(value);
|
|
5
|
+
}
|
|
6
|
+
export function formatPriceImpact(value) {
|
|
7
|
+
if (value > 0 && value < 0.01)
|
|
8
|
+
return "<0.01%";
|
|
9
|
+
return `${value.toFixed(2)}%`;
|
|
10
|
+
}
|
|
11
|
+
export function usdValue(priceUsd, amount) {
|
|
12
|
+
const numericAmount = Number(amount);
|
|
13
|
+
if (!priceUsd || !Number.isFinite(numericAmount))
|
|
14
|
+
return undefined;
|
|
15
|
+
return priceUsd * numericAmount;
|
|
16
|
+
}
|
|
17
|
+
export function formatQuoteRate(quote) {
|
|
18
|
+
const rate = Number(quote.output.amount.ui) / Math.max(Number(quote.input.amount.ui), Number.EPSILON);
|
|
19
|
+
return `1 ${quote.input.currency.symbol} ≈ ${rate.toLocaleString(undefined, { maximumSignificantDigits: 6 })} ${quote.output.currency.symbol}`;
|
|
20
|
+
}
|
package/dist/lib/quote.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { CurrencyBase, SwapQuoteRequest } from "@coin-voyage/shared/types";
|
|
1
|
+
import type { CurrencyBase, SwapExecuteRequest, SwapQuoteRequest } from "@coin-voyage/shared/types";
|
|
2
2
|
export type SwapSide = "input" | "output";
|
|
3
3
|
export declare function toCurrencyBase(currency: CurrencyBase): CurrencyBase;
|
|
4
4
|
interface BuildSwapQuoteRequestProps {
|
|
@@ -9,5 +9,14 @@ interface BuildSwapQuoteRequestProps {
|
|
|
9
9
|
slippageBps?: number;
|
|
10
10
|
}
|
|
11
11
|
export declare function buildSwapQuoteRequest({ amount, activeSide, source, destination, slippageBps, }: BuildSwapQuoteRequestProps): SwapQuoteRequest | null;
|
|
12
|
+
interface BuildSwapExecuteRequestProps {
|
|
13
|
+
quoteRequest: SwapQuoteRequest;
|
|
14
|
+
source: CurrencyBase;
|
|
15
|
+
destination: CurrencyBase;
|
|
16
|
+
senderAddress: string;
|
|
17
|
+
recipient: string;
|
|
18
|
+
slippageBps?: number;
|
|
19
|
+
}
|
|
20
|
+
export declare function buildSwapExecuteRequest({ quoteRequest, source, destination, senderAddress, recipient, slippageBps, }: BuildSwapExecuteRequestProps): SwapExecuteRequest;
|
|
12
21
|
export declare function isSameSwap(source: CurrencyBase | undefined, destination: CurrencyBase | undefined, inputWalletAddress?: string, outputWalletAddress?: string): boolean;
|
|
13
22
|
export {};
|
package/dist/lib/quote.js
CHANGED
|
@@ -16,6 +16,18 @@ export function buildSwapQuoteRequest({ amount, activeSide, source, destination,
|
|
|
16
16
|
...(slippageBps === undefined ? {} : { slippage_bps: slippageBps }),
|
|
17
17
|
};
|
|
18
18
|
}
|
|
19
|
+
export function buildSwapExecuteRequest({ quoteRequest, source, destination, senderAddress, recipient, slippageBps, }) {
|
|
20
|
+
return {
|
|
21
|
+
trade_type: quoteRequest.trade_type,
|
|
22
|
+
amount: quoteRequest.amount,
|
|
23
|
+
source: {
|
|
24
|
+
currency: toCurrencyBase(source),
|
|
25
|
+
sender_address: senderAddress,
|
|
26
|
+
...(slippageBps === undefined ? {} : { slippage_bps: slippageBps }),
|
|
27
|
+
},
|
|
28
|
+
destination: { currency: toCurrencyBase(destination), recipient },
|
|
29
|
+
};
|
|
30
|
+
}
|
|
19
31
|
export function isSameSwap(source, destination, inputWalletAddress, outputWalletAddress) {
|
|
20
32
|
if (!source || !destination || !currenciesMatch(source, destination))
|
|
21
33
|
return false;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { SlippageMode } from "../hooks/use-swap-state";
|
|
2
|
+
export interface ResolvedSlippage {
|
|
3
|
+
invalid: boolean;
|
|
4
|
+
/** Undefined in auto mode, or while the custom value is invalid. */
|
|
5
|
+
bps?: number;
|
|
6
|
+
warning?: {
|
|
7
|
+
tone: "warning" | "danger";
|
|
8
|
+
message: string;
|
|
9
|
+
};
|
|
10
|
+
}
|
|
11
|
+
export declare function resolveSlippage(mode: SlippageMode, percent: string): ResolvedSlippage;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
const HIGH_SLIPPAGE_PERCENT = 6;
|
|
2
|
+
const SEVERE_SLIPPAGE_PERCENT = 40;
|
|
3
|
+
export function resolveSlippage(mode, percent) {
|
|
4
|
+
if (mode !== "custom")
|
|
5
|
+
return { invalid: false };
|
|
6
|
+
const value = Number(percent);
|
|
7
|
+
const invalid = percent.length === 0 || !Number.isFinite(value) || value < 0 || value > 100;
|
|
8
|
+
return {
|
|
9
|
+
invalid,
|
|
10
|
+
bps: invalid ? undefined : Math.round(value * 100),
|
|
11
|
+
warning: value >= SEVERE_SLIPPAGE_PERCENT
|
|
12
|
+
? { tone: "danger", message: "This slippage tolerance may cause a severely unfavorable trade." }
|
|
13
|
+
: value >= HIGH_SLIPPAGE_PERCENT
|
|
14
|
+
? { tone: "warning", message: "High slippage can result in a worse execution price." }
|
|
15
|
+
: undefined,
|
|
16
|
+
};
|
|
17
|
+
}
|
package/dist/styles/root.js
CHANGED
|
@@ -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: ${({ $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
|
-
}
|
|
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
|
`;
|
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.
|
|
4
|
+
"version": "0.5.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.
|
|
26
|
-
"@coin-voyage/shared": "3.
|
|
25
|
+
"@coin-voyage/crypto": "3.7.0",
|
|
26
|
+
"@coin-voyage/shared": "3.7.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.
|
|
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",
|