From 36a6252db538aba1de0c8e2a220827b443be2d36 Mon Sep 17 00:00:00 2001 From: Alexander Date: Mon, 27 Oct 2025 12:43:51 +0100 Subject: [PATCH 01/17] Single store --- README.md | 5 +- src/lib/libraries/intent.ts | 41 +++--- src/lib/libraries/token.ts | 59 ++++++++ src/lib/screens/FillIntent.svelte | 5 +- src/lib/screens/Finalise.svelte | 7 +- src/lib/screens/IssueIntent.svelte | 130 +++++++---------- src/lib/screens/ManageDeposit.svelte | 80 +++++----- src/lib/screens/ReceiveMessage.svelte | 14 +- src/lib/screens/TokenModal.svelte | 68 +++------ src/lib/state.svelte.ts | 180 ++++++++++++++++------- src/routes/+page.svelte | 202 ++++---------------------- 11 files changed, 354 insertions(+), 437 deletions(-) create mode 100644 src/lib/libraries/token.ts diff --git a/README.md b/README.md index bfce562..f1b7f6d 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,6 @@ To start development copy `.env.example` to `.env`. Then fill in the env variabl Install dependencies `npm install` and start `npm run dev`. - ## Structure Lintent is built around a single page [/src/routes/+page.svelte](/src/routes/+page.svelte). @@ -21,8 +20,8 @@ The app consists of a series of screens that are displayed in a scrollable conta ### Libraries -Several helper classes that acts as wrappers for external endpoints can be found in [/src/lib/libraries/](/src/lib/libraries/). +Several helper classes that acts as wrappers for external endpoints can be found in [/src/lib/libraries/](/src/lib/libraries/). ## License -This project is licensed under the **[MIT License](/LICENSE)**. Any contributions to this repository is provided with a MIT License. \ No newline at end of file +This project is licensed under the **[MIT License](/LICENSE)**. Any contributions to this repository is provided with a MIT License. diff --git a/src/lib/libraries/intent.ts b/src/lib/libraries/intent.ts index cd38fd8..10be3c0 100644 --- a/src/lib/libraries/intent.ts +++ b/src/lib/libraries/intent.ts @@ -22,9 +22,9 @@ export type CreateIntentOptions = { exclusiveFor: string; allocatorId: string; inputTokens: Token[]; - outputToken: Token; + outputTokens: Token[]; inputAmounts: bigint[]; - outputAmount: bigint; + outputAmounts: bigint[]; verifier: Verifier; account: () => `0x${string}`; inputSettler: typeof INPUT_SETTLER_COMPACT_LIFI | typeof INPUT_SETTLER_ESCROW_LIFI; @@ -55,9 +55,9 @@ export class Intent { exclusiveFor, allocatorId, inputTokens, - outputToken, + outputTokens, inputAmounts, - outputAmount, + outputAmounts, verifier, account, inputSettler @@ -83,7 +83,6 @@ export class Intent { } const outputSettler = COIN_FILLER; - const outputOracle = getOracle(verifier, outputToken.chain)!; const inputOracle = getOracle(verifier, inputChain)!; // Get the current epoch timestamp: @@ -99,18 +98,26 @@ export class Intent { ); } - // Make Outputs - const output: MandateOutput = { - oracle: addressToBytes32(outputOracle), - settler: addressToBytes32(outputSettler), - chainId: BigInt(chainMap[outputToken.chain].id), - token: addressToBytes32(outputToken.address), - amount: outputAmount, - recipient: addressToBytes32(account()), - call: "0x", - context - }; - const outputs = [output]; + const outputs: MandateOutput[] = []; + for (let o = 0; o < outputTokens.length; ++o) { + const outputToken = outputTokens[o]; + const outputAmount = outputAmounts[o]; + + const outputOracle = getOracle(verifier, outputToken.chain)!; + + // Make Outputs + const output: MandateOutput = { + oracle: addressToBytes32(outputOracle), + settler: addressToBytes32(outputSettler), + chainId: BigInt(chainMap[outputToken.chain].id), + token: addressToBytes32(outputToken.address), + amount: outputAmount, + recipient: addressToBytes32(account()), + call: "0x", + context + }; + outputs.push(output); + } // Make order const order: StandardOrder = { diff --git a/src/lib/libraries/token.ts b/src/lib/libraries/token.ts new file mode 100644 index 0000000..2ae71f7 --- /dev/null +++ b/src/lib/libraries/token.ts @@ -0,0 +1,59 @@ +import { maxUint256 } from "viem"; +import { COMPACT_ABI } from "../abi/compact"; +import { ERC20_ABI } from "../abi/erc20"; +import { ADDRESS_ZERO, clients, COMPACT } from "../config"; +import { ResetPeriod, toId } from "../utils/idLib"; + +export async function getBalance( + user: `0x${string}` | undefined, + asset: `0x${string}`, + client: (typeof clients)[keyof typeof clients] +) { + if (!user) return 0n; + if (asset === ADDRESS_ZERO) { + return client.getBalance({ + address: user, + blockTag: "latest" + }); + } else { + return client.readContract({ + address: asset, + abi: ERC20_ABI, + functionName: "balanceOf", + args: [user] + }); + } +} + +export function getAllowance(contract: `0x${string}`) { + return async ( + user: `0x${string}` | undefined, + asset: `0x${string}`, + client: (typeof clients)[keyof typeof clients] + ) => { + if (!user) return 0n; + if (asset == ADDRESS_ZERO) return maxUint256; + return client.readContract({ + address: asset, + abi: ERC20_ABI, + functionName: "allowance", + args: [user, contract] + }); + }; +} + +export async function getCompactBalance( + user: `0x${string}` | undefined, + asset: `0x${string}`, + client: (typeof clients)[keyof typeof clients], + allocatorId: string +) { + if (!user) return 0n; + const assetId = toId(true, ResetPeriod.OneDay, allocatorId, asset); + return client.readContract({ + address: COMPACT, + abi: COMPACT_ABI, + functionName: "balanceOf", + args: [user, assetId] + }); +} diff --git a/src/lib/screens/FillIntent.svelte b/src/lib/screens/FillIntent.svelte index e14aaf2..0a0fa2c 100644 --- a/src/lib/screens/FillIntent.svelte +++ b/src/lib/screens/FillIntent.svelte @@ -14,11 +14,11 @@ import { Solver } from "$lib/libraries/solver"; import { COIN_FILLER_ABI } from "$lib/abi/outputsettler"; import AwaitButton from "$lib/components/AwaitButton.svelte"; + import store from "$lib/state.svelte"; let { scroll, orderContainer, - walletClient, fillTransactionHash = $bindable(), account, preHook, @@ -26,7 +26,6 @@ }: { scroll: (direction: boolean | number) => () => void; orderContainer: OrderContainer; - walletClient: WC; fillTransactionHash: `0x${string}` | undefined; preHook?: (chain: chain) => Promise; postHook: () => Promise; @@ -109,7 +108,7 @@ buttonFunction={filledStatus.every((v) => v == BYTES32_ZERO) ? fillWrapper( Solver.fill( - walletClient, + store.walletClient, { orderContainer, outputs diff --git a/src/lib/screens/Finalise.svelte b/src/lib/screens/Finalise.svelte index 58d2b04..d4f23f0 100644 --- a/src/lib/screens/Finalise.svelte +++ b/src/lib/screens/Finalise.svelte @@ -17,17 +17,16 @@ import { COMPACT_ABI } from "$lib/abi/compact"; import { SETTLER_ESCROW_ABI } from "$lib/abi/escrow"; import { idToToken } from "$lib/utils/convert"; + import store from "$lib/state.svelte"; let { orderContainer, - walletClient, fillTransactionHash, account, preHook, postHook }: { orderContainer: OrderContainer; - walletClient: WC; fillTransactionHash: `0x${string}`; preHook?: (chain: chain) => Promise; postHook?: () => Promise; @@ -111,7 +110,7 @@ {:else} Promise; postHook: () => Promise; account: () => `0x${string}`; } = $props(); const opts = $derived({ - exclusiveFor, - allocatorId, - inputTokens, + exclusiveFor: store.exclusiveFor, + allocatorId: store.allocatorId, + inputTokens: store.inputTokens, preHook, postHook, - outputToken, - inputAmounts, - outputAmount, - verifier, - inputSettler, + outputTokens: store.outputTokens, + inputAmounts: store.inputAmounts, + outputAmounts: store.outputAmounts, + verifier: store.verifier, + inputSettler: store.inputSettler, account }); @@ -85,31 +51,31 @@ const intentFactory = $derived( new IntentFactory({ - mainnet, - walletClient, + mainnet: store.mainnet, + walletClient: store.walletClient, preHook, postHook: postHookScroll, - ordersPointer: orders + ordersPointer: store.orders }) ); const approveFunction = $derived( - inputSettler === INPUT_SETTLER_COMPACT_LIFI - ? CompactLib.compactApprove(walletClient, opts) - : escrowApprove(walletClient, opts) + store.inputSettler === INPUT_SETTLER_COMPACT_LIFI + ? CompactLib.compactApprove(store.walletClient, opts) + : escrowApprove(store.walletClient, opts) ); let allowanceCheck = $state(true); $effect(() => { allowanceCheck = true; - if (!allowances[inputTokens[0].chain]) { + if (!store.allowances[store.inputTokens[0].chain]) { allowanceCheck = false; return; } - for (let i = 0; i < inputTokens.length; ++i) { - const token = inputTokens[i]; - const inputAmount = inputAmounts[i]; - allowances[token.chain][token.address].then((a) => { + for (let i = 0; i < store.inputTokens.length; ++i) { + const token = store.inputTokens[i]; + const inputAmount = store.inputAmounts[i]; + store.allowances[token.chain][token.address].then((a) => { allowanceCheck = allowanceCheck && a >= inputAmount; }); } @@ -117,14 +83,14 @@ let balanceCheckWallet = $state(true); $effect(() => { balanceCheckWallet = true; - if (!balances[inputTokens[0].chain]) { + if (!store.balances[store.inputTokens[0].chain]) { balanceCheckWallet = false; return; } - for (let i = 0; i < inputTokens.length; ++i) { - const token = inputTokens[i]; - const inputAmount = inputAmounts[i]; - balances[token.chain][token.address].then((b) => { + for (let i = 0; i < store.inputTokens.length; ++i) { + const token = store.inputTokens[i]; + const inputAmount = store.inputAmounts[i]; + store.balances[token.chain][token.address].then((b) => { balanceCheckWallet = balanceCheckWallet && b >= inputAmount; }); } @@ -132,20 +98,22 @@ let balanceCheckCompact = $state(true); $effect(() => { balanceCheckCompact = true; - if (!compactBalances[inputTokens[0].chain]) { + if (!store.compactBalances[store.inputTokens[0].chain]) { balanceCheckCompact = false; return; } - for (let i = 0; i < inputTokens.length; ++i) { - const token = inputTokens[i]; - const inputAmount = inputAmounts[i]; - compactBalances[token.chain][token.address].then((b) => { + for (let i = 0; i < store.inputTokens.length; ++i) { + const token = store.inputTokens[i]; + const inputAmount = store.inputAmounts[i]; + store.compactBalances[token.chain][token.address].then((b) => { balanceCheckCompact = balanceCheckCompact && b >= inputAmount; }); } }); - const allSameChains = $derived(inputTokens.every((v) => inputTokens[0].chain === v.chain)); + const allSameChains = $derived( + store.inputTokens.every((v) => store.inputTokens[0].chain === v.chain) + );
@@ -156,7 +124,7 @@

- {#each inputTokens as inputToken, i} + {#each store.inputTokens as inputToken, i}
- {#if inputSettler === INPUT_SETTLER_COMPACT_LIFI} + {#if store.inputSettler === INPUT_SETTLER_COMPACT_LIFI}

Allocator

@@ -139,17 +125,19 @@ of @@ -159,13 +147,13 @@
{#if manageAssetAction === "withdraw"} {#snippet name()} @@ -177,7 +165,7 @@ {:else if allowance < inputAmount} {:else} {#snippet name()} diff --git a/src/lib/screens/ReceiveMessage.svelte b/src/lib/screens/ReceiveMessage.svelte index 4ef05ea..136fe25 100644 --- a/src/lib/screens/ReceiveMessage.svelte +++ b/src/lib/screens/ReceiveMessage.svelte @@ -1,12 +1,5 @@ -
+

Intent Issuance

Select assets for your intent along with the verifier for the intent. Then choose your desired style of execution. Your intent will be sent to the LI.FI dev order server.

+
- {#each store.inputTokens as inputToken, i} +

You Pay

+ {#each abstractInputs as input, i} {/each} - + -->
@@ -163,6 +194,7 @@
+

You Receive

{#if showTokenSelector.index === -1}

Add Asset

{:else}

Select Asset

{/if} -
- - {#if showTokenSelector.input} - of - - {:else}{/if} +
+ +
- +
+ {#each tokenSet as tkn} + {tkn.chain} + {/each} +
+
+ {#each tokenSet as tkn, i} +
+ + of + +
+ {/each} +
Save
diff --git a/src/routes/+page.svelte b/src/routes/+page.svelte index 95f7a98..37e06de 100644 --- a/src/routes/+page.svelte +++ b/src/routes/+page.svelte @@ -1,9 +1,7 @@ -
+
{#await quoteRequest} -
Fetch Quote
+
Fetch Quote
{:then _} {#if quoteExpires !== 0} diff --git a/src/lib/libraries/compactLib.ts b/src/lib/libraries/compactLib.ts index 3383da1..be1786f 100644 --- a/src/lib/libraries/compactLib.ts +++ b/src/lib/libraries/compactLib.ts @@ -13,6 +13,7 @@ import { import { COMPACT_ABI } from "$lib/abi/compact"; import { addressToBytes32 } from "$lib/utils/convert"; import { ERC20_ABI } from "$lib/abi/erc20"; +import type { TokenContext } from "$lib/state.svelte"; export class CompactLib { static compactDeposit( @@ -20,16 +21,16 @@ export class CompactLib { opts: { preHook?: (chain: chain) => Promise; postHook?: () => Promise; - inputToken: Token; + inputToken: TokenContext; account: () => `0x${string}`; - inputAmount: bigint; allocatorId: string; } ) { return async () => { - const { preHook, postHook, inputToken, account, allocatorId, inputAmount } = opts; + const { preHook, postHook, inputToken, account, allocatorId } = opts; + const { token, amount } = inputToken; const publicClients = clients; - if (preHook) await preHook(inputToken.chain); + if (preHook) await preHook(token.chain); const lockTag: `0x${string}` = `0x${toHex( toId(true, ResetPeriod.OneDay, allocatorId, ADDRESS_ZERO), { @@ -41,27 +42,27 @@ export class CompactLib { const recipient = ADDRESS_ZERO; // This means sender. let transactionHash: `0x${string}`; - if (inputToken.address === ADDRESS_ZERO) { + if (token.address === ADDRESS_ZERO) { transactionHash = await walletClient.writeContract({ - chain: chainMap[inputToken.chain], + chain: chainMap[token.chain], account: account(), address: COMPACT, abi: COMPACT_ABI, functionName: "depositNative", - value: inputAmount, + value: amount, args: [lockTag, recipient] }); } else { transactionHash = await walletClient.writeContract({ - chain: chainMap[inputToken.chain], + chain: chainMap[token.chain], account: account(), address: COMPACT, abi: COMPACT_ABI, functionName: "depositERC20", - args: [inputToken.address, lockTag, inputAmount, recipient] + args: [token.address, lockTag, amount, recipient] }); } - await publicClients[inputToken.chain].waitForTransactionReceipt({ + await publicClients[token.chain].waitForTransactionReceipt({ hash: await transactionHash }); if (postHook) await postHook(); @@ -74,16 +75,16 @@ export class CompactLib { opts: { preHook?: (chain: chain) => Promise; postHook?: () => Promise; - inputToken: Token; + inputToken: TokenContext; account: () => `0x${string}`; - inputAmount: bigint; allocatorId: string; } ) { return async () => { - const { preHook, postHook, inputToken, account, allocatorId, inputAmount } = opts; + const { preHook, postHook, inputToken, account, allocatorId } = opts; + const { token, amount } = inputToken; const publicClients = clients; - const assetId = toId(true, ResetPeriod.OneDay, allocatorId, inputToken.address); + const assetId = toId(true, ResetPeriod.OneDay, allocatorId, token.address); const allocatedTransferStruct: { allocatorData: `0x${string}`; @@ -102,21 +103,21 @@ export class CompactLib { recipients: [ { claimant: BigInt(addressToBytes32(account())), - amount: inputAmount + amount: amount } ] }; - if (preHook) await preHook(inputToken.chain); + if (preHook) await preHook(token.chain); const transactionHash = walletClient.writeContract({ - chain: chainMap[inputToken.chain], + chain: chainMap[token.chain], account: account(), address: COMPACT, abi: COMPACT_ABI, functionName: "allocatedTransfer", args: [allocatedTransferStruct] }); - await publicClients[inputToken.chain].waitForTransactionReceipt({ + await publicClients[token.chain].waitForTransactionReceipt({ hash: await transactionHash }); if (postHook) await postHook(); @@ -129,15 +130,14 @@ export class CompactLib { opts: { preHook?: (chain: chain) => Promise; postHook?: () => Promise; - inputTokens: Token[]; - inputAmounts: bigint[]; + inputTokens: TokenContext[]; account: () => `0x${string}`; } ) { return async () => { - const { preHook, postHook, inputTokens, inputAmounts, account } = opts; + const { preHook, postHook, inputTokens, account } = opts; for (let i = 0; i < inputTokens.length; ++i) { - const inputToken = inputTokens[i]; + const { token: inputToken, amount } = inputTokens[i]; if (preHook) await preHook(inputToken.chain); const publicClient = clients[inputToken.chain]; // Check if we have sufficient allowance already. @@ -147,7 +147,7 @@ export class CompactLib { functionName: "allowance", args: [account(), COMPACT] }); - if (currentAllowance >= inputAmounts[i]) continue; + if (currentAllowance >= amount) continue; const transactionHash = walletClient.writeContract({ chain: chainMap[inputToken.chain], account: account(), diff --git a/src/lib/libraries/intent.ts b/src/lib/libraries/intent.ts index 10be3c0..357ffd0 100644 --- a/src/lib/libraries/intent.ts +++ b/src/lib/libraries/intent.ts @@ -17,14 +17,13 @@ import { ResetPeriod, toId } from "../utils/idLib"; import { compact_type_hash, compactTypes } from "../utils/typedMessage"; import { addressToBytes32 } from "../utils/convert"; import { SETTLER_ESCROW_ABI } from "../abi/escrow"; +import type { TokenContext } from "$lib/state.svelte"; export type CreateIntentOptions = { exclusiveFor: string; allocatorId: string; - inputTokens: Token[]; - outputTokens: Token[]; - inputAmounts: bigint[]; - outputAmounts: bigint[]; + inputTokens: TokenContext[]; + outputTokens: TokenContext[]; verifier: Verifier; account: () => `0x${string}`; inputSettler: typeof INPUT_SETTLER_COMPACT_LIFI | typeof INPUT_SETTLER_ESCROW_LIFI; @@ -56,8 +55,6 @@ export class Intent { allocatorId, inputTokens, outputTokens, - inputAmounts, - outputAmounts, verifier, account, inputSettler @@ -71,15 +68,15 @@ export class Intent { throw new Error(`ExclusiveFor not formatted correctly ${exclusiveFor}`); } - const inputChain = inputTokens[0].chain; + const inputChain = inputTokens[0].token.chain; const inputs: [bigint, bigint][] = []; - for (let i = 0; i < inputTokens.length; ++i) { + for (const { token, amount } of inputTokens) { // If Compact input, then generate the tokenId otherwise cast into uint256. const inputTokenId = inputSettler == INPUT_SETTLER_COMPACT_LIFI - ? toId(true, ResetPeriod.OneDay, allocatorId, inputTokens[i].address) - : BigInt(inputTokens[i].address); - inputs.push([inputTokenId, inputAmounts[i]]); + ? toId(true, ResetPeriod.OneDay, allocatorId, token.address) + : BigInt(token.address); + inputs.push([inputTokenId, amount]); } const outputSettler = COIN_FILLER; @@ -98,20 +95,17 @@ export class Intent { ); } + // Make Outputs const outputs: MandateOutput[] = []; - for (let o = 0; o < outputTokens.length; ++o) { - const outputToken = outputTokens[o]; - const outputAmount = outputAmounts[o]; + for (const { token, amount } of outputTokens) { + const outputOracle = getOracle(verifier, token.chain)!; - const outputOracle = getOracle(verifier, outputToken.chain)!; - - // Make Outputs const output: MandateOutput = { oracle: addressToBytes32(outputOracle), settler: addressToBytes32(outputSettler), - chainId: BigInt(chainMap[outputToken.chain].id), - token: addressToBytes32(outputToken.address), - amount: outputAmount, + chainId: BigInt(chainMap[token.chain].id), + token: addressToBytes32(token.address), + amount: amount, recipient: addressToBytes32(account()), call: "0x", context diff --git a/src/lib/libraries/intentFactory.ts b/src/lib/libraries/intentFactory.ts index f23fa8d..ca74f94 100644 --- a/src/lib/libraries/intentFactory.ts +++ b/src/lib/libraries/intentFactory.ts @@ -13,6 +13,7 @@ import { ERC20_ABI } from "$lib/abi/erc20"; import { Intent } from "$lib/libraries/intent"; import { OrderServer } from "$lib/libraries/orderServer"; import type { CreateIntentOptions } from "$lib/libraries/intent"; +import type { TokenContext } from "$lib/state.svelte"; /** * @notice Factory class for creating and managing intents. Functions called by integrators. @@ -71,7 +72,7 @@ export class IntentFactory { compact(opts: CreateIntentOptions) { return async () => { const { account, inputTokens } = opts; - const inputChain = inputTokens[0].chain; + const inputChain = inputTokens[0].token.chain; if (this.preHook) await this.preHook(inputChain); const intent = new Intent(opts); @@ -102,11 +103,11 @@ export class IntentFactory { const publicClients = clients; const intent = new Intent(opts); - if (this.preHook) await this.preHook(inputTokens[0].chain); + if (this.preHook) await this.preHook(inputTokens[0].token.chain); let transactionHash = await intent.depositAndRegisterCompact(account(), this.walletClient); - const recepit = await publicClients[inputTokens[0].chain].waitForTransactionReceipt({ + const recepit = await publicClients[inputTokens[0].token.chain].waitForTransactionReceipt({ hash: transactionHash }); @@ -137,7 +138,7 @@ export class IntentFactory { const { inputTokens, account } = opts; const intent = new Intent(opts); - const inputChain = inputTokens[0].chain; + const inputChain = inputTokens[0].token.chain; if (this.preHook) await this.preHook(inputChain); // Execute the open. @@ -164,28 +165,27 @@ export function escrowApprove( opts: { preHook?: (chain: chain) => Promise; postHook?: () => Promise; - inputTokens: Token[]; - inputAmounts: bigint[]; + inputTokens: TokenContext[]; account: () => `0x${string}`; } ) { return async () => { - const { preHook, postHook, inputTokens, inputAmounts, account } = opts; + const { preHook, postHook, inputTokens, account } = opts; for (let i = 0; i < inputTokens.length; ++i) { - const inputToken = inputTokens[i]; - if (preHook) await preHook(inputToken.chain); - const publicClient = clients[inputToken.chain]; + const { token, amount } = inputTokens[i]; + if (preHook) await preHook(token.chain); + const publicClient = clients[token.chain]; const currentAllowance = await publicClient.readContract({ - address: inputToken.address, + address: token.address, abi: ERC20_ABI, functionName: "allowance", args: [account(), INPUT_SETTLER_ESCROW_LIFI] }); - if (currentAllowance >= inputAmounts[i]) continue; + if (currentAllowance >= amount) continue; const transactionHash = walletClient.writeContract({ - chain: chainMap[inputToken.chain], + chain: chainMap[token.chain], account: account(), - address: inputToken.address, + address: token.address, abi: ERC20_ABI, functionName: "approve", args: [INPUT_SETTLER_ESCROW_LIFI, maxUint256] diff --git a/src/lib/screens/FillIntent.svelte b/src/lib/screens/FillIntent.svelte index 423e84c..b04acee 100644 --- a/src/lib/screens/FillIntent.svelte +++ b/src/lib/screens/FillIntent.svelte @@ -140,8 +140,8 @@ output.amount, getCoin({ address: output.token, - chain: getChainName(output.chainId).decimals - }) + chain: getChainName(output.chainId) + }).decimals )}
@@ -164,8 +164,8 @@ output.amount, getCoin({ address: output.token, - chain: getChainName(output.chainId).decimals - }) + chain: getChainName(output.chainId) + }).decimals )}
diff --git a/src/lib/screens/IssueIntent.svelte b/src/lib/screens/IssueIntent.svelte index 240e8a0..d8763e6 100644 --- a/src/lib/screens/IssueIntent.svelte +++ b/src/lib/screens/IssueIntent.svelte @@ -27,25 +27,15 @@ account: () => `0x${string}`; } = $props(); - let showTokenSelector = $state<{ - active: number; - input: boolean; - index: number; - }>({ - active: 1, - input: true, - index: 0 - }); + let tokenSelectorActive = $state(false); const opts = $derived({ exclusiveFor: store.exclusiveFor, allocatorId: store.allocatorId, inputTokens: store.inputTokens, + outputTokens: store.outputTokens, preHook, postHook, - outputTokens: store.outputTokens, - inputAmounts: store.inputAmounts, - outputAmounts: store.outputAmounts, verifier: store.verifier, inputSettler: store.inputSettler, account @@ -75,51 +65,48 @@ let allowanceCheck = $state(true); $effect(() => { allowanceCheck = true; - if (!store.allowances[store.inputTokens[0].chain]) { + if (!store.allowances[store.inputTokens[0].token.chain]) { allowanceCheck = false; return; } for (let i = 0; i < store.inputTokens.length; ++i) { - const token = store.inputTokens[i]; - const inputAmount = store.inputAmounts[i]; + const { token, amount } = store.inputTokens[i]; store.allowances[token.chain][token.address].then((a) => { - allowanceCheck = allowanceCheck && a >= inputAmount; + allowanceCheck = allowanceCheck && a >= amount; }); } }); let balanceCheckWallet = $state(true); $effect(() => { balanceCheckWallet = true; - if (!store.balances[store.inputTokens[0].chain]) { + if (!store.balances[store.inputTokens[0].token.chain]) { balanceCheckWallet = false; return; } for (let i = 0; i < store.inputTokens.length; ++i) { - const token = store.inputTokens[i]; - const inputAmount = store.inputAmounts[i]; + const { token, amount } = store.inputTokens[i]; store.balances[token.chain][token.address].then((b) => { - balanceCheckWallet = balanceCheckWallet && b >= inputAmount; + balanceCheckWallet = balanceCheckWallet && b >= amount; }); } }); let balanceCheckCompact = $state(true); $effect(() => { balanceCheckCompact = true; - if (!store.compactBalances[store.inputTokens[0].chain]) { + if (!store.compactBalances[store.inputTokens[0].token.chain]) { balanceCheckCompact = false; return; } for (let i = 0; i < store.inputTokens.length; ++i) { - const token = store.inputTokens[i]; - const inputAmount = store.inputAmounts[i]; + const { token, amount } = store.inputTokens[i]; store.compactBalances[token.chain][token.address].then((b) => { - balanceCheckCompact = balanceCheckCompact && b >= inputAmount; + balanceCheckCompact = balanceCheckCompact && b >= amount; }); } }); const allSameChains = $derived( - store.inputTokens.every((v) => store.inputTokens[0].chain === v.chain) + store.inputTokens.every((v) => store.inputTokens[0].token.chain === v.token.chain) ); const abstractInputs = $derived.by(() => { @@ -129,20 +116,27 @@ decimals: number; }[] = []; // Get all unqiue tokens. - const allUniqueNames = [...new Set(store.inputTokens.map((v) => v.name))]; + $inspect(store.inputTokens); + const allUniqueNames = [ + ...new Set( + store.inputTokens.map((v) => { + console.log("v", v); + return v.token.name; + }) + ) + ]; for (let i = 0; i < allUniqueNames.length; ++i) { - $inspect(store.inputTokens); const name = allUniqueNames[i]; console.log({ name, - found: store.inputTokens.map((v, i) => (v.name == name ? store.inputAmounts[i] : 0n)) + found: store.inputTokens.map((v, i) => (v.token.name == name ? v.amount : 0n)) }); inputs[i] = { name, amount: bigIntSum( - ...store.inputTokens.map((v, i) => (v.name == name ? store.inputAmounts[i] : 0n)) + ...store.inputTokens.map((v, i) => (v.token.name == name ? v.amount : 0n)) ), - decimals: store.inputTokens.find((v) => v.name == name)!.decimals + decimals: store.inputTokens.find((v) => v.token.name == name)!.token.decimals }; } return inputs; @@ -155,19 +149,20 @@ Select assets for your intent along with the verifier for the intent. Then choose your desired style of execution. Your intent will be sent to the LI.FI dev order server.

- + {#if tokenSelectorActive} + + {/if}

You Pay

{#each abstractInputs as input, i} - + -
- {#if showTokenSelector.index === -1} -

Add Asset

- {:else} -

Select Asset

- {/if} -
- - +
+
+ +
+

Select {input ? "Input" : "Output"}

+
+ + + +
+
+
+ {#each tokenSet as tkn} + {tkn.chain} + {/each}
-
-
- {#each tokenSet as tkn} - {tkn.chain} - {/each} -
-
- {#each tokenSet as tkn, i} -
- - of - -
- {/each} -
+
+ {#each tokenSet as tkn} +
+ + of + +
+ {/each}
-
+
-{/if} +
diff --git a/src/lib/state.svelte.ts b/src/lib/state.svelte.ts index 242d1fa..5b0acb0 100644 --- a/src/lib/state.svelte.ts +++ b/src/lib/state.svelte.ts @@ -18,6 +18,11 @@ import { getAllowance, getBalance, getCompactBalance } from "./libraries/token"; import onboard from "./utils/web3-onboard"; import { createWalletClient, custom } from "viem"; +export type TokenContext = { + token: Token; + amount: bigint; +}; + class Store { mainnet = $state(true); orders = $state([]); @@ -35,10 +40,13 @@ class Store { )!; // --- Token --- // - inputTokens = $state([]); - outputTokens = $state([]); - inputAmounts = $state([1000000n]); - outputAmounts = $state([1000000n]); + inputTokens = $state([]); + outputTokens = $state([]); + + // inputTokens = $state([]); + // outputTokens = $state([]); + // inputAmounts = $state([1000000n]); + // outputAmounts = $state([1000000n]); balances = $derived.by(() => { return this.mapOverCoins(getBalance, this.mainnet, this.updatedDerived); @@ -115,8 +123,8 @@ class Store { } constructor() { - this.inputTokens = [coinList(this.mainnet)[0]]; - this.outputTokens = [coinList(this.mainnet)[1]]; + this.inputTokens = [{ token: coinList(this.mainnet)[0], amount: 1000000n }]; + this.outputTokens = [{ token: coinList(this.mainnet)[1], amount: 1000000n }]; this.wallets.subscribe((v) => { this.activeWallet.wallet = v?.[0]; diff --git a/src/lib/utils/interopableAddresses.ts b/src/lib/utils/interopableAddresses.ts index d779b3d..4b0a618 100644 --- a/src/lib/utils/interopableAddresses.ts +++ b/src/lib/utils/interopableAddresses.ts @@ -6,7 +6,19 @@ function toHex(num: number | bigint, bytes: number = 1) { return padEven(num.toString(16), bytes * 2); } -export const getInteropableAddress = (address: `0x${string}`, chainId: number | bigint) => { +type Version = "0001"; +type ChainType = "0000"; +type ChainReferenceLength = string; +type ChainReference = string; +type Address = string; + +export type InteropableAddress = + `0x${Version}${ChainType}${ChainReferenceLength}${ChainReference}${Address}`; + +export const getInteropableAddress = ( + address: `0x${string}`, + chainId: number | bigint +): InteropableAddress => { const version = "0001"; const chainType = "0000"; diff --git a/src/routes/+page.svelte b/src/routes/+page.svelte index 37e06de..4a23acf 100644 --- a/src/routes/+page.svelte +++ b/src/routes/+page.svelte @@ -12,7 +12,6 @@ import ReceiveMessage from "$lib/screens/ReceiveMessage.svelte"; import Finalise from "$lib/screens/Finalise.svelte"; import ConnectWallet from "$lib/screens/ConnectWallet.svelte"; - import TokenModal from "$lib/screens/TokenModal.svelte"; import store from "$lib/state.svelte"; // Fix bigint so we can json serialize it: @@ -29,9 +28,7 @@ $effect(() => { store.mainnet; - console.log(coinList(store.mainnet)); - store.inputTokens = [coinList(store.mainnet)[0]]; - store.outputTokens = [coinList(store.mainnet)[1]]; + store.inputTokens = [{ token: coinList(store.mainnet)[0], amount: 1000000n }]; }); const orderServer = $derived(new OrderServer(store.mainnet)); @@ -135,14 +132,14 @@ {#if !(!store.connectedAccount || !store.walletClient)}
-

Select {input ? "Input" : "Output"}

+

Select Input

+ + +
+ {/each} +
+
+
+ + + +
+
+
diff --git a/src/lib/config.ts b/src/lib/config.ts index 0a60e03..93ee486 100644 --- a/src/lib/config.ts +++ b/src/lib/config.ts @@ -49,6 +49,11 @@ export const chainMap = { } as const; export const chains = Object.keys(chainMap) as (keyof typeof chainMap)[]; export type chain = (typeof chains)[number]; +export const chainList = (mainnet:boolean) => { + if (mainnet == true) { + return ["ethereum", "base", "arbitrum"] + } else return ["sepolia", "optimismSepolia", "baseSepolia"] +} export type balanceQuery = Record>>; diff --git a/src/lib/screens/IssueIntent.svelte b/src/lib/screens/IssueIntent.svelte index d8763e6..75018d8 100644 --- a/src/lib/screens/IssueIntent.svelte +++ b/src/lib/screens/IssueIntent.svelte @@ -11,7 +11,8 @@ import { IntentFactory, escrowApprove } from "$lib/libraries/intentFactory"; import { CompactLib } from "$lib/libraries/compactLib"; import store from "$lib/state.svelte"; - import TokenModal from "./TokenModal.svelte"; + import InputTokenModal from "../components/InputTokenModal.svelte"; + import OutputTokenModal from "$lib/components/OutputTokenModal.svelte"; const bigIntSum = (...nums: bigint[]) => nums.reduce((a, b) => a + b, 0n); @@ -27,7 +28,8 @@ account: () => `0x${string}`; } = $props(); - let tokenSelectorActive = $state(false); + let inputTokenSelectorActive = $state(false); + let outputTokenSelectorActive = $state(false); const opts = $derived({ exclusiveFor: store.exclusiveFor, @@ -149,12 +151,17 @@ Select assets for your intent along with the verifier for the intent. Then choose your desired style of execution. Your intent will be sent to the LI.FI dev order server.

- {#if tokenSelectorActive} - + {#if inputTokenSelectorActive} + + {/if} + {#if outputTokenSelectorActive} + {/if}
@@ -162,7 +169,7 @@ {#each abstractInputs as input, i} {/each} -
@@ -193,33 +189,25 @@

You Receive

-
- - + + {/each}
diff --git a/src/routes/+page.svelte b/src/routes/+page.svelte index 4a23acf..52b0d8f 100644 --- a/src/routes/+page.svelte +++ b/src/routes/+page.svelte @@ -28,7 +28,8 @@ $effect(() => { store.mainnet; - store.inputTokens = [{ token: coinList(store.mainnet)[0], amount: 1000000n }]; + store.inputTokens = [{ token: coinList(store.mainnet)[0], amount: 0n }]; + store.outputTokens = [{token: coinList(store.mainnet)[1], amount: 0n}] }); const orderServer = $derived(new OrderServer(store.mainnet)); From d68f56975b9e27b299bed7a0faa4553e923f5360 Mon Sep 17 00:00:00 2001 From: Alexander Date: Tue, 28 Oct 2025 17:52:35 +0100 Subject: [PATCH 07/17] Make the next chain made the previous --- src/lib/components/InputTokenModal.svelte | 7 ++----- src/lib/components/OutputTokenModal.svelte | 20 +++++++++++++++----- src/lib/config.ts | 8 ++++---- src/lib/screens/IssueIntent.svelte | 5 +---- src/routes/+page.svelte | 2 +- 5 files changed, 23 insertions(+), 19 deletions(-) diff --git a/src/lib/components/InputTokenModal.svelte b/src/lib/components/InputTokenModal.svelte index c0f91f5..1b5011d 100644 --- a/src/lib/components/InputTokenModal.svelte +++ b/src/lib/components/InputTokenModal.svelte @@ -77,11 +77,8 @@ if (circutBreaker || currentInputTokens[0].token.name !== selectedTokenName) { circutBreaker = true; inputs = Object.fromEntries( - (tokenSet).map((token) => [ - getInteropableAddress(token.address, chainMap[token.chain].id), - 0 - ]) - ); + tokenSet.map((token) => [getInteropableAddress(token.address, chainMap[token.chain].id), 0]) + ); } }); diff --git a/src/lib/components/OutputTokenModal.svelte b/src/lib/components/OutputTokenModal.svelte index f8a2a2e..821ef68 100644 --- a/src/lib/components/OutputTokenModal.svelte +++ b/src/lib/components/OutputTokenModal.svelte @@ -52,7 +52,7 @@ function add() { if (outputs.length == 3) return; outputs.push({ - chain: chainList(store.mainnet)[0] as chain, + chain: outputs[outputs.length -1].chain, name: "usdc", amount: 0 }); @@ -60,7 +60,7 @@ function remove() { if (outputs.length == 1) return; - outputs.pop() + outputs.pop(); } @@ -109,13 +109,23 @@ {/each}
-
- +
+ - +
diff --git a/src/lib/config.ts b/src/lib/config.ts index 93ee486..1fb1974 100644 --- a/src/lib/config.ts +++ b/src/lib/config.ts @@ -49,11 +49,11 @@ export const chainMap = { } as const; export const chains = Object.keys(chainMap) as (keyof typeof chainMap)[]; export type chain = (typeof chains)[number]; -export const chainList = (mainnet:boolean) => { +export const chainList = (mainnet: boolean) => { if (mainnet == true) { - return ["ethereum", "base", "arbitrum"] - } else return ["sepolia", "optimismSepolia", "baseSepolia"] -} + return ["ethereum", "base", "arbitrum"]; + } else return ["sepolia", "optimismSepolia", "baseSepolia"]; +}; export type balanceQuery = Record>>; diff --git a/src/lib/screens/IssueIntent.svelte b/src/lib/screens/IssueIntent.svelte index 75018d8..bde2cc7 100644 --- a/src/lib/screens/IssueIntent.svelte +++ b/src/lib/screens/IssueIntent.svelte @@ -197,10 +197,7 @@
- {formatTokenAmount( - outputToken.amount, - outputToken.token.decimals - )} + {formatTokenAmount(outputToken.amount, outputToken.token.decimals)}
{outputToken.token.name.toUpperCase()}
diff --git a/src/routes/+page.svelte b/src/routes/+page.svelte index 52b0d8f..d90893b 100644 --- a/src/routes/+page.svelte +++ b/src/routes/+page.svelte @@ -29,7 +29,7 @@ $effect(() => { store.mainnet; store.inputTokens = [{ token: coinList(store.mainnet)[0], amount: 0n }]; - store.outputTokens = [{token: coinList(store.mainnet)[1], amount: 0n}] + store.outputTokens = [{ token: coinList(store.mainnet)[1], amount: 0n }]; }); const orderServer = $derived(new OrderServer(store.mainnet)); From 1ee596e7d401e7b713d509c411a45d40b588cf07 Mon Sep 17 00:00:00 2001 From: Alexander Date: Wed, 29 Oct 2025 23:15:28 +0100 Subject: [PATCH 08/17] Finalise order creation and begin work on filling intents --- src/lib/abi/escrow.ts | 412 +------------ src/lib/abi/multichain_escrow.ts | 657 +++++++++++++++++++++ src/lib/abi/outputsettler.ts | 39 +- src/lib/abi/settlercompact.ts | 498 +--------------- src/lib/components/OutputTokenModal.svelte | 2 +- src/lib/config.ts | 2 + src/lib/libraries/intent.ts | 616 +++++++++++++++++-- src/lib/libraries/intentFactory.ts | 32 +- src/lib/libraries/solver.ts | 68 +-- src/lib/screens/IssueIntent.svelte | 48 +- src/lib/utils/idLib.ts | 8 +- src/lib/utils/multichainOrder.ts | 104 ---- src/lib/utils/orderLib.ts | 37 +- src/routes/+page.svelte | 16 +- src/types/index.ts | 2 +- 15 files changed, 1352 insertions(+), 1189 deletions(-) create mode 100644 src/lib/abi/multichain_escrow.ts delete mode 100644 src/lib/utils/multichainOrder.ts diff --git a/src/lib/abi/escrow.ts b/src/lib/abi/escrow.ts index ab3168c..5e4e38b 100644 --- a/src/lib/abi/escrow.ts +++ b/src/lib/abi/escrow.ts @@ -1,13 +1,7 @@ export const SETTLER_ESCROW_ABI = [ { type: "constructor", - inputs: [ - { - name: "initialOwner", - type: "address", - internalType: "address" - } - ], + inputs: [], stateMutability: "nonpayable" }, { @@ -23,33 +17,6 @@ export const SETTLER_ESCROW_ABI = [ ], stateMutability: "view" }, - { - type: "function", - name: "applyGovernanceFee", - inputs: [], - outputs: [], - stateMutability: "nonpayable" - }, - { - type: "function", - name: "cancelOwnershipHandover", - inputs: [], - outputs: [], - stateMutability: "payable" - }, - { - type: "function", - name: "completeOwnershipHandover", - inputs: [ - { - name: "pendingOwner", - type: "address", - internalType: "address" - } - ], - outputs: [], - stateMutability: "payable" - }, { type: "function", name: "eip712Domain", @@ -346,45 +313,6 @@ export const SETTLER_ESCROW_ABI = [ outputs: [], stateMutability: "nonpayable" }, - { - type: "function", - name: "governanceFee", - inputs: [], - outputs: [ - { - name: "", - type: "uint64", - internalType: "uint64" - } - ], - stateMutability: "view" - }, - { - type: "function", - name: "nextGovernanceFee", - inputs: [], - outputs: [ - { - name: "", - type: "uint64", - internalType: "uint64" - } - ], - stateMutability: "view" - }, - { - type: "function", - name: "nextGovernanceFeeTime", - inputs: [], - outputs: [ - { - name: "", - type: "uint64", - internalType: "uint64" - } - ], - stateMutability: "view" - }, { type: "function", name: "open", @@ -589,123 +517,6 @@ export const SETTLER_ESCROW_ABI = [ outputs: [], stateMutability: "nonpayable" }, - { - type: "function", - name: "openForAndFinalise", - inputs: [ - { - name: "order", - type: "tuple", - internalType: "struct StandardOrder", - components: [ - { - name: "user", - type: "address", - internalType: "address" - }, - { - name: "nonce", - type: "uint256", - internalType: "uint256" - }, - { - name: "originChainId", - type: "uint256", - internalType: "uint256" - }, - { - name: "expires", - type: "uint32", - internalType: "uint32" - }, - { - name: "fillDeadline", - type: "uint32", - internalType: "uint32" - }, - { - name: "inputOracle", - type: "address", - internalType: "address" - }, - { - name: "inputs", - type: "uint256[2][]", - internalType: "uint256[2][]" - }, - { - name: "outputs", - type: "tuple[]", - internalType: "struct MandateOutput[]", - components: [ - { - name: "oracle", - type: "bytes32", - internalType: "bytes32" - }, - { - name: "settler", - type: "bytes32", - internalType: "bytes32" - }, - { - name: "chainId", - type: "uint256", - internalType: "uint256" - }, - { - name: "token", - type: "bytes32", - internalType: "bytes32" - }, - { - name: "amount", - type: "uint256", - internalType: "uint256" - }, - { - name: "recipient", - type: "bytes32", - internalType: "bytes32" - }, - { - name: "call", - type: "bytes", - internalType: "bytes" - }, - { - name: "context", - type: "bytes", - internalType: "bytes" - } - ] - } - ] - }, - { - name: "sponsor", - type: "address", - internalType: "address" - }, - { - name: "signature", - type: "bytes", - internalType: "bytes" - }, - { - name: "destination", - type: "address", - internalType: "address" - }, - { - name: "call", - type: "bytes", - internalType: "bytes" - } - ], - outputs: [], - stateMutability: "nonpayable" - }, { type: "function", name: "orderIdentifier", @@ -828,38 +639,6 @@ export const SETTLER_ESCROW_ABI = [ ], stateMutability: "view" }, - { - type: "function", - name: "owner", - inputs: [], - outputs: [ - { - name: "result", - type: "address", - internalType: "address" - } - ], - stateMutability: "view" - }, - { - type: "function", - name: "ownershipHandoverExpiresAt", - inputs: [ - { - name: "pendingOwner", - type: "address", - internalType: "address" - } - ], - outputs: [ - { - name: "result", - type: "uint256", - internalType: "uint256" - } - ], - stateMutability: "view" - }, { type: "function", name: "purchaseOrder", @@ -880,7 +659,7 @@ export const SETTLER_ESCROW_ABI = [ internalType: "address" }, { - name: "call", + name: "callData", type: "bytes", internalType: "bytes" }, @@ -1135,46 +914,6 @@ export const SETTLER_ESCROW_ABI = [ outputs: [], stateMutability: "nonpayable" }, - { - type: "function", - name: "renounceOwnership", - inputs: [], - outputs: [], - stateMutability: "payable" - }, - { - type: "function", - name: "requestOwnershipHandover", - inputs: [], - outputs: [], - stateMutability: "payable" - }, - { - type: "function", - name: "setGovernanceFee", - inputs: [ - { - name: "_nextGovernanceFee", - type: "uint64", - internalType: "uint64" - } - ], - outputs: [], - stateMutability: "nonpayable" - }, - { - type: "function", - name: "transferOwnership", - inputs: [ - { - name: "newOwner", - type: "address", - internalType: "address" - } - ], - outputs: [], - stateMutability: "payable" - }, { type: "event", name: "EIP712DomainChanged", @@ -1206,44 +945,6 @@ export const SETTLER_ESCROW_ABI = [ ], anonymous: false }, - { - type: "event", - name: "GovernanceFeeChanged", - inputs: [ - { - name: "oldGovernanceFee", - type: "uint64", - indexed: false, - internalType: "uint64" - }, - { - name: "newGovernanceFee", - type: "uint64", - indexed: false, - internalType: "uint64" - } - ], - anonymous: false - }, - { - type: "event", - name: "NextGovernanceFee", - inputs: [ - { - name: "nextGovernanceFee", - type: "uint64", - indexed: false, - internalType: "uint64" - }, - { - name: "nextGovernanceFeeTime", - type: "uint64", - indexed: false, - internalType: "uint64" - } - ], - anonymous: false - }, { type: "event", name: "Open", @@ -1347,19 +1048,6 @@ export const SETTLER_ESCROW_ABI = [ ], anonymous: false }, - { - type: "event", - name: "Open", - inputs: [ - { - name: "orderId", - type: "bytes32", - indexed: true, - internalType: "bytes32" - } - ], - anonymous: false - }, { type: "event", name: "OrderPurchased", @@ -1385,51 +1073,6 @@ export const SETTLER_ESCROW_ABI = [ ], anonymous: false }, - { - type: "event", - name: "OwnershipHandoverCanceled", - inputs: [ - { - name: "pendingOwner", - type: "address", - indexed: true, - internalType: "address" - } - ], - anonymous: false - }, - { - type: "event", - name: "OwnershipHandoverRequested", - inputs: [ - { - name: "pendingOwner", - type: "address", - indexed: true, - internalType: "address" - } - ], - anonymous: false - }, - { - type: "event", - name: "OwnershipTransferred", - inputs: [ - { - name: "oldOwner", - type: "address", - indexed: true, - internalType: "address" - }, - { - name: "newOwner", - type: "address", - indexed: true, - internalType: "address" - } - ], - anonymous: false - }, { type: "event", name: "Refunded", @@ -1443,11 +1086,6 @@ export const SETTLER_ESCROW_ABI = [ ], anonymous: false }, - { - type: "error", - name: "AlreadyInitialized", - inputs: [] - }, { type: "error", name: "AlreadyPurchased", @@ -1475,15 +1113,15 @@ export const SETTLER_ESCROW_ABI = [ }, { type: "error", - name: "FilledTooLate", + name: "FillDeadlineAfterExpiry", inputs: [ { - name: "expected", + name: "fillDeadline", type: "uint32", internalType: "uint32" }, { - name: "actual", + name: "expires", type: "uint32", internalType: "uint32" } @@ -1491,13 +1129,19 @@ export const SETTLER_ESCROW_ABI = [ }, { type: "error", - name: "GovernanceFeeChangeNotReady", - inputs: [] - }, - { - type: "error", - name: "GovernanceFeeTooHigh", - inputs: [] + name: "FilledTooLate", + inputs: [ + { + name: "expected", + type: "uint32", + internalType: "uint32" + }, + { + name: "actual", + type: "uint32", + internalType: "uint32" + } + ] }, { type: "error", @@ -1529,21 +1173,11 @@ export const SETTLER_ESCROW_ABI = [ name: "InvalidTimestampLength", inputs: [] }, - { - type: "error", - name: "NewOwnerIsZeroAddress", - inputs: [] - }, { type: "error", name: "NoDestination", inputs: [] }, - { - type: "error", - name: "NoHandoverRequest", - inputs: [] - }, { type: "error", name: "NotOrderOwner", @@ -1620,8 +1254,14 @@ export const SETTLER_ESCROW_ABI = [ }, { type: "error", - name: "Unauthorized", - inputs: [] + name: "UnexpectedCaller", + inputs: [ + { + name: "expectedCaller", + type: "bytes32", + internalType: "bytes32" + } + ] }, { type: "error", diff --git a/src/lib/abi/multichain_escrow.ts b/src/lib/abi/multichain_escrow.ts new file mode 100644 index 0000000..0382ffb --- /dev/null +++ b/src/lib/abi/multichain_escrow.ts @@ -0,0 +1,657 @@ +export const MULTICHAIN_SETTLER_ESCROW_ABI = [ + { + type: "function", + name: "efficientRequireProven", + inputs: [ + { + name: "proofSeries", + type: "bytes", + internalType: "bytes" + } + ], + outputs: [], + stateMutability: "view" + }, + { + type: "function", + name: "fill", + inputs: [ + { + name: "orderId", + type: "bytes32", + internalType: "bytes32" + }, + { + name: "output", + type: "tuple", + internalType: "struct MandateOutput", + components: [ + { + name: "oracle", + type: "bytes32", + internalType: "bytes32" + }, + { + name: "settler", + type: "bytes32", + internalType: "bytes32" + }, + { + name: "chainId", + type: "uint256", + internalType: "uint256" + }, + { + name: "token", + type: "bytes32", + internalType: "bytes32" + }, + { + name: "amount", + type: "uint256", + internalType: "uint256" + }, + { + name: "recipient", + type: "bytes32", + internalType: "bytes32" + }, + { + name: "call", + type: "bytes", + internalType: "bytes" + }, + { + name: "context", + type: "bytes", + internalType: "bytes" + } + ] + }, + { + name: "fillDeadline", + type: "uint48", + internalType: "uint48" + }, + { + name: "fillerData", + type: "bytes", + internalType: "bytes" + } + ], + outputs: [ + { + name: "fillRecordHash", + type: "bytes32", + internalType: "bytes32" + } + ], + stateMutability: "payable" + }, + { + type: "function", + name: "fillOrderOutputs", + inputs: [ + { + name: "orderId", + type: "bytes32", + internalType: "bytes32" + }, + { + name: "outputs", + type: "tuple[]", + internalType: "struct MandateOutput[]", + components: [ + { + name: "oracle", + type: "bytes32", + internalType: "bytes32" + }, + { + name: "settler", + type: "bytes32", + internalType: "bytes32" + }, + { + name: "chainId", + type: "uint256", + internalType: "uint256" + }, + { + name: "token", + type: "bytes32", + internalType: "bytes32" + }, + { + name: "amount", + type: "uint256", + internalType: "uint256" + }, + { + name: "recipient", + type: "bytes32", + internalType: "bytes32" + }, + { + name: "call", + type: "bytes", + internalType: "bytes" + }, + { + name: "context", + type: "bytes", + internalType: "bytes" + } + ] + }, + { + name: "fillDeadline", + type: "uint48", + internalType: "uint48" + }, + { + name: "fillerData", + type: "bytes", + internalType: "bytes" + } + ], + outputs: [], + stateMutability: "payable" + }, + { + type: "function", + name: "getFillRecord", + inputs: [ + { + name: "orderId", + type: "bytes32", + internalType: "bytes32" + }, + { + name: "output", + type: "tuple", + internalType: "struct MandateOutput", + components: [ + { + name: "oracle", + type: "bytes32", + internalType: "bytes32" + }, + { + name: "settler", + type: "bytes32", + internalType: "bytes32" + }, + { + name: "chainId", + type: "uint256", + internalType: "uint256" + }, + { + name: "token", + type: "bytes32", + internalType: "bytes32" + }, + { + name: "amount", + type: "uint256", + internalType: "uint256" + }, + { + name: "recipient", + type: "bytes32", + internalType: "bytes32" + }, + { + name: "call", + type: "bytes", + internalType: "bytes" + }, + { + name: "context", + type: "bytes", + internalType: "bytes" + } + ] + } + ], + outputs: [ + { + name: "payloadHash", + type: "bytes32", + internalType: "bytes32" + } + ], + stateMutability: "view" + }, + { + type: "function", + name: "getFillRecord", + inputs: [ + { + name: "orderId", + type: "bytes32", + internalType: "bytes32" + }, + { + name: "outputHash", + type: "bytes32", + internalType: "bytes32" + } + ], + outputs: [ + { + name: "payloadHash", + type: "bytes32", + internalType: "bytes32" + } + ], + stateMutability: "view" + }, + { + type: "function", + name: "hasAttested", + inputs: [ + { + name: "payloads", + type: "bytes[]", + internalType: "bytes[]" + } + ], + outputs: [ + { + name: "accumulator", + type: "bool", + internalType: "bool" + } + ], + stateMutability: "view" + }, + { + type: "function", + name: "isProven", + inputs: [ + { + name: "remoteChainId", + type: "uint256", + internalType: "uint256" + }, + { + name: "remoteOracle", + type: "bytes32", + internalType: "bytes32" + }, + { + name: "application", + type: "bytes32", + internalType: "bytes32" + }, + { + name: "dataHash", + type: "bytes32", + internalType: "bytes32" + } + ], + outputs: [ + { + name: "", + type: "bool", + internalType: "bool" + } + ], + stateMutability: "view" + }, + { + type: "function", + name: "setAttestation", + inputs: [ + { + name: "orderId", + type: "bytes32", + internalType: "bytes32" + }, + { + name: "solver", + type: "bytes32", + internalType: "bytes32" + }, + { + name: "timestamp", + type: "uint32", + internalType: "uint32" + }, + { + name: "output", + type: "tuple", + internalType: "struct MandateOutput", + components: [ + { + name: "oracle", + type: "bytes32", + internalType: "bytes32" + }, + { + name: "settler", + type: "bytes32", + internalType: "bytes32" + }, + { + name: "chainId", + type: "uint256", + internalType: "uint256" + }, + { + name: "token", + type: "bytes32", + internalType: "bytes32" + }, + { + name: "amount", + type: "uint256", + internalType: "uint256" + }, + { + name: "recipient", + type: "bytes32", + internalType: "bytes32" + }, + { + name: "call", + type: "bytes", + internalType: "bytes" + }, + { + name: "context", + type: "bytes", + internalType: "bytes" + } + ] + } + ], + outputs: [], + stateMutability: "nonpayable" + }, + { + type: "event", + name: "OutputFilled", + inputs: [ + { + name: "orderId", + type: "bytes32", + indexed: true, + internalType: "bytes32" + }, + { + name: "solver", + type: "bytes32", + indexed: false, + internalType: "bytes32" + }, + { + name: "timestamp", + type: "uint32", + indexed: false, + internalType: "uint32" + }, + { + name: "output", + type: "tuple", + indexed: false, + internalType: "struct MandateOutput", + components: [ + { + name: "oracle", + type: "bytes32", + internalType: "bytes32" + }, + { + name: "settler", + type: "bytes32", + internalType: "bytes32" + }, + { + name: "chainId", + type: "uint256", + internalType: "uint256" + }, + { + name: "token", + type: "bytes32", + internalType: "bytes32" + }, + { + name: "amount", + type: "uint256", + internalType: "uint256" + }, + { + name: "recipient", + type: "bytes32", + internalType: "bytes32" + }, + { + name: "call", + type: "bytes", + internalType: "bytes" + }, + { + name: "context", + type: "bytes", + internalType: "bytes" + } + ] + }, + { + name: "finalAmount", + type: "uint256", + indexed: false, + internalType: "uint256" + } + ], + anonymous: false + }, + { + type: "event", + name: "OutputProven", + inputs: [ + { + name: "chainid", + type: "uint256", + indexed: false, + internalType: "uint256" + }, + { + name: "remoteIdentifier", + type: "bytes32", + indexed: false, + internalType: "bytes32" + }, + { + name: "application", + type: "bytes32", + indexed: false, + internalType: "bytes32" + }, + { + name: "payloadHash", + type: "bytes32", + indexed: false, + internalType: "bytes32" + } + ], + anonymous: false + }, + { + type: "error", + name: "AlreadyFilled", + inputs: [] + }, + { + type: "error", + name: "CallOutOfRange", + inputs: [] + }, + { + type: "error", + name: "ContextOutOfRange", + inputs: [] + }, + { + type: "error", + name: "ExclusiveTo", + inputs: [ + { + name: "solver", + type: "bytes32", + internalType: "bytes32" + } + ] + }, + { + type: "error", + name: "FailedCall", + inputs: [] + }, + { + type: "error", + name: "FillDeadline", + inputs: [] + }, + { + type: "error", + name: "HasDirtyBits", + inputs: [] + }, + { + type: "error", + name: "InsufficientBalance", + inputs: [ + { + name: "balance", + type: "uint256", + internalType: "uint256" + }, + { + name: "needed", + type: "uint256", + internalType: "uint256" + } + ] + }, + { + type: "error", + name: "InvalidAttestation", + inputs: [ + { + name: "storedFillRecordHash", + type: "bytes32", + internalType: "bytes32" + }, + { + name: "givenFillRecordHash", + type: "bytes32", + internalType: "bytes32" + } + ] + }, + { + type: "error", + name: "InvalidContextDataLength", + inputs: [] + }, + { + type: "error", + name: "NotDivisible", + inputs: [ + { + name: "value", + type: "uint256", + internalType: "uint256" + }, + { + name: "divisor", + type: "uint256", + internalType: "uint256" + } + ] + }, + { + type: "error", + name: "NotImplemented", + inputs: [] + }, + { + type: "error", + name: "NotProven", + inputs: [] + }, + { + type: "error", + name: "PayloadTooSmall", + inputs: [] + }, + { + type: "error", + name: "SafeERC20FailedOperation", + inputs: [ + { + name: "token", + type: "address", + internalType: "address" + } + ] + }, + { + type: "error", + name: "WrongChain", + inputs: [ + { + name: "expected", + type: "uint256", + internalType: "uint256" + }, + { + name: "actual", + type: "uint256", + internalType: "uint256" + } + ] + }, + { + type: "error", + name: "WrongOutputOracle", + inputs: [ + { + name: "addressThis", + type: "bytes32", + internalType: "bytes32" + }, + { + name: "expected", + type: "bytes32", + internalType: "bytes32" + } + ] + }, + { + type: "error", + name: "WrongOutputSettler", + inputs: [ + { + name: "addressThis", + type: "bytes32", + internalType: "bytes32" + }, + { + name: "expected", + type: "bytes32", + internalType: "bytes32" + } + ] + }, + { + type: "error", + name: "ZeroValue", + inputs: [] + } +] as const; diff --git a/src/lib/abi/outputsettler.ts b/src/lib/abi/outputsettler.ts index 1937aba..ac4a130 100644 --- a/src/lib/abi/outputsettler.ts +++ b/src/lib/abi/outputsettler.ts @@ -57,7 +57,7 @@ export const COIN_FILLER_ABI = [ internalType: "bytes32" }, { - name: "call", + name: "callbackData", type: "bytes", internalType: "bytes" }, @@ -133,7 +133,7 @@ export const COIN_FILLER_ABI = [ internalType: "bytes32" }, { - name: "call", + name: "callbackData", type: "bytes", internalType: "bytes" }, @@ -203,7 +203,7 @@ export const COIN_FILLER_ABI = [ internalType: "bytes32" }, { - name: "call", + name: "callbackData", type: "bytes", internalType: "bytes" }, @@ -356,7 +356,7 @@ export const COIN_FILLER_ABI = [ internalType: "bytes32" }, { - name: "call", + name: "callbackData", type: "bytes", internalType: "bytes" }, @@ -430,7 +430,7 @@ export const COIN_FILLER_ABI = [ internalType: "bytes32" }, { - name: "call", + name: "callbackData", type: "bytes", internalType: "bytes" }, @@ -498,23 +498,17 @@ export const COIN_FILLER_ABI = [ }, { type: "error", - name: "ExclusiveTo", - inputs: [ - { - name: "solver", - type: "bytes32", - internalType: "bytes32" - } - ] + name: "FailedCall", + inputs: [] }, { type: "error", - name: "FailedCall", + name: "FillDeadline", inputs: [] }, { type: "error", - name: "FillDeadline", + name: "HasDirtyBits", inputs: [] }, { @@ -549,11 +543,6 @@ export const COIN_FILLER_ABI = [ } ] }, - { - type: "error", - name: "InvalidContextDataLength", - inputs: [] - }, { type: "error", name: "NotDivisible", @@ -570,11 +559,6 @@ export const COIN_FILLER_ABI = [ } ] }, - { - type: "error", - name: "NotImplemented", - inputs: [] - }, { type: "error", name: "NotProven", @@ -643,10 +627,5 @@ export const COIN_FILLER_ABI = [ internalType: "bytes32" } ] - }, - { - type: "error", - name: "ZeroValue", - inputs: [] } ] as const; diff --git a/src/lib/abi/settlercompact.ts b/src/lib/abi/settlercompact.ts index 378217f..0b70a94 100644 --- a/src/lib/abi/settlercompact.ts +++ b/src/lib/abi/settlercompact.ts @@ -6,11 +6,6 @@ export const SETTLER_COMPACT_ABI = [ name: "compact", type: "address", internalType: "address" - }, - { - name: "initialOwner", - type: "address", - internalType: "address" } ], stateMutability: "nonpayable" @@ -41,130 +36,6 @@ export const SETTLER_COMPACT_ABI = [ ], stateMutability: "view" }, - { - type: "function", - name: "applyGovernanceFee", - inputs: [], - outputs: [], - stateMutability: "nonpayable" - }, - { - type: "function", - name: "broadcast", - inputs: [ - { - name: "order", - type: "tuple", - internalType: "struct StandardOrder", - components: [ - { - name: "user", - type: "address", - internalType: "address" - }, - { - name: "nonce", - type: "uint256", - internalType: "uint256" - }, - { - name: "originChainId", - type: "uint256", - internalType: "uint256" - }, - { - name: "expires", - type: "uint32", - internalType: "uint32" - }, - { - name: "fillDeadline", - type: "uint32", - internalType: "uint32" - }, - { - name: "inputOracle", - type: "address", - internalType: "address" - }, - { - name: "inputs", - type: "uint256[2][]", - internalType: "uint256[2][]" - }, - { - name: "outputs", - type: "tuple[]", - internalType: "struct MandateOutput[]", - components: [ - { - name: "oracle", - type: "bytes32", - internalType: "bytes32" - }, - { - name: "settler", - type: "bytes32", - internalType: "bytes32" - }, - { - name: "chainId", - type: "uint256", - internalType: "uint256" - }, - { - name: "token", - type: "bytes32", - internalType: "bytes32" - }, - { - name: "amount", - type: "uint256", - internalType: "uint256" - }, - { - name: "recipient", - type: "bytes32", - internalType: "bytes32" - }, - { - name: "call", - type: "bytes", - internalType: "bytes" - }, - { - name: "context", - type: "bytes", - internalType: "bytes" - } - ] - } - ] - } - ], - outputs: [], - stateMutability: "nonpayable" - }, - { - type: "function", - name: "cancelOwnershipHandover", - inputs: [], - outputs: [], - stateMutability: "payable" - }, - { - type: "function", - name: "completeOwnershipHandover", - inputs: [ - { - name: "pendingOwner", - type: "address", - internalType: "address" - } - ], - outputs: [], - stateMutability: "payable" - }, { type: "function", name: "eip712Domain", @@ -471,45 +342,6 @@ export const SETTLER_COMPACT_ABI = [ outputs: [], stateMutability: "nonpayable" }, - { - type: "function", - name: "governanceFee", - inputs: [], - outputs: [ - { - name: "", - type: "uint64", - internalType: "uint64" - } - ], - stateMutability: "view" - }, - { - type: "function", - name: "nextGovernanceFee", - inputs: [], - outputs: [ - { - name: "", - type: "uint64", - internalType: "uint64" - } - ], - stateMutability: "view" - }, - { - type: "function", - name: "nextGovernanceFeeTime", - inputs: [], - outputs: [ - { - name: "", - type: "uint64", - internalType: "uint64" - } - ], - stateMutability: "view" - }, { type: "function", name: "orderIdentifier", @@ -613,38 +445,6 @@ export const SETTLER_COMPACT_ABI = [ ], stateMutability: "view" }, - { - type: "function", - name: "owner", - inputs: [], - outputs: [ - { - name: "result", - type: "address", - internalType: "address" - } - ], - stateMutability: "view" - }, - { - type: "function", - name: "ownershipHandoverExpiresAt", - inputs: [ - { - name: "pendingOwner", - type: "address", - internalType: "address" - } - ], - outputs: [ - { - name: "result", - type: "uint256", - internalType: "uint256" - } - ], - stateMutability: "view" - }, { type: "function", name: "purchaseOrder", @@ -665,7 +465,7 @@ export const SETTLER_COMPACT_ABI = [ internalType: "address" }, { - name: "call", + name: "callData", type: "bytes", internalType: "bytes" }, @@ -823,46 +623,6 @@ export const SETTLER_COMPACT_ABI = [ ], stateMutability: "view" }, - { - type: "function", - name: "renounceOwnership", - inputs: [], - outputs: [], - stateMutability: "payable" - }, - { - type: "function", - name: "requestOwnershipHandover", - inputs: [], - outputs: [], - stateMutability: "payable" - }, - { - type: "function", - name: "setGovernanceFee", - inputs: [ - { - name: "_nextGovernanceFee", - type: "uint64", - internalType: "uint64" - } - ], - outputs: [], - stateMutability: "nonpayable" - }, - { - type: "function", - name: "transferOwnership", - inputs: [ - { - name: "newOwner", - type: "address", - internalType: "address" - } - ], - outputs: [], - stateMutability: "payable" - }, { type: "event", name: "EIP712DomainChanged", @@ -894,147 +654,6 @@ export const SETTLER_COMPACT_ABI = [ ], anonymous: false }, - { - type: "event", - name: "GovernanceFeeChanged", - inputs: [ - { - name: "oldGovernanceFee", - type: "uint64", - indexed: false, - internalType: "uint64" - }, - { - name: "newGovernanceFee", - type: "uint64", - indexed: false, - internalType: "uint64" - } - ], - anonymous: false - }, - { - type: "event", - name: "IntentRegistered", - inputs: [ - { - name: "orderId", - type: "bytes32", - indexed: true, - internalType: "bytes32" - }, - { - name: "order", - type: "tuple", - indexed: false, - internalType: "struct StandardOrder", - components: [ - { - name: "user", - type: "address", - internalType: "address" - }, - { - name: "nonce", - type: "uint256", - internalType: "uint256" - }, - { - name: "originChainId", - type: "uint256", - internalType: "uint256" - }, - { - name: "expires", - type: "uint32", - internalType: "uint32" - }, - { - name: "fillDeadline", - type: "uint32", - internalType: "uint32" - }, - { - name: "inputOracle", - type: "address", - internalType: "address" - }, - { - name: "inputs", - type: "uint256[2][]", - internalType: "uint256[2][]" - }, - { - name: "outputs", - type: "tuple[]", - internalType: "struct MandateOutput[]", - components: [ - { - name: "oracle", - type: "bytes32", - internalType: "bytes32" - }, - { - name: "settler", - type: "bytes32", - internalType: "bytes32" - }, - { - name: "chainId", - type: "uint256", - internalType: "uint256" - }, - { - name: "token", - type: "bytes32", - internalType: "bytes32" - }, - { - name: "amount", - type: "uint256", - internalType: "uint256" - }, - { - name: "recipient", - type: "bytes32", - internalType: "bytes32" - }, - { - name: "call", - type: "bytes", - internalType: "bytes" - }, - { - name: "context", - type: "bytes", - internalType: "bytes" - } - ] - } - ] - } - ], - anonymous: false - }, - { - type: "event", - name: "NextGovernanceFee", - inputs: [ - { - name: "nextGovernanceFee", - type: "uint64", - indexed: false, - internalType: "uint64" - }, - { - name: "nextGovernanceFeeTime", - type: "uint64", - indexed: false, - internalType: "uint64" - } - ], - anonymous: false - }, { type: "event", name: "OrderPurchased", @@ -1060,56 +679,6 @@ export const SETTLER_COMPACT_ABI = [ ], anonymous: false }, - { - type: "event", - name: "OwnershipHandoverCanceled", - inputs: [ - { - name: "pendingOwner", - type: "address", - indexed: true, - internalType: "address" - } - ], - anonymous: false - }, - { - type: "event", - name: "OwnershipHandoverRequested", - inputs: [ - { - name: "pendingOwner", - type: "address", - indexed: true, - internalType: "address" - } - ], - anonymous: false - }, - { - type: "event", - name: "OwnershipTransferred", - inputs: [ - { - name: "oldOwner", - type: "address", - indexed: true, - internalType: "address" - }, - { - name: "newOwner", - type: "address", - indexed: true, - internalType: "address" - } - ], - anonymous: false - }, - { - type: "error", - name: "AlreadyInitialized", - inputs: [] - }, { type: "error", name: "AlreadyPurchased", @@ -1127,13 +696,24 @@ export const SETTLER_COMPACT_ABI = [ }, { type: "error", - name: "DeadlinePassed", + name: "Expired", inputs: [] }, { type: "error", - name: "Expired", - inputs: [] + name: "FillDeadlineAfterExpiry", + inputs: [ + { + name: "fillDeadline", + type: "uint32", + internalType: "uint32" + }, + { + name: "expires", + type: "uint32", + internalType: "uint32" + } + ] }, { type: "error", @@ -1153,12 +733,7 @@ export const SETTLER_COMPACT_ABI = [ }, { type: "error", - name: "GovernanceFeeChangeNotReady", - inputs: [] - }, - { - type: "error", - name: "GovernanceFeeTooHigh", + name: "HasDirtyBits", inputs: [] }, { @@ -1181,31 +756,16 @@ export const SETTLER_COMPACT_ABI = [ name: "InvalidTimestampLength", inputs: [] }, - { - type: "error", - name: "NewOwnerIsZeroAddress", - inputs: [] - }, { type: "error", name: "NoDestination", inputs: [] }, - { - type: "error", - name: "NoHandoverRequest", - inputs: [] - }, { type: "error", name: "NotOrderOwner", inputs: [] }, - { - type: "error", - name: "NotRegistered", - inputs: [] - }, { type: "error", name: "OrderIdMismatch", @@ -1256,8 +816,14 @@ export const SETTLER_COMPACT_ABI = [ }, { type: "error", - name: "Unauthorized", - inputs: [] + name: "UnexpectedCaller", + inputs: [ + { + name: "expectedCaller", + type: "bytes32", + internalType: "bytes32" + } + ] }, { type: "error", @@ -1279,21 +845,5 @@ export const SETTLER_COMPACT_ABI = [ internalType: "uint256" } ] - }, - { - type: "error", - name: "WrongChain", - inputs: [ - { - name: "expected", - type: "uint256", - internalType: "uint256" - }, - { - name: "provided", - type: "uint256", - internalType: "uint256" - } - ] } ] as const; diff --git a/src/lib/components/OutputTokenModal.svelte b/src/lib/components/OutputTokenModal.svelte index 821ef68..f053ad7 100644 --- a/src/lib/components/OutputTokenModal.svelte +++ b/src/lib/components/OutputTokenModal.svelte @@ -52,7 +52,7 @@ function add() { if (outputs.length == 3) return; outputs.push({ - chain: outputs[outputs.length -1].chain, + chain: outputs[outputs.length - 1].chain, name: "usdc", amount: 0 }); diff --git a/src/lib/config.ts b/src/lib/config.ts index 1fb1974..3fa1787 100644 --- a/src/lib/config.ts +++ b/src/lib/config.ts @@ -15,6 +15,8 @@ export const BYTES32_ZERO = export const COMPACT = "0x00000000000000171ede64904551eeDF3C6C9788" as const; export const INPUT_SETTLER_COMPACT_LIFI = "0x000000c9eC71B1a39055Ec631200ED0022140074" as const; export const INPUT_SETTLER_ESCROW_LIFI = "0x000001bf3F3175BD007f3889b50000c7006E72c0" as const; +export const MULTICHAIN_INPUT_SETTLER_ESCROW_LIFI = + "0x000001bf3F3175BD007f3889b50000c7006E72c0" as const; export const ALWAYS_OK_ALLOCATOR = "301267367668059890006832136" as const; export const POLYMER_ALLOCATOR = "116450367070547927622991121" as const; // 0x02ecC89C25A5DCB1206053530c58E002a737BD11 signing by 0x934244C8cd6BeBDBd0696A659D77C9BDfE86Efe6 export const COIN_FILLER = "0x00000000D7278408CE7a490015577c41e57143a5" as const; diff --git a/src/lib/libraries/intent.ts b/src/lib/libraries/intent.ts index 357ffd0..95e9e85 100644 --- a/src/lib/libraries/intent.ts +++ b/src/lib/libraries/intent.ts @@ -1,5 +1,22 @@ -import { encodePacked, hashStruct, toHex } from "viem"; -import type { BatchCompact, CompactMandate, MandateOutput, StandardOrder } from "../../types"; +import { + encodeAbiParameters, + encodePacked, + hashStruct, + hashTypedData, + keccak256, + parseAbiParameters, + toHex +} from "viem"; +import type { + BatchCompact, + CompactMandate, + MandateOutput, + MultichainOrder, + MultichainOrderComponent, + NoSignature, + Signature, + StandardOrder +} from "../../types"; import { COMPACT_ABI } from "../abi/compact"; import { chainMap, @@ -8,6 +25,7 @@ import { getOracle, INPUT_SETTLER_COMPACT_LIFI, INPUT_SETTLER_ESCROW_LIFI, + MULTICHAIN_INPUT_SETTLER_ESCROW_LIFI, type chain, type Token, type Verifier, @@ -18,17 +36,45 @@ import { compact_type_hash, compactTypes } from "../utils/typedMessage"; import { addressToBytes32 } from "../utils/convert"; import { SETTLER_ESCROW_ABI } from "../abi/escrow"; import type { TokenContext } from "$lib/state.svelte"; +import { MULTICHAIN_SETTLER_ESCROW_ABI } from "$lib/abi/multichain_escrow"; +import { SETTLER_COMPACT_ABI } from "$lib/abi/settlercompact"; -export type CreateIntentOptions = { - exclusiveFor: string; +type Lock = { + lockTag: `0x${string}`; + token: `0x${string}`; + amount: bigint; +}; + +export type EscrowLock = { + type: "escrow"; +}; + +export type CompactLock = { + type: "compact"; + resetPeriod: ResetPeriod; allocatorId: string; +}; + +export type CreateIntentOptionsEscrow = { + exclusiveFor: string; + inputTokens: TokenContext[]; + outputTokens: TokenContext[]; + verifier: Verifier; + account: () => `0x${string}`; + lock: EscrowLock; +}; + +export type CreateIntentOptionsCompact = { + exclusiveFor: string; inputTokens: TokenContext[]; outputTokens: TokenContext[]; verifier: Verifier; account: () => `0x${string}`; - inputSettler: typeof INPUT_SETTLER_COMPACT_LIFI | typeof INPUT_SETTLER_ESCROW_LIFI; + lock: CompactLock; }; +export type CreateIntentOptions = CreateIntentOptionsEscrow | CreateIntentOptionsCompact; + function findChain(chainId: bigint) { for (const [name, data] of Object.entries(chainMap)) { if (BigInt(data.id) === chainId) { @@ -38,98 +84,231 @@ function findChain(chainId: bigint) { return undefined; } +function selectAllBut(arr: T[], index: number): T[] { + return [...arr.slice(0, index), ...arr.slice(index + 1, arr.length)]; +} + +function encodeOutputs(outputs: MandateOutput[]) { + return encodeAbiParameters( + parseAbiParameters( + "(bytes32 oracle, bytes32 settler, uint256 chainId, bytes32 token, uint256 amount, bytes32 recipient, bytes call, bytes context)[]" + ), + [outputs] + ); +} + +const ONE_MINUTE = 60; +const ONE_HOUR = 60 * ONE_MINUTE; +const ONE_DAY = 24 * ONE_HOUR; + /** * @notice Class representing a Li.Fi Intent. Contains intent abstractions and helpers. */ export class Intent { - private order: StandardOrder; + private lock: EscrowLock | CompactLock; - constructor(opts: CreateIntentOptions) { - const { order } = Intent.create(opts); - this.order = order; + // User facing order options + private user: () => `0x${string}`; + private inputs: TokenContext[]; + private outputs: TokenContext[]; + private verifier: Verifier; + + private exclusiveFor: `0x${string}`; + + private _nonce?: bigint; + + private expiry = ONE_DAY; + private fillDeadline = 2 * ONE_HOUR; + + constructor(opts: CreateIntentOptionsEscrow | CreateIntentOptionsCompact) { + this.lock = opts.lock; + + this.user = opts.account; + this.inputs = opts.inputTokens; + this.outputs = opts.outputTokens; + this.verifier = opts.verifier; + + this.exclusiveFor = opts.exclusiveFor as `0x${string}`; } - static create(opts: CreateIntentOptions) { - const { - exclusiveFor, - allocatorId, - inputTokens, - outputTokens, - verifier, - account, - inputSettler - } = opts; + numInputChains() { + const tokenChains = this.inputs.map(({ token }) => token.chain); + return [...new Set(tokenChains)].length; + } + + isMultichain() { + return this.numInputChains() > 1; + } + + nonce() { + if (this._nonce) return this._nonce; + this._nonce = BigInt(Math.floor(Math.random() * 2 ** 32)); + return this._nonce; + } + inputSettler(multichain: boolean) { + if (this.lock.type === "compact" && multichain === false) return INPUT_SETTLER_COMPACT_LIFI; + if (this.lock.type === "escrow" && multichain === false) return INPUT_SETTLER_ESCROW_LIFI; + if (this.lock.type === "escrow" && multichain === true) + return MULTICHAIN_INPUT_SETTLER_ESCROW_LIFI; + + throw new Error(`Not supported ${multichain}, ${this.lock}`); + } + + encodeOutputs(currentTime: number) { // Check if exclusiveFor has right formatting: - if (exclusiveFor) { + if (this.exclusiveFor) { // Length should be 42. - const formattedCorrectly = exclusiveFor.length === 42 && exclusiveFor.slice(0, 2) === "0x"; + const formattedCorrectly = + this.exclusiveFor.length === 42 && this.exclusiveFor.slice(0, 2) === "0x"; if (!formattedCorrectly) - throw new Error(`ExclusiveFor not formatted correctly ${exclusiveFor}`); + throw new Error(`ExclusiveFor not formatted correctly ${this.exclusiveFor}`); } - const inputChain = inputTokens[0].token.chain; - const inputs: [bigint, bigint][] = []; - for (const { token, amount } of inputTokens) { - // If Compact input, then generate the tokenId otherwise cast into uint256. - const inputTokenId = - inputSettler == INPUT_SETTLER_COMPACT_LIFI - ? toId(true, ResetPeriod.OneDay, allocatorId, token.address) - : BigInt(token.address); - inputs.push([inputTokenId, amount]); - } - - const outputSettler = COIN_FILLER; - const inputOracle = getOracle(verifier, inputChain)!; - // Get the current epoch timestamp: - const currentTime = Math.floor(Date.now() / 1000); + currentTime; const ONE_MINUTE = 60; let context: `0x${string}` = "0x"; - if (exclusiveFor) { - const paddedExclusiveFor: `0x${string}` = `0x${exclusiveFor.replace("0x", "").padStart(64, "0")}`; + if (this.exclusiveFor) { + const paddedExclusiveFor: `0x${string}` = `0x${this.exclusiveFor.replace("0x", "").padStart(64, "0")}`; context = encodePacked( ["bytes1", "bytes32", "uint32"], ["0xe0", paddedExclusiveFor, currentTime + ONE_MINUTE] ); } - // Make Outputs - const outputs: MandateOutput[] = []; - for (const { token, amount } of outputTokens) { - const outputOracle = getOracle(verifier, token.chain)!; + const outputSettler = COIN_FILLER; - const output: MandateOutput = { - oracle: addressToBytes32(outputOracle), + return this.outputs.map(({ token, amount }) => { + return { + oracle: addressToBytes32(getOracle(this.verifier, token.chain)!), settler: addressToBytes32(outputSettler), chainId: BigInt(chainMap[token.chain].id), token: addressToBytes32(token.address), amount: amount, - recipient: addressToBytes32(account()), + recipient: addressToBytes32(this.user()), call: "0x", context }; - outputs.push(output); - } + }) as MandateOutput[]; + } + + singlechain() { + if (this.isMultichain()) + throw new Error(`Not supported as single chain with ${this.numInputChains()} chains`); + + const inputChain = this.inputs[0].token.chain; + + const inputs: [bigint, bigint][] = this.inputs.map(({ token, amount }) => [ + this.lock.type === "compact" + ? toId(true, this.lock.resetPeriod, this.lock.allocatorId, token.address) + : BigInt(token.address), + amount + ]); + + const currentTime = Math.floor(Date.now() / 1000); + + const inputOracle = getOracle(this.verifier, inputChain)!; - // Make order const order: StandardOrder = { - user: account(), - nonce: BigInt(Math.floor(Math.random() * 2 ** 32)), // Random nonce + user: this.user(), + nonce: this.nonce(), originChainId: BigInt(chainMap[inputChain].id), - fillDeadline: currentTime + ONE_MINUTE * 120, - expires: currentTime + ONE_MINUTE * 120, + fillDeadline: currentTime + this.fillDeadline, + expires: currentTime + this.expiry, inputOracle: inputOracle, inputs: inputs, - outputs: outputs + outputs: this.encodeOutputs(currentTime) + }; + + return new StandardOrderIntent(this.inputSettler(false), order); + } + + multichain() { + const currentTime = Math.floor(Date.now() / 1000); + + // TODO: Fix before release. The input oracle is not the same on every chain. + const inputOracle = getOracle(this.verifier, this.inputs[0].token.chain)!; + + // Get all unique chains and then get all inputs for each chain. + const inputs: { chainId: bigint; inputs: [bigint, bigint][] }[] = [ + ...new Set(this.inputs.map(({ token }) => token.chain)) + ].map((chain) => { + const chainInputs = this.inputs.filter(({ token }) => token.chain === chain); + + return { + chainId: BigInt(chainMap[chain].id), + inputs: chainInputs.map(({ token, amount }) => [BigInt(token.address), amount]) + }; + }); + + const order: MultichainOrder = { + user: this.user(), + nonce: this.nonce(), + fillDeadline: currentTime + this.fillDeadline, + expires: currentTime + this.expiry, + inputOracle: inputOracle, + outputs: this.encodeOutputs(currentTime), + inputs: inputs }; - return { order }; + return new MultichainOrderIntent(this.inputSettler(true), order, this.lock); + } + + order() { + if (this.isMultichain()) return this.multichain(); + return this.singlechain(); + } +} + +/// @notice Helper function that allows you to provide an order and it will correctly generate the appropiate order. +export function orderToIntent(options: { + inputSettler: `0x${string}`; + order: StandardOrder; + lock?: { type: string }; +}): StandardOrderIntent; +export function orderToIntent(options: { + inputSettler: `0x${string}`; + order: MultichainOrder; + lock?: { type: string }; +}): MultichainOrderIntent; +export function orderToIntent(options: { + inputSettler: `0x${string}`; + order: StandardOrder | MultichainOrder; + lock?: { type: string }; +}): StandardOrderIntent | MultichainOrderIntent; +export function orderToIntent(options: { + inputSettler: `0x${string}`; + order: StandardOrder | MultichainOrder; + lock?: { type: string }; +}): StandardOrderIntent | MultichainOrderIntent { + const { inputSettler, order, lock } = options; + // Use presence of originChainId to discriminate StandardOrder vs MultichainOrder + if ("originChainId" in order) { + return new StandardOrderIntent(inputSettler, order as StandardOrder); + } + return new MultichainOrderIntent(inputSettler, order as MultichainOrder, lock); +} + +export class StandardOrderIntent { + inputSettler: `0x${string}`; + order: StandardOrder; + + constructor(inputSetter: `0x${string}`, order: StandardOrder) { + this.inputSettler = inputSetter; + this.order = order; } // -- Order Representations -- // + /** + * @notice Returns for logging + */ + asOrder(): StandardOrder { + return this.order; + } + /** * @notice Returns the order as a StandardOrder. * @returns Order as StandardOrder @@ -172,6 +351,39 @@ export class Intent { }; } + inputChains(): bigint[] { + return [this.order.originChainId]; + } + + orderId(): `0x${string}` { + return keccak256( + encodePacked( + [ + "uint256", + "address", + "address", + "uint256", + "uint32", + "uint32", + "address", + "bytes32", + "bytes" + ], + [ + this.order.originChainId, + this.inputSettler, + this.order.user, + this.order.nonce, + this.order.expires, + this.order.fillDeadline, + this.order.inputOracle, + keccak256(encodePacked(["uint256[2][]"], [this.order.inputs])), + encodeOutputs(this.order.outputs) + ] + ) + ); + } + // -- Escrow Helpers -- // /** @@ -180,18 +392,20 @@ export class Intent { * @param walletClient Wallet client for sending the call to. * @returns transactionHash for the on-chain call. */ - openEscrow(account: `0x${string}`, walletClient: WC): Promise<`0x${string}`> { + openEscrow(account: `0x${string}`, walletClient: WC): [Promise<`0x${string}`>] { const chain = findChain(this.order.originChainId); if (!chain) throw new Error("Chain not found for chainId " + this.order.originChainId.toString()); - return walletClient.writeContract({ - chain, - account, - address: INPUT_SETTLER_ESCROW_LIFI, - abi: SETTLER_ESCROW_ABI, - functionName: "open", - args: [this.order] - }); + return [ + walletClient.writeContract({ + chain, + account, + address: INPUT_SETTLER_ESCROW_LIFI, + abi: SETTLER_ESCROW_ABI, + functionName: "open", + args: [this.order] + }) + ]; } // -- Compact Helpers -- // @@ -234,4 +448,278 @@ export class Intent { args: [this.order.inputs, [[this.compactClaimHash(), compact_type_hash]]] }); } + + async finalise(options: { + sourceChain: chain; + account: `0x${string}`; + walletClient: WC; + solveParam: { timestamp: number; solver: `0x${string}` }; + signatures: { + sponsorSignature: Signature | NoSignature; + allocatorSignature: Signature | NoSignature; + }; + }) { + const { sourceChain, account, walletClient, solveParam, signatures } = options; + const actionChain = chainMap[sourceChain]; + if (actionChain.id !== Number(this.order.originChainId)) + throw new Error( + `Origin chain id and action ID does not match: ${this.order.originChainId}, ${actionChain.id}` + ); + + if (this.inputSettler.toLowerCase() === INPUT_SETTLER_ESCROW_LIFI.toLowerCase()) { + return await walletClient.writeContract({ + chain: actionChain, + account: account, + address: this.inputSettler, + abi: SETTLER_ESCROW_ABI, + functionName: "finalise", + args: [this.order, [solveParam], addressToBytes32(account), "0x"] + }); + } else if (this.inputSettler.toLowerCase() === INPUT_SETTLER_COMPACT_LIFI.toLowerCase()) { + // Check whether or not we have a signature. + const { sponsorSignature, allocatorSignature } = signatures; + console.log({ + sponsorSignature, + allocatorSignature + }); + const combinedSignatures = encodeAbiParameters(parseAbiParameters(["bytes", "bytes"]), [ + sponsorSignature.payload ?? "0x", + allocatorSignature.payload + ]); + return await walletClient.writeContract({ + chain: actionChain, + account: account, + address: this.inputSettler, + abi: SETTLER_COMPACT_ABI, + functionName: "finalise", + args: [this.order, combinedSignatures, [solveParam], addressToBytes32(account), "0x"] + }); + } else { + throw new Error(`Could not detect settler type ${this.inputSettler}`); + } + } +} + +export class MultichainOrderIntent { + lock?: { type: string } | EscrowLock | CompactLock; + + // Notice that this has to be the same address on every chain. + inputSettler: `0x${string}`; + order: MultichainOrder; + + constructor(inputSetter: `0x${string}`, order: MultichainOrder, lock?: { type: string }) { + this.inputSettler = inputSetter; + this.order = order; + + this.lock = lock; + } + + /** + * @notice Returns for logging + */ + asOrder(): MultichainOrder { + return this.order; + } + + inputChains(): bigint[] { + return [...new Set(this.order.inputs.map((i) => i.chainId))]; + } + + orderId(): `0x${string}` { + // We need a random order components. + const components = this.asComponents(); + const computedOrderIds = components.map((c) => + MultichainOrderIntent.escrowOrderId(this.inputSettler, c.orderComponent) + ); + + const orderId = computedOrderIds[0]; + computedOrderIds.map((v) => { + if (v === orderId) throw new Error(`Order ids are not equal ${computedOrderIds}`); + }); + return orderId; + } + + static escrowOrderId(inputSettler: `0x${string}`, orderComponent: MultichainOrderComponent) { + return keccak256( + encodePacked( + ["address", "address", "uint256", "uint32", "uint32", "address", "bytes32", "bytes"], + [ + inputSettler, + orderComponent.user, + orderComponent.nonce, + orderComponent.expires, + orderComponent.fillDeadline, + orderComponent.inputOracle, + MultichainOrderIntent.constructInputHash( + orderComponent.chainIdField, + orderComponent.chainIndex, + orderComponent.inputs, + orderComponent.additionalChains + ), + encodeOutputs(orderComponent.outputs) + ] + ) + ); + } + + static hashInputs(chainId: bigint, inputs: [bigint, bigint][]) { + return keccak256(encodePacked(["uint256", "uint256[2][]"], [chainId, inputs])); + } + + static constructInputHash( + inputsChainId: bigint, + chainIndex: bigint, + inputs: [bigint, bigint][], + additionalChains: `0x${string}`[] + ) { + const inputHash = MultichainOrderIntent.hashInputs(inputsChainId, inputs); + const numSegments = additionalChains.length + 1; + if (numSegments <= chainIndex) + throw new Error(`ChainIndexOutOfRange(${chainIndex},${numSegments})`); + const claimStructure: `0x${string}`[] = []; + for (let i = 0; i < numSegments; ++i) { + const additionalChainsIndex = i > chainIndex ? i - 1 : i; + const inputHashElement = + chainIndex == BigInt(i) ? inputHash : additionalChains[additionalChainsIndex]; + claimStructure[i] = inputHashElement; + } + return keccak256(encodePacked(["bytes32[]"], [claimStructure])); + } + + static inputsToLocks(inputs: [bigint, bigint][]): Lock[] { + return inputs.map((input) => { + const bytes32 = toHex(input[0]).replace("0x", ""); + return { + lockTag: `0x${bytes32.slice(0, 12 * 2)}`, + token: `0x${bytes32.slice(12 * 2, 32 * 2)}`, + amount: input[1] + }; + }); + } + + secondariesEcsrow(): { chainIdField: bigint; additionalChains: `0x${string}`[] }[] { + const inputsHash: `0x${string}`[] = this.order.inputs.map((input) => + keccak256(encodePacked(["uint256", "uint256[2][]"], [input.chainId, input.inputs])) + ); + return this.order.inputs.map((v, i) => { + return { + chainIdField: v.chainId, + additionalChains: selectAllBut(inputsHash, i) + }; + }); + } + + secondariesCompact(): { chainIdField: bigint; additionalChains: `0x${string}`[] }[] { + const { fillDeadline, inputOracle, outputs, inputs } = this.order; + const mandate: CompactMandate = { + fillDeadline, + inputOracle, + outputs + }; + const elements = inputs.map((inputs) => { + const element: { + arbiter: `0x${string}`; + chainId: bigint; + commitments: Lock[]; + mandate: CompactMandate; + } = { + arbiter: this.inputSettler, + chainId: inputs.chainId, + commitments: MultichainOrderIntent.inputsToLocks(inputs.inputs), + mandate + }; + return hashTypedData({ + types: compactTypes, + primaryType: "Element", + message: element + }); + }); + return inputs.map((_, i) => { + return { + chainIdField: inputs[0].chainId, + additionalChains: selectAllBut(elements, i) + }; + }); + } + + asComponents(): { chainId: bigint; orderComponent: MultichainOrderComponent }[] { + const { inputs, user, nonce, expires, fillDeadline, inputOracle, outputs } = this.order; + if (!this.lock) throw new Error(`No lock provided, cannot compute secondaries.`); + const secondaries = + this.lock.type == "escrow" ? this.secondariesEcsrow() : this.secondariesCompact(); + const components: { chainId: bigint; orderComponent: MultichainOrderComponent }[] = []; + for (let i = 0; i < inputs.length; ++i) { + const { chainIdField, additionalChains } = secondaries[i]; + + const orderComponent: MultichainOrderComponent = { + user: user, + nonce: nonce, + chainIdField: chainIdField, + chainIndex: BigInt(i), + expires: expires, + fillDeadline: fillDeadline, + inputOracle: inputOracle, + inputs: inputs[i].inputs, + outputs: outputs, + additionalChains: additionalChains + }; + components.push({ chainId: inputs[i].chainId, orderComponent }); + } + return components; + } + + // This code is depreciated and needs to be updated. + async openEscrow(account: `0x${string}`, walletClient: WC) { + const components = this.asComponents(); + const promises: Promise<`0x${string}`>[] = []; + for (const { chainId, orderComponent } of components) { + const chain = findChain(chainId); + promises.push( + walletClient.writeContract({ + chain, + account, + address: this.inputSettler, + abi: MULTICHAIN_SETTLER_ESCROW_ABI, + functionName: "open", + args: [orderComponent] + }) + ); + } + return promises; + } + + async finalise(options: { + sourceChain: chain; + account: `0x${string}`; + walletClient: WC; + solveParam: { timestamp: number; solver: `0x${string}` }; + signatures: { + sponsorSignature: Signature | NoSignature; + allocatorSignature: Signature | NoSignature; + }; + }) { + const { sourceChain, account, walletClient, solveParam, signatures } = options; + const actionChain = chainMap[sourceChain]; + if (actionChain.id in this.inputChains().map((v) => Number(v))) + throw new Error( + `Input chains and action ID does not match: ${this.inputChains()}, ${actionChain.id}` + ); + // Get all components for our chain. + const components = this.asComponents().filter((c) => c.chainId === BigInt(actionChain.id)); + + for (const { orderComponent } of components) { + if (this.inputSettler.toLowerCase() === MULTICHAIN_INPUT_SETTLER_ESCROW_LIFI.toLowerCase()) { + return await walletClient.writeContract({ + chain: actionChain, + account: account, + address: this.inputSettler, + abi: MULTICHAIN_SETTLER_ESCROW_ABI, + functionName: "finalise", + args: [orderComponent, [solveParam], addressToBytes32(account), "0x"] + }); + } else { + throw new Error(`Could not detect settler type ${this.inputSettler}`); + } + } + } } diff --git a/src/lib/libraries/intentFactory.ts b/src/lib/libraries/intentFactory.ts index ca74f94..b6a4c28 100644 --- a/src/lib/libraries/intentFactory.ts +++ b/src/lib/libraries/intentFactory.ts @@ -8,7 +8,13 @@ import { type WC } from "$lib/config"; import { maxUint256 } from "viem"; -import type { NoSignature, OrderContainer, Signature, StandardOrder } from "../../types"; +import type { + MultichainOrder, + NoSignature, + OrderContainer, + Signature, + StandardOrder +} from "../../types"; import { ERC20_ABI } from "$lib/abi/erc20"; import { Intent } from "$lib/libraries/intent"; import { OrderServer } from "$lib/libraries/orderServer"; @@ -48,8 +54,8 @@ export class IntentFactory { } private saveOrder(options: { - order: StandardOrder; - inputSettler: typeof INPUT_SETTLER_COMPACT_LIFI | typeof INPUT_SETTLER_ESCROW_LIFI; + order: StandardOrder | MultichainOrder; + inputSettler: `0x${string}`; sponsorSignature?: Signature | NoSignature; allocatorSignature?: Signature | NoSignature; }) { @@ -74,7 +80,7 @@ export class IntentFactory { const { account, inputTokens } = opts; const inputChain = inputTokens[0].token.chain; if (this.preHook) await this.preHook(inputChain); - const intent = new Intent(opts); + const intent = new Intent(opts).singlechain(); const sponsorSignature = await intent.signCompact(account(), this.walletClient); @@ -101,7 +107,7 @@ export class IntentFactory { return async () => { const { inputTokens, account } = opts; const publicClients = clients; - const intent = new Intent(opts); + const intent = new Intent(opts).singlechain(); if (this.preHook) await this.preHook(inputTokens[0].token.chain); @@ -136,26 +142,28 @@ export class IntentFactory { openIntent(opts: CreateIntentOptions) { return async () => { const { inputTokens, account } = opts; - const intent = new Intent(opts); + const intent = new Intent(opts).order(); const inputChain = inputTokens[0].token.chain; if (this.preHook) await this.preHook(inputChain); // Execute the open. - const transactionHash = await intent.openEscrow(account(), this.walletClient); + const transactionHashes = await intent.openEscrow(account(), this.walletClient); - await clients[inputChain].waitForTransactionReceipt({ - hash: transactionHash - }); + for (const hash of transactionHashes) { + await clients[inputChain].waitForTransactionReceipt({ + hash: await hash + }); + } if (this.postHook) await this.postHook(); this.saveOrder({ - order: intent.asStandardOrder(), + order: intent.asOrder(), inputSettler: INPUT_SETTLER_ESCROW_LIFI }); - return transactionHash; + return transactionHashes; }; } } diff --git a/src/lib/libraries/solver.ts b/src/lib/libraries/solver.ts index 05047bd..af269cf 100644 --- a/src/lib/libraries/solver.ts +++ b/src/lib/libraries/solver.ts @@ -17,8 +17,8 @@ import { POLYMER_ORACLE_ABI } from "$lib/abi/polymeroracle"; import { SETTLER_COMPACT_ABI } from "$lib/abi/settlercompact"; import { COIN_FILLER_ABI } from "$lib/abi/outputsettler"; import { ERC20_ABI } from "$lib/abi/erc20"; -import { getOrderId } from "$lib/utils/orderLib"; import { SETTLER_ESCROW_ABI } from "$lib/abi/escrow"; +import { orderToIntent } from "./intent"; /** * @notice Class for solving intents. Functions called by solvers. @@ -43,7 +43,8 @@ export class Solver { outputs } = args; const publicClients = clients; - const orderId = getOrderId({ order, inputSettler }); + // TODO: MULTICHAIN COMPACT fix escrow typing + const orderId = orderToIntent({ order, inputSettler, lock: { type: "escrow" } }).orderId(); //Check that only 1 output exists. if (outputs.length !== 1) { throw new Error("Order must have exactly one output"); @@ -106,7 +107,7 @@ export class Solver { static validate( walletClient: WC, - args: { orderContainer: OrderContainer; fillTransactionHash: string }, + args: { orderContainer: OrderContainer; fillTransactionHash: string; sourceChain: chain }, opts: { preHook?: (chain: chain) => Promise; postHook?: () => Promise; @@ -116,10 +117,10 @@ export class Solver { return async () => { const { preHook, postHook, account } = opts; const { - orderContainer: { order }, - fillTransactionHash + orderContainer: { order, inputSettler }, + fillTransactionHash, + sourceChain } = args; - const sourceChain = getChainName(order.originChainId); const outputChain = getChainName(order.outputs[0].chainId); if (order.outputs.length !== 1) { throw new Error("Order must have exactly one output"); @@ -207,6 +208,7 @@ export class Solver { args: { orderContainer: OrderContainer; fillTransactionHash: string; + sourceChain: chain; }, opts: { preHook?: (chain: chain) => Promise; @@ -216,8 +218,14 @@ export class Solver { ) { return async () => { const { preHook, postHook, account } = opts; - const { orderContainer, fillTransactionHash } = args; - const { order } = orderContainer; + const { orderContainer, fillTransactionHash, sourceChain } = args; + const { order, inputSettler } = orderContainer; + const intent = orderToIntent({ + inputSettler, + order, + lock: { type: inputSettler === INPUT_SETTLER_COMPACT_LIFI ? "compact" : "escrow" } + }); + const outputChain = getChainName(order.outputs[0].chainId); if (order.outputs.length !== 1) { throw new Error("Order must have exactly one output"); @@ -231,50 +239,20 @@ export class Solver { }); const fillTimestamp = block.timestamp; - const sourceChain = getChainName(order.originChainId); if (preHook) await preHook(sourceChain); - const inputSettler = orderContainer.inputSettler; - console.log({ orderContainer }); - let transactionHash: `0x${string}`; - const actionChain = chainMap[sourceChain]; - const solveParam = { timestamp: Number(fillTimestamp), solver: addressToBytes32(account()) }; - if (inputSettler.toLowerCase() === INPUT_SETTLER_ESCROW_LIFI.toLowerCase()) { - transactionHash = await walletClient.writeContract({ - chain: actionChain, - account: account(), - address: inputSettler, - abi: SETTLER_ESCROW_ABI, - functionName: "finalise", - args: [order, [solveParam], addressToBytes32(account()), "0x"] - }); - } else if (inputSettler.toLowerCase() === INPUT_SETTLER_COMPACT_LIFI.toLowerCase()) { - // Check whether or not we have a signature. - const { sponsorSignature, allocatorSignature } = orderContainer; - console.log({ - sponsorSignature, - allocatorSignature - }); - const combinedSignatures = encodeAbiParameters(parseAbiParameters(["bytes", "bytes"]), [ - sponsorSignature.payload ?? "0x", - allocatorSignature.payload - ]); - transactionHash = await walletClient.writeContract({ - chain: actionChain, - account: account(), - address: inputSettler, - abi: SETTLER_COMPACT_ABI, - functionName: "finalise", - args: [order, combinedSignatures, [solveParam], addressToBytes32(account()), "0x"] - }); - } else { - throw new Error(`Could not detect settler type ${orderContainer.inputSettler}`); - } + const transactionHash = intent.finalise({ + sourceChain, + account: account(), + walletClient, + solveParam, + signatures: orderContainer + }); const result = await clients[sourceChain].waitForTransactionReceipt({ hash: transactionHash }); diff --git a/src/lib/screens/IssueIntent.svelte b/src/lib/screens/IssueIntent.svelte index bde2cc7..58aafa5 100644 --- a/src/lib/screens/IssueIntent.svelte +++ b/src/lib/screens/IssueIntent.svelte @@ -13,6 +13,8 @@ import store from "$lib/state.svelte"; import InputTokenModal from "../components/InputTokenModal.svelte"; import OutputTokenModal from "$lib/components/OutputTokenModal.svelte"; + import { ResetPeriod } from "$lib/utils/idLib"; + import type { CreateIntentOptions } from "$lib/libraries/intent"; const bigIntSum = (...nums: bigint[]) => nums.reduce((a, b) => a + b, 0n); @@ -33,15 +35,18 @@ const opts = $derived({ exclusiveFor: store.exclusiveFor, - allocatorId: store.allocatorId, inputTokens: store.inputTokens, outputTokens: store.outputTokens, preHook, postHook, verifier: store.verifier, - inputSettler: store.inputSettler, + lock: { + type: store.inputSettler === INPUT_SETTLER_COMPACT_LIFI ? "compact" : "escrow", + allocatorId: store.allocatorId, + resetPeriod: ResetPeriod.OneDay + }, account - }); + } as CreateIntentOptions); const postHookScroll = async () => { await postHook(); @@ -107,10 +112,6 @@ } }); - const allSameChains = $derived( - store.inputTokens.every((v) => store.inputTokens[0].token.chain === v.token.chain) - ); - const abstractInputs = $derived.by(() => { const inputs: { name: string; @@ -143,6 +144,12 @@ } return inputs; }); + + const numInputChains = $derived.by(() => { + const tokenChains = store.inputTokens.map(({ token }) => token.chain); + const uniqueChains = [...new Set(tokenChains)]; + return uniqueChains.length; + });
@@ -179,6 +186,9 @@
{/each} + {#if numInputChains > 1} +
Multichain!
+ {/if}
@@ -236,23 +246,7 @@
- {#if !allSameChains} - - {:else if store.inputTokens.length != 1} - - {:else if !allowanceCheck} + {#if !allowanceCheck} {#snippet name()} Set allowance @@ -313,4 +307,10 @@
{/if}
+ {#if numInputChains > 1 && store.inputSettler !== INPUT_SETTLER_COMPACT_LIFI} +

+ You'll need to open the order on {numInputChains} chains. Be prepared and do not interrupt the + process. +

+ {/if}
diff --git a/src/lib/utils/idLib.ts b/src/lib/utils/idLib.ts index 1ea5561..3f1010e 100644 --- a/src/lib/utils/idLib.ts +++ b/src/lib/utils/idLib.ts @@ -21,14 +21,14 @@ export enum ResetPeriod { * - Bits 160-251: allocator ID (92 bits) * - Bits 0-159: token address (20 bytes = 160 bits) * - * @param isMultichain Whether the lock is multichain (maps to scope) + * @param inputChains Whether the lock is multichain (maps to scope) * @param resetPeriod Reset period (0-7) * @param allocatorId Allocator ID as string * @param token Token address as hex string * @returns The derived resource lock ID as a BigInt */ export function toId( - isMultichain: boolean, + inputChains: boolean, resetPeriod: number, allocatorId: string, token: string @@ -40,8 +40,8 @@ export function toId( // Validate token is a valid address and normalize it const normalizedToken = getAddress(token); - // Convert isMultichain to scope (inverse relationship) - const scope = isMultichain ? 0n : 1n; + // Convert inputChains to scope (inverse relationship) + const scope = inputChains ? 0n : 1n; // Convert allocatorId from decimal string to BigInt const allocatorBigInt = BigInt(allocatorId); diff --git a/src/lib/utils/multichainOrder.ts b/src/lib/utils/multichainOrder.ts deleted file mode 100644 index dadc724..0000000 --- a/src/lib/utils/multichainOrder.ts +++ /dev/null @@ -1,104 +0,0 @@ -import { encodePacked, hashTypedData, keccak256, toHex } from "viem"; -import type { CompactMandate, MultichainOrder, MultichainOrderComponent } from "../../types"; -import { INPUT_SETTLER_ESCROW_LIFI } from "$lib/config"; -import { compactTypes } from "./typedMessage"; - -function selectAllBut(arr: T[], index: number): T[] { - return [...arr.slice(0, index), ...arr.slice(index + 1, arr.length)]; -} - -function secondariesEcsrow( - order: MultichainOrder -): { chainIdField: bigint; additionalChains: `0x${string}`[] }[] { - const inputsHash: `0x${string}`[] = order.inputs.map((input) => - keccak256(encodePacked(["uint256", "uint256[2][]"], [input.chainId, input.inputs])) - ); - return order.inputs.map((v, i) => { - return { - chainIdField: v.chainId, - additionalChains: selectAllBut(inputsHash, i) - }; - }); -} - -type Lock = { - lockTag: `0x${string}`; - token: `0x${string}`; - amount: bigint; -}; - -function inputsToLocks(inputs: [bigint, bigint][]): Lock[] { - return inputs.map((input) => { - const bytes32 = toHex(input[0]).replace("0x", ""); - return { - lockTag: `0x${bytes32.slice(0, 12 * 2)}`, - token: `0x${bytes32.slice(12 * 2, 32 * 2)}`, - amount: input[1] - }; - }); -} - -function secondariesCompact( - order: MultichainOrder, - inputSettler: `0x${string}` -): { chainIdField: bigint; additionalChains: `0x${string}`[] }[] { - const mandate: CompactMandate = { - fillDeadline: order.fillDeadline, - inputOracle: order.inputOracle, - outputs: order.outputs - }; - const elements = order.inputs.map((inputs) => { - const element: { - arbiter: `0x${string}`; - chainId: bigint; - commitments: Lock[]; - mandate: CompactMandate; - } = { - arbiter: inputSettler, - chainId: inputs.chainId, - commitments: inputsToLocks(inputs.inputs), - mandate - }; - return hashTypedData({ - types: compactTypes, - primaryType: "Element", - message: element - }); - }); - return order.inputs.map((_, i) => { - return { - chainIdField: order.inputs[0].chainId, - additionalChains: selectAllBut(elements, i) - }; - }); -} - -function ComponentizeOrder( - order: MultichainOrder, - inputSettler: `0x${string}` -): MultichainOrderComponent[] { - const inputs = order.inputs; - const secondaries = - inputSettler === INPUT_SETTLER_ESCROW_LIFI - ? secondariesEcsrow(order) - : secondariesCompact(order, inputSettler); - const components: MultichainOrderComponent[] = []; - for (let i = 0; i < inputs.length; ++i) { - const { chainIdField, additionalChains } = secondaries[i]; - - const orderComponent: MultichainOrderComponent = { - user: order.user, - nonce: order.nonce, - chainIdField: chainIdField, - chainIndex: BigInt(i), - expires: order.expires, - fillDeadline: order.fillDeadline, - inputOracle: order.inputOracle, - inputs: order.inputs[i].inputs, - outputs: order.outputs, - additionalChains: additionalChains - }; - components.push(orderComponent); - } - return components; -} diff --git a/src/lib/utils/orderLib.ts b/src/lib/utils/orderLib.ts index ccde96f..4042482 100644 --- a/src/lib/utils/orderLib.ts +++ b/src/lib/utils/orderLib.ts @@ -1,42 +1,7 @@ import { encodeAbiParameters, encodePacked, keccak256, parseAbiParameters } from "viem"; -import type { MandateOutput, StandardOrder } from "../../types"; +import type { MandateOutput, MultichainOrder, StandardOrder } from "../../types"; import { type chain, chainMap, POLYMER_ORACLE, WORMHOLE_ORACLE } from "$lib/config"; -export function getOrderId(orderContainer: { order: StandardOrder; inputSettler: `0x${string}` }) { - const { order, inputSettler } = orderContainer; - return keccak256( - encodePacked( - [ - "uint256", - "address", - "address", - "uint256", - "uint32", - "uint32", - "address", - "bytes32", - "bytes" - ], - [ - order.originChainId, - inputSettler, - order.user, - order.nonce, - order.expires, - order.fillDeadline, - order.inputOracle, - keccak256(encodePacked(["uint256[2][]"], [order.inputs])), - encodeAbiParameters( - parseAbiParameters( - "(bytes32 oracle, bytes32 settler, uint256 chainId, bytes32 token, uint256 amount, bytes32 recipient, bytes call, bytes context)[]" - ), - [order.outputs] - ) - ] - ) - ); -} - export function getOutputHash(output: MandateOutput) { return keccak256( encodePacked( diff --git a/src/routes/+page.svelte b/src/routes/+page.svelte index d90893b..97d6295 100644 --- a/src/routes/+page.svelte +++ b/src/routes/+page.svelte @@ -68,14 +68,14 @@ onDestroy(() => s.close()); } - $effect(() => { - store.mainnet; - initatePage(); - }); - - onMount(() => { - initatePage(); - }); + // $effect(() => { + // store.mainnet; + // initatePage(); + // }); + + // onMount(() => { + // initatePage(); + // }); // --- Wallet --- // diff --git a/src/types/index.ts b/src/types/index.ts index ff58059..7afc6d4 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -85,7 +85,7 @@ export type Signature = { export type OrderContainer = { inputSettler: `0x${string}`; - order: StandardOrder; + order: StandardOrder | MultichainOrder; sponsorSignature: Signature | NoSignature; allocatorSignature: Signature | NoSignature; }; From 96d4ebba05e67a55fdce5ab12330aaa097a5e8e4 Mon Sep 17 00:00:00 2001 From: Alexander Date: Fri, 7 Nov 2025 18:46:25 +0100 Subject: [PATCH 09/17] Flow until proving phase --- src/lib/abi/multichain_escrow.ts | 1094 +++++++++++++++++-------- src/lib/config.ts | 8 +- src/lib/libraries/intent.ts | 38 +- src/lib/libraries/intentFactory.ts | 17 +- src/lib/libraries/solver.ts | 25 +- src/lib/screens/FillIntent.svelte | 7 +- src/lib/screens/IntentList.svelte | 80 +- src/lib/screens/IssueIntent.svelte | 9 +- src/lib/screens/ReceiveMessage.svelte | 6 +- src/lib/state.svelte.ts | 22 +- src/routes/+page.svelte | 6 +- 11 files changed, 884 insertions(+), 428 deletions(-) diff --git a/src/lib/abi/multichain_escrow.ts b/src/lib/abi/multichain_escrow.ts index 0382ffb..892b962 100644 --- a/src/lib/abi/multichain_escrow.ts +++ b/src/lib/abi/multichain_escrow.ts @@ -1,293 +1,681 @@ export const MULTICHAIN_SETTLER_ESCROW_ABI = [ + { + type: "constructor", + inputs: [], + stateMutability: "nonpayable" + }, { type: "function", - name: "efficientRequireProven", - inputs: [ + name: "DOMAIN_SEPARATOR", + inputs: [], + outputs: [ { - name: "proofSeries", - type: "bytes", - internalType: "bytes" + name: "", + type: "bytes32", + internalType: "bytes32" } ], - outputs: [], stateMutability: "view" }, { type: "function", - name: "fill", - inputs: [ + name: "eip712Domain", + inputs: [], + outputs: [ { - name: "orderId", + name: "fields", + type: "bytes1", + internalType: "bytes1" + }, + { + name: "name", + type: "string", + internalType: "string" + }, + { + name: "version", + type: "string", + internalType: "string" + }, + { + name: "chainId", + type: "uint256", + internalType: "uint256" + }, + { + name: "verifyingContract", + type: "address", + internalType: "address" + }, + { + name: "salt", type: "bytes32", internalType: "bytes32" }, { - name: "output", + name: "extensions", + type: "uint256[]", + internalType: "uint256[]" + } + ], + stateMutability: "view" + }, + { + type: "function", + name: "finalise", + inputs: [ + { + name: "order", type: "tuple", - internalType: "struct MandateOutput", + internalType: "struct MultichainOrderComponent", components: [ { - name: "oracle", - type: "bytes32", - internalType: "bytes32" + name: "user", + type: "address", + internalType: "address" }, { - name: "settler", - type: "bytes32", - internalType: "bytes32" - }, - { - name: "chainId", + name: "nonce", type: "uint256", internalType: "uint256" }, { - name: "token", - type: "bytes32", - internalType: "bytes32" + name: "chainIdField", + type: "uint256", + internalType: "uint256" }, { - name: "amount", + name: "chainIndex", type: "uint256", internalType: "uint256" }, { - name: "recipient", - type: "bytes32", - internalType: "bytes32" + name: "expires", + type: "uint32", + internalType: "uint32" + }, + { + name: "fillDeadline", + type: "uint32", + internalType: "uint32" + }, + { + name: "inputOracle", + type: "address", + internalType: "address" }, { - name: "call", - type: "bytes", - internalType: "bytes" + name: "inputs", + type: "uint256[2][]", + internalType: "uint256[2][]" }, { - name: "context", - type: "bytes", - internalType: "bytes" + name: "outputs", + type: "tuple[]", + internalType: "struct MandateOutput[]", + components: [ + { + name: "oracle", + type: "bytes32", + internalType: "bytes32" + }, + { + name: "settler", + type: "bytes32", + internalType: "bytes32" + }, + { + name: "chainId", + type: "uint256", + internalType: "uint256" + }, + { + name: "token", + type: "bytes32", + internalType: "bytes32" + }, + { + name: "amount", + type: "uint256", + internalType: "uint256" + }, + { + name: "recipient", + type: "bytes32", + internalType: "bytes32" + }, + { + name: "callbackData", + type: "bytes", + internalType: "bytes" + }, + { + name: "context", + type: "bytes", + internalType: "bytes" + } + ] + }, + { + name: "additionalChains", + type: "bytes32[]", + internalType: "bytes32[]" } ] }, { - name: "fillDeadline", - type: "uint48", - internalType: "uint48" + name: "solveParams", + type: "tuple[]", + internalType: "struct InputSettlerBase.SolveParams[]", + components: [ + { + name: "timestamp", + type: "uint32", + internalType: "uint32" + }, + { + name: "solver", + type: "bytes32", + internalType: "bytes32" + } + ] }, { - name: "fillerData", - type: "bytes", - internalType: "bytes" - } - ], - outputs: [ - { - name: "fillRecordHash", + name: "destination", type: "bytes32", internalType: "bytes32" + }, + { + name: "call", + type: "bytes", + internalType: "bytes" } ], - stateMutability: "payable" + outputs: [], + stateMutability: "nonpayable" }, { type: "function", - name: "fillOrderOutputs", + name: "finaliseWithSignature", inputs: [ { - name: "orderId", - type: "bytes32", - internalType: "bytes32" - }, - { - name: "outputs", - type: "tuple[]", - internalType: "struct MandateOutput[]", + name: "order", + type: "tuple", + internalType: "struct MultichainOrderComponent", components: [ { - name: "oracle", - type: "bytes32", - internalType: "bytes32" - }, - { - name: "settler", - type: "bytes32", - internalType: "bytes32" + name: "user", + type: "address", + internalType: "address" }, { - name: "chainId", + name: "nonce", type: "uint256", internalType: "uint256" }, { - name: "token", - type: "bytes32", - internalType: "bytes32" + name: "chainIdField", + type: "uint256", + internalType: "uint256" }, { - name: "amount", + name: "chainIndex", type: "uint256", internalType: "uint256" }, { - name: "recipient", - type: "bytes32", - internalType: "bytes32" + name: "expires", + type: "uint32", + internalType: "uint32" }, { - name: "call", - type: "bytes", - internalType: "bytes" + name: "fillDeadline", + type: "uint32", + internalType: "uint32" }, { - name: "context", - type: "bytes", - internalType: "bytes" + name: "inputOracle", + type: "address", + internalType: "address" + }, + { + name: "inputs", + type: "uint256[2][]", + internalType: "uint256[2][]" + }, + { + name: "outputs", + type: "tuple[]", + internalType: "struct MandateOutput[]", + components: [ + { + name: "oracle", + type: "bytes32", + internalType: "bytes32" + }, + { + name: "settler", + type: "bytes32", + internalType: "bytes32" + }, + { + name: "chainId", + type: "uint256", + internalType: "uint256" + }, + { + name: "token", + type: "bytes32", + internalType: "bytes32" + }, + { + name: "amount", + type: "uint256", + internalType: "uint256" + }, + { + name: "recipient", + type: "bytes32", + internalType: "bytes32" + }, + { + name: "callbackData", + type: "bytes", + internalType: "bytes" + }, + { + name: "context", + type: "bytes", + internalType: "bytes" + } + ] + }, + { + name: "additionalChains", + type: "bytes32[]", + internalType: "bytes32[]" } ] }, { - name: "fillDeadline", - type: "uint48", - internalType: "uint48" + name: "solveParams", + type: "tuple[]", + internalType: "struct InputSettlerBase.SolveParams[]", + components: [ + { + name: "timestamp", + type: "uint32", + internalType: "uint32" + }, + { + name: "solver", + type: "bytes32", + internalType: "bytes32" + } + ] + }, + { + name: "destination", + type: "bytes32", + internalType: "bytes32" + }, + { + name: "call", + type: "bytes", + internalType: "bytes" }, { - name: "fillerData", + name: "orderOwnerSignature", type: "bytes", internalType: "bytes" } ], outputs: [], - stateMutability: "payable" + stateMutability: "nonpayable" }, { type: "function", - name: "getFillRecord", + name: "open", inputs: [ { - name: "orderId", - type: "bytes32", - internalType: "bytes32" - }, - { - name: "output", + name: "order", type: "tuple", - internalType: "struct MandateOutput", + internalType: "struct MultichainOrderComponent", components: [ { - name: "oracle", - type: "bytes32", - internalType: "bytes32" + name: "user", + type: "address", + internalType: "address" }, { - name: "settler", - type: "bytes32", - internalType: "bytes32" - }, - { - name: "chainId", + name: "nonce", type: "uint256", internalType: "uint256" }, { - name: "token", - type: "bytes32", - internalType: "bytes32" + name: "chainIdField", + type: "uint256", + internalType: "uint256" }, { - name: "amount", + name: "chainIndex", type: "uint256", internalType: "uint256" }, { - name: "recipient", - type: "bytes32", - internalType: "bytes32" + name: "expires", + type: "uint32", + internalType: "uint32" + }, + { + name: "fillDeadline", + type: "uint32", + internalType: "uint32" }, { - name: "call", - type: "bytes", - internalType: "bytes" + name: "inputOracle", + type: "address", + internalType: "address" }, { - name: "context", - type: "bytes", - internalType: "bytes" + name: "inputs", + type: "uint256[2][]", + internalType: "uint256[2][]" + }, + { + name: "outputs", + type: "tuple[]", + internalType: "struct MandateOutput[]", + components: [ + { + name: "oracle", + type: "bytes32", + internalType: "bytes32" + }, + { + name: "settler", + type: "bytes32", + internalType: "bytes32" + }, + { + name: "chainId", + type: "uint256", + internalType: "uint256" + }, + { + name: "token", + type: "bytes32", + internalType: "bytes32" + }, + { + name: "amount", + type: "uint256", + internalType: "uint256" + }, + { + name: "recipient", + type: "bytes32", + internalType: "bytes32" + }, + { + name: "callbackData", + type: "bytes", + internalType: "bytes" + }, + { + name: "context", + type: "bytes", + internalType: "bytes" + } + ] + }, + { + name: "additionalChains", + type: "bytes32[]", + internalType: "bytes32[]" } ] } ], - outputs: [ - { - name: "payloadHash", - type: "bytes32", - internalType: "bytes32" - } - ], - stateMutability: "view" + outputs: [], + stateMutability: "nonpayable" }, { type: "function", - name: "getFillRecord", + name: "openFor", inputs: [ { - name: "orderId", - type: "bytes32", - internalType: "bytes32" + name: "order", + type: "tuple", + internalType: "struct MultichainOrderComponent", + components: [ + { + name: "user", + type: "address", + internalType: "address" + }, + { + name: "nonce", + type: "uint256", + internalType: "uint256" + }, + { + name: "chainIdField", + type: "uint256", + internalType: "uint256" + }, + { + name: "chainIndex", + type: "uint256", + internalType: "uint256" + }, + { + name: "expires", + type: "uint32", + internalType: "uint32" + }, + { + name: "fillDeadline", + type: "uint32", + internalType: "uint32" + }, + { + name: "inputOracle", + type: "address", + internalType: "address" + }, + { + name: "inputs", + type: "uint256[2][]", + internalType: "uint256[2][]" + }, + { + name: "outputs", + type: "tuple[]", + internalType: "struct MandateOutput[]", + components: [ + { + name: "oracle", + type: "bytes32", + internalType: "bytes32" + }, + { + name: "settler", + type: "bytes32", + internalType: "bytes32" + }, + { + name: "chainId", + type: "uint256", + internalType: "uint256" + }, + { + name: "token", + type: "bytes32", + internalType: "bytes32" + }, + { + name: "amount", + type: "uint256", + internalType: "uint256" + }, + { + name: "recipient", + type: "bytes32", + internalType: "bytes32" + }, + { + name: "callbackData", + type: "bytes", + internalType: "bytes" + }, + { + name: "context", + type: "bytes", + internalType: "bytes" + } + ] + }, + { + name: "additionalChains", + type: "bytes32[]", + internalType: "bytes32[]" + } + ] }, { - name: "outputHash", - type: "bytes32", - internalType: "bytes32" - } - ], - outputs: [ + name: "sponsor", + type: "address", + internalType: "address" + }, { - name: "payloadHash", - type: "bytes32", - internalType: "bytes32" + name: "signature", + type: "bytes", + internalType: "bytes" } ], - stateMutability: "view" + outputs: [], + stateMutability: "nonpayable" }, { type: "function", - name: "hasAttested", + name: "orderIdentifier", inputs: [ { - name: "payloads", - type: "bytes[]", - internalType: "bytes[]" + name: "order", + type: "tuple", + internalType: "struct MultichainOrderComponent", + components: [ + { + name: "user", + type: "address", + internalType: "address" + }, + { + name: "nonce", + type: "uint256", + internalType: "uint256" + }, + { + name: "chainIdField", + type: "uint256", + internalType: "uint256" + }, + { + name: "chainIndex", + type: "uint256", + internalType: "uint256" + }, + { + name: "expires", + type: "uint32", + internalType: "uint32" + }, + { + name: "fillDeadline", + type: "uint32", + internalType: "uint32" + }, + { + name: "inputOracle", + type: "address", + internalType: "address" + }, + { + name: "inputs", + type: "uint256[2][]", + internalType: "uint256[2][]" + }, + { + name: "outputs", + type: "tuple[]", + internalType: "struct MandateOutput[]", + components: [ + { + name: "oracle", + type: "bytes32", + internalType: "bytes32" + }, + { + name: "settler", + type: "bytes32", + internalType: "bytes32" + }, + { + name: "chainId", + type: "uint256", + internalType: "uint256" + }, + { + name: "token", + type: "bytes32", + internalType: "bytes32" + }, + { + name: "amount", + type: "uint256", + internalType: "uint256" + }, + { + name: "recipient", + type: "bytes32", + internalType: "bytes32" + }, + { + name: "callbackData", + type: "bytes", + internalType: "bytes" + }, + { + name: "context", + type: "bytes", + internalType: "bytes" + } + ] + }, + { + name: "additionalChains", + type: "bytes32[]", + internalType: "bytes32[]" + } + ] } ], outputs: [ { - name: "accumulator", - type: "bool", - internalType: "bool" + name: "", + type: "bytes32", + internalType: "bytes32" } ], stateMutability: "view" }, { type: "function", - name: "isProven", + name: "orderStatus", inputs: [ { - name: "remoteChainId", - type: "uint256", - internalType: "uint256" - }, - { - name: "remoteOracle", - type: "bytes32", - internalType: "bytes32" - }, - { - name: "application", - type: "bytes32", - internalType: "bytes32" - }, - { - name: "dataHash", + name: "orderId", type: "bytes32", internalType: "bytes32" } @@ -295,75 +683,112 @@ export const MULTICHAIN_SETTLER_ESCROW_ABI = [ outputs: [ { name: "", - type: "bool", - internalType: "bool" + type: "uint8", + internalType: "enum InputSettlerMultichainEscrow.OrderStatus" } ], stateMutability: "view" }, { type: "function", - name: "setAttestation", + name: "refund", inputs: [ { - name: "orderId", - type: "bytes32", - internalType: "bytes32" - }, - { - name: "solver", - type: "bytes32", - internalType: "bytes32" - }, - { - name: "timestamp", - type: "uint32", - internalType: "uint32" - }, - { - name: "output", + name: "order", type: "tuple", - internalType: "struct MandateOutput", + internalType: "struct MultichainOrderComponent", components: [ { - name: "oracle", - type: "bytes32", - internalType: "bytes32" - }, - { - name: "settler", - type: "bytes32", - internalType: "bytes32" + name: "user", + type: "address", + internalType: "address" }, { - name: "chainId", + name: "nonce", type: "uint256", internalType: "uint256" }, { - name: "token", - type: "bytes32", - internalType: "bytes32" + name: "chainIdField", + type: "uint256", + internalType: "uint256" }, { - name: "amount", + name: "chainIndex", type: "uint256", internalType: "uint256" }, { - name: "recipient", - type: "bytes32", - internalType: "bytes32" + name: "expires", + type: "uint32", + internalType: "uint32" + }, + { + name: "fillDeadline", + type: "uint32", + internalType: "uint32" }, { - name: "call", - type: "bytes", - internalType: "bytes" + name: "inputOracle", + type: "address", + internalType: "address" }, { - name: "context", - type: "bytes", - internalType: "bytes" + name: "inputs", + type: "uint256[2][]", + internalType: "uint256[2][]" + }, + { + name: "outputs", + type: "tuple[]", + internalType: "struct MandateOutput[]", + components: [ + { + name: "oracle", + type: "bytes32", + internalType: "bytes32" + }, + { + name: "settler", + type: "bytes32", + internalType: "bytes32" + }, + { + name: "chainId", + type: "uint256", + internalType: "uint256" + }, + { + name: "token", + type: "bytes32", + internalType: "bytes32" + }, + { + name: "amount", + type: "uint256", + internalType: "uint256" + }, + { + name: "recipient", + type: "bytes32", + internalType: "bytes32" + }, + { + name: "callbackData", + type: "bytes", + internalType: "bytes" + }, + { + name: "context", + type: "bytes", + internalType: "bytes" + } + ] + }, + { + name: "additionalChains", + type: "bytes32[]", + internalType: "bytes32[]" } ] } @@ -373,7 +798,13 @@ export const MULTICHAIN_SETTLER_ESCROW_ABI = [ }, { type: "event", - name: "OutputFilled", + name: "EIP712DomainChanged", + inputs: [], + anonymous: false + }, + { + type: "event", + name: "Finalised", inputs: [ { name: "orderId", @@ -388,94 +819,41 @@ export const MULTICHAIN_SETTLER_ESCROW_ABI = [ internalType: "bytes32" }, { - name: "timestamp", - type: "uint32", - indexed: false, - internalType: "uint32" - }, - { - name: "output", - type: "tuple", - indexed: false, - internalType: "struct MandateOutput", - components: [ - { - name: "oracle", - type: "bytes32", - internalType: "bytes32" - }, - { - name: "settler", - type: "bytes32", - internalType: "bytes32" - }, - { - name: "chainId", - type: "uint256", - internalType: "uint256" - }, - { - name: "token", - type: "bytes32", - internalType: "bytes32" - }, - { - name: "amount", - type: "uint256", - internalType: "uint256" - }, - { - name: "recipient", - type: "bytes32", - internalType: "bytes32" - }, - { - name: "call", - type: "bytes", - internalType: "bytes" - }, - { - name: "context", - type: "bytes", - internalType: "bytes" - } - ] - }, - { - name: "finalAmount", - type: "uint256", + name: "destination", + type: "bytes32", indexed: false, - internalType: "uint256" + internalType: "bytes32" } ], anonymous: false }, { type: "event", - name: "OutputProven", + name: "Open", inputs: [ { - name: "chainid", - type: "uint256", - indexed: false, - internalType: "uint256" - }, - { - name: "remoteIdentifier", + name: "orderId", type: "bytes32", - indexed: false, + indexed: true, internalType: "bytes32" }, { - name: "application", - type: "bytes32", + name: "order", + type: "bytes", indexed: false, - internalType: "bytes32" - }, + internalType: "bytes" + } + ], + anonymous: false + }, + { + type: "event", + name: "Refunded", + inputs: [ { - name: "payloadHash", + name: "orderId", type: "bytes32", - indexed: false, + indexed: true, internalType: "bytes32" } ], @@ -483,12 +861,28 @@ export const MULTICHAIN_SETTLER_ESCROW_ABI = [ }, { type: "error", - name: "AlreadyFilled", + name: "CallOutOfRange", inputs: [] }, { type: "error", - name: "CallOutOfRange", + name: "ChainIndexOutOfRange", + inputs: [ + { + name: "chainIndex", + type: "uint256", + internalType: "uint256" + }, + { + name: "numSegments", + type: "uint256", + internalType: "uint256" + } + ] + }, + { + type: "error", + name: "CodeSize0", inputs: [] }, { @@ -498,57 +892,77 @@ export const MULTICHAIN_SETTLER_ESCROW_ABI = [ }, { type: "error", - name: "ExclusiveTo", + name: "FillDeadlineAfterExpiry", inputs: [ { - name: "solver", - type: "bytes32", - internalType: "bytes32" + name: "fillDeadline", + type: "uint32", + internalType: "uint32" + }, + { + name: "expires", + type: "uint32", + internalType: "uint32" } ] }, { type: "error", - name: "FailedCall", + name: "FilledTooLate", + inputs: [ + { + name: "expected", + type: "uint32", + internalType: "uint32" + }, + { + name: "actual", + type: "uint32", + internalType: "uint32" + } + ] + }, + { + type: "error", + name: "HasDirtyBits", inputs: [] }, { type: "error", - name: "FillDeadline", + name: "InvalidOrderStatus", inputs: [] }, { type: "error", - name: "HasDirtyBits", + name: "InvalidShortString", inputs: [] }, { type: "error", - name: "InsufficientBalance", - inputs: [ - { - name: "balance", - type: "uint256", - internalType: "uint256" - }, - { - name: "needed", - type: "uint256", - internalType: "uint256" - } - ] + name: "InvalidSigner", + inputs: [] + }, + { + type: "error", + name: "InvalidTimestampLength", + inputs: [] }, { type: "error", - name: "InvalidAttestation", + name: "NoDestination", + inputs: [] + }, + { + type: "error", + name: "OrderIdMismatch", inputs: [ { - name: "storedFillRecordHash", + name: "provided", type: "bytes32", internalType: "bytes32" }, { - name: "givenFillRecordHash", + name: "computed", type: "bytes32", internalType: "bytes32" } @@ -556,78 +970,63 @@ export const MULTICHAIN_SETTLER_ESCROW_ABI = [ }, { type: "error", - name: "InvalidContextDataLength", + name: "ReentrancyDetected", inputs: [] }, { type: "error", - name: "NotDivisible", + name: "SafeERC20FailedOperation", inputs: [ { - name: "value", - type: "uint256", - internalType: "uint256" - }, - { - name: "divisor", - type: "uint256", - internalType: "uint256" + name: "token", + type: "address", + internalType: "address" } ] }, { type: "error", - name: "NotImplemented", - inputs: [] - }, - { - type: "error", - name: "NotProven", - inputs: [] - }, - { - type: "error", - name: "PayloadTooSmall", + name: "SignatureAndInputsNotEqual", inputs: [] }, { type: "error", - name: "SafeERC20FailedOperation", + name: "SignatureNotSupported", inputs: [ { - name: "token", - type: "address", - internalType: "address" + name: "", + type: "bytes1", + internalType: "bytes1" } ] }, { type: "error", - name: "WrongChain", + name: "StringTooLong", inputs: [ { - name: "expected", - type: "uint256", - internalType: "uint256" - }, - { - name: "actual", - type: "uint256", - internalType: "uint256" + name: "str", + type: "string", + internalType: "string" } ] }, { type: "error", - name: "WrongOutputOracle", + name: "TimestampNotPassed", + inputs: [] + }, + { + type: "error", + name: "TimestampPassed", + inputs: [] + }, + { + type: "error", + name: "UnexpectedCaller", inputs: [ { - name: "addressThis", - type: "bytes32", - internalType: "bytes32" - }, - { - name: "expected", + name: "expectedCaller", type: "bytes32", internalType: "bytes32" } @@ -635,23 +1034,18 @@ export const MULTICHAIN_SETTLER_ESCROW_ABI = [ }, { type: "error", - name: "WrongOutputSettler", + name: "WrongChain", inputs: [ { - name: "addressThis", - type: "bytes32", - internalType: "bytes32" + name: "expected", + type: "uint256", + internalType: "uint256" }, { - name: "expected", - type: "bytes32", - internalType: "bytes32" + name: "actual", + type: "uint256", + internalType: "uint256" } ] - }, - { - type: "error", - name: "ZeroValue", - inputs: [] } ] as const; diff --git a/src/lib/config.ts b/src/lib/config.ts index a090fc4..754ae1b 100644 --- a/src/lib/config.ts +++ b/src/lib/config.ts @@ -15,9 +15,11 @@ export const BYTES32_ZERO = export const COMPACT = "0x00000000000000171ede64904551eeDF3C6C9788" as const; export const INPUT_SETTLER_COMPACT_LIFI = "0x0000000000cd5f7fDEc90a03a31F79E5Fbc6A9Cf" as const; export const INPUT_SETTLER_ESCROW_LIFI = "0x000025c3226C00B2Cdc200005a1600509f4e00C0" as const; -export const MULTICHAIN_INPUT_SETTLER_ESCROW_LIFI = - "0x000001bf3F3175BD007f3889b50000c7006E72c0" as const; -export const ALWAYS_OK_ALLOCATOR = "301267367668059890006832136" as const; +export const MULTICHAIN_INPUT_SETTLER_ESCROW = + "0xb912b4c38ab54b94D45Ac001484dEBcbb519Bc2B" as const; +export const MULTICHAIN_INPUT_SETTLER_COMPACT = + "0x1fccC0807F25A58eB531a0B5b4bf3dCE88808Ed7" as const; +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; export const WORMHOLE_ORACLE = { diff --git a/src/lib/libraries/intent.ts b/src/lib/libraries/intent.ts index 11542de..0ed2b2f 100644 --- a/src/lib/libraries/intent.ts +++ b/src/lib/libraries/intent.ts @@ -25,7 +25,8 @@ import { getOracle, INPUT_SETTLER_COMPACT_LIFI, INPUT_SETTLER_ESCROW_LIFI, - MULTICHAIN_INPUT_SETTLER_ESCROW_LIFI, + MULTICHAIN_INPUT_SETTLER_COMPACT, + MULTICHAIN_INPUT_SETTLER_ESCROW, type chain, type Token, type Verifier, @@ -149,8 +150,7 @@ export class Intent { inputSettler(multichain: boolean) { if (this.lock.type === "compact" && multichain === false) return INPUT_SETTLER_COMPACT_LIFI; if (this.lock.type === "escrow" && multichain === false) return INPUT_SETTLER_ESCROW_LIFI; - if (this.lock.type === "escrow" && multichain === true) - return MULTICHAIN_INPUT_SETTLER_ESCROW_LIFI; + if (this.lock.type === "escrow" && multichain === true) return MULTICHAIN_INPUT_SETTLER_ESCROW; throw new Error(`Not supported ${multichain}, ${this.lock}`); } @@ -394,6 +394,7 @@ export class StandardOrderIntent { */ openEscrow(account: `0x${string}`, walletClient: WC): [Promise<`0x${string}`>] { const chain = findChain(this.order.originChainId); + walletClient.switchChain({ id: Number(this.order.originChainId) }); if (!chain) throw new Error("Chain not found for chainId " + this.order.originChainId.toString()); return [ @@ -511,7 +512,19 @@ export class MultichainOrderIntent { this.inputSettler = inputSetter; this.order = order; - this.lock = lock; + const isCompact = + this.inputSettler === INPUT_SETTLER_COMPACT_LIFI || + this.inputSettler === MULTICHAIN_INPUT_SETTLER_COMPACT; + + this.lock = lock ?? { type: isCompact ? "compact" : "escrow" }; + } + + selfTest() { + this.asOrder(); + this.inputChains(); + this.asComponents(); + + this.orderId(); } /** @@ -534,7 +547,7 @@ export class MultichainOrderIntent { const orderId = computedOrderIds[0]; computedOrderIds.map((v) => { - if (v === orderId) throw new Error(`Order ids are not equal ${computedOrderIds}`); + if (v !== orderId) throw new Error(`Order ids are not equal ${computedOrderIds}`); }); return orderId; } @@ -670,12 +683,14 @@ export class MultichainOrderIntent { // This code is depreciated and needs to be updated. async openEscrow(account: `0x${string}`, walletClient: WC) { + this.selfTest(); const components = this.asComponents(); - const promises: Promise<`0x${string}`>[] = []; + const results: `0x${string}`[] = []; for (const { chainId, orderComponent } of components) { - const chain = findChain(chainId); - promises.push( - walletClient.writeContract({ + const chain = findChain(chainId)!; + walletClient.switchChain({ id: chain.id }); + results.push( + await walletClient.writeContract({ chain, account, address: this.inputSettler, @@ -684,8 +699,9 @@ export class MultichainOrderIntent { args: [orderComponent] }) ); + console.log(results); } - return promises; + return results; } async finalise(options: { @@ -708,7 +724,7 @@ export class MultichainOrderIntent { const components = this.asComponents().filter((c) => c.chainId === BigInt(actionChain.id)); for (const { orderComponent } of components) { - if (this.inputSettler.toLowerCase() === MULTICHAIN_INPUT_SETTLER_ESCROW_LIFI.toLowerCase()) { + if (this.inputSettler.toLowerCase() === MULTICHAIN_INPUT_SETTLER_ESCROW.toLowerCase()) { return await walletClient.writeContract({ chain: actionChain, account: account, diff --git a/src/lib/libraries/intentFactory.ts b/src/lib/libraries/intentFactory.ts index b6a4c28..aa3d330 100644 --- a/src/lib/libraries/intentFactory.ts +++ b/src/lib/libraries/intentFactory.ts @@ -19,7 +19,7 @@ import { ERC20_ABI } from "$lib/abi/erc20"; import { Intent } from "$lib/libraries/intent"; import { OrderServer } from "$lib/libraries/orderServer"; import type { CreateIntentOptions } from "$lib/libraries/intent"; -import type { TokenContext } from "$lib/state.svelte"; +import { store, type TokenContext } from "$lib/state.svelte"; /** * @notice Factory class for creating and managing intents. Functions called by integrators. @@ -149,12 +149,13 @@ export class IntentFactory { // Execute the open. const transactionHashes = await intent.openEscrow(account(), this.walletClient); + console.log({ tsh: transactionHashes }); - for (const hash of transactionHashes) { - await clients[inputChain].waitForTransactionReceipt({ - hash: await hash - }); - } + // for (const hash of transactionHashes) { + // await clients[inputChain].waitForTransactionReceipt({ + // hash: await hash + // }); + // } if (this.postHook) await this.postHook(); @@ -187,7 +188,7 @@ export function escrowApprove( address: token.address, abi: ERC20_ABI, functionName: "allowance", - args: [account(), INPUT_SETTLER_ESCROW_LIFI] + args: [account(), store.inputSettler] }); if (currentAllowance >= amount) continue; const transactionHash = walletClient.writeContract({ @@ -196,7 +197,7 @@ export function escrowApprove( address: token.address, abi: ERC20_ABI, functionName: "approve", - args: [INPUT_SETTLER_ESCROW_LIFI, maxUint256] + args: [store.inputSettler, maxUint256] }); await publicClient.waitForTransactionReceipt({ diff --git a/src/lib/libraries/solver.ts b/src/lib/libraries/solver.ts index 902eee8..c51921c 100644 --- a/src/lib/libraries/solver.ts +++ b/src/lib/libraries/solver.ts @@ -186,27 +186,6 @@ export class Solver { return result; } } - - // if (order.inputOracle === getOracle("wormhole", sourceChain)) { - // // TODO: get sequence from event. - // const sequence = 0; - // // Get VAA - // const wormholeChainId = wormholeChainIds[outputChain]; - // const requestUrl = `https://api.testnet.wormholescan.io/v1/signed_vaa/${wormholeChainId}/${output.oracle.replace( - // "0x", - // "" - // )}/${sequence}?network=Testnet`; - // const response = await axios.get(requestUrl); - // console.log(response.data); - // return $walletClient.writeContract({ - // account: connectedAccount.address, - // address: order.inputOracle, - // abi: WROMHOLE_ORACLE_ABI, - // functionName: 'receiveMessage', - // args: [encodedOutput] - // }); - // return; - // } }; } @@ -253,7 +232,7 @@ export class Solver { solver: addressToBytes32(account()) }; - const transactionHash = intent.finalise({ + const transactionHash = await intent.finalise({ sourceChain, account: account(), walletClient, @@ -261,7 +240,7 @@ export class Solver { signatures: orderContainer }); const result = await clients[sourceChain].waitForTransactionReceipt({ - hash: transactionHash + hash: transactionHash! }); if (postHook) await postHook(); return result; diff --git a/src/lib/screens/FillIntent.svelte b/src/lib/screens/FillIntent.svelte index b04acee..77c5b73 100644 --- a/src/lib/screens/FillIntent.svelte +++ b/src/lib/screens/FillIntent.svelte @@ -9,12 +9,13 @@ type WC } from "$lib/config"; import { bytes32ToAddress } from "$lib/utils/convert"; - import { getOrderId, getOutputHash } from "$lib/utils/orderLib"; + import { getOutputHash } from "$lib/utils/orderLib"; import type { MandateOutput, OrderContainer } from "../../types"; import { Solver } from "$lib/libraries/solver"; import { COIN_FILLER_ABI } from "$lib/abi/outputsettler"; import AwaitButton from "$lib/components/AwaitButton.svelte"; import store from "$lib/state.svelte"; + import { Intent, orderToIntent } from "$lib/libraries/intent"; let { scroll, @@ -74,7 +75,9 @@ const filledStatusPromises: [bigint, Promise<`0x${string}`>[]][] = $derived( sortOutputsByChain(orderContainer).map(([c, outputs]) => [ c, - outputs.map((output) => isFilled(getOrderId(orderContainer), output, refreshValidation)) + outputs.map((output) => + isFilled(orderToIntent(orderContainer).orderId(), output, refreshValidation) + ) ]) ); diff --git a/src/lib/screens/IntentList.svelte b/src/lib/screens/IntentList.svelte index 5f18063..6435acb 100644 --- a/src/lib/screens/IntentList.svelte +++ b/src/lib/screens/IntentList.svelte @@ -1,7 +1,7 @@
@@ -33,37 +46,64 @@
OrderId
-
{getOrderId(orderContainer).slice(2, 12)}
+
{orderToIntent(orderContainer).orderId().slice(2, 12)}
User
{orderContainer.order.user.slice(0, 8)}...
Inputs
- {#each orderContainer.order.inputs as input} -
-
-
-
- {formatTokenAmount( - input[1], - getCoin({ + {#if "originChainId" in orderContainer.order} + {#each orderContainer.order.inputs as input} +
+
+
+
+ {formatTokenAmount( + input[1], + getCoin({ + address: idToToken(input[0]), + chain: getChainName(orderContainer.order.originChainId) + }).decimals + )} +
+
+ {getCoin({ address: idToToken(input[0]), chain: getChainName(orderContainer.order.originChainId) - }).decimals - )} + }).name} +
-
- {getCoin({ - address: idToToken(input[0]), - chain: getChainName(orderContainer.order.originChainId) - }).name} +
{getChainName(orderContainer.order.originChainId)}
+
+
+ {/each} + {:else} + {#each flattenInputs(orderContainer.order.inputs) as input} +
+
+
+
+ {formatTokenAmount( + input.input[1], + getCoin({ + address: idToToken(input.input[0]), + chain: getChainName(input.chainId) + }).decimals + )} +
+
+ {getCoin({ + address: idToToken(input.input[0]), + chain: getChainName(input.chainId) + }).name} +
+
{getChainName(input.chainId)}
-
{getChainName(orderContainer.order.originChainId)}
-
- {/each} + {/each} + {/if}
diff --git a/src/lib/screens/IssueIntent.svelte b/src/lib/screens/IssueIntent.svelte index 58aafa5..3013fd4 100644 --- a/src/lib/screens/IssueIntent.svelte +++ b/src/lib/screens/IssueIntent.svelte @@ -6,7 +6,8 @@ POLYMER_ALLOCATOR, formatTokenAmount, type chain, - INPUT_SETTLER_ESCROW_LIFI + INPUT_SETTLER_ESCROW_LIFI, + MULTICHAIN_INPUT_SETTLER_COMPACT } from "$lib/config"; import { IntentFactory, escrowApprove } from "$lib/libraries/intentFactory"; import { CompactLib } from "$lib/libraries/compactLib"; @@ -265,7 +266,7 @@ > Low Balance - {:else if store.inputSettler === INPUT_SETTLER_ESCROW_LIFI} + {:else if store.intentType === "escrow"} {#snippet name()} Execute Open @@ -274,7 +275,7 @@ Waiting for transaction... {/snippet} - {:else if store.inputSettler === INPUT_SETTLER_COMPACT_LIFI} + {:else if store.intentType === "compact"} {#snippet name()} Execute Deposit and Open @@ -284,7 +285,7 @@ {/snippet} {/if} - {#if store.inputSettler === INPUT_SETTLER_COMPACT_LIFI && store.allocatorId !== POLYMER_ALLOCATOR} + {#if store.intentType === "compact" && store.allocatorId !== POLYMER_ALLOCATOR} {#if !balanceCheckCompact} - {:then isClaimed} - {#if isClaimed} + {#each orderToIntent(orderContainer).inputChains() as inputChain} +
+

+ {getChainName(inputChain)} +

+
+
+ {#await isClaimed(inputChain, orderContainer, "")} - {:else} + {:then isClaimed} + {#if isClaimed} + + {:else} + + store.fillTranscations[ + hashStruct({ + data: output, + types: compactTypes, + primaryType: "MandateOutput" + }) + ] as string + ) + }, + { + account, + preHook, + postHook + } + )} + > + {#snippet name()} + Claim + {/snippet} + {#snippet awaiting()} + Waiting for transaction... + {/snippet} + + {/if} + {:catch} + store.fillTranscations[ + hashStruct({ + data: output, + types: compactTypes, + primaryType: "MandateOutput" + }) + ] as string + ) }, { account, @@ -129,55 +176,59 @@ Waiting for transaction... {/snippet} - {/if} - {:catch} - - {#snippet name()} - Claim - {/snippet} - {#snippet awaiting()} - Waiting for transaction... - {/snippet} - - {/await} -
- {#each orderContainer.order.inputs as input} -
-
-
-
- {formatTokenAmount( - input[1], - getCoin({ - address: idToToken(input[0]), - chain: getChainName(orderContainer.order.originChainId) - }).decimals - )} + {/await} +
+ {#if "originChainId" in orderContainer.order} + {#each orderContainer.order.inputs as input} +
+
+
+
+ {formatTokenAmount( + input[1], + getCoin({ + address: idToToken(input[0]), + chain: getChainName(orderContainer.order.originChainId) + }).decimals + )} +
+
+ {getCoin({ + address: idToToken(input[0]), + chain: getChainName(orderContainer.order.originChainId) + }).name} +
+
-
- {getCoin({ - address: idToToken(input[0]), - chain: getChainName(orderContainer.order.originChainId) - }).name} +
+ {/each} + {:else} + {#each orderContainer.order.inputs.find((v) => v.chainId === inputChain)?.inputs as input} +
+
+
+
+ {formatTokenAmount( + input[1], + getCoin({ + address: idToToken(input[0]), + chain: getChainName(inputChain) + }).decimals + )} +
+
+ {getCoin({ + address: idToToken(input[0]), + chain: getChainName(inputChain) + }).name} +
+
-
-
- {/each} + {/each} + {/if} +
-
+ {/each}
diff --git a/src/lib/screens/IssueIntent.svelte b/src/lib/screens/IssueIntent.svelte index 3013fd4..c77231e 100644 --- a/src/lib/screens/IssueIntent.svelte +++ b/src/lib/screens/IssueIntent.svelte @@ -42,7 +42,7 @@ postHook, verifier: store.verifier, lock: { - type: store.inputSettler === INPUT_SETTLER_COMPACT_LIFI ? "compact" : "escrow", + type: store.intentType, allocatorId: store.allocatorId, resetPeriod: ResetPeriod.OneDay }, @@ -65,7 +65,7 @@ ); const approveFunction = $derived( - store.inputSettler === INPUT_SETTLER_COMPACT_LIFI + store.intentType === "compact" ? CompactLib.compactApprove(store.walletClient, opts) : escrowApprove(store.walletClient, opts) ); @@ -308,7 +308,7 @@
{/if}
- {#if numInputChains > 1 && store.inputSettler !== INPUT_SETTLER_COMPACT_LIFI} + {#if numInputChains > 1 && store.intentType !== "compact"}

You'll need to open the order on {numInputChains} chains. Be prepared and do not interrupt the process. diff --git a/src/lib/screens/ManageDeposit.svelte b/src/lib/screens/ManageDeposit.svelte index b93a2c7..40dad78 100644 --- a/src/lib/screens/ManageDeposit.svelte +++ b/src/lib/screens/ManageDeposit.svelte @@ -89,14 +89,14 @@ -->

- {#if store.inputSettler === INPUT_SETTLER_COMPACT_LIFI} + {#if store.intentType === "compact"}

Allocator

diff --git a/src/lib/screens/ReceiveMessage.svelte b/src/lib/screens/ReceiveMessage.svelte index d83f875..12ce817 100644 --- a/src/lib/screens/ReceiveMessage.svelte +++ b/src/lib/screens/ReceiveMessage.svelte @@ -2,30 +2,27 @@ import { formatTokenAmount, getChainName, getClient, getCoin, type chain } from "$lib/config"; import { addressToBytes32 } from "$lib/utils/convert"; import { encodeMandateOutput } from "$lib/utils/orderLib"; - import { keccak256 } from "viem"; + import { hashStruct, keccak256 } from "viem"; import type { MandateOutput, OrderContainer } from "../../types"; import { POLYMER_ORACLE_ABI } from "$lib/abi/polymeroracle"; import { Solver } from "$lib/libraries/solver"; import AwaitButton from "$lib/components/AwaitButton.svelte"; import store from "$lib/state.svelte"; import { orderToIntent } from "$lib/libraries/intent"; + import { compactTypes } from "$lib/utils/typedMessage"; // 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. let { orderContainer, - fillTransactionHash, account, preHook, - postHook, - mainnet + postHook }: { orderContainer: OrderContainer; - fillTransactionHash: `0x${string}`; preHook?: (chain: chain) => Promise; postHook: () => Promise; account: () => `0x${string}`; - mainnet: boolean; } = $props(); let refreshValidation = $state(0); @@ -36,11 +33,13 @@ async function isValidated( orderId: `0x${string}`, + chainId: bigint, orderContainer: OrderContainer, output: MandateOutput, fillTransactionHash: `0x${string}`, _?: any ) { + if (!fillTransactionHash) return false; if ( !fillTransactionHash || !fillTransactionHash.startsWith("0x") || @@ -63,7 +62,7 @@ output ); const outputHash = keccak256(encodedOutput); - const sourceChainClient = getClient(order.originChainId); + const sourceChainClient = getClient(chainId); return await sourceChainClient.readContract({ address: order.inputOracle, abi: POLYMER_ORACLE_ABI, @@ -71,6 +70,25 @@ args: [output.chainId, output.oracle, output.settler, outputHash] }); } + + // const validations = $derived( + // orderContainer.order.outputs.map((output) => { + // return orderToIntent(orderContainer) + // .inputChains() + // .map((inputChain) => { + // return isValidated( + // orderToIntent(orderContainer).orderId(), + // inputChain, + // orderContainer, + // output, + // store.fillTranscations[ + // hashStruct({ data: output, types: compactTypes, primaryType: "MandateOutput" }) + // ], + // refreshValidation + // ); + // }); + // }) + // );
@@ -79,79 +97,94 @@ Click on each output and wait until they turn green. Polymer does not support batch validation. Continue to the right.

-
-

- {getChainName(orderContainer.order.originChainId)} -

-
-
- {#each orderContainer.order.outputs as output} - {#await isValidated(orderToIntent(orderContainer).orderId(), orderContainer, output, fillTransactionHash, refreshValidation)} -
-
-
-
- {formatTokenAmount( - output.amount, - getCoin({ address: output.token, chain: getChainName(output.chainId) }).decimals - )} -
-
- {getCoin({ address: output.token, chain: getChainName(output.chainId) }).name} + {#each orderToIntent(orderContainer).inputChains() as inputChain} +
+

+ {getChainName(inputChain)} +

+
+
+ {#each orderContainer.order.outputs as output} + {#await isValidated(orderToIntent(orderContainer).orderId(), inputChain, orderContainer, output, store.fillTranscations[hashStruct( { data: output, types: compactTypes, primaryType: "MandateOutput" } )], refreshValidation)} +
+
+
+
+ {formatTokenAmount( + output.amount, + getCoin({ address: output.token, chain: getChainName(output.chainId) }) + .decimals + )} +
+
+ {getCoin({ address: output.token, chain: getChainName(output.chainId) }).name} +
-
- {:then validated} - console.log("Has already been validated") - : Solver.validate( - store.walletClient, - { - orderContainer, - fillTransactionHash, - sourceChain: orderContainer.order.originChainId, - mainnet - }, - { - preHook, - postHook: postHookRefreshValidate, - account - } - )} - > - {#snippet name()} -
-
- {formatTokenAmount( - output.amount, - getCoin({ address: output.token, chain: getChainName(output.chainId) }).decimals - )} -
-
- {getCoin({ address: output.token, chain: getChainName(output.chainId) }).name} -
-
- {/snippet} - {#snippet awaiting()} -
-
- {formatTokenAmount( - output.amount, - getCoin({ address: output.token, chain: getChainName(output.chainId) }).decimals + {:then validated} + console.log("Has already been validated") + : Solver.validate( + store.walletClient, + { + orderContainer, + fillTransactionHash: + store.fillTranscations[ + hashStruct({ + data: output, + types: compactTypes, + primaryType: "MandateOutput" + }) + ], + sourceChain: getChainName(inputChain), + mainnet: store.mainnet + }, + { + preHook, + postHook: postHookRefreshValidate, + account + } )} + > + {#snippet name()} +
+
+ {formatTokenAmount( + output.amount, + getCoin({ address: output.token, chain: getChainName(output.chainId) }) + .decimals + )} +
+
+ {getCoin({ address: output.token, chain: getChainName(output.chainId) }).name} +
-
- {getCoin({ address: output.token, chain: getChainName(output.chainId) }).name} + {/snippet} + {#snippet awaiting()} +
+
+ {formatTokenAmount( + output.amount, + getCoin({ address: output.token, chain: getChainName(output.chainId) }) + .decimals + )} +
+
+ {getCoin({ address: output.token, chain: getChainName(output.chainId) }).name} +
-
- {/snippet} -
- {/await} - {/each} + {/snippet} + + {/await} + {/each} +
-
+ {/each}
diff --git a/src/lib/state.svelte.ts b/src/lib/state.svelte.ts index 93e672d..d41bb48 100644 --- a/src/lib/state.svelte.ts +++ b/src/lib/state.svelte.ts @@ -50,6 +50,8 @@ class Store { // inputAmounts = $state([1000000n]); // outputAmounts = $state([1000000n]); + fillTranscations = $state<{ [outputId: string]: `0x${string}` }>({}); + balances = $derived.by(() => { return this.mapOverCoins(getBalance, this.mainnet, this.updatedDerived); }); @@ -80,6 +82,7 @@ class Store { multichain = $derived([...new Set(this.inputTokens.map((i) => i.token.chain))].length > 1); // --- Input Side --- // + // TODO: remove inputSettler = $derived.by(() => { if (this.intentType === "escrow" && !this.multichain) return INPUT_SETTLER_ESCROW_LIFI; if (this.intentType === "escrow" && this.multichain) return MULTICHAIN_INPUT_SETTLER_ESCROW; diff --git a/src/lib/utils/interopableAddresses.ts b/src/lib/utils/interopableAddresses.ts index 4b0a618..43ba9dc 100644 --- a/src/lib/utils/interopableAddresses.ts +++ b/src/lib/utils/interopableAddresses.ts @@ -25,9 +25,7 @@ export const getInteropableAddress = ( const chainReference = padEven(chainId.toString(16)); const chainReferenceLength = toHex(chainReference.length / 2); - const interopableAddress = `0x${version}${chainType}${chainReferenceLength}${chainReference}${toHex( + return `0x${version}${chainType}${chainReferenceLength}${chainReference}${toHex( address.replace("0x", "").length / 2 )}${address.replace("0x", "")}`; - - return interopableAddress; }; diff --git a/src/lib/utils/typedMessage.ts b/src/lib/utils/typedMessage.ts index 1174984..9de506d 100644 --- a/src/lib/utils/typedMessage.ts +++ b/src/lib/utils/typedMessage.ts @@ -7,44 +7,44 @@ const BatchCompact = [ { name: "expires", type: "uint256" }, { name: "commitments", type: "Lock[]" }, { name: "mandate", type: "Mandate" } -]; +] as const; const MultichainCompact = [ { name: "sponsor", type: "address" }, { name: "nonce", type: "uint256" }, { name: "expires", type: "uint256" }, { name: "elements", type: "Element[]" } -]; +] as const; const Lock = [ { name: "lockTag", type: "bytes12" }, { name: "token", type: "address" }, { name: "amount", type: "uint256" } -]; +] as const; const Element = [ { name: "arbiter", type: "address" }, { name: "chainId", type: "uint256" }, { name: "commitments", type: "Lock[]" }, { name: "mandate", type: "Mandate" } -]; +] as const; const Mandate = [ { name: "fillDeadline", type: "uint32" }, { name: "inputOracle", type: "address" }, { name: "outputs", type: "MandateOutput[]" } -]; +] as const; -const MandateOutput = [ +export const MandateOutput = [ { name: "oracle", type: "bytes32" }, { name: "settler", type: "bytes32" }, { name: "chainId", type: "uint256" }, { name: "token", type: "bytes32" }, { name: "amount", type: "uint256" }, { name: "recipient", type: "bytes32" }, - { name: "call", type: "bytes" }, + { name: "callbackData", type: "bytes" }, { name: "context", type: "bytes" } -]; +] as const; export const StandardOrderAbi = [ { name: "user", type: "address" }, @@ -55,7 +55,7 @@ export const StandardOrderAbi = [ { name: "inputOracle", type: "address" }, { name: "inputs", type: "uint256[2][]" }, { name: "outputs", type: "tuple[]", components: MandateOutput } -]; +] as const; // The named list of all type definitions export const compactTypes = { diff --git a/src/routes/+page.svelte b/src/routes/+page.svelte index 0fa2eb8..f98666e 100644 --- a/src/routes/+page.svelte +++ b/src/routes/+page.svelte @@ -90,8 +90,6 @@ let selectedOrder = $state(undefined); - let fillTransactionHash = $state<`0x${string}` | undefined>(undefined); - let snapContainer: HTMLDivElement; function scroll(next: boolean | number) { @@ -155,31 +153,11 @@ {#if selectedOrder !== undefined} - - {#if fillTransactionHash} - - - {/if} + + {/if} {/if}
diff --git a/src/types/index.ts b/src/types/index.ts index 6b9738b..64bfab3 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -34,7 +34,7 @@ export type BatchCompact = { arbiter: `0x${string}`; // The account tasked with verifying and submitting the claim. sponsor: `0x${string}`; // The account to source the tokens from. nonce: bigint; // A parameter to enforce replay protection, scoped to allocator. - expires: number; // The time at which the claim expires. + expires: bigint; // The time at which the claim expires. commitments: Lock[]; // The allocated token IDs and amounts. mandate: CompactMandate; }; From f64789d1bc47054cb0a72b787976f153b1d9f075 Mon Sep 17 00:00:00 2001 From: Alexander Date: Mon, 10 Nov 2025 19:17:26 +0100 Subject: [PATCH 11/17] Multi outputs --- src/lib/libraries/solver.ts | 63 ++++++++++++++++++--------- src/lib/screens/ReceiveMessage.svelte | 45 ++++++++++--------- 2 files changed, 65 insertions(+), 43 deletions(-) diff --git a/src/lib/libraries/solver.ts b/src/lib/libraries/solver.ts index 843df83..adb9657 100644 --- a/src/lib/libraries/solver.ts +++ b/src/lib/libraries/solver.ts @@ -9,7 +9,13 @@ import { INPUT_SETTLER_ESCROW_LIFI, type WC } from "$lib/config"; -import { encodeAbiParameters, maxUint256, parseAbiParameters } from "viem"; +import { + encodeAbiParameters, + hashStruct, + maxUint256, + parseAbiParameters, + parseEventLogs +} from "viem"; import type { MandateOutput, OrderContainer } from "../../types"; import { addressToBytes32, bytes32ToAddress } from "$lib/utils/convert"; import axios from "axios"; @@ -19,6 +25,7 @@ import { COIN_FILLER_ABI } from "$lib/abi/outputsettler"; import { ERC20_ABI } from "$lib/abi/erc20"; import { SETTLER_ESCROW_ABI } from "$lib/abi/escrow"; import { orderToIntent } from "./intent"; +import { compactTypes } from "$lib/utils/typedMessage"; /** * @notice Class for solving intents. Functions called by solvers. @@ -45,17 +52,14 @@ export class Solver { const publicClients = clients; // TODO: MULTICHAIN COMPACT fix escrow typing const orderId = orderToIntent({ order, inputSettler, lock: { type: "escrow" } }).orderId(); - //Check that only 1 output exists. - if (outputs.length !== 1) { - throw new Error("Order must have exactly one output"); - } const outputChain = getChainName(outputs[0].chainId); console.log({ outputChain }); + let value = 0n; for (const output of outputs) { if (output.token === BYTES32_ZERO) { - // The destination asset cannot be ETH. - throw new Error("Output token cannot be ETH"); + value += output.amount; + continue; } if (output.chainId != outputs[0].chainId) { throw new Error("Filling outputs on multiple chains with single fill call not supported"); @@ -92,6 +96,7 @@ export class Solver { chain: chainMap[outputChain], account: account(), address: bytes32ToAddress(outputs[0].settler), + value, abi: COIN_FILLER_ABI, functionName: "fillOrderOutputs", args: [orderId, outputs, order.fillDeadline, addressToBytes32(account())] @@ -108,6 +113,7 @@ export class Solver { static validate( walletClient: WC, args: { + output: MandateOutput; orderContainer: OrderContainer; fillTransactionHash: string; sourceChain: chain; @@ -122,26 +128,46 @@ export class Solver { return async () => { const { preHook, postHook, account } = opts; const { + output, orderContainer: { order, inputSettler }, fillTransactionHash, sourceChain, mainnet } = args; const outputChain = getChainName(order.outputs[0].chainId); - if (order.outputs.length !== 1) { - throw new Error("Order must have exactly one output"); - } - // The destination asset cannot be ETH. - const output = order.outputs[0]; if (order.inputOracle === getOracle("polymer", sourceChain)) { const transactionReceipt = await clients[outputChain].getTransactionReceipt({ hash: fillTransactionHash as `0x${string}` }); - const numlogs = transactionReceipt.logs.length; - if (numlogs !== 2) throw Error(`Unexpected Logs count ${numlogs}`); - const fillLog = transactionReceipt.logs[1]; // The first log is transfer, next is fill. + const logs = parseEventLogs({ + abi: COIN_FILLER_ABI, + eventName: "OutputFilled", + logs: transactionReceipt.logs + }); + // We need to search through each log until we find one matching our output. + console.log("logs", logs); + let logIndex = -1; + const expectedOutputHash = hashStruct({ + types: compactTypes, + primaryType: "MandateOutput", + data: output + }); + for (const log of logs) { + const logOutput = log.args.output; + // TODO: Optimise by comparing the dicts. + const logOutputHash = hashStruct({ + types: compactTypes, + primaryType: "MandateOutput", + data: logOutput + }); + if (logOutputHash === expectedOutputHash) { + logIndex = log.logIndex; + break; + } + } + if (logIndex === -1) throw Error(`Could not find matching log`); let proof: string | undefined; let polymerIndex: number | undefined; @@ -149,7 +175,7 @@ export class Solver { const response = await axios.post(`/polymer`, { srcChainId: Number(order.outputs[0].chainId), srcBlockNumber: Number(transactionReceipt.blockNumber), - globalLogIndex: Number(fillLog.logIndex), + globalLogIndex: Number(logIndex), polymerIndex, mainnet: mainnet }); @@ -166,7 +192,7 @@ export class Solver { // Wait while backing off before requesting again. await new Promise((r) => setTimeout(r, i * 2 + 1000)); } - console.log({ proof }); + console.log({ logIndex, proof }); if (proof) { if (preHook) await preHook(sourceChain); @@ -213,9 +239,6 @@ export class Solver { }); const outputChain = getChainName(order.outputs[0].chainId); - if (order.outputs.length !== 1) { - throw new Error("Order must have exactly one output"); - } const transactionReceipts = await Promise.all( fillTransactionHashes.map((fth) => clients[outputChain].getTransactionReceipt({ diff --git a/src/lib/screens/ReceiveMessage.svelte b/src/lib/screens/ReceiveMessage.svelte index 12ce817..d6c3630 100644 --- a/src/lib/screens/ReceiveMessage.svelte +++ b/src/lib/screens/ReceiveMessage.svelte @@ -129,29 +129,28 @@ validated ? "bg-green-50" : "bg-yellow-50" ]} hoverClass={[validated ? "" : "hover:bg-yellow-100 cursor-pointer"]} - buttonFunction={validated - ? async () => console.log("Has already been validated") - : Solver.validate( - store.walletClient, - { - orderContainer, - fillTransactionHash: - store.fillTranscations[ - hashStruct({ - data: output, - types: compactTypes, - primaryType: "MandateOutput" - }) - ], - sourceChain: getChainName(inputChain), - mainnet: store.mainnet - }, - { - preHook, - postHook: postHookRefreshValidate, - account - } - )} + buttonFunction={Solver.validate( + store.walletClient, + { + output, + orderContainer, + fillTransactionHash: + store.fillTranscations[ + hashStruct({ + data: output, + types: compactTypes, + primaryType: "MandateOutput" + }) + ], + sourceChain: getChainName(inputChain), + mainnet: store.mainnet + }, + { + preHook, + postHook: postHookRefreshValidate, + account + } + )} > {#snippet name()}
From adc35957678d877d1003ad7aed8c85ce40ad7321 Mon Sep 17 00:00:00 2001 From: Alexander Date: Tue, 11 Nov 2025 22:59:27 +0100 Subject: [PATCH 12/17] Multichain compact intents --- src/lib/abi/multichain_compact.ts | 624 ++++++++++++++++++++++ src/lib/components/InputTokenModal.svelte | 3 - src/lib/libraries/intent.ts | 232 +++++++- src/lib/libraries/intentFactory.ts | 28 +- src/lib/libraries/solver.ts | 6 +- src/lib/screens/FillIntent.svelte | 1 - src/lib/screens/IssueIntent.svelte | 8 +- src/lib/screens/ManageDeposit.svelte | 40 +- src/lib/state.svelte.ts | 1 - src/lib/utils/interopableAddresses.ts | 9 +- src/lib/utils/typedMessage.ts | 22 +- src/types/index.ts | 14 + 12 files changed, 904 insertions(+), 84 deletions(-) create mode 100644 src/lib/abi/multichain_compact.ts diff --git a/src/lib/abi/multichain_compact.ts b/src/lib/abi/multichain_compact.ts new file mode 100644 index 0000000..a4f7ef8 --- /dev/null +++ b/src/lib/abi/multichain_compact.ts @@ -0,0 +1,624 @@ +export const MULTICHAIN_SETTLER_COMPACT_ABI = [ + { + type: "constructor", + inputs: [ + { + name: "compact", + type: "address", + internalType: "address" + } + ], + stateMutability: "nonpayable" + }, + { + type: "function", + name: "COMPACT", + inputs: [], + outputs: [ + { + name: "", + type: "address", + internalType: "contract TheCompact" + } + ], + stateMutability: "view" + }, + { + type: "function", + name: "DOMAIN_SEPARATOR", + inputs: [], + outputs: [ + { + name: "", + type: "bytes32", + internalType: "bytes32" + } + ], + stateMutability: "view" + }, + { + type: "function", + name: "eip712Domain", + inputs: [], + outputs: [ + { + name: "fields", + type: "bytes1", + internalType: "bytes1" + }, + { + name: "name", + type: "string", + internalType: "string" + }, + { + name: "version", + type: "string", + internalType: "string" + }, + { + name: "chainId", + type: "uint256", + internalType: "uint256" + }, + { + name: "verifyingContract", + type: "address", + internalType: "address" + }, + { + name: "salt", + type: "bytes32", + internalType: "bytes32" + }, + { + name: "extensions", + type: "uint256[]", + internalType: "uint256[]" + } + ], + stateMutability: "view" + }, + { + type: "function", + name: "finalise", + inputs: [ + { + name: "order", + type: "tuple", + internalType: "struct MultichainOrderComponent", + components: [ + { + name: "user", + type: "address", + internalType: "address" + }, + { + name: "nonce", + type: "uint256", + internalType: "uint256" + }, + { + name: "chainIdField", + type: "uint256", + internalType: "uint256" + }, + { + name: "chainIndex", + type: "uint256", + internalType: "uint256" + }, + { + name: "expires", + type: "uint32", + internalType: "uint32" + }, + { + name: "fillDeadline", + type: "uint32", + internalType: "uint32" + }, + { + name: "inputOracle", + type: "address", + internalType: "address" + }, + { + name: "inputs", + type: "uint256[2][]", + internalType: "uint256[2][]" + }, + { + name: "outputs", + type: "tuple[]", + internalType: "struct MandateOutput[]", + components: [ + { + name: "oracle", + type: "bytes32", + internalType: "bytes32" + }, + { + name: "settler", + type: "bytes32", + internalType: "bytes32" + }, + { + name: "chainId", + type: "uint256", + internalType: "uint256" + }, + { + name: "token", + type: "bytes32", + internalType: "bytes32" + }, + { + name: "amount", + type: "uint256", + internalType: "uint256" + }, + { + name: "recipient", + type: "bytes32", + internalType: "bytes32" + }, + { + name: "callbackData", + type: "bytes", + internalType: "bytes" + }, + { + name: "context", + type: "bytes", + internalType: "bytes" + } + ] + }, + { + name: "additionalChains", + type: "bytes32[]", + internalType: "bytes32[]" + } + ] + }, + { + name: "signatures", + type: "bytes", + internalType: "bytes" + }, + { + name: "solveParams", + type: "tuple[]", + internalType: "struct InputSettlerBase.SolveParams[]", + components: [ + { + name: "timestamp", + type: "uint32", + internalType: "uint32" + }, + { + name: "solver", + type: "bytes32", + internalType: "bytes32" + } + ] + }, + { + name: "destination", + type: "bytes32", + internalType: "bytes32" + }, + { + name: "call", + type: "bytes", + internalType: "bytes" + } + ], + outputs: [], + stateMutability: "nonpayable" + }, + { + type: "function", + name: "finaliseWithSignature", + inputs: [ + { + name: "order", + type: "tuple", + internalType: "struct MultichainOrderComponent", + components: [ + { + name: "user", + type: "address", + internalType: "address" + }, + { + name: "nonce", + type: "uint256", + internalType: "uint256" + }, + { + name: "chainIdField", + type: "uint256", + internalType: "uint256" + }, + { + name: "chainIndex", + type: "uint256", + internalType: "uint256" + }, + { + name: "expires", + type: "uint32", + internalType: "uint32" + }, + { + name: "fillDeadline", + type: "uint32", + internalType: "uint32" + }, + { + name: "inputOracle", + type: "address", + internalType: "address" + }, + { + name: "inputs", + type: "uint256[2][]", + internalType: "uint256[2][]" + }, + { + name: "outputs", + type: "tuple[]", + internalType: "struct MandateOutput[]", + components: [ + { + name: "oracle", + type: "bytes32", + internalType: "bytes32" + }, + { + name: "settler", + type: "bytes32", + internalType: "bytes32" + }, + { + name: "chainId", + type: "uint256", + internalType: "uint256" + }, + { + name: "token", + type: "bytes32", + internalType: "bytes32" + }, + { + name: "amount", + type: "uint256", + internalType: "uint256" + }, + { + name: "recipient", + type: "bytes32", + internalType: "bytes32" + }, + { + name: "callbackData", + type: "bytes", + internalType: "bytes" + }, + { + name: "context", + type: "bytes", + internalType: "bytes" + } + ] + }, + { + name: "additionalChains", + type: "bytes32[]", + internalType: "bytes32[]" + } + ] + }, + { + name: "signatures", + type: "bytes", + internalType: "bytes" + }, + { + name: "solveParams", + type: "tuple[]", + internalType: "struct InputSettlerBase.SolveParams[]", + components: [ + { + name: "timestamp", + type: "uint32", + internalType: "uint32" + }, + { + name: "solver", + type: "bytes32", + internalType: "bytes32" + } + ] + }, + { + name: "destination", + type: "bytes32", + internalType: "bytes32" + }, + { + name: "call", + type: "bytes", + internalType: "bytes" + }, + { + name: "orderOwnerSignature", + type: "bytes", + internalType: "bytes" + } + ], + outputs: [], + stateMutability: "nonpayable" + }, + { + type: "function", + name: "orderIdentifier", + inputs: [ + { + name: "order", + type: "tuple", + internalType: "struct MultichainOrderComponent", + components: [ + { + name: "user", + type: "address", + internalType: "address" + }, + { + name: "nonce", + type: "uint256", + internalType: "uint256" + }, + { + name: "chainIdField", + type: "uint256", + internalType: "uint256" + }, + { + name: "chainIndex", + type: "uint256", + internalType: "uint256" + }, + { + name: "expires", + type: "uint32", + internalType: "uint32" + }, + { + name: "fillDeadline", + type: "uint32", + internalType: "uint32" + }, + { + name: "inputOracle", + type: "address", + internalType: "address" + }, + { + name: "inputs", + type: "uint256[2][]", + internalType: "uint256[2][]" + }, + { + name: "outputs", + type: "tuple[]", + internalType: "struct MandateOutput[]", + components: [ + { + name: "oracle", + type: "bytes32", + internalType: "bytes32" + }, + { + name: "settler", + type: "bytes32", + internalType: "bytes32" + }, + { + name: "chainId", + type: "uint256", + internalType: "uint256" + }, + { + name: "token", + type: "bytes32", + internalType: "bytes32" + }, + { + name: "amount", + type: "uint256", + internalType: "uint256" + }, + { + name: "recipient", + type: "bytes32", + internalType: "bytes32" + }, + { + name: "callbackData", + type: "bytes", + internalType: "bytes" + }, + { + name: "context", + type: "bytes", + internalType: "bytes" + } + ] + }, + { + name: "additionalChains", + type: "bytes32[]", + internalType: "bytes32[]" + } + ] + } + ], + outputs: [ + { + name: "", + type: "bytes32", + internalType: "bytes32" + } + ], + stateMutability: "view" + }, + { + type: "event", + name: "EIP712DomainChanged", + inputs: [], + anonymous: false + }, + { + type: "event", + name: "Finalised", + inputs: [ + { + name: "orderId", + type: "bytes32", + indexed: true, + internalType: "bytes32" + }, + { + name: "solver", + type: "bytes32", + indexed: false, + internalType: "bytes32" + }, + { + name: "destination", + type: "bytes32", + indexed: false, + internalType: "bytes32" + } + ], + anonymous: false + }, + { + type: "error", + name: "CallOutOfRange", + inputs: [] + }, + { + type: "error", + name: "ContextOutOfRange", + inputs: [] + }, + { + type: "error", + name: "FillDeadlineAfterExpiry", + inputs: [ + { + name: "fillDeadline", + type: "uint32", + internalType: "uint32" + }, + { + name: "expires", + type: "uint32", + internalType: "uint32" + } + ] + }, + { + type: "error", + name: "FilledTooLate", + inputs: [ + { + name: "expected", + type: "uint32", + internalType: "uint32" + }, + { + name: "actual", + type: "uint32", + internalType: "uint32" + } + ] + }, + { + type: "error", + name: "InvalidShortString", + inputs: [] + }, + { + type: "error", + name: "InvalidSigner", + inputs: [] + }, + { + type: "error", + name: "InvalidTimestampLength", + inputs: [] + }, + { + type: "error", + name: "NoDestination", + inputs: [] + }, + { + type: "error", + name: "StringTooLong", + inputs: [ + { + name: "str", + type: "string", + internalType: "string" + } + ] + }, + { + type: "error", + name: "TimestampNotPassed", + inputs: [] + }, + { + type: "error", + name: "TimestampPassed", + inputs: [] + }, + { + type: "error", + name: "UnexpectedCaller", + inputs: [ + { + name: "expectedCaller", + type: "bytes32", + internalType: "bytes32" + } + ] + }, + { + type: "error", + name: "UserCannotBeSettler", + inputs: [] + }, + { + type: "error", + name: "WrongChain", + inputs: [ + { + name: "expected", + type: "uint256", + internalType: "uint256" + }, + { + name: "actual", + type: "uint256", + internalType: "uint256" + } + ] + } +] as const; diff --git a/src/lib/components/InputTokenModal.svelte b/src/lib/components/InputTokenModal.svelte index 4af8413..51bbe2e 100644 --- a/src/lib/components/InputTokenModal.svelte +++ b/src/lib/components/InputTokenModal.svelte @@ -55,9 +55,6 @@ if (inputTokens.length === 0) { inputTokens.push({ token: tokenSet[0], amount: 0n }); } - console.log({ - inputTokens - }); currentInputTokens = inputTokens; active = false; diff --git a/src/lib/libraries/intent.ts b/src/lib/libraries/intent.ts index e5099f1..9e3838a 100644 --- a/src/lib/libraries/intent.ts +++ b/src/lib/libraries/intent.ts @@ -1,16 +1,20 @@ import { + concat, encodeAbiParameters, encodePacked, + getTypesForEIP712Domain, + hashDomain, hashStruct, hashTypedData, keccak256, - parseAbiParameters, - toHex + parseAbiParameters } from "viem"; import type { BatchCompact, CompactMandate, + Element, MandateOutput, + MultichainCompact, MultichainOrder, MultichainOrderComponent, NoSignature, @@ -21,8 +25,10 @@ import type { import { COMPACT_ABI } from "../abi/compact"; import { chainMap, + clients, COIN_FILLER, COMPACT, + getChainName, getOracle, INPUT_SETTLER_COMPACT_LIFI, INPUT_SETTLER_ESCROW_LIFI, @@ -40,6 +46,8 @@ import { SETTLER_ESCROW_ABI } from "../abi/escrow"; import type { TokenContext } from "$lib/state.svelte"; import { MULTICHAIN_SETTLER_ESCROW_ABI } from "$lib/abi/multichain_escrow"; import { SETTLER_COMPACT_ABI } from "$lib/abi/settlercompact"; +import { toHex } from "$lib/utils/interopableAddresses"; +import { MULTICHAIN_SETTLER_COMPACT_ABI } from "$lib/abi/multichain_compact"; type Lock = { lockTag: `0x${string}`; @@ -150,10 +158,12 @@ export class Intent { inputSettler(multichain: boolean) { if (this.lock.type === "compact" && multichain === false) return INPUT_SETTLER_COMPACT_LIFI; + if (this.lock.type === "compact" && multichain === true) + return MULTICHAIN_INPUT_SETTLER_COMPACT; if (this.lock.type === "escrow" && multichain === false) return INPUT_SETTLER_ESCROW_LIFI; if (this.lock.type === "escrow" && multichain === true) return MULTICHAIN_INPUT_SETTLER_ESCROW; - throw new Error(`Not supported ${multichain}, ${this.lock}`); + throw new Error(`Not supported | multichain: ${multichain}, type: ${this.lock.type}`); } encodeOutputs(currentTime: number) { @@ -240,7 +250,12 @@ export class Intent { return { chainId: BigInt(chainMap[chain].id), - inputs: chainInputs.map(({ token, amount }) => [BigInt(token.address), amount]) + inputs: chainInputs.map(({ token, amount }) => [ + this.lock.type === "compact" + ? toId(true, this.lock.resetPeriod, this.lock.allocatorId, token.address) + : BigInt(token.address), + amount + ]) }; }); @@ -330,12 +345,8 @@ export class StandardOrderIntent { outputs: order.outputs }; const commitments = order.inputs.map(([tokenId, amount]) => { - const lockTag: `0x${string}` = `0x${toHex(tokenId) - .replace("0x", "") - .slice(0, 12 * 2)}`; - const token: `0x${string}` = `0x${toHex(tokenId) - .replace("0x", "") - .slice(12 * 2, 32 * 2)}`; + const lockTag: `0x${string}` = `0x${toHex(tokenId, 32).slice(0, 12 * 2)}`; + const token: `0x${string}` = `0x${toHex(tokenId, 32).slice(12 * 2, 32 * 2)}`; return { lockTag, token, @@ -543,17 +554,51 @@ export class MultichainOrderIntent { // We need a random order components. const components = this.asComponents(); const computedOrderIds = components.map((c) => - MultichainOrderIntent.escrowOrderId(this.inputSettler, c.orderComponent) + this.lock?.type === "escrow" + ? MultichainOrderIntent.escrowOrderId(this.inputSettler, c.orderComponent, c.chainId) + : MultichainOrderIntent.compactOrderid(this.inputSettler, c.orderComponent, c.chainId) ); const orderId = computedOrderIds[0]; computedOrderIds.map((v) => { if (v !== orderId) throw new Error(`Order ids are not equal ${computedOrderIds}`); }); + if (this.lock?.type === "compact") { + const multichainCompactHash = hashStruct({ + data: this.asMultichainBatchCompact(), + types: compactTypes, + primaryType: "MultichainCompact" + }); + if (multichainCompactHash !== orderId) + throw new Error( + `MultichainCompact does not match orderId, ${multichainCompactHash} ${orderId}` + ); + } return orderId; } - static escrowOrderId(inputSettler: `0x${string}`, orderComponent: MultichainOrderComponent) { + async orderIdCheck() { + const components = this.asComponents(); + const computedOrderId = this.orderId(); + const onChainOrderIds = await Promise.all( + components.map(async (component) => { + const onChainId = await clients[getChainName(component.chainId)].readContract({ + address: this.inputSettler, + abi: MULTICHAIN_SETTLER_COMPACT_ABI, + functionName: "orderIdentifier", + args: [component.orderComponent] + }); + return onChainId; + }) + ); + console.log({ computedOrderId, onChainOrderIds }); + } + + static escrowOrderId( + inputSettler: `0x${string}`, + orderComponent: MultichainOrderComponent, + _: bigint + ) { return keccak256( encodePacked( ["address", "address", "uint256", "uint32", "uint32", "address", "bytes32", "bytes"], @@ -576,6 +621,58 @@ export class MultichainOrderIntent { ); } + static compactOrderid( + inputSettler: `0x${string}`, + orderComponent: MultichainOrderComponent, + chainId: bigint + ) { + const MULTICHAIN_COMPACT_TYPEHASH_WITH_WITNESS = keccak256( + encodePacked( + ["string"], + [ + "MultichainCompact(address sponsor,uint256 nonce,uint256 expires,Element[] elements)Element(address arbiter,uint256 chainId,Lock[] commitments,Mandate mandate)Lock(bytes12 lockTag,address token,uint256 amount)Mandate(uint32 fillDeadline,address inputOracle,MandateOutput[] outputs)MandateOutput(bytes32 oracle,bytes32 settler,uint256 chainId,bytes32 token,uint256 amount,bytes32 recipient,bytes callbackData,bytes context)" + ] + ) + ); + const { fillDeadline, inputOracle, outputs, inputs } = orderComponent; + const mandate: CompactMandate = { + fillDeadline, + inputOracle, + outputs + }; + const element: Element = { + arbiter: inputSettler, + chainId: chainId, + commitments: MultichainOrderIntent.inputsToLocks(inputs), + mandate + }; + + const elementHash = hashStruct({ + types: compactTypes, + primaryType: "Element", + data: element + }); + + const elementHashes = [ + ...orderComponent.additionalChains.slice(0, Number(orderComponent.chainIndex)), + elementHash, + ...orderComponent.additionalChains.slice(Number(orderComponent.chainIndex)) + ]; + + return keccak256( + encodeAbiParameters( + parseAbiParameters(["bytes32", "address", "uint256", "uint256", "bytes32"]), + [ + MULTICHAIN_COMPACT_TYPEHASH_WITH_WITNESS, + orderComponent.user, + orderComponent.nonce, + BigInt(orderComponent.expires), + keccak256(encodePacked(["bytes32[]"], [elementHashes])) + ] + ) + ); + } + static hashInputs(chainId: bigint, inputs: [bigint, bigint][]) { return keccak256(encodePacked(["uint256", "uint256[2][]"], [chainId, inputs])); } @@ -602,7 +699,7 @@ export class MultichainOrderIntent { static inputsToLocks(inputs: [bigint, bigint][]): Lock[] { return inputs.map((input) => { - const bytes32 = toHex(input[0]).replace("0x", ""); + const bytes32 = toHex(input[0], 32); return { lockTag: `0x${bytes32.slice(0, 12 * 2)}`, token: `0x${bytes32.slice(12 * 2, 32 * 2)}`, @@ -623,30 +720,33 @@ export class MultichainOrderIntent { }); } - secondariesCompact(): { chainIdField: bigint; additionalChains: `0x${string}`[] }[] { + asCompactElements() { const { fillDeadline, inputOracle, outputs, inputs } = this.order; const mandate: CompactMandate = { fillDeadline, inputOracle, outputs }; - const elements = inputs.map((inputs) => { - const element: { - arbiter: `0x${string}`; - chainId: bigint; - commitments: Lock[]; - mandate: CompactMandate; - } = { + return inputs.map((inputs) => { + const element: Element = { arbiter: this.inputSettler, chainId: inputs.chainId, commitments: MultichainOrderIntent.inputsToLocks(inputs.inputs), mandate }; - return hashTypedData({ + return element; + }); + } + + secondariesCompact(): { chainIdField: bigint; additionalChains: `0x${string}`[] }[] { + const { inputs } = this.order; + const elements = this.asCompactElements().map((element) => { + const hash = hashStruct({ types: compactTypes, primaryType: "Element", - message: element + data: element }); + return hash; }); return inputs.map((_, i) => { return { @@ -682,6 +782,66 @@ export class MultichainOrderIntent { return components; } + // -- Compact Helpers -- // + + asMultichainBatchCompact(): MultichainCompact { + const { order } = this; + const mandate: CompactMandate = { + fillDeadline: order.fillDeadline, + inputOracle: order.inputOracle, + outputs: order.outputs + }; + const result = { + sponsor: order.user, + nonce: order.nonce, + expires: BigInt(order.expires), + elements: this.asCompactElements(), + mandate + }; + return result; + } + + compactClaimHash(): `0x${string}` { + const claimHash = hashStruct({ + data: this.asMultichainBatchCompact(), + types: compactTypes, + primaryType: "MultichainCompact" + }); + return claimHash; + } + + signCompact(account: `0x${string}`, walletClient: WC): Promise<`0x${string}`> { + this.selfTest(); + const chainId = this.order.inputs[0].chainId; + console.log("chainId", chainId); + console.log( + "hash", + hashTypedData({ + domain: { + name: "The Compact", + version: "1", + chainId, + verifyingContract: COMPACT + } as const, + types: compactTypes, + primaryType: "MultichainCompact", + message: this.asMultichainBatchCompact() + }) + ); + return walletClient.signTypedData({ + account, + domain: { + name: "The Compact", + version: "1", + chainId, + verifyingContract: COMPACT + } as const, + types: compactTypes, + primaryType: "MultichainCompact", + message: this.asMultichainBatchCompact() + }); + } + // This code is depreciated and needs to be updated. async openEscrow(account: `0x${string}`, walletClient: WC) { this.selfTest(); @@ -715,6 +875,7 @@ export class MultichainOrderIntent { allocatorSignature: Signature | NoSignature; }; }) { + this.asMultichainBatchCompact(); const { sourceChain, account, walletClient, solveParams, signatures } = options; const actionChain = chainMap[sourceChain]; if (actionChain.id in this.inputChains().map((v) => Number(v))) @@ -724,8 +885,7 @@ export class MultichainOrderIntent { // Get all components for our chain. const components = this.asComponents().filter((c) => c.chainId === BigInt(actionChain.id)); - console.log({ a: this.inputSettler, b: MULTICHAIN_INPUT_SETTLER_ESCROW }); - for (const { orderComponent } of components) { + for (const { orderComponent, chainId } of components) { if (this.inputSettler.toLowerCase() === MULTICHAIN_INPUT_SETTLER_ESCROW.toLowerCase()) { return await walletClient.writeContract({ chain: actionChain, @@ -735,6 +895,28 @@ export class MultichainOrderIntent { functionName: "finalise", args: [orderComponent, solveParams, addressToBytes32(account), "0x"] }); + } else if ( + this.inputSettler.toLowerCase() === MULTICHAIN_INPUT_SETTLER_COMPACT.toLowerCase() + ) { + const { sponsorSignature, allocatorSignature } = signatures; + console.log({ + orderComponent, + sponsorSignature, + allocatorSignature + }); + + const combinedSignatures = encodeAbiParameters(parseAbiParameters(["bytes", "bytes"]), [ + sponsorSignature.payload ?? "0x", + allocatorSignature.payload + ]); + return await walletClient.writeContract({ + chain: actionChain, + account: account, + address: this.inputSettler, + abi: MULTICHAIN_SETTLER_COMPACT_ABI, + functionName: "finalise", + args: [orderComponent, combinedSignatures, solveParams, addressToBytes32(account), "0x"] + }); } else { throw new Error(`Could not detect settler type ${this.inputSettler}`); } diff --git a/src/lib/libraries/intentFactory.ts b/src/lib/libraries/intentFactory.ts index e6a352e..900ea0c 100644 --- a/src/lib/libraries/intentFactory.ts +++ b/src/lib/libraries/intentFactory.ts @@ -81,24 +81,32 @@ export class IntentFactory { const { account, inputTokens } = opts; const inputChain = inputTokens[0].token.chain; if (this.preHook) await this.preHook(inputChain); - const intent = new Intent(opts).singlechain(); + const intent = new Intent(opts).order(); const sponsorSignature = await intent.signCompact(account(), this.walletClient); console.log({ - order: intent.asStandardOrder(), - batchCompact: intent.asBatchCompact(), + order: intent.asOrder(), sponsorSignature }); - const signedOrder = await this.orderServer.submitOrder({ - orderType: "CatalystCompactOrder", - order: intent.asStandardOrder(), - inputSettler: INPUT_SETTLER_COMPACT_LIFI, - sponsorSignature, - allocatorSignature: "0x" + this.saveOrder({ + order: intent.asOrder(), + inputSettler: intent.inputSettler, + sponsorSignature: { + type: "ECDSA", + payload: sponsorSignature + } }); - console.log("signedOrder", signedOrder); + + // const signedOrder = await this.orderServer.submitOrder({ + // orderType: "CatalystCompactOrder", + // order: intent.asStandardOrder(), + // inputSettler: INPUT_SETTLER_COMPACT_LIFI, + // sponsorSignature, + // allocatorSignature: "0x" + // }); + // console.log("signedOrder", signedOrder); if (this.postHook) await this.postHook(); }; diff --git a/src/lib/libraries/solver.ts b/src/lib/libraries/solver.ts index adb9657..aad4ab2 100644 --- a/src/lib/libraries/solver.ts +++ b/src/lib/libraries/solver.ts @@ -50,8 +50,7 @@ export class Solver { outputs } = args; const publicClients = clients; - // TODO: MULTICHAIN COMPACT fix escrow typing - const orderId = orderToIntent({ order, inputSettler, lock: { type: "escrow" } }).orderId(); + const orderId = orderToIntent({ order, inputSettler }).orderId(); const outputChain = getChainName(outputs[0].chainId); console.log({ outputChain }); @@ -234,8 +233,7 @@ export class Solver { const { order, inputSettler } = orderContainer; const intent = orderToIntent({ inputSettler, - order, - lock: { type: inputSettler === INPUT_SETTLER_COMPACT_LIFI ? "compact" : "escrow" } + order }); const outputChain = getChainName(order.outputs[0].chainId); diff --git a/src/lib/screens/FillIntent.svelte b/src/lib/screens/FillIntent.svelte index a9e2e45..91cffe2 100644 --- a/src/lib/screens/FillIntent.svelte +++ b/src/lib/screens/FillIntent.svelte @@ -49,7 +49,6 @@ functionName: "getFillRecord", args: [orderId, outputHash] }); - console.log({ orderId, output, result, outputHash }); return result; } diff --git a/src/lib/screens/IssueIntent.svelte b/src/lib/screens/IssueIntent.svelte index c77231e..e81eb4d 100644 --- a/src/lib/screens/IssueIntent.svelte +++ b/src/lib/screens/IssueIntent.svelte @@ -120,21 +120,15 @@ decimals: number; }[] = []; // Get all unqiue tokens. - $inspect(store.inputTokens); const allUniqueNames = [ ...new Set( store.inputTokens.map((v) => { - console.log("v", v); return v.token.name; }) ) ]; for (let i = 0; i < allUniqueNames.length; ++i) { const name = allUniqueNames[i]; - console.log({ - name, - found: store.inputTokens.map((v, i) => (v.token.name == name ? v.amount : 0n)) - }); inputs[i] = { name, amount: bigIntSum( @@ -295,7 +289,7 @@ Low Compact Balance {:else} - + {#snippet name()} Sign Order {/snippet} diff --git a/src/lib/screens/ManageDeposit.svelte b/src/lib/screens/ManageDeposit.svelte index 40dad78..48caaab 100644 --- a/src/lib/screens/ManageDeposit.svelte +++ b/src/lib/screens/ManageDeposit.svelte @@ -35,7 +35,6 @@ let manageAssetAction: "deposit" | "withdraw" = $state("deposit"); let inputNumber = $state(1); - let token = $state(coinList(store.mainnet)[0]); let allowance = $state(0n); const inputAmount = $derived(toBigIntWithDecimals(inputNumber, token.decimals)); @@ -49,6 +48,9 @@ allowance = a; }); }); + + let selectedTokenIndex = $state(0); + const token = $derived(coinList(store.mainnet)[selectedTokenIndex]);
@@ -79,16 +81,16 @@

Input Type

- +
diff --git a/src/lib/state.svelte.ts b/src/lib/state.svelte.ts index d41bb48..2e4af57 100644 --- a/src/lib/state.svelte.ts +++ b/src/lib/state.svelte.ts @@ -133,7 +133,6 @@ class Store { for (const token of coinList(isMainnet)) { // Check whether we have me the chain before. if (!resolved[token.chain as chain]) resolved[token.chain] = {}; - resolved[token.chain][token.address] = func( this.connectedAccount?.address, token.address, diff --git a/src/lib/utils/interopableAddresses.ts b/src/lib/utils/interopableAddresses.ts index 43ba9dc..0ff6e4b 100644 --- a/src/lib/utils/interopableAddresses.ts +++ b/src/lib/utils/interopableAddresses.ts @@ -2,8 +2,13 @@ function padEven(s: string, minimal = 2, pad: string = "0") { return s.padStart(((Math.max(s.length + 1, minimal) / 2) | 0) * 2, pad); } -function toHex(num: number | bigint, bytes: number = 1) { - return padEven(num.toString(16), bytes * 2); +export function toHex( + num: number | bigint, + bytes: number = 1, + prefix?: T +): `${T}${string}` { + const p = (prefix ?? "") as T; + return `${p}${padEven(num.toString(16), bytes * 2)}` as `${T}${string}`; } type Version = "0001"; diff --git a/src/lib/utils/typedMessage.ts b/src/lib/utils/typedMessage.ts index 9de506d..d1f1ea0 100644 --- a/src/lib/utils/typedMessage.ts +++ b/src/lib/utils/typedMessage.ts @@ -46,17 +46,6 @@ export const MandateOutput = [ { name: "context", type: "bytes" } ] as const; -export const StandardOrderAbi = [ - { name: "user", type: "address" }, - { name: "nonce", type: "uint256" }, - { name: "originChainId", type: "uint256" }, - { name: "expires", type: "uint32" }, - { name: "fillDeadline", type: "uint32" }, - { name: "inputOracle", type: "address" }, - { name: "inputs", type: "uint256[2][]" }, - { name: "outputs", type: "tuple[]", components: MandateOutput } -] as const; - // The named list of all type definitions export const compactTypes = { BatchCompact, @@ -77,3 +66,14 @@ if (compact_type_hash != compact_type_hash_contract) { `Computed typehash ${compact_type_hash} does not match expected ${compact_type_hash_contract}` ); } + +const multichain_compact_type = + "MultichainCompact(address sponsor,uint256 nonce,uint256 expires,Element[] elements)Element(address arbiter,uint256 chainId,Lock[] commitments,Mandate mandate)Lock(bytes12 lockTag,address token,uint256 amount)Mandate(uint32 fillDeadline,address inputOracle,MandateOutput[] outputs)MandateOutput(bytes32 oracle,bytes32 settler,uint256 chainId,bytes32 token,uint256 amount,bytes32 recipient,bytes callbackData,bytes context)" as const; +export const multichain_compact_type_hash = keccak256(toHex(multichain_compact_type)); +const multichain_compact_type_hash_contract = + "0x6bc0624272798c7a3ff97563d8a009ea96cffd8ea74a971b2946ca790fc50319"; +if (multichain_compact_type_hash != multichain_compact_type_hash_contract) { + throw Error( + `Computed multichain typehash ${multichain_compact_type_hash} does not match expected ${multichain_compact_type_hash_contract}` + ); +} diff --git a/src/types/index.ts b/src/types/index.ts index 64bfab3..bf774de 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -39,6 +39,20 @@ export type BatchCompact = { mandate: CompactMandate; }; +export type Element = { + arbiter: `0x${string}`; + chainId: bigint; + commitments: Lock[]; + mandate: CompactMandate; +}; +export type MultichainCompact = { + sponsor: `0x${string}`; // The account tasked with verifying and submitting the claim. + nonce: bigint; // A parameter to enforce replay protection, scoped to allocator. + expires: bigint; // The time at which the claim expires. + elements: Element[]; + mandate: CompactMandate; +}; + export type StandardOrder = { user: `0x${string}`; nonce: bigint; From 6a279861bbc08814636d976d5d61c34f1ceceace Mon Sep 17 00:00:00 2001 From: Alexander Date: Tue, 11 Nov 2025 23:00:59 +0100 Subject: [PATCH 13/17] Remove debugging --- src/lib/libraries/intent.ts | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/src/lib/libraries/intent.ts b/src/lib/libraries/intent.ts index 9e3838a..0dd0f2b 100644 --- a/src/lib/libraries/intent.ts +++ b/src/lib/libraries/intent.ts @@ -813,21 +813,6 @@ export class MultichainOrderIntent { signCompact(account: `0x${string}`, walletClient: WC): Promise<`0x${string}`> { this.selfTest(); const chainId = this.order.inputs[0].chainId; - console.log("chainId", chainId); - console.log( - "hash", - hashTypedData({ - domain: { - name: "The Compact", - version: "1", - chainId, - verifyingContract: COMPACT - } as const, - types: compactTypes, - primaryType: "MultichainCompact", - message: this.asMultichainBatchCompact() - }) - ); return walletClient.signTypedData({ account, domain: { From 9212f58994dd543d7d64055a11b2190717c3b867 Mon Sep 17 00:00:00 2001 From: Alexander Date: Wed, 12 Nov 2025 11:53:53 +0100 Subject: [PATCH 14/17] Fix escrow and a minor balance issue --- src/lib/components/InputTokenModal.svelte | 2 +- src/lib/libraries/intent.ts | 4 +-- src/lib/screens/Finalise.svelte | 39 +++++++++++++---------- 3 files changed, 26 insertions(+), 19 deletions(-) diff --git a/src/lib/components/InputTokenModal.svelte b/src/lib/components/InputTokenModal.svelte index 51bbe2e..e2661d0 100644 --- a/src/lib/components/InputTokenModal.svelte +++ b/src/lib/components/InputTokenModal.svelte @@ -83,7 +83,7 @@ const tokens = tokenSet; const balancePromises = tokenSet.map( (tkn) => - (store.intentType === "escrow" ? store.compactBalances : store.balances)[tkn.chain][ + (store.intentType === "compact" ? store.compactBalances : store.balances)[tkn.chain][ tkn.address ] ); diff --git a/src/lib/libraries/intent.ts b/src/lib/libraries/intent.ts index 0dd0f2b..03231e2 100644 --- a/src/lib/libraries/intent.ts +++ b/src/lib/libraries/intent.ts @@ -404,13 +404,13 @@ export class StandardOrderIntent { * @param walletClient Wallet client for sending the call to. * @returns transactionHash for the on-chain call. */ - openEscrow(account: `0x${string}`, walletClient: WC): [Promise<`0x${string}`>] { + async openEscrow(account: `0x${string}`, walletClient: WC): Promise<[`0x${string}`]> { const chain = findChain(this.order.originChainId); walletClient.switchChain({ id: Number(this.order.originChainId) }); if (!chain) throw new Error("Chain not found for chainId " + this.order.originChainId.toString()); return [ - walletClient.writeContract({ + await walletClient.writeContract({ chain, account, address: INPUT_SETTLER_ESCROW_LIFI, diff --git a/src/lib/screens/Finalise.svelte b/src/lib/screens/Finalise.svelte index 403d8f2..bb168fe 100644 --- a/src/lib/screens/Finalise.svelte +++ b/src/lib/screens/Finalise.svelte @@ -36,6 +36,12 @@ account: () => `0x${string}`; } = $props(); + let refreshClaimed = $state(0); + const postHookRefreshValidate = async () => { + if (postHook) await postHook(); + refreshClaimed += 1; + }; + // Order status enum const OrderStatus_None = 0; const OrderStatus_Deposited = 1; @@ -66,20 +72,21 @@ inputSettler === MULTICHAIN_INPUT_SETTLER_COMPACT ) { // Check claim status - return false; - // const [token, allocator, resetPeriod, scope] = await inputChainClient.readContract({ - // address: COMPACT, - // abi: COMPACT_ABI, - // functionName: "getLockDetails", - // args: [order.inputs[0][0]] - // }); - // // Check if nonce is spent. - // return await inputChainClient.readContract({ - // address: COMPACT, - // abi: COMPACT_ABI, - // functionName: "hasConsumedAllocatorNonce", - // args: [order.nonce, allocator] - // }); + const flattenedInputs = "originChainId" in order ? order.inputs : order.inputs[0].inputs; + + const [token, allocator, resetPeriod, scope] = await inputChainClient.readContract({ + address: COMPACT, + abi: COMPACT_ABI, + functionName: "getLockDetails", + args: [flattenedInputs[0][0]] + }); + // Check if nonce is spent. + return await inputChainClient.readContract({ + address: COMPACT, + abi: COMPACT_ABI, + functionName: "hasConsumedAllocatorNonce", + args: [order.nonce, allocator] + }); } } @@ -132,7 +139,7 @@ { account, preHook, - postHook + postHook: postHookRefreshValidate } )} > @@ -165,7 +172,7 @@ { account, preHook, - postHook + postHook: postHookRefreshValidate } )} > From b3c18163cf168514b27f9d66140b50ca5e476fb3 Mon Sep 17 00:00:00 2001 From: Alexander Date: Wed, 12 Nov 2025 21:41:31 +0100 Subject: [PATCH 15/17] Add arbitrum sepolia & remove broken register intent compact & set alwaysYesAllocator as default --- src/lib/config.ts | 21 ++++++++++++++++++++- src/lib/screens/IssueIntent.svelte | 9 --------- src/lib/state.svelte.ts | 3 ++- 3 files changed, 22 insertions(+), 11 deletions(-) diff --git a/src/lib/config.ts b/src/lib/config.ts index 754ae1b..3efc4ad 100644 --- a/src/lib/config.ts +++ b/src/lib/config.ts @@ -48,6 +48,7 @@ export const chainMap = { base, arbitrum, sepolia, + arbitrumSepolia, optimismSepolia, baseSepolia } as const; @@ -56,7 +57,7 @@ export type chain = (typeof chains)[number]; export const chainList = (mainnet: boolean) => { if (mainnet == true) { return ["ethereum", "base", "arbitrum"]; - } else return ["sepolia", "optimismSepolia", "baseSepolia"]; + } else return ["sepolia", "optimismSepolia", "baseSepolia", "arbitrumSepolia"]; }; export type balanceQuery = Record>>; @@ -146,6 +147,12 @@ export const coinList = (mainnet: boolean) => { chain: "sepolia", decimals: 6 }, + { + address: "0x75faf114eafb1BDbe2F0316DF893fd58CE46AA4d", + name: "usdc", + chain: "arbitrumSepolia", + decimals: 6 + }, { address: ADDRESS_ZERO, name: "eth", @@ -164,6 +171,12 @@ export const coinList = (mainnet: boolean) => { chain: "optimismSepolia", decimals: 18 }, + { + address: ADDRESS_ZERO, + name: "eth", + chain: "arbitrumSepolia", + decimals: 6 + }, { address: `0xfFf9976782d46CC05630D1f6eBAb18b2324d6B14`, name: "weth", @@ -181,6 +194,12 @@ export const coinList = (mainnet: boolean) => { name: "weth", chain: "optimismSepolia", decimals: 18 + }, + { + address: `0x980B62Da83eFf3D4576C647993b0c1D7faf17c73`, + name: "weth", + chain: "arbitrumSepolia", + decimals: 18 } ] as const; }; diff --git a/src/lib/screens/IssueIntent.svelte b/src/lib/screens/IssueIntent.svelte index e81eb4d..a8cb25b 100644 --- a/src/lib/screens/IssueIntent.svelte +++ b/src/lib/screens/IssueIntent.svelte @@ -269,15 +269,6 @@ Waiting for transaction... {/snippet}
- {:else if store.intentType === "compact"} - - {#snippet name()} - Execute Deposit and Open - {/snippet} - {#snippet awaiting()} - Waiting for transaction... - {/snippet} - {/if} {#if store.intentType === "compact" && store.allocatorId !== POLYMER_ALLOCATOR} {#if !balanceCheckCompact} diff --git a/src/lib/state.svelte.ts b/src/lib/state.svelte.ts index 2e4af57..ebe27be 100644 --- a/src/lib/state.svelte.ts +++ b/src/lib/state.svelte.ts @@ -1,6 +1,7 @@ import type { WalletState } from "@web3-onboard/core"; import type { OrderContainer } from "../types"; import { + ALWAYS_OK_ALLOCATOR, chainMap, clients, coinList, @@ -93,7 +94,7 @@ class Store { return INPUT_SETTLER_ESCROW_LIFI; }); intentType = $state<"escrow" | "compact">("escrow"); - allocatorId = $state(POLYMER_ALLOCATOR); + allocatorId = $state(ALWAYS_OK_ALLOCATOR); // --- Oracle --- // verifier = $state("polymer"); From 2e8e5976e1b297e617e2c98ae633a0e0570a9ee7 Mon Sep 17 00:00:00 2001 From: Alexander Date: Tue, 6 Jan 2026 15:38:24 +0400 Subject: [PATCH 16/17] Add support for same chain swaps --- src/lib/libraries/intent.ts | 22 ++++++++- src/lib/libraries/solver.ts | 72 +++++++++++++++++++----------- src/lib/screens/IssueIntent.svelte | 32 ++++++++++--- 3 files changed, 92 insertions(+), 34 deletions(-) diff --git a/src/lib/libraries/intent.ts b/src/lib/libraries/intent.ts index 03231e2..d2d21c5 100644 --- a/src/lib/libraries/intent.ts +++ b/src/lib/libraries/intent.ts @@ -150,6 +150,20 @@ export class Intent { return this.numInputChains() > 1; } + isSameChain() { + // Multichain intents cannot be same chain. Normal "Output oracle" will be used. + if (this.isMultichain()) return false; + + // Only 1 input chain is used. + const inputChain = this.inputs[0].token.chain; + const outputChains = this.outputs.map((o) => o.token.chain); + const numOutputChains = [...new Set(outputChains)].length; + if (numOutputChains > 1) return false; + // Only 1 output chain is used. + const outputChain = this.outputs[0].token.chain; + return inputChain === outputChain; + } + nonce() { if (this._nonce) return this._nonce; this._nonce = BigInt(Math.floor(Math.random() * 2 ** 32)); @@ -190,10 +204,14 @@ export class Intent { } const outputSettler = COIN_FILLER; + const sameChain = this.isSameChain(); return this.outputs.map(({ token, amount }) => { + const outputOracle = sameChain + ? addressToBytes32(outputSettler) + : addressToBytes32(getOracle(this.verifier, token.chain)!); return { - oracle: addressToBytes32(getOracle(this.verifier, token.chain)!), + oracle: outputOracle, settler: addressToBytes32(outputSettler), chainId: BigInt(chainMap[token.chain].id), token: addressToBytes32(token.address), @@ -220,7 +238,7 @@ export class Intent { const currentTime = Math.floor(Date.now() / 1000); - const inputOracle = getOracle(this.verifier, inputChain)!; + const inputOracle = this.isSameChain() ? COIN_FILLER : getOracle(this.verifier, inputChain)!; const order: StandardOrder = { user: this.user(), diff --git a/src/lib/libraries/solver.ts b/src/lib/libraries/solver.ts index aad4ab2..66beb35 100644 --- a/src/lib/libraries/solver.ts +++ b/src/lib/libraries/solver.ts @@ -3,6 +3,7 @@ import { type chain, chainMap, clients, + COIN_FILLER, getChainName, getOracle, INPUT_SETTLER_COMPACT_LIFI, @@ -135,39 +136,40 @@ export class Solver { } = args; const outputChain = getChainName(order.outputs[0].chainId); - if (order.inputOracle === getOracle("polymer", sourceChain)) { - const transactionReceipt = await clients[outputChain].getTransactionReceipt({ - hash: fillTransactionHash as `0x${string}` - }); + // Get the output filled event. + const transactionReceipt = await clients[outputChain].getTransactionReceipt({ + hash: fillTransactionHash as `0x${string}` + }); - const logs = parseEventLogs({ - abi: COIN_FILLER_ABI, - eventName: "OutputFilled", - logs: transactionReceipt.logs - }); - // We need to search through each log until we find one matching our output. - console.log("logs", logs); - let logIndex = -1; - const expectedOutputHash = hashStruct({ + const logs = parseEventLogs({ + abi: COIN_FILLER_ABI, + eventName: "OutputFilled", + logs: transactionReceipt.logs + }); + // We need to search through each log until we find one matching our output. + console.log("logs", logs); + let logIndex = -1; + const expectedOutputHash = hashStruct({ + types: compactTypes, + primaryType: "MandateOutput", + data: output + }); + for (const log of logs) { + const logOutput = log.args.output; + // TODO: Optimise by comparing the dicts. + const logOutputHash = hashStruct({ types: compactTypes, primaryType: "MandateOutput", - data: output + data: logOutput }); - for (const log of logs) { - const logOutput = log.args.output; - // TODO: Optimise by comparing the dicts. - const logOutputHash = hashStruct({ - types: compactTypes, - primaryType: "MandateOutput", - data: logOutput - }); - if (logOutputHash === expectedOutputHash) { - logIndex = log.logIndex; - break; - } + if (logOutputHash === expectedOutputHash) { + logIndex = log.logIndex; + break; } - if (logIndex === -1) throw Error(`Could not find matching log`); + } + if (logIndex === -1) throw Error(`Could not find matching log`); + if (order.inputOracle === getOracle("polymer", sourceChain)) { let proof: string | undefined; let polymerIndex: number | undefined; for (let i = 0; i < 5; ++i) { @@ -210,6 +212,22 @@ export class Solver { if (postHook) await postHook(); return result; } + } else if (order.inputOracle === COIN_FILLER) { + const log = logs.find((log) => log.logIndex === logIndex)!; + const transcationHash = await walletClient.writeContract({ + chain: chainMap[sourceChain], + account: account(), + address: order.inputOracle, + abi: COIN_FILLER_ABI, + functionName: "setAttestation", + args: [log.args.orderId, log.args.solver, log.args.timestamp, log.args.output] + }); + + const result = await clients[sourceChain].waitForTransactionReceipt({ + hash: transcationHash + }); + if (postHook) await postHook(); + return result; } }; } diff --git a/src/lib/screens/IssueIntent.svelte b/src/lib/screens/IssueIntent.svelte index a8cb25b..051f860 100644 --- a/src/lib/screens/IssueIntent.svelte +++ b/src/lib/screens/IssueIntent.svelte @@ -145,6 +145,18 @@ const uniqueChains = [...new Set(tokenChains)]; return uniqueChains.length; }); + + const sameChain = $derived.by(() => { + if (numInputChains > 1) return false; + + // Only 1 input chain is used. + const inputChain = store.inputTokens[0].token.chain; + const outputChains = store.outputTokens.map((o) => o.token.chain); + const numOutputChains = [...new Set(outputChains)].length; + if (numOutputChains > 1) return false; + const outputChain = outputChains[0]; + return inputChain === outputChain; + });
@@ -184,6 +196,9 @@ {#if numInputChains > 1}
Multichain!
{/if} + {#if sameChain} +
SameChain!
+ {/if}
@@ -223,11 +238,18 @@ >
- Verified by - + {#if sameChain} + Verified by + + {:else} + Verified by + + {/if}
Exclusive For From 2af26165f7adf9265f2bada8e9ac23467afd62a4 Mon Sep 17 00:00:00 2001 From: Alexander Date: Tue, 6 Jan 2026 16:01:52 +0400 Subject: [PATCH 17/17] Fix introduction and cleanup --- _typos.toml | 3 +++ src/lib/components/InputTokenModal.svelte | 10 ++++----- src/lib/components/Introduction.svelte | 26 +++++++++++++++++++++-- src/lib/libraries/assetSelection.ts | 6 +++--- src/lib/libraries/intent.ts | 16 ++------------ src/lib/libraries/intentFactory.ts | 2 +- src/lib/libraries/solver.ts | 10 ++++----- src/lib/screens/FillIntent.svelte | 10 ++++----- src/lib/screens/Finalise.svelte | 4 ++-- src/lib/screens/IssueIntent.svelte | 2 +- src/lib/screens/ReceiveMessage.svelte | 6 +++--- src/lib/state.svelte.ts | 2 +- src/routes/+page.svelte | 6 +++--- 13 files changed, 58 insertions(+), 45 deletions(-) create mode 100644 _typos.toml diff --git a/_typos.toml b/_typos.toml new file mode 100644 index 0000000..70c933b --- /dev/null +++ b/_typos.toml @@ -0,0 +1,3 @@ +[default.extend-words] +oif = "oif" +OIF = "OIF" \ No newline at end of file diff --git a/src/lib/components/InputTokenModal.svelte b/src/lib/components/InputTokenModal.svelte index e2661d0..e94a117 100644 --- a/src/lib/components/InputTokenModal.svelte +++ b/src/lib/components/InputTokenModal.svelte @@ -60,7 +60,7 @@ active = false; } - const unqiueTokens = $derived([...new Set(coinList(store.mainnet).map((v) => v.name))]); + const uniqueTokens = $derived([...new Set(coinList(store.mainnet).map((v) => v.name))]); // svelte-ignore state_referenced_locally let selectedTokenName = $state(currentInputTokens[0].token.name); @@ -68,11 +68,11 @@ coinList(store.mainnet).filter((v) => v.name.toLowerCase() === selectedTokenName.toLowerCase()) ); - let circutBreaker = false; + let circuitBreaker = false; $effect(() => { selectedTokenName; - if (circutBreaker || currentInputTokens[0].token.name !== selectedTokenName) { - circutBreaker = true; + if (circuitBreaker || currentInputTokens[0].token.name !== selectedTokenName) { + circuitBreaker = true; inputs = Object.fromEntries( tokenSet.map((token) => [getInteropableAddress(token.address, chainMap[token.chain].id), 0]) ); @@ -160,7 +160,7 @@ class="rounded rounded-l-none border border-gray-400 px-2 py-1" bind:value={selectedTokenName} > - {#each unqiueTokens as token} + {#each uniqueTokens as token} {/each} diff --git a/src/lib/components/Introduction.svelte b/src/lib/components/Introduction.svelte index f9f3582..ea22fea 100644 --- a/src/lib/components/Introduction.svelte +++ b/src/lib/components/Introduction.svelte @@ -4,11 +4,21 @@ Open Intents Framework. It is work in progress and currently support a seamless resource lock flow using + >. It currently support a seamless resource lock flow using The Compact and a traditional escrow flow. + > and a traditional escrow flow, along with a work in progress multichain flow. +

+ +
+ +

Multichain

+

+ A multichain intent is an intent that collects inputs on multiple chains, providing the result + on one or more chains. In other words, a multichain intent is an any to any intent. + Multichain intents are currently work in progress and will break in the future. If you are using + this interface for testing, ensure the multichain flag is not shown.


@@ -57,4 +67,16 @@ >Open Intents Framework.

+ +
+ +

Same Chain

+

+ A same chain intent is an intent that only has inputs and outputs on the same chain. The oracle + is configured different to a cross-chain intent. SetAttestation has to be called on the output + settler to expose the filled output. Learm more about same chain intents or explore a demo of how to collect inputs before delivering outputs. +

diff --git a/src/lib/libraries/assetSelection.ts b/src/lib/libraries/assetSelection.ts index 97a807b..810ea4b 100644 --- a/src/lib/libraries/assetSelection.ts +++ b/src/lib/libraries/assetSelection.ts @@ -11,7 +11,7 @@ export class AssetSelection { return bigIntSum(...values); } - static feasable(goal: bigint, values: bigint[]) { + static feasible(goal: bigint, values: bigint[]) { if (bigIntSum(...values) < goal) throw Error(`Values makes ${bigIntSum(...values)} cannot sum ${goal}`); } @@ -37,7 +37,7 @@ export class AssetSelection { } constructor(opts: { goal: bigint; values: bigint[]; weights?: bigint[] }) { - AssetSelection.feasable(opts.goal, opts.values); + AssetSelection.feasible(opts.goal, opts.values); this.goal = opts.goal; this.values = opts.values; this.weights = opts.weights; @@ -51,7 +51,7 @@ export class AssetSelection { return this.sortedValues; } - asIndicies() { + asIndices() { const zipped = AssetSelection.zip(this.sortedValues); return zipped.filter((v) => v[0] > 0); } diff --git a/src/lib/libraries/intent.ts b/src/lib/libraries/intent.ts index d2d21c5..487b4b5 100644 --- a/src/lib/libraries/intent.ts +++ b/src/lib/libraries/intent.ts @@ -1,14 +1,4 @@ -import { - concat, - encodeAbiParameters, - encodePacked, - getTypesForEIP712Domain, - hashDomain, - hashStruct, - hashTypedData, - keccak256, - parseAbiParameters -} from "viem"; +import { encodeAbiParameters, encodePacked, hashStruct, keccak256, parseAbiParameters } from "viem"; import type { BatchCompact, CompactMandate, @@ -18,7 +8,6 @@ import type { MultichainOrder, MultichainOrderComponent, NoSignature, - OrderContainer, Signature, StandardOrder } from "../../types"; @@ -35,7 +24,6 @@ import { MULTICHAIN_INPUT_SETTLER_COMPACT, MULTICHAIN_INPUT_SETTLER_ESCROW, type chain, - type Token, type Verifier, type WC } from "../config"; @@ -296,7 +284,7 @@ export class Intent { } } -/// @notice Helper function that allows you to provide an order and it will correctly generate the appropiate order. +/// @notice Helper function that allows you to provide an order and it will correctly generate the appropriate order. export function orderToIntent(options: { inputSettler: `0x${string}`; order: StandardOrder; diff --git a/src/lib/libraries/intentFactory.ts b/src/lib/libraries/intentFactory.ts index 900ea0c..9a9ed71 100644 --- a/src/lib/libraries/intentFactory.ts +++ b/src/lib/libraries/intentFactory.ts @@ -122,7 +122,7 @@ export class IntentFactory { let transactionHash = await intent.depositAndRegisterCompact(account(), this.walletClient); - const recepit = await publicClients[inputTokens[0].token.chain].waitForTransactionReceipt({ + const receipt = await publicClients[inputTokens[0].token.chain].waitForTransactionReceipt({ hash: transactionHash }); diff --git a/src/lib/libraries/solver.ts b/src/lib/libraries/solver.ts index 66beb35..0f2b944 100644 --- a/src/lib/libraries/solver.ts +++ b/src/lib/libraries/solver.ts @@ -104,7 +104,7 @@ export class Solver { await clients[outputChain].waitForTransactionReceipt({ hash: transactionHash }); - // orderInputs.validate[index] = transcationHash; + // orderInputs.validate[index] = transactionHash; if (postHook) await postHook(); return transactionHash; }; @@ -197,7 +197,7 @@ export class Solver { if (proof) { if (preHook) await preHook(sourceChain); - const transcationHash = await walletClient.writeContract({ + const transactionHash = await walletClient.writeContract({ chain: chainMap[sourceChain], account: account(), address: order.inputOracle, @@ -207,14 +207,14 @@ export class Solver { }); const result = await clients[sourceChain].waitForTransactionReceipt({ - hash: transcationHash + hash: transactionHash }); if (postHook) await postHook(); return result; } } else if (order.inputOracle === COIN_FILLER) { const log = logs.find((log) => log.logIndex === logIndex)!; - const transcationHash = await walletClient.writeContract({ + const transactionHash = await walletClient.writeContract({ chain: chainMap[sourceChain], account: account(), address: order.inputOracle, @@ -224,7 +224,7 @@ export class Solver { }); const result = await clients[sourceChain].waitForTransactionReceipt({ - hash: transcationHash + hash: transactionHash }); if (postHook) await postHook(); return result; diff --git a/src/lib/screens/FillIntent.svelte b/src/lib/screens/FillIntent.svelte index 91cffe2..c086650 100644 --- a/src/lib/screens/FillIntent.svelte +++ b/src/lib/screens/FillIntent.svelte @@ -54,15 +54,15 @@ function sortOutputsByChain(orderContainer: OrderContainer) { const outputs = orderContainer.order.outputs; - const postionMap: { [chainId: string]: number } = {}; + const positionMap: { [chainId: string]: number } = {}; const arrMap: [bigint, MandateOutput[]][] = []; for (const output of outputs) { const chainId = output.chainId; // Check if chainId exists. - let position = postionMap[chainId.toString()]; + let position = positionMap[chainId.toString()]; if (position == undefined) { position = arrMap.length; - postionMap[chainId.toString()] = position; + positionMap[chainId.toString()] = position; arrMap.push([chainId, []]); } arrMap[position][1].push(output); @@ -89,7 +89,7 @@ types: compactTypes, primaryType: "MandateOutput" }); - store.fillTranscations[outputHash] = result; + store.fillTransactions[outputHash] = result; } }; }; @@ -191,7 +191,7 @@ class="w-20 rounded border px-2 py-1" placeholder="fillTransactionHash" bind:value={ - store.fillTranscations[ + store.fillTransactions[ hashStruct({ data: { outputs: chainIdAndOutputs.outputs }, types: { diff --git a/src/lib/screens/Finalise.svelte b/src/lib/screens/Finalise.svelte index bb168fe..eb902e1 100644 --- a/src/lib/screens/Finalise.svelte +++ b/src/lib/screens/Finalise.svelte @@ -127,7 +127,7 @@ orderContainer, fillTransactionHashes: orderContainer.order.outputs.map( (output) => - store.fillTranscations[ + store.fillTransactions[ hashStruct({ data: output, types: compactTypes, @@ -160,7 +160,7 @@ orderContainer, fillTransactionHashes: orderContainer.order.outputs.map( (output) => - store.fillTranscations[ + store.fillTransactions[ hashStruct({ data: output, types: compactTypes, diff --git a/src/lib/screens/IssueIntent.svelte b/src/lib/screens/IssueIntent.svelte index 051f860..f162a9f 100644 --- a/src/lib/screens/IssueIntent.svelte +++ b/src/lib/screens/IssueIntent.svelte @@ -119,7 +119,7 @@ amount: bigint; decimals: number; }[] = []; - // Get all unqiue tokens. + // Get all unique tokens. const allUniqueNames = [ ...new Set( store.inputTokens.map((v) => { diff --git a/src/lib/screens/ReceiveMessage.svelte b/src/lib/screens/ReceiveMessage.svelte index d6c3630..0dd2288 100644 --- a/src/lib/screens/ReceiveMessage.svelte +++ b/src/lib/screens/ReceiveMessage.svelte @@ -81,7 +81,7 @@ // inputChain, // orderContainer, // output, - // store.fillTranscations[ + // store.fillTransactions[ // hashStruct({ data: output, types: compactTypes, primaryType: "MandateOutput" }) // ], // refreshValidation @@ -105,7 +105,7 @@
{#each orderContainer.order.outputs as output} - {#await isValidated(orderToIntent(orderContainer).orderId(), inputChain, orderContainer, output, store.fillTranscations[hashStruct( { data: output, types: compactTypes, primaryType: "MandateOutput" } )], refreshValidation)} + {#await isValidated(orderToIntent(orderContainer).orderId(), inputChain, orderContainer, output, store.fillTransactions[hashStruct( { data: output, types: compactTypes, primaryType: "MandateOutput" } )], refreshValidation)}
@@ -135,7 +135,7 @@ output, orderContainer, fillTransactionHash: - store.fillTranscations[ + store.fillTransactions[ hashStruct({ data: output, types: compactTypes, diff --git a/src/lib/state.svelte.ts b/src/lib/state.svelte.ts index ebe27be..78d6fe9 100644 --- a/src/lib/state.svelte.ts +++ b/src/lib/state.svelte.ts @@ -51,7 +51,7 @@ class Store { // inputAmounts = $state([1000000n]); // outputAmounts = $state([1000000n]); - fillTranscations = $state<{ [outputId: string]: `0x${string}` }>({}); + fillTransactions = $state<{ [outputId: string]: `0x${string}` }>({}); balances = $derived.by(() => { return this.mapOverCoins(getBalance, this.mainnet, this.updatedDerived); diff --git a/src/routes/+page.svelte b/src/routes/+page.svelte index f98666e..17cb27a 100644 --- a/src/routes/+page.svelte +++ b/src/routes/+page.svelte @@ -35,7 +35,7 @@ const orderServer = $derived(new OrderServer(store.mainnet)); let s: WebSocket; - function initatePage() { + function initiatePage() { if (s) s.close(); // Empty store.orders without changing the pointer: store.orders.forEach(() => { @@ -70,11 +70,11 @@ // $effect(() => { // store.mainnet; - // initatePage(); + // initiatePage(); // }); // onMount(() => { - // initatePage(); + // initiatePage(); // }); // --- Wallet --- //