Skip to main content

Sponsored and multi-party transactions

Most transactions are built, signed, and paid for by one wallet. But those are three separable roles, and pulling them apart unlocks two patterns that matter for real applications: letting your server build and co-sign a transaction the user only adds their signature to, and sponsoring fees so a user can transact before they hold any ADA.

The mechanism underneath both is the same. A Cardano transaction is built once, then carries a set of witnesses (signatures); the ledger accepts it once every required witness is present. CIP-30 partial signing is what makes this practical in a browser: a wallet can add just its own signature to a transaction it did not build, without finalizing it. So one party can construct a transaction and another can authorize it, in any combination.

Co-signed (multi-party) transactions

The canonical case is a service where the user pays but your application is in control of something. An NFT minting service is the clearest example: the user provides the inputs that cover the cost, but the minting policy belongs to your app, so the transaction needs both signatures. You never hand the user your policy key, and the user never hands you the right to move their funds; you each sign the same transaction.

The flow is always the same four steps:

The server builds the transaction with the user's UTXOs as inputs (so the user pays) and the app's own action (here, a mint under the app's policy). The unsigned transaction goes to the browser, the user's wallet partial-signs it, and the result comes back to the server, which adds its own signature and submits.

import { Transaction, type UTxO } from "@evolution-sdk/evolution"

// appClient: backend wallet that owns the policy. userClient: the browser CIP-30 wallet.
declare const userUtxos: UTxO.UTxO[] // selected by the browser, sent to the server
declare const appPolicy: any // app's native signature policy (requires the app's signature)
declare const mintedNft: any
declare const userAddress: any

// 1. SERVER builds: the user's inputs pay, the app's policy mints
const tx = await appClient
.newTx()
.collectFrom({ inputs: userUtxos })
.mintAssets({ assets: mintedNft })
.attachScript({ script: appPolicy })
.payToAddress({ address: userAddress, assets: mintedNft })
.build()
const unsignedCbor = Transaction.toCBORHex(await tx.toTransaction())

// 2. USER partial-signs in the browser (CIP-30): returns a witness, not a final tx
const userWitness = await userClient.signTx(unsignedCbor)

// 3. SERVER adds the app wallet's witness, assembles both, and submits
const appWitness = await tx.partialSign()
const submit = await tx.assemble([appWitness, userWitness])
const txHash = await submit.submit()

The same shape covers any shared-control transaction: a 2-of-3 treasury where each signer adds a witness in turn, an escrow that needs both buyer and arbiter, or a backend that countersigns to enforce a business rule. For the on-chain side of native-script multisig, see Write a validator.

Fee sponsorship

Sponsorship is the same mechanism with one change: the inputs that cover the fee come from a sponsor, not the user. Because a transaction must balance exactly, inputs equal outputs plus fee, whoever adds the extra input and takes the change back is the one who pays. A new user who holds zero ADA can still act: your sponsor wallet supplies the fee (and, for a script transaction, the collateral), while the user only signs for the part that genuinely needs their key, authorizing a required-signer check, say, or spending a token they already hold. Both parties partial-sign; the sponsor's wallet provides the fee inputs and the change address.

This removes the hardest step in onboarding, "go buy ADA before you can do anything," and is why sponsorship usually pairs with a wallet the user did not have to install. Hosted wallet services take it further: they create a non-custodial wallet through social login and sponsor the user's first transactions, so there is no extension and no seed phrase to start. See the note on hosted sign-in and sponsorship in Wallet authentication.

"Gasless" means someone else pays, not a different fee token

Developers coming from other chains call this a gasless transaction or a meta-transaction. Cardano has no gas: fees are always paid in ADA and are fixed by the transaction's size and script cost, so "gasless" here means only that a third party supplies that ADA. It works today with the partial signing shown above, no special protocol feature required. Paying a fee in a native token instead of ADA is a separate idea (Babel fees) that depends on ledger changes still in development, so do not design around it yet.

Security

Co-signing means the user authorizes a transaction your server built, so the trust rules are strict:

  • Never modify a transaction after the user signs it. Any change invalidates their witness, and a flow that silently rebuilds is indistinguishable from an attempt to get the user to sign something they didn't see. Build the complete transaction, then collect signatures.
  • The user must be able to inspect what they sign. Show the amounts, recipients, and assets before prompting. A wallet displays the transaction, but your UI sets the expectation.
  • Protect the server key. The application wallet's mnemonic stays server-side and never reaches the client; treat a leak as a full compromise of whatever that key controls.
  • Validate the user's inputs before building, and assume their UTXOs may be spent by the time you submit. Show a clear retry rather than a cryptic failure.
  • Gate and rate-limit a sponsorship endpoint. A sponsor service spends your own ADA on every request. Without a whitelist, a token-holding requirement, authentication, or rate limiting, anyone can drain it by spamming requests, so decide who qualifies and cap how often.
  • Confirm you only pay the fee. Before the sponsor signs, check that its net contribution equals the transaction fee exactly and that any of its own tokens return to it, so a crafted transaction cannot make the sponsor overpay or leak its assets.

Next steps