From 9d6528004a61e2d01b0cb6d022df43a80d47f21d Mon Sep 17 00:00:00 2001 From: MarkacRobi Date: Mon, 18 Nov 2024 18:36:00 +0100 Subject: [PATCH] feat: cancel intent order + retry postExecution --- README.md | 40 ++++++++++++++++++- src/constants.ts | 2 + src/services/EvmIntentService.ts | 30 ++++++++++++++ src/services/IntentService.ts | 67 ++++++++++++++++++++++++-------- src/services/SolverApiService.ts | 17 ++++---- src/services/SuiIntentService.ts | 47 ++++++++++++++++++++++ src/utils.ts | 24 ++++++++++++ 7 files changed, 201 insertions(+), 26 deletions(-) create mode 100644 src/utils.ts diff --git a/README.md b/README.md index 0c94406..d8f1593 100644 --- a/README.md +++ b/README.md @@ -212,6 +212,42 @@ if (isAllowanceValid.ok) { } ``` +## Cancel Intent Order + +Active Intent Order can be cancelled using order ID obtained as explained in [Get Intent Order](#get-intent-order). + +Example cancel order: + +```typescript +import { IntentService, SwapOrder, EvmProvider } from "@balanced/solver-sdk" + +const evmProvider = new EvmProvider("0x601020c5797Cdd34f64476b9bf887a353150Cb9a", (window as any).ethereum) +const intentOrder: Result = await IntentService.getOrder( + "0xabcdefasdasdsafssadasdsadsadasdsadasdsadsa", + IntentService.getChainConfig("arb").intentContract, + evmProvider, +) + +if (intentOrder.ok) { + const cancelResult: Result = await IntentService.cancelIntentOrder( + intentOrder.value.id, + "arb", + evmProvider, + ) + + if (cancelResult.ok) { + const txHash = cancelResult.value + .. + } else { + // handle error + } +} else { + // handle error +} + + +``` + ## Get Intent Order After the Intent order is created (`executeIntentOrder`), the resulting `txHash` can be used to query created on-chain order data. @@ -221,10 +257,10 @@ you should invoke `IntentService.getOrder(..)` function. Example get order: ```typescript -import { IntentService } from "@balanced/solver-sdk" +import { IntentService, SwapOrder, EvmProvider } from "@balanced/solver-sdk" const evmProvider = new EvmProvider("0x601020c5797Cdd34f64476b9bf887a353150Cb9a", (window as any).ethereum) -const intentOrder: SwapOrder = await IntentService.getOrder( +const intentOrder: Result = await IntentService.getOrder( "0xabcdefasdasdsafssadasdsadsadasdsadasdsadsa", IntentService.getChainConfig("arb").intentContract, evmProvider, diff --git a/src/constants.ts b/src/constants.ts index 443e355..bd24266 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -1,5 +1,7 @@ import type { ChainConfig, ChainName, EvmChainConfig, SuiChainConfig } from "./types.js" +export const DEFAULT_MAX_RETRY = 3 +export const DEFAULT_RETRY_DELAY_MS = 2000 export const SOLVER_API_ENDPOINT = "localhost:3000" // TODO export const chainConfig: Record = { diff --git a/src/services/EvmIntentService.ts b/src/services/EvmIntentService.ts index 326260e..946eb0b 100644 --- a/src/services/EvmIntentService.ts +++ b/src/services/EvmIntentService.ts @@ -129,6 +129,36 @@ export class EvmIntentService { } } + /** + * Cancel EVM intent order + * @param orderId - Intent order ID + * @param chainConfig - EVM chain config + * @param provider - EVM provider + */ + static async cancelIntentOrder( + orderId: bigint, + chainConfig: EvmChainConfig, + provider: EvmProvider, + ): Promise> { + try { + return { + ok: true, + value: await provider.walletClient.writeContract({ + address: chainConfig.intentContract, + abi: intentAbi, + functionName: "cancel", + args: [orderId], + chain: undefined, + }), + } + } catch (e) { + return { + ok: false, + error: e, + } + } + } + /** * Retrieve Intent order * @param txHash - Transaction hash diff --git a/src/services/IntentService.ts b/src/services/IntentService.ts index 8eb6e45..bf099a3 100644 --- a/src/services/IntentService.ts +++ b/src/services/IntentService.ts @@ -20,6 +20,7 @@ import { type GetChainProviderType, SwapOrder, type ChainProvider, + type ChainProviderType, } from "../entities/index.js" import { EvmIntentService } from "./EvmIntentService.js" import { SuiIntentService } from "./SuiIntentService.js" @@ -184,23 +185,8 @@ export class IntentService { provider: GetChainProviderType, ): Promise> { try { - const fromChainConfig = chainConfig[payload.fromChain] - - if (!fromChainConfig) { - return { - ok: false, - error: new Error(`Unsupported fromChain: ${payload.fromChain}`), - } - } - - const toChainConfig = chainConfig[payload.toChain] - - if (!toChainConfig) { - return { - ok: false, - error: new Error(`Unsupported toChain: ${payload.toChain}`), - } - } + const fromChainConfig = IntentService.getChainConfig(payload.fromChain) + const toChainConfig = IntentService.getChainConfig(payload.toChain) if (isEvmChainConfig(fromChainConfig)) { if (provider instanceof EvmProvider) { @@ -234,6 +220,53 @@ export class IntentService { } } + /** + * Cancel active Intent Order + * @param orderId - Intent Order ID (retrievable by getOrder) + * @param chain - Chain on which Order was created on + * @param provider - EVM or SUI provider + * @return string - Transaction Hash + */ + public static async cancelIntentOrder( + orderId: bigint, + chain: ChainName, + provider: ChainProviderType, + ): Promise> { + try { + const chainConfig = IntentService.getChainConfig(chain) + + if (isEvmChainConfig(chainConfig)) { + if (provider instanceof EvmProvider) { + return EvmIntentService.cancelIntentOrder(orderId, chainConfig, provider) + } else { + return { + ok: false, + error: new Error(`[IntentService.cancelIntentOrder] provider should be of type EvmProvider`), + } + } + } else if (isSuiChainConfig(chainConfig)) { + if (provider instanceof SuiProvider) { + return SuiIntentService.cancelIntentOrder(orderId, chainConfig, provider) + } else { + return { + ok: false, + error: new Error(`[IntentService.cancelIntentOrder] provider should be of type SuiProvider`), + } + } + } else { + return { + ok: false, + error: new Error(`${chain} chain not supported`), + } + } + } catch (e) { + return { + ok: false, + error: e, + } + } + } + /** * Retrieve Intent order * @param txHash - Transaction hash diff --git a/src/services/SolverApiService.ts b/src/services/SolverApiService.ts index 9a585a3..a0cb2fb 100644 --- a/src/services/SolverApiService.ts +++ b/src/services/SolverApiService.ts @@ -12,6 +12,7 @@ import { type IntentQuoteResponseRaw, } from "../types.js" import invariant from "tiny-invariant" +import { retry } from "../utils.js" export class SolverApiService { private constructor() {} @@ -94,13 +95,15 @@ export class SolverApiService { intentExecutionRequest: IntentExecutionRequest, ): Promise> { try { - const response = await fetch(`${SOLVER_API_ENDPOINT}/execute`, { - method: "POST", - headers: { - "Content-Type": "application/json", - }, - body: JSON.stringify(intentExecutionRequest), - }) + const response = await retry(() => + fetch(`${SOLVER_API_ENDPOINT}/execute`, { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify(intentExecutionRequest), + }), + ) if (!response.ok) { return { diff --git a/src/services/SuiIntentService.ts b/src/services/SuiIntentService.ts index 5d34eae..e191ef3 100644 --- a/src/services/SuiIntentService.ts +++ b/src/services/SuiIntentService.ts @@ -89,6 +89,53 @@ export class SuiIntentService { } } + /** + * Cancel SUI intent order + * @param orderId - Intent order ID + * @param chainConfig - SUI chain config + * @param provider - SUI provider + */ + public static async cancelIntentOrder( + orderId: bigint, + chainConfig: SuiChainConfig, + provider: SuiProvider, + ): Promise> { + try { + const tx = new Transaction() + + tx.moveCall({ + target: `${chainConfig.packageId}::main::cancel`, + arguments: [tx.object(chainConfig.storageId), tx.pure.string(orderId.toString())], + }) + + const signerAccount = provider.account + const chain = signerAccount.chains[0] + + if (!chain) { + return { + ok: false, + error: new Error("[SuiIntentService.cancelIntentOrder] Chain undefined in signerAccount"), + } + } + + const result = await signAndExecuteTransaction(provider.wallet, { + transaction: tx, + account: provider.account, + chain: provider.account.chains[0]!, + }) + + return { + ok: true, + value: result.digest, + } + } catch (e) { + return { + ok: false, + error: e, + } + } + } + public static async getNativeCoin( tx: Transaction, intent: SwapOrder, diff --git a/src/utils.ts b/src/utils.ts new file mode 100644 index 0000000..dc7d910 --- /dev/null +++ b/src/utils.ts @@ -0,0 +1,24 @@ +import { DEFAULT_MAX_RETRY, DEFAULT_RETRY_DELAY_MS } from "./constants.js" + +export async function retry( + action: (retryCount: number) => Promise, + retryCount: number = DEFAULT_MAX_RETRY, + delayMs = DEFAULT_RETRY_DELAY_MS, +): Promise { + do { + try { + return await action(retryCount) + } catch (e) { + retryCount-- + + if (retryCount <= 0) { + console.error(`Failed to perform operation even after ${DEFAULT_MAX_RETRY} attempts.. Throwing origin error..`) + throw e + } + } + + await new Promise((resolve) => setTimeout(resolve, delayMs)) + } while (retryCount > 0) + + throw new Error(`Retry exceeded MAX_RETRY_DEFAULT=${DEFAULT_MAX_RETRY}`) +}