import * as Mppx from 'mppx' import { tempo } from 'mppx/server' import { maxUint256 } from 'viem' import type { LocalAccount } from 'viem/accounts' import * as Store from '../../../internal/Store.js' import * as Viem from '../../../internal/Viem.js' import * as Mpp from '../Mpp.js' import * as MppError from './Error.js' import * as Screening from './Screening.js' import * as ScreenManager from './ScreenManager.js' import * as Utils from './Utils.js' /** MPPX lifecycle adapter. */ export type Adapter = { /** Broadcasts a validated MPP credential. */ broadcast(input: Mpp.RelayInput): Promise /** Validates an MPP credential without broadcasting it. */ verify(input: Mpp.RelayInput): Promise } /** Creates an adapter for Tempo charge credentials. */ export function create(options: create.Options): Adapter { const { chainId, feePayer, getClient, provider = Screening.allowAll, reportUnknown, screeningStore, screeningTtl = Screening.defaultTtl, state, time = (_, fn) => fn(), } = options const screenManager = ScreenManager.create({ chainId, provider, store: screeningStore, ttl: screeningTtl, }) // Moderato fees exceed MPPX defaults; preserve its gas cap and bound each // sponsored transaction to $1. const feePayerPolicy = chainId === Viem.chainId.testnet ? { maxFeePerGas: maxUint256, maxPriorityFeePerGas: maxUint256, maxTotalFee: 1_000_000_000_000_000_000n, } : undefined const methods = [ tempo.charge({ ...(feePayer ? { feePayer } : {}), ...(feePayerPolicy ? { feePayerPolicy } : {}), getClient: ({ chainId: chainId_option }) => getClient(chainId_option as Viem.ChainId | undefined), store: Utils.mppxStore(state), }), ] as const /** Validates and screens a credential, rejecting when either check fails. */ async function validateOrThrow(input: Mpp.RelayInput) { const result = await time('mpp_validate', () => Mppx.Method.validateCredential(methods, credential(input)), ) await time('mpp_screen', () => screenManager.screen(Utils.addresses(result))) } function failure(cause: unknown, operation: 'broadcast' | 'verify') { const error = MppError.map(cause) if (error.code === Mpp.relayErrorCode.unknown) reportUnknown?.({ operation }) return { error, success: false } as const } return { async broadcast(input) { try { await validateOrThrow(input) const result = await time('mpp_broadcast', () => Mppx.Method.broadcastCredential(methods, credential(input)), ) return { receipt: { ...(result.externalId ? { externalId: result.externalId } : {}), method: result.method, reference: result.reference, timestamp: result.timestamp, }, success: true, } } catch (cause) { return failure(cause, 'broadcast') } }, async verify(input) { try { await validateOrThrow(input) return { success: true } } catch (cause) { return failure(cause, 'verify') } }, } } function credential(input: Mpp.RelayInput) { return Mppx.Credential.from({ challenge: input.challenge, payload: input.payload, ...(input.source === undefined ? {} : { source: input.source }), }) } export declare namespace create { /** Dependencies used to validate and broadcast Tempo credentials. */ type Options = { /** Default Tempo chain for challenges that omit a chain id. */ chainId: number /** Optional account used to sponsor Tempo pull transactions. */ feePayer?: LocalAccount | undefined /** Resolves a Tempo client for each credential chain. */ getClient: Viem.GetClient /** Address-screening provider. */ provider?: Screening.Provider | undefined /** Reports an unexpected relay failure without passing through its cause. */ reportUnknown?: ((options: { operation: 'broadcast' | 'verify' }) => void) | undefined /** Cache used for screening results. */ screeningStore: Store.Store /** Screening-cache duration. @default `Ttl.days(1)` */ screeningTtl?: number | undefined /** Durable state used for MPPX replay protection. */ state: Store.State /** Measures a named relay operation. */ time?: ((name: string, fn: () => Promise) => Promise) | undefined } }