Onramp endpoints
stridge.onramp is the buy-crypto-with-fiat surface — the SDK side of the Kit's Cash rail. Ten methods, all going through projectHttpClient: each call sends the configured projectKey as X-Gateway-Key, the same auth as gateway.* and balance.*.
The shape mirrors the user journey: browse the catalog (what fiats / crypto / payment methods are available for the country), price a quote, create a session (which binds the quote, allocates the deposit address, and returns the provider's hosted-checkout URL), then poll that session until it reaches a terminal state.
| Method | HTTP | Returns |
|---|---|---|
onramp.catalog | GET /gateway/onramp/catalog | OnrampCatalogResponse |
onramp.fiats | GET /gateway/onramp/fiats | OnrampFiatsResponse |
onramp.crypto | GET /gateway/onramp/crypto | OnrampCryptoResponse |
onramp.countries | GET /gateway/onramp/countries | OnrampCountriesResponse |
onramp.paymentMethods | GET /gateway/onramp/payment-methods | OnrampPaymentMethodsResponse |
onramp.quote | POST /gateway/onramp/quote | OnrampQuoteResponse |
onramp.createSession | POST /gateway/onramp/sessions | OnrampSessionResponse |
onramp.getSession | GET /gateway/onramp/sessions/{id} | OnrampSessionResponse |
onramp.listOwnerSessions | GET /gateway/onramp/owners/{owner}/sessions | OnrampSessionListResponse |
onramp.setSessionState | PUT /debug/gateway/onramp/sessions/{id}/state | OnrampSessionResponse |
Every method accepts an optional final options argument that takes an AbortSignal (and, where it makes sense, server-side filters). Country resolution is shared across the reads: pass an explicit country (ISO-3166-1 alpha-2), or a softer clientCountry hint that maps to the X-Client-Country header; absent both, the gateway resolves via GeoIP, then the tenant default. All return types are unwrapped — the SDK strips the { data, success, message } envelope for you.
onramp.catalog
One-call catalog for first paint — the fiats the user can pay in, the crypto they can buy, and the payment methods available, all for the resolved country. Use this to populate a currency picker without fanning out to the scoped reads.
Signature
onramp.catalog(
options?: {
provider?: string // provider hint, e.g. "banxa"; defaults to the tenant's preferred provider
country?: string // explicit ISO-3166-1 alpha-2; wins over clientCountry / GeoIP
clientCountry?: string // softer hint → X-Client-Country header
fiat?: string // scope payment-method limits/fees + crypto pairs to this fiat
signal?: AbortSignal
},
): Promise<OnrampCatalogResponse>Example
const catalog = await stridge.onramp.catalog({ country: "US" })
console.log(catalog.suggestedFiat) // "USD" — preselect this
console.log(catalog.fiats.map((f) => f.code)) // ["USD", "EUR", …]
console.log(catalog.country.source) // "query" — how the country resolvedReturns
OnrampCatalogResponse — { provider, country, suggestedFiat, fiats[], crypto[], paymentMethods[] }.
countryis anOnrampCountryResolutionDto({ code, source, confidence, warning? }) echoing how the country was resolved ("query" | "header" | "geoip" | "tenant" | …) so the UI can warn on a low-confidence GeoIP miss.suggestedFiatis the code to preselect (also moved to the front offiats).fiats[]→{ code, name, symbol };crypto[]→ one entry per(code, networkId)with{ code, name, networkId, decimals, eip155, logo, minDepositAmount, contractAddress?, … };paymentMethods[]→{ id, name, fiats[], minFiat?, maxFiat?, feePercent?, processingTime?, … }.
Scoped catalog reads
When you only need one slice — or need to re-scope after the user picks a fiat / crypto — call the focused reads instead of the full catalog. They share the provider / country / clientCountry options above.
const { fiats, suggested } = await stridge.onramp.fiats({ country: "US" })
const { crypto } = await stridge.onramp.crypto({ fiat: "USD" })
const { paymentMethods } = await stridge.onramp.paymentMethods({ fiat: "USD", crypto: "USDC" })
const { countries } = await stridge.onramp.countries()| Method | Returns | Extra options |
|---|---|---|
onramp.fiats | OnrampFiatsResponse — { provider, country, suggested, fiats[] } | — |
onramp.crypto | OnrampCryptoResponse — { provider, country, crypto[] } | fiat? to constrain the pairs |
onramp.paymentMethods | OnrampPaymentMethodsResponse — { provider, country, paymentMethods[] } | fiat?, crypto? to scope methods |
onramp.countries | OnrampCountriesResponse — { provider, countries[] } | provider? only |
onramp.quote
Prices a fiat amount into the destination crypto. Only the fiat side is sent — the crypto is derived server-side from the tenant's deposit preferences, so the quote always lands the asset your gateway settles.
Signature
onramp.quote(
payload: {
fiatCurrency: string // e.g. "USD"
fiatAmount: string // positive decimal string, e.g. "100"
country?: string // resolved server-side when omitted
paymentMethodId?: string // scope fees/limits to a method
},
options?: { clientCountry?: string; signal?: AbortSignal },
): Promise<OnrampQuoteResponse>Example
const quote = await stridge.onramp.quote({ fiatCurrency: "USD", fiatAmount: "100" })
console.log(quote.assetName, quote.assetAmount) // "USDC", "99.7"
console.log(quote.rate) // "1.003" — fiat per 1 unit of crypto
console.log(quote.fees.total) // total fee on top of the crypto receivedReturns
OnrampQuoteResponse — { provider, fiatCurrency, fiatAmount, assetName, assetAmount, rate, fees }. fees is { provider?, network?, total } (the provider / network legs are omitted when zero; total is always present). quoteId and expiresAt are present only for providers that return them — treat them as optional.
Status codes
200— quote priced.422— the amount is outside the provider's accepted range, or no route could be priced. ThrowsBackendErrorcarrying the provider's own validation message.
onramp.createSession
Binds a quote, allocates the deposit address, and opens a provider checkout order — returning the hosted-checkout URL you redirect the user to. The created session always starts at SESSION_PENDING.
Signature
onramp.createSession(
payload: {
owner: string // your end-user id; owner-scoped keys may only create under their own owner
fiatCurrency: string // e.g. "USD"
fiatAmount: string // positive decimal string
destinationAsset: string // asset the user receives, e.g. "USDC"
destinationNetwork: string // network id from catalog crypto[].networkId
destinationAddress: string // the user's wallet; must be allowed for the tenant, else 403
country?: string
paymentMethodId?: string
redirectUrl?: string // return URL after checkout; falls back to the tenant default
},
options?: {
clientCountry?: string
idempotencyKey?: string // Idempotency-Key header — a retried create returns the same session
signal?: AbortSignal
},
): Promise<OnrampSessionResponse>Example
const session = await stridge.onramp.createSession(
{
owner: userId,
fiatCurrency: "USD",
fiatAmount: "100",
destinationAsset: "USDC",
destinationNetwork: "60",
destinationAddress: walletAddress,
redirectUrl: window.location.href,
},
{ idempotencyKey: `onramp-${userId}-${Date.now()}` },
)
window.open(session.checkoutUrl, "_blank") // hand off to the hosted checkoutReturns
OnrampSessionResponse (see The session shape). The checkoutUrl is present on create; the etag is the validator for cheap conditional polling.
Status codes
201— session created;stateisSESSION_PENDING.403— the destination address is not allowed/owned for this tenant (destination control).409— an active session already exists for the deposit address, or slippage exceeded during binding.422— no provider or deposit asset matched the route.
onramp.getSession
Reads a session by id. The canonical poll while the checkout is open.
Signature
onramp.getSession(
sessionId: string,
options?: { ifNoneMatch?: string; signal?: AbortSignal },
): Promise<OnrampSessionResponse>Example — poll until terminal
import { BackendError, isOnrampTerminalState } from "@stridge/sdk"
async function pollUntilTerminal(sessionId: string, etag?: string) {
for (;;) {
try {
const session = await stridge.onramp.getSession(sessionId, { ifNoneMatch: etag })
etag = session.etag
if (isOnrampTerminalState(session.state)) return session
} catch (err) {
// A matching ETag surfaces as BackendError(304) — nothing changed, keep polling.
if (!(err instanceof BackendError && err.statusCode === 304)) throw err
}
await new Promise((r) => setTimeout(r, 2_000))
}
}Reads honor If-None-Match for cheap ETag polling: pass the etag from the previous response and a matching read returns 304 Not Modified, which surfaces as a BackendError with statusCode === 304. Treat it as a "nothing changed" branch in your loop. Recommended cadence is one call every 2 seconds while a session is pending; stop once isOnrampTerminalState(state) is true.
onramp.listOwnerSessions
Owner-scoped, filterable, paginated session history — the read for a dashboard or an account "purchases" tab. Read-through cached and invalidated on every write.
Signature
onramp.listOwnerSessions(
owner: string,
options?: {
state?: string | string[] // filter by state; an array joins to a comma list
provider?: string
destinationAsset?: string
destinationNetwork?: string
fiat?: string
country?: string
q?: string // free-text search
from?: string // created-at lower bound (RFC3339)
to?: string // created-at upper bound (RFC3339)
sort?: string
order?: "asc" | "desc"
limit?: number
offset?: number
includeTotals?: boolean // include aggregate totals in the response
signal?: AbortSignal
},
): Promise<OnrampSessionListResponse>Example
const { sessions, pageInfo } = await stridge.onramp.listOwnerSessions(userId, {
state: ["SESSION_COMPLETED", "SESSION_FAILED"],
limit: 20,
})Returns
OnrampSessionListResponse — { sessions[], pageInfo, totals? }. Each row is the flat OnrampSessionListItemDto (a richer shape than the create/get response): { sessionId, state, provider, owner, fiatCurrency, fiatAmount, destinationAsset, destinationNetwork, destinationAddress, depositAsset, udaAddress, providerOrderId, providerStatus, depositTxHash, failureCode, createdAt, updatedAt, completedAt?, expiresAt, version }. totals (count, byState, fiatVolume) is present only when includeTotals: true.
The session shape
createSession and getSession return OnrampSessionResponse, whose flow field summarizes the three legs of the purchase:
interface OnrampSessionResponse {
sessionId: string
state: OnrampSessionState // create always returns "SESSION_PENDING"
checkoutUrl?: string // provider-hosted page; present on create
providerOrderId?: string
externalOrderId: string
flow: {
fiat: { currency: string; amount: string } // what the user pays
udaEntry: { address: string; networkId: string; asset: string } // crypto the provider deposits, + the address
destination: { address: string; networkId: string; asset: string; amount: string } // what the user receives, and where
}
expiresAt?: string
etag: string // pass back as If-None-Match for cheap polling
}flow.udaEntry is the Stridge Universal Deposit Address the provider settles crypto to; the gateway then swaps/bridges it to flow.destination — so a card buyer ends up with the same asset, on the same network, as an on-chain depositor.
Session state & terminal helpers
The SDK ships the state enum, the terminal-state set, and a guard so you never hard-code state strings:
import { OnrampSessionState, ONRAMP_TERMINAL_STATES, isOnrampTerminalState } from "@stridge/sdk"| State | Terminal | Meaning |
|---|---|---|
SESSION_PENDING | no | Created; checkout open, waiting for payment + deposit. |
SESSION_COMPLETED | yes | Funds delivered to the destination. Success. |
SESSION_EXPIRED | yes | The session/checkout window elapsed before completion. |
SESSION_FAILED | yes | Payment declined, cancelled, refunded, or an internal failure — see failureCode. |
OnrampSessionState— the enum (Pending/Completed/Expired/Failed→ the strings above).ONRAMP_TERMINAL_STATES— aReadonlySetof the three terminal strings.isOnrampTerminalState(state)—truewhen polling should stop.
When state is SESSION_FAILED, the failure carries an OnrampFailureCode (also exported) — e.g. payment_declined, cancelled, refunded, deposit_timeout, provider_expired, quote_failed, slippage_check_failed, no_provider_for_route, destination_not_allowed, concurrent_session_exists. Treat it as a loose string union — the backend may add codes ahead of the SDK.
setSessionState
A non-production override that drives a session straight to a terminal state without completing the real provider checkout — for deterministic integration tests against a third-party hosted page that is awkward to automate.
Signature
onramp.setSessionState(
sessionId: string,
payload: { state: OnrampSessionState; failureCode?: string },
options?: { signal?: AbortSignal },
): Promise<OnrampSessionResponse>Example
// Drive a pending session to success…
await stridge.onramp.setSessionState(session.sessionId, { state: "SESSION_COMPLETED" })
// …or to a specific failure:
await stridge.onramp.setSessionState(session.sessionId, {
state: "SESSION_FAILED",
failureCode: "payment_declined",
})This route lives under the /debug prefix and is not registered in production — it returns 404 there, so it can never short-circuit a real purchase. It uses the same gateway-key auth as every other onramp call. Point the SDK at env: "dev" to use it.
Cancellation
Every method accepts options.signal. Aborting it cancels the in-flight fetch; the SDK wraps the abort error in a BackendError. Use this for long-lived polling loops or per-request timeouts.
const controller = new AbortController()
const timer = setTimeout(() => controller.abort(), 10_000)
try {
const session = await stridge.onramp.getSession(sessionId, { signal: controller.signal })
// …
} finally {
clearTimeout(timer)
}Related
- Cash (onramp) — the Gateway Kit rail that drives this surface end to end.
- API client — how
projectKeyflows into the authenticatedprojectHttpClient. - Gateway endpoints — the UDA lifecycle + pricing surface the onramp settles through.
- Errors & retries —
BackendErrorshape, the304conditional-read branch, retry policy for polling loops.