Restrict sponsorship issuance and bound spend at the project and organization levels.
Treat sponsorship authority as permission to spend your organization’s money. Authenticate callers, validate every requested intent on the backend, and configure spend limits as a separate backstop.
createSponsorshipSigner({ shouldSponsor }) runs its filter before Rhinestone receives an extension token. For EVM, create the signer for the authenticated request, using the account address your application session is allowed to use:
import { createSponsorshipSigner } from "@rhinestone/wallet/server";const signer = createSponsorshipSigner({ shouldSponsor: { chain: ({ id }) => SPONSORED_CHAIN_IDS.has(id), account: (address) => address.toLowerCase() === accountAddress.toLowerCase(), calls: (calls) => calls.every( ({ to, value, data }) => ALLOWED_CONTRACTS.has(to.toLowerCase()) && ALLOWED_SELECTORS.has(data.slice(0, 10)) && value <= MAX_CALL_VALUE, ), },});
The destination chain, account, and destination calls must all pass. Follow Set up to connect the signer to your token endpoints.This section describes the embedded-wallet SDK’s canonical input, not the raw Caucasus HTTP schema. createSponsorshipSigner() accepts a canonical draft or a prepared envelope containing intentInput, and normalizes it before running shouldSponsor.Use shouldSponsor.intent to validate the complete normalized input. Narrow on "contractVersion" in input before reading singular fields such as account.svm, source, destination, and options; legacy inputs use destinationChainId, account.address, and destinationExecutions. The current singular approval contract is sdk-3.0.0-caucasus; unknown or retired contracts are refused. See the migration guide for compatible versions.The individual chain, account, calls, and app-fee filters do not validate source assets, delivery amounts, recipients, or instruction data. Validate these through the full intent filter when they affect your policy.For each EVM extension-token request:
Authenticate the caller with your normal application session.
Resolve the smart account that your session is allowed to use.
Validate policy fields outside the built-in filter against the complete intent payload.
Configure the account filter with the resolved address.
Check every destination target, function selector, calldata rule, and value limit your product requires.
Return 403 without minting a token when any check fails.
verifyWalletAccount() from @rhinestone/wallet/server can confirm that an address is recognized by the embedded wallet provider. Your application session must still establish that the current user is allowed to use it.
For Solana-only setup and spends, chain and intent filters apply. The chain callback receives the destination ID: 792703809 for Solana mainnet and 792703810 for devnet. Presence of any EVM account, calls, or appFees filter rejects the input, even if that callback would return true. The EVM example above therefore does not allow Solana.Use an authenticated, VM-aware policy that binds account.svm and the intended Swig, cluster, and authority to the session. Compare base58 addresses exactly. Validate the full source, destination, options, and ordered instruction data; do not authorize arbitrary instructions from a program allowlist alone.The helper accepts Swig creation and authority additions only for secp256r1 passkeys with all or secp256k1 recovery keys with manageAuthority. Authority removal and other permissions are refused. Accepted shapes are not proof that an authority belongs to the user: your policy must check the exact key and purpose. Managed setup fees and rent are app-paid, without user fallback.See Solana setup and recovery and authority limits.
Rhinestone verifies that the JWT signing key belongs to the project, that access and extension token identity claims match, and that the extension token’s jti has not already been used. The extension token also carries the canonical intent digest.An issuance filter alone does not prevent a client from changing the payload afterward. The embedded wallet browser flow returns the extension token to the client, so use compatible client and server helpers that issue intent-scoped grants under the matching approval contract. Do not infer intent binding from JWT authentication or a version header alone.
CORS and cookie attributes only constrain compatible browsers. They do not
stop a script from calling a public endpoint. Use them as browser hardening,
not as authorization.
Rotate signing keys by registering a new kid, switching issuance, then disabling the old key after outstanding tokens expire.Do not switch a rejected sponsored transaction to user-paid at submission time. With embedded wallets, sponsorshipMode: "required" remains fail-closed; to fall back, send a new intent with "disabled" and an explicit source, which the user reviews before signing. This send fallback does not apply to managed Solana setup, which is app-paid only. See Sponsor fees before enabling fallback.
With backend-held API keys, bind the request to the authenticated user’s account and validate every field that affects your policy before preparing a transaction. This EVM application-owned handler is a policy skeleton, not an SDK transaction or JWT approval input:
const session = await requireApplicationSession(request);const transaction = await request.json();const accountAddress = await accountForSession(session);if ( transaction.accountAddress.toLowerCase() !== accountAddress.toLowerCase() || !policyAllows(transaction)) { return Response.json({ error: "not sponsorable" }, { status: 403 });}// Continue with the backend-held Rhinestone SDK only after validation.
Resolve the SDK account from the session rather than trusting a client-supplied address. Check the selected source.chain and source.token, any maxAmount, auxiliary funds and source calls, and the nested destination’s chain, token, amount, recipient, calls, and gas limit. Validate sponsorship, app fees, protocol fees, deadlines, and route filters too. Do not accept an already prepared payload without applying the same checks. Keep final validation and submission on your backend when clients are not trusted.
For Solana, bind the managed wallet, Swig, cluster, and intended authority to the session. Compare base58 addresses exactly; do not lowercase them like EVM addresses. Validate selected mints, source debit caps, delivery amount and recipient, sponsorship, and any requested fees. Solana-origin requests do not accept EVM auxiliary funds, source calls, deadlines, or route filters.For instruction execution, allowlist programs and check ordered account metadata, writable/signing flags, decoded instruction data, recipients, transfer amounts, and address lookup tables. A program allowlist alone is insufficient. Deployment installs a permanent root; authority management can grant takeover power. Authorize these operations explicitly, separately from ordinary transfers, and account for sponsorship and rent costs. See Solana readiness.
For experimental_jwt clients, deploy backend helpers that accept sdk-3.0.0-caucasusbefore upgrading clients. The SDK passes getIntentExtensionToken the versioned approval input once per sponsored prepare, before requesting a quote. It does not request or present the grant at submission, including submission of restored prepared data. Denial or failure stops preparation without an unsponsored fallback.Validate the complete approval input: contractVersion, per-VM account, optional singular source, nested destination, and options. Bind account.evm.address to the authenticated session for EVM flows and validate the account configuration, not just its address. Approval fields use wire values: CAIP-2 chain IDs, native address strings, decimal-string amounts, and destination.execution.calls, not the SDK request’s viem chains and bigint amounts.Sponsorship appears only at options.sponsorship, with gas, bridgeFees, swapFees, and protocolFees. These are wire names; SDK requests still use top-level sponsored with bridging and swaps. Preserve explicit false values and array order, omit absent source, and default absent options to {}. Intent-scoped grants bind the lowercase SHA-256 digest of RFC 8785 canonical JSON. See the canonical input and digest contract.shouldSponsor from @rhinestone/sdk/jwt-server checks only the destination chain, account address, and destination calls (or HyperCore settlement calls). It does not authorize the source, delivery token or amount, recipient, account setup, or options. Check those separately against the complete input before issuing a grant. For Solana, validate wire account.svm and destination.execution directly: the helper does not inspect instructions or authority changes. It selects the EVM account address when both entries exist, otherwise the SVM address; an empty calls list must not authorize arbitrary Solana execution. Reject unsupported contracts or fields; do not translate a v3 input into legacy field names to reuse an old policy.A rejected sponsored request does not silently become user-paid. Prepare a new self-funded quote and obtain user approval if your application offers that fallback. See Sponsor fees for selective sponsorship controls.
Before issuing a singular intent-bound grant, authenticate the session, bind the per-VM accounts to it, and validate the selected source, nested destination.execution, recipient, and options against your policy. Binding preserves the approved request; it does not establish that your policy safely permits arbitrary client-supplied calls.For singular Caucasus HTTP requests using sponsorship scope intent, the orchestrator recomputes the approved digest from the received body when sponsorship is requested. A digest mismatch or unsupported binding is rejected. This enforcement is conditional on scope and a matching approval contract, not a guarantee for every client-held token. See REST API setup and the canonical input and digest contract.Policy rejection, digest mismatch, or unsupported binding fails closed. Revalidate the exact supported body and issue a fresh matching grant where permitted; do not bypass binding or switch to user-paid at submission. A user-paid alternative needs a new quote and user approval.
Admission based on recorded month-to-date spend plus the projected charge, across every project during the UTC calendar month.
Gas per intent
Selected project
Sponsored gas for one intent.
Bridge fee per intent
Selected project
Sponsored bridge fees for one intent.
Total per intent
Selected project
The total sponsorship charge for one intent, including the sponsorship surcharge and any sponsored protocol fee.
Set an organization-wide monthly usage limit in the Dashboard.
Unset limits are unlimited. Project caps do not divide or reserve the organization budget; every project draws from the same organization sponsorship account. The monthly limit does not reserve funds between concurrent intents, so simultaneous admissions can exceed it before their charges are recorded. Testnet sponsorship is not metered against these mainnet limits.
Request validation answers “should this intent be sponsored?” Budgets answer
“can this sponsorship account pay for it?” Use both.
Backend validation or shouldSponsor rejects the intent
No sponsored transaction or extension token is produced.
Keep the flow unsponsored or reject it according to your product policy.
Token is expired, mismatched, or replayed
Rhinestone rejects authentication or the sponsorship grant.
Mint a fresh token for the unchanged request. Re-evaluate policy before retrying.
Intent-bound grant digest mismatches or binding is unsupported
The sponsored quote is rejected.
Revalidate the supported body and policy before issuing a fresh matching grant.
Project per-intent cap is exceeded
The sponsored quote is rejected.
Reduce the sponsored scope or request a new user-paid quote.
Organization monthly limit is reached
Sponsorship is rejected across the organization’s projects.
Raise the limit or request a new user-paid quote.
Billing is suspended or available credits are exhausted without active billing
Sponsorship is rejected.
Restore billing or request a new user-paid quote.
Funding affects the quote and the operations the user authorizes. A user-paid fallback always needs a new quote and user approval. Managed Solana setup has no such fallback; restore sponsorship or leave setup unfinished.