Skip to content
Open
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: 4 additions & 0 deletions .env.e2e.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
E2E_PRIVATE_KEY=
E2E_CHAIN_ID=8453
E2E_BASE_RPC_URL=https://base-rpc.publicnode.com
E2E_ARBITRUM_RPC_URL=https://arbitrum-rpc.publicnode.com
2 changes: 2 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ jobs:
bun-version: latest
- name: Install dependencies
run: bun install --frozen-lockfile
- name: Run checks
run: bun run check
- name: Run unit tests
run: bun run test:unit
- name: Upload coverage artifact
Expand Down
31 changes: 17 additions & 14 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,26 +1,29 @@
# Dependencies
node_modules

# Output
# Build and framework output
.svelte-kit
build
.output
.vercel
.netlify
.wrangler
/.svelte-kit
/build
.claude

# OS
.DS_Store
Thumbs.db
coverage
playwright-report
test-results

# Env
.env
.env.*
!.env.example
!.env.test
!.env.e2e.example

# Vite
vite.config.js.timestamp-*
vite.config.ts.timestamp-*
playwright-report
test-results
# Logs
*.log

# OS / editor
.DS_Store
Thumbs.db

.claude
.claude/*
3 changes: 3 additions & 0 deletions .lintstagedrc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"*.{js,ts,svelte,css,md,json,html}": "prettier --write"
}
6 changes: 5 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,13 +26,17 @@ The project now uses a two-layer automated test suite:
Run:

- `bun run test:unit` for library/unit/integration tests with coverage output
- `bun run test:e2e` for deterministic browser tests
- `bun run test:e2e` for live browser + chain escrow flow tests
- `bun run test:all` to run both

For local Playwright setup:

1. `bun install`
2. `bunx playwright install chromium`
3. Copy `.env.e2e.example` to `.env.e2e` and set `E2E_PRIVATE_KEY`.
4. Ensure the E2E wallet has mainnet gas + tiny USDC balances on Base and Arbitrum.
5. Start E2E with `bun run test:e2e`.
6. If `E2E_PRIVATE_KEY` is not defined, private-key E2E specs are skipped.

## Structure

Expand Down
771 changes: 183 additions & 588 deletions bun.lock

Large diffs are not rendered by default.

27 changes: 12 additions & 15 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@
"dev": "vite dev",
"build": "vite build",
"preview": "vite preview",
"prepare": "husky",
"test": "bun run test:all",
"test:unit": "svelte-kit sync && bun test tests/unit tests/db.test.ts --coverage",
"test:e2e": "svelte-kit sync && bunx playwright test",
Expand All @@ -29,37 +28,35 @@
"@types/bun": "^1.3.8",
"@playwright/test": "^1.55.0",
"drizzle-kit": "^0.31.9",
"dotenv": "^17.2.3",
"eslint": "^9.18.0",
"eslint-config-prettier": "^10.0.1",
"eslint-plugin-svelte": "^3.0.0",
"globals": "^16.0.0",
"husky": "^9.1.7",
"prettier": "^3.4.2",
"prettier-plugin-svelte": "^3.3.3",
"prettier-plugin-tailwindcss": "^0.6.11",
"husky": "9.1.7",
"lint-staged": "16.1.0",
"prettier": "3.4.2",
"prettier-plugin-svelte": "3.3.3",
"prettier-plugin-tailwindcss": "0.6.11",
"svelte": "^5.0.0",
"svelte-check": "^4.0.0",
"tailwindcss": "^4.0.0",
"typescript": "^5.0.0",
"typescript-eslint": "^8.20.0",
"lint-staged": "^16.1.0",
"vite": "^7.1.1"
},
"lint-staged": {
"*.{js,ts,svelte,css,md,json,html}": "prettier --write"
},
"dependencies": {
"@lifi/intent": "0.0.3-alpha.1",
"@electric-sql/pglite": "^0.3.15",
"@metamask/sdk": "^0.34.0",
"@sveltejs/adapter-cloudflare": "^7.0.3",
"@web3-onboard/coinbase": "^2.4.2",
"@web3-onboard/core": "^2.24.1",
"@web3-onboard/injected-wallets": "^2.11.3",
"@web3-onboard/walletconnect": "^2.6.2",
"@web3-onboard/zeal": "^2.1.1",
"@wagmi/connectors": "^7.2.1",
"@wagmi/core": "^3.4.0",
"axios": "^1.9.0",
"base64-js": "^1.5.1",
"drizzle-orm": "^0.45.1",
"rxjs": "^7.8.2",
"viem": "~2.45.1"
"viem": "~2.45.1",
"wagmi": "^3.5.0"
}
}
8 changes: 5 additions & 3 deletions playwright.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,18 @@ import { defineConfig, devices } from "@playwright/test";

export default defineConfig({
testDir: "tests/e2e",
reporter: [["line"], ["html", { open: "never" }]],
timeout: 45_000,
expect: {
timeout: 10_000
},
fullyParallel: true,
fullyParallel: false,
workers: 1,
retries: process.env.CI ? 2 : 0,
use: {
baseURL: "http://127.0.0.1:4173",
trace: "retain-on-failure",
video: "retain-on-failure",
video: process.env.PW_VIDEO_ALL ? "on" : "retain-on-failure",
screenshot: "only-on-failure"
},
projects: [
Expand All @@ -21,7 +23,7 @@ export default defineConfig({
}
],
webServer: {
command: "bunx vite dev --host 127.0.0.1 --port 4173",
command: "bunx vite dev --force --host 127.0.0.1 --port 4173",
url: "http://127.0.0.1:4173",
reuseExistingServer: !process.env.CI,
timeout: 120_000
Expand Down
17 changes: 17 additions & 0 deletions src/lib/appTypes.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import type { Token, Verifier } from "./config";
import type { CreateIntentOptions } from "@lifi/intent";

export type AppTokenContext = {
token: Token;
amount: bigint;
};

export type AppCreateIntentOptions = Omit<
CreateIntentOptions,
"account" | "inputTokens" | "outputTokens"
> & {
inputTokens: AppTokenContext[];
outputTokens: AppTokenContext[];
verifier: Verifier;
account: () => `0x${string}`;
};
2 changes: 1 addition & 1 deletion src/lib/components/BalanceField.svelte
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<script lang="ts">
import { toBigIntWithDecimals } from "$lib/utils/convert";
import { toBigIntWithDecimals } from "@lifi/intent";

let {
value,
Expand Down
18 changes: 9 additions & 9 deletions src/lib/components/GetQuote.svelte
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<script lang="ts">
import { OrderServer } from "$lib/libraries/orderServer";
import type { TokenContext } from "$lib/state.svelte";
import { IntentApi } from "@lifi/intent";
import type { AppTokenContext } from "$lib/appTypes";
import { interval } from "rxjs";
import { isAddress } from "viem";

Expand All @@ -14,16 +14,16 @@
}: {
exclusiveFor: string;
useExclusiveForQuoteRequest?: boolean;
inputTokens: TokenContext[];
outputTokens: TokenContext[];
inputTokens: AppTokenContext[];
outputTokens: AppTokenContext[];
account: () => `0x${string}`;
mainnet: boolean;
} = $props();

const toRawAddress = (value: string): `0x${string}` | undefined =>
isAddress(value, { strict: false }) ? (value as `0x${string}`) : undefined;

const orderServer = $derived(new OrderServer(mainnet));
const intentApi = $derived(new IntentApi(mainnet));

async function getQuoteAndSet() {
try {
Expand All @@ -33,23 +33,23 @@
)
: undefined;

const response = await orderServer.getQuotes({
const response = await intentApi.getQuotes({
user: account(),
userChain: inputTokens[0].token.chain,
userChainId: inputTokens[0].token.chainId,
exclusiveFor: requestedExclusiveFor,
inputs: inputTokens.map(({ token, amount }) => {
return {
sender: account(),
asset: token.address,
chain: token.chain,
chainId: token.chainId,
amount: amount
};
}),
outputs: outputTokens.map(({ token }) => {
return {
receiver: account(),
asset: token.address,
chain: token.chain,
chainId: token.chainId,
amount: 0n
};
})
Expand Down
79 changes: 42 additions & 37 deletions src/lib/components/InputTokenModal.svelte
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
<script lang="ts">
import { chainMap, coinList, type Token } from "$lib/config";
import { coinList, getChainName, type Token } from "$lib/config";
import FieldRow from "$lib/components/ui/FieldRow.svelte";
import FormControl from "$lib/components/ui/FormControl.svelte";
import InlineMetaField from "$lib/components/ui/InlineMetaField.svelte";
import { AssetSelection } from "$lib/libraries/assetSelection";
import store, { type TokenContext } from "$lib/state.svelte";
import { toBigIntWithDecimals } from "$lib/utils/convert";
import { type InteropableAddress, getInteropableAddress } from "$lib/utils/interopableAddresses";
import type { AppTokenContext } from "$lib/appTypes";
import store from "$lib/state.svelte";
import { toBigIntWithDecimals } from "@lifi/intent";
import { type InteropableAddress, getInteropableAddress } from "@lifi/intent";

const v = (num: number | null) => (num ? num : 0);
const formatBalance = (value: bigint, decimals: number) =>
Expand All @@ -17,21 +18,21 @@
currentInputTokens = $bindable()
}: {
active: boolean;
currentInputTokens: TokenContext[];
currentInputTokens: AppTokenContext[];
} = $props();

let inputs = $state<{ [index: InteropableAddress]: number | null }>(
Object.fromEntries(
(currentInputTokens ?? []).map(({ token, amount }) => [
getInteropableAddress(token.address, chainMap[token.chain].id),
getInteropableAddress(token.address, token.chainId),
Number(amount) / 10 ** token.decimals
])
)
);
let enabledByToken = $state<{ [index: InteropableAddress]: boolean }>(
Object.fromEntries(
(currentInputTokens ?? []).map(({ token }) => [
getInteropableAddress(token.address, chainMap[token.chain].id),
getInteropableAddress(token.address, token.chainId),
true
])
)
Expand All @@ -42,7 +43,7 @@
type SortOrder = "largest" | "smallest";
let sortOrder = $state<SortOrder>("largest");
const rowColumns = "4.5rem minmax(0,1fr) 2rem";
const iaddrFor = (token: Token) => getInteropableAddress(token.address, chainMap[token.chain].id);
const iaddrFor = (token: Token) => getInteropableAddress(token.address, token.chainId);

const isEnabled = (address: InteropableAddress) => enabledByToken[address] ?? true;

Expand All @@ -56,7 +57,7 @@
function save() {
// Go over every single non-0 instance in the array:
const inputKeys = Object.keys(inputs) as InteropableAddress[];
const inputTokens: TokenContext[] = [];
const inputTokens: AppTokenContext[] = [];
for (const key of inputKeys) {
// Check that key is a number
const inputValue = v(inputs[key]);
Expand Down Expand Up @@ -116,9 +117,9 @@
}
const balancePromises = selectedIndices.map(
(i) =>
(store.intentType === "compact" ? store.compactBalances : store.balances)[tokens[i].chain][
tokens[i].address
]
(store.intentType === "compact" ? store.compactBalances : store.balances)[
tokens[i].chainId
][tokens[i].address]
);
const balances = await Promise.all(balancePromises);

Expand Down Expand Up @@ -211,31 +212,35 @@
<div>
{#each tokenSet as tkn, rowIndex}
{@const iaddr = iaddrFor(tkn)}
<FieldRow columns={rowColumns} striped index={rowIndex}>
<div class="truncate text-xs font-medium text-gray-700">{tkn.chain}</div>
{#await (store.intentType === "compact" ? store.compactBalances : store.balances)[tkn.chain][tkn.address]}
<InlineMetaField
bind:value={inputs[iaddr]}
metaText="..."
disabled={!isEnabled(iaddr)}
/>
{:then balance}
<InlineMetaField
bind:value={inputs[iaddr]}
metaText={formatBalance(balance, tkn.decimals)}
disabled={!isEnabled(iaddr)}
/>
{:catch _}
<InlineMetaField
bind:value={inputs[iaddr]}
metaText="err"
disabled={!isEnabled(iaddr)}
/>
{/await}
<div class="flex justify-center">
<input type="checkbox" bind:checked={enabledByToken[iaddr]} />
</div>
</FieldRow>
<div data-testid={`input-token-row-${getChainName(tkn.chainId)}`}>
<FieldRow columns={rowColumns} striped index={rowIndex}>
<div class="truncate text-xs font-medium text-gray-700">
{getChainName(tkn.chainId)}
</div>
{#await (store.intentType === "compact" ? store.compactBalances : store.balances)[tkn.chainId][tkn.address]}
<InlineMetaField
bind:value={inputs[iaddr]}
metaText="..."
disabled={!isEnabled(iaddr)}
/>
{:then balance}
<InlineMetaField
bind:value={inputs[iaddr]}
metaText={formatBalance(balance, tkn.decimals)}
disabled={!isEnabled(iaddr)}
/>
{:catch _}
<InlineMetaField
bind:value={inputs[iaddr]}
metaText="err"
disabled={!isEnabled(iaddr)}
/>
{/await}
<div class="flex justify-center">
<input type="checkbox" bind:checked={enabledByToken[iaddr]} />
</div>
</FieldRow>
</div>
{/each}
</div>
</div>
Expand Down
Loading
Loading