Appearance
Quickstart
This gets a fiat-to-crypto backend live on Cloudflare Workers, with D1 for order storage and a cron trigger to settle orders — the recommended setup. You call createFiat(...) once inside the Worker and get the full /quote → /order flow.
Prefer Next.js / Vercel? The same handler runs there too — see Deploy. This page stays on Workers + D1 end to end.
Prerequisites
- A Cloudflare account and
wrangler(npm i -D wrangler). - A Transak account — an API key + API secret. Use staging credentials for local dev (Transak is the only live provider today).
- Node 18+.
1. Install
In your Worker project:
bash
npm install @tokenflight/fiat @tokenflight/fiat-storage-d1
npm install -D @cloudflare/workers-typesThe SDK is ESM-only; its only runtime dependency is zod.
2. Create a D1 database
bash
wrangler d1 create fiatThis prints a database_id — copy it into wrangler.jsonc in the next step.
3. Configure wrangler.jsonc
Bind the D1 database as DB, add a cron trigger for settlement, and set your CORS allowlist:
jsonc
{
"name": "my-fiat-backend",
"main": "src/index.ts",
"compatibility_date": "2026-06-01",
"compatibility_flags": ["nodejs_compat"],
"d1_databases": [{ "binding": "DB", "database_name": "fiat", "database_id": "<paste-from-step-2>" }],
"triggers": { "crons": ["*/2 * * * *"] },
"vars": { "ALLOWED_ORIGINS": "https://app.example.com" },
}DBis the D1 binding the state store reads.triggers.cronsdrivesfiat.cron()— the background job that finishes orders a webhook missed. Every 2 minutes is a good default.ALLOWED_ORIGINSis a fail-closed CORS allowlist (comma-separated). Only these origins can call the browser-facing routes.
4. Write the Worker
env (with your D1 binding and secrets) is only available inside the handler, so build the instance there and reuse it. fetch serves the HTTP contract; scheduled runs the settlement cron.
ts
// src/index.ts
import { createFiat } from "@tokenflight/fiat";
import { transak } from "@tokenflight/fiat/providers/transak";
import { createD1FiatState } from "@tokenflight/fiat-storage-d1";
interface Env {
DB: D1Database;
TOKENFLIGHT_FIAT_SECRET: string;
TRANSAK_API_KEY: string;
TRANSAK_API_SECRET: string;
ALLOWED_ORIGINS: string;
}
let fiat: ReturnType<typeof createFiat> | undefined;
function getFiat(env: Env) {
if (fiat) return fiat;
fiat = createFiat({
basePath: "/v1/fiat",
providers: [
transak({
apiKey: env.TRANSAK_API_KEY,
apiSecret: env.TRANSAK_API_SECRET,
environment: "staging", // switch to "production" when you go live
}),
],
state: createD1FiatState({ database: env.DB }),
cors: {
origins: env.ALLOWED_ORIGINS.split(",")
.map((s) => s.trim())
.filter(Boolean),
},
quoteSecret: env.TOKENFLIGHT_FIAT_SECRET,
});
return fiat;
}
export default {
fetch: (req: Request, env: Env, ctx: ExecutionContext) => getFiat(env).fetch(req, { env, ctx }),
scheduled: (_e: ScheduledEvent, env: Env, ctx: ExecutionContext) => ctx.waitUntil(getFiat(env).cron({ limit: 50 }, { env, ctx })),
};That's the whole service: a provider (where crypto is bought), the D1 state store (where orders live), a CORS allowlist, and a quote secret (seals prices so they can't be tampered with between quote and order). See Configuration for every option.
5. Apply the D1 schema
@tokenflight/fiat-storage-d1 ships the schema. Apply it before serving traffic — locally now, and to the remote D1 before you deploy:
bash
# local D1 (for wrangler dev)
wrangler d1 execute fiat --local \
--file node_modules/@tokenflight/fiat-storage-d1/migrations/0001_initial.sql
# remote D1 (before deploy)
wrangler d1 execute fiat --remote \
--file node_modules/@tokenflight/fiat-storage-d1/migrations/0001_initial.sqlThe migration is idempotent (CREATE TABLE IF NOT EXISTS), so re-running is safe. See State & storage for the schema.
6. Set secrets
Secrets never go in wrangler.jsonc. For production, use wrangler secret put:
bash
wrangler secret put TOKENFLIGHT_FIAT_SECRET # any high-entropy string
wrangler secret put TRANSAK_API_KEY
wrangler secret put TRANSAK_API_SECRETFor local dev, put the same keys in a .dev.vars file (git-ignored) — wrangler dev loads it automatically:
ini
# .dev.vars
TOKENFLIGHT_FIAT_SECRET=dev-secret-change-me
TRANSAK_API_KEY=your-transak-staging-key
TRANSAK_API_SECRET=your-transak-staging-secret7. Run it locally
bash
wrangler devThe contract is now at http://localhost:8787/v1/fiat. Price a purchase — POST /quote returns one quote per configured provider, each carrying an opaque, sealed quoteId:
bash
BASE="http://localhost:8787/v1/fiat"
curl -sS -X POST "$BASE/quote" \
-H 'content-type: application/json' \
-H 'origin: https://app.example.com' \
-d '{
"fiatAmount": "100",
"fiatCurrency": "USD",
"outputAsset": { "chainId": 8453, "address": "0x833589fcd6edb6e08f4c7c32d4f71b54bda02913" },
"recipient": "0x1111111111111111111111111111111111111111"
}'Why the origin header?
Transak requires a browser origin and an end-user IP with every quote and order — a request missing either fails with CONTEXT_REQUIRED. Browsers send Origin automatically; from curl you pass one of your ALLOWED_ORIGINS yourself. The IP comes from the platform's CF-Connecting-IP header, which wrangler dev and deployed Workers set for you — on other hosts see Security.
The response is { "quotes": [...] } — pick one (there is one entry per provider; pass "provider" in the request to pin a single provider). Turn it into an order — hand that quote's quoteId back verbatim to POST /order:
bash
curl -sS -X POST "$BASE/order" \
-H 'content-type: application/json' \
-H 'origin: https://app.example.com' \
-d '{ "quoteId": "fq1..3fkQ9…" }'The response carries an action telling the frontend how to open checkout:
json
{
"orderId": "fiat_order_01J…",
"status": "pending",
"provider": "transak",
"action": { "type": "wrapper", "url": "http://localhost:8787/v1/fiat/widget/fiat_order_01J…" }
}Open or iframe action.url to hand the user into the provider's checkout, then poll GET /order/{orderId} until status is terminal (completed, failed, canceled, or expired). The scheduled cron advances anything a webhook missed. Full request/response detail is in Calling the API.
8. Deploy
With the remote D1 migrated (step 5) and secrets set (step 6):
bash
wrangler deployYour contract is now live at https://my-fiat-backend.<your-subdomain>.workers.dev/v1/fiat. Point Transak's dashboard webhook at .../v1/fiat/webhook/transak so orders progress on provider events too.
What's next
- Configuration — every
createFiatoption, in depth. - Calling the API — request/response for every route.
- Widget & wrapper — render checkout and handle the bridge.
- Jump routes — buy an intermediate asset and settle to any target via Hyperstream.
- Deploy — the full Workers walkthrough, plus Next.js / Vercel.