[RFC] Sponsored Transaction

23-02-22 update: The feature is implemented in https://github.com/MystenLabs/sui/pull/8273

This is a RFC post that intends to seek for feedback for Sponsored Transaction and Sponsor Station. Any feedback/suggestions are appreciated! We are looking to close the initial round of feedback by Dec 15th for Sui Core changes, but expecting Sponsor Station to iterate more frequently as discussions go on.

A Sponsored Transaction allows a transaction to be expensed by a gas owned by another address. Why is this interesting from the product/ecosystem’s perspective?

  1. improving web3 mass onboarding process. Web2 users can start their web3 journey before the lengthy on-ramping, increasing user conversion rate. For example, gaming studios convert their web2 gamers to web3 users by sponsoring early user transactions.
  2. incentivizing on-chain activity and web3 migration: encourage certain user behaviors such as voting and governance.
  3. customer acquisition: platforms and Dapps leverage this feature to attract new users by subsidizing the first few transactions interacting with their protocol.
  4. easier asset management - Dapps or institutions (particularly custodians) doing on-chain asset management no longer need SUI tokens in every account to transfer funds.
  5. other scenarios: facilitate using burner accounts to isolate on-chain activities; bootstrap across-chain transfers; a Dapp can give gases or gas promises as a reward to encourage users to use their products etc.

For more details, see Github Issue: Support sponsored transactions · Issue #2418 · MystenLabs/sui · GitHub for motivation (and more discussions).

Similar Features on other Blockchains

  1. Ethereum - Meta transaction & Ethereum Gas Station Network
  2. Solana - Gasless transaction
  3. Near - Prepaid Gas

Roles

In the context of a sponsored transaction, we define the roles below:

  • Users
    • the Sui address owner that has an transaction to execute
  • Sponsors
    • the Sui address owner that pays gas for the transaction
    • Sponsor could run a Sponsor Station See below to accept GasLessTransactionData or distribute/airdrop “empty check” gases to users

Transaction Construction Diagram

This section describes the ways the a sponsored transaction can be constructed:

Case I. sponsor pays for a specific transaction that user initialized (diagram)

GasLessTransactionData contains the transaction details and user address. It is not a sui-core data structure, but will be exposed in relevant SDKs so recognizable to both user and sponsor.

pub struct GasLessTransactionData {
    pub kind: TransactionKind,
    sender: SuiAddress,
}
  1. User constructs GasLessTransactionData and pass it to Sponsor for expensing.
  2. If Sponsor decides this is the right transaction to sponsor, it picks a gas, build a TransactionData (see below), and signs TransactionData to produce a Signature.
  3. Sponsor passes back TransactionData and Signature to user.
  4. User checks the signature, if all good, signs TransactionData to get the second Signature.
  5. User then forms the dual-signedSenderSignedData and submits it to fullnode for execution.

Case II. sponsor pays for a specific transaction that sponsor initialized (diagram)

  1. Sponsor constructs TransactionData (containing the transaction details and gas data) that it thinks the user may like, signs it to get Signature
  2. User checks TransactionData and if it like it, signs it to get the second Signature
  3. User submits the dual-signed transaction to fullnode or sponsor for execution.

This data flow is particularly interesting in scenarios where sponsor act as an advertiser, airdropper, or in general a party that wants to incentivize users to perform some behaviors. The unsigned TransactionData can be sent over to user in various forms, such as email, SMS or simply a Dapp frontend.

Case III. sponsor gives an “empty check” that applies to any transactions (diagram)

  1. Sponsor hands out a GasData to eligible user
  2. User constructs TransactionData signs it
  3. User passes the TransactionData and Signature to Sponsor
  4. Sponsor checks it and signs it too
  5. Sponsor submits the dual-signed TransactionData to full node.

Sui Protocol Changes

Data Structure

We propose to change the current data structure to the following. Note this change is general enough that it also applies to normal transactions (non-sponsored). The beauty of a universal single data structure makes the code less-branched and more manageable.

Proposed Data Structure I

Make tx_signatures a vector of Signature. This would work for multi-agent transactions as well.


pub struct TransactionData {
    pub kind: TransactionKind,
    sender: SuiAddress,
    pub gas_data: GasData,
}

/// I spin it off to make the structs/interfaces cleaner, but this is optional
pub struct GasData {
    gas_payment: ObjectRef,
    pub gas_price: u64,
    pub gas_budget: u64,
    pub gas_owner: SuiAddress,
}

pub struct SenderSignedData {
    pub data: TransactionData,
    /// tx_signatures is signed by the user and sponsor, applied on `data`.
    /// when sender == gas_owner, only one sig is needed
    pub tx_signatures: Vec<Signature>,
}

Alternative Data Structure II

Adding an optional field gas_owner_signature.

pub struct TransactionData {
    pub kind: TransactionKind,
    sender: SuiAddress,
    gas_payment: ObjectRef,
    pub gas_price: u64,
    pub gas_budget: u64,
}

pub struct SenderSignedData {
    pub data: TransactionData,
    /// tx_signatures is signed by the user applied on `data`.
    pub tx_signatures: Signature,
    /// gas_owner_signature is signed by the user applied on `data`.
    /// when sender == gas_owner, this should be None
    pub gas_owner_signature: Option<Signature>,
}

Alternative Data Structure III

To make this change easily extensible to Multi-agent Transaction in the future, we could make tx_details a vector of (signer address, transaction kind):

pub struct TransactionData {
                      /* signer address */
    pub tx_details: Vec<(SuiAddress, TransactionKind)>,
    pub gas_data: GasData,
}

Before Sui protocol supports Multi-agent Transaction, the code will enforce the vector’s length to be 1.

TransactionDigest Derivation

We agree to not commit user signature into TransactionDigest. The change will be released in 0.18.0.

Transaction Submission API

The sui_executeTransaction API is compatible with this proposal, no additional change is needed.

Limitations

Sponsored Transaction cannot be used in *Sui transaction types, currently including:

  • TransferSui
  • PaySui
  • PayAllSui

This is because *Sui transactions treat user input object or one of the input objects as the gas. This contradicts with the idea of sponsor transaction that sender ≠ sponsor.

Apparently Sponsored Transaction is also not usable in system transactions such as EpochChange.

However Sponsored Transaction can be used with SingleTransaction or BatchTransaction. Today’s BatchTransaction does not support *Sui Transactions either.

Threat Models

Both user and sponsor should understand potential risks before doing a joint sponsored transaction. Equivocation risk is the major threat model present in Sponsored Transaction.

A sponsored transaction may risk being equivocated by the other party. This is because when the transaction is examined by validators, all of the owned objects are locked to prevent double spend. An equivocation happens when an owned objects pair (ObjectID, SequenceNumber) is concurrently used in multiple non-finalized transactions. The worse case is we cannot get supermajority validator signatures for any of the transactions, leading some or all objects involved in these transaction lose liveness until the epoch change.

To equivocate, one party (user or sponsor) signs and submits another transaction that involves an owned object in the original transaction. Hence, only the user and sponsor is capable of doing it.

Risks

  • User or sponsor’s owned objects that appear in the sponsored transaction lose liveness until end of epoch.
  • This is a non-issue for normal transactions where user and sponsor is the same address.

Mitigations

  • Sponsors gain reputation by helping users fulfill transactions. If the sponsor is incompetent, users will seek other players in the open market. Note sponsor has nothing to gain for equivocation, so it’s practical to assume no sponsors would cause equivocation on purpose, except for the aforementioned censorship.
  • User’s input objects do not always include an owned object but gas must be owned. From this perspective, sponsor faces bigger risk if rogue users intend to abuse their gases. To mitigate this attack surface, we recommend sponsors to 1. dispense small amount of gases, 2. authorize gas request properly and 3. apply rate limiting if necessary

Note: under some circumstances, equivocation may be expected and fairly benign. For example, assuming a Dapp wants to hand out limited number of red pockets in Spring Festival. It could use the same gas object to create multiple transactions that interact with a red pocket smart contract. It’s expected at most one of few transaction will succeed, depending on which user acts faster enough. The bottom line is, the locked object only loses liveness temporarily, and will be revived at the end of epoch.

Sui Gas Station

Anyone can operate a Gas Station to fund user transactions. Depending on which data flow the sponsor is interested in handling, the Gas Station wants to support a different set of user facing functionalities/RPC interface. Below we draft a recommended specifications:

  1. Track Gas Prices in Real-time

  2. Sponsor wants to know the current gas prices on chain, to guide the gas price that they give

  3. Track Gas Usage

  4. Sponsor wants to know a rough gas usage range for the user’s transaction, to guide the gas limit they give

  5. Gas Management

  6. Sponsor wants to manage gas objects and only hand out gas objects that are nearly enough to cover the expenses, to minimize the risks of objects being locked and illiquid for a while.

  7. API endpoint: Take GaslessTransaction (see above) and return sole-signed SenderSignedData (case I)

pub fn request_gas_and_signature(gasless_tx: GaslessTransaction) -> Result<SenderSignedData, Error>;
  1. API endpoint: Take sole-signed SenderSignedData and return execution result (case III)
pub fn submit_sole_signed_transaction(sole_signed_data: SenderSignedData) -> Result<(Transaction, CertifiedTransactionEffects), Error>;
  1. API endpoint: Take a dual-signed SenderSignedData, and return execution result (case I and II)

In Case I and II, users may submit the dual-signed transaction via sponsor, such that the sponsor knows the TransactionDigest and execution result.

pub fn submit_dual_signed_transaction(dual_signed_data: SenderSignedData) -> Result<(Transaction, CertifiedTransactionEffects), Error>;
  1. API endpoint: return empty check GasData (case III)
pub fn request_gas(/*perhaps some requirement data*/) -> Result<GasData, Error>;

Risk Control

Sponsors want to take the following recommended measures to control risks of running a Gas Station.

Authorization & Rate Limiting

Depending on the nature of the Gas Station, sponsors can apply different authorization rules to avoid being spammed by bad actors. Possible policies include:

  1. Rate limit gas request per account, or per IP
  2. Only take requests with valid authorization header, which is rate limited separately

Abuse Detection

For all gas objects that the sponsor gives out, track if users ever try to equivocate and lock objects. If such behavior is detected, blocklist this user/requestor accordingly.

71 Likes

Thanks for the detailed writeup for Sponsored Transactions! From the perspective of a gas station, the specs for tools like real time gas price tracking, gas usage tracking, and gas usage estimation make perfect sense (specs 1-4). When it comes to actual transaction sponsorship, we need to first clearly define the parties involved:

  • End user: Sender of the transaction. Has a sui address and optionally a wallet.
  • Sponsor: Funder of transactions on behalf of end users, e.g. a dapp developer
  • Gas Station: Manager of gas objects for sponsors, and facilitator of sponsored transactions.
  • Node service: Endpoint for transactions to be sent to.

The key point here is differentiating between the sponsor and gas station. With these above parties, I see the majority of interactions happening between gas station <-> sponsor, and sponsor <-> end user.

The overhead for gas object management in sponsored transactions will be non-trivial at scale, which is why we don’t believe sponsors will want to actively take part in this i.e. this is the value add of a third party gas station. All tools offered by the gas station will be for the purpose of optimizing gas usage for end user transactions on behalf of the sponsor (spec 6, but gas objects fully managed by gas station)

I also think the ability to sponsor a transaction and submit a transaction should be decoupled i.e. we should leave that out as a requirement for a gas station. In scenario 1, the end user submits the final dual signed transaction. In scenarios 2 and 3, that would fall on the sponsor. For tracking purposes, the gas station already knows the assigned gas object id and the precomputed transaction id, so there should be no reason for the gas station to be involved in the execution. To that point, submit_dual_signed_transaction would be an action taken by the sponsor thus not a strict requirement of the gas station, and depending on whether we see a need for scenario 3, submit_sole_signed_transaction could be just a sign_transaction method that returns dual_signed_data:SenderSignedData

Edit Additional Note: One feature I think would be quite useful in this flow is to have expiration timestamps for the gas objects, specified by the sponsor. Could that be a parameter amended into the rpc endpoints spec?

44 Likes

I need to think about this some more, but it leaps out at me that it would be great to be able to provide a GasData struct with more defined limitations on how it can be used (e.g. on a certain package or function call).

22 Likes

I vote for Case II. sponsor pays for a specific transaction that sponsor initialized
I foresee that this would be the most common use case in sponsored transactions

17 Likes

I like this option better

13 Likes

very useful thank you so much

11 Likes

i have joined dc so warm communate

10 Likes

this is very useful exciting

7 Likes

totaly gree with you and 2023 good luck

5 Likes

ok, it is great project map, i see it very clear

7 Likes

ok, very useful informations

8 Likes

thanks for the answers for many questions

7 Likes

This is very useful exciting!!!

9 Likes

i found display problem No back button sui

3 Likes

nice idear , good way

1 Like

:grinning: :grinning: :grinning: :grinning: