diff --git a/README.md b/README.md
index 5e430d2..8dac886 100644
--- a/README.md
+++ b/README.md
@@ -9,6 +9,7 @@ This project uses SvelteKit and `npm`. It is configured for a deployment to Clou
### Development
To start development:
+
1. Copy `.env.example` to `.env`.
2. Then fill in the `env` variables by creating a [WalletConnect](https://walletconnect.com) project
3. Also create an [account](https://accounts.polymerlabs.org/) with Polymer to generation [Polymer](https://polymerlabs.org) API keys.
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/abi/escrow.ts b/src/lib/abi/escrow.ts
index a35f7bf..61a0992 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",
@@ -828,38 +756,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",
@@ -1135,46 +1031,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 +1062,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 +1165,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 +1190,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 +1203,6 @@ export const SETTLER_ESCROW_ABI = [
],
anonymous: false
},
- {
- type: "error",
- name: "AlreadyInitialized",
- inputs: []
- },
{
type: "error",
name: "AlreadyPurchased",
@@ -1491,15 +1246,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"
}
@@ -1545,21 +1300,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",
@@ -1636,8 +1381,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_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/abi/multichain_escrow.ts b/src/lib/abi/multichain_escrow.ts
new file mode 100644
index 0000000..892b962
--- /dev/null
+++ b/src/lib/abi/multichain_escrow.ts
@@ -0,0 +1,1051 @@
+export const MULTICHAIN_SETTLER_ESCROW_ABI = [
+ {
+ type: "constructor",
+ inputs: [],
+ stateMutability: "nonpayable"
+ },
+ {
+ 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: "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: "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: "open",
+ 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: [],
+ stateMutability: "nonpayable"
+ },
+ {
+ type: "function",
+ name: "openFor",
+ 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: "sponsor",
+ type: "address",
+ internalType: "address"
+ },
+ {
+ name: "signature",
+ 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: "function",
+ name: "orderStatus",
+ inputs: [
+ {
+ name: "orderId",
+ type: "bytes32",
+ internalType: "bytes32"
+ }
+ ],
+ outputs: [
+ {
+ name: "",
+ type: "uint8",
+ internalType: "enum InputSettlerMultichainEscrow.OrderStatus"
+ }
+ ],
+ stateMutability: "view"
+ },
+ {
+ type: "function",
+ name: "refund",
+ 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: [],
+ stateMutability: "nonpayable"
+ },
+ {
+ 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: "event",
+ name: "Open",
+ inputs: [
+ {
+ name: "orderId",
+ type: "bytes32",
+ indexed: true,
+ internalType: "bytes32"
+ },
+ {
+ name: "order",
+ type: "bytes",
+ indexed: false,
+ internalType: "bytes"
+ }
+ ],
+ anonymous: false
+ },
+ {
+ type: "event",
+ name: "Refunded",
+ inputs: [
+ {
+ name: "orderId",
+ type: "bytes32",
+ indexed: true,
+ internalType: "bytes32"
+ }
+ ],
+ anonymous: false
+ },
+ {
+ type: "error",
+ name: "CallOutOfRange",
+ inputs: []
+ },
+ {
+ type: "error",
+ name: "ChainIndexOutOfRange",
+ inputs: [
+ {
+ name: "chainIndex",
+ type: "uint256",
+ internalType: "uint256"
+ },
+ {
+ name: "numSegments",
+ type: "uint256",
+ internalType: "uint256"
+ }
+ ]
+ },
+ {
+ type: "error",
+ name: "CodeSize0",
+ 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: "HasDirtyBits",
+ inputs: []
+ },
+ {
+ type: "error",
+ name: "InvalidOrderStatus",
+ inputs: []
+ },
+ {
+ type: "error",
+ name: "InvalidShortString",
+ inputs: []
+ },
+ {
+ type: "error",
+ name: "InvalidSigner",
+ inputs: []
+ },
+ {
+ type: "error",
+ name: "InvalidTimestampLength",
+ inputs: []
+ },
+ {
+ type: "error",
+ name: "NoDestination",
+ inputs: []
+ },
+ {
+ type: "error",
+ name: "OrderIdMismatch",
+ inputs: [
+ {
+ name: "provided",
+ type: "bytes32",
+ internalType: "bytes32"
+ },
+ {
+ name: "computed",
+ type: "bytes32",
+ internalType: "bytes32"
+ }
+ ]
+ },
+ {
+ type: "error",
+ name: "ReentrancyDetected",
+ inputs: []
+ },
+ {
+ type: "error",
+ name: "SafeERC20FailedOperation",
+ inputs: [
+ {
+ name: "token",
+ type: "address",
+ internalType: "address"
+ }
+ ]
+ },
+ {
+ type: "error",
+ name: "SignatureAndInputsNotEqual",
+ inputs: []
+ },
+ {
+ type: "error",
+ name: "SignatureNotSupported",
+ inputs: [
+ {
+ name: "",
+ type: "bytes1",
+ internalType: "bytes1"
+ }
+ ]
+ },
+ {
+ 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: "WrongChain",
+ inputs: [
+ {
+ name: "expected",
+ type: "uint256",
+ internalType: "uint256"
+ },
+ {
+ name: "actual",
+ type: "uint256",
+ internalType: "uint256"
+ }
+ ]
+ }
+] as const;
diff --git a/src/lib/abi/outputsettler.ts b/src/lib/abi/outputsettler.ts
index 9321129..ac4a130 100644
--- a/src/lib/abi/outputsettler.ts
+++ b/src/lib/abi/outputsettler.ts
@@ -496,17 +496,6 @@ export const COIN_FILLER_ABI = [
name: "ContextOutOfRange",
inputs: []
},
- {
- type: "error",
- name: "ExclusiveTo",
- inputs: [
- {
- name: "solver",
- type: "bytes32",
- internalType: "bytes32"
- }
- ]
- },
{
type: "error",
name: "FailedCall",
@@ -554,11 +543,6 @@ export const COIN_FILLER_ABI = [
}
]
},
- {
- type: "error",
- name: "InvalidContextDataLength",
- inputs: []
- },
{
type: "error",
name: "NotDivisible",
@@ -575,11 +559,6 @@ export const COIN_FILLER_ABI = [
}
]
},
- {
- type: "error",
- name: "NotImplemented",
- inputs: []
- },
{
type: "error",
name: "NotProven",
@@ -648,10 +627,5 @@ export const COIN_FILLER_ABI = [
internalType: "bytes32"
}
]
- },
- {
- type: "error",
- name: "ZeroValue",
- inputs: []
}
] as const;
diff --git a/src/lib/components/BalanceField.svelte b/src/lib/components/BalanceField.svelte
index 3f02899..bde44f0 100644
--- a/src/lib/components/BalanceField.svelte
+++ b/src/lib/components/BalanceField.svelte
@@ -13,21 +13,21 @@
{#await value}
{:then value}
{:catch error}
diff --git a/src/lib/components/GetQuote.svelte b/src/lib/components/GetQuote.svelte
index 35c7509..f8f0fda 100644
--- a/src/lib/components/GetQuote.svelte
+++ b/src/lib/components/GetQuote.svelte
@@ -1,22 +1,18 @@
-
+
{#await quoteRequest}
-
Fetch Quote
+
Fetch Quote
{:then _}
{#if quoteExpires !== 0}
diff --git a/src/lib/components/InputTokenModal.svelte b/src/lib/components/InputTokenModal.svelte
new file mode 100644
index 0000000..e94a117
--- /dev/null
+++ b/src/lib/components/InputTokenModal.svelte
@@ -0,0 +1,199 @@
+
+
+
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/components/OutputTokenModal.svelte b/src/lib/components/OutputTokenModal.svelte
new file mode 100644
index 0000000..f053ad7
--- /dev/null
+++ b/src/lib/components/OutputTokenModal.svelte
@@ -0,0 +1,131 @@
+
+
+
diff --git a/src/lib/config.ts b/src/lib/config.ts
index fd38387..3efc4ad 100644
--- a/src/lib/config.ts
+++ b/src/lib/config.ts
@@ -15,7 +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 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 = {
@@ -43,13 +47,18 @@ export const chainMap = {
ethereum,
base,
arbitrum,
- arbitrumSepolia,
sepolia,
+ arbitrumSepolia,
optimismSepolia,
baseSepolia
} 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", "arbitrumSepolia"];
+};
export type balanceQuery = Record
>>;
@@ -199,8 +208,8 @@ export function printToken(token: Token) {
return `${token.name.toUpperCase()}, ${token.chain}`;
}
-export function formatTokenAmount(amount: bigint, token: Token, decimals = 4) {
- const formattedAmount = Number(amount) / 10 ** token.decimals;
+export function formatTokenAmount(amount: bigint, tokenDecimals: number, decimals = 4) {
+ const formattedAmount = Number(amount) / 10 ** tokenDecimals;
return formattedAmount.toFixed(decimals);
}
diff --git a/src/lib/libraries/assetSelection.ts b/src/lib/libraries/assetSelection.ts
new file mode 100644
index 0000000..810ea4b
--- /dev/null
+++ b/src/lib/libraries/assetSelection.ts
@@ -0,0 +1,80 @@
+const bigIntSum = (...nums: bigint[]) => nums.reduce((a, b) => a + b, 0n);
+
+export class AssetSelection {
+ goal: bigint;
+ values: bigint[];
+ weights?: bigint[];
+
+ sortedValues: bigint[];
+
+ static Sum(values: bigint[]) {
+ return bigIntSum(...values);
+ }
+
+ static feasible(goal: bigint, values: bigint[]) {
+ if (bigIntSum(...values) < goal)
+ throw Error(`Values makes ${bigIntSum(...values)} cannot sum ${goal}`);
+ }
+
+ static zip(arr: bigint[]): [bigint, number][] {
+ return arr.map((v, i) => [v, i]);
+ }
+
+ static unzip(arr: [bigint, number][]): bigint[] {
+ arr.sort((a, b) => a[1] - b[1]);
+ return arr.map((v) => v[0]);
+ }
+
+ static takeFromArray(goal: bigint, values: [bigint, T][]) {
+ let sum = 0n;
+ for (let i = 0; i < values.length; ++i) {
+ const value = values[i][0];
+ const less = goal - sum;
+ const diff = less < value ? less : value;
+ sum += diff;
+ values[i][0] = diff;
+ }
+ }
+
+ constructor(opts: { goal: bigint; values: bigint[]; weights?: bigint[] }) {
+ AssetSelection.feasible(opts.goal, opts.values);
+ this.goal = opts.goal;
+ this.values = opts.values;
+ this.weights = opts.weights;
+
+ this.sortedValues = this.values;
+ }
+
+ // --- Get sorted values as --- //
+
+ asValues() {
+ return this.sortedValues;
+ }
+
+ asIndices() {
+ const zipped = AssetSelection.zip(this.sortedValues);
+ return zipped.filter((v) => v[0] > 0);
+ }
+
+ // --- Sorting Methods --- //
+
+ largest() {
+ const values = AssetSelection.zip(this.values);
+ values.sort((a, b) => Number(b[0] - a[0]));
+
+ AssetSelection.takeFromArray(this.goal, values);
+
+ this.sortedValues = AssetSelection.unzip(values);
+ return this;
+ }
+
+ smallest() {
+ const values = AssetSelection.zip(this.values);
+ values.sort((a, b) => Number(a[0] - b[0]));
+
+ AssetSelection.takeFromArray(this.goal, values);
+
+ this.sortedValues = AssetSelection.unzip(values);
+ return this;
+ }
+}
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 894226e..487b4b5 100644
--- a/src/lib/libraries/intent.ts
+++ b/src/lib/libraries/intent.ts
@@ -1,15 +1,29 @@
-import { encodePacked, hashStruct, toHex } from "viem";
-import type { BatchCompact, CompactMandate, MandateOutput, StandardOrder } from "../../types";
+import { encodeAbiParameters, encodePacked, hashStruct, keccak256, parseAbiParameters } from "viem";
+import type {
+ BatchCompact,
+ CompactMandate,
+ Element,
+ MandateOutput,
+ MultichainCompact,
+ MultichainOrder,
+ MultichainOrderComponent,
+ NoSignature,
+ Signature,
+ StandardOrder
+} from "../../types";
import { COMPACT_ABI } from "../abi/compact";
import {
chainMap,
+ clients,
COIN_FILLER,
COMPACT,
+ getChainName,
getOracle,
INPUT_SETTLER_COMPACT_LIFI,
INPUT_SETTLER_ESCROW_LIFI,
+ MULTICHAIN_INPUT_SETTLER_COMPACT,
+ MULTICHAIN_INPUT_SETTLER_ESCROW,
type chain,
- type Token,
type Verifier,
type WC
} from "../config";
@@ -17,19 +31,48 @@ 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";
+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";
-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;
- inputTokens: Token[];
- outputToken: Token;
- inputAmounts: bigint[];
- outputAmount: bigint;
+};
+
+export type CreateIntentOptionsEscrow = {
+ exclusiveFor: string;
+ inputTokens: TokenContext[];
+ outputTokens: TokenContext[];
verifier: Verifier;
account: () => `0x${string}`;
- inputSettler: typeof INPUT_SETTLER_COMPACT_LIFI | typeof INPUT_SETTLER_ESCROW_LIFI;
+ lock: EscrowLock;
};
+export type CreateIntentOptionsCompact = {
+ exclusiveFor: string;
+ inputTokens: TokenContext[];
+ outputTokens: TokenContext[];
+ verifier: Verifier;
+ account: () => `0x${string}`;
+ lock: CompactLock;
+};
+
+export type CreateIntentOptions = CreateIntentOptionsEscrow | CreateIntentOptionsCompact;
+
function findChain(chainId: bigint) {
for (const [name, data] of Object.entries(chainMap)) {
if (BigInt(data.id) === chainId) {
@@ -39,96 +82,255 @@ 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 callbackData, 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,
- outputToken,
- inputAmounts,
- outputAmount,
- verifier,
- account,
- inputSettler
- } = opts;
+ numInputChains() {
+ const tokenChains = this.inputs.map(({ token }) => token.chain);
+ return [...new Set(tokenChains)].length;
+ }
+
+ isMultichain() {
+ 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));
+ return this._nonce;
+ }
+
+ 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: ${multichain}, type: ${this.lock.type}`);
+ }
+
+ 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}`);
- }
-
- const inputChain = inputTokens[0].chain;
- const inputs: [bigint, bigint][] = [];
- for (let i = 0; i < inputTokens.length; ++i) {
- // 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]]);
+ throw new Error(`ExclusiveFor not formatted correctly ${this.exclusiveFor}`);
}
- const outputSettler = COIN_FILLER;
- const outputOracle = getOracle(verifier, outputToken.chain)!;
- 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 output: MandateOutput = {
- oracle: addressToBytes32(outputOracle),
- settler: addressToBytes32(outputSettler),
- chainId: BigInt(chainMap[outputToken.chain].id),
- token: addressToBytes32(outputToken.address),
- amount: outputAmount,
- recipient: addressToBytes32(account()),
- callbackData: "0x",
- context
- };
- const outputs = [output];
+ 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: outputOracle,
+ settler: addressToBytes32(outputSettler),
+ chainId: BigInt(chainMap[token.chain].id),
+ token: addressToBytes32(token.address),
+ amount: amount,
+ recipient: addressToBytes32(this.user()),
+ callbackData: "0x",
+ context
+ };
+ }) 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 = this.isSameChain() ? COIN_FILLER : 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 { order };
+ 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 }) => [
+ this.lock.type === "compact"
+ ? toId(true, this.lock.resetPeriod, this.lock.allocatorId, token.address)
+ : 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 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 appropriate 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
@@ -149,12 +351,8 @@ export class Intent {
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,
@@ -165,12 +363,45 @@ export class Intent {
arbiter: INPUT_SETTLER_COMPACT_LIFI,
sponsor: order.user,
nonce: order.nonce,
- expires: order.expires,
+ expires: BigInt(order.expires),
commitments,
mandate
};
}
+ 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 -- //
/**
@@ -179,18 +410,21 @@ 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}`> {
+ 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({
- chain,
- account,
- address: INPUT_SETTLER_ESCROW_LIFI,
- abi: SETTLER_ESCROW_ABI,
- functionName: "open",
- args: [this.order]
- });
+ return [
+ await walletClient.writeContract({
+ chain,
+ account,
+ address: INPUT_SETTLER_ESCROW_LIFI,
+ abi: SETTLER_ESCROW_ABI,
+ functionName: "open",
+ args: [this.order]
+ })
+ ];
}
// -- Compact Helpers -- //
@@ -233,4 +467,450 @@ export class Intent {
args: [this.order.inputs, [[this.compactClaimHash(), compact_type_hash]]]
});
}
+
+ async finalise(options: {
+ sourceChain: chain;
+ account: `0x${string}`;
+ walletClient: WC;
+ solveParams: { timestamp: number; solver: `0x${string}` }[];
+ signatures: {
+ sponsorSignature: Signature | NoSignature;
+ allocatorSignature: Signature | NoSignature;
+ };
+ }) {
+ const { sourceChain, account, walletClient, solveParams, 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, solveParams, 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, solveParams, 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;
+
+ 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();
+ }
+
+ /**
+ * @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) =>
+ 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;
+ }
+
+ 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"],
+ [
+ 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 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]));
+ }
+
+ 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], 32);
+ 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)
+ };
+ });
+ }
+
+ asCompactElements() {
+ const { fillDeadline, inputOracle, outputs, inputs } = this.order;
+ const mandate: CompactMandate = {
+ fillDeadline,
+ inputOracle,
+ outputs
+ };
+ return inputs.map((inputs) => {
+ const element: Element = {
+ arbiter: this.inputSettler,
+ chainId: inputs.chainId,
+ commitments: MultichainOrderIntent.inputsToLocks(inputs.inputs),
+ mandate
+ };
+ 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",
+ data: element
+ });
+ return hash;
+ });
+ 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;
+ }
+
+ // -- 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;
+ 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();
+ const components = this.asComponents();
+ const results: `0x${string}`[] = [];
+ for (const { chainId, orderComponent } of components) {
+ const chain = findChain(chainId)!;
+ walletClient.switchChain({ id: chain.id });
+ results.push(
+ await walletClient.writeContract({
+ chain,
+ account,
+ address: this.inputSettler,
+ abi: MULTICHAIN_SETTLER_ESCROW_ABI,
+ functionName: "open",
+ args: [orderComponent]
+ })
+ );
+ console.log(results);
+ }
+ return results;
+ }
+
+ async finalise(options: {
+ sourceChain: chain;
+ account: `0x${string}`;
+ walletClient: WC;
+ solveParams: { timestamp: number; solver: `0x${string}` }[];
+ signatures: {
+ sponsorSignature: Signature | NoSignature;
+ 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)))
+ 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, chainId } of components) {
+ if (this.inputSettler.toLowerCase() === MULTICHAIN_INPUT_SETTLER_ESCROW.toLowerCase()) {
+ return await walletClient.writeContract({
+ chain: actionChain,
+ account: account,
+ address: this.inputSettler,
+ abi: MULTICHAIN_SETTLER_ESCROW_ABI,
+ 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 f23fa8d..9a9ed71 100644
--- a/src/lib/libraries/intentFactory.ts
+++ b/src/lib/libraries/intentFactory.ts
@@ -4,15 +4,23 @@ import {
clients,
INPUT_SETTLER_COMPACT_LIFI,
INPUT_SETTLER_ESCROW_LIFI,
+ MULTICHAIN_INPUT_SETTLER_ESCROW,
type Token,
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";
import type { CreateIntentOptions } from "$lib/libraries/intent";
+import { store, type TokenContext } from "$lib/state.svelte";
/**
* @notice Factory class for creating and managing intents. Functions called by integrators.
@@ -47,8 +55,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;
}) {
@@ -71,26 +79,34 @@ 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);
+ 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();
};
@@ -100,13 +116,13 @@ 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].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 receipt = await publicClients[inputTokens[0].token.chain].waitForTransactionReceipt({
hash: transactionHash
});
@@ -135,26 +151,29 @@ 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].chain;
+ 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);
+ console.log({ tsh: transactionHashes });
- 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(),
- inputSettler: INPUT_SETTLER_ESCROW_LIFI
+ order: intent.asOrder(),
+ inputSettler: store.inputSettler
});
- return transactionHash;
+ return transactionHashes;
};
}
}
@@ -164,31 +183,32 @@ 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 settler = store.multichain ? MULTICHAIN_INPUT_SETTLER_ESCROW : INPUT_SETTLER_ESCROW_LIFI;
+
+ 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]
+ args: [account(), settler]
});
- 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]
+ args: [settler, maxUint256]
});
await publicClient.waitForTransactionReceipt({
diff --git a/src/lib/libraries/solver.ts b/src/lib/libraries/solver.ts
index b423a0f..0f2b944 100644
--- a/src/lib/libraries/solver.ts
+++ b/src/lib/libraries/solver.ts
@@ -3,13 +3,20 @@ import {
type chain,
chainMap,
clients,
+ COIN_FILLER,
getChainName,
getOracle,
INPUT_SETTLER_COMPACT_LIFI,
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";
@@ -17,8 +24,9 @@ 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";
+import { compactTypes } from "$lib/utils/typedMessage";
/**
* @notice Class for solving intents. Functions called by solvers.
@@ -43,18 +51,15 @@ export class Solver {
outputs
} = args;
const publicClients = clients;
- const orderId = getOrderId({ order, inputSettler });
- //Check that only 1 output exists.
- if (outputs.length !== 1) {
- throw new Error("Order must have exactly one output");
- }
+ const orderId = orderToIntent({ order, inputSettler }).orderId();
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");
@@ -91,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())]
@@ -98,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;
};
@@ -106,7 +112,13 @@ export class Solver {
static validate(
walletClient: WC,
- args: { orderContainer: OrderContainer; fillTransactionHash: string; mainnet: boolean },
+ args: {
+ output: MandateOutput;
+ orderContainer: OrderContainer;
+ fillTransactionHash: string;
+ sourceChain: chain;
+ mainnet: boolean;
+ },
opts: {
preHook?: (chain: chain) => Promise;
postHook?: () => Promise;
@@ -116,34 +128,55 @@ export class Solver {
return async () => {
const { preHook, postHook, account } = opts;
const {
- orderContainer: { order },
+ output,
+ orderContainer: { order, inputSettler },
fillTransactionHash,
+ sourceChain,
mainnet
} = 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");
- }
- // 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}`
- });
+ // Get the output filled event.
+ 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`);
+ if (order.inputOracle === getOracle("polymer", sourceChain)) {
let proof: string | undefined;
let polymerIndex: number | undefined;
for (let i = 0; i < 5; ++i) {
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
});
@@ -160,11 +193,11 @@ 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);
- const transcationHash = await walletClient.writeContract({
+ const transactionHash = await walletClient.writeContract({
chain: chainMap[sourceChain],
account: account(),
address: order.inputOracle,
@@ -174,33 +207,28 @@ 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 transactionHash = 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]
+ });
- // 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;
- // }
+ const result = await clients[sourceChain].waitForTransactionReceipt({
+ hash: transactionHash
+ });
+ if (postHook) await postHook();
+ return result;
+ }
};
}
@@ -208,7 +236,8 @@ export class Solver {
walletClient: WC,
args: {
orderContainer: OrderContainer;
- fillTransactionHash: string;
+ fillTransactionHashes: string[];
+ sourceChain: chain;
},
opts: {
preHook?: (chain: chain) => Promise;
@@ -218,67 +247,48 @@ export class Solver {
) {
return async () => {
const { preHook, postHook, account } = opts;
- const { orderContainer, fillTransactionHash } = args;
- const { order } = orderContainer;
- const outputChain = getChainName(order.outputs[0].chainId);
- if (order.outputs.length !== 1) {
- throw new Error("Order must have exactly one output");
- }
- const transactionReceipt = await clients[outputChain].getTransactionReceipt({
- hash: fillTransactionHash as `0x${string}`
- });
- const blockHashOfFill = transactionReceipt.blockHash;
- const block = await clients[outputChain].getBlock({
- blockHash: blockHashOfFill
+ const { orderContainer, fillTransactionHashes, sourceChain } = args;
+ const { order, inputSettler } = orderContainer;
+ const intent = orderToIntent({
+ inputSettler,
+ order
});
- const fillTimestamp = block.timestamp;
- const sourceChain = getChainName(order.originChainId);
- if (preHook) await preHook(sourceChain);
+ const outputChain = getChainName(order.outputs[0].chainId);
+ const transactionReceipts = await Promise.all(
+ fillTransactionHashes.map((fth) =>
+ clients[outputChain].getTransactionReceipt({
+ hash: fth as `0x${string}`
+ })
+ )
+ );
+ const blocks = await Promise.all(
+ transactionReceipts.map((r) =>
+ clients[outputChain].getBlock({
+ blockHash: r.blockHash
+ })
+ )
+ );
+ const fillTimestamps = blocks.map((b) => b.timestamp);
- const inputSettler = orderContainer.inputSettler;
- console.log({ orderContainer });
- let transactionHash: `0x${string}`;
- const actionChain = chainMap[sourceChain];
+ if (preHook) await preHook(sourceChain);
- const solveParam = {
- timestamp: Number(fillTimestamp),
- solver: addressToBytes32(account())
- };
+ const solveParams = fillTimestamps.map((fillTimestamp) => {
+ return {
+ 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 = await intent.finalise({
+ sourceChain,
+ account: account(),
+ walletClient,
+ solveParams,
+ signatures: orderContainer
+ });
const result = await clients[sourceChain].waitForTransactionReceipt({
- hash: transactionHash
+ hash: transactionHash!
});
if (postHook) await postHook();
return result;
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..c086650 100644
--- a/src/lib/screens/FillIntent.svelte
+++ b/src/lib/screens/FillIntent.svelte
@@ -9,25 +9,25 @@
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";
+ import { compactTypes } from "$lib/utils/typedMessage";
+ import { hashStruct } from "viem";
let {
scroll,
orderContainer,
- walletClient,
- fillTransactionHash = $bindable(),
account,
preHook,
postHook
}: {
scroll: (direction: boolean | number) => () => void;
orderContainer: OrderContainer;
- walletClient: WC;
- fillTransactionHash: `0x${string}` | undefined;
preHook?: (chain: chain) => Promise;
postHook: () => Promise;
account: () => `0x${string}`;
@@ -49,40 +49,48 @@
functionName: "getFillRecord",
args: [orderId, outputHash]
});
- console.log({ orderId, output, result, outputHash });
return result;
}
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);
}
- console.log(arrMap);
return arrMap;
}
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)
+ )
])
);
- const fillWrapper = (func: ReturnType) => {
+ const fillWrapper = (outputs: MandateOutput[], func: ReturnType) => {
return async () => {
const result = await func();
- fillTransactionHash = result;
+
+ for (const output of outputs) {
+ const outputHash = hashStruct({
+ data: output,
+ types: compactTypes,
+ primaryType: "MandateOutput"
+ });
+ store.fillTransactions[outputHash] = result;
+ }
};
};
@@ -94,9 +102,9 @@
hash in the input box.
- {#each sortOutputsByChain(orderContainer) as [chainId, outputs], c}
+ {#each sortOutputsByChain(orderContainer) as chainIdAndOutputs, c}
- {getChainName(chainId)}
+ {getChainName(chainIdAndOutputs[0])}
@@ -108,11 +116,12 @@
v == BYTES32_ZERO)
? fillWrapper(
+ chainIdAndOutputs[1],
Solver.fill(
- walletClient,
+ store.walletClient,
{
orderContainer,
- outputs
+ outputs: chainIdAndOutputs[1]
},
{
preHook,
@@ -131,7 +140,7 @@
{/snippet}
{/await}
- {#each outputs as output, i}
+ {#each chainIdAndOutputs[1] as output, i}
{#await filledStatusPromises[c][1][i]}
@@ -139,7 +148,10 @@
{formatTokenAmount(
output.amount,
- getCoin({ address: output.token, chain: getChainName(output.chainId) })
+ getCoin({
+ address: output.token,
+ chain: getChainName(output.chainId)
+ }).decimals
)}
@@ -160,7 +172,10 @@
{formatTokenAmount(
output.amount,
- getCoin({ address: output.token, chain: getChainName(output.chainId) })
+ getCoin({
+ address: output.token,
+ chain: getChainName(output.chainId)
+ }).decimals
)}
@@ -172,12 +187,22 @@
{/await}
{/each}
+
{/each}
-
-
diff --git a/src/lib/screens/Finalise.svelte b/src/lib/screens/Finalise.svelte
index 58d2b04..eb902e1 100644
--- a/src/lib/screens/Finalise.svelte
+++ b/src/lib/screens/Finalise.svelte
@@ -11,65 +11,74 @@
getCoin,
INPUT_SETTLER_COMPACT_LIFI,
INPUT_SETTLER_ESCROW_LIFI,
+ MULTICHAIN_INPUT_SETTLER_COMPACT,
+ MULTICHAIN_INPUT_SETTLER_ESCROW,
type chain,
type WC
} from "$lib/config";
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";
+ import { orderToIntent } from "$lib/libraries/intent";
+ import { hashStruct } from "viem";
+ import { compactTypes } from "$lib/utils/typedMessage";
let {
orderContainer,
- walletClient,
- fillTransactionHash,
account,
preHook,
postHook
}: {
orderContainer: OrderContainer;
- walletClient: WC;
- fillTransactionHash: `0x${string}`;
preHook?: (chain: chain) => Promise
;
postHook?: () => Promise;
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;
const OrderStatus_Claimed = 2;
const OrderStatus_Refunded = 3;
- async function isClaimed(
- container: { order: StandardOrder; inputSettler: `0x${string}` },
- _: any
- ) {
+ async function isClaimed(chainId: bigint, container: OrderContainer, _: any) {
const { order, inputSettler } = container;
- const inputChainClient = getClient(order.originChainId);
+ const inputChainClient = getClient(chainId);
+ const intent = orderToIntent(container);
+ const orderId = intent.orderId();
// Determine the order type.
- if (inputSettler == INPUT_SETTLER_ESCROW_LIFI) {
+ if (
+ inputSettler === INPUT_SETTLER_ESCROW_LIFI ||
+ inputSettler === MULTICHAIN_INPUT_SETTLER_ESCROW
+ ) {
// Check order status
- const orderId = await inputChainClient.readContract({
- address: inputSettler,
- abi: SETTLER_ESCROW_ABI,
- functionName: "orderIdentifier",
- args: [order]
- });
const orderStatus = await inputChainClient.readContract({
address: inputSettler,
abi: SETTLER_ESCROW_ABI,
functionName: "orderStatus",
args: [orderId]
});
- return orderStatus == OrderStatus_Claimed || orderStatus == OrderStatus_Refunded;
- } else if (inputSettler == INPUT_SETTLER_COMPACT_LIFI) {
+ return orderStatus === OrderStatus_Claimed || orderStatus === OrderStatus_Refunded;
+ } else if (
+ inputSettler === INPUT_SETTLER_COMPACT_LIFI ||
+ inputSettler === MULTICHAIN_INPUT_SETTLER_COMPACT
+ ) {
// Check claim status
+ 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: [order.inputs[0][0]]
+ args: [flattenedInputs[0][0]]
});
// Check if nonce is spent.
return await inputChainClient.readContract({
@@ -85,41 +94,85 @@
Finalise Intent
Finalise the order to receive the inputs.
-
-
- {getChainName(orderContainer.order.originChainId)}
-
-
-
- {#await isClaimed(orderContainer, "")}
-
- Finalise
-
- {:then isClaimed}
- {#if isClaimed}
+ {#each orderToIntent(orderContainer).inputChains() as inputChain}
+
+
+ {getChainName(inputChain)}
+
+
+
+ {#await isClaimed(inputChain, orderContainer, "")}
- Finalised
+ Finalise
- {:else}
+ {:then isClaimed}
+ {#if isClaimed}
+
+ Finalised
+
+ {:else}
+
+ store.fillTransactions[
+ hashStruct({
+ data: output,
+ types: compactTypes,
+ primaryType: "MandateOutput"
+ })
+ ] as string
+ )
+ },
+ {
+ account,
+ preHook,
+ postHook: postHookRefreshValidate
+ }
+ )}
+ >
+ {#snippet name()}
+ Claim
+ {/snippet}
+ {#snippet awaiting()}
+ Waiting for transaction...
+ {/snippet}
+
+ {/if}
+ {:catch}
+ store.fillTransactions[
+ hashStruct({
+ data: output,
+ types: compactTypes,
+ primaryType: "MandateOutput"
+ })
+ ] as string
+ )
},
{
account,
preHook,
- postHook
+ postHook: postHookRefreshValidate
}
)}
>
@@ -130,55 +183,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)
- })
- )}
+ {/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/IntentList.svelte b/src/lib/screens/IntentList.svelte
index c7fc3b2..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)
- })
- )}
+ }).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}
@@ -76,6 +116,7 @@
{formatTokenAmount(
output.amount,
getCoin({ address: output.token, chain: getChainName(output.chainId) })
+ .decimals
)}
diff --git a/src/lib/screens/IssueIntent.svelte b/src/lib/screens/IssueIntent.svelte
index 911b78f..f162a9f 100644
--- a/src/lib/screens/IssueIntent.svelte
+++ b/src/lib/screens/IssueIntent.svelte
@@ -3,80 +3,51 @@
import GetQuote from "$lib/components/GetQuote.svelte";
import {
INPUT_SETTLER_COMPACT_LIFI,
- type WC,
- type availableAllocators,
- type availableInputSettlers,
- type balanceQuery,
- type Token,
- type Verifier,
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";
- import type { OrderContainer } from "../../types";
+ 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);
let {
scroll,
- showTokenSelector = $bindable(),
- exclusiveFor = $bindable(),
- mainnet,
- inputSettler,
- allocatorId,
- inputAmounts,
- outputAmount,
- inputTokens,
- outputToken,
- verifier,
- compactBalances,
- balances,
- allowances,
- walletClient,
- orders = $bindable(),
preHook,
postHook,
account
}: {
scroll: (direction: boolean | number) => () => void;
- showTokenSelector: {
- active: number;
- input: boolean;
- index: number;
- };
- exclusiveFor: string;
- inputSettler: availableInputSettlers;
- mainnet: boolean;
- allocatorId: availableAllocators;
- inputAmounts: bigint[];
- outputAmount: bigint;
- inputTokens: Token[];
- outputToken: Token;
- compactBalances: balanceQuery;
- verifier: Verifier;
- balances: balanceQuery;
- allowances: balanceQuery;
- walletClient: WC;
- orders: OrderContainer[];
preHook?: (chain: chain) => Promise
;
postHook: () => Promise;
account: () => `0x${string}`;
} = $props();
+ let inputTokenSelectorActive = $state(false);
+ let outputTokenSelectorActive = $state(false);
+
const opts = $derived({
- exclusiveFor,
- allocatorId,
- inputTokens,
+ exclusiveFor: store.exclusiveFor,
+ inputTokens: store.inputTokens,
+ outputTokens: store.outputTokens,
preHook,
postHook,
- outputToken,
- inputAmounts,
- outputAmount,
- verifier,
- inputSettler,
+ verifier: store.verifier,
+ lock: {
+ type: store.intentType,
+ allocatorId: store.allocatorId,
+ resetPeriod: ResetPeriod.OneDay
+ },
account
- });
+ } as CreateIntentOptions);
const postHookScroll = async () => {
await postHook();
@@ -85,107 +56,149 @@
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.intentType === "compact"
+ ? 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].token.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) => {
- allowanceCheck = allowanceCheck && a >= inputAmount;
+ for (let i = 0; i < store.inputTokens.length; ++i) {
+ const { token, amount } = store.inputTokens[i];
+ store.allowances[token.chain][token.address].then((a) => {
+ allowanceCheck = allowanceCheck && a >= amount;
});
}
});
let balanceCheckWallet = $state(true);
$effect(() => {
balanceCheckWallet = true;
- if (!balances[inputTokens[0].chain]) {
+ if (!store.balances[store.inputTokens[0].token.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) => {
- balanceCheckWallet = balanceCheckWallet && b >= inputAmount;
+ for (let i = 0; i < store.inputTokens.length; ++i) {
+ const { token, amount } = store.inputTokens[i];
+ store.balances[token.chain][token.address].then((b) => {
+ balanceCheckWallet = balanceCheckWallet && b >= amount;
});
}
});
let balanceCheckCompact = $state(true);
$effect(() => {
balanceCheckCompact = true;
- if (!compactBalances[inputTokens[0].chain]) {
+ if (!store.compactBalances[store.inputTokens[0].token.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) => {
- balanceCheckCompact = balanceCheckCompact && b >= inputAmount;
+ for (let i = 0; i < store.inputTokens.length; ++i) {
+ const { token, amount } = store.inputTokens[i];
+ store.compactBalances[token.chain][token.address].then((b) => {
+ balanceCheckCompact = balanceCheckCompact && b >= amount;
});
}
});
- const allSameChains = $derived(inputTokens.every((v) => inputTokens[0].chain === v.chain));
+ const abstractInputs = $derived.by(() => {
+ const inputs: {
+ name: string;
+ amount: bigint;
+ decimals: number;
+ }[] = [];
+ // Get all unique tokens.
+ const allUniqueNames = [
+ ...new Set(
+ store.inputTokens.map((v) => {
+ return v.token.name;
+ })
+ )
+ ];
+ for (let i = 0; i < allUniqueNames.length; ++i) {
+ const name = allUniqueNames[i];
+ inputs[i] = {
+ name,
+ amount: bigIntSum(
+ ...store.inputTokens.map((v, i) => (v.token.name == name ? v.amount : 0n))
+ ),
+ decimals: store.inputTokens.find((v) => v.token.name == name)!.token.decimals
+ };
+ }
+ return inputs;
+ });
+
+ const numInputChains = $derived.by(() => {
+ const tokenChains = store.inputTokens.map(({ token }) => token.chain);
+ 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;
+ });
-
+
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.
+ {#if inputTokenSelectorActive}
+
+ {/if}
+ {#if outputTokenSelectorActive}
+
+ {/if}
- {#each inputTokens as inputToken, i}
+
You Pay
+ {#each abstractInputs as input, i}
- (showTokenSelector = {
- active: new Date().getTime(),
- input: true,
- index: i
- })}
+ onclick={() => (inputTokenSelectorActive = true)}
>
-
{formatTokenAmount(inputAmounts[i], inputToken)}
-
{inputToken.name.toUpperCase()}
+
{formatTokenAmount(input.amount, input.decimals)}
-
{inputToken.chain}
+
{input.name.toUpperCase()}
{/each}
-
- (showTokenSelector = {
- active: new Date().getTime(),
- input: true,
- index: -1
- })}
- >
- +
-
+ {#if numInputChains > 1}
+
Multichain!
+ {/if}
+ {#if sameChain}
+
SameChain!
+ {/if}
-
- (showTokenSelector = {
- active: new Date().getTime(),
- input: false,
- index: 0
- })}
- >
-
-
-
{formatTokenAmount(outputAmount, outputToken)}
-
{outputToken.name.toUpperCase()}
+
You Receive
+ {#each store.outputTokens as outputToken}
+
(outputTokenSelectorActive = true)}
+ >
+
+
+
+ {formatTokenAmount(outputToken.amount, outputToken.token.decimals)}
+
+
{outputToken.token.name.toUpperCase()}
+
+
{outputToken.token.chain}
- {outputToken.chain}
-
-
-
-
+
+ {/each}
+
+
+
- Verified by
-
- Polymer
- Wormhole
-
+ {#if sameChain}
+ Verified by
+
+ Settler
+
+ {:else}
+ Verified by
+
+ Polymer
+ Wormhole
+
+ {/if}
Exclusive For
@@ -247,29 +257,13 @@
type="text"
class="w-20 rounded border border-gray-800 bg-gray-50 px-2 py-1"
placeholder="0x..."
- bind:value={exclusiveFor}
+ bind:value={store.exclusiveFor}
/>
- {#if !allSameChains}
-
- Not Same Chain Inputs
-
- {:else if inputTokens.length != 1}
-
- Not supported yet
-
- {:else if !allowanceCheck}
+ {#if !allowanceCheck}
{#snippet name()}
Set allowance
@@ -288,7 +282,7 @@
>
Low Balance
- {:else if inputSettler === INPUT_SETTLER_ESCROW_LIFI}
+ {:else if store.intentType === "escrow"}
{#snippet name()}
Execute Open
@@ -297,17 +291,8 @@
Waiting for transaction...
{/snippet}
- {:else if inputSettler === INPUT_SETTLER_COMPACT_LIFI}
-
- {#snippet name()}
- Execute Deposit and Open
- {/snippet}
- {#snippet awaiting()}
- Waiting for transaction...
- {/snippet}
-
{/if}
- {#if inputSettler === INPUT_SETTLER_COMPACT_LIFI && allocatorId !== POLYMER_ALLOCATOR}
+ {#if store.intentType === "compact" && store.allocatorId !== POLYMER_ALLOCATOR}
{#if !balanceCheckCompact}
{:else}
-
+
{#snippet name()}
Sign Order
{/snippet}
@@ -330,4 +315,10 @@
{/if}
+ {#if numInputChains > 1 && store.intentType !== "compact"}
+
+ You'll need to open the order on {numInputChains} chains. Be prepared and do not interrupt the
+ process.
+
+ {/if}
diff --git a/src/lib/screens/ManageDeposit.svelte b/src/lib/screens/ManageDeposit.svelte
index 197cec7..48caaab 100644
--- a/src/lib/screens/ManageDeposit.svelte
+++ b/src/lib/screens/ManageDeposit.svelte
@@ -18,32 +18,15 @@
import AwaitButton from "$lib/components/AwaitButton.svelte";
import { CompactLib } from "$lib/libraries/compactLib";
import { toBigIntWithDecimals } from "$lib/utils/convert";
+ import store from "$lib/state.svelte";
let {
scroll,
- mainnet = $bindable(),
- inputSettler = $bindable(),
- allocatorId = $bindable(),
- inputNumber = $bindable(),
- inputToken = $bindable(),
- compactBalances,
- balances,
- allowances,
- walletClient,
preHook,
postHook,
account
}: {
scroll: (direction: boolean | number) => () => void;
- mainnet: boolean;
- inputSettler: availableInputSettlers;
- allocatorId: availableAllocators;
- inputNumber: number;
- inputToken: Token;
- compactBalances: balanceQuery;
- balances: balanceQuery;
- allowances: balanceQuery;
- walletClient: WC;
preHook: (chain: chain) => Promise
;
postHook: () => Promise;
account: () => `0x${string}`;
@@ -51,18 +34,23 @@
let manageAssetAction: "deposit" | "withdraw" = $state("deposit");
+ let inputNumber = $state(1);
+
let allowance = $state(0n);
- const inputAmount = $derived(toBigIntWithDecimals(inputNumber, inputToken.decimals));
+ const inputAmount = $derived(toBigIntWithDecimals(inputNumber, token.decimals));
$effect(() => {
// Check if allowances contain the chain.
- if (!allowances[inputToken.chain]) {
+ if (!store.allowances[token.chain]) {
allowance = 0n;
return;
}
- allowances[inputToken.chain][inputToken.address].then((a) => {
+ store.allowances[token.chain][token.address].then((a) => {
allowance = a;
});
});
+
+ let selectedTokenIndex = $state(0);
+ const token = $derived(coinList(store.mainnet)[selectedTokenIndex]);
@@ -76,57 +64,57 @@
Network
(mainnet = false)}
+ class:hover:bg-gray-100={store.mainnet !== false}
+ class:font-bold={store.mainnet === false}
+ onclick={() => (store.mainnet = false)}
>
Testnet
(mainnet = true)}
+ class:hover:bg-gray-100={store.mainnet !== true}
+ class:font-bold={store.mainnet === true}
+ onclick={() => (store.mainnet = true)}
>
Mainnet
Input Type
-
+
(inputSettler = INPUT_SETTLER_ESCROW_LIFI)}
+ class=" h-8 rounded-r border border-l-0 px-4"
+ class:hover:bg-gray-100={store.intentType !== "escrow"}
+ class:font-bold={store.intentType === "escrow"}
+ onclick={() => (store.intentType = "escrow")}
>
Escrow
- {#if inputSettler === INPUT_SETTLER_COMPACT_LIFI}
+ {#if store.intentType === "compact"}