From 1a4438aca931e9490d6dbfe54625b7acdb01ba00 Mon Sep 17 00:00:00 2001 From: Asem- Abdelhady Date: Tue, 7 Apr 2026 10:55:50 +0200 Subject: [PATCH 1/4] accommodate intent.ts standardise-order-classes API changes - add containerToIntent() helper in utils/intent.ts that wraps orderToIntent with automatic namespace detection (solana vs eip155) from OrderContainer - replace all orderToIntent(container) call sites with containerToIntent() - alias StandardOrderIntent as StandardEVMIntent in intentExecution.ts - add local idToToken() in intentList.ts (removed from @lifi/intent exports) --- src/lib/libraries/flowProgress.ts | 6 +++--- src/lib/libraries/intentExecution.ts | 2 +- src/lib/libraries/intentList.ts | 4 ++-- src/lib/libraries/solver.ts | 9 +++------ src/lib/screens/FillIntent.svelte | 4 ++-- src/lib/screens/Finalise.svelte | 6 +++--- src/lib/screens/IntentList.svelte | 4 ++-- src/lib/screens/ReceiveMessage.svelte | 6 +++--- src/lib/state.svelte.ts | 6 +++--- src/lib/utils/intent.ts | 26 ++++++++++++++++++++++++++ src/routes/+page.svelte | 14 +++++++------- 11 files changed, 55 insertions(+), 32 deletions(-) create mode 100644 src/lib/utils/intent.ts 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/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..23e903d 100644 --- a/src/lib/libraries/solver.ts +++ b/src/lib/libraries/solver.ts @@ -6,7 +6,7 @@ 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,7 @@ 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 (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..9c47327 --- /dev/null +++ b/src/lib/utils/intent.ts @@ -0,0 +1,26 @@ +import { + orderToIntent, + SOLANA_MAINNET_CHAIN_ID, + SOLANA_TESTNET_CHAIN_ID, + SOLANA_DEVNET_CHAIN_ID +} from "@lifi/intent"; +import type { OrderContainer, OrderIntent } from "@lifi/intent"; + +const SOLANA_CHAIN_IDS = new Set([ + SOLANA_MAINNET_CHAIN_ID, + SOLANA_TESTNET_CHAIN_ID, + SOLANA_DEVNET_CHAIN_ID +]); + +function isSolanaOrder(order: OrderContainer["order"]): boolean { + if (!("originChainId" in order)) return false; + return SOLANA_CHAIN_IDS.has(order.originChainId); +} + +export function containerToIntent(container: OrderContainer): OrderIntent { + const { inputSettler, order } = container; + if (isSolanaOrder(order)) { + 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; } } From 0e509864943648e7f1a08ef926910087f7883bc5 Mon Sep 17 00:00:00 2001 From: Asem- Abdelhady Date: Tue, 7 Apr 2026 12:16:23 +0200 Subject: [PATCH 2/4] Update @lifi/intent to 0.0.4 and fix breaking API changes - Bump @lifi/intent from 0.0.3-alpha.1 to 0.0.4 - Add chainNamespace field to CoreToken in toCoreTokenContext - Fix containerToIntent: use 'in' narrowing for proper TypeScript overload resolution - Add StandardSolanaIntent guards in IntentFactory compact/escrow methods - Add StandardSolanaIntent guard in Solver.claim before finaliseIntent --- bun.lock | 6 ++++-- package.json | 2 +- src/lib/libraries/intentFactory.ts | 28 ++++++++++++++++++++++++---- src/lib/libraries/solver.ts | 4 +++- src/lib/utils/intent.ts | 21 ++++++++++++--------- 5 files changed, 44 insertions(+), 17 deletions(-) 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/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/solver.ts b/src/lib/libraries/solver.ts index 23e903d..38e393d 100644 --- a/src/lib/libraries/solver.ts +++ b/src/lib/libraries/solver.ts @@ -1,7 +1,7 @@ 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"; @@ -311,6 +311,8 @@ export class Solver { const { orderContainer, fillTransactionHashes, sourceChainId } = args; const { order, inputSettler } = orderContainer; 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/utils/intent.ts b/src/lib/utils/intent.ts index 9c47327..b5967ad 100644 --- a/src/lib/utils/intent.ts +++ b/src/lib/utils/intent.ts @@ -2,9 +2,12 @@ import { orderToIntent, SOLANA_MAINNET_CHAIN_ID, SOLANA_TESTNET_CHAIN_ID, - SOLANA_DEVNET_CHAIN_ID + SOLANA_DEVNET_CHAIN_ID, + StandardEVMIntent, + StandardSolanaIntent, + MultichainOrderIntent } from "@lifi/intent"; -import type { OrderContainer, OrderIntent } from "@lifi/intent"; +import type { OrderContainer } from "@lifi/intent"; const SOLANA_CHAIN_IDS = new Set([ SOLANA_MAINNET_CHAIN_ID, @@ -12,14 +15,14 @@ const SOLANA_CHAIN_IDS = new Set([ SOLANA_DEVNET_CHAIN_ID ]); -function isSolanaOrder(order: OrderContainer["order"]): boolean { - if (!("originChainId" in order)) return false; - return SOLANA_CHAIN_IDS.has(order.originChainId); -} - -export function containerToIntent(container: OrderContainer): OrderIntent { +export function containerToIntent( + container: OrderContainer +): StandardEVMIntent | StandardSolanaIntent | MultichainOrderIntent { const { inputSettler, order } = container; - if (isSolanaOrder(order)) { + 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 }); From 07956a61a2e6960beff6d88d93bd4eda24978b11 Mon Sep 17 00:00:00 2001 From: Asem- Abdelhady Date: Tue, 7 Apr 2026 12:47:53 +0200 Subject: [PATCH 3/4] fix: CI/CD tests to use secret variables --- .github/workflows/test.yml | 4 ++++ 1 file changed, 4 insertions(+) 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 From 6233eda9db7a8df97e31cc505ee79c05b895c740 Mon Sep 17 00:00:00 2001 From: Asem Abdelhady Date: Wed, 6 May 2026 14:36:08 +0200 Subject: [PATCH 4/4] Merge pull request #41 from lifinance/feature/v2-116-add-recipient-field-on-lintentorg Add optional recipient field for intent outputs --- src/lib/libraries/intentFactory.ts | 2 ++ src/lib/screens/IssueIntent.svelte | 27 ++++++++++----- src/lib/state.svelte.ts | 1 + tests/unit/recipientField.test.ts | 54 ++++++++++++++++++++++++++++++ 4 files changed, 75 insertions(+), 9 deletions(-) create mode 100644 tests/unit/recipientField.test.ts diff --git a/src/lib/libraries/intentFactory.ts b/src/lib/libraries/intentFactory.ts index 5b1476e..3b3d146 100644 --- a/src/lib/libraries/intentFactory.ts +++ b/src/lib/libraries/intentFactory.ts @@ -59,6 +59,7 @@ function toCoreCreateIntentOptions(opts: AppCreateIntentOptions): CreateIntentOp outputTokens: opts.outputTokens.map(toCoreTokenContext), verifier: opts.verifier, account, + outputRecipient: opts.outputRecipient, lock: { type: "compact", resetPeriod: opts.lock.resetPeriod, @@ -73,6 +74,7 @@ function toCoreCreateIntentOptions(opts: AppCreateIntentOptions): CreateIntentOp outputTokens: opts.outputTokens.map(toCoreTokenContext), verifier: opts.verifier, account, + outputRecipient: opts.outputRecipient, lock: { type: "escrow" } diff --git a/src/lib/screens/IssueIntent.svelte b/src/lib/screens/IssueIntent.svelte index 167d326..725b386 100644 --- a/src/lib/screens/IssueIntent.svelte +++ b/src/lib/screens/IssueIntent.svelte @@ -34,12 +34,16 @@ const resolveExclusiveFor = (value: string): `0x${string}` | undefined => isAddress(value, { strict: false }) ? value : undefined; + const resolveRecipient = (value: string): `0x${string}` | undefined => + isAddress(value, { strict: false }) ? value : undefined; + const intentOptions = $derived.by( (): AppCreateIntentOptions => ({ exclusiveFor: resolveExclusiveFor(store.exclusiveFor), inputTokens: store.inputTokens, outputTokens: store.outputTokens, verifier: store.verifier, + outputRecipient: resolveRecipient(store.recipient), lock: store.intentType === "compact" ? { @@ -276,6 +280,19 @@
+
+ Recipient + 0 && !resolveRecipient(store.recipient) + ? "error" + : "default"} + bind:value={store.recipient} + /> +
Verifier {#if sameChain} @@ -313,15 +330,7 @@
- {#if !true} - - {:else if !allowanceCheck} + {#if !allowanceCheck} {#snippet name()} Set allowance diff --git a/src/lib/state.svelte.ts b/src/lib/state.svelte.ts index 1265ea7..e88d152 100644 --- a/src/lib/state.svelte.ts +++ b/src/lib/state.svelte.ts @@ -273,6 +273,7 @@ class Store { allocatorId = $state(ALWAYS_OK_ALLOCATOR); verifier = $state("polymer"); exclusiveFor: string = $state(""); + recipient: string = $state(""); useExclusiveForQuoteRequest = $state(false); invalidateWalletReadCache(scope: "all" | "balance" | "allowance" | "compact" = "all") { diff --git a/tests/unit/recipientField.test.ts b/tests/unit/recipientField.test.ts new file mode 100644 index 0000000..841307b --- /dev/null +++ b/tests/unit/recipientField.test.ts @@ -0,0 +1,54 @@ +import { describe, expect, it } from "bun:test"; +import { isAddress } from "viem"; + +// Mirrors the resolveRecipient helper in IssueIntent.svelte +const resolveRecipient = (value: string): `0x${string}` | undefined => + isAddress(value, { strict: false }) ? (value as `0x${string}`) : undefined; + +describe("resolveRecipient", () => { + it("returns the address for a valid checksummed EVM address", () => { + const addr = "0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045"; + expect(resolveRecipient(addr)).toBe(addr); + }); + + it("returns the address for a valid lowercase EVM address (strict: false)", () => { + const addr = "0xd8da6bf26964af9d7eed9e03e53415d37aa96045"; + expect(resolveRecipient(addr)).toBe(addr); + }); + + it("returns undefined for an empty string", () => { + expect(resolveRecipient("")).toBeUndefined(); + }); + + it("returns undefined for a partial address", () => { + expect(resolveRecipient("0x1234")).toBeUndefined(); + }); + + it("returns undefined for arbitrary non-address text", () => { + expect(resolveRecipient("alice.eth")).toBeUndefined(); + }); + + it("returns undefined for a hex string that is too long", () => { + expect(resolveRecipient("0x" + "a".repeat(42))).toBeUndefined(); + }); +}); + +describe("outputRecipient in AppCreateIntentOptions", () => { + it("is undefined when recipient field is empty", () => { + const recipient = ""; + const outputRecipient = resolveRecipient(recipient); + expect(outputRecipient).toBeUndefined(); + }); + + it("is set when a valid address is provided", () => { + const recipient = "0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045"; + const outputRecipient = resolveRecipient(recipient); + expect(outputRecipient).toBe(recipient); + }); + + it("is undefined for an invalid address, so wallet default is used", () => { + const recipient = "not-an-address"; + const outputRecipient = resolveRecipient(recipient); + expect(outputRecipient).toBeUndefined(); + }); +});