Skip to content

Commit 10ba152

Browse files
authored
feat: add v3 quote swap flow (#1590)
* feat: remove redundant code * fix: fix next playground build issues, add exact=true to bunfig * feat: implement improved token selection UI, rework balance calculation in swapkit context * feat: improve balance state calculation, improve wallet drawer ui * fix: fix hook after balance naming changes * feat: switch to built-in assetValue staticTokensMap, load tokens in the swapkit-context module * feat: add small optimisation for calculating filtered values * fix: fix createSwapKit sdk type errors * fix: fix swapkit ui type errors * chore: revert WalletConnectButton and keystore dialog changes * chore: apply changes after review * chore: revert type changes * fix: fix types * fix: resolve missing dependencies issue, set exact=true in bunfig.toml * feat: set up the dialog components for keystore * feat: add tabs from shadcn/ui, improve dialog footer layout * feat: restore and refactor some of the keystore connect implementation * feat: add shadcn/ui components for implementing rich forms * feat: move keystore connection state outside of swapkit context, use react-hook-form for WalletKeystoreConnectDialog * feat: add loading states, move file input onChange handler outside of jsx * chore: reorder imports * feat: show new keystore connect dialog on keystore click * chore: remove old keystore dialog, remove dialog components outside of the scope * chore: update bun.lock * feat: tweak dialog styles to handle edge cases * feat: add missing variables, handle styling edge cases * feat: add setup for v3 quote and swap requets * feat: move into new way of overriding endpoints using SKConfig * feat: adjust swap flow to match API changes * chore: remove redundant code, fix smaller issues * feat: add simple localStorage form integration, remove custom apiKey field * feat: add `sk-ui-` prefix to widget's tailwindcss config, update @swapkit/ui to use prefixed classNames (#1591) * feat: update variables and tailwind config to use prefix * chore: replace tailwind classes in @swapkit/ui to match prefix * feat: set up sk-ui- prefix in tailwind config, disable preflight, update dependencies * feat: convert missing classNames to prefixed ones
1 parent 8172ce1 commit 10ba152

37 files changed

+582
-452
lines changed

bun.lock

Lines changed: 68 additions & 104 deletions
Large diffs are not rendered by default.

packages/helpers/src/api/swapkitApi/endpoints.ts

Lines changed: 43 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import {
88
SKConfig,
99
SwapKitError,
1010
} from "@swapkit/helpers";
11-
11+
import { match, P } from "ts-pattern";
1212
import {
1313
type BalanceResponse,
1414
type BrokerDepositChannelParams,
@@ -24,6 +24,8 @@ import {
2424
PriceResponseSchema,
2525
type QuoteRequest,
2626
type QuoteResponse,
27+
type QuoteResponseRoute,
28+
QuoteResponseRouteItem,
2729
QuoteResponseSchema,
2830
type TokenListProvidersResponse,
2931
type TokensResponseV2,
@@ -32,12 +34,7 @@ import {
3234
type TrackingRequest,
3335
} from "./types";
3436

35-
const SKRequestClient = RequestClient.extend({
36-
dynamicHeader: () => {
37-
const { swapKit } = SKConfig.get("apiKeys");
38-
return swapKit ? { "x-api-key": swapKit } : {};
39-
},
40-
});
37+
export const SKRequestClient = RequestClient;
4138

4239
export async function getTrackerDetails(json: TrackingRequest) {
4340
const response = await SKRequestClient.post<TrackerResponse>(getApiUrl("/track"), { json });
@@ -57,6 +54,10 @@ export async function getTrackerDetails(json: TrackingRequest) {
5754
}
5855

5956
export async function getSwapQuote(json: QuoteRequest) {
57+
const { getQuote } = SKConfig.get("endpoints");
58+
59+
if (getQuote) return getQuote(json);
60+
6061
const response = await SKRequestClient.post<QuoteResponse>(getApiUrl("/quote"), { json });
6162

6263
if (response.error) {
@@ -77,6 +78,27 @@ export async function getSwapQuote(json: QuoteRequest) {
7778
}
7879
}
7980

81+
export async function getRouteWithTx(json: { routeId: string; sourceAddress: string; destinationAddress: string }) {
82+
const { getRouteWithTx } = SKConfig.get("endpoints");
83+
84+
if (getRouteWithTx) return getRouteWithTx(json);
85+
86+
const response = await SKRequestClient.post<QuoteResponseRoute>(getApiUrl("/swap"), { json });
87+
88+
try {
89+
const parsedResponse = QuoteResponseRouteItem.safeParse(response);
90+
91+
if (!parsedResponse.success) {
92+
throw new SwapKitError("api_v2_invalid_response", parsedResponse.error);
93+
}
94+
95+
return parsedResponse.data;
96+
} catch (error) {
97+
console.error(new SwapKitError("api_v2_invalid_response", error));
98+
return response;
99+
}
100+
}
101+
80102
export async function getChainBalance<T extends Chain>({
81103
chain,
82104
address,
@@ -187,9 +209,20 @@ export async function getNearDepositChannel(body: NearDepositChannelParams) {
187209
}
188210

189211
function getApiUrl(path?: `/${string}`) {
190-
const { isDev, apiUrl, devApiUrl } = SKConfig.get("envs");
191-
192-
return `${isDev ? devApiUrl : apiUrl}${path}`;
212+
const { isDev, apiUrl, devApiUrl, experimental_apiUrlQuote, experimental_apiUrlSwap } = SKConfig.get("envs");
213+
214+
const defaultUrl = `${isDev ? devApiUrl : apiUrl}${path}`;
215+
216+
return match({ experimental_apiUrlQuote, experimental_apiUrlSwap, path })
217+
.with(
218+
{ experimental_apiUrlQuote: P.string.startsWith("http"), path: "/quote" },
219+
({ experimental_apiUrlQuote, path }) => `${experimental_apiUrlQuote}${path}`,
220+
)
221+
.with(
222+
{ experimental_apiUrlSwap: P.string.startsWith("http"), path: "/swap" },
223+
({ experimental_apiUrlSwap, path }) => `${experimental_apiUrlSwap}${path}`,
224+
)
225+
.otherwise(() => defaultUrl);
193226
}
194227

195228
function evmAssetHasAddress(assetString: string) {

packages/helpers/src/api/swapkitApi/types.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -553,7 +553,7 @@ const QuoteResponseRouteLegItem = object({
553553
sellAsset: string().describe("Asset to sell"),
554554
});
555555

556-
const QuoteResponseRouteItem = object({
556+
export const QuoteResponseRouteItem = object({
557557
buyAsset: string().describe("Asset to buy"),
558558
destinationAddress: string().describe("Destination address"),
559559
estimatedTime: optional(EstimatedTimeSchema),
@@ -566,6 +566,7 @@ const QuoteResponseRouteItem = object({
566566
memo: optional(string().describe("Memo")),
567567
meta: RouteQuoteMetadataV2Schema,
568568
providers: array(z.enum(ProviderName)),
569+
routeId: string().describe("Route ID"),
569570
sellAmount: string().describe("Sell amount"),
570571
sellAsset: string().describe("Asset to sell"),
571572
sourceAddress: string().describe("Source address"),

packages/helpers/src/modules/swapKitConfig.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import {
88
} from "@swapkit/types";
99
import { create } from "zustand";
1010
import { useShallow } from "zustand/shallow";
11-
import type { BalanceResponse } from "../api";
11+
import type { BalanceResponse, QuoteRequest, QuoteResponse, QuoteResponseRoute } from "../api";
1212
import { WalletOption } from "../types";
1313
import type { FeeMultiplierConfig } from "./feeMultiplier";
1414

@@ -38,6 +38,8 @@ export type SKConfigIntegrations = {
3838

3939
export type CustomApiEndpoints = {
4040
getBalance: ({ chain, address }: { chain: Chain; address: string }) => Promise<BalanceResponse>;
41+
getQuote: (json: QuoteRequest) => Promise<QuoteResponse>;
42+
getRouteWithTx: (json: { routeId: string }) => Promise<QuoteResponseRoute>;
4143
};
4244

4345
const rpcUrls = AllChains.reduce(
@@ -60,6 +62,9 @@ const initialState = {
6062
envs: {
6163
apiUrl: "https://api.swapkit.dev",
6264
devApiUrl: "https://dev-api.swapkit.dev",
65+
experimental_apiKey: null as string | null,
66+
experimental_apiUrlQuote: null as string | null,
67+
experimental_apiUrlSwap: null as string | null,
6368
isDev: false,
6469
isStagenet: false,
6570
},
@@ -112,6 +117,7 @@ export const useSwapKitStore = create<SwapKitConfigStore>((set) => ({
112117
set((s) => ({
113118
apiKeys: { ...s.apiKeys, ...config?.apiKeys },
114119
chains: s.chains.concat(config?.chains || []),
120+
endpoints: { ...s.endpoints, ...config?.endpoints },
115121
envs: { ...s.envs, ...config?.envs },
116122
feeMultipliers: config?.feeMultipliers || s.feeMultipliers,
117123
integrations: { ...s.integrations, ...config?.integrations },
@@ -138,6 +144,7 @@ export const useSwapKitConfig = () =>
138144
useShallow((state) => ({
139145
apiKeys: state?.apiKeys,
140146
chains: state?.chains,
147+
endpoints: state?.endpoints,
141148
envs: state?.envs,
142149
feeMultipliers: state?.feeMultipliers,
143150
integrations: state?.integrations,

packages/sdk/src/index.ts

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -114,13 +114,11 @@ export const defaultWallets = {
114114
...walletSelectorWallet,
115115
...walletconnectWallet,
116116
...xamanWallet,
117-
};
117+
} as ReturnType<typeof createWallet>;
118118

119119
export function createSwapKit<
120-
PluginName extends string,
121-
WalletName extends string,
122-
Plugins extends Record<PluginName, ReturnType<typeof createPlugin>[PluginName]>,
123-
Wallets extends Record<WalletName, ReturnType<typeof createWallet>[WalletName]>,
120+
Plugins extends ReturnType<typeof createPlugin>,
121+
Wallets extends ReturnType<typeof createWallet>,
124122
>({ config, plugins, wallets }: { config?: SKConfigState; plugins?: Plugins; wallets?: Wallets } = {}) {
125123
const mergedPlugins = { ...defaultPlugins, ...plugins };
126124
const mergedWallets = { ...defaultWallets, ...wallets };

packages/ui/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@
2626
"react": "19.1.1",
2727
"react-hook-form": "7.65.0",
2828
"sonner": "2.0.7",
29-
"tailwind-merge": "3.3.1",
29+
"tailwind-merge": "2.6.0",
3030
"tailwindcss": "3.4.18",
3131
"tailwindcss-animate": "1.0.7",
3232
"ts-pattern": "5.9.0",

packages/ui/src/lib/utils.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
11
import { type ClassValue, clsx } from "clsx";
2-
import { twMerge } from "tailwind-merge";
2+
import { extendTailwindMerge } from "tailwind-merge";
3+
import tailwindConfig from "../../tailwind.config";
4+
5+
const twMergeWithPrefix = extendTailwindMerge({ prefix: tailwindConfig.prefix });
36

47
export function cn(...inputs: ClassValue[]) {
5-
return twMerge(clsx(inputs));
8+
return twMergeWithPrefix(clsx(inputs));
69
}
710

811
export function formatCurrency(amount: number | null) {

packages/ui/src/react/components/asset-icon.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,10 +20,10 @@ export function AssetIcon({ asset, className }: AssetIconProps) {
2020
const assetValue = AssetValue.from({ asset });
2121

2222
return (
23-
<div className={cn("relative size-10", className)}>
23+
<div className={cn("sk-ui-relative sk-ui-size-10", className)}>
2424
<img
2525
alt={assetValue?.ticker}
26-
className={"size-full overflow-hidden rounded-full"}
26+
className={"sk-ui-size-full sk-ui-overflow-hidden sk-ui-rounded-full"}
2727
height={40}
2828
src={`${temp_host}/images/${assetValue?.chain?.toLowerCase()}.${assetValue?.symbol?.toLowerCase()}.png`}
2929
width={40}
@@ -32,7 +32,7 @@ export function AssetIcon({ asset, className }: AssetIconProps) {
3232
{assetValue?.type !== "Native" && (
3333
<img
3434
alt={assetValue?.chain}
35-
className="-bottom-0.5 absolute right-0 size-[45%] rounded-full border-2 border-secondary bg-secondary"
35+
className="sk-ui--bottom-0.5 sk-ui-absolute sk-ui-right-0 sk-ui-size-[45%] sk-ui-rounded-full sk-ui-border-2 sk-ui-border-secondary sk-ui-bg-secondary"
3636
height={24}
3737
src={`${temp_host}/images/${assetValue?.chain?.toLowerCase()}.${assetValue?.chainId?.toLowerCase()}.png`}
3838
width={24}

packages/ui/src/react/components/chain-icon.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,13 +16,13 @@ export function ChainIcon({ chain, className }: ChainIconProps) {
1616

1717
if (!iconUrl) {
1818
return (
19-
<div className={cn("flex items-center justify-center rounded-full bg-card font-medium text-xs", className)}>
19+
<div className={cn("sk-ui-flex sk-ui-items-center sk-ui-justify-center sk-ui-rounded-full sk-ui-bg-card sk-ui-font-medium sk-ui-text-xs", className)}>
2020
{chain?.slice(0, 2)}
2121
</div>
2222
);
2323
}
2424

2525
return (
26-
<img alt={chain} className={cn("rounded-full object-contain", className)} height={24} src={iconUrl} width={24} />
26+
<img alt={chain} className={cn("sk-ui-rounded-full sk-ui-object-contain", className)} height={24} src={iconUrl} width={24} />
2727
);
2828
}

packages/ui/src/react/components/composable/swap-amount-input.tsx

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -18,20 +18,20 @@ export function SwapAmountInput({
1818
setAmount?: (amount: string) => void;
1919
}) {
2020
return (
21-
<div className={cn("flex flex-col items-end", className)}>
21+
<div className={cn("sk-ui-flex sk-ui-flex-col sk-ui-items-end", className)}>
2222
<Input
23-
className="-mr-3 !shadow-none !border-0 !ring-0 !ring-offset-0 bg-transparent text-end font-medium text-2xl"
23+
className="sk-ui--mr-3 !sk-ui-shadow-none !sk-ui-border-0 !sk-ui-ring-0 !sk-ui-ring-offset-0 sk-ui-bg-transparent sk-ui-text-end sk-ui-font-medium sk-ui-text-2xl"
2424
disabled={disabled}
2525
onChange={(e) => setAmount?.(e.target.value)}
2626
placeholder="0.00"
2727
type="text"
2828
value={amount ?? "0.00"}
2929
/>
3030

31-
<div className="flex items-center gap-1">
32-
{isLoading && <Loader2Icon className="size-3.5 animate-spin" />}
31+
<div className="sk-ui-flex sk-ui-items-center sk-ui-gap-1">
32+
{isLoading && <Loader2Icon className="sk-ui-size-3.5 sk-ui-animate-spin" />}
3333

34-
<span className="text-muted-foreground text-sm">{formattedAmountUSD}</span>
34+
<span className="sk-ui-text-muted-foreground sk-ui-text-sm">{formattedAmountUSD}</span>
3535
</div>
3636
</div>
3737
);

0 commit comments

Comments
 (0)