0xtrails
Version:
SDK for Trails
288 lines (250 loc) • 6.98 kB
text/typescript
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,
}
}