@xswap-link/sdk 0.15.0 → 0.15.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/.eslintrc.json +1 -0
- package/CHANGELOG.md +16 -0
- package/README.md +22 -4
- package/dist/index.d.mts +47 -1
- package/dist/index.d.ts +47 -1
- package/dist/index.global.js +94 -88
- package/dist/index.js +1378 -985
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1256 -863
- package/dist/index.mjs.map +1 -1
- package/example/index.html +12 -0
- package/example/package.json +24 -0
- package/example/pnpm-lock.yaml +1164 -0
- package/example/src/App.tsx +421 -0
- package/example/src/main.tsx +8 -0
- package/example/vite.config.ts +9 -0
- package/package.json +2 -1
- package/postcss.config.js +2 -0
- package/src/assets/icons/ChevronRightThinIcon.tsx +23 -0
- package/src/assets/icons/InfoThinIcon.tsx +33 -0
- package/src/assets/icons/RedirectThinIcon.tsx +15 -0
- package/src/assets/icons/index.ts +3 -0
- package/src/components/Swap/Header/index.tsx +3 -1
- package/src/components/Swap/HistoryView/ActivityCard/index.tsx +2 -2
- package/src/components/Swap/ReorderButton/ReorderButton.tsx +4 -1
- package/src/components/Swap/SettingsView/Delivery/index.tsx +3 -3
- package/src/components/Swap/SettingsView/InfiniteApproval/index.tsx +3 -3
- package/src/components/Swap/SettingsView/Slippage/index.tsx +5 -3
- package/src/components/Swap/SwapView/ConfirmationView/TokenTiles/index.tsx +5 -2
- package/src/components/Swap/SwapView/ConfirmationView/TxOverview/Header/index.tsx +1 -1
- package/src/components/Swap/SwapView/ConfirmationView/TxOverview/index.tsx +12 -7
- package/src/components/Swap/SwapView/ConfirmationView/TxProgress/index.tsx +136 -0
- package/src/components/Swap/SwapView/ConfirmationView/TxResult/index.tsx +108 -26
- package/src/components/Swap/SwapView/ConfirmationView/index.tsx +19 -4
- package/src/components/Swap/SwapView/FeesPanel/Fees/index.tsx +12 -6
- package/src/components/Swap/SwapView/FeesPanel/index.tsx +3 -1
- package/src/components/Swap/SwapView/SwapPanel/AmountPanel/index.tsx +9 -0
- package/src/components/Swap/SwapView/SwapPanel/TokenPanel/ChainPicker/index.tsx +1 -1
- package/src/components/Swap/SwapView/SwapPanel/TokenPanel/TokenPicker/NetworkFilter/index.tsx +52 -42
- package/src/components/Swap/SwapView/SwapPanel/TokenPanel/TokenPicker/TokenItem/index.tsx +1 -1
- package/src/components/Swap/SwapView/SwapPanel/TokenPanel/TokenPicker/index.tsx +5 -5
- package/src/components/Swap/SwapView/SwapPanel/TokenPanel/index.tsx +24 -2
- package/src/components/Swap/SwapView/SwapPanel/WalletPanel/index.tsx +14 -3
- package/src/components/Swap/SwapView/SwapPanel/index.tsx +14 -2
- package/src/components/Swap/SwapView/WalletPicker/index.tsx +1 -1
- package/src/components/ToggleButton/index.tsx +5 -3
- package/src/components/TxModal/index.tsx +2 -6
- package/src/components/global.css +85 -0
- package/src/config/fonts.ts +15 -2
- package/src/context/SwapProvider.tsx +22 -7
- package/src/context/TxUIWrapper.tsx +34 -4
- package/src/hooks/index.ts +1 -0
- package/src/hooks/useCrossChainDelivery.ts +143 -0
- package/src/models/Route.ts +13 -0
- package/src/models/TransactionDelivery.ts +40 -0
- package/src/models/index.ts +1 -0
- package/src/services/api.ts +23 -0
- package/tailwind.config.js +3 -9
|
@@ -0,0 +1,421 @@
|
|
|
1
|
+
import { TxWidget } from "@xswap-link/sdk";
|
|
2
|
+
import { useEffect, useMemo, useState } from "react";
|
|
3
|
+
|
|
4
|
+
type Token = { address: string; symbol: string; quickPick?: boolean };
|
|
5
|
+
type Chain = {
|
|
6
|
+
chainId: string;
|
|
7
|
+
displayName: string;
|
|
8
|
+
ecosystem: string;
|
|
9
|
+
web3Environment: string;
|
|
10
|
+
bridgeSupported: boolean;
|
|
11
|
+
swapSupported: boolean;
|
|
12
|
+
tokens: Token[];
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
type Cfg = Record<string, string | boolean | undefined>;
|
|
16
|
+
|
|
17
|
+
const DEFAULT_API = "https://xswap.link/api";
|
|
18
|
+
|
|
19
|
+
const API_URLS = [
|
|
20
|
+
DEFAULT_API,
|
|
21
|
+
"https://develop-72sp7lmyaa-ew.a.run.app/api/beta",
|
|
22
|
+
"http://localhost:3000/api",
|
|
23
|
+
];
|
|
24
|
+
|
|
25
|
+
const FONTS = ["Inter", "Roboto", "Poppins", "monospace", "serif"];
|
|
26
|
+
|
|
27
|
+
const BOOL_FIELDS = [
|
|
28
|
+
"lightTheme",
|
|
29
|
+
"defaultWalletPicker",
|
|
30
|
+
"bridge",
|
|
31
|
+
"srcChainLocked",
|
|
32
|
+
"srcTokenLocked",
|
|
33
|
+
"dstChainLocked",
|
|
34
|
+
"dstTokenLocked",
|
|
35
|
+
];
|
|
36
|
+
|
|
37
|
+
const DEFAULTS: Cfg = {
|
|
38
|
+
integratorId: "d6e438dfa14e80701b9b",
|
|
39
|
+
defaultWalletPicker: true,
|
|
40
|
+
bridge: true,
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
const STORAGE_KEY = "xswap-example-config";
|
|
44
|
+
|
|
45
|
+
const loadCfg = (): Cfg => {
|
|
46
|
+
try {
|
|
47
|
+
return { ...DEFAULTS, ...JSON.parse(localStorage.getItem(STORAGE_KEY)!) };
|
|
48
|
+
} catch {
|
|
49
|
+
return DEFAULTS;
|
|
50
|
+
}
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
const str = (v: Cfg[string]) =>
|
|
54
|
+
typeof v === "string" && v.trim() ? v.trim() : undefined;
|
|
55
|
+
|
|
56
|
+
const log =
|
|
57
|
+
(name: string) =>
|
|
58
|
+
(...args: unknown[]) =>
|
|
59
|
+
console.log(`[${name}]`, ...args);
|
|
60
|
+
|
|
61
|
+
const toProps = (c: Cfg) => ({
|
|
62
|
+
integratorId: String(c.integratorId ?? ""),
|
|
63
|
+
srcChain: str(c.srcChain),
|
|
64
|
+
srcToken: str(c.srcToken),
|
|
65
|
+
dstChain: str(c.dstChain),
|
|
66
|
+
dstToken: str(c.dstToken),
|
|
67
|
+
dstDisplayToken: str(c.dstDisplayToken),
|
|
68
|
+
highlightedDstTokens: str(c.highlightedDstTokens)?.split(","),
|
|
69
|
+
widgetTitle: str(c.widgetTitle),
|
|
70
|
+
desc: str(c.desc),
|
|
71
|
+
integratorFee: str(c.integratorFee) ? Number(c.integratorFee) : undefined,
|
|
72
|
+
integratorFeeReceiverAddress: str(c.integratorFeeReceiverAddress),
|
|
73
|
+
override: str(c.apiUrl) ? { apiUrl: String(c.apiUrl) } : undefined,
|
|
74
|
+
styles: {
|
|
75
|
+
mainAccentLight: str(c.mainAccentLight),
|
|
76
|
+
mainAccentDark: str(c.mainAccentDark),
|
|
77
|
+
textSecondary: str(c.textSecondary),
|
|
78
|
+
fontFamily: str(c.fontFamily),
|
|
79
|
+
width: str(c.width),
|
|
80
|
+
},
|
|
81
|
+
lightTheme: !!c.lightTheme,
|
|
82
|
+
defaultWalletPicker: !!c.defaultWalletPicker,
|
|
83
|
+
bridge: !!c.bridge,
|
|
84
|
+
srcChainLocked: !!c.srcChainLocked,
|
|
85
|
+
srcTokenLocked: !!c.srcTokenLocked,
|
|
86
|
+
dstChainLocked: !!c.dstChainLocked,
|
|
87
|
+
dstTokenLocked: !!c.dstTokenLocked,
|
|
88
|
+
onSrcChainChange: log("onSrcChainChange"),
|
|
89
|
+
onSrcTokenChange: log("onSrcTokenChange"),
|
|
90
|
+
onDstChainChange: log("onDstChainChange"),
|
|
91
|
+
onDstTokenChange: log("onDstTokenChange"),
|
|
92
|
+
onConnectWallet: log("onConnectWallet"),
|
|
93
|
+
onPendingTransactionsChange: log("onPendingTransactionsChange"),
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
const useChains = (apiUrl: string) => {
|
|
97
|
+
const [chains, setChains] = useState<Chain[]>([]);
|
|
98
|
+
|
|
99
|
+
useEffect(() => {
|
|
100
|
+
fetch(`${apiUrl}/chains?data={}`)
|
|
101
|
+
.then((r) => r.json())
|
|
102
|
+
.then((all: Chain[]) =>
|
|
103
|
+
setChains(
|
|
104
|
+
all.filter(
|
|
105
|
+
(c) =>
|
|
106
|
+
c.web3Environment === "mainnet" &&
|
|
107
|
+
(c.bridgeSupported || c.swapSupported),
|
|
108
|
+
),
|
|
109
|
+
),
|
|
110
|
+
)
|
|
111
|
+
.catch((e) => console.error("failed to load chains", e));
|
|
112
|
+
}, [apiUrl]);
|
|
113
|
+
|
|
114
|
+
return chains;
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
const sortTokens = (tokens: Token[] = []) =>
|
|
118
|
+
[...tokens].sort((a, b) => Number(!!b.quickPick) - Number(!!a.quickPick));
|
|
119
|
+
|
|
120
|
+
type FieldProps = {
|
|
121
|
+
k: string;
|
|
122
|
+
cfg: Cfg;
|
|
123
|
+
set: (key: string, value: string | boolean) => void;
|
|
124
|
+
};
|
|
125
|
+
|
|
126
|
+
// ponytail: declared outside App, otherwise every render remounts the inputs and typing loses focus
|
|
127
|
+
const Text = ({ k, cfg, set, ...rest }: FieldProps & Record<string, unknown>) => (
|
|
128
|
+
<label>
|
|
129
|
+
<span>{k}</span>
|
|
130
|
+
<input
|
|
131
|
+
value={(cfg[k] as string) ?? ""}
|
|
132
|
+
onChange={(e) => set(k, e.target.value)}
|
|
133
|
+
{...rest}
|
|
134
|
+
/>
|
|
135
|
+
</label>
|
|
136
|
+
);
|
|
137
|
+
|
|
138
|
+
const Color = ({ k, cfg, set }: FieldProps) => (
|
|
139
|
+
<label>
|
|
140
|
+
<span>{k}</span>
|
|
141
|
+
<div className="inline">
|
|
142
|
+
<input
|
|
143
|
+
type="color"
|
|
144
|
+
value={(cfg[k] as string) || "#3b82f6"}
|
|
145
|
+
onChange={(e) => set(k, e.target.value)}
|
|
146
|
+
/>
|
|
147
|
+
<input
|
|
148
|
+
value={(cfg[k] as string) ?? ""}
|
|
149
|
+
placeholder="unset"
|
|
150
|
+
onChange={(e) => set(k, e.target.value)}
|
|
151
|
+
/>
|
|
152
|
+
<button onClick={() => set(k, "")}>×</button>
|
|
153
|
+
</div>
|
|
154
|
+
</label>
|
|
155
|
+
);
|
|
156
|
+
|
|
157
|
+
const ChainSelect = ({
|
|
158
|
+
side,
|
|
159
|
+
cfg,
|
|
160
|
+
chains,
|
|
161
|
+
onChange,
|
|
162
|
+
}: {
|
|
163
|
+
side: "src" | "dst";
|
|
164
|
+
cfg: Cfg;
|
|
165
|
+
chains: Chain[];
|
|
166
|
+
onChange: (chainId: string) => void;
|
|
167
|
+
}) => (
|
|
168
|
+
<label>
|
|
169
|
+
<span>{side}Chain</span>
|
|
170
|
+
<select
|
|
171
|
+
value={(cfg[`${side}Chain`] as string) ?? ""}
|
|
172
|
+
onChange={(e) => onChange(e.target.value)}
|
|
173
|
+
>
|
|
174
|
+
<option value="">— any —</option>
|
|
175
|
+
{chains.map((c) => (
|
|
176
|
+
<option key={c.chainId} value={c.chainId}>
|
|
177
|
+
{c.displayName} ({c.ecosystem} · {c.chainId})
|
|
178
|
+
</option>
|
|
179
|
+
))}
|
|
180
|
+
</select>
|
|
181
|
+
</label>
|
|
182
|
+
);
|
|
183
|
+
|
|
184
|
+
const TokenSelect = ({
|
|
185
|
+
k,
|
|
186
|
+
cfg,
|
|
187
|
+
set,
|
|
188
|
+
tokens,
|
|
189
|
+
}: FieldProps & { tokens: Token[] }) => (
|
|
190
|
+
<label>
|
|
191
|
+
<span>{k}</span>
|
|
192
|
+
<select
|
|
193
|
+
value={(cfg[k] as string) ?? ""}
|
|
194
|
+
onChange={(e) => set(k, e.target.value)}
|
|
195
|
+
>
|
|
196
|
+
<option value="">— any —</option>
|
|
197
|
+
{tokens.map((t) => (
|
|
198
|
+
<option key={t.address} value={t.address}>
|
|
199
|
+
{t.symbol}
|
|
200
|
+
</option>
|
|
201
|
+
))}
|
|
202
|
+
</select>
|
|
203
|
+
</label>
|
|
204
|
+
);
|
|
205
|
+
|
|
206
|
+
export const App = () => {
|
|
207
|
+
const [cfg, setCfg] = useState(loadCfg);
|
|
208
|
+
const [mountKey, setMountKey] = useState(0);
|
|
209
|
+
|
|
210
|
+
const chains = useChains(str(cfg.apiUrl) ?? DEFAULT_API);
|
|
211
|
+
const byId = useMemo(
|
|
212
|
+
() => Object.fromEntries(chains.map((c) => [c.chainId, c])),
|
|
213
|
+
[chains],
|
|
214
|
+
);
|
|
215
|
+
|
|
216
|
+
useEffect(() => {
|
|
217
|
+
localStorage.setItem(STORAGE_KEY, JSON.stringify(cfg));
|
|
218
|
+
}, [cfg]);
|
|
219
|
+
|
|
220
|
+
const set = (key: string, value: string | boolean) =>
|
|
221
|
+
setCfg((c) => ({ ...c, [key]: value }));
|
|
222
|
+
|
|
223
|
+
// ponytail: switching a chain invalidates the token picked on the old one
|
|
224
|
+
const setChain = (side: "src" | "dst", chainId: string) =>
|
|
225
|
+
setCfg((c) => ({
|
|
226
|
+
...c,
|
|
227
|
+
[`${side}Chain`]: chainId,
|
|
228
|
+
[`${side}Token`]: "",
|
|
229
|
+
...(side === "dst" ? { dstDisplayToken: "", highlightedDstTokens: "" } : {}),
|
|
230
|
+
}));
|
|
231
|
+
|
|
232
|
+
const reset = () => {
|
|
233
|
+
setCfg(DEFAULTS);
|
|
234
|
+
setMountKey((k) => k + 1);
|
|
235
|
+
};
|
|
236
|
+
|
|
237
|
+
const srcTokens = sortTokens(byId[cfg.srcChain as string]?.tokens);
|
|
238
|
+
const dstTokens = sortTokens(byId[cfg.dstChain as string]?.tokens);
|
|
239
|
+
const highlighted = str(cfg.highlightedDstTokens)?.split(",") ?? [];
|
|
240
|
+
|
|
241
|
+
// ponytail: the widget reads chain/token props only on mount, so remount when they change.
|
|
242
|
+
// Text fields (integratorId, apiUrl) are excluded — they'd remount on every keystroke.
|
|
243
|
+
const remountKey = [
|
|
244
|
+
mountKey,
|
|
245
|
+
cfg.srcChain,
|
|
246
|
+
cfg.srcToken,
|
|
247
|
+
cfg.dstChain,
|
|
248
|
+
cfg.dstToken,
|
|
249
|
+
cfg.dstDisplayToken,
|
|
250
|
+
cfg.highlightedDstTokens,
|
|
251
|
+
].join("|");
|
|
252
|
+
|
|
253
|
+
return (
|
|
254
|
+
<div className="page">
|
|
255
|
+
<style>{CSS}</style>
|
|
256
|
+
|
|
257
|
+
<aside>
|
|
258
|
+
<h2>Widget config</h2>
|
|
259
|
+
|
|
260
|
+
<Text k="integratorId" cfg={cfg} set={set} />
|
|
261
|
+
|
|
262
|
+
<ChainSelect
|
|
263
|
+
side="src"
|
|
264
|
+
cfg={cfg}
|
|
265
|
+
chains={chains}
|
|
266
|
+
onChange={(id) => setChain("src", id)}
|
|
267
|
+
/>
|
|
268
|
+
<TokenSelect k="srcToken" cfg={cfg} set={set} tokens={srcTokens} />
|
|
269
|
+
<ChainSelect
|
|
270
|
+
side="dst"
|
|
271
|
+
cfg={cfg}
|
|
272
|
+
chains={chains}
|
|
273
|
+
onChange={(id) => setChain("dst", id)}
|
|
274
|
+
/>
|
|
275
|
+
<TokenSelect k="dstToken" cfg={cfg} set={set} tokens={dstTokens} />
|
|
276
|
+
<TokenSelect
|
|
277
|
+
k="dstDisplayToken"
|
|
278
|
+
cfg={cfg}
|
|
279
|
+
set={set}
|
|
280
|
+
tokens={dstTokens}
|
|
281
|
+
/>
|
|
282
|
+
|
|
283
|
+
<label>
|
|
284
|
+
<span>highlightedDstTokens (ctrl/cmd + click)</span>
|
|
285
|
+
<select
|
|
286
|
+
multiple
|
|
287
|
+
size={5}
|
|
288
|
+
value={highlighted}
|
|
289
|
+
onChange={(e) =>
|
|
290
|
+
set(
|
|
291
|
+
"highlightedDstTokens",
|
|
292
|
+
[...e.target.selectedOptions].map((o) => o.value).join(","),
|
|
293
|
+
)
|
|
294
|
+
}
|
|
295
|
+
>
|
|
296
|
+
{dstTokens.map((t) => (
|
|
297
|
+
<option key={t.address} value={t.address}>
|
|
298
|
+
{t.symbol}
|
|
299
|
+
</option>
|
|
300
|
+
))}
|
|
301
|
+
</select>
|
|
302
|
+
</label>
|
|
303
|
+
|
|
304
|
+
<Text k="widgetTitle" cfg={cfg} set={set} placeholder="Bridge" />
|
|
305
|
+
<Text k="desc" cfg={cfg} set={set} />
|
|
306
|
+
<Text
|
|
307
|
+
k="integratorFee"
|
|
308
|
+
cfg={cfg}
|
|
309
|
+
set={set}
|
|
310
|
+
type="number"
|
|
311
|
+
step="0.001"
|
|
312
|
+
placeholder="0.003"
|
|
313
|
+
/>
|
|
314
|
+
<Text
|
|
315
|
+
k="integratorFeeReceiverAddress"
|
|
316
|
+
cfg={cfg}
|
|
317
|
+
set={set}
|
|
318
|
+
placeholder="0x…"
|
|
319
|
+
/>
|
|
320
|
+
|
|
321
|
+
<label>
|
|
322
|
+
<span>apiUrl (override)</span>
|
|
323
|
+
<input
|
|
324
|
+
list="api-urls"
|
|
325
|
+
value={(cfg.apiUrl as string) ?? ""}
|
|
326
|
+
placeholder={DEFAULT_API}
|
|
327
|
+
onChange={(e) => set("apiUrl", e.target.value)}
|
|
328
|
+
/>
|
|
329
|
+
<datalist id="api-urls">
|
|
330
|
+
{API_URLS.map((u) => (
|
|
331
|
+
<option key={u} value={u} />
|
|
332
|
+
))}
|
|
333
|
+
</datalist>
|
|
334
|
+
</label>
|
|
335
|
+
|
|
336
|
+
<h3>styles</h3>
|
|
337
|
+
<Color k="mainAccentLight" cfg={cfg} set={set} />
|
|
338
|
+
<Color k="mainAccentDark" cfg={cfg} set={set} />
|
|
339
|
+
<Color k="textSecondary" cfg={cfg} set={set} />
|
|
340
|
+
|
|
341
|
+
<label>
|
|
342
|
+
<span>fontFamily</span>
|
|
343
|
+
<input
|
|
344
|
+
list="fonts"
|
|
345
|
+
value={(cfg.fontFamily as string) ?? ""}
|
|
346
|
+
onChange={(e) => set("fontFamily", e.target.value)}
|
|
347
|
+
/>
|
|
348
|
+
<datalist id="fonts">
|
|
349
|
+
{FONTS.map((f) => (
|
|
350
|
+
<option key={f} value={f} />
|
|
351
|
+
))}
|
|
352
|
+
</datalist>
|
|
353
|
+
</label>
|
|
354
|
+
|
|
355
|
+
<label>
|
|
356
|
+
<span>width — {(cfg.width as string) || "default"}</span>
|
|
357
|
+
<div className="inline">
|
|
358
|
+
<input
|
|
359
|
+
type="range"
|
|
360
|
+
min={300}
|
|
361
|
+
max={600}
|
|
362
|
+
step={10}
|
|
363
|
+
value={parseInt((cfg.width as string) || "420", 10)}
|
|
364
|
+
onChange={(e) => set("width", `${e.target.value}px`)}
|
|
365
|
+
/>
|
|
366
|
+
<button onClick={() => set("width", "")}>×</button>
|
|
367
|
+
</div>
|
|
368
|
+
</label>
|
|
369
|
+
|
|
370
|
+
<h3>flags</h3>
|
|
371
|
+
<div className="flags">
|
|
372
|
+
{BOOL_FIELDS.map((f) => (
|
|
373
|
+
<label key={f} className="check">
|
|
374
|
+
<input
|
|
375
|
+
type="checkbox"
|
|
376
|
+
checked={!!cfg[f]}
|
|
377
|
+
onChange={(e) => set(f, e.target.checked)}
|
|
378
|
+
/>
|
|
379
|
+
{f}
|
|
380
|
+
</label>
|
|
381
|
+
))}
|
|
382
|
+
</div>
|
|
383
|
+
|
|
384
|
+
<div className="inline">
|
|
385
|
+
<button onClick={() => setMountKey((k) => k + 1)}>Remount</button>
|
|
386
|
+
<button onClick={reset}>Reset</button>
|
|
387
|
+
</div>
|
|
388
|
+
|
|
389
|
+
<pre>{JSON.stringify(toProps(cfg), null, 2)}</pre>
|
|
390
|
+
</aside>
|
|
391
|
+
|
|
392
|
+
<main>
|
|
393
|
+
<TxWidget key={remountKey} {...toProps(cfg)} />
|
|
394
|
+
</main>
|
|
395
|
+
</div>
|
|
396
|
+
);
|
|
397
|
+
};
|
|
398
|
+
|
|
399
|
+
const CSS = `
|
|
400
|
+
body { margin: 0; font: 13px/1.4 system-ui, sans-serif; background: #f6f7f9; }
|
|
401
|
+
.page { display: flex; gap: 32px; align-items: flex-start; padding: 24px; }
|
|
402
|
+
aside { width: 340px; display: flex; flex-direction: column; gap: 10px;
|
|
403
|
+
background: #fff; border: 1px solid #e3e5e8; border-radius: 12px; padding: 16px; }
|
|
404
|
+
main { position: sticky; top: 24px; }
|
|
405
|
+
h2, h3 { margin: 4px 0; }
|
|
406
|
+
h3 { color: #667; text-transform: uppercase; font-size: 11px; letter-spacing: .08em; }
|
|
407
|
+
label { display: flex; flex-direction: column; gap: 3px; }
|
|
408
|
+
label > span { color: #667; font-size: 11px; }
|
|
409
|
+
input, select, button { font: inherit; padding: 5px 6px; border: 1px solid #d3d6da;
|
|
410
|
+
border-radius: 6px; background: #fff; }
|
|
411
|
+
input[type=color] { padding: 0; width: 34px; height: 30px; }
|
|
412
|
+
input[type=range] { padding: 0; flex: 1; }
|
|
413
|
+
button { cursor: pointer; }
|
|
414
|
+
.inline { display: flex; gap: 6px; align-items: center; }
|
|
415
|
+
.inline > input:not([type=color]) { flex: 1; min-width: 0; }
|
|
416
|
+
.flags { display: grid; grid-template-columns: 1fr 1fr; gap: 4px; }
|
|
417
|
+
.check { flex-direction: row; align-items: center; gap: 5px; font-size: 12px; }
|
|
418
|
+
.check input { margin: 0; }
|
|
419
|
+
pre { font-size: 10px; background: #f2f3f5; border-radius: 8px; padding: 10px;
|
|
420
|
+
max-height: 260px; overflow: auto; margin: 0; }
|
|
421
|
+
`;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { Buffer } from "buffer";
|
|
2
|
+
import { createRoot } from "react-dom/client";
|
|
3
|
+
import { App } from "./App";
|
|
4
|
+
|
|
5
|
+
// ponytail: SDK deps (solana/sui) expect a Buffer global in the browser
|
|
6
|
+
globalThis.Buffer ??= Buffer;
|
|
7
|
+
|
|
8
|
+
createRoot(document.getElementById("root")!).render(<App />);
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import react from "@vitejs/plugin-react";
|
|
2
|
+
import { defineConfig } from "vite";
|
|
3
|
+
|
|
4
|
+
export default defineConfig({
|
|
5
|
+
plugins: [react()],
|
|
6
|
+
// ponytail: linked package -> force pre-bundling so its deps resolve from ../node_modules
|
|
7
|
+
optimizeDeps: { include: ["@xswap-link/sdk"] },
|
|
8
|
+
define: { global: "globalThis" },
|
|
9
|
+
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@xswap-link/sdk",
|
|
3
|
-
"version": "0.15.
|
|
3
|
+
"version": "0.15.2",
|
|
4
4
|
"description": "JavaScript SDK for XSwap platform",
|
|
5
5
|
"homepage": "https://github.com/xswap-link/xswap-sdk",
|
|
6
6
|
"repository": {
|
|
@@ -81,6 +81,7 @@
|
|
|
81
81
|
"@types/react-dom": "^18.2.0",
|
|
82
82
|
"@typescript-eslint/eslint-plugin": "^7.1.0",
|
|
83
83
|
"@typescript-eslint/parser": "^7.1.0",
|
|
84
|
+
"autoprefixer": "^10.5.4",
|
|
84
85
|
"babel-jest": "^29.7.0",
|
|
85
86
|
"cross-fetch": "^4.1.0",
|
|
86
87
|
"eslint": "^8.57.0",
|
package/postcss.config.js
CHANGED
|
@@ -4,6 +4,8 @@ module.exports = {
|
|
|
4
4
|
require("postcss-import"),
|
|
5
5
|
// Resolves TailwindCSS
|
|
6
6
|
require("tailwindcss")(),
|
|
7
|
+
// Adds vendor prefixes (e.g. -webkit-backdrop-filter for Safari < 18)
|
|
8
|
+
require("autoprefixer"),
|
|
7
9
|
// Removes comments from CSS and redundant whitespaces
|
|
8
10
|
require("postcss-minify"),
|
|
9
11
|
],
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
// Glyph-tight box (no 16/24px padding frame) so flex gaps around it match
|
|
2
|
+
// the design's spacing exactly. Geometry is Figma's "Vector 8262" chevron
|
|
3
|
+
// rotated to point right.
|
|
4
|
+
export const ChevronRightThinIcon = ({
|
|
5
|
+
className,
|
|
6
|
+
}: {
|
|
7
|
+
className?: string;
|
|
8
|
+
}) => {
|
|
9
|
+
return (
|
|
10
|
+
<svg
|
|
11
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
12
|
+
width="4"
|
|
13
|
+
height="7"
|
|
14
|
+
viewBox="0 0 3.58671 7"
|
|
15
|
+
fill="none"
|
|
16
|
+
stroke="currentColor"
|
|
17
|
+
strokeLinejoin="round"
|
|
18
|
+
className={className}
|
|
19
|
+
>
|
|
20
|
+
<path d="M0 6.5H0.500926L2.79382 4.20711C3.18434 3.81658 3.18434 3.18342 2.79382 2.79289L0.500926 0.5H0" />
|
|
21
|
+
</svg>
|
|
22
|
+
);
|
|
23
|
+
};
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { forwardRef } from "react";
|
|
2
|
+
|
|
3
|
+
const InfoThinIcon = forwardRef<SVGSVGElement, { className?: string }>(
|
|
4
|
+
({ className }, ref) => {
|
|
5
|
+
return (
|
|
6
|
+
<svg
|
|
7
|
+
ref={ref}
|
|
8
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
9
|
+
width="16"
|
|
10
|
+
height="16"
|
|
11
|
+
viewBox="0 0 16 16"
|
|
12
|
+
fill="none"
|
|
13
|
+
className={className || ""}
|
|
14
|
+
>
|
|
15
|
+
<circle cx="8" cy="8" r="8" fill="currentColor" fillOpacity="0.05" />
|
|
16
|
+
<circle
|
|
17
|
+
cx="8"
|
|
18
|
+
cy="8"
|
|
19
|
+
r="7.5"
|
|
20
|
+
stroke="currentColor"
|
|
21
|
+
strokeOpacity="0.05"
|
|
22
|
+
/>
|
|
23
|
+
<g opacity="0.6">
|
|
24
|
+
<path d="M8 7.5V10.5" stroke="currentColor" />
|
|
25
|
+
<path d="M8 5.5V6.5" stroke="currentColor" />
|
|
26
|
+
</g>
|
|
27
|
+
</svg>
|
|
28
|
+
);
|
|
29
|
+
},
|
|
30
|
+
);
|
|
31
|
+
|
|
32
|
+
InfoThinIcon.displayName = "InfoThinIcon";
|
|
33
|
+
export { InfoThinIcon };
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export const RedirectThinIcon = ({ className }: { className?: string }) => {
|
|
2
|
+
return (
|
|
3
|
+
<svg
|
|
4
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
5
|
+
width="12"
|
|
6
|
+
height="12"
|
|
7
|
+
viewBox="0 0 12 12"
|
|
8
|
+
fill="currentColor"
|
|
9
|
+
className={className}
|
|
10
|
+
>
|
|
11
|
+
<path d="M10.9226 10.9226H1.07744V1.07744H5.45455L4.3771 0H0V12H12V7.6229L10.9226 6.54545V10.9226Z" />
|
|
12
|
+
<path d="M7.07071 0L8.14815 1.07744H10.1549L9.15825 2.07407L9.92593 2.84175L10.9226 1.84512V3.83838L12 4.91582V0H7.07071Z" />
|
|
13
|
+
</svg>
|
|
14
|
+
);
|
|
15
|
+
};
|
|
@@ -17,6 +17,7 @@ export * from "./CheckIcon";
|
|
|
17
17
|
export * from "./ChevronDownIcon";
|
|
18
18
|
export * from "./ChevronDownThinIcon";
|
|
19
19
|
export * from "./ChevronDownWideThinIcon";
|
|
20
|
+
export * from "./ChevronRightThinIcon";
|
|
20
21
|
export * from "./ChevronsUpDownThinIcon";
|
|
21
22
|
export * from "./ChevronUpIcon";
|
|
22
23
|
export * from "./CircularProgressIcon";
|
|
@@ -34,12 +35,14 @@ export * from "./HistoryGradientBgIcon";
|
|
|
34
35
|
export * from "./HistoryIcon";
|
|
35
36
|
export * from "./HourGlassIcon";
|
|
36
37
|
export * from "./InfoIcon";
|
|
38
|
+
export * from "./InfoThinIcon";
|
|
37
39
|
export * from "./LinkExternalIcon";
|
|
38
40
|
export * from "./LinkExternalThinIcon";
|
|
39
41
|
export * from "./LoadingRingIcon";
|
|
40
42
|
export * from "./PercentageIcon";
|
|
41
43
|
export * from "./ProgressRingThinIcon";
|
|
42
44
|
export * from "./RankIcon";
|
|
45
|
+
export * from "./RedirectThinIcon";
|
|
43
46
|
export * from "./SearchIcon";
|
|
44
47
|
export * from "./SearchThinIcon";
|
|
45
48
|
export * from "./SettingsGradientBgIcon";
|
|
@@ -16,7 +16,9 @@ export const Header = ({ onClose }: Props) => {
|
|
|
16
16
|
|
|
17
17
|
return (
|
|
18
18
|
<div className="flex justify-between items-center">
|
|
19
|
-
<div className="text-2xl leading-8 tracking-[0.01em]">
|
|
19
|
+
<div className="text-2xl font-medium leading-8 tracking-[0.01em]">
|
|
20
|
+
{title}
|
|
21
|
+
</div>
|
|
20
22
|
<Controls onClose={onClose} />
|
|
21
23
|
</div>
|
|
22
24
|
);
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import {
|
|
2
|
-
|
|
2
|
+
ChevronRightThinIcon,
|
|
3
3
|
LinkExternalThinIcon,
|
|
4
4
|
LoadingRingIcon,
|
|
5
5
|
} from "@src/assets/icons";
|
|
@@ -170,7 +170,7 @@ export const ActivityCard = ({ transaction }: Props) => {
|
|
|
170
170
|
<span>{srcToken?.symbol || "?"}</span>
|
|
171
171
|
{dstToken && (
|
|
172
172
|
<>
|
|
173
|
-
<
|
|
173
|
+
<ChevronRightThinIcon className="shrink-0 opacity-50" />
|
|
174
174
|
<span className="truncate">{dstToken.symbol}</span>
|
|
175
175
|
</>
|
|
176
176
|
)}
|
|
@@ -23,10 +23,13 @@ export const ReorderButton: FC = () => {
|
|
|
23
23
|
|
|
24
24
|
return (
|
|
25
25
|
<div className="h-0 w-full relative z-[1]">
|
|
26
|
+
{/* Opaque bg on purpose: Chromium ignores backdrop-filter nested
|
|
27
|
+
inside the widget's own backdrop-blur, so a translucent bg would
|
|
28
|
+
let the panel edges show through the button. */}
|
|
26
29
|
<button
|
|
27
30
|
onClick={onReorderClick}
|
|
28
31
|
aria-label="Switch target token with the source token"
|
|
29
|
-
className="absolute left-1/2 -translate-x-1/2 -translate-y-1/2 flex items-center justify-center w-8 h-8 rounded-md backdrop-blur-[24px] bg-t_bg_secondary bg-
|
|
32
|
+
className="absolute left-1/2 -translate-x-1/2 -translate-y-1/2 flex items-center justify-center w-8 h-8 rounded-md backdrop-blur-[24px] bg-t_bg_secondary bg-gradient-to-r from-t_bg_tertiary/5 to-t_bg_tertiary/5 hover:from-t_bg_tertiary/10 hover:to-t_bg_tertiary/10 transition-colors"
|
|
30
33
|
>
|
|
31
34
|
<div className="flex text-t_text_primary opacity-50">
|
|
32
35
|
<ChevronsUpDownThinIcon />
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { InfoThinIcon } from "@src/assets/icons";
|
|
2
2
|
import { ToggleButton, Tooltip } from "@src/components";
|
|
3
3
|
import { useSwapContext } from "@src/context";
|
|
4
4
|
import { useRef } from "react";
|
|
@@ -26,9 +26,9 @@ export const Delivery = () => {
|
|
|
26
26
|
triggerRef={tooltipTriggerRef}
|
|
27
27
|
id="express-delivery-tooltip"
|
|
28
28
|
/>
|
|
29
|
-
<
|
|
29
|
+
<InfoThinIcon
|
|
30
30
|
ref={tooltipTriggerRef}
|
|
31
|
-
className="cursor-help
|
|
31
|
+
className="cursor-help text-t_text_primary w-4 h-4 shrink-0"
|
|
32
32
|
/>
|
|
33
33
|
</div>
|
|
34
34
|
</div>
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { InfoThinIcon } from "@src/assets/icons";
|
|
2
2
|
import { ToggleButton } from "@src/components/ToggleButton";
|
|
3
3
|
import { Tooltip } from "@src/components/Tooltip";
|
|
4
4
|
import { useSwapContext } from "@src/context";
|
|
@@ -27,9 +27,9 @@ export const InfiniteApproval = () => {
|
|
|
27
27
|
triggerRef={tooltipTriggerRef}
|
|
28
28
|
id="infinite-approval-tooltip"
|
|
29
29
|
/>
|
|
30
|
-
<
|
|
30
|
+
<InfoThinIcon
|
|
31
31
|
ref={tooltipTriggerRef}
|
|
32
|
-
className="cursor-help
|
|
32
|
+
className="cursor-help text-t_text_primary w-4 h-4 shrink-0"
|
|
33
33
|
/>
|
|
34
34
|
</div>
|
|
35
35
|
</div>
|
|
@@ -31,7 +31,7 @@ export const Slippage = () => {
|
|
|
31
31
|
<div className="flex flex-col gap-4">
|
|
32
32
|
<div className="flex items-center gap-2">
|
|
33
33
|
<h2 className="text-sm leading-6 tracking-[0.01em] text-t_text_primary">
|
|
34
|
-
|
|
34
|
+
Slippage
|
|
35
35
|
</h2>
|
|
36
36
|
<div className="w-0.5 h-0.5 bg-t_text_primary opacity-25 shrink-0" />
|
|
37
37
|
<p className="text-sm leading-6 tracking-[0.01em] text-t_text_primary text-opacity-50">
|
|
@@ -77,11 +77,13 @@ export const Slippage = () => {
|
|
|
77
77
|
</div>
|
|
78
78
|
</div>
|
|
79
79
|
<div className="flex flex-col gap-3">
|
|
80
|
-
<p className="text-[10px]
|
|
80
|
+
<p className="text-[10px] leading-4 tracking-[0.015em] text-t_text_primary text-opacity-30">
|
|
81
81
|
NOTE.
|
|
82
82
|
</p>
|
|
83
83
|
<p className="text-sm leading-[18px] tracking-[0.01em] text-t_text_primary text-opacity-40">
|
|
84
|
-
{
|
|
84
|
+
{/* The design sets the leading term in Medium, the rest Regular. */}
|
|
85
|
+
<span className="font-medium">Slippage</span>
|
|
86
|
+
{` defines how much a price can change before your
|
|
85
87
|
transfer falls back to USDC. If the price moves more than your
|
|
86
88
|
tolerance allows, you'll receive USDC on your destination chain.`}
|
|
87
89
|
</p>
|