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
| Mode | SDK call | App JWT | Extension token | Who pays |
|---|---|---|---|---|
| Sponsored (default) | sendIntent({ ... }) | required | required | app |
| User-paid | sendIntent({ ..., sponsor: false }) | required | not fetched | user |
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:
| Variable | Description |
|---|---|
RHINESTONE_JWT_PRIVATE_KEY | JSON-encoded JWK (EC P-256 / ES256 recommended, RSA accepted) |
RHINESTONE_INTEGRATOR_ID | Your integrator handle |
RHINESTONE_PROJECT_ID | Project ID the app belongs to |
RHINESTONE_APP_ID | App ID used as the JWT app_id claim |
RHINESTONE_KEY_ID | Key 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,
})