Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
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
4 changes: 3 additions & 1 deletion packages/app/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@
"css:build": "tailwindcss -c ./tailwind.config.js -i ./src/app.css -o ./src/styles/common.css --minify",
"css:watch": "tailwindcss -c ./tailwind.config.js -i ./src/app.css -o ./src/styles/common.css --watch",
"css:build:all": "yarn css:build && yarn workspace arb-token-bridge-ui css:build && yarn workspace portal css:build",
"css:watch:all": "yarn css:watch && yarn workspace arb-token-bridge-ui css:watch && yarn workspace portal css:watch"
"css:watch:all": "yarn css:watch && yarn workspace arb-token-bridge-ui css:watch && yarn workspace portal css:watch",
"test": "vitest --config vitest.config.ts --watch",
"test:ci": "vitest --config vitest.config.ts --run"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html

exports[`mapSourceTokensToTransferTokens > For other chain, map WETH to WETH and ETH to ETH 1`] = `
[
{
"address": "0x0000000000000000000000000000000000000002",
"chainId": 1,
"decimals": 18,
"destinationToken": {
"address": "0x0000000000000000000000000000000000000001",
"chainId": 55244,
"decimals": 18,
"name": "Token",
"priceUSD": "1",
"symbol": "WETH",
},
"name": "Token",
"priceUSD": "1",
"symbol": "WETH",
},
{
"address": "0x0000000000000000000000000000000000000002",
"chainId": 1,
"decimals": 18,
"destinationToken": {
"address": "0x0000000000000000000000000000000000000001",
"chainId": 55244,
"decimals": 18,
"name": "Token",
"priceUSD": "1",
"symbol": "ETH",
},
"name": "Token",
"priceUSD": "1",
"symbol": "ETH",
},
]
`;

exports[`mapSourceTokensToTransferTokens > from ApeChain to ArbitrumOne, withdraw WETH to WETH 1`] = `
[
{
"address": "0x0000000000000000000000000000000000000002",
"chainId": 33139,
"decimals": 18,
"destinationToken": {
"address": "0x0000000000000000000000000000000000000001",
"chainId": 42161,
"decimals": 18,
"name": "Token",
"priceUSD": "1",
"symbol": "WETH",
},
"name": "Token",
"priceUSD": "1",
"symbol": "WETH",
},
]
`;

exports[`mapSourceTokensToTransferTokens > maps ETH deposits into ApeChain to WETH destination token 1`] = `
[
{
"address": "0x0000000000000000000000000000000000000001",
"chainId": 42161,
"decimals": 18,
"destinationToken": {
"address": "0x0000000000000000000000000000000000000001",
"chainId": 33139,
"decimals": 18,
"name": "Token",
"priceUSD": "1",
"symbol": "WETH",
},
"name": "Token",
"priceUSD": "1",
"symbol": "ETH",
},
{
"address": "0x0000000000000000000000000000000000000001",
"chainId": 42161,
"decimals": 18,
"destinationToken": {
"address": "0x0000000000000000000000000000000000000001",
"chainId": 33139,
"decimals": 18,
"name": "Token",
"priceUSD": "1",
"symbol": "WETH",
},
"name": "Token",
"priceUSD": "1",
"symbol": "WETH",
},
]
`;
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
import { CoinKey } from '@lifi/sdk';
import { describe, expect, it } from 'vitest';

import { ChainId } from '@/bridge/types/ChainId';

import type { LifiTokenWithCoinKey } from '../../registry';
import { mapSourceTokensToTransferTokens } from './route';

const buildToken = (
overrides: Partial<LifiTokenWithCoinKey> & Pick<LifiTokenWithCoinKey, 'coinKey'>,
): LifiTokenWithCoinKey => ({
address: '0x0000000000000000000000000000000000000001',
name: 'Token',
symbol: overrides.coinKey,
decimals: 18,
priceUSD: '1',
chainId: 42161,
...overrides,
});

describe('mapSourceTokensToTransferTokens', () => {
it('maps ETH deposits into ApeChain to WETH destination token', () => {
const sourceTokens = [
buildToken({ coinKey: CoinKey.ETH, chainId: 42161 }),
buildToken({ coinKey: CoinKey.WETH, chainId: 42161 }),
];
const destinationTokensByCoinKey = {
WETH: buildToken({ chainId: 33139, coinKey: CoinKey.WETH }),
};

const result = mapSourceTokensToTransferTokens({
sourceTokens,
destinationTokensByCoinKey,
destinationChainId: ChainId.ApeChain,
});

expect(result).toHaveLength(2);
// Both ETH and WETH on Arbitrum map to WETH on ApeChain
expect(result[0]?.destinationToken).toEqual(result[1]?.destinationToken);
expect(result).toMatchSnapshot();
});

it('from ApeChain to ArbitrumOne, withdraw WETH to WETH', () => {
const sourceTokens = [
buildToken({
coinKey: CoinKey.WETH,
chainId: 33139,
address: '0x0000000000000000000000000000000000000002',
}),
];
const destinationTokensByCoinKey = {
WETH: buildToken({ chainId: 42161, coinKey: CoinKey.WETH }),
};

const result = mapSourceTokensToTransferTokens({
sourceTokens,
destinationTokensByCoinKey,
destinationChainId: ChainId.ArbitrumOne,
});

expect(result).toHaveLength(1);
expect(result).toMatchSnapshot();
});

it('For other chain, map WETH to WETH and ETH to ETH', () => {
const sourceTokens = [
buildToken({
coinKey: CoinKey.WETH,
chainId: 1,
address: '0x0000000000000000000000000000000000000002',
}),
buildToken({
coinKey: CoinKey.ETH,
chainId: 1,
address: '0x0000000000000000000000000000000000000002',
}),
];
const destinationTokensByCoinKey = {
WETH: buildToken({ chainId: 55244, coinKey: CoinKey.WETH }),
ETH: buildToken({ chainId: 55244, coinKey: CoinKey.ETH }),
};

const result = mapSourceTokensToTransferTokens({
sourceTokens,
destinationTokensByCoinKey,
destinationChainId: ChainId.ArbitrumOne,
});

expect(result).toHaveLength(2);
expect(result).toMatchSnapshot();
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
import { CoinKey } from '@lifi/sdk';
import { NextResponse } from 'next/server';

import {
type LifiTokenInfo,
type LifiTokensApiResponse,
type LifiTransferToken,
} from '@/bridge/app/api/crosschain-transfers/lifiTokens';
import { ChainId } from '@/bridge/types/ChainId';

import {
ALLOWED_DESTINATION_CHAIN_IDS,
ALLOWED_SOURCE_CHAIN_IDS,
LifiTokenWithCoinKey,
getLifiTokenRegistry,
} from '../../registry';

export const dynamic = 'force-static';
export const revalidate = 60 * 60; // 1 hour

type RouteParams = {
sourceChainId: string;
destinationChainId: string;
};

const stripCoinKey = ({ coinKey, ...token }: LifiTokenWithCoinKey): LifiTokenInfo => token;

type MapTokensParams = {
sourceTokens: LifiTokenWithCoinKey[];
destinationTokensByCoinKey: Record<string, LifiTokenWithCoinKey>;
destinationChainId: number;
};

export const mapSourceTokensToTransferTokens = ({
sourceTokens,
destinationTokensByCoinKey,
destinationChainId,
}: MapTokensParams): LifiTransferToken[] => {
return sourceTokens.reduce<LifiTransferToken[]>((acc, token) => {
const destinationToken =
destinationChainId === ChainId.ApeChain && token.coinKey === CoinKey.ETH
? destinationTokensByCoinKey[CoinKey.WETH]
: destinationTokensByCoinKey[token.coinKey];

if (!destinationToken) {
return acc;
}

acc.push({
...stripCoinKey(token),
destinationToken: stripCoinKey(destinationToken),
});

return acc;
}, []);
};

export async function GET(
_request: Request,
{ params }: { params: RouteParams },
): Promise<NextResponse<LifiTokensApiResponse>> {
const sourceChainId = Number(params.sourceChainId);
const destinationChainId = Number(params.destinationChainId);

if (
!ALLOWED_SOURCE_CHAIN_IDS.includes(sourceChainId) ||
!ALLOWED_DESTINATION_CHAIN_IDS.includes(destinationChainId)
) {
return NextResponse.json(
{ message: 'Chain pair not supported for LiFi tokens' },
{ status: 400 },
);
}

try {
const { tokensByChain, tokensByChainAndCoinKey } = await getLifiTokenRegistry();

const sourceTokens = tokensByChain[sourceChainId] ?? [];
const destinationTokensByCoinKey = tokensByChainAndCoinKey[destinationChainId];

if (
!sourceTokens.length ||
!destinationTokensByCoinKey ||
Object.keys(destinationTokensByCoinKey).length === 0
) {
return NextResponse.json(
{
data: [],
},
{ status: 200 },
);
}

const tokens = mapSourceTokensToTransferTokens({
sourceTokens,
destinationTokensByCoinKey,
destinationChainId,
});

return NextResponse.json({ data: tokens }, { status: 200 });
} catch (error: any) {
return NextResponse.json(
{
message: error?.message ?? 'Unable to load LiFi tokens',
},
{ status: 500 },
);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import { CoinKey } from '@lifi/sdk';
import { ChainId } from '@lifi/sdk';
import { describe, expect, it } from 'vitest';

import { handleUSDC } from './registry';

const placeholderToken = {
address: '0x0000000000000000000000000000000000000000',
name: 'Ether',
symbol: 'ETH',
decimals: 18,
priceUSD: '0',
};

describe('handleUSDC', () => {
it('drops USDCe on chains where native USDC exist', () => {
expect(
handleUSDC({ ...placeholderToken, chainId: ChainId.ARB, coinKey: CoinKey.USDCe }),
).toBeNull();
expect(
handleUSDC({ ...placeholderToken, chainId: ChainId.ETH, coinKey: CoinKey.USDCe }),
).toBeNull();
expect(
handleUSDC({ ...placeholderToken, chainId: ChainId.BAS, coinKey: CoinKey.USDCe }),
).toBeNull();
});

it('keeps USDCe on chains without native USDC', () => {
expect(
handleUSDC({ ...placeholderToken, chainId: ChainId.APE, coinKey: CoinKey.USDCe }),
).toEqual({
...placeholderToken,
chainId: ChainId.APE,
coinKey: CoinKey.USDC,
});
expect(
handleUSDC({ ...placeholderToken, chainId: ChainId.SUP, coinKey: CoinKey.USDCe }),
).toEqual({
...placeholderToken,
chainId: ChainId.SUP,
coinKey: CoinKey.USDC,
});
});

it('returns original token for non USDC tokens', () => {
expect(
handleUSDC({ ...placeholderToken, chainId: ChainId.BAS, coinKey: CoinKey.WBTC }),
).toEqual({
...placeholderToken,
chainId: ChainId.BAS,
coinKey: CoinKey.WBTC,
});
expect(
handleUSDC({ ...placeholderToken, chainId: ChainId.APE, coinKey: CoinKey.WETH }),
).toEqual({
...placeholderToken,
chainId: ChainId.APE,
coinKey: CoinKey.WETH,
});
});
});
Loading
Loading