Developers

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.

MethodHTTPReturns
onramp.catalogGET /gateway/onramp/catalogOnrampCatalogResponse
onramp.fiatsGET /gateway/onramp/fiatsOnrampFiatsResponse
onramp.cryptoGET /gateway/onramp/cryptoOnrampCryptoResponse
onramp.countriesGET /gateway/onramp/countriesOnrampCountriesResponse
onramp.paymentMethodsGET /gateway/onramp/payment-methodsOnrampPaymentMethodsResponse
onramp.quotePOST /gateway/onramp/quoteOnrampQuoteResponse
onramp.createSessionPOST /gateway/onramp/sessionsOnrampSessionResponse
onramp.getSessionGET /gateway/onramp/sessions/{id}OnrampSessionResponse
onramp.listOwnerSessionsGET /gateway/onramp/owners/{owner}/sessionsOnrampSessionListResponse
onramp.setSessionStatePUT /debug/gateway/onramp/sessions/{id}/stateOnrampSessionResponse

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 resolved

Returns

OnrampCatalogResponse{ provider, country, suggestedFiat, fiats[], crypto[], paymentMethods[] }.

  • country is an OnrampCountryResolutionDto ({ 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.
  • suggestedFiat is the code to preselect (also moved to the front of fiats).
  • 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()
MethodReturnsExtra options
onramp.fiatsOnrampFiatsResponse{ provider, country, suggested, fiats[] }
onramp.cryptoOnrampCryptoResponse{ provider, country, crypto[] }fiat? to constrain the pairs
onramp.paymentMethodsOnrampPaymentMethodsResponse{ provider, country, paymentMethods[] }fiat?, crypto? to scope methods
onramp.countriesOnrampCountriesResponse{ 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 received

Returns

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. Throws BackendError carrying 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 checkout

Returns

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; state is SESSION_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))
  }
}
Note

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"
StateTerminalMeaning
SESSION_PENDINGnoCreated; checkout open, waiting for payment + deposit.
SESSION_COMPLETEDyesFunds delivered to the destination. Success.
SESSION_EXPIREDyesThe session/checkout window elapsed before completion.
SESSION_FAILEDyesPayment declined, cancelled, refunded, or an internal failure — see failureCode.
  • OnrampSessionState — the enum (Pending / Completed / Expired / Failed → the strings above).
  • ONRAMP_TERMINAL_STATES — a ReadonlySet of the three terminal strings.
  • isOnrampTerminalState(state)true when 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",
})
Warning

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)
}
  • Cash (onramp) — the Gateway Kit rail that drives this surface end to end.
  • API client — how projectKey flows into the authenticated projectHttpClient.
  • Gateway endpoints — the UDA lifecycle + pricing surface the onramp settles through.
  • Errors & retriesBackendError shape, the 304 conditional-read branch, retry policy for polling loops.
Was this page helpful?