Are you an LLM? Read llms.txt for a summary of the docs, or llms-full.txt for the full context.
Fee Sponsorship – 1auth
Skip to content

Fee Sponsorship

Every user-initiated intent authenticates with the calling app's JWT. The app independently chooses, per intent, whether to sponsor (pay the gas/bridge fees) or to let the user pay from their source assets.

Sponsored vs. user-paid

ModeSDK callApp JWTExtension tokenWho pays
Sponsored (default)sendIntent({ ... })requiredrequiredapp
User-paidsendIntent({ ..., sponsor: false })requirednot fetcheduser

The sponsor flag is an explicit per-call decision — setting it to false skips the extension-token fetch entirely, saving one HTTP round trip when the user is paying.

Set up sponsorship on the server

The SDK client signs every intent with a short-lived JWT minted by your backend. You expose two endpoints; the SDK calls them same-origin with the user's session cookie attached.

Environment variables

Create a Rhinestone app in the dashboard and copy these values into your server environment:

VariableDescription
RHINESTONE_JWT_PRIVATE_KEYJSON-encoded JWK (EC P-256 / ES256 recommended, RSA accepted)
RHINESTONE_INTEGRATOR_IDYour integrator handle
RHINESTONE_PROJECT_IDProject ID the app belongs to
RHINESTONE_APP_IDApp ID used as the JWT app_id claim
RHINESTONE_KEY_IDKey ID registered with the orchestrator (kid header)

Next.js App Router

// app/api/sponsorship/access-token/route.ts
import { NextResponse } from "next/server";
import { createSponsorshipSigner } from "@rhinestone/1auth/server";
import { getSession } from "@/lib/session"; // your auth
 
const signer = createSponsorshipSigner();
 
export async function GET() {
  const session = await getSession();
  if (!session.userId) return NextResponse.json({ error: "unauthorized" }, { status: 401 });
  return NextResponse.json({ token: await signer.accessToken() });
}
// app/api/sponsorship/extension-token/route.ts
import { NextRequest, NextResponse } from "next/server";
import { createSponsorshipSigner } from "@rhinestone/1auth/server";
import { getSession } from "@/lib/session";
 
const signer = createSponsorshipSigner();
 
export async function POST(req: NextRequest) {
  const session = await getSession();
  if (!session.userId) return NextResponse.json({ error: "unauthorized" }, { status: 401 });
 
  const { intentOp } = await req.json();
  return NextResponse.json({ token: await signer.extensionToken(intentOp) });
}

createSponsorshipSigner() reads all five env vars automatically. Pass { credentials: { ... } } to override them, or { shouldSponsor } to filter which intents you'll sponsor.

Other frameworks

The factory returns plain async functions, so it works with any HTTP framework. Express example:

import express from "express";
import { createSponsorshipSigner } from "@rhinestone/1auth/server";
 
const signer = createSponsorshipSigner();
const app = express();
 
app.get("/api/sponsorship/access-token", requireSession, async (_req, res) => {
  res.json({ token: await signer.accessToken() });
});
 
app.post("/api/sponsorship/extension-token", requireSession, async (req, res) => {
  res.json({ token: await signer.extensionToken(req.body.intentOp) });
});

Advanced: custom claims

If you need a custom audience or additional claims, drop down to the raw createJwtSigner from @rhinestone/sdk/jwt-server, also re-exported from @rhinestone/1auth/server.

Configure sponsorship on the client

Point the SDK at the two endpoints you just created:

import { OneAuthClient } from "@rhinestone/1auth";
 
const client = new OneAuthClient({
  providerUrl: "https://passkey.1auth.box",
  sponsorship: {
    accessTokenUrl: "/api/sponsorship/access-token",
    extensionTokenUrl: "/api/sponsorship/extension-token",
  },
});

sponsorship is required whenever the client submits user intents — there is no anonymous fallback. A client without sponsorship configured will reject sendIntent calls with MISSING_APP_CREDENTIALS.

Demo

User-paid intent (sponsor: false)

client.sendIntent({
  targetChain: 84532,
  calls: [{ to: MockUSD, data: transfer(0x180b..., 100000) }],
  tokenRequests: [{ token: MockUSD, amount: 100000 }],
  sourceChainId: 84532,
  sponsor: false,
})