import { Address, Base58, Bytes, Hash, Value as core_Value } from 'ox' import * as z from 'zod/mini' import * as core_Provider from '../Provider.js' import * as OpenApi from '../OpenApi.js' import * as Schema from '../Schema.js' import * as Value from '../Value.js' import * as Action from './Action.js' import type * as core_Catalog from './Catalog.js' import type * as core_Chain from './Chain.js' import type * as core_Route from './Route.js' import type * as core_Token from './Token.js' import type * as Transfer from './Transfer.js' /** Tempo token used when the caller omits `destinationToken`. */ const defaultDestinationToken = 'usdce' /** Funding provider schemas. */ export namespace schema { /** Bounded upstream error code safe for structured logs. */ export const ErrorCode = z.string().check(z.regex(/^[A-Za-z0-9_.:-]{1,64}$/)) /** Nested upstream error fields used to extract a stable code. */ export const Error = z.object({ code: z.optional(z.string()), type: z.optional(z.string()), }) /** Common upstream error body fields used to extract a stable code. */ export const ErrorBody = z.object({ _tag: z.optional(z.string()), code: z.optional(z.string()), error: z.optional(Error), errorCode: z.optional(z.string()), errorType: z.optional(z.string()), type: z.optional(z.string()), }) /** Positive decimal amount accepted by funding quote requests. */ export const PositiveDecimal = z.string().check(z.regex(/^(?=.*[1-9])\d+(\.\d+)?$/)) /** Positive base-unit amount accepted by funding quote requests. */ export const PositiveInteger = z.string().check(z.regex(/^(?=.*[1-9])\d+$/)) /** Normalized chain reference passed to funding providers. */ export const ChainRef = OpenApi.component( Schema.describe( z.object({ addressFormat: z .enum(['base58', 'base58check', 'hex']) .check( z.describe('Address encoding used by accounts and token identifiers on this chain.'), z.meta({ examples: ['hex'] }), ), id: z.string().check(z.describe('CAIP-2 chain id.'), z.meta({ examples: ['eip155:8453'] })), kind: z .enum(['evm', 'solana', 'tron']) .check( z.describe('Chain execution family used for provider routing.'), z.meta({ examples: ['evm'] }), ), name: z .string() .check(z.describe('Human-readable chain name.'), z.meta({ examples: ['Base'] })), }), 'A normalized chain reference for funding quotes.', ), 'FundingChain', ) /** Normalized token reference passed to funding providers. */ export const TokenRef = OpenApi.component( Schema.describe( z.object({ address: z .string() .check( z.describe('Contract address, mint, or issuer address on the token chain.'), z.meta({ examples: ['0x833589fcd6edb6e08f4c7c32d4f71b54bda02913'] }), ), currency: z .string() .check( z.describe('Monetary denomination represented by this token.'), z.meta({ examples: ['USD'] }), ), decimals: z .number() .check( z.int(), z.gte(0), z.describe('Number of decimal places this token uses.'), z.meta({ examples: [6] }), ), name: z .string() .check(z.describe('Human-readable token name.'), z.meta({ examples: ['USD Coin'] })), standard: z .string() .check( z.describe('Token standard on the token chain.'), z.meta({ examples: ['ERC-20'] }), ), symbol: z .string() .check(z.describe('Short token ticker symbol.'), z.meta({ examples: ['USDC'] })), tokenKey: z.string().check( z.describe('Stable Tempo token key scoped to the token chain.'), z.meta({ examples: ['eip155:8453/erc20:0x833589fcd6edb6e08f4c7c32d4f71b54bda02913'], }), ), verified: z .boolean() .check( z.describe('Whether Tempo recognizes this token in its funding quote inventory.'), z.meta({ examples: [true] }), ), }), 'A normalized token reference for funding quotes.', ), 'FundingToken', ) /** Provider identity embedded in funding resources. */ export const ProviderRef = OpenApi.component( Schema.describe( z.object({ id: z.string().check(z.describe('Stable provider id.'), z.meta({ examples: ['relay'] })), name: z .string() .check(z.describe('Human-readable provider name.'), z.meta({ examples: ['Relay'] })), }), 'Provider metadata embedded in a funding quote.', ), 'FundingProvider', ) /** Source amount unit accepted by a funding quote request. */ export const SourceAmountUnits = z ._default(z.enum(['baseUnits', 'decimal']), 'baseUnits') .check( z.describe('Whether `sourceAmount` is base units or a human-readable decimal.'), z.meta({ examples: ['baseUnits', 'decimal'] }), ) /** Normalized caller request passed to funding providers. */ export const QuoteRequest = z .strictObject({ destinationToken: z .optional(z.string()) .check( z.describe('Tempo destination token symbol, address, or token key. Defaults to USDC.e.'), z.meta({ examples: ['usdc.e'] }), ), provider: z .optional(z.string().check(z.minLength(1))) .check( z.describe( 'Only request a quote from this provider. Use an id returned by the funding providers endpoint, or omit it to query every available provider.', ), z.meta({ examples: ['relay'] }), ), sourceAmount: PositiveDecimal.check( z.describe('Positive source token amount. Base units by default, so 1 USDC is `1000000`.'), z.meta({ examples: ['1000000'] }), ), sourceAmountUnits: SourceAmountUnits, sourceChain: z .string() .check( z.minLength(1), z.describe( 'Source chain alias or stable id, such as `base`, `solana`, or `eip155:8453`.', ), z.meta({ examples: ['base', 'eip155:8453'] }), ), sourceToken: z .string() .check( z.minLength(1), z.describe( 'Source token symbol, contract address, or Tempo token key. Lookup is scoped to `sourceChain`; non-EVM addresses are case-sensitive. An unknown token returns an empty list.', ), z.meta({ examples: ['usdc', '0x833589fcd6edb6e08f4c7c32d4f71b54bda02913'] }), ), }) .check(z.describe('Query parameters for live funding quotes into Tempo.')) /** Provider-specific quote status for the requested amount. */ export const QuoteStatus = z.enum(['available', 'thin', 'unknown', 'unavailable']) /** Machine-readable provider quote quality tier. */ export const QualityTier = z.enum(['liquid', 'thin', 'unknown', 'unavailable']) /** Quote data returned by a funding provider. */ export const GetQuoteResult = Schema.describe( z.object({ expiresAt: z.optional(z.iso.datetime()), destinationAmountMin: z.optional(Schema.DecimalString), destinationAmount: z.optional(Schema.DecimalString), quality: z.optional( z.object({ estimatedSeconds: z.optional(z.number().check(z.int(), z.nonnegative())), liquiditySource: z.string(), sourceDetail: z.optional(z.string()), tier: z.optional(QualityTier), }), ), sampledAt: z.iso.datetime(), status: QuoteStatus, }), 'Quote data returned by a funding provider.', ) /** Executable preparation returned by a funding provider. */ export const PrepareResult = z.object({ action: Action.schema.Action, correlation: z.optional( z.object({ checkEndpoint: z.optional(z.string()), requestId: z.string().check(z.minLength(1)), }), ), destinationAmount: PositiveInteger, destinationAmountMin: PositiveInteger, expiresAt: z.optional(z.iso.datetime()), fees: z.array( z.object({ amount: Schema.DecimalString, side: z.enum(['destination', 'source']), token: z.optional(TokenRef), }), ), providerState: z.optional(z.record(z.string(), z.unknown())), quality: GetQuoteResult.shape.quality, sampledAt: z.iso.datetime(), }) /** Reusable deposit address provisioned by a funding provider. */ export const DepositAddressResult = z .object({ address: z .string() .check(z.minLength(1), z.describe('Reusable source-chain deposit address.')), correlation: z .object({ checkEndpoint: z .optional(z.string()) .check(z.describe('Provider status endpoint for the original address request.')), requestId: z .string() .check(z.minLength(1), z.describe('Provider-issued address request identifier.')), }) .check(z.describe('Private provider correlation retained for reconciliation.')), destinationAmount: PositiveInteger, destinationAmountMin: PositiveInteger, expiresAt: z.optional(z.iso.datetime()), fees: PrepareResult.shape.fees, sampledAt: z.iso.datetime(), }) .check(z.describe('Reusable deposit address and quoted delivery terms returned by a provider.')) /** One provider request discovered for a reusable deposit address. */ export const DepositAddressRequest = z.object({ createdAt: z.iso.datetime(), destinationTransactionHashes: z.array(z.string()), id: z.string().check(z.minLength(1)), providerState: z.optional(z.record(z.string(), z.unknown())), refundAmountExpected: z.optional(z.string().check(z.regex(/^\d+$/))), refundTransactionHashes: z.array(z.string()), sourceTransactionHashes: z.array(z.string()), status: z.string().check(z.minLength(1)), updatedAt: z.iso.datetime(), }) /** One page of provider requests discovered for a reusable address. */ export const DepositAddressRequestsResult = z.object({ continuation: z.optional(z.string().check(z.minLength(1))), requests: z.array(DepositAddressRequest), }) /** Quote quality signals exposed to funding API callers. */ export const FundingQuoteQuality = OpenApi.component( Schema.describe( z.object({ estimatedSeconds: z .optional(z.number().check(z.int(), z.nonnegative())) .check( z.describe('Estimated settlement time in seconds, when known.'), z.meta({ examples: [2] }), ), liquiditySource: z .string() .check( z.describe('Provider source for the live quote quality signal.'), z.meta({ examples: ['providerQuote'] }), ), sourceDetail: z .optional(z.string()) .check( z.describe('Provider-specific source or endpoint detail for the quality signal.'), z.meta({ examples: ['providerEndpoint'] }), ), tier: z .enum(['liquid', 'thin']) .check( z.describe('Machine-readable quote quality tier for a successful quote.'), z.meta({ examples: ['liquid'] }), ), }), 'Machine-readable liquidity, fee, and source signals for a successful quote.', ), 'FundingQuoteQuality', ) /** Provider quote freshness data exposed to funding API callers. */ export const QuoteDetails = OpenApi.component( Schema.describe( z.object({ expiresAt: z .optional(z.iso.datetime()) .check( z.describe('Timestamp after which the provider quote is no longer valid.'), z.meta({ examples: ['2026-01-01T00:01:00Z'] }), ), sampledAt: z.iso .datetime() .check( z.describe('Timestamp for the live quote request.'), z.meta({ examples: ['2026-01-01T00:00:00Z'] }), ), }), 'Freshness data for a provider funding quote.', ), 'FundingQuoteDetails', ) /** Successful normalized funding quote returned to callers. */ export const FundingQuote = OpenApi.component( Schema.describe( z.object({ destinationAmount: Schema.TokenAmount.check(z.describe('Expected destination amount.')), destinationAmountMin: z .optional(Schema.TokenAmount) .check( z.describe( 'Provider-derived minimum destination amount. Symbiosis and Squid quotes use a fixed 1% slippage tolerance.', ), ), destinationChain: ChainRef.check( z.describe('Destination chain, always Tempo mainnet in V1.'), ), destinationToken: TokenRef.check(z.describe('Tempo token expected from this quote.')), id: z .string() .check( z.describe('Stable deterministic funding quote id.'), z.meta({ examples: ['base-usdc-tempo-usdce-relay'] }), ), provider: ProviderRef, quality: FundingQuoteQuality, quote: QuoteDetails, sourceAmount: Schema.TokenAmount, sourceChain: ChainRef.check(z.describe('Chain where the caller currently holds funds.')), sourceToken: TokenRef.check(z.describe('Token the caller currently holds.')), }), 'One successful provider-specific funding quote.', ), 'FundingQuote', ) } /** Chain metadata declared by a funding route. */ export type Chain = core_Chain.Chain /** Token metadata declared by a funding route. */ export type Token = core_Token.Resolved /** Provider ids accepted by funding quote requests. */ export type ProviderId = string /** Provider-specific quote status for the requested amount. */ export type QuoteStatus = z.output /** Machine-readable quote quality tier. */ export type QualityTier = z.output /** Normalized chain reference passed to funding providers. */ export type ChainRef = z.output /** Normalized token amount passed to funding providers. */ export type TokenAmount = ReturnType /** Normalized token reference passed to funding providers. */ export type TokenRef = z.output /** A normalized internal candidate passed to funding quote providers. */ export type QuoteCandidate = ReturnType /** A normalized route candidate passed to deposit-address providers. */ export type DepositAddressCandidate = ReturnType /** A successful normalized funding quote returned to callers. */ export type FundingQuote = z.output /** Cache policy for a single funding quote request. */ export type CachePolicy = { /** Whether providers should bypass their own quote or inventory caches. */ bypass: boolean } /** Normalized caller request passed to funding providers. */ export type QuoteRequest = z.output /** Contract of the provider `getQuote` method. */ export declare namespace getQuote { /** Input passed to a funding provider for one curated quote candidate. */ type Parameters = { /** Curated candidate selected for live provider quoting. */ candidate: QuoteCandidate /** Cache policy for provider-side discovery and quote lookups. */ cache: CachePolicy /** Destination chain, always Tempo mainnet in V1. */ destinationChain: ChainRef /** Tempo token expected from this quote. */ destinationToken: TokenRef /** Normalized caller request that produced this quote candidate. */ request: QuoteRequest /** Canonical funding route selected for this quote candidate. */ route: core_Route.Route /** Source amount in base units and display form. */ sourceAmount: TokenAmount /** Chain where the caller currently holds funds. */ sourceChain: ChainRef /** Token the caller currently holds. */ sourceToken: TokenRef } /** Quote data returned by a funding provider. */ type ReturnType = z.output } /** Contract of the provider `prepareTransfer` method. */ export declare namespace prepareTransfer { /** Private provider correlation retained for tracking, never exposed publicly. */ type Correlation = { /** Provider status endpoint bound to this preparation, when returned. */ checkEndpoint?: string | undefined /** Provider-issued request identifier. */ requestId: string } /** Input passed to a funding provider preparing one executable transfer. */ type Parameters = { /** Curated candidate selected for preparation. */ candidate: QuoteCandidate /** Funding method the caller selected. */ method: Transfer.Method /** Amount mode the caller selected. */ mode: Transfer.Mode /** Final beneficiary on the destination chain. */ recipient: string /** Source-chain account that signs the funding action. */ sender: string /** Caller slippage bound in basis points. */ slippageBps?: number | undefined } /** Executable preparation returned by a funding provider. */ type ReturnType = z.output } /** Contract for provider-specific executable action validation. */ export declare namespace validateTransferAction { /** Prepared action and its curated route terms. */ type Parameters = prepareTransfer.Parameters & { /** Executable action returned by the provider. */ action: Action.Action } /** Provider-specific constraints established by validation. */ type ReturnType = { /** Exact native values independently validated against provider calldata. */ nativeValues?: readonly Action.NativeValue[] | undefined } } /** Contract for provider-specific source transaction verification. */ export declare namespace verifySourceTransaction { /** Stored transfer terms and submitted source transaction. */ type Parameters = { /** Configured chain that receives the provider delivery. */ destinationChain: Chain /** Private provider state captured while preparing the transfer. */ providerState?: Transfer.ProviderState | undefined /** Current configuration for the stored provider route. */ routeConfiguration?: core_Catalog.Configuration | undefined /** Configured chain containing the submitted transaction. */ sourceChain: Chain /** Submitted transaction reference. */ transactionHash: string /** Stored public transfer terms. */ transfer: Transfer.Snapshot } /** Source transaction verification outcome. */ type ReturnType = | { type: 'invalid' } | { type: 'pending' } | { /** Private provider evidence retained for reconciliation. */ providerState?: Transfer.ProviderState | undefined /** The transaction matches the stored transfer terms. */ type: 'verified' } } /** Contract of the provider `createDepositAddress` method. */ export declare namespace createDepositAddress { /** Input passed to a provider provisioning one reusable deposit address. */ type Parameters = { /** Source token amount in base units used for initial route provisioning. */ amount: string /** Curated reusable-address route selected for provisioning. */ candidate: DepositAddressCandidate /** Final beneficiary on Tempo. */ recipient: string /** Source-chain account that receives refunds. */ refundAddress: string /** Whether Tempo guarantees normalized 1:1 delivery. */ subsidize: boolean } /** Reusable address and private provider correlation. */ type ReturnType = z.output } /** Contract of the provider `listDepositAddressRequests` method. */ export declare namespace listDepositAddressRequests { /** Input passed to a provider when reconciling one reusable address. */ type Parameters = { /** Reusable provider deposit address. */ address: string /** Opaque provider cursor returned by the previous page. */ continuation?: string | undefined /** Existing provider request to refresh directly. */ requestId?: string | undefined } /** One page of normalized provider requests. */ type ReturnType = z.output } /** Contract of the provider `observeDepositAddressRequest` method. */ export declare namespace observeDepositAddressRequest { /** Verified source transfer used to correlate provider delivery. */ type Source = { /** Token quantity in base units. */ amount: string /** Verified source transaction reference. */ transactionHash: string } /** Input passed to a provider observing one pending reusable-address request. */ type Parameters = { /** Provider-owned reusable deposit address. */ address: string /** Chain that receives provider delivery. */ destinationChain: ChainRef /** Token delivered by the provider. */ destinationToken: TokenRef /** Bounded private provider correlation from an earlier observation. */ providerState?: Record | undefined /** Final beneficiary on the destination chain. */ recipient: string /** Source-chain account credited by the provider deposit. */ refundAddress: string /** Provider request, when the provider has indexed the source transfer. */ request?: listDepositAddressRequests.ReturnType['requests'][number] | undefined /** One verified source transfer received by the deposit address. */ source: Source /** Chain containing the source transfer. */ sourceChain: ChainRef /** Token received by the reusable address. */ sourceToken: TokenRef } /** Private provider evidence shared by pending and verified observations. */ type Evidence = { /** Bounded private provider correlation retained for reconciliation. */ providerState?: Record | undefined /** Verified provider-leg transaction references. */ providerTransactionHashes?: readonly string[] | undefined } /** Bounded provider delivery observation. */ type ReturnType = Evidence & ( | { /** No matching provider delivery has reached the destination yet. */ type: 'pending' } | { /** This provider or route cannot produce authenticated delivery observations. */ type: 'unsupported' } | { /** Verified destination transaction references. */ destinationTransactionHashes: readonly string[] /** Authenticated provider delivery was observed onchain. */ type: 'verified' } ) } /** Optional webhook capability implemented by a complete funding integration. */ export type Webhook = { /** Authenticates, resolves, and enqueues one provider status event. */ receive(input: Webhook.receive.Parameters): Promise /** Authenticates a provider status event and returns its bounded hint. */ verify(input: Webhook.verify.Parameters): Webhook.verify.ReturnType } /** Contracts for a funding provider webhook capability. */ export declare namespace Webhook { /** Contracts for receiving one authenticated provider webhook. */ namespace receive { /** Raw provider webhook request passed to a funding integration. */ type Parameters = { /** Exact request body bytes decoded as UTF-8. */ body: string /** Original provider webhook headers. */ headers: Headers } /** Durable queue message emitted for one authenticated funding status hint. */ type Dispatchable = { /** Stored funding deposit address id. */ addressId: string /** Bounded follow-up state for work awaiting provider or settlement confirmation. */ followUp?: FollowUp | undefined /** Reconciliation lease fencing token, when a scheduled poll already claimed the address. */ pollLeaseVersion?: number | undefined /** Optional provider request id that triggered reconciliation. */ requestId?: string | undefined /** Mechanism that requested reconciliation. */ trigger: 'chain' | 'manual' | 'poll' | 'webhook' /** Reconciliation queue message discriminator. */ type: 'funding:deposit-address:reconcile' /** Authenticated provider webhook timing carried into reconciliation. */ webhookTiming?: WebhookTiming | undefined } /** Bounded follow-up state carried between delayed reconciliation messages. */ type FollowUp = { /** Zero-based attempt in the current follow-up schedule. */ attempt: number /** Work that still awaits confirmation. */ kind: 'delivery' | 'provider' | 'settlement' } /** Authenticated provider webhook timing used for latency measurement. */ type WebhookTiming = { /** When Tempo sent the reconciliation message to its queue. */ enqueuedAt: string /** Bounded provider status carried by the webhook. */ providerStatus: string /** When the provider last changed the request status. */ providerUpdatedAt: string /** When Tempo started processing the webhook request. */ receivedAt: string /** When the provider sent the signed webhook. */ sentAt: string } /** Durable reconciliation message, including messages queued before trigger attribution. */ type Queued = Omit & { /** Mechanism that requested reconciliation, when written by a trigger-aware producer. */ trigger?: Dispatchable['trigger'] | undefined } /** Safe disposition of one authenticated funding provider webhook. */ type ReturnType = | { addressId: string; type: 'queued' } | { type: 'ignored' } | { type: 'unknown_address' } } /** Contracts for verifying one provider webhook. */ namespace verify { /** Raw provider webhook request passed to a funding integration. */ type Parameters = receive.Parameters /** Authenticated provider hint extracted from a funding webhook. */ type ReturnType = { /** Reusable provider address, when the provider attributed one. */ address?: string | undefined /** Bounded provider status carried by the webhook. */ providerStatus?: string | undefined /** When the provider last changed the request status. */ providerUpdatedAt?: string | undefined /** Provider request id, when included in the event. */ requestId?: string | undefined /** When the provider sent the signed webhook. */ sentAt?: string | undefined } } } /** A funding provider descriptor and live quote implementation. */ export type Provider = core_Provider.Provider & { /** Provisions a reusable funding deposit address, when supported. */ createDepositAddress?( input: createDepositAddress.Parameters, signal: AbortSignal, ): Promise /** Requests a live funding quote. */ getQuote(input: getQuote.Parameters, signal: AbortSignal): Promise /** Lists requests discovered for a reusable deposit address, when supported. */ listDepositAddressRequests?( input: listDepositAddressRequests.Parameters, signal: AbortSignal, ): Promise /** Observes authenticated delivery for a verified reusable-address transfer, when supported. */ observeDepositAddressRequest?( input: observeDepositAddressRequest.Parameters, signal: AbortSignal, ): Promise /** Prepares an executable funding transfer, when the provider supports it. */ prepareTransfer?( input: prepareTransfer.Parameters, signal: AbortSignal, ): Promise /** Validates provider-specific action semantics before generic persistence checks. */ validateTransferAction?( input: validateTransferAction.Parameters, ): validateTransferAction.ReturnType /** Verifies a submitted source transaction, when supported. */ verifySourceTransaction?( input: verifySourceTransaction.Parameters, signal: AbortSignal, ): Promise /** Receives authenticated provider status events, when configured. */ webhook?: Webhook | undefined } /** Defines a funding provider from its identity and quote implementation. */ export function from(options: from.Options): Provider { if (options.id !== normalizeInput(options.id) || options.id.length === 0) throw new ProviderConfigurationError('Provider ids must be lowercase and trimmed.') return core_Provider.from({ ...options, type: 'funding' }) } export declare namespace from { /** Funding provider definition. */ type Options = Omit, 'type'> } /** Narrows a registered provider to a funding provider. */ export function is(provider: core_Provider.Provider): provider is Provider { return ( provider.type === 'funding' && 'getQuote' in provider && typeof provider.getQuote === 'function' ) } /** Narrows a funding provider to one that prepares executable transfers. */ export function canPrepareTransfer(provider: Provider): provider is Provider & { prepareTransfer: NonNullable } { return typeof provider.prepareTransfer === 'function' } /** Narrows a funding provider to one that provisions reusable deposit addresses. */ export function canCreateDepositAddress(provider: Provider): provider is Provider & { createDepositAddress: NonNullable } { return typeof provider.createDepositAddress === 'function' } /** Narrows a funding provider to one that tracks reusable deposit addresses. */ export function canListDepositAddressRequests(provider: Provider): provider is Provider & { listDepositAddressRequests: NonNullable } { return typeof provider.listDepositAddressRequests === 'function' } /** Narrows a funding provider to one that observes reusable-address delivery onchain. */ export function canObserveDepositAddressRequest(provider: Provider): provider is Provider & { observeDepositAddressRequest: NonNullable } { return typeof provider.observeDepositAddressRequest === 'function' } /** Narrows a funding provider to one that verifies submitted source transactions. */ export function canVerifySourceTransaction(provider: Provider): provider is Provider & { verifySourceTransaction: NonNullable } { return typeof provider.verifySourceTransaction === 'function' } /** Narrows a funding provider to one with authenticated webhook receipt. */ export function canReceiveWebhook(provider: Provider): provider is Provider & { webhook: Webhook } { return provider.webhook !== undefined } /** Funding provider method recorded by request diagnostics. */ export type ProviderOperation = | 'createDepositAddress' | 'getQuote' | 'listDepositAddressRequests' | 'prepareTransfer' /** One provider attempt made while resolving a funding quote request. */ export type ProviderAttempt = | { /** Wall-clock duration of the provider attempt in milliseconds. */ durationMs: number /** Provider method invoked by the attempt. */ operation: ProviderOperation /** Successful or expected-unavailable provider outcome. */ outcome: 'available' | 'thin' | 'unavailable' /** Provider that handled the quote candidate. */ provider: Provider } | { /** Error thrown by the provider boundary. */ cause: unknown /** Wall-clock duration of the provider attempt in milliseconds. */ durationMs: number /** Provider method invoked by the attempt. */ operation: ProviderOperation /** Failed provider outcome. */ outcome: 'failed' /** Provider that handled the quote candidate. */ provider: Provider } /** Bounded result for one provider attempt. */ export type ProviderAttemptResult = { /** Wall-clock duration of the provider attempt in milliseconds. */ durationMs: number /** Failure class, present only when the provider attempt failed. */ failure?: failure.Result['failure'] | undefined /** Provider identifier. */ id: string /** Provider method invoked by the attempt. */ operation: ProviderOperation /** Provider attempt outcome. */ outcome: ProviderAttempt['outcome'] } /** A provider failure retained for bounded request diagnostics. */ export type ProviderFailure = { /** Error thrown by the provider boundary. */ cause: unknown /** Provider method that failed. */ operation: ProviderOperation /** Provider that failed. */ provider: Provider } // Slow provider responses can still contain valid quotes; keep the boundary // above observed Relay latency while bounding the total request duration. const defaultQuoteTimeoutMs = 5_000 /** Returns normalized chains and source tokens supported by quote providers. */ export function getChains(options: getChains.Options) { const routes = getRoutes(options.catalog, options.providers) const chains = uniqueBy( routes.map((entry) => entry.route.source.chain), (chain) => chain.id, ) return chains.flatMap((chain) => { const tokens = routes .filter((entry) => entry.route.source.chain.id === chain.id) .map((entry) => entry.route.source) if (tokens.length === 0) return [] return [ { ...chainRef(chain), tokens: uniqueBy(tokens, (token) => token.address).map(tokenRef), }, ] }) } export declare namespace getChains { /** Options for getting provider-supported source chains. */ type Options = { /** Compiled funding catalog containing provider routes. */ catalog: core_Catalog.Snapshot /** Providers whose routes contribute source chains and tokens. */ providers: readonly Provider[] } } /** Returns the supplied funding quote providers. */ export function getProviders(options: getProviders.Options) { return options.providers .filter((provider) => (options.catalog.routesByProvider.get(provider.id)?.length ?? 0) > 0) .map((provider) => schema.ProviderRef.parse(provider)) } export declare namespace getProviders { /** Options for getting providers. */ type Options = { /** Compiled funding catalog containing provider routes. */ catalog: core_Catalog.Snapshot /** Funding providers included in this deployment. */ providers: readonly Provider[] } } /** Returns successful live funding quotes from the supplied providers. */ export async function getQuotes(options: getQuotes.Options) { const providers = options.providers ?? [] const operation = 'getQuote' satisfies ProviderOperation const candidates = getQuoteCandidates(options).flatMap((candidate) => { const provider = providers.find((entry) => entry.id === candidate.provider.id) return provider ? [{ candidate, provider }] : [] }) const attempts = new Map() const results = await Promise.allSettled( candidates.map(async ({ candidate, provider }, index) => { const start = performance.now() try { const result = await quoteCandidate(candidate, { cache: options.cache ?? { bypass: false }, provider, request: { destinationToken: options.destinationToken, provider: options.provider, sourceAmount: options.sourceAmount, sourceAmountUnits: options.sourceAmountUnits, sourceChain: options.sourceChain, sourceToken: options.sourceToken, }, timeoutMs: options.providerTimeoutMs ?? defaultQuoteTimeoutMs, }) attempts.set(index, { durationMs: performance.now() - start, operation, outcome: result.outcome, provider, }) return 'quote' in result ? result.quote : undefined } catch (cause) { attempts.set(index, { cause, durationMs: performance.now() - start, operation, outcome: 'failed', provider, }) throw cause } }), ) const values = [...candidates.keys()].flatMap((index) => { const attempt = attempts.get(index) return attempt ? [attempt] : [] }) for (const attempt of values) { try { options.onAttempt?.(attempt) } catch { // Observability hooks never alter funding quote behavior. } } const quotes = results.flatMap((result) => result.status === 'fulfilled' && result.value ? [result.value] : [], ) const failures = results.flatMap((result, index) => result.status === 'rejected' ? [ { cause: result.reason, operation, provider: candidates[index]!.provider, } satisfies ProviderFailure, ] : [], ) if (quotes.length === 0 && failures.length > 0) throw new ProviderQuoteError(failures, values) return quotes } /** Returns internal quote candidates for a normalized caller request. */ export function getQuoteCandidates(options: getQuoteCandidates.Options) { const { catalog, providers } = options const amount = ( options.sourceAmountUnits === 'baseUnits' ? schema.PositiveInteger : schema.PositiveDecimal ).safeParse(options.sourceAmount) if (!amount.success) throw new InvalidAmountError() const sourceChain = resolveSourceChain(options.sourceChain, catalog) const sourceToken = resolveSourceToken(sourceChain, options.sourceToken, catalog) const destination = resolveDestinationToken(options.destinationToken, catalog) const provider = options.provider ? resolveProvider(options.provider, providers) : undefined if (providers.length === 0) return [] if (!sourceToken) return [] const sourceAmount = resolveSourceAmount({ amount: options.sourceAmount, token: sourceToken, units: options.sourceAmountUnits, }) return getRoutes(catalog, providers) .filter((entry) => entry.route.source.chain.id === sourceChain.id) .filter((entry) => entry.route.source.address === sourceToken.address) .filter((entry) => entry.route.destination.chain.id === destination.chain.id) .filter((entry) => entry.route.destination.address === destination.address) .filter((entry) => provider === undefined || entry.provider.id === provider.id) .map((entry) => createQuoteCandidate({ configuration: entry.configuration, destinationToken: entry.route.destination, provider: entry.provider, route: entry.route, sourceAmount, sourceToken: entry.route.source, }), ) } export declare namespace getQuoteCandidates { /** Options for selecting funding quote candidates. */ type Options = QuoteRequest & { /** Compiled funding catalog containing provider routes. */ catalog: core_Catalog.Snapshot /** Funding providers considered by the request. */ providers: readonly Provider[] } } export declare namespace getQuotes { /** Options for requesting live funding quotes. */ type Options = getQuoteCandidates.Options & { /** Cache policy for provider-side discovery and quote lookups. */ cache?: CachePolicy | undefined /** Receives one bounded observation for every provider attempt. */ onAttempt?: ((attempt: ProviderAttempt) => void) | undefined /** Per-provider quote timeout in milliseconds. */ providerTimeoutMs?: number | undefined } } const defaultPrepareTimeoutMs = 10_000 // Providers that return no quote validity window get a conservative default: // the caller must sign and submit promptly, mirroring wallet quote UX. const defaultQuoteTtlMs = 60_000 /** * Provisions one reusable deposit address through the first deterministic * capability-enabled provider. Returns `undefined` when no route is enabled. */ export async function createDepositAddress( options: createDepositAddress.Options, ): Promise { const operation = 'createDepositAddress' satisfies ProviderOperation if (!schema.PositiveInteger.safeParse(options.amount).success) throw new InvalidAmountError() const candidates = getDepositAddressCandidates(options).sort((a, b) => a.provider.id.localeCompare(b.provider.id), ) const selected = candidates[0] if (!selected) return undefined const recipient = normalizeAccountAddress(options.recipient, selected.candidate.destinationChain) const refundAddress = normalizeAccountAddress( options.refundAddress, selected.candidate.sourceChain, ) if (!recipient) throw new ProviderPayloadError() if (!refundAddress) throw new InvalidRefundAddressError() const start = performance.now() try { const parsed = schema.DepositAddressResult.safeParse( await withTimeout( (signal) => selected.provider.createDepositAddress( { amount: options.amount, candidate: selected.candidate, recipient, refundAddress, subsidize: options.subsidize, }, signal, ), options.providerTimeoutMs ?? defaultPrepareTimeoutMs, ), ) if (!parsed.success) throw new ProviderPayloadError() const result = parsed.data if (!normalizeAccountAddress(result.address, selected.candidate.sourceChain)) throw new ProviderPayloadError() if (BigInt(result.destinationAmountMin) > BigInt(result.destinationAmount)) throw new ProviderPayloadError() if (result.expiresAt !== undefined && Date.parse(result.expiresAt) <= Date.now()) throw new ProviderPayloadError() const attempt: ProviderAttempt = { durationMs: performance.now() - start, operation, outcome: 'available', provider: selected.provider, } try { options.onAttempt?.(attempt) } catch { // Observability hooks never alter provisioning behavior. } return { ...selected, recipient, refundAddress, result } } catch (cause) { const attempt: ProviderAttempt = cause instanceof ProviderUnavailableError ? { durationMs: performance.now() - start, operation, outcome: 'unavailable', provider: selected.provider, } : { cause, durationMs: performance.now() - start, operation, outcome: 'failed', provider: selected.provider, } try { options.onAttempt?.(attempt) } catch { // Observability hooks never alter provisioning behavior. } if (cause instanceof ProviderUnavailableError) return undefined throw new ProviderQuoteError([{ cause, operation, provider: selected.provider }], [attempt]) } } export declare namespace createDepositAddress { /** Options for provisioning one reusable deposit address. */ type Options = getDepositAddressCandidates.Options & { /** Source token amount in base units used for initial route provisioning. */ amount: string /** Receives one bounded observation for the provider attempt. */ onAttempt?: ((attempt: ProviderAttempt) => void) | undefined /** Per-provider provisioning timeout in milliseconds. */ providerTimeoutMs?: number | undefined /** Final beneficiary on Tempo. */ recipient: string /** Source-chain account that receives refunds. */ refundAddress: string /** Whether Tempo guarantees normalized 1:1 delivery. */ subsidize: boolean } /** Selected provider route and its reusable address. */ type Selection = { /** Curated route used to provision the address. */ candidate: DepositAddressCandidate /** Provider that provisioned the address. */ provider: Provider /** Normalized final beneficiary on Tempo. */ recipient: string /** Normalized source-chain refund account. */ refundAddress: string /** Reusable address and private provider correlation. */ result: createDepositAddress.ReturnType } } /** * Prepares one executable transfer across capability-enabled candidates, * selecting the best successful preparation. Returns `undefined` when no * enabled corridor matches; throws {@link ProviderQuoteError} when every * matching provider failed unexpectedly. */ export async function prepareTransfer( options: prepareTransfer.Options, ): Promise { const operation = 'prepareTransfer' satisfies ProviderOperation const candidates = getPrepareCandidates(options) if (candidates.length === 0) return undefined const attempts: ProviderAttempt[] = [] const results = await Promise.allSettled( candidates.map(async ({ candidate, provider }) => { const start = performance.now() try { const parameters = { candidate, method: options.method, mode: options.mode, recipient: options.recipient, sender: options.sender, slippageBps: options.slippageBps, } const parsed = schema.PrepareResult.safeParse( await withTimeout( (signal) => provider.prepareTransfer(parameters, signal), options.providerTimeoutMs ?? defaultPrepareTimeoutMs, ), ) if (!parsed.success) throw new ProviderPayloadError() const result = parsed.data const expiresAt = result.expiresAt ?? new Date(Date.parse(result.sampledAt) + defaultQuoteTtlMs).toISOString() if (Date.parse(expiresAt) <= Date.now()) throw new ProviderPayloadError() if (BigInt(result.destinationAmountMin) > BigInt(result.destinationAmount)) throw new ProviderPayloadError() // Providers may floor slippage-adjusted amounts to whole base units; // reject only minima below that allowed rounding. if (options.slippageBps !== undefined) { const basisPointScale = 10_000n const minimumAllowed = (BigInt(result.destinationAmount) * BigInt(10_000 - options.slippageBps)) / basisPointScale if (BigInt(result.destinationAmountMin) < minimumAllowed) throw new ProviderPayloadError() } if (result.action.type === 'evm:calls') try { const constraints = provider.validateTransferAction?.({ ...parameters, action: result.action, }) Action.validateEvmCalls({ action: result.action, nativeValues: constraints?.nativeValues, sourceAmount: candidate.sourceAmount.amount, sourceTokenAddress: candidate.sourceToken.address, }) } catch (cause) { if (cause instanceof Action.InvalidActionError) throw new ProviderPayloadError() throw cause } attempts.push({ durationMs: performance.now() - start, operation, outcome: 'available', provider, }) return { candidate, provider, result: { ...result, expiresAt } } } catch (cause) { if (cause instanceof ProviderUnavailableError) { attempts.push({ durationMs: performance.now() - start, operation, outcome: 'unavailable', provider, }) return undefined } attempts.push({ cause, durationMs: performance.now() - start, operation, outcome: 'failed', provider, }) throw cause } }), ) for (const attempt of attempts) { try { options.onAttempt?.(attempt) } catch { // Observability hooks never alter preparation behavior. } } const prepared = results.flatMap((result) => result.status === 'fulfilled' && result.value ? [result.value] : [], ) if (prepared.length === 0) { const failures = results.flatMap((result, index) => result.status === 'rejected' ? [ { cause: result.reason, operation, provider: candidates[index]!.provider, } satisfies ProviderFailure, ] : [], ) if (failures.length === 0) return undefined throw new ProviderQuoteError(failures, attempts) } // Deterministic selection: highest guaranteed minimum, then provider id. prepared.sort((a, b) => { const delta = BigInt(b.result.destinationAmountMin) - BigInt(a.result.destinationAmountMin) if (delta !== 0n) return delta > 0n ? 1 : -1 return a.provider.id < b.provider.id ? -1 : 1 }) return prepared[0]! } export declare namespace prepareTransfer { /** Options for preparing one executable transfer. */ type Options = getPrepareCandidates.Options & { /** Receives one bounded observation for every provider attempt. */ onAttempt?: ((attempt: ProviderAttempt) => void) | undefined /** Per-provider preparation timeout in milliseconds. */ providerTimeoutMs?: number | undefined /** Final beneficiary on the destination chain. */ recipient: string /** Source-chain account that signs the funding action. */ sender: string /** Caller slippage bound in basis points. */ slippageBps?: number | undefined } /** The selected preparation with a guaranteed expiry. */ type Selection = { /** Curated candidate the preparation was produced for. */ candidate: QuoteCandidate /** Provider that produced the selected preparation. */ provider: Provider /** The preparation, with a default expiry applied when the provider gave none. */ result: prepareTransfer.ReturnType & { expiresAt: string } } } /** * Returns capability-enabled reusable-address candidates for a route. Tokens * resolve within their chains and only provider delivery qualifies. */ export function getDepositAddressCandidates(options: getDepositAddressCandidates.Options) { const sourceChain = resolveSourceChain(options.sourceChain, options.catalog) const destinationChainId = `eip155:${options.destinationChainId}` return options.catalog.routes.flatMap((entry) => { if (entry.route.source.chain.id !== sourceChain.id) return [] if (entry.route.destination.chain.id !== destinationChainId) return [] if (!matchesToken(entry.route.source, options.sourceToken)) return [] if (!matchesToken(entry.route.destination, options.destinationToken)) return [] if (!entry.capabilities?.depositAddress) return [] const provider = options.providers.find((candidate) => candidate.id === entry.providerId) if (!provider || !canCreateDepositAddress(provider)) return [] return [ { candidate: createDepositAddressCandidate({ configuration: entry.configuration, provider, route: entry.route, }), provider, }, ] }) } export declare namespace getDepositAddressCandidates { /** Options for selecting reusable deposit-address routes. */ type Options = { /** Compiled funding catalog containing provider routes and capabilities. */ catalog: core_Catalog.Snapshot /** Tempo destination chain selected by app composition. */ destinationChainId: number /** Tempo destination token symbol, contract address, or token key. */ destinationToken: string /** Funding providers considered by the request. */ providers: readonly Provider[] /** Source chain CAIP-2 id, slug, or alias. */ sourceChain: string /** Source token symbol, contract address, or token key. */ sourceToken: string } } /** * Returns capability-enabled preparation candidates for a creation request. * Tokens resolve within their chains. A route must enable the requested mode, * and its provider must implement `prepareTransfer`. */ export function getPrepareCandidates(options: getPrepareCandidates.Options) { const amount = schema.PositiveInteger.safeParse(options.sourceAmount) if (!amount.success) throw new InvalidAmountError() const sourceChain = resolveSourceChain(options.sourceChain, options.catalog) const destinationChainId = `eip155:${options.destinationChainId}` const selectedProvider = options.provider ? resolveProvider(options.provider, options.providers) : undefined return options.catalog.routes.flatMap((entry) => { if (entry.route.source.chain.id !== sourceChain.id) return [] if (entry.route.destination.chain.id !== destinationChainId) return [] if (!matchesToken(entry.route.source, options.sourceToken)) return [] if (!matchesToken(entry.route.destination, options.destinationToken)) return [] if (!entry.capabilities?.transfer?.modes.includes(options.mode)) return [] const provider = selectedProvider ?? options.providers.find((candidate) => candidate.id === entry.providerId) if (!provider || provider.id !== entry.providerId || !canPrepareTransfer(provider)) return [] return [ { candidate: createQuoteCandidate({ configuration: entry.configuration, destinationToken: entry.route.destination, provider, route: entry.route, sourceAmount: resolveSourceAmount({ amount: options.sourceAmount, token: entry.route.source, units: 'baseUnits', }), sourceToken: entry.route.source, }), provider, }, ] }) } export declare namespace getPrepareCandidates { /** Options for selecting preparation candidates. */ type Options = { /** Compiled funding catalog containing provider routes and capabilities. */ catalog: core_Catalog.Snapshot /** Tempo destination chain selected by app composition. */ destinationChainId: number /** Tempo destination token symbol, contract address, or token key. */ destinationToken: string /** Funding method passed to provider preparation. */ method: Transfer.Method /** Amount mode the caller selected. */ mode: Transfer.Mode /** Funding provider ID selected by the caller. */ provider?: string | undefined /** Funding providers considered by the request. */ providers: readonly Provider[] /** Source amount in base units. */ sourceAmount: string /** Source chain CAIP-2 id, slug, or alias. */ sourceChain: string /** Source token symbol, contract address, or token key. */ sourceToken: string } } /** Source amount unit accepted by the funding quote request. */ export type SourceAmountUnits = z.output /** Fetch implementation accepted by funding provider factories. */ export type Fetch = typeof globalThis.fetch /** Returns bounded provider failure details suitable for structured request logs. */ export function failure(options: ProviderFailure): failure.Result { const { cause, operation, provider } = options if ( cause instanceof ProviderTimeoutError || (cause instanceof DOMException && cause.name === 'AbortError') ) return { failure: 'timeout', id: provider.id, operation } if (cause instanceof ProviderResponseError) { const code = errorCode(cause.body) return { ...(code === undefined ? {} : { code }), failure: 'http', id: provider.id, operation, status: cause.status, } } if (cause instanceof ProviderPayloadError) return { failure: 'payload', id: provider.id, operation } if (cause instanceof ProviderRateLimitError) return { failure: 'rate_limit', id: provider.id, operation } if (cause instanceof TypeError) return { failure: 'network', id: provider.id, operation } return { failure: 'unknown', id: provider.id, operation } } export declare namespace failure { /** Bounded provider failure fields. */ type Result = { /** Stable provider error code, when one is safe to retain. */ code?: string | undefined /** Failure boundary that rejected the provider request. */ failure: 'http' | 'network' | 'payload' | 'rate_limit' | 'timeout' | 'unknown' /** Provider identifier. */ id: string /** Provider operation identifier. */ operation: ProviderOperation /** Upstream HTTP status, when a response was received. */ status?: number | undefined } } /** Returns a supported expected-unavailable code from a provider response. */ export function unavailable(cause: unknown, codes: readonly string[]) { if (!(cause instanceof ProviderResponseError) || cause.status < 400 || cause.status >= 500) return undefined const code = errorCode(cause.body) if (code && codes.includes(code)) return code return undefined } /** Requests and parses a JSON provider response. */ export async function requestJson(url: URL, options: requestJson.Options) { const headers = { accept: 'application/json', ...options.headers } const init: RequestInit = { headers: options.body === undefined ? headers : { ...headers, 'content-type': 'application/json' }, method: options.method ?? 'GET', signal: options.signal, } if (options.body !== undefined) init.body = JSON.stringify(options.body) const response = await options.fetch(url, init) const text = await response.text() const body = (() => { if (!text) return null try { return JSON.parse(text) as unknown } catch { return text } })() if (!response.ok) throw new ProviderResponseError(response.status, body) return body } export declare namespace requestJson { /** Provider request options. */ type Options = { /** Optional JSON request body. */ body?: unknown /** Fetch implementation. */ fetch: Fetch /** Optional request headers. */ headers?: Record | undefined /** Optional HTTP method. */ method?: string | undefined /** Request abort signal. */ signal: AbortSignal } } /** Creates a normalized expected-unavailable quote result. */ export function unavailableResult(options: unavailableResult.Options): getQuote.ReturnType { return { quality: { liquiditySource: options.source, sourceDetail: `${options.detail}:${options.message}`, tier: 'unavailable', }, sampledAt: options.now.toISOString(), status: 'unavailable', } } export declare namespace unavailableResult { /** Expected-unavailable quote details. */ type Options = { /** Provider operation detail. */ detail: string /** Stable provider reason. */ message: string /** Quote sample time. */ now: Date /** Liquidity signal source. */ source: string } } function tokenKey(token: Token) { const namespace = (() => { if (token.chain.kind === 'solana') return 'token' if (token.chain.kind === 'tron') return 'trc20' return 'erc20' })() return `${token.chain.id}/${namespace}:${token.address}` } async function quoteCandidate( candidate: QuoteCandidate, options: { cache: CachePolicy provider: Provider request: QuoteRequest timeoutMs: number }, ) { const result = schema.GetQuoteResult.safeParse( await withTimeout( (signal) => options.provider.getQuote( { candidate, cache: options.cache, destinationChain: candidate.destinationChain, destinationToken: candidate.destinationToken, request: options.request, route: candidate.route, sourceAmount: candidate.sourceAmount, sourceChain: candidate.sourceChain, sourceToken: candidate.sourceToken, }, signal, ), options.timeoutMs, ), ) if (!result.success) throw new ProviderPayloadError() const quote = result.data if (quote.status === 'unavailable') return { outcome: quote.status } if ( (quote.status !== 'available' && quote.status !== 'thin') || quote.destinationAmount === undefined || quote.destinationAmount === null ) throw new ProviderPayloadError() const destinationAmount = BigInt(quote.destinationAmount) const destinationAmountMin = quote.destinationAmountMin === undefined ? undefined : BigInt(quote.destinationAmountMin) if ( destinationAmount === 0n || destinationAmountMin === 0n || (destinationAmountMin !== undefined && destinationAmountMin > destinationAmount) ) throw new ProviderPayloadError() return { outcome: quote.status, quote: createQuote(candidate, { ...quote, destinationAmount: quote.destinationAmount, status: quote.status, }), } } function tokenRef(token: Token): TokenRef { return schema.TokenRef.parse({ address: token.address, currency: token.currency, decimals: token.decimals, name: token.name, standard: token.standard, symbol: token.symbol, tokenKey: tokenKey(token), verified: true, }) } function chainRef(chain: Chain): ChainRef { return schema.ChainRef.parse({ addressFormat: chain.addressFormat, id: chain.id, kind: chain.kind, name: chain.name, }) } function createDepositAddressCandidate(options: createDepositAddressCandidate.Options) { return { ...(options.configuration ? { configuration: options.configuration } : {}), destinationChain: chainRef(options.route.destination.chain), destinationToken: tokenRef(options.route.destination), provider: schema.ProviderRef.parse(options.provider), route: options.route, sourceChain: chainRef(options.route.source.chain), sourceToken: tokenRef(options.route.source), } as const } declare namespace createDepositAddressCandidate { type Options = { configuration?: core_Catalog.Configuration | undefined provider: Provider route: core_Route.Route } } function createQuoteCandidate(options: createQuoteCandidate.Options) { return { ...(options.configuration ? { configuration: options.configuration } : {}), destinationChain: chainRef(options.destinationToken.chain), destinationToken: tokenRef(options.destinationToken), id: [ options.sourceToken.chain.slug, options.sourceToken.slug, options.destinationToken.chain.slug, options.destinationToken.slug, options.provider.id, ].join('-'), provider: schema.ProviderRef.parse(options.provider), route: options.route, sourceAmount: options.sourceAmount, sourceChain: chainRef(options.sourceToken.chain), sourceToken: tokenRef(options.sourceToken), } as const } declare namespace createQuoteCandidate { type Options = { configuration?: core_Catalog.Configuration | undefined destinationToken: Token provider: Provider route: core_Route.Route sourceAmount: ReturnType sourceToken: Token } } function normalizeInput(value: string) { return value.trim().toLowerCase() } function parseBaseUnits(value: string) { return BigInt(value).toString() } function parseDecimalUnits(value: string, decimals: number) { const fraction = value.split('.')[1]?.replace(/0+$/, '') ?? '' if (fraction.length > decimals) throw new InvalidAmountError() try { const amount = core_Value.from(value, decimals) if (amount <= 0n) throw new InvalidAmountError() return amount.toString() } catch (cause) { if (cause instanceof InvalidAmountError) throw cause throw new InvalidAmountError() } } type SuccessfulQuoteResult = getQuote.ReturnType & { destinationAmount: string status: 'available' | 'thin' } function createQuote(candidate: QuoteCandidate, quote: SuccessfulQuoteResult): FundingQuote { return schema.FundingQuote.parse({ destinationAmount: Value.tokenAmount({ baseUnits: quote.destinationAmount, currency: candidate.destinationToken.currency, decimals: candidate.destinationToken.decimals, }), ...(quote.destinationAmountMin !== undefined ? { destinationAmountMin: Value.tokenAmount({ baseUnits: quote.destinationAmountMin, currency: candidate.destinationToken.currency, decimals: candidate.destinationToken.decimals, }), } : {}), destinationChain: candidate.destinationChain, destinationToken: candidate.destinationToken, id: candidate.id, provider: candidate.provider, quality: { ...(quote.quality?.estimatedSeconds !== undefined ? { estimatedSeconds: quote.quality.estimatedSeconds } : {}), liquiditySource: quote.quality?.liquiditySource ?? 'providerQuote', ...(quote.quality?.sourceDetail !== undefined ? { sourceDetail: quote.quality.sourceDetail } : {}), tier: quote.status === 'thin' ? ('thin' as const) : ('liquid' as const), }, quote: { ...(quote.expiresAt !== undefined ? { expiresAt: quote.expiresAt } : {}), sampledAt: quote.sampledAt, }, sourceAmount: Value.tokenAmount({ baseUnits: candidate.sourceAmount.amount, currency: candidate.sourceToken.currency, decimals: candidate.sourceToken.decimals, }), sourceChain: candidate.sourceChain, sourceToken: candidate.sourceToken, }) } function withTimeout(fn: (signal: AbortSignal) => Promise, timeoutMs: number) { const controller = new AbortController() const timeout = setTimeout(() => controller.abort(), timeoutMs) // Some provider implementations may not honor the signal, so the boundary also races them. return Promise.race([ fn(controller.signal), new Promise((_, reject) => { if (controller.signal.aborted) { reject(new ProviderTimeoutError()) return } controller.signal.addEventListener('abort', () => reject(new ProviderTimeoutError()), { once: true, }) }), ]).finally(() => clearTimeout(timeout)) } function errorCode(input: unknown) { const result = schema.ErrorBody.safeParse(input) if (!result.success) return undefined const body = result.data const code = body._tag ?? body.code ?? body.errorCode ?? body.errorType ?? body.type ?? body.error?.code ?? body.error?.type const parsed = schema.ErrorCode.safeParse(code) return parsed.success ? parsed.data : undefined } function attemptResult(attempt: ProviderAttempt): ProviderAttemptResult { return { durationMs: attempt.durationMs, ...(attempt.outcome === 'failed' ? { failure: failure({ cause: attempt.cause, operation: attempt.operation, provider: attempt.provider, }).failure, } : {}), id: attempt.provider.id, operation: attempt.operation, outcome: attempt.outcome, } } function resolveDestinationToken(input: string | undefined, catalog: core_Catalog.Snapshot) { const destinations = uniqueBy( catalog.routes.map((entry) => entry.route.destination), (token) => `${token.chain.id}:${token.address}`, ) if (input === undefined) { const destination = destinations.find((candidate) => candidate.slug === defaultDestinationToken) if (!destination) throw new UnsupportedDestinationTokenError() return destination } const destination = destinations.find((candidate) => matchesToken(candidate, input)) if (!destination) throw new UnsupportedDestinationTokenError() return destination } function resolveProvider(input: string, providers: readonly Provider[]) { const provider = providers.find((candidate) => candidate.id === normalizeInput(input)) if (!provider) throw new UnsupportedProviderError() return provider } function resolveSourceAmount(options: resolveSourceAmount.Options) { const amount = options.units === 'baseUnits' ? parseBaseUnits(options.amount) : parseDecimalUnits(options.amount, options.token.decimals) return { amount, formatted: core_Value.format(BigInt(amount), options.token.decimals), } } declare namespace resolveSourceAmount { type Options = { amount: string token: Token units: SourceAmountUnits } } function resolveSourceToken(chain: Chain, input: string, catalog: core_Catalog.Snapshot) { const tokens = catalog.routes .filter((entry) => entry.route.source.chain.id === chain.id) .map((entry) => entry.route.source) return tokens.find((token) => matchesToken(token, input)) } function matchesToken(token: Token, input: string) { const trimmed = input.trim() const normalized = normalizeInput(input) return ( token.slug === normalized || token.symbol.toLowerCase() === normalized || matchesTokenIdentifier(token.chain, token.address, trimmed, normalized) || matchesTokenIdentifier(token.chain, tokenKey(token), trimmed, normalized) ) } function matchesTokenIdentifier( chain: Chain, candidate: string, trimmed: string, normalized: string, ) { return chain.kind === 'evm' ? candidate.toLowerCase() === normalized : candidate === trimmed } function resolveSourceChain(input: string, catalog: core_Catalog.Snapshot) { const normalized = normalizeInput(input) const chain = catalog.chainsByKey.get(normalized) if (!chain || !catalog.routes.some((entry) => entry.route.source.chain.id === chain.id)) throw new UnsupportedSourceChainError() return chain } function getRoutes(catalog: core_Catalog.Snapshot, providers: readonly Provider[]) { return providers.flatMap((provider) => (catalog.routesByProvider.get(provider.id) ?? []).map((entry) => ({ ...entry, provider })), ) } function uniqueBy(values: readonly value[], key: (value: value) => string): value[] { const seen = new Set() return values.filter((value) => { const id = key(value) if (seen.has(id)) return false seen.add(id) return true }) } function normalizeAccountAddress(value: string, chain: ChainRef): string | undefined { if (chain.kind === 'evm') return Address.validate(value, { strict: false }) ? Address.checksum(value) : undefined try { const bytes = Base58.toBytes(value) if (chain.kind === 'solana') return bytes.length === 32 ? value : undefined if (bytes.length !== 25 || bytes[0] !== 0x41) return undefined const payload = bytes.slice(0, 21) const checksum = Hash.sha256(Hash.sha256(payload), { as: 'Bytes' }).slice(0, 4) return Bytes.isEqual(bytes.slice(21), checksum) ? value : undefined } catch { return undefined } } /** Error thrown when the caller supplies an unsupported destination token. */ export class UnsupportedDestinationTokenError extends Error { override name = 'FundingProvider.UnsupportedDestinationTokenError' } /** Error thrown when a provider webhook cannot be authenticated. */ export class WebhookAuthenticationError extends Error { override name = 'FundingProvider.WebhookAuthenticationError' } /** Error thrown when an authenticated provider webhook has an invalid body. */ export class WebhookPayloadError extends Error { override name = 'FundingProvider.WebhookPayloadError' } /** Error thrown when the caller supplies an invalid source amount. */ export class InvalidAmountError extends Error { override name = 'FundingProvider.InvalidAmountError' } /** Error thrown when the caller supplies an invalid source-chain refund address. */ export class InvalidRefundAddressError extends Error { override name = 'FundingProvider.InvalidRefundAddressError' } /** Error thrown when provider deployment configuration is invalid. */ export class ProviderConfigurationError extends Error { override name = 'FundingProvider.ProviderConfigurationError' } /** Error thrown when a provider response does not match the requested quote. */ export class ProviderPayloadError extends Error { override name = 'FundingProvider.ProviderPayloadError' } /** Error thrown when a provider confirms that no executable route is available. */ export class ProviderUnavailableError extends Error { override name = 'FundingProvider.ProviderUnavailableError' } /** Error thrown internally when a provider exceeds its quote timeout. */ export class ProviderTimeoutError extends Error { override name = 'FundingProvider.ProviderTimeoutError' } /** Error thrown when an internal provider quota rejects a quote request. */ export class ProviderRateLimitError extends Error { override name = 'FundingProvider.ProviderRateLimitError' } /** Error thrown when a provider returns an unsuccessful HTTP response. */ export class ProviderResponseError extends Error { override name = 'FundingProvider.ProviderResponseError' constructor( /** Provider response status. */ readonly status: number, /** Parsed provider response body. */ readonly body: unknown, ) { super(`Provider request failed with status ${status}`) } } /** Error thrown when every matching provider fails to produce a quote. */ export class ProviderQuoteError extends Error { override name = 'FundingProvider.ProviderQuoteError' /** Provider failures that prevented a successful quote. */ failures: readonly ProviderFailure[] /** Bounded provider attempts retained for request logs. */ fundingProviderAttempts: readonly ProviderAttemptResult[] /** First bounded provider failure retained for request logs. */ providerFailure?: failure.Result | undefined /** Bounded provider failures retained when multiple providers failed. */ providerFailures?: readonly failure.Result[] | undefined constructor(failures: readonly ProviderFailure[], attempts: readonly ProviderAttempt[] = []) { super() this.failures = failures this.fundingProviderAttempts = attempts.map(attemptResult) const values = failures.map(failure) const first = values[0] if (first) this.providerFailure = first if (values.length > 1) this.providerFailures = values } } /** Error thrown when the caller filters to an unknown provider. */ export class UnsupportedProviderError extends Error { override name = 'FundingProvider.UnsupportedProviderError' } /** Error thrown when the caller supplies an unsupported source chain. */ export class UnsupportedSourceChainError extends Error { override name = 'FundingProvider.UnsupportedSourceChainError' }