import { GelatoRelay, TaskState } from "@gelatonetwork/relay-sdk" import { BaseRelayParams } from "@gelatonetwork/relay-sdk/dist/lib/types" import { Hash, Hex } from "viem" import { backoffRetrier } from "./backoff" type PromiseReturn = T extends Promise ? A : never export type BackoffRetrierOptions = { /** * The number of retries to perform before an error is finally thrown. The * sender will try `retries` number of times to send the transaction only if * it fails. */ retries: number /** * Initial backoff step in milliseconds that will be increased exponentially * for subsequent retry attempts. */ backoffStepMs: number } export default async function relayTransaction( relay: GelatoRelay, relayApiKey: string, request: BaseRelayParams, backoffOptions: BackoffRetrierOptions, ): Promise<{ transactionHash: Hash | undefined }> { const { taskId } = await relay.sponsoredCall(request, relayApiKey) let status: PromiseReturn> while ( // Yes, I'm being terrible. // eslint-disable-next-line no-cond-assign, no-await-in-loop (status = await backoffRetrier< Awaited> >( backoffOptions.retries, backoffOptions.backoffStepMs, )(() => relay.getTaskStatus(taskId)))?.taskState !== TaskState.ExecSuccess ) { if ( status?.taskState === TaskState.ExecReverted || status?.taskState === TaskState.Cancelled ) { throw new Error( `Relayed transaction failed with hash ${status.transactionHash}; ${status.lastCheckMessage ?? "no error data available"}`, ) } // Yes, I am still being terrible. // eslint-disable-next-line no-await-in-loop await new Promise((resolve) => { setTimeout(resolve, 1000) }) } return { transactionHash: status?.transactionHash as Hex } }