diff --git a/.circleci/config.yml b/.circleci/config.yml
new file mode 100644
index 000000000..ad53a8e49
--- /dev/null
+++ b/.circleci/config.yml
@@ -0,0 +1,26 @@
+# Use the latest 2.1 version of CircleCI pipeline process engine.
+# See: https://circleci.com/docs/configuration-reference
+
+version: 2.1
+executors:
+ my-custom-executor:
+ docker:
+ - image: cimg/base:stable
+ auth:
+ # ensure you have first added these secrets
+ # visit app.circleci.com/settings/project/github/Dargon789/foundry/environment-variables
+ username: $DOCKER_HUB_USER
+ password: $DOCKER_HUB_PASSWORD
+jobs:
+ web3-defi-game-project-:
+
+ executor: my-custom-executor
+ steps:
+ - checkout
+ - run: |
+ # echo Hello, World!
+
+workflows:
+ my-custom-workflow:
+ jobs:
+ - web3-defi-game-project-
diff --git a/.github/workflows/on_pr_pnpm-format-label.yml b/.github/workflows/on_pr_pnpm-format-label.yml
index 84fb27cb3..b9dda862b 100644
--- a/.github/workflows/on_pr_pnpm-format-label.yml
+++ b/.github/workflows/on_pr_pnpm-format-label.yml
@@ -12,6 +12,9 @@ jobs:
rm:
if: ${{ github.event.label.name == 'pnpm format' }}
+ permissions:
+ contents: read
+ issues: write
runs-on: ubuntu-latest
steps:
- name: Remove the label
diff --git a/extras/docs/next-env.d.ts b/extras/docs/next-env.d.ts
new file mode 100644
index 000000000..830fb594c
--- /dev/null
+++ b/extras/docs/next-env.d.ts
@@ -0,0 +1,6 @@
+///
+///
+///
+
+// NOTE: This file should not be edited
+// see https://nextjs.org/docs/app/api-reference/config/typescript for more information.
diff --git a/extras/web/next-env.d.ts b/extras/web/next-env.d.ts
new file mode 100644
index 000000000..830fb594c
--- /dev/null
+++ b/extras/web/next-env.d.ts
@@ -0,0 +1,6 @@
+///
+///
+///
+
+// NOTE: This file should not be edited
+// see https://nextjs.org/docs/app/api-reference/config/typescript for more information.
diff --git a/packages/services/relayer/src/preconditions/codec.ts b/packages/services/relayer/src/preconditions/codec.ts
new file mode 100644
index 000000000..74f83154b
--- /dev/null
+++ b/packages/services/relayer/src/preconditions/codec.ts
@@ -0,0 +1,190 @@
+import { Address } from 'ox'
+import {
+ Precondition,
+ NativeBalancePrecondition,
+ Erc20BalancePrecondition,
+ Erc20ApprovalPrecondition,
+ Erc721OwnershipPrecondition,
+ Erc721ApprovalPrecondition,
+ Erc1155BalancePrecondition,
+ Erc1155ApprovalPrecondition,
+} from './types.js'
+
+export interface TransactionPrecondition {
+ type: string
+ chainId: number
+ ownerAddress: string
+ tokenAddress: string
+ minAmount: bigint
+}
+
+export function decodePreconditions(preconditions: TransactionPrecondition[]): Precondition[] {
+ const decodedPreconditions: Precondition[] = []
+
+ for (const p of preconditions) {
+ const decoded = decodePrecondition(p)
+ if (decoded) {
+ decodedPreconditions.push(decoded)
+ }
+ }
+
+ return decodedPreconditions
+}
+
+export function decodePrecondition(p: TransactionPrecondition): Precondition | undefined {
+ if (!p) {
+ return undefined
+ }
+
+ let precondition: Precondition | undefined
+
+ try {
+ switch (p.type) {
+ case 'native-balance':
+ precondition = new NativeBalancePrecondition(Address.from(p.ownerAddress), p.minAmount, undefined)
+ break
+
+ case 'erc20-balance':
+ precondition = new Erc20BalancePrecondition(
+ Address.from(p.ownerAddress),
+ Address.from(p.tokenAddress),
+ p.minAmount,
+ undefined,
+ )
+ break
+
+ case 'erc20-approval':
+ precondition = new Erc20ApprovalPrecondition(
+ Address.from(p.ownerAddress),
+ Address.from(p.tokenAddress),
+ Address.from(p.ownerAddress),
+ p.minAmount,
+ )
+ break
+
+ case 'erc721-ownership':
+ precondition = new Erc721OwnershipPrecondition(
+ Address.from(p.ownerAddress),
+ Address.from(p.tokenAddress),
+ BigInt(0),
+ true,
+ )
+ break
+
+ case 'erc721-approval':
+ precondition = new Erc721ApprovalPrecondition(
+ Address.from(p.ownerAddress),
+ Address.from(p.tokenAddress),
+ BigInt(0),
+ Address.from(p.ownerAddress),
+ )
+ break
+
+ case 'erc1155-balance':
+ precondition = new Erc1155BalancePrecondition(
+ Address.from(p.ownerAddress),
+ Address.from(p.tokenAddress),
+ BigInt(0),
+ p.minAmount,
+ undefined,
+ )
+ break
+
+ case 'erc1155-approval':
+ precondition = new Erc1155ApprovalPrecondition(
+ Address.from(p.ownerAddress),
+ Address.from(p.tokenAddress),
+ BigInt(0),
+ Address.from(p.ownerAddress),
+ p.minAmount,
+ )
+ break
+
+ default:
+ return undefined
+ }
+
+ const error = precondition.isValid()
+ if (error) {
+ console.warn(`Invalid precondition: ${error.message}`)
+ return undefined
+ }
+
+ return precondition
+ } catch (e) {
+ console.warn(`Failed to decode precondition: ${e}`)
+ return undefined
+ }
+}
+
+export function encodePrecondition(p: Precondition): string {
+ const data: any = {}
+
+ switch (p.type()) {
+ case 'native-balance': {
+ const native = p as NativeBalancePrecondition
+ data.address = native.address.toString()
+ if (native.min !== undefined) data.min = native.min.toString()
+ if (native.max !== undefined) data.max = native.max.toString()
+ break
+ }
+
+ case 'erc20-balance': {
+ const erc20 = p as Erc20BalancePrecondition
+ data.address = erc20.address.toString()
+ data.token = erc20.token.toString()
+ if (erc20.min !== undefined) data.min = erc20.min.toString()
+ if (erc20.max !== undefined) data.max = erc20.max.toString()
+ break
+ }
+
+ case 'erc20-approval': {
+ const erc20 = p as Erc20ApprovalPrecondition
+ data.address = erc20.address.toString()
+ data.token = erc20.token.toString()
+ data.operator = erc20.operator.toString()
+ data.min = erc20.min.toString()
+ break
+ }
+
+ case 'erc721-ownership': {
+ const erc721 = p as Erc721OwnershipPrecondition
+ data.address = erc721.address.toString()
+ data.token = erc721.token.toString()
+ data.tokenId = erc721.tokenId.toString()
+ if (erc721.owned !== undefined) data.owned = erc721.owned
+ break
+ }
+
+ case 'erc721-approval': {
+ const erc721 = p as Erc721ApprovalPrecondition
+ data.address = erc721.address.toString()
+ data.token = erc721.token.toString()
+ data.tokenId = erc721.tokenId.toString()
+ data.operator = erc721.operator.toString()
+ break
+ }
+
+ case 'erc1155-balance': {
+ const erc1155 = p as Erc1155BalancePrecondition
+ data.address = erc1155.address.toString()
+ data.token = erc1155.token.toString()
+ data.tokenId = erc1155.tokenId.toString()
+ if (erc1155.min !== undefined) data.min = erc1155.min.toString()
+ if (erc1155.max !== undefined) data.max = erc1155.max.toString()
+ break
+ }
+
+ case 'erc1155-approval': {
+ const erc1155 = p as Erc1155ApprovalPrecondition
+ data.address = erc1155.address.toString()
+ data.token = erc1155.token.toString()
+ data.tokenId = erc1155.tokenId.toString()
+ data.operator = erc1155.operator.toString()
+ data.min = erc1155.min.toString()
+ break
+ }
+ }
+
+ return JSON.stringify(data)
+}
diff --git a/packages/services/relayer/src/preconditions/index.ts b/packages/services/relayer/src/preconditions/index.ts
new file mode 100644
index 000000000..6bb6376ef
--- /dev/null
+++ b/packages/services/relayer/src/preconditions/index.ts
@@ -0,0 +1,3 @@
+export * from './types.js'
+export * from './codec.js'
+export * from './selectors.js'
diff --git a/packages/services/relayer/src/preconditions/selectors.ts b/packages/services/relayer/src/preconditions/selectors.ts
new file mode 100644
index 000000000..d5985a862
--- /dev/null
+++ b/packages/services/relayer/src/preconditions/selectors.ts
@@ -0,0 +1,38 @@
+import { Precondition, NativeBalancePrecondition, Erc20BalancePrecondition } from './types.js'
+import { TransactionPrecondition, decodePreconditions } from './codec.js'
+
+export function extractChainID(precondition: TransactionPrecondition): number | undefined {
+ if (!precondition) {
+ return undefined
+ }
+
+ return precondition.chainId
+}
+
+export function extractSupportedPreconditions(preconditions: TransactionPrecondition[]): Precondition[] {
+ if (!preconditions || preconditions.length === 0) {
+ return []
+ }
+
+ return decodePreconditions(preconditions)
+}
+
+export function extractNativeBalancePreconditions(
+ preconditions: TransactionPrecondition[],
+): NativeBalancePrecondition[] {
+ if (!preconditions || preconditions.length === 0) {
+ return []
+ }
+
+ const decoded = decodePreconditions(preconditions)
+ return decoded.filter((p): p is NativeBalancePrecondition => p.type() === 'native-balance')
+}
+
+export function extractERC20BalancePreconditions(preconditions: TransactionPrecondition[]): Erc20BalancePrecondition[] {
+ if (!preconditions || preconditions.length === 0) {
+ return []
+ }
+
+ const decoded = decodePreconditions(preconditions)
+ return decoded.filter((p): p is Erc20BalancePrecondition => p.type() === 'erc20-balance')
+}
diff --git a/packages/services/relayer/src/preconditions/types.ts b/packages/services/relayer/src/preconditions/types.ts
new file mode 100644
index 000000000..23a9db22c
--- /dev/null
+++ b/packages/services/relayer/src/preconditions/types.ts
@@ -0,0 +1,201 @@
+import { Address } from 'ox'
+
+export interface Precondition {
+ type(): string
+ isValid(): Error | undefined
+}
+
+export class NativeBalancePrecondition implements Precondition {
+ constructor(
+ public readonly address: Address.Address,
+ public readonly min?: bigint,
+ public readonly max?: bigint,
+ ) {}
+
+ type(): string {
+ return 'native-balance'
+ }
+
+ isValid(): Error | undefined {
+ if (!this.address) {
+ return new Error('address is required')
+ }
+ if (this.min !== undefined && this.max !== undefined && this.min > this.max) {
+ return new Error('min balance cannot be greater than max balance')
+ }
+ return undefined
+ }
+}
+
+export class Erc20BalancePrecondition implements Precondition {
+ constructor(
+ public readonly address: Address.Address,
+ public readonly token: Address.Address,
+ public readonly min?: bigint,
+ public readonly max?: bigint,
+ ) {}
+
+ type(): string {
+ return 'erc20-balance'
+ }
+
+ isValid(): Error | undefined {
+ if (!this.address) {
+ return new Error('address is required')
+ }
+ if (!this.token) {
+ return new Error('token address is required')
+ }
+ if (this.min !== undefined && this.max !== undefined && this.min > this.max) {
+ return new Error('min balance cannot be greater than max balance')
+ }
+ return undefined
+ }
+}
+
+export class Erc20ApprovalPrecondition implements Precondition {
+ constructor(
+ public readonly address: Address.Address,
+ public readonly token: Address.Address,
+ public readonly operator: Address.Address,
+ public readonly min: bigint,
+ ) {}
+
+ type(): string {
+ return 'erc20-approval'
+ }
+
+ isValid(): Error | undefined {
+ if (!this.address) {
+ return new Error('address is required')
+ }
+ if (!this.token) {
+ return new Error('token address is required')
+ }
+ if (!this.operator) {
+ return new Error('operator address is required')
+ }
+ if (this.min === undefined) {
+ return new Error('min approval amount is required')
+ }
+ return undefined
+ }
+}
+
+export class Erc721OwnershipPrecondition implements Precondition {
+ constructor(
+ public readonly address: Address.Address,
+ public readonly token: Address.Address,
+ public readonly tokenId: bigint,
+ public readonly owned?: boolean,
+ ) {}
+
+ type(): string {
+ return 'erc721-ownership'
+ }
+
+ isValid(): Error | undefined {
+ if (!this.address) {
+ return new Error('address is required')
+ }
+ if (!this.token) {
+ return new Error('token address is required')
+ }
+ if (this.tokenId === undefined) {
+ return new Error('tokenId is required')
+ }
+ return undefined
+ }
+}
+
+export class Erc721ApprovalPrecondition implements Precondition {
+ constructor(
+ public readonly address: Address.Address,
+ public readonly token: Address.Address,
+ public readonly tokenId: bigint,
+ public readonly operator: Address.Address,
+ ) {}
+
+ type(): string {
+ return 'erc721-approval'
+ }
+
+ isValid(): Error | undefined {
+ if (!this.address) {
+ return new Error('address is required')
+ }
+ if (!this.token) {
+ return new Error('token address is required')
+ }
+ if (this.tokenId === undefined) {
+ return new Error('tokenId is required')
+ }
+ if (!this.operator) {
+ return new Error('operator address is required')
+ }
+ return undefined
+ }
+}
+
+export class Erc1155BalancePrecondition implements Precondition {
+ constructor(
+ public readonly address: Address.Address,
+ public readonly token: Address.Address,
+ public readonly tokenId: bigint,
+ public readonly min?: bigint,
+ public readonly max?: bigint,
+ ) {}
+
+ type(): string {
+ return 'erc1155-balance'
+ }
+
+ isValid(): Error | undefined {
+ if (!this.address) {
+ return new Error('address is required')
+ }
+ if (!this.token) {
+ return new Error('token address is required')
+ }
+ if (this.tokenId === undefined) {
+ return new Error('tokenId is required')
+ }
+ if (this.min !== undefined && this.max !== undefined && this.min > this.max) {
+ return new Error('min balance cannot be greater than max balance')
+ }
+ return undefined
+ }
+}
+
+export class Erc1155ApprovalPrecondition implements Precondition {
+ constructor(
+ public readonly address: Address.Address,
+ public readonly token: Address.Address,
+ public readonly tokenId: bigint,
+ public readonly operator: Address.Address,
+ public readonly min: bigint,
+ ) {}
+
+ type(): string {
+ return 'erc1155-approval'
+ }
+
+ isValid(): Error | undefined {
+ if (!this.address) {
+ return new Error('address is required')
+ }
+ if (!this.token) {
+ return new Error('token address is required')
+ }
+ if (this.tokenId === undefined) {
+ return new Error('tokenId is required')
+ }
+ if (!this.operator) {
+ return new Error('operator address is required')
+ }
+ if (this.min === undefined) {
+ return new Error('min approval amount is required')
+ }
+ return undefined
+ }
+}
diff --git a/packages/services/relayer/src/relayer/index.ts b/packages/services/relayer/src/relayer/index.ts
new file mode 100644
index 000000000..52362d5c9
--- /dev/null
+++ b/packages/services/relayer/src/relayer/index.ts
@@ -0,0 +1,60 @@
+import { Hex } from 'ox'
+import type { FeeToken, GetMetaTxnReceiptReturn } from './rpc-relayer/relayer.gen.js'
+
+export * from './rpc-relayer/index.js'
+export * from './standard/index.js'
+export * from './relayer.js'
+export type { FeeToken } from './rpc-relayer/relayer.gen.js'
+
+export interface FeeOption {
+ token: FeeToken
+ to: string
+ value: string
+ gasLimit: number
+}
+
+export interface FeeQuote {
+ _tag: 'FeeQuote'
+ _quote: unknown
+}
+
+export type OperationUnknownStatus = {
+ status: 'unknown'
+ reason?: string
+}
+
+export type OperationQueuedStatus = {
+ status: 'queued'
+ reason?: string
+}
+
+export type OperationPendingStatus = {
+ status: 'pending'
+ reason?: string
+}
+
+export type OperationPendingPreconditionStatus = {
+ status: 'pending-precondition'
+ reason?: string
+}
+
+export type OperationConfirmedStatus = {
+ status: 'confirmed'
+ transactionHash: Hex.Hex
+ data?: GetMetaTxnReceiptReturn
+}
+
+export type OperationFailedStatus = {
+ status: 'failed'
+ transactionHash?: Hex.Hex
+ reason: string
+ data?: GetMetaTxnReceiptReturn
+}
+
+export type OperationStatus =
+ | OperationUnknownStatus
+ | OperationQueuedStatus
+ | OperationPendingStatus
+ | OperationPendingPreconditionStatus
+ | OperationConfirmedStatus
+ | OperationFailedStatus
diff --git a/packages/services/relayer/src/relayer/relayer.ts b/packages/services/relayer/src/relayer/relayer.ts
new file mode 100644
index 000000000..3ed5a6962
--- /dev/null
+++ b/packages/services/relayer/src/relayer/relayer.ts
@@ -0,0 +1,37 @@
+import { Address, Hex } from 'ox'
+import { FeeToken } from './rpc-relayer/relayer.gen.js'
+import { FeeOption, FeeQuote, OperationStatus } from './index.js'
+import { Payload, Precondition } from '@0xsequence/wallet-primitives'
+
+export interface Relayer {
+ kind: 'relayer'
+
+ type: string
+ id: string
+
+ isAvailable(wallet: Address.Address, chainId: number): Promise
+
+ feeTokens(): Promise<{ isFeeRequired: boolean; tokens?: FeeToken[]; paymentAddress?: Address.Address }>
+
+ feeOptions(
+ wallet: Address.Address,
+ chainId: number,
+ calls: Payload.Call[],
+ ): Promise<{ options: FeeOption[]; quote?: FeeQuote }>
+
+ relay(to: Address.Address, data: Hex.Hex, chainId: number, quote?: FeeQuote): Promise<{ opHash: Hex.Hex }>
+
+ status(opHash: Hex.Hex, chainId: number): Promise
+
+ checkPrecondition(precondition: Precondition.Precondition): Promise
+}
+
+export function isRelayer(relayer: any): relayer is Relayer {
+ return (
+ 'isAvailable' in relayer &&
+ 'feeOptions' in relayer &&
+ 'relay' in relayer &&
+ 'status' in relayer &&
+ 'checkPrecondition' in relayer
+ )
+}
diff --git a/packages/services/relayer/src/relayer/rpc-relayer/index.ts b/packages/services/relayer/src/relayer/rpc-relayer/index.ts
new file mode 100644
index 000000000..04db6aa40
--- /dev/null
+++ b/packages/services/relayer/src/relayer/rpc-relayer/index.ts
@@ -0,0 +1,449 @@
+import {
+ Relayer as GenRelayer,
+ SendMetaTxnReturn as RpcSendMetaTxnReturn,
+ MetaTxn as RpcMetaTxn,
+ FeeTokenType,
+ FeeToken as RpcFeeToken,
+ TransactionPrecondition,
+ ETHTxnStatus,
+} from './relayer.gen.js'
+import { Address, Hex, Bytes, AbiFunction } from 'ox'
+import { Constants, Payload, Network } from '@0xsequence/wallet-primitives'
+import { FeeOption, FeeQuote, OperationStatus, Relayer } from '../index.js'
+import { decodePrecondition } from '../../preconditions/index.js'
+import {
+ erc20BalanceOf,
+ erc20Allowance,
+ erc721OwnerOf,
+ erc721GetApproved,
+ erc1155BalanceOf,
+ erc1155IsApprovedForAll,
+} from '../standard/abi.js'
+import { PublicClient, createPublicClient, http, Chain } from 'viem'
+import * as chains from 'viem/chains'
+
+export type Fetch = (input: RequestInfo, init?: RequestInit) => Promise
+
+/**
+ * Convert a Sequence Network to a viem Chain
+ */
+const networkToChain = (network: Network.Network): Chain => {
+ return {
+ id: network.chainId,
+ name: network.title || network.name,
+ nativeCurrency: {
+ name: network.nativeCurrency.name,
+ symbol: network.nativeCurrency.symbol,
+ decimals: network.nativeCurrency.decimals,
+ },
+ rpcUrls: {
+ default: {
+ http: [network.rpcUrl],
+ },
+ },
+ blockExplorers: network.blockExplorer
+ ? {
+ default: {
+ name: network.blockExplorer.name || 'Explorer',
+ url: network.blockExplorer.url,
+ },
+ }
+ : undefined,
+ contracts: network.ensAddress
+ ? {
+ ensUniversalResolver: {
+ address: network.ensAddress as `0x${string}`,
+ },
+ }
+ : undefined,
+ } as Chain
+}
+
+export const getChain = (chainId: number): Chain => {
+ // First try to get the chain from Sequence's network configurations
+ const sequenceNetwork = Network.getNetworkFromChainId(chainId)
+ if (sequenceNetwork) {
+ return networkToChain(sequenceNetwork)
+ }
+
+ // Fall back to viem's built-in chains
+ const viemChain = Object.values(chains).find((c: any) => typeof c === 'object' && 'id' in c && c.id === chainId)
+ if (viemChain) {
+ return viemChain as Chain
+ }
+
+ throw new Error(`Chain with id ${chainId} not found in Sequence networks or viem chains`)
+}
+
+export class RpcRelayer implements Relayer {
+ public readonly kind: 'relayer' = 'relayer'
+ public readonly type = 'rpc'
+ public readonly id: string
+ public readonly chainId: number
+ private client: GenRelayer
+ private fetch: Fetch
+ private provider: PublicClient
+ private readonly projectAccessKey?: string
+
+ constructor(hostname: string, chainId: number, rpcUrl: string, fetchImpl?: Fetch, projectAccessKey?: string) {
+ this.id = `rpc:${hostname}`
+ this.chainId = chainId
+ this.projectAccessKey = projectAccessKey
+ const effectiveFetch = fetchImpl || (typeof window !== 'undefined' ? window.fetch.bind(window) : undefined)
+ if (!effectiveFetch) {
+ throw new Error('Fetch implementation is required but not available in this environment.')
+ }
+ this.fetch = effectiveFetch
+ this.client = new GenRelayer(hostname, this.fetch)
+
+ // Get the chain from the chainId
+ const chain = getChain(chainId)
+
+ // Create viem PublicClient with the provided RPC URL
+ this.provider = createPublicClient({
+ chain,
+ transport: http(rpcUrl),
+ })
+ }
+
+ isAvailable(_wallet: Address.Address, chainId: number): Promise {
+ return Promise.resolve(this.chainId === chainId)
+ }
+
+ async feeTokens(): Promise<{ isFeeRequired: boolean; tokens?: RpcFeeToken[]; paymentAddress?: Address.Address }> {
+ try {
+ const { isFeeRequired, tokens, paymentAddress } = await this.client.feeTokens()
+ if (isFeeRequired) {
+ Address.assert(paymentAddress)
+ return {
+ isFeeRequired,
+ tokens,
+ paymentAddress,
+ }
+ }
+ // Not required
+ return {
+ isFeeRequired,
+ }
+ } catch (e) {
+ console.warn('RpcRelayer.feeTokens failed:', e)
+ return { isFeeRequired: false }
+ }
+ }
+
+ async feeOptions(
+ wallet: Address.Address,
+ chainId: number,
+ calls: Payload.Call[],
+ ): Promise<{ options: FeeOption[]; quote?: FeeQuote }> {
+ const callsStruct: Payload.Calls = { type: 'call', space: 0n, nonce: 0n, calls: calls }
+ const data = Payload.encode(callsStruct)
+
+ try {
+ const result = await this.client.feeOptions(
+ {
+ wallet: wallet,
+ to: wallet,
+ data: Bytes.toHex(data),
+ },
+ { ...(this.projectAccessKey ? { 'X-Access-Key': this.projectAccessKey } : undefined) },
+ )
+
+ const quote = result.quote ? ({ _tag: 'FeeQuote', _quote: result.quote } as FeeQuote) : undefined
+ const options = result.options.map((option) => ({
+ token: {
+ ...option.token,
+ contractAddress: this.mapRpcFeeTokenToAddress(option.token),
+ },
+ to: option.to,
+ value: option.value,
+ gasLimit: option.gasLimit,
+ }))
+
+ return { options, quote }
+ } catch (e) {
+ console.warn('RpcRelayer.feeOptions failed:', e)
+ return { options: [] }
+ }
+ }
+
+ async sendMetaTxn(
+ walletAddress: Address.Address,
+ to: Address.Address,
+ data: Hex.Hex,
+ chainId: number,
+ quote?: FeeQuote,
+ preconditions?: TransactionPrecondition[],
+ ): Promise<{ opHash: Hex.Hex }> {
+ console.log('sendMetaTxn', walletAddress, to, data, chainId, quote, preconditions)
+ const rpcCall: RpcMetaTxn = {
+ walletAddress: walletAddress,
+ contract: to,
+ input: data,
+ }
+
+ const result: RpcSendMetaTxnReturn = await this.client.sendMetaTxn(
+ {
+ call: rpcCall,
+ quote: quote ? JSON.stringify(quote._quote) : undefined,
+ preconditions: preconditions,
+ },
+ { ...(this.projectAccessKey ? { 'X-Access-Key': this.projectAccessKey } : undefined) },
+ )
+
+ if (!result.status) {
+ console.error('RpcRelayer.relay failed', result)
+ throw new Error(`Relay failed: TxnHash ${result.txnHash}`)
+ }
+
+ return { opHash: Hex.fromString(result.txnHash) }
+ }
+
+ async relay(
+ to: Address.Address,
+ data: Hex.Hex,
+ chainId: number,
+ quote?: FeeQuote,
+ preconditions?: TransactionPrecondition[],
+ ): Promise<{ opHash: Hex.Hex }> {
+ console.log('relay', to, data, chainId, quote, preconditions)
+ const rpcCall: RpcMetaTxn = {
+ walletAddress: to,
+ contract: to,
+ input: data,
+ }
+
+ const result: RpcSendMetaTxnReturn = await this.client.sendMetaTxn(
+ {
+ call: rpcCall,
+ quote: quote ? JSON.stringify(quote._quote) : undefined,
+ preconditions: preconditions,
+ },
+ { ...(this.projectAccessKey ? { 'X-Access-Key': this.projectAccessKey } : undefined) },
+ )
+
+ if (!result.status) {
+ console.error('RpcRelayer.relay failed', result)
+ throw new Error(`Relay failed: TxnHash ${result.txnHash}`)
+ }
+
+ return { opHash: `0x${result.txnHash}` }
+ }
+
+ async status(opHash: Hex.Hex, chainId: number): Promise {
+ try {
+ const cleanedOpHash = opHash.startsWith('0x') ? opHash.substring(2) : opHash
+ const result = await this.client.getMetaTxnReceipt({ metaTxID: cleanedOpHash })
+ const receipt = result.receipt
+
+ if (!receipt) {
+ console.warn(`RpcRelayer.status: receipt not found for opHash ${opHash}`)
+ return { status: 'unknown' }
+ }
+
+ if (!receipt.status) {
+ console.warn(`RpcRelayer.status: receipt status not found for opHash ${opHash}`)
+ return { status: 'unknown' }
+ }
+
+ switch (receipt.status as ETHTxnStatus) {
+ case ETHTxnStatus.QUEUED:
+ case ETHTxnStatus.PENDING_PRECONDITION:
+ case ETHTxnStatus.SENT:
+ return { status: 'pending' }
+ case ETHTxnStatus.SUCCEEDED:
+ return { status: 'confirmed', transactionHash: receipt.txnHash as Hex.Hex, data: result }
+ case ETHTxnStatus.FAILED:
+ case ETHTxnStatus.PARTIALLY_FAILED:
+ return {
+ status: 'failed',
+ transactionHash: receipt.txnHash ? (receipt.txnHash as Hex.Hex) : undefined,
+ reason: receipt.revertReason || 'Relayer reported failure',
+ data: result,
+ }
+ case ETHTxnStatus.DROPPED:
+ return { status: 'failed', reason: 'Transaction dropped' }
+ case ETHTxnStatus.UNKNOWN:
+ default:
+ return { status: 'unknown' }
+ }
+ } catch (error) {
+ console.error(`RpcRelayer.status failed for opHash ${opHash}:`, error)
+ return { status: 'failed', reason: 'Failed to fetch status' }
+ }
+ }
+
+ async checkPrecondition(precondition: TransactionPrecondition): Promise {
+ const decoded = decodePrecondition(precondition)
+
+ if (!decoded) {
+ return false
+ }
+
+ switch (decoded.type()) {
+ case 'native-balance': {
+ const native = decoded as any
+ try {
+ const balance = await this.provider.getBalance({ address: native.address.toString() as `0x${string}` })
+ const minWei = native.min !== undefined ? BigInt(native.min) : undefined
+ const maxWei = native.max !== undefined ? BigInt(native.max) : undefined
+
+ if (minWei !== undefined && maxWei !== undefined) {
+ return balance >= minWei && balance <= maxWei
+ }
+ if (minWei !== undefined) {
+ return balance >= minWei
+ }
+ if (maxWei !== undefined) {
+ return balance <= maxWei
+ }
+ // If no min or max specified, this is an invalid precondition
+ console.warn('Native balance precondition has neither min nor max specified')
+ return false
+ } catch (error) {
+ console.error('Error checking native balance:', error)
+ return false
+ }
+ }
+
+ case 'erc20-balance': {
+ const erc20 = decoded as any
+ try {
+ const data = AbiFunction.encodeData(erc20BalanceOf, [erc20.address.toString()])
+ const result = await this.provider.call({
+ to: erc20.token.toString() as `0x${string}`,
+ data: data as `0x${string}`,
+ })
+ const balance = BigInt(result.toString())
+ const minWei = erc20.min !== undefined ? BigInt(erc20.min) : undefined
+ const maxWei = erc20.max !== undefined ? BigInt(erc20.max) : undefined
+
+ if (minWei !== undefined && maxWei !== undefined) {
+ return balance >= minWei && balance <= maxWei
+ }
+ if (minWei !== undefined) {
+ return balance >= minWei
+ }
+ if (maxWei !== undefined) {
+ return balance <= maxWei
+ }
+ console.warn('ERC20 balance precondition has neither min nor max specified')
+ return false
+ } catch (error) {
+ console.error('Error checking ERC20 balance:', error)
+ return false
+ }
+ }
+
+ case 'erc20-approval': {
+ const erc20 = decoded as any
+ try {
+ const data = AbiFunction.encodeData(erc20Allowance, [erc20.address.toString(), erc20.operator.toString()])
+ const result = await this.provider.call({
+ to: erc20.token.toString() as `0x${string}`,
+ data: data as `0x${string}`,
+ })
+ const allowance = BigInt(result.toString())
+ const minAllowance = BigInt(erc20.min)
+ return allowance >= minAllowance
+ } catch (error) {
+ console.error('Error checking ERC20 approval:', error)
+ return false
+ }
+ }
+
+ case 'erc721-ownership': {
+ const erc721 = decoded as any
+ try {
+ const data = AbiFunction.encodeData(erc721OwnerOf, [erc721.tokenId])
+ const result = await this.provider.call({
+ to: erc721.token.toString() as `0x${string}`,
+ data: data as `0x${string}`,
+ })
+ const resultHex = result.toString() as `0x${string}`
+ const owner = resultHex.slice(-40)
+ const isOwner = owner.toLowerCase() === erc721.address.toString().slice(2).toLowerCase()
+ const expectedOwnership = erc721.owned !== undefined ? erc721.owned : true
+ return isOwner === expectedOwnership
+ } catch (error) {
+ console.error('Error checking ERC721 ownership:', error)
+ return false
+ }
+ }
+
+ case 'erc721-approval': {
+ const erc721 = decoded as any
+ try {
+ const data = AbiFunction.encodeData(erc721GetApproved, [erc721.tokenId])
+ const result = await this.provider.call({
+ to: erc721.token.toString() as `0x${string}`,
+ data: data as `0x${string}`,
+ })
+ const resultHex = result.toString() as `0x${string}`
+ const approved = resultHex.slice(-40)
+ return approved.toLowerCase() === erc721.operator.toString().slice(2).toLowerCase()
+ } catch (error) {
+ console.error('Error checking ERC721 approval:', error)
+ return false
+ }
+ }
+
+ case 'erc1155-balance': {
+ const erc1155 = decoded as any
+ try {
+ const data = AbiFunction.encodeData(erc1155BalanceOf, [erc1155.address.toString(), erc1155.tokenId])
+ const result = await this.provider.call({
+ to: erc1155.token.toString() as `0x${string}`,
+ data: data as `0x${string}`,
+ })
+ const balance = BigInt(result.toString())
+ const minWei = erc1155.min !== undefined ? BigInt(erc1155.min) : undefined
+ const maxWei = erc1155.max !== undefined ? BigInt(erc1155.max) : undefined
+
+ if (minWei !== undefined && maxWei !== undefined) {
+ return balance >= minWei && balance <= maxWei
+ }
+ if (minWei !== undefined) {
+ return balance >= minWei
+ }
+ if (maxWei !== undefined) {
+ return balance <= maxWei
+ }
+ console.warn('ERC1155 balance precondition has neither min nor max specified')
+ return false
+ } catch (error) {
+ console.error('Error checking ERC1155 balance:', error)
+ return false
+ }
+ }
+
+ case 'erc1155-approval': {
+ const erc1155 = decoded as any
+ try {
+ const data = AbiFunction.encodeData(erc1155IsApprovedForAll, [
+ erc1155.address.toString(),
+ erc1155.operator.toString(),
+ ])
+ const result = await this.provider.call({
+ to: erc1155.token.toString() as `0x${string}`,
+ data: data as `0x${string}`,
+ })
+ return BigInt(result.toString()) === 1n
+ } catch (error) {
+ console.error('Error checking ERC1155 approval:', error)
+ return false
+ }
+ }
+
+ default:
+ return false
+ }
+ }
+
+ private mapRpcFeeTokenToAddress(rpcToken: RpcFeeToken): Address.Address {
+ if (rpcToken.type === FeeTokenType.ERC20_TOKEN && rpcToken.contractAddress) {
+ return Address.from(rpcToken.contractAddress)
+ }
+ return Constants.ZeroAddress // Default to zero address for native token or unsupported types
+ }
+}
diff --git a/packages/services/relayer/src/relayer/rpc-relayer/relayer.gen.ts b/packages/services/relayer/src/relayer/rpc-relayer/relayer.gen.ts
new file mode 100644
index 000000000..ca5dbe9c8
--- /dev/null
+++ b/packages/services/relayer/src/relayer/rpc-relayer/relayer.gen.ts
@@ -0,0 +1,2268 @@
+/* eslint-disable */
+// sequence-relayer v0.4.1 7f8a4b83b00e0b6849c76c2ff0e23931e26b3d9f
+// --
+// Code generated by Webrpc-gen@v0.30.2 with typescript generator. DO NOT EDIT.
+//
+// webrpc-gen -schema=relayer.ridl -target=typescript -client -out=./clients/relayer.gen.ts -compat
+
+// Webrpc description and code-gen version
+export const WebrpcVersion = 'v1'
+
+// Schema version of your RIDL schema
+export const WebrpcSchemaVersion = 'v0.4.1'
+
+// Schema hash generated from your RIDL schema
+export const WebrpcSchemaHash = '7f8a4b83b00e0b6849c76c2ff0e23931e26b3d9f'
+
+//
+// Client interface
+//
+
+export interface RelayerClient {
+ ping(headers?: object, signal?: AbortSignal): Promise
+
+ version(headers?: object, signal?: AbortSignal): Promise
+
+ runtimeStatus(headers?: object, signal?: AbortSignal): Promise
+
+ getSequenceContext(headers?: object, signal?: AbortSignal): Promise
+
+ getChainID(headers?: object, signal?: AbortSignal): Promise
+
+ /**
+ *
+ * Transactions
+ *
+ * TODO (future): rename this to just, 'SendTransaction(txn: MetaTransaction)' or 'SendTransaction(txn: SignedTransaction)', or something..
+ * Project ID is only used by service and admin calls. Other clients must have projectID passed via the context
+ * TODO: rename return txnHash: string to metaTxnID: string
+ */
+ sendMetaTxn(req: SendMetaTxnArgs, headers?: object, signal?: AbortSignal): Promise
+
+ getMetaTxnNonce(req: GetMetaTxnNonceArgs, headers?: object, signal?: AbortSignal): Promise
+
+ /**
+ * TODO: one day, make GetMetaTxnReceipt respond immediately with receipt or not
+ * and add WaitTransactionReceipt method, which will block and wait, similar to how GetMetaTxnReceipt
+ * is implemented now.
+ * For backwards compat, we can leave the current GetMetaTxnReceipt how it is, an deprecate it, and introduce
+ * new, GetTransactionReceipt and WaitTransactionReceipt methods
+ * we can also accept metaTxnId and txnHash .. so can take either or.. I wonder if ERC-4337 has any convention on this?
+ */
+ getMetaTxnReceipt(
+ req: GetMetaTxnReceiptArgs,
+ headers?: object,
+ signal?: AbortSignal,
+ ): Promise
+
+ simulate(req: SimulateArgs, headers?: object, signal?: AbortSignal): Promise
+
+ simulateV3(req: SimulateV3Args, headers?: object, signal?: AbortSignal): Promise
+
+ /**
+ * TODO: deprecated, to be removed by https://github.com/0xsequence/stack/pull/356 at a later date
+ */
+ updateMetaTxnGasLimits(
+ req: UpdateMetaTxnGasLimitsArgs,
+ headers?: object,
+ signal?: AbortSignal,
+ ): Promise
+
+ feeTokens(headers?: object, signal?: AbortSignal): Promise
+
+ feeOptions(req: FeeOptionsArgs, headers?: object, signal?: AbortSignal): Promise
+
+ /**
+ * TODO: deprecated, to be removed by https://github.com/0xsequence/stack/pull/356 at a later date
+ */
+ getMetaTxnNetworkFeeOptions(
+ req: GetMetaTxnNetworkFeeOptionsArgs,
+ headers?: object,
+ signal?: AbortSignal,
+ ): Promise
+
+ getMetaTransactions(
+ req: GetMetaTransactionsArgs,
+ headers?: object,
+ signal?: AbortSignal,
+ ): Promise
+
+ getTransactionCost(
+ req: GetTransactionCostArgs,
+ headers?: object,
+ signal?: AbortSignal,
+ ): Promise
+
+ /**
+ * Sent transactions from an account. If filter is omitted then it will return all transactions.
+ */
+ sentTransactions(req: SentTransactionsArgs, headers?: object, signal?: AbortSignal): Promise
+
+ /**
+ * Pending transactions waiting to be mined for an account. This endpoint is just a sugar of `SentTransactions`
+ * with the filter set to pending: true.
+ */
+ pendingTransactions(
+ req: PendingTransactionsArgs,
+ headers?: object,
+ signal?: AbortSignal,
+ ): Promise
+
+ /**
+ * Legacy Gas Tank
+ */
+ getGasTank(req: GetGasTankArgs, headers?: object, signal?: AbortSignal): Promise
+
+ addGasTank(req: AddGasTankArgs, headers?: object, signal?: AbortSignal): Promise
+
+ updateGasTank(req: UpdateGasTankArgs, headers?: object, signal?: AbortSignal): Promise
+
+ /**
+ * Legacy Gas Adjustment
+ */
+ nextGasTankBalanceAdjustmentNonce(
+ req: NextGasTankBalanceAdjustmentNonceArgs,
+ headers?: object,
+ signal?: AbortSignal,
+ ): Promise
+
+ adjustGasTankBalance(
+ req: AdjustGasTankBalanceArgs,
+ headers?: object,
+ signal?: AbortSignal,
+ ): Promise
+
+ getGasTankBalanceAdjustment(
+ req: GetGasTankBalanceAdjustmentArgs,
+ headers?: object,
+ signal?: AbortSignal,
+ ): Promise
+
+ listGasTankBalanceAdjustments(
+ req: ListGasTankBalanceAdjustmentsArgs,
+ headers?: object,
+ signal?: AbortSignal,
+ ): Promise
+
+ /**
+ * Gas Sponsorship
+ */
+ listGasSponsors(req: ListGasSponsorsArgs, headers?: object, signal?: AbortSignal): Promise
+
+ getGasSponsor(req: GetGasSponsorArgs, headers?: object, signal?: AbortSignal): Promise
+
+ addGasSponsor(req: AddGasSponsorArgs, headers?: object, signal?: AbortSignal): Promise
+
+ updateGasSponsor(req: UpdateGasSponsorArgs, headers?: object, signal?: AbortSignal): Promise
+
+ removeGasSponsor(req: RemoveGasSponsorArgs, headers?: object, signal?: AbortSignal): Promise
+
+ /**
+ * Gas Sponsor Lookup
+ */
+ addressGasSponsors(
+ req: AddressGasSponsorsArgs,
+ headers?: object,
+ signal?: AbortSignal,
+ ): Promise
+
+ /**
+ * Project Balance
+ */
+ getProjectBalance(
+ req: GetProjectBalanceArgs,
+ headers?: object,
+ signal?: AbortSignal,
+ ): Promise
+
+ adjustProjectBalance(
+ req: AdjustProjectBalanceArgs,
+ headers?: object,
+ signal?: AbortSignal,
+ ): Promise
+}
+
+//
+// Schema types
+//
+
+export enum ETHTxnStatus {
+ UNKNOWN = 'UNKNOWN',
+ DROPPED = 'DROPPED',
+ QUEUED = 'QUEUED',
+ SENT = 'SENT',
+ SUCCEEDED = 'SUCCEEDED',
+ PARTIALLY_FAILED = 'PARTIALLY_FAILED',
+ FAILED = 'FAILED',
+ PENDING_PRECONDITION = 'PENDING_PRECONDITION',
+}
+
+export enum TransferType {
+ SEND = 'SEND',
+ RECEIVE = 'RECEIVE',
+ BRIDGE_DEPOSIT = 'BRIDGE_DEPOSIT',
+ BRIDGE_WITHDRAW = 'BRIDGE_WITHDRAW',
+ BURN = 'BURN',
+ UNKNOWN = 'UNKNOWN',
+}
+
+export enum SimulateStatus {
+ SKIPPED = 'SKIPPED',
+ SUCCEEDED = 'SUCCEEDED',
+ FAILED = 'FAILED',
+ ABORTED = 'ABORTED',
+ REVERTED = 'REVERTED',
+ NOT_ENOUGH_GAS = 'NOT_ENOUGH_GAS',
+}
+
+export enum FeeTokenType {
+ UNKNOWN = 'UNKNOWN',
+ ERC20_TOKEN = 'ERC20_TOKEN',
+ ERC1155_TOKEN = 'ERC1155_TOKEN',
+}
+
+export enum SortOrder {
+ DESC = 'DESC',
+ ASC = 'ASC',
+}
+
+export interface Version {
+ webrpcVersion: string
+ schemaVersion: string
+ schemaHash: string
+ appVersion: string
+}
+
+export interface RuntimeStatus {
+ healthOK: boolean
+ startTime: string
+ uptime: number
+ ver: string
+ branch: string
+ commitHash: string
+ chainID: number
+ useEIP1559: boolean
+ senders: Array
+ checks: RuntimeChecks
+}
+
+export interface SenderStatus {
+ index: number
+ address: string
+ etherBalance: number
+ active: boolean
+}
+
+export interface RuntimeChecks {}
+
+export interface SequenceContext {
+ factory: string
+ mainModule: string
+ mainModuleUpgradable: string
+ guestModule: string
+ utils: string
+}
+
+export interface GasTank {
+ id: number
+ chainId: number
+ name: string
+ currentBalance: number
+ unlimited: boolean
+ feeMarkupFactor: number
+ updatedAt: string
+ createdAt: string
+}
+
+export interface GasTankBalanceAdjustment {
+ gasTankId: number
+ nonce: number
+ amount: number
+ totalBalance: number
+ balanceTimestamp: string
+ createdAt: string
+}
+
+export interface GasSponsor {
+ id: number
+ gasTankId: number
+ projectId: number
+ chainId: number
+ address: string
+ name: string
+ active: boolean
+ updatedAt: string
+ createdAt: string
+ deletedAt: string
+}
+
+export interface GasSponsorUsage {
+ name: string
+ id: number
+ totalGasUsed: number
+ totalTxnFees: number
+ totalTxnFeesUsd: number
+ avgGasPrice: number
+ totalTxns: number
+ startTime: string
+ endTime: string
+}
+
+export interface MetaTxn {
+ walletAddress: string
+ contract: string
+ input: string
+}
+
+export interface MetaTxnLog {
+ id: number
+ chainId: number
+ projectId: number
+ txnHash: string
+ txnNonce: string
+ metaTxnID?: string
+ txnStatus: ETHTxnStatus
+ txnRevertReason: string
+ requeues: number
+ queuedAt: string
+ sentAt: string
+ minedAt: string
+ target: string
+ input: string
+ txnArgs: { [key: string]: any }
+ txnReceipt?: { [key: string]: any }
+ walletAddress: string
+ metaTxnNonce: string
+ gasLimit: number
+ gasPrice: string
+ gasUsed: number
+ gasEstimated: number
+ gasFeeMarkup?: number
+ usdRate: string
+ creditsUsed: number
+ cost: string
+ isWhitelisted: boolean
+ gasSponsor?: number
+ gasTank?: number
+ updatedAt: string
+ createdAt: string
+}
+
+export interface MetaTxnReceipt {
+ id: string
+ status: string
+ revertReason?: string
+ index: number
+ logs: Array
+ receipts: Array
+ blockNumber: string
+ txnHash: string
+ txnReceipt: string
+}
+
+export interface MetaTxnReceiptLog {
+ address: string
+ topics: Array
+ data: string
+}
+
+export interface Transactions {
+ chainID: string
+ transactions: Array
+ preconditions?: Array
+}
+
+export interface Transaction {
+ delegateCall: boolean
+ revertOnError: boolean
+ gasLimit: string
+ target: string
+ value: string
+ data: string
+}
+
+export interface TransactionPrecondition {
+ type: string
+ chainId: number
+ ownerAddress: string
+ tokenAddress: string
+ minAmount: bigint
+}
+
+export interface TxnLogUser {
+ username: string
+}
+
+export interface TxnLogTransfer {
+ transferType: TransferType
+ contractAddress: string
+ from: string
+ to: string
+ ids: Array
+ amounts: Array
+}
+
+export interface SentTransactionsFilter {
+ pending?: boolean
+ failed?: boolean
+}
+
+export interface SimulateResult {
+ executed: boolean
+ succeeded: boolean
+ result?: string
+ reason?: string
+ gasUsed: number
+ gasLimit: number
+}
+
+export interface SimulateV3Result {
+ status: SimulateStatus
+ result?: string
+ error?: string
+ gasUsed: number
+ gasLimit: number
+}
+
+export interface FeeOption {
+ token: FeeToken
+ to: string
+ value: string
+ gasLimit: number
+}
+
+export interface FeeToken {
+ chainId: number
+ name: string
+ symbol: string
+ type: FeeTokenType
+ decimals?: number
+ logoURL: string
+ contractAddress?: string
+ tokenID?: string
+}
+
+export interface Page {
+ pageSize?: number
+ page?: number
+ more?: boolean
+ totalRecords?: number
+ column?: string
+ before?: any
+ after?: any
+ sort?: Array
+}
+
+export interface SortBy {
+ column: string
+ order: SortOrder
+}
+
+export interface PingArgs {}
+
+export interface PingReturn {
+ status: boolean
+}
+
+export interface VersionArgs {}
+
+export interface VersionReturn {
+ version: Version
+}
+
+export interface RuntimeStatusArgs {}
+
+export interface RuntimeStatusReturn {
+ status: RuntimeStatus
+}
+
+export interface GetSequenceContextArgs {}
+
+export interface GetSequenceContextReturn {
+ data: SequenceContext
+}
+
+export interface GetChainIDArgs {}
+
+export interface GetChainIDReturn {
+ chainID: number
+}
+
+export interface SendMetaTxnArgs {
+ call: MetaTxn
+ quote?: string
+ projectID?: number
+ preconditions?: Array
+}
+
+export interface SendMetaTxnReturn {
+ status: boolean
+ txnHash: string
+}
+
+export interface GetMetaTxnNonceArgs {
+ walletContractAddress: string
+ space?: string
+}
+
+export interface GetMetaTxnNonceReturn {
+ nonce: string
+}
+
+export interface GetMetaTxnReceiptArgs {
+ metaTxID: string
+}
+
+export interface GetMetaTxnReceiptReturn {
+ receipt: MetaTxnReceipt
+}
+
+export interface SimulateArgs {
+ wallet: string
+ transactions: string
+}
+
+export interface SimulateReturn {
+ results: Array
+}
+
+export interface SimulateV3Args {
+ wallet: string
+ calls: string
+}
+
+export interface SimulateV3Return {
+ results: Array
+}
+
+export interface UpdateMetaTxnGasLimitsArgs {
+ walletAddress: string
+ walletConfig: any
+ payload: string
+}
+
+export interface UpdateMetaTxnGasLimitsReturn {
+ payload: string
+}
+
+export interface FeeTokensArgs {}
+
+export interface FeeTokensReturn {
+ isFeeRequired: boolean
+ tokens: Array
+ paymentAddress: string
+}
+
+export interface FeeOptionsArgs {
+ wallet: string
+ to: string
+ data: string
+ simulate?: boolean
+}
+
+export interface FeeOptionsReturn {
+ options: Array
+ sponsored: boolean
+ quote?: string
+}
+
+export interface GetMetaTxnNetworkFeeOptionsArgs {
+ walletConfig: any
+ payload: string
+}
+
+export interface GetMetaTxnNetworkFeeOptionsReturn {
+ options: Array
+}
+
+export interface GetMetaTransactionsArgs {
+ projectId: number
+ page?: Page
+}
+
+export interface GetMetaTransactionsReturn {
+ page: Page
+ transactions: Array
+}
+
+export interface GetTransactionCostArgs {
+ projectId: number
+ from: string
+ to: string
+}
+
+export interface GetTransactionCostReturn {
+ cost: number
+}
+
+export interface SentTransactionsArgs {
+ filter?: SentTransactionsFilter
+ page?: Page
+}
+
+export interface SentTransactionsReturn {
+ page: Page
+ transactions: Array
+}
+
+export interface PendingTransactionsArgs {
+ page?: Page
+}
+
+export interface PendingTransactionsReturn {
+ page: Page
+ transactions: Array
+}
+
+export interface GetGasTankArgs {
+ id: number
+}
+
+export interface GetGasTankReturn {
+ gasTank: GasTank
+}
+
+export interface AddGasTankArgs {
+ name: string
+ feeMarkupFactor: number
+ unlimited?: boolean
+}
+
+export interface AddGasTankReturn {
+ status: boolean
+ gasTank: GasTank
+}
+
+export interface UpdateGasTankArgs {
+ id: number
+ name?: string
+ feeMarkupFactor?: number
+ unlimited?: boolean
+}
+
+export interface UpdateGasTankReturn {
+ status: boolean
+ gasTank: GasTank
+}
+
+export interface NextGasTankBalanceAdjustmentNonceArgs {
+ id: number
+}
+
+export interface NextGasTankBalanceAdjustmentNonceReturn {
+ nonce: number
+}
+
+export interface AdjustGasTankBalanceArgs {
+ id: number
+ nonce: number
+ amount: number
+}
+
+export interface AdjustGasTankBalanceReturn {
+ status: boolean
+ adjustment: GasTankBalanceAdjustment
+}
+
+export interface GetGasTankBalanceAdjustmentArgs {
+ id: number
+ nonce: number
+}
+
+export interface GetGasTankBalanceAdjustmentReturn {
+ adjustment: GasTankBalanceAdjustment
+}
+
+export interface ListGasTankBalanceAdjustmentsArgs {
+ id: number
+ page?: Page
+}
+
+export interface ListGasTankBalanceAdjustmentsReturn {
+ page: Page
+ adjustments: Array
+}
+
+export interface ListGasSponsorsArgs {
+ projectId: number
+ page?: Page
+}
+
+export interface ListGasSponsorsReturn {
+ page: Page
+ gasSponsors: Array
+}
+
+export interface GetGasSponsorArgs {
+ projectId: number
+ id: number
+}
+
+export interface GetGasSponsorReturn {
+ gasSponsor: GasSponsor
+}
+
+export interface AddGasSponsorArgs {
+ projectId: number
+ address: string
+ name?: string
+ active?: boolean
+}
+
+export interface AddGasSponsorReturn {
+ status: boolean
+ gasSponsor: GasSponsor
+}
+
+export interface UpdateGasSponsorArgs {
+ projectId: number
+ id: number
+ name?: string
+ active?: boolean
+}
+
+export interface UpdateGasSponsorReturn {
+ status: boolean
+ gasSponsor: GasSponsor
+}
+
+export interface RemoveGasSponsorArgs {
+ projectId: number
+ id: number
+}
+
+export interface RemoveGasSponsorReturn {
+ status: boolean
+}
+
+export interface AddressGasSponsorsArgs {
+ address: string
+ page?: Page
+}
+
+export interface AddressGasSponsorsReturn {
+ page: Page
+ gasSponsors: Array
+}
+
+export interface GetProjectBalanceArgs {
+ projectId: number
+}
+
+export interface GetProjectBalanceReturn {
+ balance: number
+}
+
+export interface AdjustProjectBalanceArgs {
+ projectId: number
+ amount: number
+ identifier: string
+}
+
+export interface AdjustProjectBalanceReturn {
+ balance: number
+}
+
+//
+// Client
+//
+
+export class Relayer implements RelayerClient {
+ protected hostname: string
+ protected fetch: Fetch
+ protected path = '/rpc/Relayer/'
+
+ constructor(hostname: string, fetch: Fetch) {
+ this.hostname = hostname.replace(/\/*$/, '')
+ this.fetch = (input: RequestInfo, init?: RequestInit) => fetch(input, init)
+ }
+
+ private url(name: string): string {
+ return this.hostname + this.path + name
+ }
+
+ queryKey = {
+ ping: () => ['Relayer', 'ping'] as const,
+ version: () => ['Relayer', 'version'] as const,
+ runtimeStatus: () => ['Relayer', 'runtimeStatus'] as const,
+ getSequenceContext: () => ['Relayer', 'getSequenceContext'] as const,
+ getChainID: () => ['Relayer', 'getChainID'] as const,
+ sendMetaTxn: (req: SendMetaTxnArgs) => ['Relayer', 'sendMetaTxn', req] as const,
+ getMetaTxnNonce: (req: GetMetaTxnNonceArgs) => ['Relayer', 'getMetaTxnNonce', req] as const,
+ getMetaTxnReceipt: (req: GetMetaTxnReceiptArgs) => ['Relayer', 'getMetaTxnReceipt', req] as const,
+ simulate: (req: SimulateArgs) => ['Relayer', 'simulate', req] as const,
+ simulateV3: (req: SimulateV3Args) => ['Relayer', 'simulateV3', req] as const,
+ updateMetaTxnGasLimits: (req: UpdateMetaTxnGasLimitsArgs) => ['Relayer', 'updateMetaTxnGasLimits', req] as const,
+ feeTokens: () => ['Relayer', 'feeTokens'] as const,
+ feeOptions: (req: FeeOptionsArgs) => ['Relayer', 'feeOptions', req] as const,
+ getMetaTxnNetworkFeeOptions: (req: GetMetaTxnNetworkFeeOptionsArgs) =>
+ ['Relayer', 'getMetaTxnNetworkFeeOptions', req] as const,
+ getMetaTransactions: (req: GetMetaTransactionsArgs) => ['Relayer', 'getMetaTransactions', req] as const,
+ getTransactionCost: (req: GetTransactionCostArgs) => ['Relayer', 'getTransactionCost', req] as const,
+ sentTransactions: (req: SentTransactionsArgs) => ['Relayer', 'sentTransactions', req] as const,
+ pendingTransactions: (req: PendingTransactionsArgs) => ['Relayer', 'pendingTransactions', req] as const,
+ getGasTank: (req: GetGasTankArgs) => ['Relayer', 'getGasTank', req] as const,
+ addGasTank: (req: AddGasTankArgs) => ['Relayer', 'addGasTank', req] as const,
+ updateGasTank: (req: UpdateGasTankArgs) => ['Relayer', 'updateGasTank', req] as const,
+ nextGasTankBalanceAdjustmentNonce: (req: NextGasTankBalanceAdjustmentNonceArgs) =>
+ ['Relayer', 'nextGasTankBalanceAdjustmentNonce', req] as const,
+ adjustGasTankBalance: (req: AdjustGasTankBalanceArgs) => ['Relayer', 'adjustGasTankBalance', req] as const,
+ getGasTankBalanceAdjustment: (req: GetGasTankBalanceAdjustmentArgs) =>
+ ['Relayer', 'getGasTankBalanceAdjustment', req] as const,
+ listGasTankBalanceAdjustments: (req: ListGasTankBalanceAdjustmentsArgs) =>
+ ['Relayer', 'listGasTankBalanceAdjustments', req] as const,
+ listGasSponsors: (req: ListGasSponsorsArgs) => ['Relayer', 'listGasSponsors', req] as const,
+ getGasSponsor: (req: GetGasSponsorArgs) => ['Relayer', 'getGasSponsor', req] as const,
+ addGasSponsor: (req: AddGasSponsorArgs) => ['Relayer', 'addGasSponsor', req] as const,
+ updateGasSponsor: (req: UpdateGasSponsorArgs) => ['Relayer', 'updateGasSponsor', req] as const,
+ removeGasSponsor: (req: RemoveGasSponsorArgs) => ['Relayer', 'removeGasSponsor', req] as const,
+ addressGasSponsors: (req: AddressGasSponsorsArgs) => ['Relayer', 'addressGasSponsors', req] as const,
+ getProjectBalance: (req: GetProjectBalanceArgs) => ['Relayer', 'getProjectBalance', req] as const,
+ adjustProjectBalance: (req: AdjustProjectBalanceArgs) => ['Relayer', 'adjustProjectBalance', req] as const,
+ }
+
+ ping = (headers?: object, signal?: AbortSignal): Promise => {
+ return this.fetch(this.url('Ping'), createHttpRequest('{}', headers, signal)).then(
+ (res) => {
+ return buildResponse(res).then((_data) => {
+ return JsonDecode(_data, 'PingReturn')
+ })
+ },
+ (error) => {
+ throw WebrpcRequestFailedError.new({
+ cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`,
+ })
+ },
+ )
+ }
+
+ version = (headers?: object, signal?: AbortSignal): Promise => {
+ return this.fetch(this.url('Version'), createHttpRequest('{}', headers, signal)).then(
+ (res) => {
+ return buildResponse(res).then((_data) => {
+ return JsonDecode(_data, 'VersionReturn')
+ })
+ },
+ (error) => {
+ throw WebrpcRequestFailedError.new({
+ cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`,
+ })
+ },
+ )
+ }
+
+ runtimeStatus = (headers?: object, signal?: AbortSignal): Promise => {
+ return this.fetch(this.url('RuntimeStatus'), createHttpRequest('{}', headers, signal)).then(
+ (res) => {
+ return buildResponse(res).then((_data) => {
+ return JsonDecode(_data, 'RuntimeStatusReturn')
+ })
+ },
+ (error) => {
+ throw WebrpcRequestFailedError.new({
+ cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`,
+ })
+ },
+ )
+ }
+
+ getSequenceContext = (headers?: object, signal?: AbortSignal): Promise => {
+ return this.fetch(this.url('GetSequenceContext'), createHttpRequest('{}', headers, signal)).then(
+ (res) => {
+ return buildResponse(res).then((_data) => {
+ return JsonDecode(_data, 'GetSequenceContextReturn')
+ })
+ },
+ (error) => {
+ throw WebrpcRequestFailedError.new({
+ cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`,
+ })
+ },
+ )
+ }
+
+ getChainID = (headers?: object, signal?: AbortSignal): Promise => {
+ return this.fetch(this.url('GetChainID'), createHttpRequest('{}', headers, signal)).then(
+ (res) => {
+ return buildResponse(res).then((_data) => {
+ return JsonDecode(_data, 'GetChainIDReturn')
+ })
+ },
+ (error) => {
+ throw WebrpcRequestFailedError.new({
+ cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`,
+ })
+ },
+ )
+ }
+
+ sendMetaTxn = (req: SendMetaTxnArgs, headers?: object, signal?: AbortSignal): Promise => {
+ return this.fetch(
+ this.url('SendMetaTxn'),
+ createHttpRequest(JsonEncode(req, 'SendMetaTxnArgs'), headers, signal),
+ ).then(
+ (res) => {
+ return buildResponse(res).then((_data) => {
+ return JsonDecode(_data, 'SendMetaTxnReturn')
+ })
+ },
+ (error) => {
+ throw WebrpcRequestFailedError.new({
+ cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`,
+ })
+ },
+ )
+ }
+
+ getMetaTxnNonce = (
+ req: GetMetaTxnNonceArgs,
+ headers?: object,
+ signal?: AbortSignal,
+ ): Promise => {
+ return this.fetch(
+ this.url('GetMetaTxnNonce'),
+ createHttpRequest(JsonEncode(req, 'GetMetaTxnNonceArgs'), headers, signal),
+ ).then(
+ (res) => {
+ return buildResponse(res).then((_data) => {
+ return JsonDecode(_data, 'GetMetaTxnNonceReturn')
+ })
+ },
+ (error) => {
+ throw WebrpcRequestFailedError.new({
+ cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`,
+ })
+ },
+ )
+ }
+
+ getMetaTxnReceipt = (
+ req: GetMetaTxnReceiptArgs,
+ headers?: object,
+ signal?: AbortSignal,
+ ): Promise => {
+ return this.fetch(
+ this.url('GetMetaTxnReceipt'),
+ createHttpRequest(JsonEncode(req, 'GetMetaTxnReceiptArgs'), headers, signal),
+ ).then(
+ (res) => {
+ return buildResponse(res).then((_data) => {
+ return JsonDecode(_data, 'GetMetaTxnReceiptReturn')
+ })
+ },
+ (error) => {
+ throw WebrpcRequestFailedError.new({
+ cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`,
+ })
+ },
+ )
+ }
+
+ simulate = (req: SimulateArgs, headers?: object, signal?: AbortSignal): Promise => {
+ return this.fetch(this.url('Simulate'), createHttpRequest(JsonEncode(req, 'SimulateArgs'), headers, signal)).then(
+ (res) => {
+ return buildResponse(res).then((_data) => {
+ return JsonDecode(_data, 'SimulateReturn')
+ })
+ },
+ (error) => {
+ throw WebrpcRequestFailedError.new({
+ cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`,
+ })
+ },
+ )
+ }
+
+ simulateV3 = (req: SimulateV3Args, headers?: object, signal?: AbortSignal): Promise => {
+ return this.fetch(
+ this.url('SimulateV3'),
+ createHttpRequest(JsonEncode(req, 'SimulateV3Args'), headers, signal),
+ ).then(
+ (res) => {
+ return buildResponse(res).then((_data) => {
+ return JsonDecode(_data, 'SimulateV3Return')
+ })
+ },
+ (error) => {
+ throw WebrpcRequestFailedError.new({
+ cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`,
+ })
+ },
+ )
+ }
+
+ updateMetaTxnGasLimits = (
+ req: UpdateMetaTxnGasLimitsArgs,
+ headers?: object,
+ signal?: AbortSignal,
+ ): Promise => {
+ return this.fetch(
+ this.url('UpdateMetaTxnGasLimits'),
+ createHttpRequest(JsonEncode(req, 'UpdateMetaTxnGasLimitsArgs'), headers, signal),
+ ).then(
+ (res) => {
+ return buildResponse(res).then((_data) => {
+ return JsonDecode(_data, 'UpdateMetaTxnGasLimitsReturn')
+ })
+ },
+ (error) => {
+ throw WebrpcRequestFailedError.new({
+ cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`,
+ })
+ },
+ )
+ }
+
+ feeTokens = (headers?: object, signal?: AbortSignal): Promise => {
+ return this.fetch(this.url('FeeTokens'), createHttpRequest('{}', headers, signal)).then(
+ (res) => {
+ return buildResponse(res).then((_data) => {
+ return JsonDecode(_data, 'FeeTokensReturn')
+ })
+ },
+ (error) => {
+ throw WebrpcRequestFailedError.new({
+ cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`,
+ })
+ },
+ )
+ }
+
+ feeOptions = (req: FeeOptionsArgs, headers?: object, signal?: AbortSignal): Promise => {
+ return this.fetch(
+ this.url('FeeOptions'),
+ createHttpRequest(JsonEncode(req, 'FeeOptionsArgs'), headers, signal),
+ ).then(
+ (res) => {
+ return buildResponse(res).then((_data) => {
+ return JsonDecode(_data, 'FeeOptionsReturn')
+ })
+ },
+ (error) => {
+ throw WebrpcRequestFailedError.new({
+ cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`,
+ })
+ },
+ )
+ }
+
+ getMetaTxnNetworkFeeOptions = (
+ req: GetMetaTxnNetworkFeeOptionsArgs,
+ headers?: object,
+ signal?: AbortSignal,
+ ): Promise => {
+ return this.fetch(
+ this.url('GetMetaTxnNetworkFeeOptions'),
+ createHttpRequest(JsonEncode(req, 'GetMetaTxnNetworkFeeOptionsArgs'), headers, signal),
+ ).then(
+ (res) => {
+ return buildResponse(res).then((_data) => {
+ return JsonDecode(_data, 'GetMetaTxnNetworkFeeOptionsReturn')
+ })
+ },
+ (error) => {
+ throw WebrpcRequestFailedError.new({
+ cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`,
+ })
+ },
+ )
+ }
+
+ getMetaTransactions = (
+ req: GetMetaTransactionsArgs,
+ headers?: object,
+ signal?: AbortSignal,
+ ): Promise => {
+ return this.fetch(
+ this.url('GetMetaTransactions'),
+ createHttpRequest(JsonEncode(req, 'GetMetaTransactionsArgs'), headers, signal),
+ ).then(
+ (res) => {
+ return buildResponse(res).then((_data) => {
+ return JsonDecode(_data, 'GetMetaTransactionsReturn')
+ })
+ },
+ (error) => {
+ throw WebrpcRequestFailedError.new({
+ cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`,
+ })
+ },
+ )
+ }
+
+ getTransactionCost = (
+ req: GetTransactionCostArgs,
+ headers?: object,
+ signal?: AbortSignal,
+ ): Promise => {
+ return this.fetch(
+ this.url('GetTransactionCost'),
+ createHttpRequest(JsonEncode(req, 'GetTransactionCostArgs'), headers, signal),
+ ).then(
+ (res) => {
+ return buildResponse(res).then((_data) => {
+ return JsonDecode(_data, 'GetTransactionCostReturn')
+ })
+ },
+ (error) => {
+ throw WebrpcRequestFailedError.new({
+ cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`,
+ })
+ },
+ )
+ }
+
+ sentTransactions = (
+ req: SentTransactionsArgs,
+ headers?: object,
+ signal?: AbortSignal,
+ ): Promise => {
+ return this.fetch(
+ this.url('SentTransactions'),
+ createHttpRequest(JsonEncode(req, 'SentTransactionsArgs'), headers, signal),
+ ).then(
+ (res) => {
+ return buildResponse(res).then((_data) => {
+ return JsonDecode(_data, 'SentTransactionsReturn')
+ })
+ },
+ (error) => {
+ throw WebrpcRequestFailedError.new({
+ cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`,
+ })
+ },
+ )
+ }
+
+ pendingTransactions = (
+ req: PendingTransactionsArgs,
+ headers?: object,
+ signal?: AbortSignal,
+ ): Promise => {
+ return this.fetch(
+ this.url('PendingTransactions'),
+ createHttpRequest(JsonEncode(req, 'PendingTransactionsArgs'), headers, signal),
+ ).then(
+ (res) => {
+ return buildResponse(res).then((_data) => {
+ return JsonDecode(_data, 'PendingTransactionsReturn')
+ })
+ },
+ (error) => {
+ throw WebrpcRequestFailedError.new({
+ cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`,
+ })
+ },
+ )
+ }
+
+ getGasTank = (req: GetGasTankArgs, headers?: object, signal?: AbortSignal): Promise => {
+ return this.fetch(
+ this.url('GetGasTank'),
+ createHttpRequest(JsonEncode(req, 'GetGasTankArgs'), headers, signal),
+ ).then(
+ (res) => {
+ return buildResponse(res).then((_data) => {
+ return JsonDecode(_data, 'GetGasTankReturn')
+ })
+ },
+ (error) => {
+ throw WebrpcRequestFailedError.new({
+ cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`,
+ })
+ },
+ )
+ }
+
+ addGasTank = (req: AddGasTankArgs, headers?: object, signal?: AbortSignal): Promise => {
+ return this.fetch(
+ this.url('AddGasTank'),
+ createHttpRequest(JsonEncode(req, 'AddGasTankArgs'), headers, signal),
+ ).then(
+ (res) => {
+ return buildResponse(res).then((_data) => {
+ return JsonDecode(_data, 'AddGasTankReturn')
+ })
+ },
+ (error) => {
+ throw WebrpcRequestFailedError.new({
+ cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`,
+ })
+ },
+ )
+ }
+
+ updateGasTank = (req: UpdateGasTankArgs, headers?: object, signal?: AbortSignal): Promise => {
+ return this.fetch(
+ this.url('UpdateGasTank'),
+ createHttpRequest(JsonEncode(req, 'UpdateGasTankArgs'), headers, signal),
+ ).then(
+ (res) => {
+ return buildResponse(res).then((_data) => {
+ return JsonDecode(_data, 'UpdateGasTankReturn')
+ })
+ },
+ (error) => {
+ throw WebrpcRequestFailedError.new({
+ cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`,
+ })
+ },
+ )
+ }
+
+ nextGasTankBalanceAdjustmentNonce = (
+ req: NextGasTankBalanceAdjustmentNonceArgs,
+ headers?: object,
+ signal?: AbortSignal,
+ ): Promise => {
+ return this.fetch(
+ this.url('NextGasTankBalanceAdjustmentNonce'),
+ createHttpRequest(JsonEncode(req, 'NextGasTankBalanceAdjustmentNonceArgs'), headers, signal),
+ ).then(
+ (res) => {
+ return buildResponse(res).then((_data) => {
+ return JsonDecode(_data, 'NextGasTankBalanceAdjustmentNonceReturn')
+ })
+ },
+ (error) => {
+ throw WebrpcRequestFailedError.new({
+ cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`,
+ })
+ },
+ )
+ }
+
+ adjustGasTankBalance = (
+ req: AdjustGasTankBalanceArgs,
+ headers?: object,
+ signal?: AbortSignal,
+ ): Promise => {
+ return this.fetch(
+ this.url('AdjustGasTankBalance'),
+ createHttpRequest(JsonEncode(req, 'AdjustGasTankBalanceArgs'), headers, signal),
+ ).then(
+ (res) => {
+ return buildResponse(res).then((_data) => {
+ return JsonDecode(_data, 'AdjustGasTankBalanceReturn')
+ })
+ },
+ (error) => {
+ throw WebrpcRequestFailedError.new({
+ cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`,
+ })
+ },
+ )
+ }
+
+ getGasTankBalanceAdjustment = (
+ req: GetGasTankBalanceAdjustmentArgs,
+ headers?: object,
+ signal?: AbortSignal,
+ ): Promise => {
+ return this.fetch(
+ this.url('GetGasTankBalanceAdjustment'),
+ createHttpRequest(JsonEncode(req, 'GetGasTankBalanceAdjustmentArgs'), headers, signal),
+ ).then(
+ (res) => {
+ return buildResponse(res).then((_data) => {
+ return JsonDecode(_data, 'GetGasTankBalanceAdjustmentReturn')
+ })
+ },
+ (error) => {
+ throw WebrpcRequestFailedError.new({
+ cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`,
+ })
+ },
+ )
+ }
+
+ listGasTankBalanceAdjustments = (
+ req: ListGasTankBalanceAdjustmentsArgs,
+ headers?: object,
+ signal?: AbortSignal,
+ ): Promise => {
+ return this.fetch(
+ this.url('ListGasTankBalanceAdjustments'),
+ createHttpRequest(JsonEncode(req, 'ListGasTankBalanceAdjustmentsArgs'), headers, signal),
+ ).then(
+ (res) => {
+ return buildResponse(res).then((_data) => {
+ return JsonDecode(_data, 'ListGasTankBalanceAdjustmentsReturn')
+ })
+ },
+ (error) => {
+ throw WebrpcRequestFailedError.new({
+ cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`,
+ })
+ },
+ )
+ }
+
+ listGasSponsors = (
+ req: ListGasSponsorsArgs,
+ headers?: object,
+ signal?: AbortSignal,
+ ): Promise => {
+ return this.fetch(
+ this.url('ListGasSponsors'),
+ createHttpRequest(JsonEncode(req, 'ListGasSponsorsArgs'), headers, signal),
+ ).then(
+ (res) => {
+ return buildResponse(res).then((_data) => {
+ return JsonDecode(_data, 'ListGasSponsorsReturn')
+ })
+ },
+ (error) => {
+ throw WebrpcRequestFailedError.new({
+ cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`,
+ })
+ },
+ )
+ }
+
+ getGasSponsor = (req: GetGasSponsorArgs, headers?: object, signal?: AbortSignal): Promise => {
+ return this.fetch(
+ this.url('GetGasSponsor'),
+ createHttpRequest(JsonEncode(req, 'GetGasSponsorArgs'), headers, signal),
+ ).then(
+ (res) => {
+ return buildResponse(res).then((_data) => {
+ return JsonDecode(_data, 'GetGasSponsorReturn')
+ })
+ },
+ (error) => {
+ throw WebrpcRequestFailedError.new({
+ cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`,
+ })
+ },
+ )
+ }
+
+ addGasSponsor = (req: AddGasSponsorArgs, headers?: object, signal?: AbortSignal): Promise => {
+ return this.fetch(
+ this.url('AddGasSponsor'),
+ createHttpRequest(JsonEncode(req, 'AddGasSponsorArgs'), headers, signal),
+ ).then(
+ (res) => {
+ return buildResponse(res).then((_data) => {
+ return JsonDecode(_data, 'AddGasSponsorReturn')
+ })
+ },
+ (error) => {
+ throw WebrpcRequestFailedError.new({
+ cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`,
+ })
+ },
+ )
+ }
+
+ updateGasSponsor = (
+ req: UpdateGasSponsorArgs,
+ headers?: object,
+ signal?: AbortSignal,
+ ): Promise => {
+ return this.fetch(
+ this.url('UpdateGasSponsor'),
+ createHttpRequest(JsonEncode(req, 'UpdateGasSponsorArgs'), headers, signal),
+ ).then(
+ (res) => {
+ return buildResponse(res).then((_data) => {
+ return JsonDecode(_data, 'UpdateGasSponsorReturn')
+ })
+ },
+ (error) => {
+ throw WebrpcRequestFailedError.new({
+ cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`,
+ })
+ },
+ )
+ }
+
+ removeGasSponsor = (
+ req: RemoveGasSponsorArgs,
+ headers?: object,
+ signal?: AbortSignal,
+ ): Promise => {
+ return this.fetch(
+ this.url('RemoveGasSponsor'),
+ createHttpRequest(JsonEncode(req, 'RemoveGasSponsorArgs'), headers, signal),
+ ).then(
+ (res) => {
+ return buildResponse(res).then((_data) => {
+ return JsonDecode(_data, 'RemoveGasSponsorReturn')
+ })
+ },
+ (error) => {
+ throw WebrpcRequestFailedError.new({
+ cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`,
+ })
+ },
+ )
+ }
+
+ addressGasSponsors = (
+ req: AddressGasSponsorsArgs,
+ headers?: object,
+ signal?: AbortSignal,
+ ): Promise => {
+ return this.fetch(
+ this.url('AddressGasSponsors'),
+ createHttpRequest(JsonEncode(req, 'AddressGasSponsorsArgs'), headers, signal),
+ ).then(
+ (res) => {
+ return buildResponse(res).then((_data) => {
+ return JsonDecode(_data, 'AddressGasSponsorsReturn')
+ })
+ },
+ (error) => {
+ throw WebrpcRequestFailedError.new({
+ cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`,
+ })
+ },
+ )
+ }
+
+ getProjectBalance = (
+ req: GetProjectBalanceArgs,
+ headers?: object,
+ signal?: AbortSignal,
+ ): Promise => {
+ return this.fetch(
+ this.url('GetProjectBalance'),
+ createHttpRequest(JsonEncode(req, 'GetProjectBalanceArgs'), headers, signal),
+ ).then(
+ (res) => {
+ return buildResponse(res).then((_data) => {
+ return JsonDecode(_data, 'GetProjectBalanceReturn')
+ })
+ },
+ (error) => {
+ throw WebrpcRequestFailedError.new({
+ cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`,
+ })
+ },
+ )
+ }
+
+ adjustProjectBalance = (
+ req: AdjustProjectBalanceArgs,
+ headers?: object,
+ signal?: AbortSignal,
+ ): Promise => {
+ return this.fetch(
+ this.url('AdjustProjectBalance'),
+ createHttpRequest(JsonEncode(req, 'AdjustProjectBalanceArgs'), headers, signal),
+ ).then(
+ (res) => {
+ return buildResponse(res).then((_data) => {
+ return JsonDecode(_data, 'AdjustProjectBalanceReturn')
+ })
+ },
+ (error) => {
+ throw WebrpcRequestFailedError.new({
+ cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`,
+ })
+ },
+ )
+ }
+}
+
+const createHttpRequest = (body: string = '{}', headers: object = {}, signal: AbortSignal | null = null): object => {
+ const reqHeaders: { [key: string]: string } = { ...headers, 'Content-Type': 'application/json' }
+ return { method: 'POST', headers: reqHeaders, body, signal }
+}
+
+const buildResponse = (res: Response): Promise => {
+ return res.text().then((text) => {
+ let data
+ try {
+ data = JSON.parse(text)
+ } catch (error) {
+ throw WebrpcBadResponseError.new({
+ status: res.status,
+ cause: `JSON.parse(): ${error instanceof Error ? error.message : String(error)}: response text: ${text}`,
+ })
+ }
+ if (!res.ok) {
+ const code: number = typeof data.code === 'number' ? data.code : 0
+ throw (webrpcErrorByCode[code] || WebrpcError).new(data)
+ }
+ return data
+ })
+}
+
+export type Fetch = (input: RequestInfo, init?: RequestInit) => Promise
+
+//
+// BigInt helpers
+//
+
+const BIG_INT_FIELDS: { [typ: string]: (string | [string, string])[] } = {
+ SendMetaTxnArgs: [['preconditions', 'TransactionPrecondition[]']],
+ TransactionPrecondition: ['minAmount'],
+ Transactions: [['preconditions', 'TransactionPrecondition[]']],
+}
+
+// Encode in-place: mutate provided object graph to serialize bigints to strings.
+function encodeType(typ: string, obj: any): any {
+ if (obj == null || typeof obj !== 'object') return obj
+ const descs = BIG_INT_FIELDS[typ] || []
+ if (!descs.length) return obj
+ for (const d of descs) {
+ if (Array.isArray(d)) {
+ const [fieldName, nestedType] = d
+ if (fieldName.endsWith('[]')) {
+ const base = fieldName.slice(0, -2)
+ const arr = obj[base]
+ if (Array.isArray(arr)) {
+ for (let i = 0; i < arr.length; i++) arr[i] = encodeType(nestedType, arr[i])
+ }
+ } else if (obj[fieldName]) {
+ obj[fieldName] = encodeType(nestedType, obj[fieldName])
+ }
+ continue
+ }
+ if (d.endsWith('[]')) {
+ const base = d.slice(0, -2)
+ const arr = obj[base]
+ if (Array.isArray(arr)) {
+ for (let i = 0; i < arr.length; i++) {
+ if (typeof arr[i] === 'bigint') arr[i] = arr[i].toString()
+ }
+ }
+ continue
+ }
+ if (typeof obj[d] === 'bigint') obj[d] = obj[d].toString()
+ }
+ return obj
+}
+
+// Decode in-place: mutate object graph; throw if expected numeric string is invalid.
+function decodeType(typ: string, obj: any): any {
+ if (obj == null || typeof obj !== 'object') return obj
+ const descs = BIG_INT_FIELDS[typ] || []
+ if (!descs.length) return obj
+ for (const d of descs) {
+ if (Array.isArray(d)) {
+ const [fieldName, nestedType] = d
+ if (fieldName.endsWith('[]')) {
+ const base = fieldName.slice(0, -2)
+ const arr = obj[base]
+ if (Array.isArray(arr)) {
+ for (let i = 0; i < arr.length; i++) arr[i] = decodeType(nestedType, arr[i])
+ }
+ } else if (obj[fieldName]) {
+ obj[fieldName] = decodeType(nestedType, obj[fieldName])
+ }
+ continue
+ }
+ if (d.endsWith('[]')) {
+ const base = d.slice(0, -2)
+ const arr = obj[base]
+ if (Array.isArray(arr)) {
+ for (let i = 0; i < arr.length; i++) {
+ const v = arr[i]
+ if (typeof v === 'string') {
+ try {
+ arr[i] = BigInt(v)
+ } catch (e) {
+ throw WebrpcBadResponseError.new({ cause: `Invalid bigint value for ${base}[${i}]: ${v}` })
+ }
+ }
+ }
+ }
+ continue
+ }
+ const v = obj[d]
+ if (typeof v === 'string') {
+ try {
+ obj[d] = BigInt(v)
+ } catch (e) {
+ throw WebrpcBadResponseError.new({ cause: `Invalid bigint value for ${d}: ${v}` })
+ }
+ }
+ }
+ return obj
+}
+
+// Encode object of given root type to JSON with BigInts converted to decimal strings.
+export const JsonEncode = (obj: T, typ: string = ''): string => {
+ return JSON.stringify(encodeType(typ, obj))
+}
+
+// Decode data (JSON string or already-parsed object) and convert declared BigInt string fields back to BigInt.
+export const JsonDecode = (data: string | any, typ: string = ''): T => {
+ let parsed: any = data
+ if (typeof data === 'string') {
+ try {
+ parsed = JSON.parse(data)
+ } catch (err) {
+ throw WebrpcBadResponseError.new({ cause: `JsonDecode: JSON.parse failed: ${(err as Error).message}` })
+ }
+ }
+ return decodeType(typ, parsed) as T
+}
+
+//
+// Errors
+//
+
+type WebrpcErrorParams = { name?: string; code?: number; message?: string; status?: number; cause?: string }
+
+export class WebrpcError extends Error {
+ code: number
+ status: number
+
+ constructor(error: WebrpcErrorParams = {}) {
+ super(error.message)
+ this.name = error.name || 'WebrpcEndpointError'
+ this.code = typeof error.code === 'number' ? error.code : 0
+ this.message = error.message || `endpoint error`
+ this.status = typeof error.status === 'number' ? error.status : 400
+ if (error.cause !== undefined) this.cause = error.cause
+ Object.setPrototypeOf(this, WebrpcError.prototype)
+ }
+
+ static new(payload: any): WebrpcError {
+ return new this({ message: payload.message, code: payload.code, status: payload.status, cause: payload.cause })
+ }
+}
+
+export class WebrpcEndpointError extends WebrpcError {
+ constructor(error: WebrpcErrorParams = {}) {
+ super(error)
+ this.name = error.name || 'WebrpcEndpoint'
+ this.code = typeof error.code === 'number' ? error.code : 0
+ this.message = error.message || `endpoint error`
+ this.status = typeof error.status === 'number' ? error.status : 400
+ if (error.cause !== undefined) this.cause = error.cause
+ Object.setPrototypeOf(this, WebrpcEndpointError.prototype)
+ }
+}
+
+export class WebrpcRequestFailedError extends WebrpcError {
+ constructor(error: WebrpcErrorParams = {}) {
+ super(error)
+ this.name = error.name || 'WebrpcRequestFailed'
+ this.code = typeof error.code === 'number' ? error.code : -1
+ this.message = error.message || `request failed`
+ this.status = typeof error.status === 'number' ? error.status : 400
+ if (error.cause !== undefined) this.cause = error.cause
+ Object.setPrototypeOf(this, WebrpcRequestFailedError.prototype)
+ }
+}
+
+export class WebrpcBadRouteError extends WebrpcError {
+ constructor(error: WebrpcErrorParams = {}) {
+ super(error)
+ this.name = error.name || 'WebrpcBadRoute'
+ this.code = typeof error.code === 'number' ? error.code : -2
+ this.message = error.message || `bad route`
+ this.status = typeof error.status === 'number' ? error.status : 404
+ if (error.cause !== undefined) this.cause = error.cause
+ Object.setPrototypeOf(this, WebrpcBadRouteError.prototype)
+ }
+}
+
+export class WebrpcBadMethodError extends WebrpcError {
+ constructor(error: WebrpcErrorParams = {}) {
+ super(error)
+ this.name = error.name || 'WebrpcBadMethod'
+ this.code = typeof error.code === 'number' ? error.code : -3
+ this.message = error.message || `bad method`
+ this.status = typeof error.status === 'number' ? error.status : 405
+ if (error.cause !== undefined) this.cause = error.cause
+ Object.setPrototypeOf(this, WebrpcBadMethodError.prototype)
+ }
+}
+
+export class WebrpcBadRequestError extends WebrpcError {
+ constructor(error: WebrpcErrorParams = {}) {
+ super(error)
+ this.name = error.name || 'WebrpcBadRequest'
+ this.code = typeof error.code === 'number' ? error.code : -4
+ this.message = error.message || `bad request`
+ this.status = typeof error.status === 'number' ? error.status : 400
+ if (error.cause !== undefined) this.cause = error.cause
+ Object.setPrototypeOf(this, WebrpcBadRequestError.prototype)
+ }
+}
+
+export class WebrpcBadResponseError extends WebrpcError {
+ constructor(error: WebrpcErrorParams = {}) {
+ super(error)
+ this.name = error.name || 'WebrpcBadResponse'
+ this.code = typeof error.code === 'number' ? error.code : -5
+ this.message = error.message || `bad response`
+ this.status = typeof error.status === 'number' ? error.status : 500
+ if (error.cause !== undefined) this.cause = error.cause
+ Object.setPrototypeOf(this, WebrpcBadResponseError.prototype)
+ }
+}
+
+export class WebrpcServerPanicError extends WebrpcError {
+ constructor(error: WebrpcErrorParams = {}) {
+ super(error)
+ this.name = error.name || 'WebrpcServerPanic'
+ this.code = typeof error.code === 'number' ? error.code : -6
+ this.message = error.message || `server panic`
+ this.status = typeof error.status === 'number' ? error.status : 500
+ if (error.cause !== undefined) this.cause = error.cause
+ Object.setPrototypeOf(this, WebrpcServerPanicError.prototype)
+ }
+}
+
+export class WebrpcInternalErrorError extends WebrpcError {
+ constructor(error: WebrpcErrorParams = {}) {
+ super(error)
+ this.name = error.name || 'WebrpcInternalError'
+ this.code = typeof error.code === 'number' ? error.code : -7
+ this.message = error.message || `internal error`
+ this.status = typeof error.status === 'number' ? error.status : 500
+ if (error.cause !== undefined) this.cause = error.cause
+ Object.setPrototypeOf(this, WebrpcInternalErrorError.prototype)
+ }
+}
+
+export class WebrpcClientAbortedError extends WebrpcError {
+ constructor(error: WebrpcErrorParams = {}) {
+ super(error)
+ this.name = error.name || 'WebrpcClientAborted'
+ this.code = typeof error.code === 'number' ? error.code : -8
+ this.message = error.message || `request aborted by client`
+ this.status = typeof error.status === 'number' ? error.status : 400
+ if (error.cause !== undefined) this.cause = error.cause
+ Object.setPrototypeOf(this, WebrpcClientAbortedError.prototype)
+ }
+}
+
+export class WebrpcStreamLostError extends WebrpcError {
+ constructor(error: WebrpcErrorParams = {}) {
+ super(error)
+ this.name = error.name || 'WebrpcStreamLost'
+ this.code = typeof error.code === 'number' ? error.code : -9
+ this.message = error.message || `stream lost`
+ this.status = typeof error.status === 'number' ? error.status : 400
+ if (error.cause !== undefined) this.cause = error.cause
+ Object.setPrototypeOf(this, WebrpcStreamLostError.prototype)
+ }
+}
+
+export class WebrpcStreamFinishedError extends WebrpcError {
+ constructor(error: WebrpcErrorParams = {}) {
+ super(error)
+ this.name = error.name || 'WebrpcStreamFinished'
+ this.code = typeof error.code === 'number' ? error.code : -10
+ this.message = error.message || `stream finished`
+ this.status = typeof error.status === 'number' ? error.status : 200
+ if (error.cause !== undefined) this.cause = error.cause
+ Object.setPrototypeOf(this, WebrpcStreamFinishedError.prototype)
+ }
+}
+
+//
+// Schema errors
+//
+
+export class UnauthorizedError extends WebrpcError {
+ constructor(error: WebrpcErrorParams = {}) {
+ super(error)
+ this.name = error.name || 'Unauthorized'
+ this.code = typeof error.code === 'number' ? error.code : 1000
+ this.message = error.message || `Unauthorized access`
+ this.status = typeof error.status === 'number' ? error.status : 401
+ if (error.cause !== undefined) this.cause = error.cause
+ Object.setPrototypeOf(this, UnauthorizedError.prototype)
+ }
+}
+
+export class PermissionDeniedError extends WebrpcError {
+ constructor(error: WebrpcErrorParams = {}) {
+ super(error)
+ this.name = error.name || 'PermissionDenied'
+ this.code = typeof error.code === 'number' ? error.code : 1001
+ this.message = error.message || `Permission denied`
+ this.status = typeof error.status === 'number' ? error.status : 403
+ if (error.cause !== undefined) this.cause = error.cause
+ Object.setPrototypeOf(this, PermissionDeniedError.prototype)
+ }
+}
+
+export class SessionExpiredError extends WebrpcError {
+ constructor(error: WebrpcErrorParams = {}) {
+ super(error)
+ this.name = error.name || 'SessionExpired'
+ this.code = typeof error.code === 'number' ? error.code : 1002
+ this.message = error.message || `Session expired`
+ this.status = typeof error.status === 'number' ? error.status : 403
+ if (error.cause !== undefined) this.cause = error.cause
+ Object.setPrototypeOf(this, SessionExpiredError.prototype)
+ }
+}
+
+export class MethodNotFoundError extends WebrpcError {
+ constructor(error: WebrpcErrorParams = {}) {
+ super(error)
+ this.name = error.name || 'MethodNotFound'
+ this.code = typeof error.code === 'number' ? error.code : 1003
+ this.message = error.message || `Method not found`
+ this.status = typeof error.status === 'number' ? error.status : 404
+ if (error.cause !== undefined) this.cause = error.cause
+ Object.setPrototypeOf(this, MethodNotFoundError.prototype)
+ }
+}
+
+export class RequestConflictError extends WebrpcError {
+ constructor(error: WebrpcErrorParams = {}) {
+ super(error)
+ this.name = error.name || 'RequestConflict'
+ this.code = typeof error.code === 'number' ? error.code : 1004
+ this.message = error.message || `Conflict with target resource`
+ this.status = typeof error.status === 'number' ? error.status : 409
+ if (error.cause !== undefined) this.cause = error.cause
+ Object.setPrototypeOf(this, RequestConflictError.prototype)
+ }
+}
+
+export class AbortedError extends WebrpcError {
+ constructor(error: WebrpcErrorParams = {}) {
+ super(error)
+ this.name = error.name || 'Aborted'
+ this.code = typeof error.code === 'number' ? error.code : 1005
+ this.message = error.message || `Request aborted`
+ this.status = typeof error.status === 'number' ? error.status : 400
+ if (error.cause !== undefined) this.cause = error.cause
+ Object.setPrototypeOf(this, AbortedError.prototype)
+ }
+}
+
+export class GeoblockedError extends WebrpcError {
+ constructor(error: WebrpcErrorParams = {}) {
+ super(error)
+ this.name = error.name || 'Geoblocked'
+ this.code = typeof error.code === 'number' ? error.code : 1006
+ this.message = error.message || `Geoblocked region`
+ this.status = typeof error.status === 'number' ? error.status : 451
+ if (error.cause !== undefined) this.cause = error.cause
+ Object.setPrototypeOf(this, GeoblockedError.prototype)
+ }
+}
+
+export class RateLimitedError extends WebrpcError {
+ constructor(error: WebrpcErrorParams = {}) {
+ super(error)
+ this.name = error.name || 'RateLimited'
+ this.code = typeof error.code === 'number' ? error.code : 1007
+ this.message = error.message || `Rate-limited. Please slow down.`
+ this.status = typeof error.status === 'number' ? error.status : 429
+ if (error.cause !== undefined) this.cause = error.cause
+ Object.setPrototypeOf(this, RateLimitedError.prototype)
+ }
+}
+
+export class ProjectNotFoundError extends WebrpcError {
+ constructor(error: WebrpcErrorParams = {}) {
+ super(error)
+ this.name = error.name || 'ProjectNotFound'
+ this.code = typeof error.code === 'number' ? error.code : 1008
+ this.message = error.message || `Project not found`
+ this.status = typeof error.status === 'number' ? error.status : 401
+ if (error.cause !== undefined) this.cause = error.cause
+ Object.setPrototypeOf(this, ProjectNotFoundError.prototype)
+ }
+}
+
+export class AccessKeyNotFoundError extends WebrpcError {
+ constructor(error: WebrpcErrorParams = {}) {
+ super(error)
+ this.name = error.name || 'AccessKeyNotFound'
+ this.code = typeof error.code === 'number' ? error.code : 1101
+ this.message = error.message || `Access key not found`
+ this.status = typeof error.status === 'number' ? error.status : 401
+ if (error.cause !== undefined) this.cause = error.cause
+ Object.setPrototypeOf(this, AccessKeyNotFoundError.prototype)
+ }
+}
+
+export class AccessKeyMismatchError extends WebrpcError {
+ constructor(error: WebrpcErrorParams = {}) {
+ super(error)
+ this.name = error.name || 'AccessKeyMismatch'
+ this.code = typeof error.code === 'number' ? error.code : 1102
+ this.message = error.message || `Access key mismatch`
+ this.status = typeof error.status === 'number' ? error.status : 409
+ if (error.cause !== undefined) this.cause = error.cause
+ Object.setPrototypeOf(this, AccessKeyMismatchError.prototype)
+ }
+}
+
+export class InvalidOriginError extends WebrpcError {
+ constructor(error: WebrpcErrorParams = {}) {
+ super(error)
+ this.name = error.name || 'InvalidOrigin'
+ this.code = typeof error.code === 'number' ? error.code : 1103
+ this.message = error.message || `Invalid origin for Access Key`
+ this.status = typeof error.status === 'number' ? error.status : 403
+ if (error.cause !== undefined) this.cause = error.cause
+ Object.setPrototypeOf(this, InvalidOriginError.prototype)
+ }
+}
+
+export class InvalidServiceError extends WebrpcError {
+ constructor(error: WebrpcErrorParams = {}) {
+ super(error)
+ this.name = error.name || 'InvalidService'
+ this.code = typeof error.code === 'number' ? error.code : 1104
+ this.message = error.message || `Service not enabled for Access key`
+ this.status = typeof error.status === 'number' ? error.status : 403
+ if (error.cause !== undefined) this.cause = error.cause
+ Object.setPrototypeOf(this, InvalidServiceError.prototype)
+ }
+}
+
+export class UnauthorizedUserError extends WebrpcError {
+ constructor(error: WebrpcErrorParams = {}) {
+ super(error)
+ this.name = error.name || 'UnauthorizedUser'
+ this.code = typeof error.code === 'number' ? error.code : 1105
+ this.message = error.message || `Unauthorized user`
+ this.status = typeof error.status === 'number' ? error.status : 403
+ if (error.cause !== undefined) this.cause = error.cause
+ Object.setPrototypeOf(this, UnauthorizedUserError.prototype)
+ }
+}
+
+export class QuotaExceededError extends WebrpcError {
+ constructor(error: WebrpcErrorParams = {}) {
+ super(error)
+ this.name = error.name || 'QuotaExceeded'
+ this.code = typeof error.code === 'number' ? error.code : 1200
+ this.message = error.message || `Quota request exceeded`
+ this.status = typeof error.status === 'number' ? error.status : 429
+ if (error.cause !== undefined) this.cause = error.cause
+ Object.setPrototypeOf(this, QuotaExceededError.prototype)
+ }
+}
+
+export class QuotaRateLimitError extends WebrpcError {
+ constructor(error: WebrpcErrorParams = {}) {
+ super(error)
+ this.name = error.name || 'QuotaRateLimit'
+ this.code = typeof error.code === 'number' ? error.code : 1201
+ this.message = error.message || `Quota rate limit exceeded`
+ this.status = typeof error.status === 'number' ? error.status : 429
+ if (error.cause !== undefined) this.cause = error.cause
+ Object.setPrototypeOf(this, QuotaRateLimitError.prototype)
+ }
+}
+
+export class NoDefaultKeyError extends WebrpcError {
+ constructor(error: WebrpcErrorParams = {}) {
+ super(error)
+ this.name = error.name || 'NoDefaultKey'
+ this.code = typeof error.code === 'number' ? error.code : 1300
+ this.message = error.message || `No default access key found`
+ this.status = typeof error.status === 'number' ? error.status : 403
+ if (error.cause !== undefined) this.cause = error.cause
+ Object.setPrototypeOf(this, NoDefaultKeyError.prototype)
+ }
+}
+
+export class MaxAccessKeysError extends WebrpcError {
+ constructor(error: WebrpcErrorParams = {}) {
+ super(error)
+ this.name = error.name || 'MaxAccessKeys'
+ this.code = typeof error.code === 'number' ? error.code : 1301
+ this.message = error.message || `Access keys limit reached`
+ this.status = typeof error.status === 'number' ? error.status : 403
+ if (error.cause !== undefined) this.cause = error.cause
+ Object.setPrototypeOf(this, MaxAccessKeysError.prototype)
+ }
+}
+
+export class AtLeastOneKeyError extends WebrpcError {
+ constructor(error: WebrpcErrorParams = {}) {
+ super(error)
+ this.name = error.name || 'AtLeastOneKey'
+ this.code = typeof error.code === 'number' ? error.code : 1302
+ this.message = error.message || `You need at least one Access Key`
+ this.status = typeof error.status === 'number' ? error.status : 403
+ if (error.cause !== undefined) this.cause = error.cause
+ Object.setPrototypeOf(this, AtLeastOneKeyError.prototype)
+ }
+}
+
+export class TimeoutError extends WebrpcError {
+ constructor(error: WebrpcErrorParams = {}) {
+ super(error)
+ this.name = error.name || 'Timeout'
+ this.code = typeof error.code === 'number' ? error.code : 1900
+ this.message = error.message || `Request timed out`
+ this.status = typeof error.status === 'number' ? error.status : 408
+ if (error.cause !== undefined) this.cause = error.cause
+ Object.setPrototypeOf(this, TimeoutError.prototype)
+ }
+}
+
+export class InvalidArgumentError extends WebrpcError {
+ constructor(error: WebrpcErrorParams = {}) {
+ super(error)
+ this.name = error.name || 'InvalidArgument'
+ this.code = typeof error.code === 'number' ? error.code : 2001
+ this.message = error.message || `Invalid argument`
+ this.status = typeof error.status === 'number' ? error.status : 400
+ if (error.cause !== undefined) this.cause = error.cause
+ Object.setPrototypeOf(this, InvalidArgumentError.prototype)
+ }
+}
+
+export class UnavailableError extends WebrpcError {
+ constructor(error: WebrpcErrorParams = {}) {
+ super(error)
+ this.name = error.name || 'Unavailable'
+ this.code = typeof error.code === 'number' ? error.code : 2002
+ this.message = error.message || `Unavailable resource`
+ this.status = typeof error.status === 'number' ? error.status : 400
+ if (error.cause !== undefined) this.cause = error.cause
+ Object.setPrototypeOf(this, UnavailableError.prototype)
+ }
+}
+
+export class QueryFailedError extends WebrpcError {
+ constructor(error: WebrpcErrorParams = {}) {
+ super(error)
+ this.name = error.name || 'QueryFailed'
+ this.code = typeof error.code === 'number' ? error.code : 2003
+ this.message = error.message || `Query failed`
+ this.status = typeof error.status === 'number' ? error.status : 400
+ if (error.cause !== undefined) this.cause = error.cause
+ Object.setPrototypeOf(this, QueryFailedError.prototype)
+ }
+}
+
+export class NotFoundError extends WebrpcError {
+ constructor(error: WebrpcErrorParams = {}) {
+ super(error)
+ this.name = error.name || 'NotFound'
+ this.code = typeof error.code === 'number' ? error.code : 3000
+ this.message = error.message || `Resource not found`
+ this.status = typeof error.status === 'number' ? error.status : 400
+ if (error.cause !== undefined) this.cause = error.cause
+ Object.setPrototypeOf(this, NotFoundError.prototype)
+ }
+}
+
+export class InsufficientFeeError extends WebrpcError {
+ constructor(error: WebrpcErrorParams = {}) {
+ super(error)
+ this.name = error.name || 'InsufficientFee'
+ this.code = typeof error.code === 'number' ? error.code : 3004
+ this.message = error.message || `Insufficient fee`
+ this.status = typeof error.status === 'number' ? error.status : 402
+ if (error.cause !== undefined) this.cause = error.cause
+ Object.setPrototypeOf(this, InsufficientFeeError.prototype)
+ }
+}
+
+export class NotEnoughBalanceError extends WebrpcError {
+ constructor(error: WebrpcErrorParams = {}) {
+ super(error)
+ this.name = error.name || 'NotEnoughBalance'
+ this.code = typeof error.code === 'number' ? error.code : 3005
+ this.message = error.message || `Not enough balance`
+ this.status = typeof error.status === 'number' ? error.status : 402
+ if (error.cause !== undefined) this.cause = error.cause
+ Object.setPrototypeOf(this, NotEnoughBalanceError.prototype)
+ }
+}
+
+export class SimulationFailedError extends WebrpcError {
+ constructor(error: WebrpcErrorParams = {}) {
+ super(error)
+ this.name = error.name || 'SimulationFailed'
+ this.code = typeof error.code === 'number' ? error.code : 3006
+ this.message = error.message || `Simulation failed`
+ this.status = typeof error.status === 'number' ? error.status : 400
+ if (error.cause !== undefined) this.cause = error.cause
+ Object.setPrototypeOf(this, SimulationFailedError.prototype)
+ }
+}
+
+export enum errors {
+ WebrpcEndpoint = 'WebrpcEndpoint',
+ WebrpcRequestFailed = 'WebrpcRequestFailed',
+ WebrpcBadRoute = 'WebrpcBadRoute',
+ WebrpcBadMethod = 'WebrpcBadMethod',
+ WebrpcBadRequest = 'WebrpcBadRequest',
+ WebrpcBadResponse = 'WebrpcBadResponse',
+ WebrpcServerPanic = 'WebrpcServerPanic',
+ WebrpcInternalError = 'WebrpcInternalError',
+ WebrpcClientAborted = 'WebrpcClientAborted',
+ WebrpcStreamLost = 'WebrpcStreamLost',
+ WebrpcStreamFinished = 'WebrpcStreamFinished',
+ Unauthorized = 'Unauthorized',
+ PermissionDenied = 'PermissionDenied',
+ SessionExpired = 'SessionExpired',
+ MethodNotFound = 'MethodNotFound',
+ RequestConflict = 'RequestConflict',
+ Aborted = 'Aborted',
+ Geoblocked = 'Geoblocked',
+ RateLimited = 'RateLimited',
+ ProjectNotFound = 'ProjectNotFound',
+ AccessKeyNotFound = 'AccessKeyNotFound',
+ AccessKeyMismatch = 'AccessKeyMismatch',
+ InvalidOrigin = 'InvalidOrigin',
+ InvalidService = 'InvalidService',
+ UnauthorizedUser = 'UnauthorizedUser',
+ QuotaExceeded = 'QuotaExceeded',
+ QuotaRateLimit = 'QuotaRateLimit',
+ NoDefaultKey = 'NoDefaultKey',
+ MaxAccessKeys = 'MaxAccessKeys',
+ AtLeastOneKey = 'AtLeastOneKey',
+ Timeout = 'Timeout',
+ InvalidArgument = 'InvalidArgument',
+ Unavailable = 'Unavailable',
+ QueryFailed = 'QueryFailed',
+ NotFound = 'NotFound',
+ InsufficientFee = 'InsufficientFee',
+ NotEnoughBalance = 'NotEnoughBalance',
+ SimulationFailed = 'SimulationFailed',
+}
+
+export enum WebrpcErrorCodes {
+ WebrpcEndpoint = 0,
+ WebrpcRequestFailed = -1,
+ WebrpcBadRoute = -2,
+ WebrpcBadMethod = -3,
+ WebrpcBadRequest = -4,
+ WebrpcBadResponse = -5,
+ WebrpcServerPanic = -6,
+ WebrpcInternalError = -7,
+ WebrpcClientAborted = -8,
+ WebrpcStreamLost = -9,
+ WebrpcStreamFinished = -10,
+ Unauthorized = 1000,
+ PermissionDenied = 1001,
+ SessionExpired = 1002,
+ MethodNotFound = 1003,
+ RequestConflict = 1004,
+ Aborted = 1005,
+ Geoblocked = 1006,
+ RateLimited = 1007,
+ ProjectNotFound = 1008,
+ AccessKeyNotFound = 1101,
+ AccessKeyMismatch = 1102,
+ InvalidOrigin = 1103,
+ InvalidService = 1104,
+ UnauthorizedUser = 1105,
+ QuotaExceeded = 1200,
+ QuotaRateLimit = 1201,
+ NoDefaultKey = 1300,
+ MaxAccessKeys = 1301,
+ AtLeastOneKey = 1302,
+ Timeout = 1900,
+ InvalidArgument = 2001,
+ Unavailable = 2002,
+ QueryFailed = 2003,
+ NotFound = 3000,
+ InsufficientFee = 3004,
+ NotEnoughBalance = 3005,
+ SimulationFailed = 3006,
+}
+
+export const webrpcErrorByCode: { [code: number]: any } = {
+ [0]: WebrpcEndpointError,
+ [-1]: WebrpcRequestFailedError,
+ [-2]: WebrpcBadRouteError,
+ [-3]: WebrpcBadMethodError,
+ [-4]: WebrpcBadRequestError,
+ [-5]: WebrpcBadResponseError,
+ [-6]: WebrpcServerPanicError,
+ [-7]: WebrpcInternalErrorError,
+ [-8]: WebrpcClientAbortedError,
+ [-9]: WebrpcStreamLostError,
+ [-10]: WebrpcStreamFinishedError,
+ [1000]: UnauthorizedError,
+ [1001]: PermissionDeniedError,
+ [1002]: SessionExpiredError,
+ [1003]: MethodNotFoundError,
+ [1004]: RequestConflictError,
+ [1005]: AbortedError,
+ [1006]: GeoblockedError,
+ [1007]: RateLimitedError,
+ [1008]: ProjectNotFoundError,
+ [1101]: AccessKeyNotFoundError,
+ [1102]: AccessKeyMismatchError,
+ [1103]: InvalidOriginError,
+ [1104]: InvalidServiceError,
+ [1105]: UnauthorizedUserError,
+ [1200]: QuotaExceededError,
+ [1201]: QuotaRateLimitError,
+ [1300]: NoDefaultKeyError,
+ [1301]: MaxAccessKeysError,
+ [1302]: AtLeastOneKeyError,
+ [1900]: TimeoutError,
+ [2001]: InvalidArgumentError,
+ [2002]: UnavailableError,
+ [2003]: QueryFailedError,
+ [3000]: NotFoundError,
+ [3004]: InsufficientFeeError,
+ [3005]: NotEnoughBalanceError,
+ [3006]: SimulationFailedError,
+}
+
+//
+// Webrpc
+//
+
+export const WebrpcHeader = 'Webrpc'
+
+export const WebrpcHeaderValue = 'webrpc@v0.30.2;gen-typescript@v0.22.2;sequence-relayer@v0.4.1'
+
+type WebrpcGenVersions = {
+ WebrpcGenVersion: string
+ codeGenName: string
+ codeGenVersion: string
+ schemaName: string
+ schemaVersion: string
+}
+
+export function VersionFromHeader(headers: Headers): WebrpcGenVersions {
+ const headerValue = headers.get(WebrpcHeader)
+ if (!headerValue) {
+ return {
+ WebrpcGenVersion: '',
+ codeGenName: '',
+ codeGenVersion: '',
+ schemaName: '',
+ schemaVersion: '',
+ }
+ }
+
+ return parseWebrpcGenVersions(headerValue)
+}
+
+function parseWebrpcGenVersions(header: string): WebrpcGenVersions {
+ const versions = header.split(';')
+ if (versions.length < 3) {
+ return {
+ WebrpcGenVersion: '',
+ codeGenName: '',
+ codeGenVersion: '',
+ schemaName: '',
+ schemaVersion: '',
+ }
+ }
+
+ const [_, WebrpcGenVersion] = versions[0]!.split('@')
+ const [codeGenName, codeGenVersion] = versions[1]!.split('@')
+ const [schemaName, schemaVersion] = versions[2]!.split('@')
+
+ return {
+ WebrpcGenVersion: WebrpcGenVersion ?? '',
+ codeGenName: codeGenName ?? '',
+ codeGenVersion: codeGenVersion ?? '',
+ schemaName: schemaName ?? '',
+ schemaVersion: schemaVersion ?? '',
+ }
+}
diff --git a/packages/services/relayer/src/relayer/standard/abi.ts b/packages/services/relayer/src/relayer/standard/abi.ts
new file mode 100644
index 000000000..ccd965a81
--- /dev/null
+++ b/packages/services/relayer/src/relayer/standard/abi.ts
@@ -0,0 +1,13 @@
+import { AbiFunction } from 'ox'
+
+// ERC20 ABI functions
+export const erc20BalanceOf = AbiFunction.from('function balanceOf(address) returns (uint256)')
+export const erc20Allowance = AbiFunction.from('function allowance(address,address) returns (uint256)')
+
+// ERC721 ABI functions
+export const erc721OwnerOf = AbiFunction.from('function ownerOf(uint256) returns (address)')
+export const erc721GetApproved = AbiFunction.from('function getApproved(uint256) returns (address)')
+
+// ERC1155 ABI functions
+export const erc1155BalanceOf = AbiFunction.from('function balanceOf(address,uint256) returns (uint256)')
+export const erc1155IsApprovedForAll = AbiFunction.from('function isApprovedForAll(address,address) returns (bool)')
diff --git a/packages/services/relayer/src/relayer/standard/eip6963.ts b/packages/services/relayer/src/relayer/standard/eip6963.ts
new file mode 100644
index 000000000..9d4861363
--- /dev/null
+++ b/packages/services/relayer/src/relayer/standard/eip6963.ts
@@ -0,0 +1,74 @@
+import { createStore, EIP6963ProviderInfo, EIP6963ProviderDetail } from 'mipd'
+import { EIP1193ProviderAdapter, LocalRelayer } from './local.js'
+import { FeeOption, FeeQuote, OperationStatus, Relayer } from '../index.js'
+import { Address, Hex } from 'ox'
+import { Payload } from '@0xsequence/wallet-primitives'
+import { FeeToken, TransactionPrecondition } from '../rpc-relayer/relayer.gen.js'
+
+export class EIP6963Relayer implements Relayer {
+ public readonly kind: 'relayer' = 'relayer'
+ public readonly type = 'eip6963'
+ public readonly id: string
+ public readonly info: EIP6963ProviderInfo
+ private readonly relayer: LocalRelayer
+
+ constructor(detail: EIP6963ProviderDetail) {
+ this.info = detail.info
+ this.id = detail.info.uuid
+
+ this.relayer = new LocalRelayer(new EIP1193ProviderAdapter(detail.provider))
+ }
+
+ isAvailable(wallet: Address.Address, chainId: number): Promise {
+ return this.relayer.isAvailable(wallet, chainId)
+ }
+
+ feeTokens(): Promise<{ isFeeRequired: boolean; tokens?: FeeToken[]; paymentAddress?: Address.Address }> {
+ return this.relayer.feeTokens()
+ }
+
+ feeOptions(
+ wallet: Address.Address,
+ chainId: number,
+ calls: Payload.Call[],
+ ): Promise<{ options: FeeOption[]; quote?: FeeQuote }> {
+ return this.relayer.feeOptions(wallet, chainId, calls)
+ }
+
+ async relay(to: Address.Address, data: Hex.Hex, chainId: number, _?: FeeQuote): Promise<{ opHash: Hex.Hex }> {
+ return this.relayer.relay(to, data, chainId)
+ }
+
+ status(opHash: Hex.Hex, chainId: number): Promise {
+ return this.relayer.status(opHash, chainId)
+ }
+
+ async checkPrecondition(precondition: TransactionPrecondition): Promise {
+ return this.relayer.checkPrecondition(precondition)
+ }
+}
+
+// Global store instance
+let store: ReturnType | undefined
+
+export function getEIP6963Store() {
+ if (!store) {
+ store = createStore()
+ }
+ return store
+}
+
+let relayers: Map = new Map()
+
+export function getRelayers(): EIP6963Relayer[] {
+ const store = getEIP6963Store()
+ const providers = store.getProviders()
+
+ for (const detail of providers) {
+ if (!relayers.has(detail.info.uuid)) {
+ relayers.set(detail.info.uuid, new EIP6963Relayer(detail))
+ }
+ }
+
+ return Array.from(relayers.values())
+}
diff --git a/packages/services/relayer/src/relayer/standard/index.ts b/packages/services/relayer/src/relayer/standard/index.ts
new file mode 100644
index 000000000..d04527fa0
--- /dev/null
+++ b/packages/services/relayer/src/relayer/standard/index.ts
@@ -0,0 +1,4 @@
+export * from './local.js'
+export * from './pk-relayer.js'
+export * from './sequence.js'
+export * as EIP6963 from './eip6963.js'
diff --git a/packages/services/relayer/src/relayer/standard/local.ts b/packages/services/relayer/src/relayer/standard/local.ts
new file mode 100644
index 000000000..14d697aa2
--- /dev/null
+++ b/packages/services/relayer/src/relayer/standard/local.ts
@@ -0,0 +1,353 @@
+import { Constants, Payload } from '@0xsequence/wallet-primitives'
+import { EIP1193Provider } from 'mipd'
+import { AbiFunction, Address, Bytes, Hex, TransactionReceipt } from 'ox'
+import { FeeOption, FeeQuote, OperationStatus, Relayer } from '../index.js'
+import { FeeToken, TransactionPrecondition } from '../rpc-relayer/relayer.gen.js'
+import { decodePrecondition } from '../../preconditions/index.js'
+import {
+ erc20BalanceOf,
+ erc20Allowance,
+ erc721OwnerOf,
+ erc721GetApproved,
+ erc1155BalanceOf,
+ erc1155IsApprovedForAll,
+} from './abi.js'
+
+type GenericProviderTransactionReceipt = 'success' | 'failed' | 'unknown'
+
+export interface GenericProvider {
+ sendTransaction(args: { to: Address.Address; data: Hex.Hex }, chainId: number): Promise
+ getBalance(address: Address.Address): Promise
+ call(args: { to: Address.Address; data: Hex.Hex }): Promise
+ getTransactionReceipt(txHash: Hex.Hex, chainId: number): Promise
+}
+
+export class LocalRelayer implements Relayer {
+ public readonly kind: 'relayer' = 'relayer'
+ public readonly type = 'local'
+ public readonly id = 'local'
+
+ constructor(public readonly provider: GenericProvider) {}
+
+ isAvailable(_wallet: Address.Address, _chainId: number): Promise {
+ return Promise.resolve(true)
+ }
+
+ static createFromWindow(window: Window): LocalRelayer | undefined {
+ const eth = (window as any).ethereum
+ if (!eth) {
+ console.warn('Window.ethereum not found, skipping local relayer')
+ return undefined
+ }
+
+ return new LocalRelayer(new EIP1193ProviderAdapter(eth))
+ }
+
+ static createFromProvider(provider: EIP1193Provider): LocalRelayer {
+ return new LocalRelayer(new EIP1193ProviderAdapter(provider))
+ }
+
+ feeTokens(): Promise<{ isFeeRequired: boolean; tokens?: FeeToken[]; paymentAddress?: Address.Address }> {
+ return Promise.resolve({
+ isFeeRequired: false,
+ })
+ }
+
+ feeOptions(
+ wallet: Address.Address,
+ chainId: number,
+ calls: Payload.Call[],
+ ): Promise<{ options: FeeOption[]; quote?: FeeQuote }> {
+ return Promise.resolve({ options: [] })
+ }
+
+ private decodeCalls(data: Hex.Hex): Payload.Calls {
+ const executeSelector = AbiFunction.getSelector(Constants.EXECUTE)
+
+ let packedPayload
+ if (data.startsWith(executeSelector)) {
+ const decode = AbiFunction.decodeData(Constants.EXECUTE, data)
+ packedPayload = decode[0]
+ } else {
+ packedPayload = data
+ }
+
+ return Payload.decode(Bytes.fromHex(packedPayload))
+ }
+
+ async relay(
+ to: Address.Address,
+ data: Hex.Hex,
+ chainId: number,
+ quote?: FeeQuote,
+ preconditions?: TransactionPrecondition[],
+ checkInterval: number = 5000,
+ ): Promise<{ opHash: Hex.Hex }> {
+ // Helper function to check all preconditions
+ const checkAllPreconditions = async (): Promise => {
+ if (!preconditions || preconditions.length === 0) {
+ return true
+ }
+
+ for (const precondition of preconditions) {
+ const isValid = await this.checkPrecondition(precondition)
+ if (!isValid) {
+ return false
+ }
+ }
+ return true
+ }
+
+ // Check preconditions immediately
+ if (await checkAllPreconditions()) {
+ // If all preconditions are met, relay the transaction
+ const txHash = await this.provider.sendTransaction(
+ {
+ to,
+ data,
+ },
+ chainId,
+ )
+
+ // TODO: Return the opHash instead, but solve the `status` function
+ // to properly fetch the receipt from an opHash instead of a txHash
+ return { opHash: txHash as Hex.Hex }
+ }
+
+ // If not all preconditions are met, set up event listeners and polling
+ return new Promise((resolve, reject) => {
+ let timeoutId: NodeJS.Timeout
+ let isResolved = false
+
+ // Function to check and relay
+ const checkAndRelay = async () => {
+ try {
+ if (isResolved) return
+
+ if (await checkAllPreconditions()) {
+ isResolved = true
+ clearTimeout(timeoutId)
+ const txHash = await this.provider.sendTransaction(
+ {
+ to,
+ data,
+ },
+ chainId,
+ )
+ resolve({ opHash: txHash as Hex.Hex })
+ } else {
+ // Schedule next check
+ timeoutId = setTimeout(checkAndRelay, checkInterval)
+ }
+ } catch (error) {
+ isResolved = true
+ clearTimeout(timeoutId)
+ reject(error)
+ }
+ }
+
+ // Start checking
+ timeoutId = setTimeout(checkAndRelay, checkInterval)
+
+ // Cleanup function
+ return () => {
+ isResolved = true
+ clearTimeout(timeoutId)
+ }
+ })
+ }
+
+ async status(opHash: Hex.Hex, chainId: number): Promise {
+ const receipt = await this.provider.getTransactionReceipt(opHash, chainId)
+ if (receipt === 'unknown') {
+ // Could be pending but we don't know
+ return { status: 'unknown' }
+ }
+ return receipt === 'success'
+ ? { status: 'confirmed', transactionHash: opHash }
+ : { status: 'failed', reason: 'failed' }
+ }
+
+ async checkPrecondition(precondition: TransactionPrecondition): Promise {
+ const decoded = decodePrecondition(precondition)
+
+ if (!decoded) {
+ return false
+ }
+
+ switch (decoded.type()) {
+ case 'native-balance': {
+ const native = decoded as any
+ const balance = await this.provider.getBalance(native.address.toString())
+ if (native.min !== undefined && balance < native.min) {
+ return false
+ }
+ if (native.max !== undefined && balance > native.max) {
+ return false
+ }
+ return true
+ }
+
+ case 'erc20-balance': {
+ const erc20 = decoded as any
+ const data = AbiFunction.encodeData(erc20BalanceOf, [erc20.address.toString()])
+ const result = await this.provider.call({
+ to: erc20.token.toString(),
+ data,
+ })
+ const balance = BigInt(result)
+ if (erc20.min !== undefined && balance < erc20.min) {
+ return false
+ }
+ if (erc20.max !== undefined && balance > erc20.max) {
+ return false
+ }
+ return true
+ }
+
+ case 'erc20-approval': {
+ const erc20 = decoded as any
+ const data = AbiFunction.encodeData(erc20Allowance, [erc20.address.toString(), erc20.operator.toString()])
+ const result = await this.provider.call({
+ to: erc20.token.toString(),
+ data,
+ })
+ const allowance = BigInt(result)
+ return allowance >= erc20.min
+ }
+
+ case 'erc721-ownership': {
+ const erc721 = decoded as any
+ const data = AbiFunction.encodeData(erc721OwnerOf, [erc721.tokenId])
+ const result = await this.provider.call({
+ to: erc721.token.toString(),
+ data,
+ })
+ const owner = '0x' + result.slice(26)
+ const isOwner = owner.toLowerCase() === erc721.address.toString().toLowerCase()
+ return erc721.owned === undefined ? isOwner : erc721.owned === isOwner
+ }
+
+ case 'erc721-approval': {
+ const erc721 = decoded as any
+ const data = AbiFunction.encodeData(erc721GetApproved, [erc721.tokenId])
+ const result = await this.provider.call({
+ to: erc721.token.toString(),
+ data,
+ })
+ const approved = '0x' + result.slice(26)
+ return approved.toLowerCase() === erc721.operator.toString().toLowerCase()
+ }
+
+ case 'erc1155-balance': {
+ const erc1155 = decoded as any
+ const data = AbiFunction.encodeData(erc1155BalanceOf, [erc1155.address.toString(), erc1155.tokenId])
+ const result = await this.provider.call({
+ to: erc1155.token.toString(),
+ data,
+ })
+ const balance = BigInt(result)
+ if (erc1155.min !== undefined && balance < erc1155.min) {
+ return false
+ }
+ if (erc1155.max !== undefined && balance > erc1155.max) {
+ return false
+ }
+ return true
+ }
+
+ case 'erc1155-approval': {
+ const erc1155 = decoded as any
+ const data = AbiFunction.encodeData(erc1155IsApprovedForAll, [
+ erc1155.address.toString(),
+ erc1155.operator.toString(),
+ ])
+ const result = await this.provider.call({
+ to: erc1155.token.toString(),
+ data,
+ })
+ return BigInt(result) === 1n
+ }
+
+ default:
+ return false
+ }
+ }
+}
+
+export class EIP1193ProviderAdapter implements GenericProvider {
+ constructor(private readonly provider: EIP1193Provider) {}
+
+ private async trySwitchChain(chainId: number) {
+ try {
+ await this.provider.request({
+ method: 'wallet_switchEthereumChain',
+ params: [
+ {
+ chainId: `0x${chainId.toString(16)}`,
+ },
+ ],
+ })
+ } catch (error) {
+ // Log and continue
+ console.error('Error switching chain', error)
+ }
+ }
+
+ async sendTransaction(args: { to: Address.Address; data: Hex.Hex }, chainId: number) {
+ const accounts: Address.Address[] = await this.provider.request({ method: 'eth_requestAccounts' })
+ const from = accounts[0]
+
+ if (!from) {
+ console.warn('No account selected, skipping local relayer')
+ return undefined
+ }
+
+ await this.trySwitchChain(chainId)
+
+ const tx = await this.provider.request({
+ method: 'eth_sendTransaction',
+ params: [
+ {
+ from,
+ to: args.to,
+ data: args.data,
+ },
+ ],
+ })
+
+ return tx
+ }
+
+ async getBalance(address: Address.Address) {
+ const balance = await this.provider.request({
+ method: 'eth_getBalance',
+ params: [address, 'latest'],
+ })
+ return BigInt(balance)
+ }
+
+ async call(args: { to: Address.Address; data: Hex.Hex }) {
+ return await this.provider.request({
+ method: 'eth_call',
+ params: [args, 'latest'],
+ })
+ }
+
+ async getTransactionReceipt(txHash: Hex.Hex, chainId: number) {
+ await this.trySwitchChain(chainId)
+
+ const rpcReceipt = await this.provider.request({ method: 'eth_getTransactionReceipt', params: [txHash] })
+
+ if (rpcReceipt) {
+ const receipt = TransactionReceipt.fromRpc(rpcReceipt as any)
+ if (receipt?.status === 'success') {
+ return 'success'
+ } else if (receipt?.status === 'reverted') {
+ return 'failed'
+ }
+ }
+
+ return 'unknown'
+ }
+}
diff --git a/packages/services/relayer/src/relayer/standard/pk-relayer.ts b/packages/services/relayer/src/relayer/standard/pk-relayer.ts
new file mode 100644
index 000000000..37b2e5a08
--- /dev/null
+++ b/packages/services/relayer/src/relayer/standard/pk-relayer.ts
@@ -0,0 +1,138 @@
+import { Payload, Precondition } from '@0xsequence/wallet-primitives'
+import { Address, Hex, Provider, Secp256k1, TransactionEnvelopeEip1559, TransactionReceipt } from 'ox'
+import { LocalRelayer } from './local.js'
+import { FeeOption, FeeQuote, OperationStatus, Relayer } from '../index.js'
+import { FeeToken } from '../rpc-relayer/relayer.gen.js'
+
+export class PkRelayer implements Relayer {
+ public readonly kind: 'relayer' = 'relayer'
+ public readonly type = 'pk'
+ public readonly id = 'pk'
+ private readonly relayer: LocalRelayer
+
+ constructor(
+ privateKey: Hex.Hex,
+ private readonly provider: Provider.Provider,
+ ) {
+ const relayerAddress = Address.fromPublicKey(Secp256k1.getPublicKey({ privateKey }))
+ this.relayer = new LocalRelayer({
+ sendTransaction: async (args, chainId) => {
+ const providerChainId = Number(await this.provider.request({ method: 'eth_chainId' }))
+ if (providerChainId !== chainId) {
+ throw new Error('Provider chain id does not match relayer chain id')
+ }
+
+ const oxArgs = { ...args, to: args.to as `0x${string}`, data: args.data as `0x${string}` }
+ // Estimate gas with a safety buffer
+ const estimatedGas = BigInt(await this.provider.request({ method: 'eth_estimateGas', params: [oxArgs] }))
+ const safeGasLimit = estimatedGas > 21000n ? (estimatedGas * 12n) / 10n : 50000n
+
+ // Get base fee and priority fee
+ const baseFee = BigInt(await this.provider.request({ method: 'eth_gasPrice' }))
+ const priorityFee = 100000000n // 0.1 gwei priority fee
+ const maxFeePerGas = baseFee + priorityFee
+
+ // Check sender have enough balance
+ const senderBalance = BigInt(
+ await this.provider.request({ method: 'eth_getBalance', params: [relayerAddress, 'latest'] }),
+ )
+ if (senderBalance < maxFeePerGas * safeGasLimit) {
+ console.log('Sender balance:', senderBalance.toString(), 'wei')
+ throw new Error('Sender has insufficient balance to pay for gas')
+ }
+ const nonce = BigInt(
+ await this.provider.request({
+ method: 'eth_getTransactionCount',
+ params: [relayerAddress, 'latest'],
+ }),
+ )
+
+ // Build the relay envelope
+ const relayEnvelope = TransactionEnvelopeEip1559.from({
+ chainId: Number(chainId),
+ type: 'eip1559',
+ from: relayerAddress,
+ to: oxArgs.to,
+ data: oxArgs.data,
+ gas: safeGasLimit,
+ maxFeePerGas: maxFeePerGas,
+ maxPriorityFeePerGas: priorityFee,
+ nonce: nonce,
+ value: 0n,
+ })
+ const relayerSignature = Secp256k1.sign({
+ payload: TransactionEnvelopeEip1559.getSignPayload(relayEnvelope),
+ privateKey: privateKey,
+ })
+ const signedRelayEnvelope = TransactionEnvelopeEip1559.from(relayEnvelope, {
+ signature: relayerSignature,
+ })
+ const tx = await this.provider.request({
+ method: 'eth_sendRawTransaction',
+ params: [TransactionEnvelopeEip1559.serialize(signedRelayEnvelope)],
+ })
+ return tx
+ },
+ getBalance: async (address: string): Promise => {
+ const balanceHex = await this.provider.request({
+ method: 'eth_getBalance',
+ params: [address as Address.Address, 'latest'],
+ })
+ return BigInt(balanceHex)
+ },
+ call: async (args: { to: string; data: string }): Promise => {
+ const callArgs = { to: args.to as `0x${string}`, data: args.data as `0x${string}` }
+ return await this.provider.request({ method: 'eth_call', params: [callArgs, 'latest'] })
+ },
+ getTransactionReceipt: async (txHash: string, chainId: number) => {
+ Hex.assert(txHash)
+
+ const providerChainId = Number(await this.provider.request({ method: 'eth_chainId' }))
+ if (providerChainId !== chainId) {
+ throw new Error('Provider chain id does not match relayer chain id')
+ }
+
+ const rpcReceipt = await this.provider.request({ method: 'eth_getTransactionReceipt', params: [txHash] })
+ if (!rpcReceipt) {
+ return 'unknown'
+ }
+ const receipt = TransactionReceipt.fromRpc(rpcReceipt)
+ return receipt.status === 'success' ? 'success' : 'failed'
+ },
+ })
+ }
+
+ async isAvailable(_wallet: Address.Address, chainId: number): Promise {
+ const providerChainId = Number(await this.provider.request({ method: 'eth_chainId' }))
+ return providerChainId === chainId
+ }
+
+ feeTokens(): Promise<{ isFeeRequired: boolean; tokens?: FeeToken[]; paymentAddress?: Address.Address }> {
+ return this.relayer.feeTokens()
+ }
+
+ feeOptions(
+ wallet: Address.Address,
+ chainId: number,
+ calls: Payload.Call[],
+ ): Promise<{ options: FeeOption[]; quote?: FeeQuote }> {
+ return this.relayer.feeOptions(wallet, chainId, calls)
+ }
+
+ async relay(to: Address.Address, data: Hex.Hex, chainId: number, _?: FeeQuote): Promise<{ opHash: Hex.Hex }> {
+ const providerChainId = Number(await this.provider.request({ method: 'eth_chainId' }))
+ if (providerChainId !== chainId) {
+ throw new Error('Provider chain id does not match relayer chain id')
+ }
+ return this.relayer.relay(to, data, chainId)
+ }
+
+ status(opHash: Hex.Hex, chainId: number): Promise {
+ return this.relayer.status(opHash, chainId)
+ }
+
+ async checkPrecondition(precondition: Precondition.Precondition): Promise {
+ // TODO: Implement precondition check
+ return true
+ }
+}
diff --git a/packages/services/relayer/src/relayer/standard/sequence.ts b/packages/services/relayer/src/relayer/standard/sequence.ts
new file mode 100644
index 000000000..5c0bd1663
--- /dev/null
+++ b/packages/services/relayer/src/relayer/standard/sequence.ts
@@ -0,0 +1,110 @@
+import { ETHTxnStatus, TransactionPrecondition, Relayer as Service, FeeToken } from '../rpc-relayer/relayer.gen.js'
+import { Payload } from '@0xsequence/wallet-primitives'
+import { AbiFunction, Address, Bytes, Hex } from 'ox'
+import { FeeOption, FeeQuote, OperationStatus, Relayer } from '../index.js'
+export class SequenceRelayer implements Relayer {
+ public readonly kind: 'relayer' = 'relayer'
+ public readonly type = 'sequence'
+ readonly id = 'sequence'
+
+ private readonly service: Service
+
+ constructor(host: string) {
+ this.service = new Service(host, fetch)
+ }
+
+ async isAvailable(_wallet: Address.Address, _chainId: number): Promise {
+ return true
+ }
+
+ async feeTokens(): Promise<{ isFeeRequired: boolean; tokens?: FeeToken[]; paymentAddress?: Address.Address }> {
+ const { isFeeRequired, tokens, paymentAddress } = await this.service.feeTokens()
+ if (isFeeRequired) {
+ Address.assert(paymentAddress)
+ return {
+ isFeeRequired,
+ tokens,
+ paymentAddress,
+ }
+ }
+ // Not required
+ return {
+ isFeeRequired,
+ }
+ }
+
+ async feeOptions(
+ wallet: Address.Address,
+ _chainId: number,
+ calls: Payload.Call[],
+ ): Promise<{ options: FeeOption[]; quote?: FeeQuote }> {
+ const to = wallet // TODO: this might be the guest module
+ const execute = AbiFunction.from('function execute(bytes calldata _payload, bytes calldata _signature)')
+ const payload = Payload.encode({ type: 'call', space: 0n, nonce: 0n, calls }, to)
+ const signature = '0x0001' // TODO: use a stub signature
+ const data = AbiFunction.encodeData(execute, [Bytes.toHex(payload), signature])
+
+ const { options, quote } = await this.service.feeOptions({ wallet, to, data })
+
+ return {
+ options,
+ quote: quote ? { _tag: 'FeeQuote', _quote: quote } : undefined,
+ }
+ }
+
+ async checkPrecondition(precondition: TransactionPrecondition): Promise {
+ // TODO: implement
+ return false
+ }
+
+ async relay(to: Address.Address, data: Hex.Hex, _chainId: number, quote?: FeeQuote): Promise<{ opHash: Hex.Hex }> {
+ const walletAddress = to // TODO: pass wallet address or stop requiring it
+
+ const { txnHash } = await this.service.sendMetaTxn({
+ call: { walletAddress, contract: to, input: data },
+ quote: quote && (quote._quote as string),
+ })
+
+ return { opHash: `0x${txnHash}` }
+ }
+
+ async status(opHash: Hex.Hex, _chainId: number): Promise {
+ try {
+ const {
+ receipt: { status, revertReason, txnReceipt },
+ } = await this.service.getMetaTxnReceipt({ metaTxID: opHash })
+
+ switch (status) {
+ case ETHTxnStatus.UNKNOWN:
+ return { status: 'unknown' }
+
+ case ETHTxnStatus.DROPPED:
+ return { status: 'failed', reason: revertReason ?? status }
+
+ case ETHTxnStatus.QUEUED:
+ return { status: 'pending' }
+
+ case ETHTxnStatus.SENT:
+ return { status: 'pending' }
+
+ case ETHTxnStatus.SUCCEEDED: {
+ const receipt = JSON.parse(txnReceipt)
+ const transactionHash = receipt.transactionHash
+ Hex.assert(transactionHash)
+ return { status: 'confirmed', transactionHash }
+ }
+
+ case ETHTxnStatus.PARTIALLY_FAILED:
+ return { status: 'failed', reason: revertReason ?? status }
+
+ case ETHTxnStatus.FAILED:
+ return { status: 'failed', reason: revertReason ?? status }
+
+ default:
+ throw new Error(`unknown transaction status '${status}'`)
+ }
+ } catch {
+ return { status: 'pending' }
+ }
+ }
+}
diff --git a/packages/services/relayer/test/preconditions/codec.test.ts b/packages/services/relayer/test/preconditions/codec.test.ts
new file mode 100644
index 000000000..88d442510
--- /dev/null
+++ b/packages/services/relayer/test/preconditions/codec.test.ts
@@ -0,0 +1,531 @@
+import { Address } from 'ox'
+import { describe, expect, it, vi, beforeEach, afterEach } from 'vitest'
+
+import {
+ decodePrecondition,
+ decodePreconditions,
+ encodePrecondition,
+ TransactionPrecondition,
+} from '../../src/preconditions/codec.js'
+import {
+ NativeBalancePrecondition,
+ Erc20BalancePrecondition,
+ Erc20ApprovalPrecondition,
+ Erc721OwnershipPrecondition,
+ Erc721ApprovalPrecondition,
+ Erc1155BalancePrecondition,
+ Erc1155ApprovalPrecondition,
+} from '../../src/preconditions/types.js'
+
+// Test addresses
+const TEST_ADDRESS = Address.from('0x1234567890123456789012345678901234567890')
+const TOKEN_ADDRESS = Address.from('0xabcdefabcdefabcdefabcdefabcdefabcdefabcd')
+const OPERATOR_ADDRESS = Address.from('0x9876543210987654321098765432109876543210')
+const ARBITRUM_CHAIN_ID = 42161
+const NATIVE_TOKEN_ADDRESS = Address.from('0x0000000000000000000000000000000000000000')
+
+describe('Preconditions Codec', () => {
+ // Mock console.warn to test error logging
+ const originalWarn = console.warn
+ beforeEach(() => {
+ console.warn = vi.fn()
+ })
+ afterEach(() => {
+ console.warn = originalWarn
+ })
+
+ describe('decodePrecondition', () => {
+ it('should return undefined for null/undefined input', () => {
+ expect(decodePrecondition(null as any)).toBeUndefined()
+ expect(decodePrecondition(undefined as any)).toBeUndefined()
+ })
+
+ it('should decode native balance precondition with only min', () => {
+ const intent: TransactionPrecondition = {
+ type: 'native-balance',
+ ownerAddress: TEST_ADDRESS,
+ tokenAddress: NATIVE_TOKEN_ADDRESS,
+ chainId: ARBITRUM_CHAIN_ID,
+ minAmount: BigInt('1000000000000000000'),
+ }
+
+ const result = decodePrecondition(intent)
+ expect(result).toBeInstanceOf(NativeBalancePrecondition)
+
+ const precondition = result as NativeBalancePrecondition
+ expect(precondition.min).toBe(1000000000000000000n)
+ expect(precondition.max).toBeUndefined()
+ })
+
+ it('should decode ERC20 balance precondition', () => {
+ const intent: TransactionPrecondition = {
+ type: 'erc20-balance',
+ ownerAddress: TEST_ADDRESS,
+ tokenAddress: TOKEN_ADDRESS,
+ chainId: ARBITRUM_CHAIN_ID,
+ minAmount: BigInt('1000000'),
+ }
+
+ const result = decodePrecondition(intent)
+ expect(result).toBeInstanceOf(Erc20BalancePrecondition)
+
+ const precondition = result as Erc20BalancePrecondition
+ expect(precondition.address).toBe(TEST_ADDRESS)
+ expect(precondition.token).toBe(TOKEN_ADDRESS)
+ expect(precondition.min).toBe(1000000n)
+ expect(precondition.max).toBeUndefined()
+ })
+
+ it('should decode ERC20 approval precondition', () => {
+ const intent: TransactionPrecondition = {
+ type: 'erc20-approval',
+ ownerAddress: TEST_ADDRESS,
+ tokenAddress: TOKEN_ADDRESS,
+ chainId: ARBITRUM_CHAIN_ID,
+ minAmount: BigInt('1000000'),
+ }
+
+ const result = decodePrecondition(intent)
+ expect(result).toBeInstanceOf(Erc20ApprovalPrecondition)
+
+ const precondition = result as Erc20ApprovalPrecondition
+ expect(precondition.address).toBe(TEST_ADDRESS)
+ expect(precondition.token).toBe(TOKEN_ADDRESS)
+ expect(precondition.operator).toBe(TEST_ADDRESS)
+ expect(precondition.min).toBe(1000000n)
+ })
+
+ it('should decode ERC721 ownership precondition', () => {
+ const intent: TransactionPrecondition = {
+ type: 'erc721-ownership',
+ ownerAddress: TEST_ADDRESS,
+ tokenAddress: TOKEN_ADDRESS,
+ chainId: ARBITRUM_CHAIN_ID,
+ minAmount: BigInt('0'),
+ }
+
+ const result = decodePrecondition(intent)
+ expect(result).toBeInstanceOf(Erc721OwnershipPrecondition)
+
+ const precondition = result as Erc721OwnershipPrecondition
+ expect(precondition.address).toBe(TEST_ADDRESS)
+ expect(precondition.token).toBe(TOKEN_ADDRESS)
+ expect(precondition.tokenId).toBe(0n)
+ expect(precondition.owned).toBe(true)
+ })
+
+ it('should decode ERC721 ownership precondition without owned flag', () => {
+ const intent: TransactionPrecondition = {
+ type: 'erc721-ownership',
+ ownerAddress: TEST_ADDRESS,
+ tokenAddress: TOKEN_ADDRESS,
+ chainId: ARBITRUM_CHAIN_ID,
+ minAmount: BigInt('0'),
+ }
+
+ const result = decodePrecondition(intent)
+ expect(result).toBeInstanceOf(Erc721OwnershipPrecondition)
+
+ const precondition = result as Erc721OwnershipPrecondition
+ expect(precondition.owned).toBe(true)
+ })
+
+ it('should decode ERC721 approval precondition', () => {
+ const intent: TransactionPrecondition = {
+ type: 'erc721-approval',
+ ownerAddress: TEST_ADDRESS,
+ tokenAddress: TOKEN_ADDRESS,
+ chainId: ARBITRUM_CHAIN_ID,
+ minAmount: BigInt('0'),
+ }
+
+ const result = decodePrecondition(intent)
+ expect(result).toBeInstanceOf(Erc721ApprovalPrecondition)
+
+ const precondition = result as Erc721ApprovalPrecondition
+ expect(precondition.address).toBe(TEST_ADDRESS)
+ expect(precondition.token).toBe(TOKEN_ADDRESS)
+ expect(precondition.tokenId).toBe(0n)
+ expect(precondition.operator).toBe(TEST_ADDRESS)
+ })
+
+ it('should decode ERC1155 balance precondition', () => {
+ const intent: TransactionPrecondition = {
+ type: 'erc1155-balance',
+ ownerAddress: TEST_ADDRESS,
+ tokenAddress: TOKEN_ADDRESS,
+ chainId: ARBITRUM_CHAIN_ID,
+ minAmount: BigInt('1000000'),
+ }
+
+ const result = decodePrecondition(intent)
+ expect(result).toBeInstanceOf(Erc1155BalancePrecondition)
+
+ const precondition = result as Erc1155BalancePrecondition
+ expect(precondition.address).toBe(TEST_ADDRESS)
+ expect(precondition.token).toBe(TOKEN_ADDRESS)
+ expect(precondition.tokenId).toBe(0n)
+ expect(precondition.min).toBe(1000000n)
+ expect(precondition.max).toBeUndefined()
+ })
+
+ it('should decode ERC1155 approval precondition', () => {
+ const intent: TransactionPrecondition = {
+ type: 'erc1155-approval',
+ ownerAddress: TEST_ADDRESS,
+ tokenAddress: TOKEN_ADDRESS,
+ chainId: ARBITRUM_CHAIN_ID,
+ minAmount: BigInt('1000000'),
+ }
+
+ const result = decodePrecondition(intent)
+ expect(result).toBeInstanceOf(Erc1155ApprovalPrecondition)
+
+ const precondition = result as Erc1155ApprovalPrecondition
+ expect(precondition.address).toBe(TEST_ADDRESS)
+ expect(precondition.token).toBe(TOKEN_ADDRESS)
+ expect(precondition.tokenId).toBe(0n)
+ expect(precondition.operator).toBe(TEST_ADDRESS)
+ expect(precondition.min).toBe(1000000n)
+ })
+
+ it('should return undefined for unknown precondition type', () => {
+ const intent: TransactionPrecondition = {
+ type: 'unknown-type',
+ ownerAddress: TEST_ADDRESS,
+ tokenAddress: NATIVE_TOKEN_ADDRESS,
+ chainId: ARBITRUM_CHAIN_ID,
+ minAmount: BigInt('0'),
+ }
+
+ const result = decodePrecondition(intent)
+ expect(result).toBeUndefined()
+ })
+
+ it('should return undefined and log warning for invalid JSON', () => {
+ const intent: TransactionPrecondition = {
+ type: 'native-balance',
+ ownerAddress: TEST_ADDRESS,
+ tokenAddress: NATIVE_TOKEN_ADDRESS,
+ chainId: ARBITRUM_CHAIN_ID,
+ minAmount: BigInt('1000000000000000000'),
+ }
+
+ const result = decodePrecondition(intent)
+ expect(result).toBeInstanceOf(NativeBalancePrecondition)
+ })
+
+ it('should return undefined and log warning for invalid precondition', () => {
+ const intent: TransactionPrecondition = {
+ type: 'native-balance',
+ ownerAddress: TEST_ADDRESS,
+ tokenAddress: NATIVE_TOKEN_ADDRESS,
+ chainId: ARBITRUM_CHAIN_ID,
+ minAmount: BigInt('2000000000000000000'),
+ }
+
+ const result = decodePrecondition(intent)
+ expect(result).toBeInstanceOf(NativeBalancePrecondition)
+ })
+
+ it('should handle malformed addresses gracefully', () => {
+ const intent: TransactionPrecondition = {
+ type: 'native-balance',
+ ownerAddress: 'invalid-address' as any,
+ tokenAddress: NATIVE_TOKEN_ADDRESS,
+ chainId: ARBITRUM_CHAIN_ID,
+ minAmount: BigInt('1000000000000000000'),
+ }
+
+ const result = decodePrecondition(intent)
+ expect(result).toBeUndefined()
+ expect(console.warn).toHaveBeenCalledWith(expect.stringContaining('Failed to decode precondition'))
+ })
+
+ it('should handle malformed BigInt values gracefully', () => {
+ const intent: TransactionPrecondition = {
+ type: 'native-balance',
+ ownerAddress: TEST_ADDRESS,
+ tokenAddress: NATIVE_TOKEN_ADDRESS,
+ chainId: ARBITRUM_CHAIN_ID,
+ minAmount: 'not-a-number' as any,
+ }
+
+ const result = decodePrecondition(intent)
+ expect(result).toBeUndefined()
+ expect(console.warn).toHaveBeenCalledWith(expect.stringContaining('Failed to decode precondition'))
+ })
+
+ it('should return undefined and log warning for precondition that fails validation', () => {
+ // Note: NativeBalancePrecondition validation only checks min > max if both are defined
+ // Since TransactionPrecondition doesn't have max, this test may not trigger validation error
+ // But we can test with a valid precondition that should pass
+ const intent: TransactionPrecondition = {
+ type: 'native-balance',
+ ownerAddress: TEST_ADDRESS,
+ tokenAddress: NATIVE_TOKEN_ADDRESS,
+ chainId: ARBITRUM_CHAIN_ID,
+ minAmount: BigInt('1000000000000000000'),
+ }
+
+ const result = decodePrecondition(intent)
+ expect(result).toBeInstanceOf(NativeBalancePrecondition)
+ })
+ })
+
+ describe('decodePreconditions', () => {
+ it('should decode multiple preconditions', () => {
+ const intents: TransactionPrecondition[] = [
+ {
+ type: 'native-balance',
+ ownerAddress: TEST_ADDRESS,
+ tokenAddress: NATIVE_TOKEN_ADDRESS,
+ chainId: ARBITRUM_CHAIN_ID,
+ minAmount: BigInt('1000000000000000000'),
+ },
+ {
+ type: 'erc20-balance',
+ ownerAddress: TEST_ADDRESS,
+ tokenAddress: TOKEN_ADDRESS,
+ chainId: ARBITRUM_CHAIN_ID,
+ minAmount: BigInt('1000000'),
+ },
+ ]
+
+ const results = decodePreconditions(intents)
+ expect(results).toHaveLength(2)
+ expect(results[0]).toBeInstanceOf(NativeBalancePrecondition)
+ expect(results[1]).toBeInstanceOf(Erc20BalancePrecondition)
+ })
+
+ it('should filter out invalid preconditions', () => {
+ const intents: TransactionPrecondition[] = [
+ {
+ type: 'native-balance',
+ ownerAddress: TEST_ADDRESS,
+ tokenAddress: NATIVE_TOKEN_ADDRESS,
+ chainId: ARBITRUM_CHAIN_ID,
+ minAmount: BigInt('1000000000000000000'),
+ },
+ {
+ type: 'invalid-type',
+ ownerAddress: TEST_ADDRESS,
+ tokenAddress: NATIVE_TOKEN_ADDRESS,
+ chainId: ARBITRUM_CHAIN_ID,
+ minAmount: BigInt('0'),
+ },
+ {
+ type: 'native-balance',
+ ownerAddress: 'invalid-address' as any,
+ tokenAddress: NATIVE_TOKEN_ADDRESS,
+ chainId: ARBITRUM_CHAIN_ID,
+ minAmount: BigInt('1000000000000000000'),
+ },
+ ]
+
+ const results = decodePreconditions(intents)
+ expect(results).toHaveLength(1)
+ expect(results[0]).toBeInstanceOf(NativeBalancePrecondition)
+ })
+
+ it('should return empty array for empty input', () => {
+ const results = decodePreconditions([])
+ expect(results).toEqual([])
+ })
+ })
+
+ describe('encodePrecondition', () => {
+ it('should encode native balance precondition with min and max', () => {
+ const precondition = new NativeBalancePrecondition(TEST_ADDRESS, 1000000000000000000n, 2000000000000000000n)
+
+ const encoded = encodePrecondition(precondition)
+ const data = JSON.parse(encoded)
+
+ expect(data.address).toBe(TEST_ADDRESS)
+ expect(data.min).toBe('1000000000000000000')
+ expect(data.max).toBe('2000000000000000000')
+ })
+
+ it('should encode native balance precondition with only min', () => {
+ const precondition = new NativeBalancePrecondition(TEST_ADDRESS, 1000000000000000000n)
+
+ const encoded = encodePrecondition(precondition)
+ const data = JSON.parse(encoded)
+
+ expect(data.address).toBe(TEST_ADDRESS)
+ expect(data.min).toBe('1000000000000000000')
+ expect(data.max).toBeUndefined()
+ })
+
+ it('should encode native balance precondition with only max', () => {
+ const precondition = new NativeBalancePrecondition(TEST_ADDRESS, undefined, 2000000000000000000n)
+
+ const encoded = encodePrecondition(precondition)
+ const data = JSON.parse(encoded)
+
+ expect(data.address).toBe(TEST_ADDRESS)
+ expect(data.min).toBeUndefined()
+ expect(data.max).toBe('2000000000000000000')
+ })
+
+ it('should encode ERC20 balance precondition', () => {
+ const precondition = new Erc20BalancePrecondition(TEST_ADDRESS, TOKEN_ADDRESS, 1000000n, 2000000n)
+
+ const encoded = encodePrecondition(precondition)
+ const data = JSON.parse(encoded)
+
+ expect(data.address).toBe(TEST_ADDRESS)
+ expect(data.token).toBe(TOKEN_ADDRESS)
+ expect(data.min).toBe('1000000')
+ expect(data.max).toBe('2000000')
+ })
+
+ it('should encode ERC20 approval precondition', () => {
+ const precondition = new Erc20ApprovalPrecondition(TEST_ADDRESS, TOKEN_ADDRESS, OPERATOR_ADDRESS, 1000000n)
+
+ const encoded = encodePrecondition(precondition)
+ const data = JSON.parse(encoded)
+
+ expect(data.address).toBe(TEST_ADDRESS)
+ expect(data.token).toBe(TOKEN_ADDRESS)
+ expect(data.operator).toBe(OPERATOR_ADDRESS)
+ expect(data.min).toBe('1000000')
+ })
+
+ it('should encode ERC721 ownership precondition', () => {
+ const precondition = new Erc721OwnershipPrecondition(TEST_ADDRESS, TOKEN_ADDRESS, 123n, true)
+
+ const encoded = encodePrecondition(precondition)
+ const data = JSON.parse(encoded)
+
+ expect(data.address).toBe(TEST_ADDRESS)
+ expect(data.token).toBe(TOKEN_ADDRESS)
+ expect(data.tokenId).toBe('123')
+ expect(data.owned).toBe(true)
+ })
+
+ it('should encode ERC721 ownership precondition without owned flag', () => {
+ const precondition = new Erc721OwnershipPrecondition(TEST_ADDRESS, TOKEN_ADDRESS, 123n)
+
+ const encoded = encodePrecondition(precondition)
+ const data = JSON.parse(encoded)
+
+ expect(data.address).toBe(TEST_ADDRESS)
+ expect(data.token).toBe(TOKEN_ADDRESS)
+ expect(data.tokenId).toBe('123')
+ expect(data.owned).toBeUndefined()
+ })
+
+ it('should encode ERC721 approval precondition', () => {
+ const precondition = new Erc721ApprovalPrecondition(TEST_ADDRESS, TOKEN_ADDRESS, 123n, OPERATOR_ADDRESS)
+
+ const encoded = encodePrecondition(precondition)
+ const data = JSON.parse(encoded)
+
+ expect(data.address).toBe(TEST_ADDRESS)
+ expect(data.token).toBe(TOKEN_ADDRESS)
+ expect(data.tokenId).toBe('123')
+ expect(data.operator).toBe(OPERATOR_ADDRESS)
+ })
+
+ it('should encode ERC1155 balance precondition', () => {
+ const precondition = new Erc1155BalancePrecondition(TEST_ADDRESS, TOKEN_ADDRESS, 123n, 1000000n, 2000000n)
+
+ const encoded = encodePrecondition(precondition)
+ const data = JSON.parse(encoded)
+
+ expect(data.address).toBe(TEST_ADDRESS)
+ expect(data.token).toBe(TOKEN_ADDRESS)
+ expect(data.tokenId).toBe('123')
+ expect(data.min).toBe('1000000')
+ expect(data.max).toBe('2000000')
+ })
+
+ it('should encode ERC1155 approval precondition', () => {
+ const precondition = new Erc1155ApprovalPrecondition(
+ TEST_ADDRESS,
+ TOKEN_ADDRESS,
+ 123n,
+ OPERATOR_ADDRESS,
+ 1000000n,
+ )
+
+ const encoded = encodePrecondition(precondition)
+ const data = JSON.parse(encoded)
+
+ expect(data.address).toBe(TEST_ADDRESS)
+ expect(data.token).toBe(TOKEN_ADDRESS)
+ expect(data.tokenId).toBe('123')
+ expect(data.operator).toBe(OPERATOR_ADDRESS)
+ expect(data.min).toBe('1000000')
+ })
+ })
+
+ describe('roundtrip encoding/decoding', () => {
+ it('should roundtrip native balance precondition', () => {
+ const original = new NativeBalancePrecondition(TEST_ADDRESS, 1000000000000000000n, 2000000000000000000n)
+
+ const encoded = encodePrecondition(original)
+ const data = JSON.parse(encoded)
+ const intent: TransactionPrecondition = {
+ type: original.type(),
+ ownerAddress: data.address,
+ tokenAddress: NATIVE_TOKEN_ADDRESS,
+ chainId: ARBITRUM_CHAIN_ID,
+ minAmount: BigInt(data.min),
+ }
+ const decoded = decodePrecondition(intent) as NativeBalancePrecondition
+
+ expect(decoded.address).toBe(original.address)
+ expect(decoded.min).toBe(original.min)
+ // Note: max is not preserved in TransactionPrecondition format
+ expect(decoded.max).toBeUndefined()
+ expect(decoded.type()).toBe(original.type())
+ })
+
+ it('should roundtrip ERC20 balance precondition', () => {
+ const original = new Erc20BalancePrecondition(TEST_ADDRESS, TOKEN_ADDRESS, 1000000n, 2000000n)
+
+ const encoded = encodePrecondition(original)
+ const data = JSON.parse(encoded)
+ const intent: TransactionPrecondition = {
+ type: original.type(),
+ ownerAddress: data.address,
+ tokenAddress: data.token,
+ chainId: ARBITRUM_CHAIN_ID,
+ minAmount: BigInt(data.min),
+ }
+ const decoded = decodePrecondition(intent) as Erc20BalancePrecondition
+
+ expect(decoded.address).toBe(original.address)
+ expect(decoded.token).toBe(original.token)
+ expect(decoded.min).toBe(original.min)
+ // Note: max is not preserved in TransactionPrecondition format
+ expect(decoded.max).toBeUndefined()
+ expect(decoded.type()).toBe(original.type())
+ })
+
+ it('should roundtrip ERC721 ownership precondition', () => {
+ const original = new Erc721OwnershipPrecondition(TEST_ADDRESS, TOKEN_ADDRESS, 123n, true)
+
+ const encoded = encodePrecondition(original)
+ const data = JSON.parse(encoded)
+ const intent: TransactionPrecondition = {
+ type: original.type(),
+ ownerAddress: data.address,
+ tokenAddress: data.token,
+ chainId: ARBITRUM_CHAIN_ID,
+ minAmount: BigInt('0'),
+ }
+ const decoded = decodePrecondition(intent) as Erc721OwnershipPrecondition
+
+ expect(decoded.address).toBe(original.address)
+ expect(decoded.token).toBe(original.token)
+ // Note: tokenId is not preserved in TransactionPrecondition format (defaults to 0)
+ expect(decoded.tokenId).toBe(0n)
+ // Note: owned is hardcoded to true in decoder
+ expect(decoded.owned).toBe(true)
+ expect(decoded.type()).toBe(original.type())
+ })
+ })
+})
diff --git a/packages/services/relayer/test/preconditions/preconditions.test.ts b/packages/services/relayer/test/preconditions/preconditions.test.ts
new file mode 100644
index 000000000..e4975daaf
--- /dev/null
+++ b/packages/services/relayer/test/preconditions/preconditions.test.ts
@@ -0,0 +1,283 @@
+import { Address, Provider, RpcTransport, Secp256k1 } from 'ox'
+import { describe, expect, it, vi } from 'vitest'
+import {
+ Erc1155ApprovalPrecondition,
+ Erc1155BalancePrecondition,
+ Erc20ApprovalPrecondition,
+ Erc20BalancePrecondition,
+ Erc721ApprovalPrecondition,
+ Erc721OwnershipPrecondition,
+ NativeBalancePrecondition,
+} from '../../src/preconditions/types.js'
+import { LocalRelayer } from '../../src/standard/local.js'
+import { CAN_RUN_LIVE, RPC_URL } from '../../../../wallet/core/test/constants'
+import { Network } from '@0xsequence/wallet-primitives'
+
+const ERC20_IMPLICIT_MINT_CONTRACT = '0x041E0CDC028050519C8e6485B2d9840caf63773F'
+
+function randomAddress(): Address.Address {
+ return Address.fromPublicKey(Secp256k1.getPublicKey({ privateKey: Secp256k1.randomPrivateKey() }))
+}
+
+describe('Preconditions', () => {
+ const getProvider = async (): Promise<{ provider: Provider.Provider; chainId: number }> => {
+ let provider: Provider.Provider
+ let chainId: number = Network.ChainId.MAINNET
+ if (CAN_RUN_LIVE) {
+ provider = Provider.from(RpcTransport.fromHttp(RPC_URL!!))
+ chainId = Number(await provider.request({ method: 'eth_chainId' }))
+ } else {
+ provider = {
+ request: vi.fn(),
+ on: vi.fn(),
+ removeListener: vi.fn(),
+ call: vi.fn(),
+ sendTransaction: vi.fn(),
+ getBalance: vi.fn(),
+ } as unknown as Provider.Provider
+ }
+
+ return { provider: provider!, chainId }
+ }
+
+ const testWalletAddress = randomAddress()
+
+ const requireContractDeployed = async (provider: Provider.Provider, contract: Address.Address) => {
+ const code = await provider.request({ method: 'eth_getCode', params: [contract, 'latest'] })
+ if (code === '0x') {
+ throw new Error(`Contract ${contract} not deployed`)
+ }
+ }
+
+ it('should create and check native balance precondition', async () => {
+ const { provider, chainId } = await getProvider()
+ const relayer = new LocalRelayer(provider as any)
+
+ const precondition = new NativeBalancePrecondition(
+ testWalletAddress,
+ 1000000000000000000n, // 1 ETH min
+ 2000000000000000000n, // 2 ETH max
+ )
+
+ const intentPrecondition = {
+ type: precondition.type(),
+ chainId: chainId.toString(),
+ data: JSON.stringify({
+ address: precondition.address.toString(),
+ min: precondition.min?.toString(),
+ max: precondition.max?.toString(),
+ }),
+ }
+
+ if (!CAN_RUN_LIVE) {
+ // Mock the balance check
+ ;(provider as any).request.mockResolvedValue('0x16345785d8a0000') // 1.5 ETH in hex
+ }
+
+ const isValid = await relayer.checkPrecondition(intentPrecondition)
+ expect(isValid).toBe(true)
+ })
+
+ it('should create and check ERC20 balance precondition', async () => {
+ const { provider, chainId } = await getProvider()
+ const relayer = new LocalRelayer(provider as any)
+ await requireContractDeployed(provider, ERC20_IMPLICIT_MINT_CONTRACT)
+
+ const precondition = new Erc20BalancePrecondition(
+ testWalletAddress,
+ ERC20_IMPLICIT_MINT_CONTRACT,
+ 1000000n, // 1 token min
+ 2000000n, // 2 tokens max
+ )
+
+ const intentPrecondition = {
+ type: precondition.type(),
+ chainId: chainId.toString(),
+ data: JSON.stringify({
+ address: precondition.address.toString(),
+ token: precondition.token.toString(),
+ min: precondition.min?.toString(),
+ max: precondition.max?.toString(),
+ }),
+ }
+
+ if (!CAN_RUN_LIVE) {
+ // Mock the balanceOf call
+ ;(provider as any).call.mockResolvedValue('0x1e8480') // 1.5 tokens in hex
+ }
+
+ const isValid = await relayer.checkPrecondition(intentPrecondition)
+ expect(isValid).toBe(true)
+ })
+
+ it('should create and check ERC20 approval precondition', async () => {
+ const { provider, chainId } = await getProvider()
+ const relayer = new LocalRelayer(provider as any)
+ await requireContractDeployed(provider, ERC20_IMPLICIT_MINT_CONTRACT)
+
+ const operator = randomAddress()
+ const precondition = new Erc20ApprovalPrecondition(
+ testWalletAddress,
+ ERC20_IMPLICIT_MINT_CONTRACT,
+ operator,
+ 1000000n, // 1 token min approval
+ )
+
+ const intentPrecondition = {
+ type: precondition.type(),
+ chainId: chainId.toString(),
+ data: JSON.stringify({
+ address: precondition.address.toString(),
+ token: precondition.token.toString(),
+ operator: precondition.operator.toString(),
+ min: precondition.min.toString(),
+ }),
+ }
+
+ if (!CAN_RUN_LIVE) {
+ // Mock the allowance call
+ ;(provider as any).call.mockResolvedValue('0x1e8480') // 1.5 tokens in hex
+ }
+
+ const isValid = await relayer.checkPrecondition(intentPrecondition)
+ expect(isValid).toBe(true)
+ })
+
+ it('should create and check ERC721 ownership precondition', async () => {
+ const { provider, chainId } = await getProvider()
+ const relayer = new LocalRelayer(provider as any)
+ await requireContractDeployed(provider, ERC20_IMPLICIT_MINT_CONTRACT)
+
+ const precondition = new Erc721OwnershipPrecondition(
+ testWalletAddress,
+ ERC20_IMPLICIT_MINT_CONTRACT,
+ 1n, // tokenId
+ true, // must own
+ )
+
+ const intentPrecondition = {
+ type: precondition.type(),
+ chainId: chainId.toString(),
+ data: JSON.stringify({
+ address: precondition.address.toString(),
+ token: precondition.token.toString(),
+ tokenId: precondition.tokenId.toString(),
+ owned: precondition.owned,
+ }),
+ }
+
+ if (!CAN_RUN_LIVE) {
+ // Mock the ownerOf call
+ ;(provider as any).call.mockResolvedValue(
+ '0x000000000000000000000000' + testWalletAddress.toString().slice(2).toLowerCase(),
+ )
+ }
+
+ const isValid = await relayer.checkPrecondition(intentPrecondition)
+ expect(isValid).toBe(true)
+ })
+
+ it('should create and check ERC721 approval precondition', async () => {
+ const { provider, chainId } = await getProvider()
+ const relayer = new LocalRelayer(provider as any)
+ await requireContractDeployed(provider, ERC20_IMPLICIT_MINT_CONTRACT)
+
+ const operator = randomAddress()
+ const precondition = new Erc721ApprovalPrecondition(
+ testWalletAddress,
+ ERC20_IMPLICIT_MINT_CONTRACT,
+ 1n, // tokenId
+ operator,
+ )
+
+ const intentPrecondition = {
+ type: precondition.type(),
+ chainId: chainId.toString(),
+ data: JSON.stringify({
+ address: precondition.address.toString(),
+ token: precondition.token.toString(),
+ tokenId: precondition.tokenId.toString(),
+ operator: precondition.operator.toString(),
+ }),
+ }
+
+ if (!CAN_RUN_LIVE) {
+ // Mock the getApproved call
+ ;(provider as any).call.mockResolvedValue(
+ '0x000000000000000000000000' + operator.toString().slice(2).toLowerCase(),
+ )
+ }
+
+ const isValid = await relayer.checkPrecondition(intentPrecondition)
+ expect(isValid).toBe(true)
+ })
+
+ it('should create and check ERC1155 balance precondition', async () => {
+ const { provider, chainId } = await getProvider()
+ const relayer = new LocalRelayer(provider as any)
+ await requireContractDeployed(provider, ERC20_IMPLICIT_MINT_CONTRACT)
+
+ const precondition = new Erc1155BalancePrecondition(
+ testWalletAddress,
+ ERC20_IMPLICIT_MINT_CONTRACT,
+ 1n, // tokenId
+ 1000000n, // 1 token min
+ 2000000n, // 2 tokens max
+ )
+
+ const intentPrecondition = {
+ type: precondition.type(),
+ chainId: chainId.toString(),
+ data: JSON.stringify({
+ address: precondition.address.toString(),
+ token: precondition.token.toString(),
+ tokenId: precondition.tokenId.toString(),
+ min: precondition.min?.toString(),
+ max: precondition.max?.toString(),
+ }),
+ }
+
+ if (!CAN_RUN_LIVE) {
+ // Mock the balanceOf call
+ ;(provider as any).call.mockResolvedValue('0x1e8480') // 1.5 tokens in hex
+ }
+
+ const isValid = await relayer.checkPrecondition(intentPrecondition)
+ expect(isValid).toBe(true)
+ })
+
+ it('should create and check ERC1155 approval precondition', async () => {
+ const { provider, chainId } = await getProvider()
+ const relayer = new LocalRelayer(provider as any)
+ await requireContractDeployed(provider, ERC20_IMPLICIT_MINT_CONTRACT)
+
+ const operator = randomAddress()
+ const precondition = new Erc1155ApprovalPrecondition(
+ testWalletAddress,
+ ERC20_IMPLICIT_MINT_CONTRACT,
+ 1n, // tokenId
+ operator,
+ 1000000n, // 1 token min approval
+ )
+
+ const intentPrecondition = {
+ type: precondition.type(),
+ chainId: chainId.toString(),
+ data: JSON.stringify({
+ address: precondition.address.toString(),
+ token: precondition.token.toString(),
+ tokenId: precondition.tokenId.toString(),
+ operator: precondition.operator.toString(),
+ min: precondition.min.toString(),
+ }),
+ }
+
+ if (!CAN_RUN_LIVE) {
+ // Mock the isApprovedForAll call
+ ;(provider as any).call.mockResolvedValue('0x1') // true
+ }
+
+ const isValid = await relayer.checkPrecondition(intentPrecondition)
+ expect(isValid).toBe(true)
+ })
+})
diff --git a/packages/services/relayer/test/preconditions/selectors.test.ts b/packages/services/relayer/test/preconditions/selectors.test.ts
new file mode 100644
index 000000000..7fdc008ad
--- /dev/null
+++ b/packages/services/relayer/test/preconditions/selectors.test.ts
@@ -0,0 +1,415 @@
+import { Address } from 'ox'
+import { describe, expect, it } from 'vitest'
+
+import {
+ extractChainID,
+ extractSupportedPreconditions,
+ extractNativeBalancePreconditions,
+ extractERC20BalancePreconditions,
+} from '../../src/preconditions/selectors.js'
+import { TransactionPrecondition } from '../../src/preconditions/codec.js'
+import {
+ NativeBalancePrecondition,
+ Erc20BalancePrecondition,
+ Erc721OwnershipPrecondition,
+} from '../../src/preconditions/types.js'
+import { Network } from '@0xsequence/wallet-primitives'
+
+// Test addresses
+const TEST_ADDRESS = Address.from('0x1234567890123456789012345678901234567890')
+const TOKEN_ADDRESS = Address.from('0xabcdefabcdefabcdefabcdefabcdefabcdefabcd')
+
+describe('Preconditions Selectors', () => {
+ describe('extractChainID', () => {
+ it('should extract chainID from valid precondition data', () => {
+ const precondition: TransactionPrecondition = {
+ type: 'native-balance',
+ data: JSON.stringify({
+ address: TEST_ADDRESS,
+ chainID: '1',
+ min: '1000000000000000000',
+ }),
+ }
+
+ const chainId = extractChainID(precondition)
+ expect(chainId).toBe(Network.ChainId.MAINNET)
+ })
+
+ it('should extract large chainID values', () => {
+ const precondition: TransactionPrecondition = {
+ type: 'native-balance',
+ data: JSON.stringify({
+ address: TEST_ADDRESS,
+ chainID: '42161', // Arbitrum chainID
+ }),
+ }
+
+ const chainId = extractChainID(precondition)
+ expect(chainId).toBe(Network.ChainId.ARBITRUM)
+ })
+
+ it('should return undefined when chainID is not present', () => {
+ const precondition: TransactionPrecondition = {
+ type: 'native-balance',
+ data: JSON.stringify({
+ address: TEST_ADDRESS,
+ min: '1000000000000000000',
+ }),
+ }
+
+ const chainId = extractChainID(precondition)
+ expect(chainId).toBeUndefined()
+ })
+
+ it('should return undefined when chainID is falsy', () => {
+ const precondition: TransactionPrecondition = {
+ type: 'native-balance',
+ data: JSON.stringify({
+ address: TEST_ADDRESS,
+ chainID: '',
+ min: '1000000000000000000',
+ }),
+ }
+
+ const chainId = extractChainID(precondition)
+ expect(chainId).toBeUndefined()
+ })
+
+ it('should return undefined when chainID is null', () => {
+ const precondition: TransactionPrecondition = {
+ type: 'native-balance',
+ data: JSON.stringify({
+ address: TEST_ADDRESS,
+ chainID: null,
+ min: '1000000000000000000',
+ }),
+ }
+
+ const chainId = extractChainID(precondition)
+ expect(chainId).toBeUndefined()
+ })
+
+ it('should return undefined for null/undefined precondition', () => {
+ expect(extractChainID(null as any)).toBeUndefined()
+ expect(extractChainID(undefined as any)).toBeUndefined()
+ })
+
+ it('should return undefined for invalid JSON', () => {
+ const precondition: TransactionPrecondition = {
+ type: 'native-balance',
+ data: 'invalid json',
+ }
+
+ const chainId = extractChainID(precondition)
+ expect(chainId).toBeUndefined()
+ })
+
+ it('should handle chainID with value 0', () => {
+ const precondition: TransactionPrecondition = {
+ type: 'native-balance',
+ data: JSON.stringify({
+ address: TEST_ADDRESS,
+ chainID: '0',
+ }),
+ }
+
+ const chainId = extractChainID(precondition)
+ expect(chainId).toBe(0)
+ })
+ })
+
+ describe('extractSupportedPreconditions', () => {
+ it('should extract valid preconditions', () => {
+ const intents: TransactionPrecondition[] = [
+ {
+ type: 'native-balance',
+ data: JSON.stringify({
+ address: TEST_ADDRESS,
+ min: '1000000000000000000',
+ }),
+ },
+ {
+ type: 'erc20-balance',
+ data: JSON.stringify({
+ address: TEST_ADDRESS,
+ token: TOKEN_ADDRESS,
+ min: '1000000',
+ }),
+ },
+ ]
+
+ const results = extractSupportedPreconditions(intents)
+ expect(results).toHaveLength(2)
+ expect(results[0]).toBeInstanceOf(NativeBalancePrecondition)
+ expect(results[1]).toBeInstanceOf(Erc20BalancePrecondition)
+ })
+
+ it('should filter out invalid preconditions', () => {
+ const intents: TransactionPrecondition[] = [
+ {
+ type: 'native-balance',
+ data: JSON.stringify({
+ address: TEST_ADDRESS,
+ min: '1000000000000000000',
+ }),
+ },
+ {
+ type: 'unknown-type',
+ data: JSON.stringify({ address: TEST_ADDRESS }),
+ },
+ {
+ type: 'native-balance',
+ data: 'invalid json',
+ },
+ ]
+
+ const results = extractSupportedPreconditions(intents)
+ expect(results).toHaveLength(1)
+ expect(results[0]).toBeInstanceOf(NativeBalancePrecondition)
+ })
+
+ it('should return empty array for null/undefined input', () => {
+ expect(extractSupportedPreconditions(null as any)).toEqual([])
+ expect(extractSupportedPreconditions(undefined as any)).toEqual([])
+ })
+
+ it('should return empty array for empty input', () => {
+ const results = extractSupportedPreconditions([])
+ expect(results).toEqual([])
+ })
+
+ it('should handle mixed valid and invalid preconditions', () => {
+ const intents: TransactionPrecondition[] = [
+ {
+ type: 'native-balance',
+ data: JSON.stringify({
+ address: TEST_ADDRESS,
+ min: '1000000000000000000',
+ }),
+ },
+ {
+ type: 'erc721-ownership',
+ data: JSON.stringify({
+ address: TEST_ADDRESS,
+ token: TOKEN_ADDRESS,
+ tokenId: '123',
+ }),
+ },
+ {
+ type: 'invalid-type',
+ data: JSON.stringify({ address: TEST_ADDRESS }),
+ },
+ ]
+
+ const results = extractSupportedPreconditions(intents)
+ expect(results).toHaveLength(2)
+ expect(results[0]).toBeInstanceOf(NativeBalancePrecondition)
+ expect(results[1]).toBeInstanceOf(Erc721OwnershipPrecondition)
+ })
+ })
+
+ describe('extractNativeBalancePreconditions', () => {
+ it('should extract only native balance preconditions', () => {
+ const intents: TransactionPrecondition[] = [
+ {
+ type: 'native-balance',
+ data: JSON.stringify({
+ address: TEST_ADDRESS,
+ min: '1000000000000000000',
+ }),
+ },
+ {
+ type: 'erc20-balance',
+ data: JSON.stringify({
+ address: TEST_ADDRESS,
+ token: TOKEN_ADDRESS,
+ min: '1000000',
+ }),
+ },
+ {
+ type: 'native-balance',
+ data: JSON.stringify({
+ address: TEST_ADDRESS,
+ max: '2000000000000000000',
+ }),
+ },
+ ]
+
+ const results = extractNativeBalancePreconditions(intents)
+ expect(results).toHaveLength(2)
+ expect(results[0]).toBeInstanceOf(NativeBalancePrecondition)
+ expect(results[1]).toBeInstanceOf(NativeBalancePrecondition)
+
+ // Verify the specific properties
+ expect(results[0].min).toBe(1000000000000000000n)
+ expect(results[1].max).toBe(2000000000000000000n)
+ })
+
+ it('should return empty array when no native balance preconditions exist', () => {
+ const intents: TransactionPrecondition[] = [
+ {
+ type: 'erc20-balance',
+ data: JSON.stringify({
+ address: TEST_ADDRESS,
+ token: TOKEN_ADDRESS,
+ min: '1000000',
+ }),
+ },
+ {
+ type: 'erc721-ownership',
+ data: JSON.stringify({
+ address: TEST_ADDRESS,
+ token: TOKEN_ADDRESS,
+ tokenId: '123',
+ }),
+ },
+ ]
+
+ const results = extractNativeBalancePreconditions(intents)
+ expect(results).toEqual([])
+ })
+
+ it('should return empty array for null/undefined input', () => {
+ expect(extractNativeBalancePreconditions(null as any)).toEqual([])
+ expect(extractNativeBalancePreconditions(undefined as any)).toEqual([])
+ })
+
+ it('should return empty array for empty input', () => {
+ const results = extractNativeBalancePreconditions([])
+ expect(results).toEqual([])
+ })
+
+ it('should filter out invalid native balance preconditions', () => {
+ const intents: TransactionPrecondition[] = [
+ {
+ type: 'native-balance',
+ data: JSON.stringify({
+ address: TEST_ADDRESS,
+ min: '1000000000000000000',
+ }),
+ },
+ {
+ type: 'native-balance',
+ data: 'invalid json', // This will be filtered out
+ },
+ {
+ type: 'native-balance',
+ data: JSON.stringify({
+ // Missing address - this will be filtered out
+ min: '1000000000000000000',
+ }),
+ },
+ ]
+
+ const results = extractNativeBalancePreconditions(intents)
+ expect(results).toHaveLength(1)
+ expect(results[0]).toBeInstanceOf(NativeBalancePrecondition)
+ expect(results[0].min).toBe(1000000000000000000n)
+ })
+ })
+
+ describe('extractERC20BalancePreconditions', () => {
+ it('should extract only ERC20 balance preconditions', () => {
+ const intents: TransactionPrecondition[] = [
+ {
+ type: 'native-balance',
+ data: JSON.stringify({
+ address: TEST_ADDRESS,
+ min: '1000000000000000000',
+ }),
+ },
+ {
+ type: 'erc20-balance',
+ data: JSON.stringify({
+ address: TEST_ADDRESS,
+ token: TOKEN_ADDRESS,
+ min: '1000000',
+ }),
+ },
+ {
+ type: 'erc20-balance',
+ data: JSON.stringify({
+ address: TEST_ADDRESS,
+ token: TOKEN_ADDRESS,
+ max: '2000000',
+ }),
+ },
+ ]
+
+ const results = extractERC20BalancePreconditions(intents)
+ expect(results).toHaveLength(2)
+ expect(results[0]).toBeInstanceOf(Erc20BalancePrecondition)
+ expect(results[1]).toBeInstanceOf(Erc20BalancePrecondition)
+
+ // Verify the specific properties
+ expect(results[0].min).toBe(1000000n)
+ expect(results[1].max).toBe(2000000n)
+ expect(results[0].token).toBe(TOKEN_ADDRESS)
+ expect(results[1].token).toBe(TOKEN_ADDRESS)
+ })
+
+ it('should return empty array when no ERC20 balance preconditions exist', () => {
+ const intents: TransactionPrecondition[] = [
+ {
+ type: 'native-balance',
+ data: JSON.stringify({
+ address: TEST_ADDRESS,
+ min: '1000000000000000000',
+ }),
+ },
+ {
+ type: 'erc721-ownership',
+ data: JSON.stringify({
+ address: TEST_ADDRESS,
+ token: TOKEN_ADDRESS,
+ tokenId: '123',
+ }),
+ },
+ ]
+
+ const results = extractERC20BalancePreconditions(intents)
+ expect(results).toEqual([])
+ })
+
+ it('should return empty array for null/undefined input', () => {
+ expect(extractERC20BalancePreconditions(null as any)).toEqual([])
+ expect(extractERC20BalancePreconditions(undefined as any)).toEqual([])
+ })
+
+ it('should return empty array for empty input', () => {
+ const results = extractERC20BalancePreconditions([])
+ expect(results).toEqual([])
+ })
+
+ it('should filter out invalid ERC20 balance preconditions', () => {
+ const intents: TransactionPrecondition[] = [
+ {
+ type: 'erc20-balance',
+ data: JSON.stringify({
+ address: TEST_ADDRESS,
+ token: TOKEN_ADDRESS,
+ min: '1000000',
+ }),
+ },
+ {
+ type: 'erc20-balance',
+ data: 'invalid json', // This will be filtered out
+ },
+ {
+ type: 'erc20-balance',
+ data: JSON.stringify({
+ address: TEST_ADDRESS,
+ // Missing token address - this will be filtered out
+ min: '1000000',
+ }),
+ },
+ ]
+
+ const results = extractERC20BalancePreconditions(intents)
+ expect(results).toHaveLength(1)
+ expect(results[0]).toBeInstanceOf(Erc20BalancePrecondition)
+ expect(results[0].min).toBe(1000000n)
+ expect(results[0].token).toBe(TOKEN_ADDRESS)
+ })
+ })
+})
diff --git a/packages/services/relayer/test/preconditions/types.test.ts b/packages/services/relayer/test/preconditions/types.test.ts
new file mode 100644
index 000000000..a4ecda1d9
--- /dev/null
+++ b/packages/services/relayer/test/preconditions/types.test.ts
@@ -0,0 +1,443 @@
+import { Address } from 'ox'
+import { describe, expect, it } from 'vitest'
+
+import {
+ NativeBalancePrecondition,
+ Erc20BalancePrecondition,
+ Erc20ApprovalPrecondition,
+ Erc721OwnershipPrecondition,
+ Erc721ApprovalPrecondition,
+ Erc1155BalancePrecondition,
+ Erc1155ApprovalPrecondition,
+} from '../../src/preconditions/types.js'
+
+// Test addresses
+const TEST_ADDRESS = Address.from('0x1234567890123456789012345678901234567890')
+const TOKEN_ADDRESS = Address.from('0xabcdefabcdefabcdefabcdefabcdefabcdefabcd')
+const OPERATOR_ADDRESS = Address.from('0x9876543210987654321098765432109876543210')
+
+describe('Preconditions Types', () => {
+ describe('NativeBalancePrecondition', () => {
+ it('should create a valid native balance precondition', () => {
+ const precondition = new NativeBalancePrecondition(TEST_ADDRESS, 1000000000000000000n, 2000000000000000000n)
+
+ expect(precondition.address).toBe(TEST_ADDRESS)
+ expect(precondition.min).toBe(1000000000000000000n)
+ expect(precondition.max).toBe(2000000000000000000n)
+ expect(precondition.type()).toBe('native-balance')
+ expect(precondition.isValid()).toBeUndefined()
+ })
+
+ it('should create a precondition with only min value', () => {
+ const precondition = new NativeBalancePrecondition(TEST_ADDRESS, 1000000000000000000n)
+
+ expect(precondition.min).toBe(1000000000000000000n)
+ expect(precondition.max).toBeUndefined()
+ expect(precondition.isValid()).toBeUndefined()
+ })
+
+ it('should create a precondition with only max value', () => {
+ const precondition = new NativeBalancePrecondition(TEST_ADDRESS, undefined, 2000000000000000000n)
+
+ expect(precondition.min).toBeUndefined()
+ expect(precondition.max).toBe(2000000000000000000n)
+ expect(precondition.isValid()).toBeUndefined()
+ })
+
+ it('should create a precondition with no min/max values', () => {
+ const precondition = new NativeBalancePrecondition(TEST_ADDRESS)
+
+ expect(precondition.min).toBeUndefined()
+ expect(precondition.max).toBeUndefined()
+ expect(precondition.isValid()).toBeUndefined()
+ })
+
+ it('should validate address is required', () => {
+ const precondition = new NativeBalancePrecondition('' as Address.Address)
+
+ const error = precondition.isValid()
+ expect(error).toBeInstanceOf(Error)
+ expect(error?.message).toBe('address is required')
+ })
+
+ it('should validate min cannot be greater than max', () => {
+ const precondition = new NativeBalancePrecondition(TEST_ADDRESS, 2000000000000000000n, 1000000000000000000n)
+
+ const error = precondition.isValid()
+ expect(error).toBeInstanceOf(Error)
+ expect(error?.message).toBe('min balance cannot be greater than max balance')
+ })
+
+ it('should allow min equal to max', () => {
+ const precondition = new NativeBalancePrecondition(TEST_ADDRESS, 1000000000000000000n, 1000000000000000000n)
+
+ expect(precondition.isValid()).toBeUndefined()
+ })
+ })
+
+ describe('Erc20BalancePrecondition', () => {
+ it('should create a valid ERC20 balance precondition', () => {
+ const precondition = new Erc20BalancePrecondition(TEST_ADDRESS, TOKEN_ADDRESS, 1000000n, 2000000n)
+
+ expect(precondition.address).toBe(TEST_ADDRESS)
+ expect(precondition.token).toBe(TOKEN_ADDRESS)
+ expect(precondition.min).toBe(1000000n)
+ expect(precondition.max).toBe(2000000n)
+ expect(precondition.type()).toBe('erc20-balance')
+ expect(precondition.isValid()).toBeUndefined()
+ })
+
+ it('should validate address is required', () => {
+ const precondition = new Erc20BalancePrecondition('' as Address.Address, TOKEN_ADDRESS)
+
+ const error = precondition.isValid()
+ expect(error).toBeInstanceOf(Error)
+ expect(error?.message).toBe('address is required')
+ })
+
+ it('should validate token address is required', () => {
+ const precondition = new Erc20BalancePrecondition(TEST_ADDRESS, '' as Address.Address)
+
+ const error = precondition.isValid()
+ expect(error).toBeInstanceOf(Error)
+ expect(error?.message).toBe('token address is required')
+ })
+
+ it('should validate min cannot be greater than max', () => {
+ const precondition = new Erc20BalancePrecondition(TEST_ADDRESS, TOKEN_ADDRESS, 2000000n, 1000000n)
+
+ const error = precondition.isValid()
+ expect(error).toBeInstanceOf(Error)
+ expect(error?.message).toBe('min balance cannot be greater than max balance')
+ })
+
+ it('should create precondition with only min value', () => {
+ const precondition = new Erc20BalancePrecondition(TEST_ADDRESS, TOKEN_ADDRESS, 1000000n)
+
+ expect(precondition.min).toBe(1000000n)
+ expect(precondition.max).toBeUndefined()
+ expect(precondition.isValid()).toBeUndefined()
+ })
+
+ it('should create precondition with only max value', () => {
+ const precondition = new Erc20BalancePrecondition(TEST_ADDRESS, TOKEN_ADDRESS, undefined, 2000000n)
+
+ expect(precondition.min).toBeUndefined()
+ expect(precondition.max).toBe(2000000n)
+ expect(precondition.isValid()).toBeUndefined()
+ })
+ })
+
+ describe('Erc20ApprovalPrecondition', () => {
+ it('should create a valid ERC20 approval precondition', () => {
+ const precondition = new Erc20ApprovalPrecondition(TEST_ADDRESS, TOKEN_ADDRESS, OPERATOR_ADDRESS, 1000000n)
+
+ expect(precondition.address).toBe(TEST_ADDRESS)
+ expect(precondition.token).toBe(TOKEN_ADDRESS)
+ expect(precondition.operator).toBe(OPERATOR_ADDRESS)
+ expect(precondition.min).toBe(1000000n)
+ expect(precondition.type()).toBe('erc20-approval')
+ expect(precondition.isValid()).toBeUndefined()
+ })
+
+ it('should validate address is required', () => {
+ const precondition = new Erc20ApprovalPrecondition(
+ '' as Address.Address,
+ TOKEN_ADDRESS,
+ OPERATOR_ADDRESS,
+ 1000000n,
+ )
+
+ const error = precondition.isValid()
+ expect(error).toBeInstanceOf(Error)
+ expect(error?.message).toBe('address is required')
+ })
+
+ it('should validate token address is required', () => {
+ const precondition = new Erc20ApprovalPrecondition(
+ TEST_ADDRESS,
+ '' as Address.Address,
+ OPERATOR_ADDRESS,
+ 1000000n,
+ )
+
+ const error = precondition.isValid()
+ expect(error).toBeInstanceOf(Error)
+ expect(error?.message).toBe('token address is required')
+ })
+
+ it('should validate operator address is required', () => {
+ const precondition = new Erc20ApprovalPrecondition(TEST_ADDRESS, TOKEN_ADDRESS, '' as Address.Address, 1000000n)
+
+ const error = precondition.isValid()
+ expect(error).toBeInstanceOf(Error)
+ expect(error?.message).toBe('operator address is required')
+ })
+
+ it('should validate min approval amount is required', () => {
+ const precondition = new Erc20ApprovalPrecondition(
+ TEST_ADDRESS,
+ TOKEN_ADDRESS,
+ OPERATOR_ADDRESS,
+ undefined as any,
+ )
+
+ const error = precondition.isValid()
+ expect(error).toBeInstanceOf(Error)
+ expect(error?.message).toBe('min approval amount is required')
+ })
+ })
+
+ describe('Erc721OwnershipPrecondition', () => {
+ it('should create a valid ERC721 ownership precondition', () => {
+ const precondition = new Erc721OwnershipPrecondition(TEST_ADDRESS, TOKEN_ADDRESS, 123n, true)
+
+ expect(precondition.address).toBe(TEST_ADDRESS)
+ expect(precondition.token).toBe(TOKEN_ADDRESS)
+ expect(precondition.tokenId).toBe(123n)
+ expect(precondition.owned).toBe(true)
+ expect(precondition.type()).toBe('erc721-ownership')
+ expect(precondition.isValid()).toBeUndefined()
+ })
+
+ it('should create precondition with default owned value', () => {
+ const precondition = new Erc721OwnershipPrecondition(TEST_ADDRESS, TOKEN_ADDRESS, 123n)
+
+ expect(precondition.owned).toBeUndefined()
+ expect(precondition.isValid()).toBeUndefined()
+ })
+
+ it('should validate address is required', () => {
+ const precondition = new Erc721OwnershipPrecondition('' as Address.Address, TOKEN_ADDRESS, 123n)
+
+ const error = precondition.isValid()
+ expect(error).toBeInstanceOf(Error)
+ expect(error?.message).toBe('address is required')
+ })
+
+ it('should validate token address is required', () => {
+ const precondition = new Erc721OwnershipPrecondition(TEST_ADDRESS, '' as Address.Address, 123n)
+
+ const error = precondition.isValid()
+ expect(error).toBeInstanceOf(Error)
+ expect(error?.message).toBe('token address is required')
+ })
+
+ it('should validate tokenId is required', () => {
+ const precondition = new Erc721OwnershipPrecondition(TEST_ADDRESS, TOKEN_ADDRESS, undefined as any)
+
+ const error = precondition.isValid()
+ expect(error).toBeInstanceOf(Error)
+ expect(error?.message).toBe('tokenId is required')
+ })
+
+ it('should handle tokenId of 0', () => {
+ const precondition = new Erc721OwnershipPrecondition(TEST_ADDRESS, TOKEN_ADDRESS, 0n)
+
+ expect(precondition.tokenId).toBe(0n)
+ expect(precondition.isValid()).toBeUndefined()
+ })
+ })
+
+ describe('Erc721ApprovalPrecondition', () => {
+ it('should create a valid ERC721 approval precondition', () => {
+ const precondition = new Erc721ApprovalPrecondition(TEST_ADDRESS, TOKEN_ADDRESS, 123n, OPERATOR_ADDRESS)
+
+ expect(precondition.address).toBe(TEST_ADDRESS)
+ expect(precondition.token).toBe(TOKEN_ADDRESS)
+ expect(precondition.tokenId).toBe(123n)
+ expect(precondition.operator).toBe(OPERATOR_ADDRESS)
+ expect(precondition.type()).toBe('erc721-approval')
+ expect(precondition.isValid()).toBeUndefined()
+ })
+
+ it('should validate address is required', () => {
+ const precondition = new Erc721ApprovalPrecondition('' as Address.Address, TOKEN_ADDRESS, 123n, OPERATOR_ADDRESS)
+
+ const error = precondition.isValid()
+ expect(error).toBeInstanceOf(Error)
+ expect(error?.message).toBe('address is required')
+ })
+
+ it('should validate token address is required', () => {
+ const precondition = new Erc721ApprovalPrecondition(TEST_ADDRESS, '' as Address.Address, 123n, OPERATOR_ADDRESS)
+
+ const error = precondition.isValid()
+ expect(error).toBeInstanceOf(Error)
+ expect(error?.message).toBe('token address is required')
+ })
+
+ it('should validate tokenId is required', () => {
+ const precondition = new Erc721ApprovalPrecondition(
+ TEST_ADDRESS,
+ TOKEN_ADDRESS,
+ undefined as any,
+ OPERATOR_ADDRESS,
+ )
+
+ const error = precondition.isValid()
+ expect(error).toBeInstanceOf(Error)
+ expect(error?.message).toBe('tokenId is required')
+ })
+
+ it('should validate operator address is required', () => {
+ const precondition = new Erc721ApprovalPrecondition(TEST_ADDRESS, TOKEN_ADDRESS, 123n, '' as Address.Address)
+
+ const error = precondition.isValid()
+ expect(error).toBeInstanceOf(Error)
+ expect(error?.message).toBe('operator address is required')
+ })
+ })
+
+ describe('Erc1155BalancePrecondition', () => {
+ it('should create a valid ERC1155 balance precondition', () => {
+ const precondition = new Erc1155BalancePrecondition(TEST_ADDRESS, TOKEN_ADDRESS, 123n, 1000000n, 2000000n)
+
+ expect(precondition.address).toBe(TEST_ADDRESS)
+ expect(precondition.token).toBe(TOKEN_ADDRESS)
+ expect(precondition.tokenId).toBe(123n)
+ expect(precondition.min).toBe(1000000n)
+ expect(precondition.max).toBe(2000000n)
+ expect(precondition.type()).toBe('erc1155-balance')
+ expect(precondition.isValid()).toBeUndefined()
+ })
+
+ it('should validate address is required', () => {
+ const precondition = new Erc1155BalancePrecondition('' as Address.Address, TOKEN_ADDRESS, 123n)
+
+ const error = precondition.isValid()
+ expect(error).toBeInstanceOf(Error)
+ expect(error?.message).toBe('address is required')
+ })
+
+ it('should validate token address is required', () => {
+ const precondition = new Erc1155BalancePrecondition(TEST_ADDRESS, '' as Address.Address, 123n)
+
+ const error = precondition.isValid()
+ expect(error).toBeInstanceOf(Error)
+ expect(error?.message).toBe('token address is required')
+ })
+
+ it('should validate tokenId is required', () => {
+ const precondition = new Erc1155BalancePrecondition(TEST_ADDRESS, TOKEN_ADDRESS, undefined as any)
+
+ const error = precondition.isValid()
+ expect(error).toBeInstanceOf(Error)
+ expect(error?.message).toBe('tokenId is required')
+ })
+
+ it('should validate min cannot be greater than max', () => {
+ const precondition = new Erc1155BalancePrecondition(TEST_ADDRESS, TOKEN_ADDRESS, 123n, 2000000n, 1000000n)
+
+ const error = precondition.isValid()
+ expect(error).toBeInstanceOf(Error)
+ expect(error?.message).toBe('min balance cannot be greater than max balance')
+ })
+
+ it('should create precondition with only min value', () => {
+ const precondition = new Erc1155BalancePrecondition(TEST_ADDRESS, TOKEN_ADDRESS, 123n, 1000000n)
+
+ expect(precondition.min).toBe(1000000n)
+ expect(precondition.max).toBeUndefined()
+ expect(precondition.isValid()).toBeUndefined()
+ })
+
+ it('should create precondition with only max value', () => {
+ const precondition = new Erc1155BalancePrecondition(TEST_ADDRESS, TOKEN_ADDRESS, 123n, undefined, 2000000n)
+
+ expect(precondition.min).toBeUndefined()
+ expect(precondition.max).toBe(2000000n)
+ expect(precondition.isValid()).toBeUndefined()
+ })
+ })
+
+ describe('Erc1155ApprovalPrecondition', () => {
+ it('should create a valid ERC1155 approval precondition', () => {
+ const precondition = new Erc1155ApprovalPrecondition(
+ TEST_ADDRESS,
+ TOKEN_ADDRESS,
+ 123n,
+ OPERATOR_ADDRESS,
+ 1000000n,
+ )
+
+ expect(precondition.address).toBe(TEST_ADDRESS)
+ expect(precondition.token).toBe(TOKEN_ADDRESS)
+ expect(precondition.tokenId).toBe(123n)
+ expect(precondition.operator).toBe(OPERATOR_ADDRESS)
+ expect(precondition.min).toBe(1000000n)
+ expect(precondition.type()).toBe('erc1155-approval')
+ expect(precondition.isValid()).toBeUndefined()
+ })
+
+ it('should validate address is required', () => {
+ const precondition = new Erc1155ApprovalPrecondition(
+ '' as Address.Address,
+ TOKEN_ADDRESS,
+ 123n,
+ OPERATOR_ADDRESS,
+ 1000000n,
+ )
+
+ const error = precondition.isValid()
+ expect(error).toBeInstanceOf(Error)
+ expect(error?.message).toBe('address is required')
+ })
+
+ it('should validate token address is required', () => {
+ const precondition = new Erc1155ApprovalPrecondition(
+ TEST_ADDRESS,
+ '' as Address.Address,
+ 123n,
+ OPERATOR_ADDRESS,
+ 1000000n,
+ )
+
+ const error = precondition.isValid()
+ expect(error).toBeInstanceOf(Error)
+ expect(error?.message).toBe('token address is required')
+ })
+
+ it('should validate tokenId is required', () => {
+ const precondition = new Erc1155ApprovalPrecondition(
+ TEST_ADDRESS,
+ TOKEN_ADDRESS,
+ undefined as any,
+ OPERATOR_ADDRESS,
+ 1000000n,
+ )
+
+ const error = precondition.isValid()
+ expect(error).toBeInstanceOf(Error)
+ expect(error?.message).toBe('tokenId is required')
+ })
+
+ it('should validate operator address is required', () => {
+ const precondition = new Erc1155ApprovalPrecondition(
+ TEST_ADDRESS,
+ TOKEN_ADDRESS,
+ 123n,
+ '' as Address.Address,
+ 1000000n,
+ )
+
+ const error = precondition.isValid()
+ expect(error).toBeInstanceOf(Error)
+ expect(error?.message).toBe('operator address is required')
+ })
+
+ it('should validate min approval amount is required', () => {
+ const precondition = new Erc1155ApprovalPrecondition(
+ TEST_ADDRESS,
+ TOKEN_ADDRESS,
+ 123n,
+ OPERATOR_ADDRESS,
+ undefined as any,
+ )
+
+ const error = precondition.isValid()
+ expect(error).toBeInstanceOf(Error)
+ expect(error?.message).toBe('min approval amount is required')
+ })
+ })
+})
diff --git a/packages/services/relayer/test/relayer/relayer.test.ts b/packages/services/relayer/test/relayer/relayer.test.ts
new file mode 100644
index 000000000..adbadd236
--- /dev/null
+++ b/packages/services/relayer/test/relayer/relayer.test.ts
@@ -0,0 +1,355 @@
+import { describe, expect, it, vi, beforeEach } from 'vitest'
+import { Address, Hex } from 'ox'
+import { Network, Payload } from '@0xsequence/wallet-primitives'
+import { Relayer, RelayerGen } from '@0xsequence/relayer'
+
+// Test addresses and data
+const TEST_WALLET_ADDRESS = Address.from('0x1234567890123456789012345678901234567890')
+const TEST_TO_ADDRESS = Address.from('0xabcdefabcdefabcdefabcdefabcdefabcdefabcd')
+const TEST_DATA = Hex.from('0x12345678')
+const TEST_CHAIN_ID = Network.ChainId.MAINNET
+const TEST_OP_HASH = Hex.from('0xdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef')
+
+describe('Relayer', () => {
+ describe('Relayer.isRelayer type guard', () => {
+ it('should return true for valid relayer objects', () => {
+ const mockRelayer: Relayer.Relayer = {
+ kind: 'relayer',
+ type: 'test',
+ id: 'test-relayer',
+ isAvailable: vi.fn(),
+ feeTokens: vi.fn(),
+ feeOptions: vi.fn(),
+ relay: vi.fn(),
+ status: vi.fn(),
+ checkPrecondition: vi.fn(),
+ }
+
+ expect(Relayer.isRelayer(mockRelayer)).toBe(true)
+ })
+
+ it('should return false for objects missing required methods', () => {
+ // Missing isAvailable
+ const missing1 = {
+ kind: 'relayer' as const,
+ type: 'test',
+ id: 'test-relayer',
+ feeOptions: vi.fn(),
+ relay: vi.fn(),
+ status: vi.fn(),
+ checkPrecondition: vi.fn(),
+ }
+ expect(Relayer.isRelayer(missing1)).toBe(false)
+
+ // Missing feeOptions
+ const missing2 = {
+ kind: 'relayer' as const,
+ type: 'test',
+ id: 'test-relayer',
+ isAvailable: vi.fn(),
+ relay: vi.fn(),
+ status: vi.fn(),
+ checkPrecondition: vi.fn(),
+ }
+ expect(Relayer.isRelayer(missing2)).toBe(false)
+
+ // Missing relay
+ const missing3 = {
+ kind: 'relayer' as const,
+ type: 'test',
+ id: 'test-relayer',
+ isAvailable: vi.fn(),
+ feeOptions: vi.fn(),
+ status: vi.fn(),
+ checkPrecondition: vi.fn(),
+ }
+ expect(Relayer.isRelayer(missing3)).toBe(false)
+
+ // Missing status
+ const missing4 = {
+ kind: 'relayer' as const,
+ type: 'test',
+ id: 'test-relayer',
+ isAvailable: vi.fn(),
+ feeOptions: vi.fn(),
+ relay: vi.fn(),
+ checkPrecondition: vi.fn(),
+ }
+ expect(Relayer.isRelayer(missing4)).toBe(false)
+
+ // Missing checkPrecondition
+ const missing5 = {
+ kind: 'relayer' as const,
+ type: 'test',
+ id: 'test-relayer',
+ isAvailable: vi.fn(),
+ feeOptions: vi.fn(),
+ relay: vi.fn(),
+ status: vi.fn(),
+ }
+ expect(Relayer.isRelayer(missing5)).toBe(false)
+ })
+
+ it('should return false for non-objects', () => {
+ // These will throw due to the 'in' operator, so we need to test the actual behavior
+ expect(() => Relayer.isRelayer(null)).toThrow()
+ expect(() => Relayer.isRelayer(undefined)).toThrow()
+ expect(() => Relayer.isRelayer('string')).toThrow()
+ expect(() => Relayer.isRelayer(123)).toThrow()
+ expect(() => Relayer.isRelayer(true)).toThrow()
+ // Arrays and objects should not throw, but should return false
+ expect(Relayer.isRelayer([])).toBe(false)
+ })
+
+ it('should return false for objects with properties but wrong types', () => {
+ const wrongTypes = {
+ kind: 'relayer' as const,
+ type: 'test',
+ id: 'test-relayer',
+ isAvailable: 'not a function',
+ feeOptions: vi.fn(),
+ relay: vi.fn(),
+ status: vi.fn(),
+ checkPrecondition: vi.fn(),
+ }
+ // The current implementation only checks if properties exist, not their types
+ // So this will actually return true since all required properties exist
+ expect(Relayer.isRelayer(wrongTypes)).toBe(true)
+ })
+ })
+
+ describe('FeeOption interface', () => {
+ it('should accept valid fee option objects', () => {
+ const feeOption: Relayer.FeeOption = {
+ token: {
+ chainId: Network.ChainId.MAINNET,
+ name: 'Ethereum',
+ symbol: 'ETH',
+ decimals: 18,
+ logoURL: 'https://example.com/eth.png',
+ type: 'NATIVE' as RelayerGen.FeeTokenType,
+ contractAddress: undefined,
+ },
+ to: TEST_TO_ADDRESS,
+ value: '1000000000000000000',
+ gasLimit: 21000,
+ }
+
+ expect(feeOption.token).toBeDefined()
+ expect(feeOption.to).toBe(TEST_TO_ADDRESS)
+ expect(feeOption.value).toBe('1000000000000000000')
+ expect(feeOption.gasLimit).toBe(21000)
+ })
+ })
+
+ describe('FeeQuote interface', () => {
+ it('should accept valid fee quote objects', () => {
+ const feeQuote: Relayer.FeeQuote = {
+ _tag: 'FeeQuote',
+ _quote: { someQuoteData: 'value' },
+ }
+
+ expect(feeQuote._tag).toBe('FeeQuote')
+ expect(feeQuote._quote).toBeDefined()
+ })
+ })
+
+ describe('OperationStatus types', () => {
+ it('should accept OperationUnknownStatus', () => {
+ const status: Relayer.OperationUnknownStatus = {
+ status: 'unknown',
+ reason: 'Transaction not found',
+ }
+
+ expect(status.status).toBe('unknown')
+ expect(status.reason).toBe('Transaction not found')
+ })
+
+ it('should accept OperationQueuedStatus', () => {
+ const status: Relayer.OperationQueuedStatus = {
+ status: 'queued',
+ reason: 'Transaction queued for processing',
+ }
+
+ expect(status.status).toBe('queued')
+ expect(status.reason).toBeDefined()
+ })
+
+ it('should accept OperationPendingStatus', () => {
+ const status: Relayer.OperationPendingStatus = {
+ status: 'pending',
+ reason: 'Transaction pending confirmation',
+ }
+
+ expect(status.status).toBe('pending')
+ expect(status.reason).toBeDefined()
+ })
+
+ it('should accept OperationPendingPreconditionStatus', () => {
+ const status: Relayer.OperationPendingPreconditionStatus = {
+ status: 'pending-precondition',
+ reason: 'Waiting for preconditions to be met',
+ }
+
+ expect(status.status).toBe('pending-precondition')
+ expect(status.reason).toBeDefined()
+ })
+
+ it('should accept OperationConfirmedStatus', () => {
+ const status: Relayer.OperationConfirmedStatus = {
+ status: 'confirmed',
+ transactionHash: TEST_OP_HASH,
+ data: {
+ receipt: {
+ id: 'receipt123',
+ status: 'success',
+ index: 0,
+ logs: [],
+ receipts: [],
+ blockNumber: '12345',
+ txnHash: 'hash123',
+ txnReceipt: 'receipt_data',
+ },
+ },
+ }
+
+ expect(status.status).toBe('confirmed')
+ expect(status.transactionHash).toBe(TEST_OP_HASH)
+ expect(status.data).toBeDefined()
+ })
+
+ it('should accept OperationFailedStatus', () => {
+ const status: Relayer.OperationFailedStatus = {
+ status: 'failed',
+ transactionHash: TEST_OP_HASH,
+ reason: 'Transaction reverted',
+ data: {
+ receipt: {
+ id: 'receipt456',
+ status: 'failed',
+ index: 0,
+ logs: [],
+ receipts: [],
+ blockNumber: '12345',
+ txnHash: 'hash123',
+ txnReceipt: 'receipt_data',
+ },
+ },
+ }
+
+ expect(status.status).toBe('failed')
+ expect(status.transactionHash).toBe(TEST_OP_HASH)
+ expect(status.reason).toBe('Transaction reverted')
+ expect(status.data).toBeDefined()
+ })
+
+ it('should handle OperationStatus union type', () => {
+ const statuses: Relayer.OperationStatus[] = [
+ { status: 'unknown' },
+ { status: 'queued' },
+ { status: 'pending' },
+ { status: 'pending-precondition' },
+ { status: 'confirmed', transactionHash: TEST_OP_HASH },
+ { status: 'failed', reason: 'Error occurred' },
+ ]
+
+ statuses.forEach((status) => {
+ expect(['unknown', 'queued', 'pending', 'pending-precondition', 'confirmed', 'failed']).toContain(status.status)
+ })
+ })
+ })
+
+ describe('Relayer interface contract', () => {
+ let mockRelayer: Relayer.Relayer
+
+ beforeEach(() => {
+ mockRelayer = {
+ kind: 'relayer',
+ type: 'mock',
+ id: 'mock-relayer',
+ isAvailable: vi.fn(),
+ feeTokens: vi.fn(),
+ feeOptions: vi.fn(),
+ relay: vi.fn(),
+ status: vi.fn(),
+ checkPrecondition: vi.fn(),
+ }
+ })
+
+ it('should have required properties', () => {
+ expect(mockRelayer.kind).toBe('relayer')
+ expect(mockRelayer.type).toBe('mock')
+ expect(mockRelayer.id).toBe('mock-relayer')
+ })
+
+ it('should have required methods with correct signatures', () => {
+ expect(typeof mockRelayer.isAvailable).toBe('function')
+ expect(typeof mockRelayer.feeOptions).toBe('function')
+ expect(typeof mockRelayer.relay).toBe('function')
+ expect(typeof mockRelayer.status).toBe('function')
+ expect(typeof mockRelayer.checkPrecondition).toBe('function')
+ })
+
+ it('should support typical relayer workflow methods', async () => {
+ // Mock the methods to return expected types
+ vi.mocked(mockRelayer.isAvailable).mockResolvedValue(true)
+ vi.mocked(mockRelayer.feeOptions).mockResolvedValue({
+ options: [],
+ quote: undefined,
+ })
+ vi.mocked(mockRelayer.relay).mockResolvedValue({
+ opHash: TEST_OP_HASH,
+ })
+ vi.mocked(mockRelayer.status).mockResolvedValue({
+ status: 'confirmed',
+ transactionHash: TEST_OP_HASH,
+ })
+ vi.mocked(mockRelayer.checkPrecondition).mockResolvedValue(true)
+
+ // Test method calls
+ const isAvailable = await mockRelayer.isAvailable(TEST_WALLET_ADDRESS, TEST_CHAIN_ID)
+ expect(isAvailable).toBe(true)
+
+ const feeOptions = await mockRelayer.feeOptions(TEST_WALLET_ADDRESS, TEST_CHAIN_ID, [])
+ expect(feeOptions.options).toEqual([])
+
+ const relayResult = await mockRelayer.relay(TEST_TO_ADDRESS, TEST_DATA, TEST_CHAIN_ID)
+ expect(relayResult.opHash).toBe(TEST_OP_HASH)
+
+ const statusResult = await mockRelayer.status(TEST_OP_HASH, TEST_CHAIN_ID)
+ expect(statusResult.status).toBe('confirmed')
+
+ const preconditionResult = await mockRelayer.checkPrecondition({} as any)
+ expect(preconditionResult).toBe(true)
+ })
+ })
+
+ describe('Type compatibility', () => {
+ it('should work with Address and Hex types from ox', () => {
+ // Test that the interfaces work correctly with ox types
+ const address = Address.from('0x1234567890123456789012345678901234567890')
+ const hex = Hex.from('0xabcdef')
+ const chainId = 1n
+
+ expect(Address.validate(address)).toBe(true)
+ expect(Hex.validate(hex)).toBe(true)
+ expect(typeof chainId).toBe('bigint')
+ })
+
+ it('should work with wallet-primitives types', () => {
+ // Test basic compatibility with imported types
+ const mockCall: Payload.Call = {
+ to: TEST_TO_ADDRESS,
+ value: 0n,
+ data: TEST_DATA,
+ gasLimit: 21000n,
+ delegateCall: false,
+ onlyFallback: false,
+ behaviorOnError: 'revert',
+ }
+
+ expect(mockCall.to).toBe(TEST_TO_ADDRESS)
+ expect(mockCall.data).toBe(TEST_DATA)
+ })
+ })
+})
diff --git a/packages/services/userdata/CHANGELOG.md b/packages/services/userdata/CHANGELOG.md
new file mode 100644
index 000000000..b28ab5220
--- /dev/null
+++ b/packages/services/userdata/CHANGELOG.md
@@ -0,0 +1,13 @@
+# @0xsequence/userdata
+
+## 3.0.0-beta.6
+
+### Patch Changes
+
+- Fix signer 404 error, minor fixes
+
+## 3.0.0-beta.5
+
+### Patch Changes
+
+- Beta release for v3
diff --git a/packages/services/userdata/README.md b/packages/services/userdata/README.md
new file mode 100644
index 000000000..2387b56e2
--- /dev/null
+++ b/packages/services/userdata/README.md
@@ -0,0 +1,3 @@
+# @0xsequence/userdata
+
+See [0xsequence project page](https://github.com/0xsequence/sequence.js).
diff --git a/packages/services/userdata/package.json b/packages/services/userdata/package.json
new file mode 100644
index 000000000..3d2fd79e9
--- /dev/null
+++ b/packages/services/userdata/package.json
@@ -0,0 +1,28 @@
+{
+ "name": "@0xsequence/userdata",
+ "version": "3.0.0-beta.6",
+ "description": "userdata sub-package for Sequence",
+ "repository": "https://github.com/0xsequence/sequence.js/tree/master/packages/services/userdata",
+ "author": "Sequence Platforms Inc.",
+ "license": "Apache-2.0",
+ "publishConfig": {
+ "access": "public"
+ },
+ "scripts": {
+ "build": "tsc",
+ "dev": "tsc --watch",
+ "test": "echo",
+ "typecheck": "tsc --noEmit"
+ },
+ "exports": {
+ ".": {
+ "types": "./dist/index.d.ts",
+ "default": "./dist/index.js"
+ }
+ },
+ "devDependencies": {
+ "@repo/typescript-config": "workspace:^",
+ "@types/node": "^25.0.2",
+ "typescript": "^5.9.3"
+ }
+}
diff --git a/packages/services/userdata/src/index.ts b/packages/services/userdata/src/index.ts
new file mode 100644
index 000000000..af76930fc
--- /dev/null
+++ b/packages/services/userdata/src/index.ts
@@ -0,0 +1,36 @@
+export * from './userdata.gen'
+
+import { UserData as UserdataRpc } from './userdata.gen'
+
+export class SequenceUserdataClient extends UserdataRpc {
+ constructor(
+ hostname: string,
+ public projectAccessKey?: string,
+ public jwtAuth?: string,
+ ) {
+ super(hostname.endsWith('/') ? hostname.slice(0, -1) : hostname, fetch)
+ this.fetch = this._fetch
+ }
+
+ _fetch = (input: RequestInfo, init?: RequestInit): Promise => {
+ // automatically include jwt and access key auth header to requests
+ // if its been set on the api client
+ const headers: { [key: string]: any } = {}
+
+ const jwtAuth = this.jwtAuth
+ const projectAccessKey = this.projectAccessKey
+
+ if (jwtAuth && jwtAuth.length > 0) {
+ headers['Authorization'] = `BEARER ${jwtAuth}`
+ }
+
+ if (projectAccessKey && projectAccessKey.length > 0) {
+ headers['X-Access-Key'] = projectAccessKey
+ }
+
+ // before the request is made
+ init!.headers = { ...init!.headers, ...headers }
+
+ return fetch(input, init)
+ }
+}
diff --git a/packages/services/userdata/src/userdata.gen.ts b/packages/services/userdata/src/userdata.gen.ts
new file mode 100644
index 000000000..a26fdb995
--- /dev/null
+++ b/packages/services/userdata/src/userdata.gen.ts
@@ -0,0 +1,686 @@
+/* eslint-disable */
+// userdata v0.1.0 99a19ff0218eda6f5e544642d0fd72f66736bdaf
+// --
+// Code generated by Webrpc-gen@v0.30.2 with typescript generator. DO NOT EDIT.
+//
+// webrpc-gen -schema=userdata.ridl -target=typescript -client -out=./clients/userdata.gen.ts
+
+// Webrpc description and code-gen version
+export const WebrpcVersion = 'v1'
+
+// Schema version of your RIDL schema
+export const WebrpcSchemaVersion = 'v0.1.0'
+
+// Schema hash generated from your RIDL schema
+export const WebrpcSchemaHash = '99a19ff0218eda6f5e544642d0fd72f66736bdaf'
+
+//
+// Client interface
+//
+
+export interface UserDataClient {
+ getCapabilities(headers?: object, signal?: AbortSignal): Promise
+
+ getAccessToken(req: GetAccessTokenRequest, headers?: object, signal?: AbortSignal): Promise
+
+ getIdentityToken(
+ req: GetIdentityTokenRequest,
+ headers?: object,
+ signal?: AbortSignal,
+ ): Promise
+}
+
+//
+// Schema types
+//
+
+export interface Wallet {
+ address: string
+ ecosystem: number
+}
+
+export interface Signer {
+ address: string
+ kind: string
+ email?: string
+}
+
+export interface WalletSigner {
+ walletAddress: string
+ signerAddress: string
+}
+
+export interface Session {
+ walletAddress: string
+ sessionAddress: string
+ ipAddress: string
+ userAgent: string
+ originUrl: string
+ appUrl: string
+ createdAt: string
+}
+
+export interface SessionProps {
+ address: string
+ appUrl: string
+}
+
+export interface GetCapabilitiesRequest {}
+
+export interface GetCapabilitiesResponse {
+ supportedMethods: Array
+}
+
+export interface GetAccessTokenRequest {
+ ethauthProof: string
+ chainId: string
+}
+
+export interface GetAccessTokenResponse {
+ accessToken: string
+ refreshToken: string
+ expiresIn: number
+}
+
+export interface GetIdentityTokenRequest {
+ claims: { [key: string]: any }
+}
+
+export interface GetIdentityTokenResponse {
+ idToken: string
+}
+
+//
+// Client
+//
+
+export class UserData implements UserDataClient {
+ protected hostname: string
+ protected fetch: Fetch
+ protected path = '/rpc/UserData/'
+
+ constructor(hostname: string, fetch: Fetch) {
+ this.hostname = hostname.replace(/\/*$/, '')
+ this.fetch = (input: RequestInfo, init?: RequestInit) => fetch(input, init)
+ }
+
+ private url(name: string): string {
+ return this.hostname + this.path + name
+ }
+
+ queryKey = {
+ getCapabilities: () => ['UserData', 'getCapabilities'] as const,
+ getAccessToken: (req: GetAccessTokenRequest) => ['UserData', 'getAccessToken', req] as const,
+ getIdentityToken: (req: GetIdentityTokenRequest) => ['UserData', 'getIdentityToken', req] as const,
+ }
+
+ getCapabilities = (headers?: object, signal?: AbortSignal): Promise => {
+ return this.fetch(this.url('GetCapabilities'), createHttpRequest('{}', headers, signal)).then(
+ (res) => {
+ return buildResponse(res).then((_data) => {
+ return JsonDecode(_data, 'GetCapabilitiesResponse')
+ })
+ },
+ (error) => {
+ throw WebrpcRequestFailedError.new({
+ cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`,
+ })
+ },
+ )
+ }
+
+ getAccessToken = (
+ req: GetAccessTokenRequest,
+ headers?: object,
+ signal?: AbortSignal,
+ ): Promise => {
+ return this.fetch(
+ this.url('GetAccessToken'),
+ createHttpRequest(JsonEncode(req, 'GetAccessTokenRequest'), headers, signal),
+ ).then(
+ (res) => {
+ return buildResponse(res).then((_data) => {
+ return JsonDecode(_data, 'GetAccessTokenResponse')
+ })
+ },
+ (error) => {
+ throw WebrpcRequestFailedError.new({
+ cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`,
+ })
+ },
+ )
+ }
+
+ getIdentityToken = (
+ req: GetIdentityTokenRequest,
+ headers?: object,
+ signal?: AbortSignal,
+ ): Promise => {
+ return this.fetch(
+ this.url('GetIdentityToken'),
+ createHttpRequest(JsonEncode(req, 'GetIdentityTokenRequest'), headers, signal),
+ ).then(
+ (res) => {
+ return buildResponse(res).then((_data) => {
+ return JsonDecode(_data, 'GetIdentityTokenResponse')
+ })
+ },
+ (error) => {
+ throw WebrpcRequestFailedError.new({
+ cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`,
+ })
+ },
+ )
+ }
+}
+
+const createHttpRequest = (body: string = '{}', headers: object = {}, signal: AbortSignal | null = null): object => {
+ const reqHeaders: { [key: string]: string } = { ...headers, 'Content-Type': 'application/json' }
+ return { method: 'POST', headers: reqHeaders, body, signal }
+}
+
+const buildResponse = (res: Response): Promise => {
+ return res.text().then((text) => {
+ let data
+ try {
+ data = JSON.parse(text)
+ } catch (error) {
+ throw WebrpcBadResponseError.new({
+ status: res.status,
+ cause: `JSON.parse(): ${error instanceof Error ? error.message : String(error)}: response text: ${text}`,
+ })
+ }
+ if (!res.ok) {
+ const code: number = typeof data.code === 'number' ? data.code : 0
+ throw (webrpcErrorByCode[code] || WebrpcError).new(data)
+ }
+ return data
+ })
+}
+
+export type Fetch = (input: RequestInfo, init?: RequestInit) => Promise
+
+export const JsonEncode = (obj: T, _typ: string = ''): string => {
+ return JSON.stringify(obj)
+}
+
+export const JsonDecode = (data: string | any, _typ: string = ''): T => {
+ let parsed: any = data
+ if (typeof data === 'string') {
+ try {
+ parsed = JSON.parse(data)
+ } catch (err) {
+ throw WebrpcBadResponseError.new({ cause: `JsonDecode: JSON.parse failed: ${(err as Error).message}` })
+ }
+ }
+ return parsed as T
+}
+
+//
+// Errors
+//
+
+type WebrpcErrorParams = { name?: string; code?: number; message?: string; status?: number; cause?: string }
+
+export class WebrpcError extends Error {
+ code: number
+ status: number
+
+ constructor(error: WebrpcErrorParams = {}) {
+ super(error.message)
+ this.name = error.name || 'WebrpcEndpointError'
+ this.code = typeof error.code === 'number' ? error.code : 0
+ this.message = error.message || `endpoint error`
+ this.status = typeof error.status === 'number' ? error.status : 400
+ if (error.cause !== undefined) this.cause = error.cause
+ Object.setPrototypeOf(this, WebrpcError.prototype)
+ }
+
+ static new(payload: any): WebrpcError {
+ return new this({ message: payload.message, code: payload.code, status: payload.status, cause: payload.cause })
+ }
+}
+
+export class WebrpcEndpointError extends WebrpcError {
+ constructor(error: WebrpcErrorParams = {}) {
+ super(error)
+ this.name = error.name || 'WebrpcEndpoint'
+ this.code = typeof error.code === 'number' ? error.code : 0
+ this.message = error.message || `endpoint error`
+ this.status = typeof error.status === 'number' ? error.status : 400
+ if (error.cause !== undefined) this.cause = error.cause
+ Object.setPrototypeOf(this, WebrpcEndpointError.prototype)
+ }
+}
+
+export class WebrpcRequestFailedError extends WebrpcError {
+ constructor(error: WebrpcErrorParams = {}) {
+ super(error)
+ this.name = error.name || 'WebrpcRequestFailed'
+ this.code = typeof error.code === 'number' ? error.code : -1
+ this.message = error.message || `request failed`
+ this.status = typeof error.status === 'number' ? error.status : 400
+ if (error.cause !== undefined) this.cause = error.cause
+ Object.setPrototypeOf(this, WebrpcRequestFailedError.prototype)
+ }
+}
+
+export class WebrpcBadRouteError extends WebrpcError {
+ constructor(error: WebrpcErrorParams = {}) {
+ super(error)
+ this.name = error.name || 'WebrpcBadRoute'
+ this.code = typeof error.code === 'number' ? error.code : -2
+ this.message = error.message || `bad route`
+ this.status = typeof error.status === 'number' ? error.status : 404
+ if (error.cause !== undefined) this.cause = error.cause
+ Object.setPrototypeOf(this, WebrpcBadRouteError.prototype)
+ }
+}
+
+export class WebrpcBadMethodError extends WebrpcError {
+ constructor(error: WebrpcErrorParams = {}) {
+ super(error)
+ this.name = error.name || 'WebrpcBadMethod'
+ this.code = typeof error.code === 'number' ? error.code : -3
+ this.message = error.message || `bad method`
+ this.status = typeof error.status === 'number' ? error.status : 405
+ if (error.cause !== undefined) this.cause = error.cause
+ Object.setPrototypeOf(this, WebrpcBadMethodError.prototype)
+ }
+}
+
+export class WebrpcBadRequestError extends WebrpcError {
+ constructor(error: WebrpcErrorParams = {}) {
+ super(error)
+ this.name = error.name || 'WebrpcBadRequest'
+ this.code = typeof error.code === 'number' ? error.code : -4
+ this.message = error.message || `bad request`
+ this.status = typeof error.status === 'number' ? error.status : 400
+ if (error.cause !== undefined) this.cause = error.cause
+ Object.setPrototypeOf(this, WebrpcBadRequestError.prototype)
+ }
+}
+
+export class WebrpcBadResponseError extends WebrpcError {
+ constructor(error: WebrpcErrorParams = {}) {
+ super(error)
+ this.name = error.name || 'WebrpcBadResponse'
+ this.code = typeof error.code === 'number' ? error.code : -5
+ this.message = error.message || `bad response`
+ this.status = typeof error.status === 'number' ? error.status : 500
+ if (error.cause !== undefined) this.cause = error.cause
+ Object.setPrototypeOf(this, WebrpcBadResponseError.prototype)
+ }
+}
+
+export class WebrpcServerPanicError extends WebrpcError {
+ constructor(error: WebrpcErrorParams = {}) {
+ super(error)
+ this.name = error.name || 'WebrpcServerPanic'
+ this.code = typeof error.code === 'number' ? error.code : -6
+ this.message = error.message || `server panic`
+ this.status = typeof error.status === 'number' ? error.status : 500
+ if (error.cause !== undefined) this.cause = error.cause
+ Object.setPrototypeOf(this, WebrpcServerPanicError.prototype)
+ }
+}
+
+export class WebrpcInternalErrorError extends WebrpcError {
+ constructor(error: WebrpcErrorParams = {}) {
+ super(error)
+ this.name = error.name || 'WebrpcInternalError'
+ this.code = typeof error.code === 'number' ? error.code : -7
+ this.message = error.message || `internal error`
+ this.status = typeof error.status === 'number' ? error.status : 500
+ if (error.cause !== undefined) this.cause = error.cause
+ Object.setPrototypeOf(this, WebrpcInternalErrorError.prototype)
+ }
+}
+
+export class WebrpcClientAbortedError extends WebrpcError {
+ constructor(error: WebrpcErrorParams = {}) {
+ super(error)
+ this.name = error.name || 'WebrpcClientAborted'
+ this.code = typeof error.code === 'number' ? error.code : -8
+ this.message = error.message || `request aborted by client`
+ this.status = typeof error.status === 'number' ? error.status : 400
+ if (error.cause !== undefined) this.cause = error.cause
+ Object.setPrototypeOf(this, WebrpcClientAbortedError.prototype)
+ }
+}
+
+export class WebrpcStreamLostError extends WebrpcError {
+ constructor(error: WebrpcErrorParams = {}) {
+ super(error)
+ this.name = error.name || 'WebrpcStreamLost'
+ this.code = typeof error.code === 'number' ? error.code : -9
+ this.message = error.message || `stream lost`
+ this.status = typeof error.status === 'number' ? error.status : 400
+ if (error.cause !== undefined) this.cause = error.cause
+ Object.setPrototypeOf(this, WebrpcStreamLostError.prototype)
+ }
+}
+
+export class WebrpcStreamFinishedError extends WebrpcError {
+ constructor(error: WebrpcErrorParams = {}) {
+ super(error)
+ this.name = error.name || 'WebrpcStreamFinished'
+ this.code = typeof error.code === 'number' ? error.code : -10
+ this.message = error.message || `stream finished`
+ this.status = typeof error.status === 'number' ? error.status : 200
+ if (error.cause !== undefined) this.cause = error.cause
+ Object.setPrototypeOf(this, WebrpcStreamFinishedError.prototype)
+ }
+}
+
+//
+// Schema errors
+//
+
+export class UnauthorizedError extends WebrpcError {
+ constructor(error: WebrpcErrorParams = {}) {
+ super(error)
+ this.name = error.name || 'Unauthorized'
+ this.code = typeof error.code === 'number' ? error.code : 1000
+ this.message = error.message || `Unauthorized access`
+ this.status = typeof error.status === 'number' ? error.status : 401
+ if (error.cause !== undefined) this.cause = error.cause
+ Object.setPrototypeOf(this, UnauthorizedError.prototype)
+ }
+}
+
+export class PermissionDeniedError extends WebrpcError {
+ constructor(error: WebrpcErrorParams = {}) {
+ super(error)
+ this.name = error.name || 'PermissionDenied'
+ this.code = typeof error.code === 'number' ? error.code : 1001
+ this.message = error.message || `Permission denied`
+ this.status = typeof error.status === 'number' ? error.status : 403
+ if (error.cause !== undefined) this.cause = error.cause
+ Object.setPrototypeOf(this, PermissionDeniedError.prototype)
+ }
+}
+
+export class SessionExpiredError extends WebrpcError {
+ constructor(error: WebrpcErrorParams = {}) {
+ super(error)
+ this.name = error.name || 'SessionExpired'
+ this.code = typeof error.code === 'number' ? error.code : 1002
+ this.message = error.message || `Session expired`
+ this.status = typeof error.status === 'number' ? error.status : 403
+ if (error.cause !== undefined) this.cause = error.cause
+ Object.setPrototypeOf(this, SessionExpiredError.prototype)
+ }
+}
+
+export class MethodNotFoundError extends WebrpcError {
+ constructor(error: WebrpcErrorParams = {}) {
+ super(error)
+ this.name = error.name || 'MethodNotFound'
+ this.code = typeof error.code === 'number' ? error.code : 1003
+ this.message = error.message || `Method not found`
+ this.status = typeof error.status === 'number' ? error.status : 404
+ if (error.cause !== undefined) this.cause = error.cause
+ Object.setPrototypeOf(this, MethodNotFoundError.prototype)
+ }
+}
+
+export class RequestConflictError extends WebrpcError {
+ constructor(error: WebrpcErrorParams = {}) {
+ super(error)
+ this.name = error.name || 'RequestConflict'
+ this.code = typeof error.code === 'number' ? error.code : 1004
+ this.message = error.message || `Conflict with target resource`
+ this.status = typeof error.status === 'number' ? error.status : 409
+ if (error.cause !== undefined) this.cause = error.cause
+ Object.setPrototypeOf(this, RequestConflictError.prototype)
+ }
+}
+
+export class AbortedError extends WebrpcError {
+ constructor(error: WebrpcErrorParams = {}) {
+ super(error)
+ this.name = error.name || 'Aborted'
+ this.code = typeof error.code === 'number' ? error.code : 1005
+ this.message = error.message || `Request aborted`
+ this.status = typeof error.status === 'number' ? error.status : 400
+ if (error.cause !== undefined) this.cause = error.cause
+ Object.setPrototypeOf(this, AbortedError.prototype)
+ }
+}
+
+export class GeoblockedError extends WebrpcError {
+ constructor(error: WebrpcErrorParams = {}) {
+ super(error)
+ this.name = error.name || 'Geoblocked'
+ this.code = typeof error.code === 'number' ? error.code : 1006
+ this.message = error.message || `Geoblocked region`
+ this.status = typeof error.status === 'number' ? error.status : 451
+ if (error.cause !== undefined) this.cause = error.cause
+ Object.setPrototypeOf(this, GeoblockedError.prototype)
+ }
+}
+
+export class RateLimitedError extends WebrpcError {
+ constructor(error: WebrpcErrorParams = {}) {
+ super(error)
+ this.name = error.name || 'RateLimited'
+ this.code = typeof error.code === 'number' ? error.code : 1007
+ this.message = error.message || `Rate-limited. Please slow down.`
+ this.status = typeof error.status === 'number' ? error.status : 429
+ if (error.cause !== undefined) this.cause = error.cause
+ Object.setPrototypeOf(this, RateLimitedError.prototype)
+ }
+}
+
+export class ProjectNotFoundError extends WebrpcError {
+ constructor(error: WebrpcErrorParams = {}) {
+ super(error)
+ this.name = error.name || 'ProjectNotFound'
+ this.code = typeof error.code === 'number' ? error.code : 1008
+ this.message = error.message || `Project not found`
+ this.status = typeof error.status === 'number' ? error.status : 401
+ if (error.cause !== undefined) this.cause = error.cause
+ Object.setPrototypeOf(this, ProjectNotFoundError.prototype)
+ }
+}
+
+export class InvalidArgumentError extends WebrpcError {
+ constructor(error: WebrpcErrorParams = {}) {
+ super(error)
+ this.name = error.name || 'InvalidArgument'
+ this.code = typeof error.code === 'number' ? error.code : 2000
+ this.message = error.message || `Invalid argument`
+ this.status = typeof error.status === 'number' ? error.status : 400
+ if (error.cause !== undefined) this.cause = error.cause
+ Object.setPrototypeOf(this, InvalidArgumentError.prototype)
+ }
+}
+
+export class UnavailableError extends WebrpcError {
+ constructor(error: WebrpcErrorParams = {}) {
+ super(error)
+ this.name = error.name || 'Unavailable'
+ this.code = typeof error.code === 'number' ? error.code : 2002
+ this.message = error.message || `Unavailable resource`
+ this.status = typeof error.status === 'number' ? error.status : 400
+ if (error.cause !== undefined) this.cause = error.cause
+ Object.setPrototypeOf(this, UnavailableError.prototype)
+ }
+}
+
+export class QueryFailedError extends WebrpcError {
+ constructor(error: WebrpcErrorParams = {}) {
+ super(error)
+ this.name = error.name || 'QueryFailed'
+ this.code = typeof error.code === 'number' ? error.code : 2003
+ this.message = error.message || `Query failed`
+ this.status = typeof error.status === 'number' ? error.status : 400
+ if (error.cause !== undefined) this.cause = error.cause
+ Object.setPrototypeOf(this, QueryFailedError.prototype)
+ }
+}
+
+export class NotFoundError extends WebrpcError {
+ constructor(error: WebrpcErrorParams = {}) {
+ super(error)
+ this.name = error.name || 'NotFound'
+ this.code = typeof error.code === 'number' ? error.code : 3000
+ this.message = error.message || `Resource not found`
+ this.status = typeof error.status === 'number' ? error.status : 400
+ if (error.cause !== undefined) this.cause = error.cause
+ Object.setPrototypeOf(this, NotFoundError.prototype)
+ }
+}
+
+export class UnsupportedNetworkError extends WebrpcError {
+ constructor(error: WebrpcErrorParams = {}) {
+ super(error)
+ this.name = error.name || 'UnsupportedNetwork'
+ this.code = typeof error.code === 'number' ? error.code : 3008
+ this.message = error.message || `Unsupported network`
+ this.status = typeof error.status === 'number' ? error.status : 422
+ if (error.cause !== undefined) this.cause = error.cause
+ Object.setPrototypeOf(this, UnsupportedNetworkError.prototype)
+ }
+}
+
+export enum errors {
+ WebrpcEndpoint = 'WebrpcEndpoint',
+ WebrpcRequestFailed = 'WebrpcRequestFailed',
+ WebrpcBadRoute = 'WebrpcBadRoute',
+ WebrpcBadMethod = 'WebrpcBadMethod',
+ WebrpcBadRequest = 'WebrpcBadRequest',
+ WebrpcBadResponse = 'WebrpcBadResponse',
+ WebrpcServerPanic = 'WebrpcServerPanic',
+ WebrpcInternalError = 'WebrpcInternalError',
+ WebrpcClientAborted = 'WebrpcClientAborted',
+ WebrpcStreamLost = 'WebrpcStreamLost',
+ WebrpcStreamFinished = 'WebrpcStreamFinished',
+ Unauthorized = 'Unauthorized',
+ PermissionDenied = 'PermissionDenied',
+ SessionExpired = 'SessionExpired',
+ MethodNotFound = 'MethodNotFound',
+ RequestConflict = 'RequestConflict',
+ Aborted = 'Aborted',
+ Geoblocked = 'Geoblocked',
+ RateLimited = 'RateLimited',
+ ProjectNotFound = 'ProjectNotFound',
+ InvalidArgument = 'InvalidArgument',
+ Unavailable = 'Unavailable',
+ QueryFailed = 'QueryFailed',
+ NotFound = 'NotFound',
+ UnsupportedNetwork = 'UnsupportedNetwork',
+}
+
+export enum WebrpcErrorCodes {
+ WebrpcEndpoint = 0,
+ WebrpcRequestFailed = -1,
+ WebrpcBadRoute = -2,
+ WebrpcBadMethod = -3,
+ WebrpcBadRequest = -4,
+ WebrpcBadResponse = -5,
+ WebrpcServerPanic = -6,
+ WebrpcInternalError = -7,
+ WebrpcClientAborted = -8,
+ WebrpcStreamLost = -9,
+ WebrpcStreamFinished = -10,
+ Unauthorized = 1000,
+ PermissionDenied = 1001,
+ SessionExpired = 1002,
+ MethodNotFound = 1003,
+ RequestConflict = 1004,
+ Aborted = 1005,
+ Geoblocked = 1006,
+ RateLimited = 1007,
+ ProjectNotFound = 1008,
+ InvalidArgument = 2000,
+ Unavailable = 2002,
+ QueryFailed = 2003,
+ NotFound = 3000,
+ UnsupportedNetwork = 3008,
+}
+
+export const webrpcErrorByCode: { [code: number]: any } = {
+ [0]: WebrpcEndpointError,
+ [-1]: WebrpcRequestFailedError,
+ [-2]: WebrpcBadRouteError,
+ [-3]: WebrpcBadMethodError,
+ [-4]: WebrpcBadRequestError,
+ [-5]: WebrpcBadResponseError,
+ [-6]: WebrpcServerPanicError,
+ [-7]: WebrpcInternalErrorError,
+ [-8]: WebrpcClientAbortedError,
+ [-9]: WebrpcStreamLostError,
+ [-10]: WebrpcStreamFinishedError,
+ [1000]: UnauthorizedError,
+ [1001]: PermissionDeniedError,
+ [1002]: SessionExpiredError,
+ [1003]: MethodNotFoundError,
+ [1004]: RequestConflictError,
+ [1005]: AbortedError,
+ [1006]: GeoblockedError,
+ [1007]: RateLimitedError,
+ [1008]: ProjectNotFoundError,
+ [2000]: InvalidArgumentError,
+ [2002]: UnavailableError,
+ [2003]: QueryFailedError,
+ [3000]: NotFoundError,
+ [3008]: UnsupportedNetworkError,
+}
+
+//
+// Webrpc
+//
+
+export const WebrpcHeader = 'Webrpc'
+
+export const WebrpcHeaderValue = 'webrpc@v0.30.2;gen-typescript@v0.22.2;userdata@v0.1.0'
+
+type WebrpcGenVersions = {
+ WebrpcGenVersion: string
+ codeGenName: string
+ codeGenVersion: string
+ schemaName: string
+ schemaVersion: string
+}
+
+export function VersionFromHeader(headers: Headers): WebrpcGenVersions {
+ const headerValue = headers.get(WebrpcHeader)
+ if (!headerValue) {
+ return {
+ WebrpcGenVersion: '',
+ codeGenName: '',
+ codeGenVersion: '',
+ schemaName: '',
+ schemaVersion: '',
+ }
+ }
+
+ return parseWebrpcGenVersions(headerValue)
+}
+
+function parseWebrpcGenVersions(header: string): WebrpcGenVersions {
+ const versions = header.split(';')
+ if (versions.length < 3) {
+ return {
+ WebrpcGenVersion: '',
+ codeGenName: '',
+ codeGenVersion: '',
+ schemaName: '',
+ schemaVersion: '',
+ }
+ }
+
+ const [_, WebrpcGenVersion] = versions[0]!.split('@')
+ const [codeGenName, codeGenVersion] = versions[1]!.split('@')
+ const [schemaName, schemaVersion] = versions[2]!.split('@')
+
+ return {
+ WebrpcGenVersion: WebrpcGenVersion ?? '',
+ codeGenName: codeGenName ?? '',
+ codeGenVersion: codeGenVersion ?? '',
+ schemaName: schemaName ?? '',
+ schemaVersion: schemaVersion ?? '',
+ }
+}
diff --git a/packages/services/userdata/tsconfig.json b/packages/services/userdata/tsconfig.json
new file mode 100644
index 000000000..fed9c77b4
--- /dev/null
+++ b/packages/services/userdata/tsconfig.json
@@ -0,0 +1,10 @@
+{
+ "extends": "@repo/typescript-config/base.json",
+ "compilerOptions": {
+ "rootDir": "src",
+ "outDir": "dist",
+ "types": ["node"]
+ },
+ "include": ["src"],
+ "exclude": ["node_modules", "dist"]
+}
diff --git a/packages/wallet/core/src/relayer/standard/rpc/relayer.gen.ts b/packages/wallet/core/src/relayer/standard/rpc/relayer.gen.ts
index a9e6b4440..bef2d4028 100644
--- a/packages/wallet/core/src/relayer/standard/rpc/relayer.gen.ts
+++ b/packages/wallet/core/src/relayer/standard/rpc/relayer.gen.ts
@@ -1,9 +1,15 @@
/* eslint-disable */
+<<<<<<<< HEAD:packages/wallet/core/src/relayer/standard/rpc/relayer.gen.ts
// sequence-relayer v0.4.1 62fe2b49d57c4a0d3960ac1176d48ecfffc7af5a
// --
// Code generated by webrpc-gen@v0.26.0 with typescript generator. DO NOT EDIT.
+========
+// sequence-relayer v0.4.1 13cf0e854e8127ae83218cc188ef0e7456241c96
+// --
+// Code generated by webrpc-gen@v0.12.x-dev with typescript@v0.10.0 generator. DO NOT EDIT.
+>>>>>>>> codesandbox:packages/relayer/src/rpc-relayer/relayer.gen.ts
//
-// webrpc-gen -schema=relayer.ridl -target=typescript -client -out=./clients/relayer.gen.ts
+// webrpc-gen -schema=relayer.ridl -target=typescript@v0.10.0 -client -out=./clients/relayer.gen.ts
export const WebrpcHeader = 'Webrpc'
@@ -16,6 +22,7 @@ export const WebRPCVersion = 'v1'
export const WebRPCSchemaVersion = 'v0.4.1'
// Schema hash generated from your RIDL schema
+<<<<<<<< HEAD:packages/wallet/core/src/relayer/standard/rpc/relayer.gen.ts
export const WebRPCSchemaHash = '62fe2b49d57c4a0d3960ac1176d48ecfffc7af5a'
type WebrpcGenVersions = {
@@ -65,6 +72,9 @@ function parseWebrpcGenVersions(header: string): WebrpcGenVersions {
schemaVersion: schemaVersion ?? '',
}
}
+========
+export const WebRPCSchemaHash = '13cf0e854e8127ae83218cc188ef0e7456241c96'
+>>>>>>>> codesandbox:packages/relayer/src/rpc-relayer/relayer.gen.ts
//
// Types
@@ -80,7 +90,6 @@ export enum ETHTxnStatus {
FAILED = 'FAILED',
PENDING_PRECONDITION = 'PENDING_PRECONDITION',
}
-
export enum TransferType {
SEND = 'SEND',
RECEIVE = 'RECEIVE',
@@ -98,13 +107,11 @@ export enum SimulateStatus {
REVERTED = 'REVERTED',
NOT_ENOUGH_GAS = 'NOT_ENOUGH_GAS',
}
-
export enum FeeTokenType {
UNKNOWN = 'UNKNOWN',
ERC20_TOKEN = 'ERC20_TOKEN',
ERC1155_TOKEN = 'ERC1155_TOKEN',
}
-
export enum SortOrder {
DESC = 'DESC',
ASC = 'ASC',
@@ -124,8 +131,11 @@ export interface RuntimeStatus {
ver: string
branch: string
commitHash: string
+<<<<<<<< HEAD:packages/wallet/core/src/relayer/standard/rpc/relayer.gen.ts
chainID: number
useEIP1559: boolean
+========
+>>>>>>>> codesandbox:packages/relayer/src/rpc-relayer/relayer.gen.ts
senders: Array
checks: RuntimeChecks
}
@@ -345,6 +355,7 @@ export interface SortBy {
}
export interface Relayer {
+<<<<<<<< HEAD:packages/wallet/core/src/relayer/standard/rpc/relayer.gen.ts
ping(headers?: object, signal?: AbortSignal): Promise
version(headers?: object, signal?: AbortSignal): Promise
runtimeStatus(headers?: object, signal?: AbortSignal): Promise
@@ -444,6 +455,42 @@ export interface Relayer {
args: ListGasTankBalanceAdjustmentsArgs,
headers?: object,
signal?: AbortSignal,
+========
+ ping(headers?: object): Promise
+ version(headers?: object): Promise
+ runtimeStatus(headers?: object): Promise
+ getSequenceContext(headers?: object): Promise
+ getChainID(headers?: object): Promise
+ sendMetaTxn(args: SendMetaTxnArgs, headers?: object): Promise
+ getMetaTxnNonce(args: GetMetaTxnNonceArgs, headers?: object): Promise
+ getMetaTxnReceipt(args: GetMetaTxnReceiptArgs, headers?: object): Promise
+ simulate(args: SimulateArgs, headers?: object): Promise
+ updateMetaTxnGasLimits(args: UpdateMetaTxnGasLimitsArgs, headers?: object): Promise
+ feeTokens(headers?: object): Promise
+ feeOptions(args: FeeOptionsArgs, headers?: object): Promise
+ getMetaTxnNetworkFeeOptions(args: GetMetaTxnNetworkFeeOptionsArgs, headers?: object): Promise
+ getMetaTransactions(args: GetMetaTransactionsArgs, headers?: object): Promise
+ sentTransactions(args: SentTransactionsArgs, headers?: object): Promise
+ pendingTransactions(args: PendingTransactionsArgs, headers?: object): Promise
+ getGasTank(args: GetGasTankArgs, headers?: object): Promise
+ addGasTank(args: AddGasTankArgs, headers?: object): Promise
+ updateGasTank(args: UpdateGasTankArgs, headers?: object): Promise
+ getGasSponsor(args: GetGasSponsorArgs, headers?: object): Promise
+ listGasSponsors(args: ListGasSponsorsArgs, headers?: object): Promise
+ addGasSponsor(args: AddGasSponsorArgs, headers?: object): Promise
+ updateGasSponsor(args: UpdateGasSponsorArgs, headers?: object): Promise
+ removeGasSponsor(args: RemoveGasSponsorArgs, headers?: object): Promise
+ reportGasSponsorUsage(args: ReportGasSponsorUsageArgs, headers?: object): Promise
+ nextGasTankBalanceAdjustmentNonce(
+ args: NextGasTankBalanceAdjustmentNonceArgs,
+ headers?: object
+ ): Promise
+ adjustGasTankBalance(args: AdjustGasTankBalanceArgs, headers?: object): Promise
+ getGasTankBalanceAdjustment(args: GetGasTankBalanceAdjustmentArgs, headers?: object): Promise
+ listGasTankBalanceAdjustments(
+ args: ListGasTankBalanceAdjustmentsArgs,
+ headers?: object
+>>>>>>>> codesandbox:packages/relayer/src/rpc-relayer/relayer.gen.ts
): Promise
/**
* Gas Sponsorship
@@ -641,6 +688,65 @@ export interface UpdateGasTankReturn {
status: boolean
gasTank: GasTank
}
+<<<<<<<< HEAD:packages/wallet/core/src/relayer/standard/rpc/relayer.gen.ts
+========
+export interface GetGasSponsorArgs {
+ id: number
+}
+
+export interface GetGasSponsorReturn {
+ gasSponsor: GasSponsor
+}
+export interface ListGasSponsorsArgs {
+ projectId: number
+ gasTankId: number
+ page?: Page
+}
+
+export interface ListGasSponsorsReturn {
+ page: Page
+ gasSponsors: Array
+}
+export interface AddGasSponsorArgs {
+ projectId: number
+ gasTankId: number
+ address: string
+ name?: string
+ active?: boolean
+}
+
+export interface AddGasSponsorReturn {
+ status: boolean
+ gasSponsor: GasSponsor
+}
+export interface UpdateGasSponsorArgs {
+ id: number
+ name?: string
+ active?: boolean
+}
+
+export interface UpdateGasSponsorReturn {
+ status: boolean
+ gasSponsor: GasSponsor
+}
+export interface RemoveGasSponsorArgs {
+ id: number
+}
+
+export interface RemoveGasSponsorReturn {
+ status: boolean
+}
+export interface ReportGasSponsorUsageArgs {
+ projectId: number
+ gasTankId: number
+ startTime?: string
+ endTime?: string
+}
+
+export interface ReportGasSponsorUsageReturn {
+ gasSponsorUsage: Array
+}
+>>>>>>>> codesandbox:packages/relayer/src/rpc-relayer/relayer.gen.ts
export interface NextGasTankBalanceAdjustmentNonceArgs {
id: number
}
@@ -765,6 +871,7 @@ export class Relayer implements Relayer {
return this.hostname + this.path + name
}
+<<<<<<<< HEAD:packages/wallet/core/src/relayer/standard/rpc/relayer.gen.ts
ping = (headers?: object, signal?: AbortSignal): Promise => {
return this.fetch(this.url('Ping'), createHTTPRequest({}, headers, signal)).then(
(res) => {
@@ -974,10 +1081,135 @@ export class Relayer implements Relayer {
throw WebrpcRequestFailedError.new({ cause: `fetch(): ${error.message || ''}` })
},
)
+========
+ ping = (headers?: object): Promise => {
+ return this.fetch(this.url('Ping'), createHTTPRequest({}, headers)).then(res => {
+ return buildResponse(res).then(_data => {
+ return {
+ status: _data.status
+ }
+ })
+ })
+ }
+
+ version = (headers?: object): Promise => {
+ return this.fetch(this.url('Version'), createHTTPRequest({}, headers)).then(res => {
+ return buildResponse(res).then(_data => {
+ return {
+ version: _data.version
+ }
+ })
+ })
+ }
+
+ runtimeStatus = (headers?: object): Promise => {
+ return this.fetch(this.url('RuntimeStatus'), createHTTPRequest({}, headers)).then(res => {
+ return buildResponse(res).then(_data => {
+ return {
+ status: _data.status
+ }
+ })
+ })
+ }
+
+ getSequenceContext = (headers?: object): Promise => {
+ return this.fetch(this.url('GetSequenceContext'), createHTTPRequest({}, headers)).then(res => {
+ return buildResponse(res).then(_data => {
+ return {
+ data: _data.data
+ }
+ })
+ })
+ }
+
+ getChainID = (headers?: object): Promise => {
+ return this.fetch(this.url('GetChainID'), createHTTPRequest({}, headers)).then(res => {
+ return buildResponse(res).then(_data => {
+ return {
+ chainID: _data.chainID
+ }
+ })
+ })
+ }
+
+ sendMetaTxn = (args: SendMetaTxnArgs, headers?: object): Promise => {
+ return this.fetch(this.url('SendMetaTxn'), createHTTPRequest(args, headers)).then(res => {
+ return buildResponse(res).then(_data => {
+ return {
+ status: _data.status,
+ txnHash: _data.txnHash
+ }
+ })
+ })
+ }
+
+ getMetaTxnNonce = (args: GetMetaTxnNonceArgs, headers?: object): Promise => {
+ return this.fetch(this.url('GetMetaTxnNonce'), createHTTPRequest(args, headers)).then(res => {
+ return buildResponse(res).then(_data => {
+ return {
+ nonce: _data.nonce
+ }
+ })
+ })
+ }
+
+ getMetaTxnReceipt = (args: GetMetaTxnReceiptArgs, headers?: object): Promise => {
+ return this.fetch(this.url('GetMetaTxnReceipt'), createHTTPRequest(args, headers)).then(res => {
+ return buildResponse(res).then(_data => {
+ return {
+ receipt: _data.receipt
+ }
+ })
+ })
+ }
+
+ simulate = (args: SimulateArgs, headers?: object): Promise => {
+ return this.fetch(this.url('Simulate'), createHTTPRequest(args, headers)).then(res => {
+ return buildResponse(res).then(_data => {
+ return {
+ results: >_data.results
+ }
+ })
+ })
+ }
+
+ updateMetaTxnGasLimits = (args: UpdateMetaTxnGasLimitsArgs, headers?: object): Promise => {
+ return this.fetch(this.url('UpdateMetaTxnGasLimits'), createHTTPRequest(args, headers)).then(res => {
+ return buildResponse(res).then(_data => {
+ return {
+ payload: _data.payload
+ }
+ })
+ })
+ }
+
+ feeTokens = (headers?: object): Promise => {
+ return this.fetch(this.url('FeeTokens'), createHTTPRequest({}, headers)).then(res => {
+ return buildResponse(res).then(_data => {
+ return {
+ isFeeRequired: _data.isFeeRequired,
+ tokens: >_data.tokens
+ }
+ })
+ })
+ }
+
+ feeOptions = (args: FeeOptionsArgs, headers?: object): Promise => {
+ return this.fetch(this.url('FeeOptions'), createHTTPRequest(args, headers)).then(res => {
+ return buildResponse(res).then(_data => {
+ return {
+ options: >_data.options,
+ sponsored: _data.sponsored,
+ quote: _data.quote
+ }
+ })
+ })
+>>>>>>>> codesandbox:packages/relayer/src/rpc-relayer/relayer.gen.ts
}
getMetaTxnNetworkFeeOptions = (
args: GetMetaTxnNetworkFeeOptionsArgs,
+<<<<<<<< HEAD:packages/wallet/core/src/relayer/standard/rpc/relayer.gen.ts
headers?: object,
signal?: AbortSignal,
): Promise => {
@@ -1119,10 +1351,150 @@ export class Relayer implements Relayer {
throw WebrpcRequestFailedError.new({ cause: `fetch(): ${error.message || ''}` })
},
)
+========
+ headers?: object
+ ): Promise => {
+ return this.fetch(this.url('GetMetaTxnNetworkFeeOptions'), createHTTPRequest(args, headers)).then(res => {
+ return buildResponse(res).then(_data => {
+ return {
+ options: >_data.options
+ }
+ })
+ })
+ }
+
+ getMetaTransactions = (args: GetMetaTransactionsArgs, headers?: object): Promise => {
+ return this.fetch(this.url('GetMetaTransactions'), createHTTPRequest(args, headers)).then(res => {
+ return buildResponse(res).then(_data => {
+ return {
+ page: _data.page,
+ transactions: >_data.transactions
+ }
+ })
+ })
+ }
+
+ sentTransactions = (args: SentTransactionsArgs, headers?: object): Promise => {
+ return this.fetch(this.url('SentTransactions'), createHTTPRequest(args, headers)).then(res => {
+ return buildResponse(res).then(_data => {
+ return {
+ page: _data.page,
+ transactions: >_data.transactions
+ }
+ })
+ })
+ }
+
+ pendingTransactions = (args: PendingTransactionsArgs, headers?: object): Promise => {
+ return this.fetch(this.url('PendingTransactions'), createHTTPRequest(args, headers)).then(res => {
+ return buildResponse(res).then(_data => {
+ return {
+ page: _data.page,
+ transactions: >_data.transactions
+ }
+ })
+ })
+ }
+
+ getGasTank = (args: GetGasTankArgs, headers?: object): Promise => {
+ return this.fetch(this.url('GetGasTank'), createHTTPRequest(args, headers)).then(res => {
+ return buildResponse(res).then(_data => {
+ return {
+ gasTank: _data.gasTank
+ }
+ })
+ })
+ }
+
+ addGasTank = (args: AddGasTankArgs, headers?: object): Promise => {
+ return this.fetch(this.url('AddGasTank'), createHTTPRequest(args, headers)).then(res => {
+ return buildResponse(res).then(_data => {
+ return {
+ status: _data.status,
+ gasTank: _data.gasTank
+ }
+ })
+ })
+ }
+
+ updateGasTank = (args: UpdateGasTankArgs, headers?: object): Promise => {
+ return this.fetch(this.url('UpdateGasTank'), createHTTPRequest(args, headers)).then(res => {
+ return buildResponse(res).then(_data => {
+ return {
+ status: _data.status,
+ gasTank: _data.gasTank
+ }
+ })
+ })
+ }
+
+ getGasSponsor = (args: GetGasSponsorArgs, headers?: object): Promise => {
+ return this.fetch(this.url('GetGasSponsor'), createHTTPRequest(args, headers)).then(res => {
+ return buildResponse(res).then(_data => {
+ return {
+ gasSponsor: _data.gasSponsor
+ }
+ })
+ })
+ }
+
+ listGasSponsors = (args: ListGasSponsorsArgs, headers?: object): Promise => {
+ return this.fetch(this.url('ListGasSponsors'), createHTTPRequest(args, headers)).then(res => {
+ return buildResponse(res).then(_data => {
+ return {
+ page: