-
Notifications
You must be signed in to change notification settings - Fork 0
Asem/v2 109/solana orders #3
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
348160b
73741b7
9ef3a04
14445f4
2f75746
f7e0d0b
33b239e
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -43,6 +43,7 @@ | |
| ] | ||
| }, | ||
| "dependencies": { | ||
| "borsh": "^2.0.0", | ||
| "ky": "^1.12.0", | ||
| "viem": "~2.45.1" | ||
| }, | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -21,6 +21,9 @@ export function toBigIntWithDecimals(value: number, decimals: number): bigint { | |
| } | ||
|
|
||
| export function addressToBytes32(address: `0x${string}`): `0x${string}` { | ||
| if (address.length === 66) return address; // already bytes32 with 0x prefix | ||
| if (address.length === 64) return `0x${address}`; // already bytes32 without 0x | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Does this properly catch 64 length addresses prefixed with 0x? I would rather use something like:
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. yeah i think we should remove |
||
| // Accept only EVM addresses here. | ||
| if (address.length !== 42 && address.length !== 40) { | ||
| throw new Error(`Invalid address length: ${address.length}`); | ||
| } | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -5,40 +5,49 @@ import type { | |
| CompactLock, | ||
| CreateIntentOptions, | ||
| EscrowLock, | ||
| SolanaEscrowLock, | ||
| MultichainOrder, | ||
| StandardOrder, | ||
| TokenContext, | ||
| SolanaStandardOrder, | ||
| } from "../types/index"; | ||
| import { MultichainOrderIntent } from "./multichain"; | ||
| import { StandardOrderIntent } from "./standard"; | ||
| import { buildMandateOutputs } from "./helpers/output-encoding"; | ||
| import { ONE_DAY, ONE_HOUR, inputSettlerForLock } from "./helpers/shared"; | ||
| import { addressToBytes32 } from "../helpers/convert"; | ||
| import { SolanaStandardOrderIntent } from "./solanaStandard"; | ||
|
|
||
|
|
||
| /** | ||
| * @notice Class representing a Li.Fi Intent. Contains intent abstractions and helpers. | ||
| */ | ||
| export class Intent { | ||
| private lock: EscrowLock | CompactLock; | ||
| private lock: EscrowLock | SolanaEscrowLock | CompactLock; | ||
|
|
||
| private user: `0x${string}`; | ||
| private walletUser: `0x${string}`; | ||
| private inputs: TokenContext[]; | ||
| private outputs: TokenContext[]; | ||
| private getOracle: IntentDeps["getOracle"]; | ||
| private getSettler: IntentDeps["getSettler"]; | ||
| private verifier: string; | ||
| private exclusiveFor?: `0x${string}`; | ||
| private outputRecipient?: `0x${string}`; | ||
|
|
||
| private _nonce?: bigint; | ||
| private expiry = ONE_DAY; | ||
| private fillDeadline = 2 * ONE_HOUR; | ||
|
|
||
| constructor(opts: CreateIntentOptions, deps: IntentDeps) { | ||
| this.lock = opts.lock; | ||
| this.user = opts.account; | ||
| this.walletUser = opts.account; | ||
| this.inputs = opts.inputTokens; | ||
| this.outputs = opts.outputTokens; | ||
| this.verifier = opts.verifier; | ||
| this.getOracle = deps.getOracle; | ||
| this.getSettler = deps.getSettler; | ||
| this.exclusiveFor = opts.exclusiveFor; | ||
| this.outputRecipient = opts.outputRecipient; | ||
| } | ||
|
|
||
| numInputChains() { | ||
|
|
@@ -98,12 +107,40 @@ export class Intent { | |
| ]); | ||
|
|
||
| const currentTime = Math.floor(Date.now() / 1000); | ||
| const bytes32Recipient = this.outputRecipient ? addressToBytes32(this.outputRecipient) : addressToBytes32(this.walletUser); | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. No need for |
||
|
|
||
| if (this.lock.type === "solanaEscrow") { | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. See previous message. This does not seem correct. |
||
| const inputOracle = this.getOracle(this.verifier, inputChain)!; | ||
| const solanaStandardOrder: SolanaStandardOrder = { | ||
| user: this.walletUser, | ||
| nonce: this.nonce(), | ||
| originChainId: inputChain, | ||
| fillDeadline: currentTime + this.fillDeadline, | ||
| expires: currentTime + this.expiry, | ||
| inputOracle, | ||
| input: { | ||
| token: firstInput.token.address, | ||
| amount: firstInput.amount, | ||
| }, | ||
|
Comment on lines
+121
to
+124
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Only 1 input? Please add verification for only 1.
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes solana supports only one, at least for now. I agree there should be length verification |
||
| outputs: buildMandateOutputs({ | ||
| exclusiveFor: this.exclusiveFor, | ||
| outputTokens: this.outputs, | ||
| getOracle: this.getOracle, | ||
| getSettler: this.getSettler, | ||
| verifier: this.verifier, | ||
| sameChain: this.isSameChain(), | ||
| bytes32Recipient, | ||
| currentTime, | ||
| }), | ||
| }; | ||
| return new SolanaStandardOrderIntent(inputSettlerForLock(this.lock, false), solanaStandardOrder); | ||
| } | ||
| const inputOracle = this.isSameChain() | ||
| ? COIN_FILLER | ||
| : this.getOracle(this.verifier, inputChain)!; | ||
|
|
||
| const order: StandardOrder = { | ||
| user: this.user, | ||
| user: this.walletUser, | ||
| nonce: this.nonce(), | ||
| originChainId: inputChain, | ||
| fillDeadline: currentTime + this.fillDeadline, | ||
|
|
@@ -114,9 +151,10 @@ export class Intent { | |
| exclusiveFor: this.exclusiveFor, | ||
| outputTokens: this.outputs, | ||
| getOracle: this.getOracle, | ||
| getSettler: this.getSettler, | ||
| verifier: this.verifier, | ||
| sameChain: this.isSameChain(), | ||
| recipient: this.user, | ||
| bytes32Recipient, | ||
| currentTime, | ||
| }), | ||
| }; | ||
|
|
@@ -137,7 +175,7 @@ export class Intent { | |
| this.verifier, | ||
| firstInput.token.chainId, | ||
| )!; | ||
|
|
||
| const bytes32Recipient = this.outputRecipient ? addressToBytes32(this.outputRecipient) : addressToBytes32(this.walletUser); | ||
| const inputs: { chainId: bigint; inputs: [bigint, bigint][] }[] = [ | ||
| ...new Set(this.inputs.map(({ token }) => token.chainId)), | ||
| ].map((chain) => { | ||
|
|
@@ -160,8 +198,9 @@ export class Intent { | |
| }; | ||
| }); | ||
|
|
||
|
|
||
| const order: MultichainOrder = { | ||
| user: this.user, | ||
| user: this.walletUser, | ||
| nonce: this.nonce(), | ||
| fillDeadline: currentTime + this.fillDeadline, | ||
| expires: currentTime + this.expiry, | ||
|
|
@@ -170,9 +209,10 @@ export class Intent { | |
| exclusiveFor: this.exclusiveFor, | ||
| outputTokens: this.outputs, | ||
| getOracle: this.getOracle, | ||
| getSettler: this.getSettler, | ||
| verifier: this.verifier, | ||
| sameChain: false, | ||
| recipient: this.user, | ||
| bytes32Recipient, | ||
| currentTime, | ||
| }), | ||
| inputs, | ||
|
|
@@ -181,7 +221,7 @@ export class Intent { | |
| return new MultichainOrderIntent( | ||
| inputSettlerForLock(this.lock, true), | ||
| order, | ||
| this.lock, | ||
| this.lock as EscrowLock | CompactLock, | ||
| ); | ||
| } | ||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -2,6 +2,7 @@ import type { | |
| CompactLock, | ||
| EscrowLock, | ||
| MultichainOrder, | ||
| SolanaStandardOrder, | ||
| StandardOrder, | ||
| } from "../types/index"; | ||
| import { | ||
|
|
@@ -11,14 +12,20 @@ import { | |
| import { ResetPeriod } from "../compact/idLib"; | ||
| import { MultichainOrderIntent } from "./multichain"; | ||
| import { StandardOrderIntent } from "./standard"; | ||
| import { SolanaStandardOrderIntent } from "./solanaStandard"; | ||
|
|
||
| type OrderLike = StandardOrder | MultichainOrder; | ||
| type OrderLike = StandardOrder | SolanaStandardOrder | MultichainOrder; | ||
|
|
||
| type StandardOrderToIntentOptions = { | ||
| inputSettler: `0x${string}`; | ||
| order: StandardOrder; | ||
| }; | ||
|
|
||
| type SolanaStandardOrderToIntentOptions = { | ||
| inputSettler: `0x${string}`; | ||
| order: SolanaStandardOrder; | ||
| }; | ||
|
|
||
| type MultichainOrderToIntentOptions = { | ||
| inputSettler: `0x${string}`; | ||
| order: MultichainOrder; | ||
|
|
@@ -47,31 +54,40 @@ function inferLock(inputSettler: `0x${string}`): EscrowLock | CompactLock { | |
| } | ||
|
|
||
| export function isStandardOrder(order: OrderLike): order is StandardOrder { | ||
| return "originChainId" in order; | ||
| return "originChainId" in order && "inputs" in order; | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There is no way this works. Because multichain order has inputs.
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
| } | ||
|
|
||
| export function isSolanaStandardOrder( | ||
| order: OrderLike, | ||
| ): order is SolanaStandardOrder { | ||
| return "originChainId" in order && "input" in order; | ||
| } | ||
|
|
||
| export function orderToIntent( | ||
| options: StandardOrderToIntentOptions, | ||
| ): StandardOrderIntent; | ||
| export function orderToIntent( | ||
| options: SolanaStandardOrderToIntentOptions, | ||
| ): SolanaStandardOrderIntent; | ||
| export function orderToIntent( | ||
| options: MultichainOrderToIntentOptions, | ||
| ): MultichainOrderIntent; | ||
| export function orderToIntent( | ||
| options: OrderToIntentOptions, | ||
| ): StandardOrderIntent | MultichainOrderIntent; | ||
| ): StandardOrderIntent | SolanaStandardOrderIntent | MultichainOrderIntent; | ||
| export function orderToIntent( | ||
| options: | ||
| | StandardOrderToIntentOptions | ||
| | SolanaStandardOrderToIntentOptions | ||
| | MultichainOrderToIntentOptions | ||
| | OrderToIntentOptions, | ||
| ): StandardOrderIntent | MultichainOrderIntent { | ||
| ): StandardOrderIntent | SolanaStandardOrderIntent | MultichainOrderIntent { | ||
| const { inputSettler, order } = options; | ||
| if (isStandardOrder(order)) { | ||
| return new StandardOrderIntent(inputSettler, order); | ||
| } | ||
| return new MultichainOrderIntent( | ||
| inputSettler, | ||
| order, | ||
| (options as MultichainOrderToIntentOptions).lock ?? inferLock(inputSettler), | ||
| ); | ||
|
|
||
| if (isStandardOrder(order)) return new StandardOrderIntent(inputSettler, order); | ||
| if (isSolanaStandardOrder(order)) return new SolanaStandardOrderIntent(inputSettler, order); | ||
|
|
||
| const lock = "lock" in options ? options.lock ?? inferLock(inputSettler) : inferLock(inputSettler); | ||
|
|
||
| return new MultichainOrderIntent(inputSettler, order, lock); | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -18,18 +18,20 @@ export function buildMandateOutputs(options: { | |
| exclusiveFor?: `0x${string}`; | ||
| outputTokens: TokenContext[]; | ||
| getOracle: IntentDeps["getOracle"]; | ||
| getSettler?: IntentDeps["getSettler"]; | ||
| verifier: CoreVerifier; | ||
| sameChain: boolean; | ||
| recipient: `0x${string}`; | ||
| bytes32Recipient: `0x${string}`; | ||
| currentTime: number; | ||
| }): MandateOutput[] { | ||
| const { | ||
| exclusiveFor, | ||
| outputTokens, | ||
| getOracle, | ||
| getSettler, | ||
| verifier, | ||
| sameChain, | ||
| recipient, | ||
| bytes32Recipient, | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't like using
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. i agree |
||
| currentTime, | ||
| } = options; | ||
|
|
||
|
|
@@ -50,8 +52,8 @@ export function buildMandateOutputs(options: { | |
| ); | ||
| } | ||
|
|
||
| const outputSettler = COIN_FILLER; | ||
| return outputTokens.map(({ token, amount }) => { | ||
| const outputSettler = getSettler?.(token.chainId) ?? COIN_FILLER; | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Potentially bad fallback for Solana consumers |
||
| const outputOracle = sameChain | ||
| ? addressToBytes32(outputSettler) | ||
| : addressToBytes32(getOracle(verifier, token.chainId)!); | ||
|
|
@@ -61,7 +63,7 @@ export function buildMandateOutputs(options: { | |
| chainId: token.chainId, | ||
| token: addressToBytes32(token.address), | ||
| amount: amount, | ||
| recipient: addressToBytes32(recipient), | ||
| recipient: bytes32Recipient, | ||
| callbackData: "0x", | ||
| context, | ||
| }; | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -3,8 +3,9 @@ import { | |
| INPUT_SETTLER_ESCROW_LIFI, | ||
| MULTICHAIN_INPUT_SETTLER_COMPACT, | ||
| MULTICHAIN_INPUT_SETTLER_ESCROW, | ||
| SOLANA_INPUT_SETTLER_ESCROW, | ||
| } from "../../constants"; | ||
| import type { CompactLock, EscrowLock } from "../../types"; | ||
| import type { CompactLock, EscrowLock, SolanaEscrowLock } from "../../types"; | ||
|
|
||
| export const ONE_MINUTE = 60; | ||
| export const ONE_HOUR = 60 * ONE_MINUTE; | ||
|
|
@@ -15,7 +16,7 @@ export function selectAllBut<T>(arr: T[], index: number): T[] { | |
| } | ||
|
|
||
| export function inputSettlerForLock( | ||
| lock: EscrowLock | CompactLock, | ||
| lock: EscrowLock | CompactLock | SolanaEscrowLock, | ||
| multichain: boolean, | ||
| ) { | ||
| if (lock.type === "compact" && multichain === false) | ||
|
|
@@ -26,6 +27,8 @@ export function inputSettlerForLock( | |
| return INPUT_SETTLER_ESCROW_LIFI; | ||
| if (lock.type === "escrow" && multichain === true) | ||
| return MULTICHAIN_INPUT_SETTLER_ESCROW; | ||
| if (lock.type === "solanaEscrow" && multichain === false) | ||
| return SOLANA_INPUT_SETTLER_ESCROW; | ||
|
Comment on lines
+30
to
+31
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why do we add a new lock type for Solana? Why not add chain type or similar as the differentiator?
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Since we're following the structure anyway i believe it's a good idea to commit to everything
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. what i'm also okay with is adding a field inside the lock that identifies the chain |
||
|
|
||
| throw new Error( | ||
| `Not supported | multichain: ${multichain}, type: ${lock.type}`, | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.