UNPKG

0xtrails

Version:

SDK for Trails

288 lines (250 loc) 6.98 kB
import { Relayer } from "@0xsequence/wallet-core" import { useMemo } from "react" import { getChainInfo, getRpcUrl } from "./chains.js" import { Hex } from "ox" import { getSequenceProjectAccessKey, getSequenceEnv } from "./config.js" import { logger } from "./logger.js" import { bigintReplacer } from "./utils.js" import { gnosis, bsc, katana, soneium, xai, etherlink, mainnet, arbitrum, base, baseSepolia, polygon, apeChain, arbitrumNova, avalanche, b3, blast, optimism, } from "viem/chains" export interface MetaTxnReceiptLog { address: string topics: Array<string> data: string } export interface MetaTxnReceipt { id: string status: string revertReason?: string index: number logs: Array<MetaTxnReceiptLog> receipts: Array<MetaTxnReceipt> blockNumber: string txnHash: string txnReceipt: string } export type RelayerOperationStatus = Relayer.OperationStatus export type RpcRelayer = Relayer.Standard.Rpc.RpcRelayer export type RelayerConfig = { hostname: string chainId: number rpcUrl: string } export type RelayerEnv = "local" | "cors-anywhere" | "dev" | "prod" export type RelayerEnvConfig = { env?: RelayerEnv } export type { Relayer } const relayerChainIdToSlug: Record<number, string> = { [arbitrum.id]: "arbitrum", [base.id]: "base", [baseSepolia.id]: "base-sepolia", [polygon.id]: "polygon", [mainnet.id]: "mainnet", [apeChain.id]: "apechain", [arbitrumNova.id]: "arbitrum-nova", [avalanche.id]: "avalanche", [optimism.id]: "optimism", [b3.id]: "b3", [blast.id]: "blast", [gnosis.id]: "gnosis", [soneium.id]: "soneium", [xai.id]: "xai", [bsc.id]: "bsc", [katana.id]: "katana", [etherlink.id]: "etherlink", } const relayerLocalhostUrlMap: Record<number, string> = { [mainnet.id]: "http://0.0.0.0:9969", [arbitrum.id]: "http://0.0.0.0:9997", [base.id]: "http://0.0.0.0:9996", [polygon.id]: "http://0.0.0.0:9999", [optimism.id]: "http://0.0.0.0:9998", [apeChain.id]: "http://0.0.0.0:9995", } export function getRelayerLocalhostUrl(chainId: number): string { return relayerLocalhostUrlMap[chainId] ?? "" } export function getRelayerChainIdToSlug(chainId: number): string { return relayerChainIdToSlug[chainId] ?? "" } // Wrapped fetch function that includes access token header function wrappedFetch( input: RequestInfo, init?: RequestInit, ): Promise<Response> { if (typeof window === "undefined") { return Promise.resolve(new Response("Not available in server environment")) } const headers = new Headers(init?.headers) headers.set("x-access-key", getSequenceProjectAccessKey()) return fetch(input, { ...init, headers, }) } export function getBackupRelayer( chainId: number, ): Relayer.Standard.Rpc.RpcRelayer | undefined { if (chainId === 42161) { return new Relayer.Standard.Rpc.RpcRelayer( "https://a1b4a8c5d856.ngrok.app/", chainId, getRpcUrl(chainId)!, ) } else if (chainId === 8453) { return new Relayer.Standard.Rpc.RpcRelayer( "https://644a6aeb891e.ngrok.app/", chainId, getRpcUrl(chainId)!, ) } return undefined } // TODO: add relayer url to config export function getRelayerUrl( config: RelayerEnvConfig = { env: getSequenceEnv() as RelayerEnv, }, chainId: number, ): string { let relayerUrl = "" if (config.env === "local") { relayerUrl = getRelayerLocalhostUrl(chainId) return relayerUrl } // For cors-anywhere, dev, and production environments const baseUrl = config.env === "cors-anywhere" ? "http://localhost:8080/https://" : config.env === "dev" ? "https://dev-relayer.sequence.app" : "https://" const chainSlug = getRelayerChainIdToSlug(chainId) if (!config.env || config.env === "prod") { if (chainSlug) { relayerUrl = `https://${chainSlug}-relayer.sequence.app` } else { relayerUrl = `${baseUrl}${getChainInfo(chainId)!.name?.replace(" ", "-")}-relayer.sequence.app` } } // Chain-specific relayer endpoints if (config.env === "dev") { if (chainSlug) { relayerUrl = `https://dev-${chainSlug}-relayer.sequence.app` } else { // Fallback to general dev relayer for other chains if V3 is specified but chain not V3-supported relayerUrl = `${baseUrl}${getChainInfo(chainId)!.name?.replace(" ", "-")}-relayer.sequence.app` } } return relayerUrl } export function getRelayer( config: RelayerEnvConfig = { env: getSequenceEnv() as RelayerEnv, }, chainId: number, ): Relayer.Standard.Rpc.RpcRelayer { const chain = getChainInfo(chainId) if (!chain) { throw new Error(`Chain with id ${chainId} not found`) } const rpcUrl = chain.rpcUrls.default.http[0] if (!rpcUrl) { throw new Error(`No RPC URL found for chain ${chainId}`) } const relayerUrl = getRelayerUrl(config, chainId) const relayer = new Relayer.Standard.Rpc.RpcRelayer( relayerUrl, chainId, rpcUrl, wrappedFetch, ) // Monkey-patch the sendMetaTxn method to handle quote correctly // This fixes the double serialization issue where JSON.stringify was adding extra quotes relayer.sendMetaTxn = async ( walletAddress, to, data, _chainId, quote, preconditions, ) => { const rpcCall: any = { walletAddress: walletAddress, contract: to, input: data, } const result = await (relayer as any).client.sendMetaTxn({ call: rpcCall, quote: quote ? typeof (quote as any)._quote === "string" ? (quote as any)._quote : (quote as any)._quote?.toJSON ? (quote as any)._quote.toJSON() : JSON.stringify((quote as any)._quote, bigintReplacer, 2) : undefined, preconditions: preconditions, }) if (!result.status) { logger.console.error("RpcRelayer.relay failed", result) throw new Error(`Relay failed: TxnHash ${result.txnHash}`) } return { opHash: Hex.fromString( result.txnHash.startsWith("0x") ? result.txnHash : `0x${result.txnHash}`, ), } } return relayer } export function useRelayers( config: RelayerEnvConfig = { env: getSequenceEnv() as RelayerEnv, }, ): { relayers: Map<number, Relayer.Standard.Rpc.RpcRelayer> getRelayer: (chainId: number) => Relayer.Standard.Rpc.RpcRelayer getBackupRelayer: ( chainId: number, ) => Relayer.Standard.Rpc.RpcRelayer | undefined } { const relayers = useMemo(() => { const relayerMap = new Map<number, Relayer.Standard.Rpc.RpcRelayer>() return relayerMap }, []) const getCachedRelayer = ( chainId: number, ): Relayer.Standard.Rpc.RpcRelayer => { let relayer = relayers.get(chainId) if (!relayer) { relayer = getRelayer(config, chainId) relayers.set(chainId, relayer) } return relayer } return { relayers, getRelayer: getCachedRelayer, getBackupRelayer, } }