@openrampkit/server
The OpenRampKit server: a web-standard Request -> Response handler plus a small backend API. It runs on Cloudflare Workers, Vercel and Next.js, Node 20+, Bun and Deno.
import { createOpenRamp } from '@openrampkit/server'
const openramp = createOpenRamp({
secret: process.env.OPENRAMP_SECRET!,
baseUrl: 'https://app.example.com/api/openramp',
adapters: [/* ... */],
})createOpenRamp(config)
createOpenRamp throws at startup when secret or admin.token is shorter than 32 characters, when webhooks.secret or tasksToken is shorter than 16 characters, when an adapter targets another API version (the adapter API is version 2), or when two adapters share an id. It logs a warning when treasury has no address: then quotes for app-custody withdrawals use a placeholder sender.
| Option | Type | Default | Description |
|---|---|---|---|
secret | string | required | Signs start URLs. At least 32 characters. |
baseUrl | string | required | Public URL where the handler is mounted, e.g. https://app.example.com/api/openramp. Its path is stripped from incoming requests. It also builds the return URL, the webhook URLs and the start URLs. |
adapters | Adapter[] | required | Provider adapters |
store | SessionStore | memoryStore() | Where sessions live. Use a shared store in production. See Session stores. |
livemode | boolean | false | Marks sessions and events as live. Adapters may switch to sandbox when it is false (Coinbase without env). The server checks each adapter.env at start: with true, a sandbox adapter stops the start; with false, a production adapter gets a warning. See Sandbox and production. |
policy.maxLegs | 1 | 2 | 2 | Longest pathway |
policy.regions | RegionPolicy | allow all | App-wide region policy, applied on top of each leg's policy |
policy.methodPriority | Record<country, string[]> | built-in | Method order per country |
policy.disabledMethods | string[] | none | Methods never offered |
policy.maxAttempts | number | 10 | The most payment attempts of one session. A failed attempt that leaves no attempt makes the session failed (final). |
policy.outputToleranceBps | number | 100 (1%) | How much less than the quote a provider may report as a leg's output before the delivery is short (result.delivery). A quote with a minOutput uses that minimum instead. See SessionResult. |
policy.hopPreference | CryptoAsset[] | USDC on Base, Arbitrum, Polygon, Optimism, Ethereum | Hop assets for two-leg pathways, most preferred first. See Hops. |
webhooks | { url: string; secret: string; retryHours?: number; maxAttempts?: number } | none | Signed webhooks to your backend. secret must have at least 16 characters (use 32 random bytes). sweep() retries failed deliveries for retryHours (default 24), or until maxAttempts attempts in all when you set it. Then the event is a dead letter. See Delivery. |
tasksToken | string | none | Bearer token for POST /tasks/sweep and GET /health?deep=1. At least 16 characters. Without it, those two are off. |
geo | (req) => { country?, region? } | undefined | Cloudflare / Vercel headers | Country and region for POST /sessions |
authorize | (req, body) => Promise<CreateSessionInput | null> | none | Enables POST /sessions from the browser or another service |
cors | { origins: string[] | '*' } | same origin only | Allowed origins for CORS |
logger | Logger | console (no debug) | debug, info, warn, error |
returnUrl | string | {baseUrl}/return | Where providers send the user back |
fetch | typeof fetch | global fetch | Used by the server and passed to adapters |
timeouts.quote | number (ms) | 9000 | Per quoted pathway |
timeouts.webhook | number (ms) | 4000 | Per outgoing webhook |
limits.providerCallsPerMinute | number | 60 | Per session: requests to /plan, /target, /quotes, /select, /transitions/*, /deposit-address and /history in one minute. More get 429 RATE_LIMITED. |
screenAddress | (address, chain) => Promise<boolean> | none | Withdraw: check a "To wallet" address. false or an error refuses it (fail closed). See Screen addresses. |
screenLightningAddress | (address, { cashtag? }) => Promise<boolean> | none | Withdraw: check a "To Cash App" target (a $cashtag or a Lightning address). false or an error refuses it (fail closed). With screenAddress set and this hook not set, every Lightning target is refused. See Screen Lightning targets. |
screenDepositWallets | boolean | false | Deposit: also run screenAddress on the connected wallet before a wallet payment starts, on the chain it pays from. A refused wallet gets 403 ADDRESS_REJECTED. A deposit to an address (transfer) has no known sender before the money arrives, so it is not screened. |
treasury | TreasuryHook | none | Withdraw with custody: 'app': sends the transactions from your wallet. Throw TreasuryRefusedError (exported) to refuse when nothing was sent; any other error makes the failure final. See Custody. |
withdraw.checkBalance | { rpcUrls: Record<string, string>, timeoutMs? } | off | Withdraw with custody: 'user_wallet' on EVM: read the wallet balance of the source token (ERC-20 balanceOf, or eth_getBalance for native) before quotes and before the first leg starts. An amount above the balance gets 422 AMOUNT_TOO_HIGH. rpcUrls maps a CAIP-2 chain id to a JSON-RPC URL; other chains are not checked. timeoutMs per call, default 3000. RPC errors and timeouts fail open and are logged. See Check the balance on the server. |
payPage | false | { scriptUrl?, title? } | on, script from esm.sh | The hosted pay page GET /pay/:credential. false turns it off. See The pay link. |
admin | { token?, stuckAfterMinutes?, indexDays?, page? } | none | Admin tools. With admin, the server keeps a time index of new sessions. With token (at least 32 characters), the routes /admin/* and the dashboard GET /admin are on. See Admin and observability. |
latePayments | { graceHours?, pollMinutes? } | { graceHours: 72, pollMinutes: 10 } | After the sweep expires a session whose payment still waits, it keeps polling that payment for graceHours, every pollMinutes. A late payment completes the session and sends session.late_payment. graceHours: 0 turns it off. See Background sweep. |
telemetry | { onMetric(name, value, tags) } | none | A plain metrics callback: quote latency, start errors, webhook failures, outbox depth, sweep lag. See Metrics. |
routing.healthWindowHours | number | 24 | Hours of provider health counts for the success rate and admin.providers(). See Provider health. |
routing.minSamples | number | 20 | Fewest samples before a rate counts. With fewer, the adapter is neutral in the ranking and the breaker does not open. |
routing.breakerFailureRate | number | 0.8 | The circuit breaker skips an adapter when more than this share of its quotes and starts in the last one to two hours failed. 1 turns the breaker off. |
routing.breakerCooldownMinutes | number | 10 | How long an open breaker skips the adapter before it lets one probe quote through |
routing.breaker | boolean | true | false turns the circuit breaker off. Ranking and failover still work. |
history | boolean | { retentionDays?: number } | false | The user's history: GET /sessions/:id/history and the History link in the widget (shown when the user has at least one past session). The server keeps one index of sessions per user from the time you turn it on. Each new session removes up to 20 index entries older than retentionDays (default 90; true keeps 90 days). The route counts against limits.providerCallsPerMinute. Off: the route answers 404 and the widget hides the link. See History. |
depositAddresses.widget | boolean | false | Offer "Your deposit address" in the widget (method deposit_address) and POST /sessions/:id/deposit-address. See Standing deposit addresses. |
The default geo lookup reads cf-ipcountry or x-vercel-ip-country (ignoring XX), and x-vercel-ip-country-region for the region (as {country}-{region}).
Return value
const openramp = createOpenRamp(config)
openramp.handle(req) // Promise<Response>. Mount at baseUrl.
openramp.fetch(req) // Same as handle, for `export default openramp` on Workers, Bun, Deno
openramp.nextHandlers() // { GET, POST, OPTIONS } for a Next.js App Router catch-all route
await openramp.sessions.create(input) // Promise<CreatedSession>; with externalId: Promise<CreatedSession | ExistingSession>
await openramp.sessions.retrieve(id) // Promise<Session | null>: the backend view (PublicSession plus userId and metadata)
await openramp.sessions.refresh(id) // Promise<Session | null>: ask the active leg's adapter for status now
await openramp.sessions.cancel(id, { reason? }) // Promise<Session | null>: cancel before money moved (reason default 'requested_by_app'); 409 otherwise (see POST /sessions/:id/cancel)
await openramp.sessions.payLink(id, { ttlMinutes? }) // Promise<PayLink | null>: { id, url, expiresAt }, a signed link to the pay page
await openramp.sessions.revokePayLink(id, linkId) // Promise<boolean>: make one pay link stop working; false when the session does not exist
await openramp.sweep({ limit? }) // Promise<SweepResult>: retry webhooks, poll standing addresses, refresh open payments, expire sessions
await openramp.depositAddresses.get({ userId, origin, destination, adapterId?, dedicatedDestination? }) // Promise<StandingAddress>: the permanent address of a user and route
await openramp.depositAddresses.list(userId) // Promise<StandingAddress[]>: newest first
await openramp.webhooks.verify(req, rawBody) // Promise<boolean>: verify a webhook this server sent
await openramp.webhooks.replay(sessionId) // Promise<number>: send the dead letters of a session again
await openramp.admin.list({ direction?, state?, olderThan?, stuck?, limit?, cursor? }) // Promise<AdminListResult>
await openramp.admin.get(id) // Promise<AdminSession | null>: legs, attempts, outbox, refs, timeline
await openramp.admin.findByRef(provider, ref) // Promise<AdminSession | null>
await openramp.admin.findByTx(chain, txHash) // Promise<AdminSessionSummary[]>: a transaction of any role, in any attempt
await openramp.admin.stats({ since? }) // Promise<AdminStats>
await openramp.admin.resolve(id, state, note) // Promise<AdminSession>: force a final state, with an audit note and a webhook
await openramp.admin.replayWebhooks(id) // Promise<{ queued: number }>
await openramp.admin.providers() // Promise<ProviderHealth[]>: counts, success rate, recent failure rate and breaker state per adapterhandle never throws. Errors become JSON responses (see HTTP routes). It answers OPTIONS with 204 and the CORS headers.
sessions.revokePayLink(id, linkId) takes the id that payLink returned. The pay page and the session routes then refuse that link. Other links of the session and the client secret keep working. A session keeps at most 100 revoked link ids. See POST /sessions/:id/pay-link/revoke.
sessions.refresh(id) skips the 2-second rate limit that browser polls have. It sends any webhooks that become due. You rarely need it: sweep() refreshes every open session.
webhooks.verify returns false when config.webhooks is not set.
webhooks.replay(sessionId) sends again the events of one session whose retries stopped (dead letters). They keep their event ids and get a new retry window. It returns the number of events, or 0 when the session has none.
depositAddresses.get returns a permanent deposit address for one user and route, and makes it on the first call. Each deposit to it becomes a deposit session with the usual webhooks. See Standing deposit addresses.
admin.* is for your backend and for operators. list, stats and findByTx read the time index, which the server keeps only when config.admin is set. See Admin and observability.
The type of the returned object is exported as OpenRamp.
Background sweep
Users close tabs, and webhook deliveries fail. openramp.sweep() does the background work. Run it every minute or so:
- from a scheduler that runs your code (a Cloudflare Cron Trigger, a Vercel Cron Job, or any cron), or
- over HTTP with
POST {baseUrl}/tasks/sweepandAuthorization: Bearer {tasksToken}(see HTTP routes).
Each run does these things:
- Retries failed webhooks. Each event is saved in its session record (the outbox), and the session id goes on the outbox queue. A delivery that fails (no 2xx answer, or a timeout) stays there. The sweep sends it again when it is due. The wait starts at 30 seconds and doubles after each failed attempt, up to 2 hours. After
webhooks.retryHours(default 24), orwebhooks.maxAttemptsattempts when you set it, the event becomes a dead letter in the session record. The server logswebhook moved to dead letter after retriesas an error.openramp.webhooks.replay(sessionId)sends the dead letters again. - Polls standing deposit addresses. For each standing address that is due, it asks the adapter for new deposits, and makes one session per new deposit. An address is polled every 30 seconds after a deposit in the last hour, then less often, up to every 10 minutes. After 90 days with no deposit and no
depositAddresses.get, the sweep stops polling it until the nextget. - Refreshes open payments. For each open session with an active payment, it asks the active leg's adapter for status, like
sessions.refresh(id). This settles legs without provider webhooks (Relay) after the user leaves. It also asks for the status of earlier attempts that the user left withrestartand that still wait. When one of them was paid, the session completes with it. - Expires idle sessions. A session past its expiry moves to
EXPIREDwhen no payment started, or when the active leg still waits for the user (requires_action). The server sendssession.expired. A leg that the provider is processing is not expired: the sweep refreshes it instead. - Polls late payments. A bank transfer or a deposit to an address can arrive after the session expired. When the expired session's active leg has a provider ref and the adapter has
status(), the session goes on the grace list. The sweep polls it everylatePayments.pollMinutes(default 10) forlatePayments.graceHours(default 72). When the payment arrives, the session moves on fromEXPIRED: it completes (or the next leg starts), and the server sendssession.late_payment(withreason: 'after_expiry') and thensession.succeeded. SetgraceHours: 0to turn it off. A provider webhook for the waiting leg has the same effect inside the grace window. Only money that arrives on the leg that waited at expiry moves the session on. After the window (or withgraceHours: 0), a late payment changes the leg data only: the session staysEXPIRED, and the server sendssession.late_paymentwithreason: 'after_grace'. A failure event, an event for an earlier attempt, or a browser request does not change an expired session.
type SweepResult = {
webhooks: { retried: number; delivered: number; dropped: number; pending: number }
sessions: { checked: number; changed: number; expired: number; open: number; grace: number } // grace: expired sessions polled for a late payment
standing: { checked: number; deposits: number } // standing addresses polled, and new deposits (one session each)
}limit (default 50) caps the sessions with webhooks to retry and the open sessions to check in one run. The rest wait for the next run. webhooks.pending is the number of sessions on the outbox queue. A session can stay there for up to 30 seconds after its last event was sent; the next due run removes it.
The outbox queue and the open-session list are store queues: one entry per session id, with an atomic add. A session that is added while a sweep runs is never lost. Each run takes the entries that waited longest, so every open session gets its turn (round robin), also with more than limit open sessions. The run holds a 10-minute lease on what it took, so a second sweep at the same time skips those entries.
At-least-once delivery
A delivery can still arrive more than once, for example after a timeout. A repeat has the same event id. Deduplicate by event id (openramp-id) in your backend. See Credit exactly once.
With admin set, each run also removes days of the admin time index that are older than admin.indexDays.
The server tracks only the sessions it creates. A session goes back on the open-session list after a restart, and when a provider event arrives for it.
Provider health
The server counts, per adapter and hour, quotes that worked or failed, starts that worked or failed, and legs that succeeded or failed. A leg counts once per session, attempt and leg, also when a save is retried. A leg that failed because of the user (for example PAYMENT_FAILED, KYC_REJECTED, PROVIDER_DECLINED or QUOTE_EXPIRED) is not a provider failure. The counts live in the store's KV space (one key per adapter, health:{adapterId}), for routing.healthWindowHours (default 24). A write is one read and one write with no lock: two writes at the same time can lose one update, and a store error only skips the update. A request never fails because of these counts.
An error that is an answer from a working provider is not a failure: REGION_UNSUPPORTED, AMOUNT_TOO_LOW, AMOUNT_TOO_HIGH, NO_QUOTES, QUOTE_EXPIRED, PROVIDER_DECLINED, KYC_REJECTED, BAD_REQUEST, ADDRESS_REJECTED, DESTINATION_NOT_ALLOWED, DESTINATION_LOCKED, CLIENT_UPGRADE_REQUIRED, SESSION_EXPIRED, CANCELED and CONFLICT. Any other error, and a quote timeout, is a failure.
The server uses the counts in three ways:
- Ranking. After the delivered amount and the ETA, quotes with the same values go in the order of the recent success rate: good starts and succeeded legs, out of all starts and leg outcomes. A pathway's rate is the product of its legs' rates. An adapter with fewer than
routing.minSamples(default 20) samples is neutral: it takes the mean rate of the rated adapters in the list. Then the planner's order decides. - Circuit breaker. When more than
routing.breakerFailureRate(default 0.8) of an adapter's quotes and starts in the current and the previous hour failed, with at leastminSamplesof them, the breaker opens.POST /sessions/:id/quotesthen skips every pathway with that adapter, with no network call, and addsPROVIDER_UNAVAILABLEtoerrors. Afterrouting.breakerCooldownMinutes(default 10), one request may quote the adapter (half open). A good quote closes the breaker, and the failures from before do not count again. A failed quote opens it for one more cool-down. Turn it off withrouting.breaker: falseorbreakerFailureRate: 1. Metrics:breaker.opened,breaker.closed,quote.skipped. - Failover at start. When the first leg's
start()throwsPROVIDER_UNAVAILABLEor another error withretryable: true,POST /sessions/:id/selectstarts the next best quote of the same quote request and method from another adapter. The quote must not have expired, and must be insideamountBounds. It must also keep the terms that the user confirmed: the same input amount and asset, the same output asset, and at least 99% of the chosen output. A timeout (an exception, orPROVIDER_UNAVAILABLEwith status 504) never fails over, because the provider may have made the order. The server tries at most 2 more quotes. Each try recordspayment.failover({ from, to, quoteId }) in the timeline and the metricpayment.failover. The answer is the session, with the quote that started inpayment.quoteId. A user error (for exampleAMOUNT_TOO_LOW,KYC_REJECTEDorBAD_REQUEST) never fails over. Whenstart()returned any step, the provider has an order, so the server never starts another provider for that payment. When every try fails, the answer is the first error, and the session is as it was before the select.
openramp.admin.providers() and GET /admin/providers show the counts per adapter:
type ProviderHealth = {
adapterId: string
windowHours: number
counts: { quoteOk: number; quoteFail: number; startOk: number; startFail: number; legSucceeded: number; legFailed: number }
successRate: number | null // null with fewer than minSamples starts and leg outcomes
recentFailureRate: number | null // quotes and starts in the breaker window; null with fewer than minSamples
breaker: { state: 'closed' | 'open' | 'half_open'; openedAt?: string }
}CreateSessionInput
| Field | Type | Default | Description |
|---|---|---|---|
userId | string | required | Your user id. Passed to adapters, and in the backend view of the session (session.userId) in webhooks. |
externalId | string | none | Your own id for the session (for example an order id): 1 to 256 printable characters, unique per app. See externalId. It is in the backend view (session.externalId) and the admin views, never in the browser view. |
direction | 'deposit' | 'withdraw' | 'deposit' | See Withdrawals |
destination | Destination | required for a deposit | Where the money goes. See below. Withdraw: optional. The app sets the destination at creation: { type: 'crypto', chain, token, address, symbol?, decimals? }, { type: 'fiat', currency } or { type: 'lightning', address } (the same shape as the body of POST /sessions/:id/target). The server checks the format, allowedDestinations and screenAddress. A refused destination throws (400, 403 or 503) and no session is made. Without it, the user picks the destination. |
source | WithdrawSource | required for a withdrawal | { chain, token, symbol?, decimals?, custody }: the asset that leaves, and who holds it ('user_wallet' or 'app') |
allowedDestinations | AllowedDestinations | any destination | Withdraw only: { crypto?: { chains? }, fiat?: { currencies? }, lightning?: { domains? } }. See Allowed targets. |
lockDestination | boolean | false | Withdraw with destination only (a deposit destination is always locked). Nobody can change the destination: POST /sessions/:id/target answers 409 DESTINATION_LOCKED. The session shows destinationLocked: true, and the modal skips the target screen. See Locked targets. |
country | string | none | ISO 3166-1 alpha-2. Picks the currency and local methods. |
region | string | none | ISO 3166-2, e.g. US-NY |
email | string | none | Prefilled at providers that support it |
locale | string | none | BCP 47. Picks the modal language; adapters get en when unset. |
amountBounds | { min?, max?, currency } | none | Shown on the amount screen and enforced by the server on what the user pays (see below) |
allowedMethods | string[] | all | Only these methods are planned and quoted |
metadata | Record<string, string> | none | In the backend view of the session (session.metadata) in every webhook. At most 50 keys. A key has at most 40 characters, a value at most 500. |
ttlMinutes | number | 30 | Session lifetime. More than 0 and at most 10080 (7 days). |
The server checks the input and throws a 400 (BAD_REQUEST) when a field is not valid:
userId: a string of 1 to 256 characters.country: two letters.region: at most 16 characters.email: at most 254.locale: at most 35.amountBounds.minandamountBounds.max: decimal strings, for example"25.50".destinationof typecrypto: a CAIP-2chain, a token address ornative, and anaddressthat is valid for the chain (an EVM address must not be the zero address). Other types: an ISO 4217currency.
These checks also apply to the input that authorize returns for POST /sessions.
type Destination =
| { type: 'crypto'; chain: string; token: string; address: string; symbol?: string; decimals?: number;
calls?: ContractCall[]; settlement?: { contract: string } }
| { type: 'merchant'; currency: string; accountRef?: string }
| { type: 'fiat'; currency: string } // withdraw to cash: set by the server from the user's targetchain is CAIP-2 (eip155:8453), token is an address or native. Pass symbol and decimals so the modal can format amounts without a lookup.
settlement pays through an OpenRampSettlement contract. It needs an EVM chain, an EVM address (the recipient) and an ERC-20 token. calls need settlement, and a call cannot send native value. The server answers 400 when one of these rules fails. See On-chain settlement.
Amount bounds
The server checks amountBounds against the input of each quote (what the user pays). It checks only when the bounds currency matches the input: a fiat currency code for fiat inputs, or a token symbol (for example USDC) for crypto inputs. Other inputs pass.
POST /sessions/:id/quotesdrops a quote outside the bounds and addsAMOUNT_TOO_LOWorAMOUNT_TOO_HIGH(withrecovery: 'requote') toerrors.POST /sessions/:id/selectchecks again and answers422with the same code.
Provider limits still apply.
sessions.create returns CreatedSession:
type CreatedSession = { id: string; clientSecret: string; expiresAt: string }
type ExistingSession = { id: string; expiresAt: string; existing: true } // a repeated externalId: no client secretGive clientSecret to the browser. Keep id if you want to look the session up later.
externalId
externalId lets your backend send a create again (for example after a timeout) and get the same session. The rules keep a session, and its client secret, with the caller that made it:
The create repeats an externalId of | Result |
|---|---|
a session that is not final, with the same userId and the same input (all fields; the server compares a hash) | { id, expiresAt, existing: true }. No client secret. |
a session of another userId | 409 EXTERNAL_ID_CONFLICT. No lookup: the answer has no session id, status or secret. |
| a session with other input (destination, amount bounds, metadata, ...) | 409 EXTERNAL_ID_CONFLICT |
a final session (succeeded, failed, canceled, expired, refunded, reversed) | 409 EXTERNAL_ID_CONFLICT |
- The server keeps only a hash of each client secret, so it never gives a secret again. Use the secret from the first create (keep it with your order), or make a pay link with
openramp.sessions.payLink(id). - Two creates at the same time make one session: one gets the client secret, the other gets
existing: true. - Nothing looks a session up by
externalIdfrom the outside: there is no public or admin route for it. Only your server, with the input of the first create, gets the session id back.
verifyWebhook
import { verifyWebhook } from '@openrampkit/server'
await verifyWebhook(secret, headers, rawBody, toleranceSec = 300) // Promise<boolean>
generateWebhookSecret() // 'whsec_...': a new Standard Webhooks secret (32 random bytes)
await signWebhook(secret, id, timestamp, body) // 'v1,<base64>': the signature the server sendsThe webhooks follow Standard Webhooks. verifyWebhook reads the webhook-id, webhook-timestamp and webhook-signature headers. Use it in a service that does not have the openramp instance. See Webhooks to your backend.
WebhookEvent, WebhookEventOf, WebhookEventType, Session and ClientEvent are also exported from @openrampkit/server (they come from @openrampkit/core).
Every response of the handler has the header openramp-version: 1 (the same as apiVersion in webhook events).
Stores
import { memoryStore, durableObjectStore, OpenRampStore, cloudflareKvStore, redisStore, fromNodeRedis, fromNodeRedisV4, scopedKV, VersionConflictError } from '@openrampkit/server'| Export | Description |
|---|---|
memoryStore() | In memory. Development and tests only. |
durableObjectStore(ns, { sessionTtlSec? }) | Cloudflare Durable Objects. Strongly consistent; the production choice on Workers. |
OpenRampStore | The Durable Object class for durableObjectStore. Export it from your Worker entry. |
cloudflareKvStore(ns, { sessionTtlSec? }) | Workers KV, for demos. Eventually consistent: the version check is best effort. |
redisStore(redis, { prefix?, sessionTtlSec? }) | Redis with an atomic version check (Lua). Works with @upstash/redis directly. |
fromNodeRedis(client) | Wraps an ioredis client for redisStore |
fromNodeRedisV4(client) | Wraps a node-redis v4+ client for redisStore |
scopedKV(store, prefix) | A prefixed key-value view, as adapters get |
VersionConflictError | Thrown by put on a version mismatch |
SESSION_SCHEMA | The record schema that this server writes in SessionRecord.schema (now 3) |
migrateRecord(rec) | Brings a stored record up to SESSION_SCHEMA, in place. The server runs it on every store read. See Record schema. |
migratingStore(store) | store, with migrateRecord on every get. The server wraps config.store with it. |
Types: SessionStore, SessionRecord, StoreQueue, QueueItem, TimelineEntry, Resolution, ActiveLeg, StoredQuote, DurableObjectNamespaceLike, DurableObjectStateLike, KVNamespaceLike, RedisLike, NodeRedisLike, NodeRedisV4Like, RedisStoreOptions. See Session stores for the interface and a custom store.
Other exports
Types: OpenRampConfig, CreateSessionInput, CreatedSession, OpenRamp, PayLink, SweepResult, TreasuryHook, TreasurySendInput, AdminConfig, Telemetry, AdminListOptions, AdminListResult, AdminSessionSummary, AdminSession, AdminPayment, AdminLeg, AdminOutboxEvent, AdminStats.
isValidAddress(chain, address) checks an address format for a chain: 0x and 40 hex digits (not the zero address) on EVM chains, base58 of 32 to 44 characters on Solana. The /target route uses it.