diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 9e4956f..95a45ed 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -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 diff --git a/bun.lock b/bun.lock index 5eec092..886db9d 100644 --- a/bun.lock +++ b/bun.lock @@ -6,7 +6,7 @@ "name": "cat-swapper", "dependencies": { "@electric-sql/pglite": "^0.3.15", - "@lifi/intent": "0.0.3-alpha.1", + "@lifi/intent": "0.0.4", "@metamask/sdk": "^0.34.0", "@sveltejs/adapter-cloudflare": "^7.0.3", "@wagmi/connectors": "^7.2.1", @@ -219,7 +219,7 @@ "@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.29", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, "sha512-uw6guiW/gcAGPDhLmd77/6lW8QLeiV5RUTsAX46Db6oLhGaVj4lhnPwb184s1bkc8kdVg/+h988dro8GRDpmYQ=="], - "@lifi/intent": ["@lifi/intent@0.0.3-alpha.1", "", { "dependencies": { "ky": "^1.12.0", "viem": "~2.45.1" } }, "sha512-dzEcS8U5buW7nLpkMmC0kj5R7EQC7l8l1mBd9IWr6R5/vSnSF3kO9zVaNQOmrbezSOS9TROrOpGDUWc847d/Uw=="], + "@lifi/intent": ["@lifi/intent@0.0.4", "", { "dependencies": { "borsh": "^2.0.0", "ky": "^1.12.0", "viem": "~2.45.1" } }, "sha512-T9wJGAY6sW6JcunEusXIvehxZcg2pRkaK0b+PUpSje2234yfZSmmcUPS1QTRxB6Iq6XROYopl6aUBIqzTHznew=="], "@metamask/json-rpc-engine": ["@metamask/json-rpc-engine@8.0.2", "", { "dependencies": { "@metamask/rpc-errors": "^6.2.1", "@metamask/safe-event-emitter": "^3.0.0", "@metamask/utils": "^8.3.0" } }, "sha512-IoQPmql8q7ABLruW7i4EYVHWUbF74yrp63bRuXV5Zf9BQwcn5H9Ww1eLtROYvI1bUXwOiHZ6qT5CWTrDc/t/AA=="], @@ -441,6 +441,8 @@ "blake3-wasm": ["blake3-wasm@2.1.5", "", {}, "sha512-F1+K8EbfOZE49dtoPtmxUQrpXaBIl3ICvasLh+nJta0xkz+9kF/7uet9fLnwKqhDrmj6g+6K3Tw9yQPUg2ka5g=="], + "borsh": ["borsh@2.0.0", "", {}, "sha512-kc9+BgR3zz9+cjbwM8ODoUB4fs3X3I5A/HtX7LZKxCLaMrEeDFoBpnhZY//DTS1VZBSs6S5v46RZRbZjRFspEg=="], + "bowser": ["bowser@2.14.1", "", {}, "sha512-tzPjzCxygAKWFOJP011oxFHs57HzIhOEracIgAePE4pqB3LikALKnSzUyU4MGs9/iCEUuHlAJTjTc5M+u7YEGg=="], "brace-expansion": ["brace-expansion@1.1.12", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg=="], diff --git a/package.json b/package.json index dbfc3dc..bcb2b76 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/src/lib/libraries/flowProgress.ts b/src/lib/libraries/flowProgress.ts index 510e33f..9c89091 100644 --- a/src/lib/libraries/flowProgress.ts +++ b/src/lib/libraries/flowProgress.ts @@ -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"; @@ -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 ( @@ -185,7 +185,7 @@ export async function getOrderProgressChecks( fillTransactions: Record ): Promise { try { - const intent = orderToIntent(orderContainer); + const intent = containerToIntent(orderContainer); const orderId = intent.orderId(); const inputChains = intent.inputChains(); const outputs = orderContainer.order.outputs; diff --git a/src/lib/libraries/intentExecution.ts b/src/lib/libraries/intentExecution.ts index 1c95197..03ecffe 100644 --- a/src/lib/libraries/intentExecution.ts +++ b/src/lib/libraries/intentExecution.ts @@ -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"; diff --git a/src/lib/libraries/intentFactory.ts b/src/lib/libraries/intentFactory.ts index 5b1b7a1..5b1476e 100644 --- a/src/lib/libraries/intentFactory.ts +++ b/src/lib/libraries/intentFactory.ts @@ -16,21 +16,35 @@ import type { Signature, StandardOrder } from "@lifi/intent"; +import { + Intent, + IntentApi, + StandardSolanaIntent, + SOLANA_MAINNET_CHAIN_ID, + SOLANA_TESTNET_CHAIN_ID, + SOLANA_DEVNET_CHAIN_ID +} from "@lifi/intent"; import type { AppCreateIntentOptions, AppTokenContext } from "$lib/appTypes"; import { ERC20_ABI } from "$lib/abi/erc20"; -import { Intent } from "@lifi/intent"; -import { IntentApi } from "@lifi/intent"; import { store } from "$lib/state.svelte"; import { depositAndRegisterCompact, openEscrowIntent, signIntentCompact } from "./intentExecution"; import { intentDeps } from "./coreDeps"; +const SOLANA_CHAIN_IDS = new Set([ + SOLANA_MAINNET_CHAIN_ID, + SOLANA_TESTNET_CHAIN_ID, + SOLANA_DEVNET_CHAIN_ID +]); + function toCoreTokenContext(input: AppTokenContext): TokenContext { + const chainId = BigInt(input.token.chainId); return { token: { address: input.token.address, name: input.token.name, - chainId: BigInt(input.token.chainId), - decimals: input.token.decimals + chainId, + decimals: input.token.decimals, + chainNamespace: SOLANA_CHAIN_IDS.has(chainId) ? "solana" : "eip155" }, amount: input.amount }; @@ -127,6 +141,8 @@ export class IntentFactory { const inputChain = inputTokens[0].token.chainId; if (this.preHook) await this.preHook(inputChain); const intent = new Intent(toCoreCreateIntentOptions(opts), intentDeps).order(); + if (intent instanceof StandardSolanaIntent) + throw new Error("Compact signing is not supported for Solana intents."); const sponsorSignature = await signIntentCompact(intent, account(), this.walletClient); @@ -165,6 +181,8 @@ export class IntentFactory { return async () => { const { inputTokens, account } = opts; const intent = new Intent(toCoreCreateIntentOptions(opts), intentDeps).singlechain(); + if (intent instanceof StandardSolanaIntent) + throw new Error("Compact deposit and register is not supported for Solana intents."); if (this.preHook) await this.preHook(inputTokens[0].token.chainId); @@ -200,6 +218,8 @@ export class IntentFactory { return async () => { const { inputTokens, account } = opts; const intent = new Intent(toCoreCreateIntentOptions(opts), intentDeps).order(); + if (intent instanceof StandardSolanaIntent) + throw new Error("openEscrowIntent is not supported for Solana intents."); const inputChain = inputTokens[0].token.chainId; if (this.preHook) await this.preHook(inputChain); diff --git a/src/lib/libraries/intentList.ts b/src/lib/libraries/intentList.ts index 4689d51..b5415f2 100644 --- a/src/lib/libraries/intentList.ts +++ b/src/lib/libraries/intentList.ts @@ -7,8 +7,8 @@ import { MULTICHAIN_INPUT_SETTLER_ESCROW, MULTICHAIN_INPUT_SETTLER_COMPACT } from "../config"; -import { orderToIntent } from "@lifi/intent"; import { bytes32ToAddress, idToToken } from "@lifi/intent"; +import { containerToIntent } from "$lib/utils/intent"; import type { OrderContainer, StandardOrder, MultichainOrder } from "@lifi/intent"; import { validateOrderContainerWithReason } from "@lifi/intent"; import { orderValidationDeps } from "./coreDeps"; @@ -201,7 +201,7 @@ function getContextDetails(orderContainer: OrderContainer): ContextDetails { export function buildBaseIntentRow(orderContainer: OrderContainer): BaseIntentRow { const order = orderContainer.order; - const orderId = orderToIntent(orderContainer).orderId(); + const orderId = containerToIntent(orderContainer).orderId(); const inputChipsRaw = getInputs(order); const outputChipsRaw = getOutputs(order); const chainScope = getChainScope(order); diff --git a/src/lib/libraries/solver.ts b/src/lib/libraries/solver.ts index 674ce29..38e393d 100644 --- a/src/lib/libraries/solver.ts +++ b/src/lib/libraries/solver.ts @@ -1,12 +1,12 @@ import { BYTES32_ZERO, COIN_FILLER, getChain, getClient, getOracle, type WC } from "$lib/config"; import { hashStruct, maxUint256, parseEventLogs } from "viem"; import type { MandateOutput, OrderContainer } from "@lifi/intent"; -import { addressToBytes32, bytes32ToAddress } from "@lifi/intent"; +import { addressToBytes32, bytes32ToAddress, StandardSolanaIntent } from "@lifi/intent"; import axios from "axios"; import { POLYMER_ORACLE_ABI } from "$lib/abi/polymeroracle"; import { COIN_FILLER_ABI } from "$lib/abi/outputsettler"; import { ERC20_ABI } from "$lib/abi/erc20"; -import { orderToIntent } from "@lifi/intent"; +import { containerToIntent } from "$lib/utils/intent"; import { compactTypes } from "@lifi/intent"; import store from "$lib/state.svelte"; import { finaliseIntent } from "./intentExecution"; @@ -66,7 +66,7 @@ export class Solver { orderContainer: { order, inputSettler }, outputs } = args; - const orderId = orderToIntent({ order, inputSettler }).orderId(); + const orderId = containerToIntent(args.orderContainer).orderId(); const outputChainId = Number(outputs[0].chainId); const outputChain = getChain(outputChainId); @@ -310,10 +310,9 @@ export class Solver { const { preHook, postHook, account } = opts; const { orderContainer, fillTransactionHashes, sourceChainId } = args; const { order, inputSettler } = orderContainer; - const intent = orderToIntent({ - inputSettler, - order - }); + const intent = containerToIntent(orderContainer); + if (intent instanceof StandardSolanaIntent) + throw new Error("Finalise is not supported for Solana input intents."); if (fillTransactionHashes.length !== order.outputs.length) { throw new Error( `Fill transaction hash count (${fillTransactionHashes.length}) does not match output count (${order.outputs.length}).` diff --git a/src/lib/screens/FillIntent.svelte b/src/lib/screens/FillIntent.svelte index 2147aa1..597bc3a 100644 --- a/src/lib/screens/FillIntent.svelte +++ b/src/lib/screens/FillIntent.svelte @@ -11,7 +11,7 @@ import ChainActionRow from "$lib/components/ui/ChainActionRow.svelte"; import TokenAmountChip from "$lib/components/ui/TokenAmountChip.svelte"; import store from "$lib/state.svelte"; - import { orderToIntent } from "@lifi/intent"; + import { containerToIntent } from "$lib/utils/intent"; import { compactTypes } from "@lifi/intent"; import { hashStruct } from "viem"; @@ -77,7 +77,7 @@ $effect(() => { refreshValidation; - const orderId = orderToIntent(orderContainer).orderId(); + const orderId = containerToIntent(orderContainer).orderId(); if (autoScrolledOrderId === orderId) return; const outputs = sortOutputsByChain(orderContainer).flatMap(([, chainOutputs]) => chainOutputs); diff --git a/src/lib/screens/Finalise.svelte b/src/lib/screens/Finalise.svelte index 5dcf169..16fbabe 100644 --- a/src/lib/screens/Finalise.svelte +++ b/src/lib/screens/Finalise.svelte @@ -22,7 +22,7 @@ import { SETTLER_ESCROW_ABI } from "$lib/abi/escrow"; import { idToToken } from "@lifi/intent"; import store from "$lib/state.svelte"; - import { orderToIntent } from "@lifi/intent"; + import { containerToIntent } from "$lib/utils/intent"; import { hashStruct } from "viem"; import { compactTypes } from "@lifi/intent"; @@ -41,7 +41,7 @@ let refreshClaimed = $state(0); let claimedByChain = $state>({}); let claimStatusRun = 0; - const inputChains = $derived(orderToIntent(orderContainer).inputChains()); + const inputChains = $derived(containerToIntent(orderContainer).inputChains()); const getInputsForChain = (container: OrderContainer, inputChain: bigint): [bigint, bigint][] => { const { order } = container; if ("originChainId" in order) { @@ -89,7 +89,7 @@ const { order, inputSettler } = container; const inputChainClient = getClient(chainId); - const intent = orderToIntent(container); + const intent = containerToIntent(container); const orderId = intent.orderId(); // Determine the order type. if ( diff --git a/src/lib/screens/IntentList.svelte b/src/lib/screens/IntentList.svelte index f3cd53b..1506c2a 100644 --- a/src/lib/screens/IntentList.svelte +++ b/src/lib/screens/IntentList.svelte @@ -1,7 +1,7 @@ diff --git a/src/lib/screens/ReceiveMessage.svelte b/src/lib/screens/ReceiveMessage.svelte index a721f0f..2fe9bda 100644 --- a/src/lib/screens/ReceiveMessage.svelte +++ b/src/lib/screens/ReceiveMessage.svelte @@ -12,7 +12,7 @@ import ChainActionRow from "$lib/components/ui/ChainActionRow.svelte"; import TokenAmountChip from "$lib/components/ui/TokenAmountChip.svelte"; import store from "$lib/state.svelte"; - import { orderToIntent } from "@lifi/intent"; + import { containerToIntent } from "$lib/utils/intent"; import { compactTypes } from "@lifi/intent"; // This script needs to be updated to be able to fetch the associated events of fills. Currently, this presents an issue since it can only fill single outputs. @@ -110,7 +110,7 @@ $effect(() => { refreshValidation; - const intent = orderToIntent(orderContainer); + const intent = containerToIntent(orderContainer); const orderId = intent.orderId(); if (autoScrolledOrderId === orderId) return; @@ -167,7 +167,7 @@ description="Click on each output and wait until they turn green. Polymer does not support batch validation. Continue to the right." >
- {#each orderToIntent(orderContainer).inputChains() as inputChain} + {#each containerToIntent(orderContainer).inputChains() as inputChain} {#snippet action()} diff --git a/src/lib/state.svelte.ts b/src/lib/state.svelte.ts index 145bc99..1265ea7 100644 --- a/src/lib/state.svelte.ts +++ b/src/lib/state.svelte.ts @@ -23,7 +23,7 @@ import { transactionReceipts as transactionReceiptsTable } from "./schema"; import { and, eq } from "drizzle-orm"; -import { orderToIntent } from "@lifi/intent"; +import { containerToIntent } from "./utils/intent"; import { getOrFetchRpc, invalidateRpcPrefix } from "./libraries/rpcCache"; import { getCurrentConnection, @@ -49,7 +49,7 @@ class Store { async saveOrderToDb(order: OrderContainer) { if (!browser) return; if (!db) await initDb(); - const orderId = orderToIntent(order).orderId(); + const orderId = containerToIntent(order).orderId(); const now = Math.floor(Date.now() / 1000); const id = (order as any).id ?? (typeof crypto !== "undefined" ? crypto.randomUUID() : String(now)); @@ -89,7 +89,7 @@ class Store { console.warn("saveOrderToDb db write failed", { orderId, error }); } } - const idx = this.orders.findIndex((o) => orderToIntent(o).orderId() === orderId); + const idx = this.orders.findIndex((o) => containerToIntent(o).orderId() === orderId); if (idx >= 0) this.orders[idx] = order; else this.orders.push(order); } diff --git a/src/lib/utils/intent.ts b/src/lib/utils/intent.ts new file mode 100644 index 0000000..b5967ad --- /dev/null +++ b/src/lib/utils/intent.ts @@ -0,0 +1,29 @@ +import { + orderToIntent, + SOLANA_MAINNET_CHAIN_ID, + SOLANA_TESTNET_CHAIN_ID, + SOLANA_DEVNET_CHAIN_ID, + StandardEVMIntent, + StandardSolanaIntent, + MultichainOrderIntent +} from "@lifi/intent"; +import type { OrderContainer } from "@lifi/intent"; + +const SOLANA_CHAIN_IDS = new Set([ + SOLANA_MAINNET_CHAIN_ID, + SOLANA_TESTNET_CHAIN_ID, + SOLANA_DEVNET_CHAIN_ID +]); + +export function containerToIntent( + container: OrderContainer +): StandardEVMIntent | StandardSolanaIntent | MultichainOrderIntent { + const { inputSettler, order } = container; + if (!("originChainId" in order)) { + return orderToIntent({ namespace: "eip155", inputSettler, order }); + } + if (SOLANA_CHAIN_IDS.has(order.originChainId)) { + return orderToIntent({ namespace: "solana", inputSettler, order }); + } + return orderToIntent({ namespace: "eip155", inputSettler, order }); +} diff --git a/src/routes/+page.svelte b/src/routes/+page.svelte index 2b98745..af83649 100644 --- a/src/routes/+page.svelte +++ b/src/routes/+page.svelte @@ -13,7 +13,7 @@ import ConnectWallet from "$lib/screens/ConnectWallet.svelte"; import FlowStepTracker from "$lib/components/ui/FlowStepTracker.svelte"; import store from "$lib/state.svelte"; - import { orderToIntent } from "@lifi/intent"; + import { containerToIntent } from "$lib/utils/intent"; // Fix bigint so we can json serialize it: (BigInt.prototype as any).toJSON = function () { @@ -67,8 +67,8 @@ const orderContainer = { ...order, allocatorSignature, sponsorSignature }; // Deduplicate: only add if not already present - const orderId = orderToIntent(orderContainer).orderId(); - const alreadyExists = store.orders.some((o) => orderToIntent(o).orderId() === orderId); + const orderId = containerToIntent(orderContainer).orderId(); + const alreadyExists = store.orders.some((o) => containerToIntent(o).orderId() === orderId); if (alreadyExists) return; store.orders.push(orderContainer); @@ -100,18 +100,18 @@ let scrollStepProgress = $state(0); async function importOrderById(orderId: `0x${string}`): Promise<"inserted" | "updated"> { const importedOrder = await intentApi.getOrderByOnChainOrderId(orderId); - const importedOrderId = orderToIntent(importedOrder).orderId(); + const importedOrderId = containerToIntent(importedOrder).orderId(); const existingIndex = store.orders.findIndex( - (o) => orderToIntent(o).orderId() === importedOrderId + (o) => containerToIntent(o).orderId() === importedOrderId ); await store.saveOrderToDb(importedOrder); selectedOrder = - store.orders.find((o) => orderToIntent(o).orderId() === importedOrderId) ?? importedOrder; + store.orders.find((o) => containerToIntent(o).orderId() === importedOrderId) ?? importedOrder; return existingIndex >= 0 ? "updated" : "inserted"; } async function deleteOrderById(orderId: `0x${string}`): Promise { await store.deleteOrderFromDb(orderId); - if (selectedOrder && orderToIntent(selectedOrder).orderId() === orderId) { + if (selectedOrder && containerToIntent(selectedOrder).orderId() === orderId) { selectedOrder = undefined; } }