Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
124 commits
Select commit Hold shift + click to select a range
4cc5309
initial port
gsteenkamp89 Sep 19, 2025
11d6044
add chains and tokens fetchers
gsteenkamp89 Sep 22, 2025
0aa37c7
update frontend swap api client
gsteenkamp89 Sep 22, 2025
185ada7
add base strategies fro swap and bridge
gsteenkamp89 Sep 26, 2025
1ccac28
get bridge and swap routes. refactor
gsteenkamp89 Sep 26, 2025
32401c3
centralize navigation links
gsteenkamp89 Sep 27, 2025
3739878
style selectorButton
gsteenkamp89 Sep 27, 2025
d66a0da
style input form
gsteenkamp89 Sep 27, 2025
62d8fb4
style balance selector
gsteenkamp89 Sep 27, 2025
23181f3
use swap quote fees
gsteenkamp89 Sep 27, 2025
22cacbf
better button states
gsteenkamp89 Sep 27, 2025
8911b7b
refactor
gsteenkamp89 Sep 28, 2025
259f6e8
show validation
gsteenkamp89 Sep 28, 2025
28e9560
refactor
gsteenkamp89 Sep 30, 2025
2d3a9f9
clean up
gsteenkamp89 Sep 30, 2025
7fd9e83
update validation warning
gsteenkamp89 Sep 30, 2025
ff834f2
update icons and validation logic
gsteenkamp89 Sep 30, 2025
0b0b0c2
add tooltip
gsteenkamp89 Sep 30, 2025
5139625
rsolve swap token info
gsteenkamp89 Sep 30, 2025
79c617d
disable unreachable tokens
gsteenkamp89 Sep 30, 2025
526ca76
filter and sort
gsteenkamp89 Sep 30, 2025
dd6cb80
fixup
gsteenkamp89 Sep 30, 2025
efd6b76
mobile token selector
gsteenkamp89 Oct 1, 2025
d0d4509
add sections
gsteenkamp89 Oct 1, 2025
bee06c8
better types
gsteenkamp89 Oct 1, 2025
020fedb
sort
gsteenkamp89 Oct 1, 2025
5cda61a
refactor
gsteenkamp89 Oct 1, 2025
945f63a
improve searchbar styles
gsteenkamp89 Oct 2, 2025
06d5f1f
restrict tabbing inside modal
gsteenkamp89 Oct 2, 2025
2bfe97b
clean up
gsteenkamp89 Oct 2, 2025
950be65
add dialog
gsteenkamp89 Oct 2, 2025
c6f7540
fixup
gsteenkamp89 Oct 3, 2025
2062525
move balance call to endpoint
gsteenkamp89 Oct 3, 2025
77d3c74
refactor
gsteenkamp89 Oct 3, 2025
fcaaf6d
fixup
gsteenkamp89 Oct 3, 2025
8ef2453
style switch button
gsteenkamp89 Oct 3, 2025
b5d6c9b
reset search on close
gsteenkamp89 Oct 6, 2025
2b6fdfe
show no results warning
gsteenkamp89 Oct 6, 2025
d155f1c
popular tokens, refine search behaviour
gsteenkamp89 Oct 6, 2025
6bf343f
Merge branch 'master' into feat/gsteenkamp-port-existing-ui
gsteenkamp89 Oct 7, 2025
9b4673b
update packages
gsteenkamp89 Oct 7, 2025
3dd29c4
add hype logo
gsteenkamp89 Oct 7, 2025
a4ae00f
disable and debounce inputs
gsteenkamp89 Oct 7, 2025
e62fd3a
add sol logo
gsteenkamp89 Oct 7, 2025
f1cb30a
separate chains
gsteenkamp89 Oct 8, 2025
f08e502
add footer
gsteenkamp89 Oct 8, 2025
19a04b4
add hotkey
gsteenkamp89 Oct 8, 2025
4e88d99
use swap fees
gsteenkamp89 Oct 9, 2025
9575121
cache swap tokens at build time, refetch every minute
gsteenkamp89 Oct 9, 2025
5ebb81a
get swap chains statically
gsteenkamp89 Oct 9, 2025
b889a81
fixup
gsteenkamp89 Oct 9, 2025
a0687a8
switch for usd
gsteenkamp89 Oct 13, 2025
1caac98
show on output side
gsteenkamp89 Oct 13, 2025
ef7bc83
set default route on load & connect
gsteenkamp89 Oct 13, 2025
b78a095
fix max width
gsteenkamp89 Oct 14, 2025
5b02763
fix converted amount scaling issue
gsteenkamp89 Oct 14, 2025
554f7c0
fixup
gsteenkamp89 Oct 14, 2025
8100099
import global css vars from figma
gsteenkamp89 Oct 14, 2025
565910f
fix padding, hover styles for converter
gsteenkamp89 Oct 14, 2025
f29bec8
clean up
gsteenkamp89 Oct 14, 2025
72d5363
reset amounts if set to 0
gsteenkamp89 Oct 14, 2025
65326b1
fix arrow icon
gsteenkamp89 Oct 14, 2025
7a9e970
add all font sizes
gsteenkamp89 Oct 14, 2025
29a8f89
fix hover style
gsteenkamp89 Oct 14, 2025
0ff33f7
hide summary when button expanded
gsteenkamp89 Oct 14, 2025
45d10d3
fix balance animation
gsteenkamp89 Oct 14, 2025
a536dfc
fix confirmation button states
gsteenkamp89 Oct 15, 2025
9448102
fixup
gsteenkamp89 Oct 15, 2025
1a78a15
use bridge-and-swap route
gsteenkamp89 Oct 15, 2025
88bcfbd
redirect from old bridge route
gsteenkamp89 Oct 15, 2025
aa4b2cb
fix all chains token sorting
gsteenkamp89 Oct 15, 2025
86968b8
remove unreachable logic
gsteenkamp89 Oct 17, 2025
c1be7a9
hide summary when expanded
gsteenkamp89 Oct 17, 2025
2c48ed5
fix animation
gsteenkamp89 Oct 17, 2025
a1df6cc
fixup
gsteenkamp89 Oct 17, 2025
065a1b0
fix icon, tab index, scroll behaviour
gsteenkamp89 Oct 20, 2025
a1e1af8
set fallback token image. add api error component
gsteenkamp89 Oct 20, 2025
66b9c52
fix math
gsteenkamp89 Oct 20, 2025
589e032
switch both input units simultaneously
gsteenkamp89 Oct 20, 2025
eae872e
add prefix if usd unit
gsteenkamp89 Oct 20, 2025
b1e1339
fix swap quote refetch and response parsing logic
gsteenkamp89 Oct 21, 2025
454f64d
fix tab index
gsteenkamp89 Oct 21, 2025
727389a
edit recipient address
gsteenkamp89 Oct 22, 2025
6d68992
refactor and style change account modal
gsteenkamp89 Oct 23, 2025
aaec77d
fixup
gsteenkamp89 Oct 23, 2025
96ef0bd
better quote errors
gsteenkamp89 Oct 23, 2025
9d2415a
add native balance support
gsteenkamp89 Oct 23, 2025
af3e606
refactor quoteError logic
gsteenkamp89 Oct 23, 2025
5a43e32
specify tokens, add solana support to balance endpoint
gsteenkamp89 Oct 28, 2025
e47857d
more efficient filtering
gsteenkamp89 Oct 28, 2025
2ec620f
wire up frontend
gsteenkamp89 Oct 28, 2025
84df56d
refactor. handle edge cases
gsteenkamp89 Oct 28, 2025
b8d26a2
add tracing
gsteenkamp89 Oct 28, 2025
a8b516a
prompt user to connect svm wallet if svm route
gsteenkamp89 Oct 28, 2025
51bfa14
allow user to manually set recipient
gsteenkamp89 Oct 28, 2025
67fcb20
fixup
gsteenkamp89 Oct 28, 2025
4c4c8da
fixup balance selector update
gsteenkamp89 Oct 29, 2025
30e8448
resolve longtail token info
gsteenkamp89 Oct 30, 2025
3be3d22
preserve selected chain
gsteenkamp89 Oct 30, 2025
de20df4
fix confirmation button states
gsteenkamp89 Oct 30, 2025
957e4f3
allow quote with placeholder addresses
gsteenkamp89 Oct 30, 2025
92c9b5a
add back prices cus I'm an idiot
gsteenkamp89 Oct 30, 2025
d0f8cb1
fix input colors. allow switching wiht empty input
gsteenkamp89 Oct 31, 2025
017fbcf
fix button states and styling errors
gsteenkamp89 Oct 31, 2025
3f2367b
fix hover effect
gsteenkamp89 Oct 31, 2025
3e22fba
Merge branch 'master' into feat/gsteenkamp-port-existing-ui
gsteenkamp89 Oct 31, 2025
aeebe6c
fixup button colors
gsteenkamp89 Oct 31, 2025
19a83dd
consolidate input validation messages
gsteenkamp89 Oct 31, 2025
11e6bff
consolidate api errors into the button itself
gsteenkamp89 Oct 31, 2025
2624b0e
fixup
gsteenkamp89 Oct 31, 2025
82c948a
better balance caching. refetch on fill
gsteenkamp89 Nov 3, 2025
8121eea
fix fallback image trigger
gsteenkamp89 Nov 3, 2025
68987e6
reset if unreachable. add warning
gsteenkamp89 Nov 4, 2025
31ee5bb
small fixes
gsteenkamp89 Nov 4, 2025
2a43a15
Merge branch 'master' into feat/gsteenkamp-port-existing-ui
gsteenkamp89 Nov 4, 2025
d85ad71
fixup
gsteenkamp89 Nov 4, 2025
7330976
add blockexplorer link
gsteenkamp89 Nov 5, 2025
a0b5da3
let API use default slippage
gsteenkamp89 Nov 5, 2025
a692fac
fixup
gsteenkamp89 Nov 5, 2025
9203d18
Merge branch 'master' into feat/gsteenkamp-port-existing-ui
gsteenkamp89 Nov 5, 2025
d702838
fix(balance-selector): respect usd as unit
gsteenkamp89 Nov 5, 2025
b86662e
rework getter for updated api response. use new fee structure
gsteenkamp89 Nov 6, 2025
d88cf33
style fees. add tooltips
gsteenkamp89 Nov 6, 2025
6d96487
fixup
gsteenkamp89 Nov 6, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion api/_balance.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ export async function getBalance(
);
}

async function getSvmBalance(
export async function getSvmBalance(
chainId: string | number,
account: string,
token: string
Expand Down
8 changes: 8 additions & 0 deletions api/_constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -378,6 +378,14 @@ export const CUSTOM_GAS_TOKENS = {
[CHAIN_IDs.HYPERCORE]: "HYPE",
};

export const EVM_CHAIN_IDs = Object.entries(constants.PUBLIC_NETWORKS)
.filter(([_, chain]) => chain.family !== constants.ChainFamily.SVM)
.map(([chainId]) => Number(chainId));

export const SVM_CHAIN_IDs = Object.entries(constants.PUBLIC_NETWORKS)
.filter(([_, chain]) => chain.family === constants.ChainFamily.SVM)
.map(([chainId]) => Number(chainId));

export const STABLE_COIN_SYMBOLS = Array.from(
new Set([
...sdkConstants.STABLE_COIN_SYMBOLS,
Expand Down
13 changes: 13 additions & 0 deletions api/_providers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -210,3 +210,16 @@ export function getProviderHeaders(

return rpcHeaders?.[String(chainId)];
}

/**
* Gets the Alchemy RPC URL for a given chain ID from the rpc-providers.json configuration
* @param chainId The chain ID to get the Alchemy RPC URL for
* @returns The Alchemy RPC URL or undefined if not available
*/
export function getAlchemyRpcFromConfigJson(
chainId: number
): string | undefined {
const { providers } = rpcProvidersJson;
const alchemyUrls = providers.urls.alchemy as Record<string, string>;
return alchemyUrls?.[String(chainId)];
}
298 changes: 298 additions & 0 deletions api/swap/tokens/_service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,298 @@
import axios from "axios";
import { constants } from "ethers";
import mainnetChains from "../../../src/data/chains_1.json";
import indirectChains from "../../../src/data/indirect_chains_1.json";
import { CHAIN_IDs, TOKEN_SYMBOLS_MAP } from "../../_constants";
import {
ENABLED_ROUTES,
getChainInfo,
getFallbackTokenLogoURI,
} from "../../_utils";

export type SwapToken = {
chainId: number;
address: string;
name: string;
symbol: string;
decimals: number;
logoUrl: string;
priceUsd: string | null;
};

const chains = mainnetChains;
const chainIds = [...chains, ...indirectChains].map((chain) => chain.chainId);

function getUniswapTokens(
uniswapResponse: any,
chainIds: number[],
pricesForLifiTokens: Record<number, Record<string, string>>
): SwapToken[] {
return uniswapResponse.tokens.reduce((acc: SwapToken[], token: any) => {
if (chainIds.includes(token.chainId)) {
acc.push({
chainId: token.chainId,
address: token.address,
name: token.name,
symbol: token.symbol,
decimals: token.decimals,
logoUrl: token.logoURI,
priceUsd: pricesForLifiTokens[token.chainId]?.[token.address] || null,
});
}
return acc;
}, []);
}

function getNativeTokensFromLifiTokens(
lifiTokensResponse: any,
chainIds: number[],
pricesForLifiTokens: Record<number, Record<string, string>>
): SwapToken[] {
return chainIds.reduce((acc: SwapToken[], chainId) => {
const nativeToken = lifiTokensResponse?.tokens?.[chainId]?.find(
(token: any) => token.address === constants.AddressZero
);
if (nativeToken) {
acc.push({
chainId,
address: nativeToken.address,
name: nativeToken.name,
symbol: nativeToken.symbol,
decimals: nativeToken.decimals,
logoUrl: nativeToken.logoURI,
priceUsd: pricesForLifiTokens[chainId]?.[nativeToken.address] || null,
});
}
return acc;
}, []);
}

function getPricesForLifiTokens(lifiTokensResponse: any, chainIds: number[]) {
return chainIds.reduce(
(acc, chainId) => {
const tokens = lifiTokensResponse.tokens[chainId];
if (!tokens) {
return acc;
}
tokens.forEach((token: any) => {
if (!acc[chainId]) {
acc[chainId] = {};
}
acc[chainId][token.address] = token.priceUSD;
});
return acc;
},
{} as Record<number, Record<string, string>>
);
}

function getJupiterTokens(
jupiterTokensResponse: any[],
chainIds: number[]
): SwapToken[] {
if (!chainIds.includes(CHAIN_IDs.SOLANA)) {
return [];
}

return jupiterTokensResponse.reduce((acc: SwapToken[], token: any) => {
if (token.organicScoreLabel === "high") {
acc.push({
chainId: CHAIN_IDs.SOLANA,
address: token.id,
name: token.name,
symbol: token.symbol,
decimals: token.decimals,
logoUrl: token.icon,
priceUsd: token.usdPrice?.toString() || null,
});
}
return acc;
}, []);
}

function getIndirectChainTokens(
chainIds: number[],
pricesForLifiTokens: Record<number, Record<string, string>>
): SwapToken[] {
// Chain ID to use for token price lookups
const PRICE_LOOKUP_CHAIN_ID = CHAIN_IDs.MAINNET;

return indirectChains.flatMap((chain) => {
if (!chainIds.includes(chain.chainId)) {
return [];
}

return chain.outputTokens.map((token) => {
// Try to resolve price using L1 address from TOKEN_SYMBOLS_MAP
let priceUsd: string | null = null;
const tokenInfo =
TOKEN_SYMBOLS_MAP[token.symbol as keyof typeof TOKEN_SYMBOLS_MAP];

if (tokenInfo) {
// Get L1 address
const l1Address = tokenInfo.addresses[PRICE_LOOKUP_CHAIN_ID];
if (l1Address) {
priceUsd =
pricesForLifiTokens[PRICE_LOOKUP_CHAIN_ID]?.[l1Address] || null;
}
}

return {
chainId: chain.chainId,
address: token.address,
name: token.name,
symbol: token.symbol,
decimals: token.decimals,
logoUrl: token.logoUrl,
priceUsd,
};
});
});
}

function getTokensFromEnabledRoutes(
chainIds: number[],
pricesForLifiTokens: Record<number, Record<string, string>>
): SwapToken[] {
const tokens: SwapToken[] = [];
const seenTokens = new Set<string>();

const addToken = (
chainId: number,
tokenSymbol: string,
tokenAddress: string,
l1TokenAddress: string,
isNative: boolean
) => {
const finalAddress = isNative ? constants.AddressZero : tokenAddress;

const tokenKey = `${chainId}-${tokenSymbol}-${finalAddress.toLowerCase()}`;

// Only add each unique token once
if (!seenTokens.has(tokenKey)) {
seenTokens.add(tokenKey);

const tokenInfo =
TOKEN_SYMBOLS_MAP[tokenSymbol as keyof typeof TOKEN_SYMBOLS_MAP];

tokens.push({
chainId,
address: finalAddress,
name: tokenInfo.name,
symbol: tokenSymbol,
decimals: tokenInfo.decimals,
logoUrl: getFallbackTokenLogoURI(l1TokenAddress),
priceUsd: pricesForLifiTokens[chainId]?.[finalAddress] || null,
});
}
};

ENABLED_ROUTES.routes.forEach((route) => {
// Process origin tokens (fromChain)
if (chainIds.includes(route.fromChain)) {
addToken(
route.fromChain,
route.fromTokenSymbol,
route.fromTokenAddress,
route.l1TokenAddress,
route.isNative
);
}

// Process destination tokens (toChain)
if (chainIds.includes(route.toChain)) {
// For destination tokens, check if token is native on that chain
const chainInfo = getChainInfo(route.toChain);
const nativeSymbol =
route.toChain === CHAIN_IDs.LENS ? "GHO" : chainInfo.nativeToken;
const isDestinationNative = route.toTokenSymbol === nativeSymbol;

addToken(
route.toChain,
route.toTokenSymbol,
route.toTokenAddress,
route.l1TokenAddress,
isDestinationNative
);
}
});

return tokens;
}

function deduplicateTokens(tokens: SwapToken[]): SwapToken[] {
const seen = new Map<string, SwapToken>();

tokens.forEach((token) => {
const key = `${token.chainId}-${token.symbol}-${token.address.toLowerCase()}`;

// Keep first occurrence
if (!seen.has(key)) {
seen.set(key, token);
}
});

return Array.from(seen.values());
}

export async function fetchSwapTokensData(
filteredChainIds?: number[]
): Promise<SwapToken[]> {
const targetChainIds = filteredChainIds || chainIds;

const [uniswapTokensResponse, lifiTokensResponse, jupiterTokensResponse] =
await Promise.all([
axios.get("https://tokens.uniswap.org"),
axios.get("https://li.quest/v1/tokens"),
axios.get("https://lite-api.jup.ag/tokens/v2/toporganicscore/24h"),
]);

const pricesForLifiTokens = getPricesForLifiTokens(
lifiTokensResponse.data,
targetChainIds
);

const responseJson: SwapToken[] = [];

// Add Uniswap tokens
const uniswapTokens = getUniswapTokens(
uniswapTokensResponse.data,
targetChainIds,
pricesForLifiTokens
);
responseJson.push(...uniswapTokens);

// Add native tokens from LiFi
const nativeTokens = getNativeTokensFromLifiTokens(
lifiTokensResponse.data,
targetChainIds,
pricesForLifiTokens
);
responseJson.push(...nativeTokens);

// Add Jupiter tokens
const jupiterTokens = getJupiterTokens(
jupiterTokensResponse.data,
targetChainIds
);
responseJson.push(...jupiterTokens);

// Add tokens from indirect chains (e.g., USDT-SPOT on HyperCore)
const indirectChainTokens = getIndirectChainTokens(
targetChainIds,
pricesForLifiTokens
);
responseJson.push(...indirectChainTokens);

// Add tokens from Across' enabled routes (fills gaps from external sources)
const tokensFromEnabledRoutes = getTokensFromEnabledRoutes(
targetChainIds,
pricesForLifiTokens
);
responseJson.push(...tokensFromEnabledRoutes);

// Deduplicate tokens (external sources take precedence)
const deduplicatedTokens = deduplicateTokens(responseJson);

return deduplicatedTokens;
}
Loading
Loading