Skip to main content

Sponsored Transactions

A sponsored transaction is when one address pays the gas fee for a transaction submitted by another address. You can use sponsored transactions to cover the fees for transactions initiated by users on your site or dApp. This removes a significant obstacle that Web2 users encounter when entering Web3, as they often have to purchase tokens to cover the gas fee and execute a transaction on-chain.

You can provide a wildcard GasData object to users. The object covers the gas fees for a user transaction. The GasData object covers any fee amount determined for the transaction as long as the budget is sufficient.

Sponsored transactions can also facilitate asset management as you don't need to maintain multiple accounts with SUI tokens to transfer funds.

Potential risks

The most significant risk when using sponsored transactions is equivocation. An equivocation occurs when an owned object's pair (ObjectID, SequenceNumber) is concurrently used in multiple non-finalized transactions. To avoid double spending, validators lock objects as they validate transactions.

In some scenarios, sponsored transactions can result in locking all associated owned objects.

To equivocate, either the user or the sponsor signs and submits another transaction that attempts to manipulate an owned object in the original transaction. Because only the object owner can use an owned object, only the user and sponsor can cause this condition.

Create a user-initiated sponsored transaction

A user-initiated sponsored transaction involves the following steps:

  1. A user initializes a GasLessTransactionData transaction.

  2. The user sends GasLessTransactionData to the sponsor.

  3. The sponsor validates the transaction, constructs TransactionData with gas fees, and then signs TransactionData.

  4. The sponsor sends the signed TransactionData and the sponsor Signature back to the user.

  5. The user verifies and then signs TransactionData and sends the dual-signed transaction to Sui network through a full node or the sponsor.

GasLessTransactionData

GasLessTransactionData is TransactionData without GasData. It is not a sui-core data structure; it is only an interface between user and sponsor.

The following example constructs a GasLessTransactionData object:

pub struct GasLessTransactionData {
pub kind: TransactionKind,
sender: SuiAddress,

}

Create a sponsor-initiated sponsored transaction

A sponsor-initiated sponsored transaction involves the following steps:

  1. A sponsor constructs a TransactionData object that contains the transaction details and associated gas fee data. The sponsor signs it to generate a Signature before sending it to a user. You can send unsigned TransactionData via email, SMS, or an application interface.

  2. The user checks the transaction and signs it to generate the second Signature for the transaction.

  3. The user submits the dual-signed transaction to a Sui full node or sponsor to execute it.

You can use a sponsor-initiated sponsored transaction as an advertiser, or to incentivize specific user actions without requiring the user to pay for gas fees.

Create sponsored transactions using a GasData object

To use a GasData object to sponsor the gas fees for a transaction, create a GasData object that covers the fees determined for the transaction. The user doesn't need to know how much the fee is or approve it.

A sponsor transaction using a GasData object involves the following steps:

  1. The sponsor provides a GasData object to a user.

  2. The user constructs TransactionData and signs it to generate a Signature.

  3. The user sends the TransactionData and the Signature to the sponsor.

  4. The sponsor confirms the TransactionData and then signs it.

  5. The sponsor submits the dual-signed TransactionData to a full node to execute the transaction.

The following code block describes the TransactionData structure for sponsored transactions and GasObject. You can view the source code in the Sui GitHub repository.

TransactionData structure

#[derive(Debug, PartialEq, Eq, Hash, Clone, Serialize, Deserialize)]
pub struct TransactionDataV1 {
pub kind: TransactionKind,
pub sender: SuiAddress,
pub gas_data: GasData,
pub expiration: TransactionExpiration,
}

GasData structure

#[derive(Debug, PartialEq, Eq, Hash, Clone, Serialize, Deserialize)]
pub struct GasData {
pub payment: Vec<ObjectRef>,
pub owner: SuiAddress,
pub price: u64,
pub budget: u64,
}

Create a Sui gas station

On Sui, the term gas station refers to a concept where you set up processes to sponsor user transactions. You can customize a gas station to support the specific user-facing functionality you need. Some example use cases for a Sui gas station include:

  • Monitor real-time gas prices on the network to determine the gas price that the station provides.

  • Track usage of gas provided to users on the network.

  • Gas pool management, such as using specific gas objects to minimize costs or reduce the risk of a large amount of locked objects that remain illiquid while locked.

Depending on the nature of your gas station, you can apply different authorization rules to avoid being spammed by bad actors. Possible policies include rate limiting gas requests per account or per IP address, or only accepting requests with a valid authorization header.

warning

For all gas objects that you provide as a sponsor, you should track if users ever try to equivocate and lock objects. If you detect such behavior, block the user or requester accordingly.

Example

The following Rust SDK code examples demonstrate how to implement a Sui gas station that supports each of the sponsored transaction types described previously.

User-initiated sponsored transactions

Use the API endpoint to receive GaslessTransaction transactions and return a sole-signed SenderSignedData object.

pub fn request_gas_and_signature(gasless_tx: GaslessTransaction) -> Result<SenderSignedData, Error>;

Use the API endpoint to receive single-signed SenderSignedData and return the result of the transaction.

pub fn submit_sole_signed_transaction(sole_signed_data: SenderSignedData) -> Result<(Transaction, CertifiedTransactionEffects), Error>;

Alternatively, use the API endpoint to return a GasData object.

pub fn request_gas(/*requirement data*/) -> Result<GasData, Error>;

User and sponsor-initiated transaction

Use the API endpoint to receive dual-signed SenderSignedData and return the result of the transaction.

pub fn submit_dual_signed_transaction(dual_signed_data: SenderSignedData) -> Result<(Transaction, CertifiedTransactionEffects), Error>;

For user and sponsor-initiated transactions, users can submit the dual-signed transaction via either a sponsor or a full node.