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 .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,10 @@ jobs:
run: bun install --frozen-lockfile
- name: Run checks
run: bun run check
env:
PRIVATE_POLYMER_MAINNET_ZONE_API_KEY: ${{ secrets.PRIVATE_POLYMER_MAINNET_ZONE_API_KEY }}
PRIVATE_POLYMER_TESTNET_ZONE_API_KEY: ${{ secrets.PRIVATE_POLYMER_TESTNET_ZONE_API_KEY }}
PUBLIC_WALLET_CONNECT_PROJECT_ID: ${{ secrets.PUBLIC_WALLET_CONNECT_PROJECT_ID }}
- name: Run unit tests
run: bun run test:unit
- name: Upload coverage artifact
Expand Down
6 changes: 4 additions & 2 deletions bun.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -46,8 +46,8 @@
"vite": "^7.1.1"
},
"dependencies": {
"@lifi/intent": "0.0.3-alpha.1",
"@electric-sql/pglite": "^0.3.15",
"@lifi/intent": "0.0.4",
"@metamask/sdk": "^0.34.0",
"@sveltejs/adapter-cloudflare": "^7.0.3",
"@wagmi/connectors": "^7.2.1",
Expand Down
8 changes: 5 additions & 3 deletions src/lib/components/InputTokenModal.svelte
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<script lang="ts">
import { coinList, getChainName, type Token } from "$lib/config";
import { evmCoinList, 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";
Expand Down Expand Up @@ -80,7 +80,7 @@

const uniqueInputTokens = $derived([
...new Set(
coinList(store.mainnet)
evmCoinList(store.mainnet)
.map((v) => v.name)
.filter((v) => v !== "eth")
)
Expand All @@ -89,7 +89,9 @@
// svelte-ignore state_referenced_locally
let selectedTokenName = $state<string>(currentInputTokens[0].token.name);
const tokenSet = $derived(
coinList(store.mainnet).filter((v) => v.name.toLowerCase() === selectedTokenName.toLowerCase())
evmCoinList(store.mainnet).filter(
(v) => v.name.toLowerCase() === selectedTokenName.toLowerCase()
)
);

let circuitBreaker = false;
Expand Down
140 changes: 113 additions & 27 deletions src/lib/config.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
import { createPublicClient, createWalletClient, custom, fallback, http } from "viem";
import { createPublicClient, createWalletClient, custom, defineChain, fallback, http } from "viem";
import {
SOLANA_MAINNET_CHAIN_ID,
SOLANA_TESTNET_CHAIN_ID,
SOLANA_DEVNET_CHAIN_ID
} from "@lifi/intent";
import {
arbitrum,
arbitrumSepolia,
Expand Down Expand Up @@ -26,6 +31,40 @@ export const MULTICHAIN_INPUT_SETTLER_COMPACT =
export const ALWAYS_OK_ALLOCATOR = "281773970620737143753120258" as const;
export const POLYMER_ALLOCATOR = "116450367070547927622991121" as const; // 0x02ecC89C25A5DCB1206053530c58E002a737BD11 signing by 0x934244C8cd6BeBDBd0696A659D77C9BDfE86Efe6
export const COIN_FILLER = "0x0000000000eC36B683C2E6AC89e9A75989C22a2e" as const;
// All three Solana chain IDs as numbers (all values are < 2^53, safe as JS numbers)
export const SOLANA_MAINNET_CHAIN_ID_NUM = Number(SOLANA_MAINNET_CHAIN_ID);
export const SOLANA_TESTNET_CHAIN_ID_NUM = Number(SOLANA_TESTNET_CHAIN_ID);
export const SOLANA_DEVNET_CHAIN_ID_NUM = Number(SOLANA_DEVNET_CHAIN_ID);
export const SOLANA_CHAIN_IDS = new Set([
SOLANA_MAINNET_CHAIN_ID_NUM,
SOLANA_TESTNET_CHAIN_ID_NUM,
SOLANA_DEVNET_CHAIN_ID_NUM
]);

export const solanaMainnet = defineChain({
id: SOLANA_MAINNET_CHAIN_ID_NUM,
name: "Solana",
nativeCurrency: { name: "SOL", symbol: "SOL", decimals: 9 },
rpcUrls: { default: { http: ["https://api.mainnet-beta.solana.com"] } }
});
export const solanaTestnet = defineChain({
id: SOLANA_TESTNET_CHAIN_ID_NUM,
name: "Solana Testnet",
nativeCurrency: { name: "SOL", symbol: "SOL", decimals: 9 },
rpcUrls: { default: { http: ["https://api.testnet.solana.com"] } },
testnet: true
});
export const solanaDevnet = defineChain({
id: SOLANA_DEVNET_CHAIN_ID_NUM,
name: "Solana Devnet",
nativeCurrency: { name: "SOL", symbol: "SOL", decimals: 9 },
rpcUrls: { default: { http: ["https://api.devnet.solana.com"] } },
testnet: true
});

// Source: PDA(seed: "polymer", program: SOLANA_POLYMER_ORACLE) — confirmed in default_orders.json
const SOLANA_POLYMER_ORACLE_DEVNET =
"0xe48a6f95df84c28a030f60ba5b74e4a02922a4a5724c9633109f089b2287edfc" as const;
export const WORMHOLE_ORACLE: Partial<Record<number, `0x${string}`>> = {
[ethereum.id]: "0x0000000000000000000000000000000000000000",
[arbitrum.id]: "0x0000000000000000000000000000000000000000",
Expand All @@ -43,7 +82,8 @@ export const POLYMER_ORACLE: Partial<Record<number, `0x${string}`>> = {
[sepolia.id]: "0x00d5b500ECa100F7cdeDC800eC631Aca00BaAC00",
[baseSepolia.id]: "0x00d5b500ECa100F7cdeDC800eC631Aca00BaAC00",
[arbitrumSepolia.id]: "0x00d5b500ECa100F7cdeDC800eC631Aca00BaAC00",
[optimismSepolia.id]: "0x00d5b500ECa100F7cdeDC800eC631Aca00BaAC00"
[optimismSepolia.id]: "0x00d5b500ECa100F7cdeDC800eC631Aca00BaAC00",
[SOLANA_DEVNET_CHAIN_ID_NUM]: SOLANA_POLYMER_ORACLE_DEVNET
};

export type availableAllocators = typeof ALWAYS_OK_ALLOCATOR | typeof POLYMER_ALLOCATOR;
Expand All @@ -62,19 +102,30 @@ export const chainMap = {
katana,
megaeth,
bsc,
polygon
polygon,
solanaMainnet,
solanaTestnet,
solanaDevnet
} as const;
type ChainName = keyof typeof chainMap;
export const chains = Object.keys(chainMap) as ChainName[];
export const chainList = (mainnet: boolean) => {
if (mainnet == true) {
return ["ethereum", "base", "arbitrum", "megaeth", "katana", "polygon", "bsc"] as ChainName[];
} else return ["sepolia", "optimismSepolia", "baseSepolia", "arbitrumSepolia"] as ChainName[];
export const chainList = (mainnet: boolean): ChainName[] => {
if (mainnet) {
return ["ethereum", "base", "arbitrum", "megaeth", "katana", "polygon", "bsc", "solanaMainnet"];
} else {
return [
"sepolia",
"optimismSepolia",
"baseSepolia",
"arbitrumSepolia",
"solanaTestnet",
"solanaDevnet"
];
}
};

export const chainIdList = (mainnet: boolean) => {
return chainList(mainnet).map((name) => chainMap[name].id);
};
export const chainIdList = (mainnet: boolean) =>
chainList(mainnet).map((name) => chainMap[name].id) as number[];

const chainEntries = chains.map((name) => [chainMap[name].id, chainMap[name]] as const);
const chainNameEntries = chains.map((name) => [chainMap[name].id, name] as const);
Expand All @@ -88,8 +139,8 @@ export type Token = {
decimals: number;
};

export const coinList = (mainnet: boolean) => {
if (mainnet == true)
export const coinList = (mainnet: boolean): Token[] => {
if (mainnet)
return [
{
address: `0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913`,
Expand Down Expand Up @@ -192,6 +243,14 @@ export const coinList = (mainnet: boolean) => {
name: "usdc.e",
chainId: polygon.id,
decimals: 6
},
// Solana mainnet — SPL mint addresses encoded as bytes32 (66 chars); ADDRESS_ZERO = native SOL
{ address: ADDRESS_ZERO, name: "sol", chainId: SOLANA_MAINNET_CHAIN_ID_NUM, decimals: 9 },
{
address: `0xc6fa7af3bedbad3a3d65f36aabc97431b1bbe4c2d2f6e0e47ca60203452f5d61`,
name: "usdc",
chainId: SOLANA_MAINNET_CHAIN_ID_NUM,
decimals: 6
}
] as const;
else
Expand Down Expand Up @@ -267,10 +326,24 @@ export const coinList = (mainnet: boolean) => {
name: "weth",
chainId: arbitrumSepolia.id,
decimals: 18
},
// Solana devnet — SPL mint addresses encoded as bytes32 (66 chars); ADDRESS_ZERO = native SOL
{ address: ADDRESS_ZERO, name: "sol", chainId: SOLANA_DEVNET_CHAIN_ID_NUM, decimals: 9 },
{
address: `0x3b442cb3912157f13a933d0134282d032b5ffecd01a2dbf1b7790608df002ea7`,
name: "usdc",
chainId: SOLANA_DEVNET_CHAIN_ID_NUM,
decimals: 6
}
] as const;
];
};

export const evmCoinList = (mainnet: boolean) =>
coinList(mainnet).filter((t) => !!evmClientsById[t.chainId]);

export const solanaCoinList = (mainnet: boolean) =>
coinList(mainnet).filter((t) => SOLANA_CHAIN_IDS.has(t.chainId));

export function printToken(token: Token) {
return `${token.name.toUpperCase()}, ${getChainName(token.chainId)}`;
}
Expand Down Expand Up @@ -324,9 +397,12 @@ export function getCoin(
) {
const { name = undefined, address = undefined } = args;
const chainId = normalizeChainId(args.chainId);
// ensure the address is ERC20-sized.
const concatedAddress =
"0x" + address?.replace("0x", "")?.slice(address.length - 42, address.length);
// For Solana chains, the token address is a full 32-byte pubkey (66-char hex) — compare directly.
// For EVM chains, the address may arrive as a bytes32 left-padded with zeros — slice to 20 bytes.
const isSolanaChain = SOLANA_CHAIN_IDS.has(chainId);
const concatedAddress = isSolanaChain
? address?.toLowerCase()
: "0x" + address?.replace("0x", "")?.slice(address.length - 42, address.length);
for (const token of coinList(!isChainIdTestnet(chainId))) {
// check chain first.
if (token.chainId === chainId) {
Expand Down Expand Up @@ -355,7 +431,7 @@ export function isChainIdTestnet(chainId: number | bigint | string) {
const normalized = normalizeChainId(chainId);
const chain = chainById[normalized];
if (!chain) throw new Error(`Chain is not known: ${normalized}`);
return chain.testnet;
return chain.testnet ?? false;
}

export function getChainName(chainId: number | bigint | string) {
Expand Down Expand Up @@ -391,12 +467,12 @@ export function getChain(chainId: number | bigint | string) {

export function getClient(chainId: number | bigint | string) {
const normalized = normalizeChainId(chainId);
const client = clientsById[normalized];
const client = evmClientsById[normalized];
if (!client) throw new Error(`Could not find client for chainId ${normalized}`);
return client;
}

export const clients = {
export const evmClients = {
ethereum: createPublicClient({
chain: ethereum,
transport: fallback([
Expand Down Expand Up @@ -471,16 +547,26 @@ export const clients = {
})
} as const;

export const chainById = Object.fromEntries(chainEntries) as Record<
number,
(typeof chainMap)[keyof typeof chainMap]
>;
export const chainById = {
...Object.fromEntries(chainEntries),
[solanaMainnet.id]: solanaMainnet,
[solanaTestnet.id]: solanaTestnet,
[solanaDevnet.id]: solanaDevnet
} as Record<number, (typeof chainMap)[keyof typeof chainMap] | typeof solanaMainnet>;

export const chainNameById = Object.fromEntries(chainNameEntries) as Record<number, ChainName>;
export const chainNameById = {
...Object.fromEntries(chainNameEntries),
[solanaMainnet.id]: "solana",
[solanaTestnet.id]: "solana-testnet",
[solanaDevnet.id]: "solana-devnet"
} as Record<number, string>;

export const clientsById = Object.fromEntries(
chains.map((name) => [chainMap[name].id, clients[name]])
) as Record<number, (typeof clients)[keyof typeof clients]>;
export const evmClientsById = Object.fromEntries(
(Object.keys(evmClients) as (keyof typeof evmClients)[]).map((name) => [
chainMap[name].id,
evmClients[name]
])
) as Record<number, (typeof evmClients)[keyof typeof evmClients]>;

export type WC = ReturnType<
typeof createWalletClient<ReturnType<typeof custom>, undefined, undefined, undefined>
Expand Down
11 changes: 9 additions & 2 deletions src/lib/libraries/coreDeps.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,11 @@ import {
POLYMER_ORACLE,
WORMHOLE_ORACLE
} from "$lib/config";
import type { IntentDeps, OrderContainerValidationDeps } from "@lifi/intent";
import {
SOLANA_OUTPUT_SETTLER_PDAS,
type IntentDeps,
type OrderContainerValidationDeps
} from "@lifi/intent";

function isNonZeroAddress(value: string | undefined): value is `0x${string}` {
return !!value && value.toLowerCase() !== "0x0000000000000000000000000000000000000000";
Expand Down Expand Up @@ -49,6 +53,9 @@ export const orderValidationDeps: OrderContainerValidationDeps = {
return allowed;
},
allowedOutputSettlers() {
return [COIN_FILLER];
const solanaSettlers = Object.values(SOLANA_OUTPUT_SETTLER_PDAS).filter(
(v): v is `0x${string}` => !!v
);
return [COIN_FILLER, ...solanaSettlers];
}
};
6 changes: 3 additions & 3 deletions src/lib/libraries/flowProgress.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import { hashStruct, keccak256 } from "viem";
import { compactTypes } from "@lifi/intent";
import { getOutputHash, encodeMandateOutput } from "@lifi/intent";
import { addressToBytes32, bytes32ToAddress } from "@lifi/intent";
import { orderToIntent } from "@lifi/intent";
import { containerToIntent } from "$lib/utils/intent";
import { getOrFetchRpc } from "$lib/libraries/rpcCache";
import type { MandateOutput, OrderContainer } from "@lifi/intent";
import store from "$lib/state.svelte";
Expand Down Expand Up @@ -128,7 +128,7 @@ async function isOutputValidatedOnChain(
async function isInputChainFinalised(chainId: bigint, container: OrderContainer) {
const { order, inputSettler } = container;
const inputChainClient = getClient(chainId);
const intent = orderToIntent(container);
const intent = containerToIntent(container);
const orderId = intent.orderId();

if (
Expand Down Expand Up @@ -185,7 +185,7 @@ export async function getOrderProgressChecks(
fillTransactions: Record<string, `0x${string}`>
): Promise<FlowCheckState> {
try {
const intent = orderToIntent(orderContainer);
const intent = containerToIntent(orderContainer);
const orderId = intent.orderId();
const inputChains = intent.inputChains();
const outputs = orderContainer.order.outputs;
Expand Down
2 changes: 1 addition & 1 deletion src/lib/libraries/intentExecution.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import {
import { compact_type_hash } from "@lifi/intent";
import { addressToBytes32 } from "@lifi/intent";
import { signMultichainCompact, signStandardCompact } from "@lifi/intent";
import { MultichainOrderIntent, StandardOrderIntent } from "@lifi/intent";
import { MultichainOrderIntent, StandardEVMIntent as StandardOrderIntent } from "@lifi/intent";
import type { NoSignature, Signature } from "@lifi/intent";
import type { TypedDataSigner } from "@lifi/intent";
import { switchWalletChain } from "$lib/utils/walletClientRuntime";
Expand Down
Loading
Loading