KIP 247: Gasless Transaction Source

AuthorIan, Shiki, Ollie
Discussions-Tohttps://devforum.kaia.io/t/discussion-on-kip-247/8089
StatusReview
TypeCore
Created2025-02-18
Requires 245

Abstract

This KIP introduces Gasless Transaction (GaslessTx) where users can swap whitelisted fungible tokens into KAIA without possessing any KAIA.

Motivation

For users who have onboarded onto KAIA from another blockchain via a bridge, it is possible that they do not possess any KAIA tokens and hold only fungible tokens from the bridged assets. These users will be unable to perform any on-chain actions unless they acquire KAIA through alternative channels. This limitation effectively restricts their ability to engage with decentralized applications, execute transactions, or participate in any protocol activities within the KAIA ecosystem.

To enhance the user experience for those, this KIP introduces a concept of gasless transaction (GaslessTx) where transactions with a specific pattern is recognized as the GaslessTx. Upon detection of GaslessTx, the block proposer may lend the user KAIA to facilitate the GaslessTx execution. A gasless swap from Token to KAIA shall pay back the lent amount.

Specification

Definition of GaslessTx

There are two types of GaslessTx: GaslessApproveTx, and GaslessSwapTx.

GaslessApproveTx

A GaslessApproveTx must meet all the following conditions to be inserted to txpool.queue:

  • A1: GaslessApproveTx.to is a whitelisted ERC-20 token.
  • A2: GaslessApproveTx.data is approve(spender, amount).
  • A3: spender is a whitelisted GaslessSwapRouter.
  • A4: amount is a nonzero.
  • A5: nonce is getNonce(tx.from).

(Note that above statements can be verified solely through a static validation of a transaction.)

A GaslessApproveTx must meet all the following conditions to be promoted to txpool.pending:

  • AP1: GaslessApproveTx is followed by a GaslessSwapTx of the same sender and the GaslessSwapTx can be promoted to txpool.pending.

GaslessSwapTx

A GaslessSwapTx must meet all the following conditions to be inserted to txpool.queue:

  • S1: GaslessSwapTx.to is a whitelisted GaslessSwapRouter.
  • S2: GaslessSwapTx.data is swapForGas(token, amountIn, amountOut, amountRepay).
  • S3. token is a whitelisted ERC20 token.

(Note that above statements can be verified solely through a static validation of a transaction.)

A GaslessSwapTx must meet all the following conditions to be promoted to txpool.pending:

  • SP1: (If GaslessApproveTx exists in txpool.queue) GaslessApproveTx.to=token.
  • SP2: (If GaslessApproveTx exists in txpool.queue) GaslessApproveTx.data.amount>=amountIn.
  • SP3: Nonce is the correct value.
    • (If GaslessApproveTx exists in txpool.queue) GaslessApproveTx.nonce+1=tx.nonce=getNonce(tx.from)+1.
    • (otherwise) tx.nonce=getNonce(tx.from).
  • SP4: amountRepay is the correct value.
    • (If GaslessApproveTx exists in txpool.queue) amountRepay = CalcRepayAmount(GaslessApproveTx, GaslessSwapTx).
    • (otherwise) amountRepay = amountRepay(GaslessSwapTx).
    • amountRepay(GaslessApproveTx, GaslessSwapTx) = V1 + V2 + V3 and amountRepay(GaslessSwapTx) = V1 + V3 where
      • V1 = LendTx.Fee() = SwapTx.GasPrice() * 21000
      • V2 = ApproveTx.Fee() (if exists)
      • V3 = SwapTx.Fee()

While GaslessApproveTx must be followed by with GaslessSwapTx in order to be promoted, GaslessSwapTx can exist by itself.

Definition of LendTx

Upon detection of a GaslessTx (either GaslessApproveTx or GaslessSwapTx), the block proposer shall inject a transaction, denoted by LendTx, which transfers KAIA to the GaslessTx sender. LendTx must meet all the following requirements:

  • L1: LendTx.type is 0x7802, a Ethereum dynamic fee transaction type.
  • L2: LendTx.from is the proposer itself.
  • L3: LendTx.to is GaslessSwapTx.from
  • L4: LendTx.value is amountLend
    • amountLend is V2 + V3 (from aforementioned amountRepay definition).

GaslessSwapRouter

GaslessSwapRouter is a smart contract responsible for token swap and repayment. It supports only single-hop swaps through a predetermined DEX contract. It must support the following interface:

interface IKIP247 {
    // `factory` is to check if the token-WKAIA pair has been deployed. (`factory.getPair(token1, WKAIA)`)
    // `router` is for swap (`router.swapExactTokensForETH(...)`).
    struct DEXInfo {
        address factory;
        address router;
    }

    function swapForGas(address token, uint256 amountIn, uint256 minAmountOut, uint256 amountRepay) external;
    function addToken(address token, address factory, address router) external;
    function removeToken(address token) external;

    function claimCommission() external;
    function updateCommissionRate(uint256 _commissionRate) external;

    // view functions
    function dexAddress(address token) external view returns (address dex);
    function getAmountIn(address token, uint amountOut) external view returns (uint amountIn);
    function getSupportedTokens() external view returns (address[] memory);
}

swapForGas

swapForGas() must ensure that all the following conditions are satisfied:

  • R1: Sender has enough tokens. That is, token.balanceOf(msg.sender) >= amountIn.
  • R2: Token is whitelisted and has a corresponding DEX address other than zero.
  • R3: amountReceived >= minAmountOut >= amountRepay where amountReceived is the swap output.

swapForGas() must distribute the swap output (amountReceived) as follows:

  • The block proposer (block.coinbase) receives amountRepay.
  • User receives (amountReceived - amountRepay) * (1 - commissionRate).
  • The commission (amountReceived - amountRepay) * commissionRate is stored in the contract for later claim.

addToken

addToken() allows the contract owner to whitelist a token for gasless swaps.

addToken() must ensure all the following conditions are satisfied:

  • All addresses (token, factory, router) must be non-zero.
  • The token must not already be supported.
  • The factory must have a valid pair between the token and WKAIA.

removeToken

removeToken() allows the contract owner to remove a previously whitelisted token.

removeToken() must ensure all the following conditions are satisfied:

  • The token must be currently supported.

claimCommission

claimCommission() allows the contract owner to withdraw accumulated commission fees.

claimCommission() must ensure all the following conditions are satisfied:

  • There must be a non-zero balance in the contract.

updateCommissionRate

updateCommissionRate() allows the contract owner to set the commission rate for gasless swaps.

updateCommissionRate() must ensure all the following conditions are satisfied:

  • The commission rate must be between 0 and 10000 (0% to 100%).

dexAddress

dexAddress() returns the factory address associated with a supported token.

dexAddress() must ensure all the following conditions are satisfied:

  • The token must be supported.

getAmountIn

getAmountIn() calculates the required input amount of tokens to receive a specific amount of KAIA.

getAmountIn() must ensure all the following conditions are satisfied:

  • The token must be supported.

getSupportedTokens

getSupportedTokens() returns a list of all currently supported tokens for gasless swaps.

TxPool Policy Updates

GaslessTx Validation (validateTx)

To prevent GaslessTx to be discarded, the validation logic at txpool.queue insertion needs to detect GaslessTx and skip the account balance check.

// txpool.validateTx()
if ...
else: // if tx is NOT feeDelegatedTx
    if senderBalance.Cmp(tx.Cost()) < 0:
        if tx is GaslessTx:
            validation ok
        else:
            insufficientFund error

Promoting GaslessTx (promoteExecutables)

GaslessApproveTx cannot be executed unless the corresponding GaslessSwapTx from the same sender is already in the transaction pool. Conversely, if the token has not been approved, GaslessSwapTx cannot be executed before GaslessApproveTx. In such cases, both transactions need to be promoted simultaneously.

// txpool.promoteExecutables()
if tx is GaslessApproveTx:
    assert tx.nonce == getNonce(tx.from)
    if GaslessSwapTx is in queue:
        assert GaslessSwapTx.nonce == getNonce(tx.from) + 1
        promote tx and GaslessSwapTx to pending
    else:
        push tx to queue

if tx is GaslessSwapTx:
    if GaslessApproveTx is in queue:
        assert tx.nonce == getNonce(tx.from) + 1
        assert GaslessApproveTx.nonce == getNonce(tx.from)
        promote tx and GaslessApproveTx to pending
    else if tx.nonce == getNonce(tx.from):
        promote tx to pending
    else:
        push tx to queue

Transaction Bundling

This KIP implements TxBundlingModule specified in KIP-245. These are the possible bundles:

  • LendTx - GaslessApproveTx - GaslessSwapTx
  • LendTx - GaslessSwapTx

Bundles from this module are placed at the position of each GaslessSwapTx.

ExtractTxBundles(txs, prevBundles):
    if GaslessTx is disabled:
        return nothing

    filter ApproveTx, SwapTx from txs
    move ApproveTx (if exists) before SwapTx
    inject LendTx before ApproveTx if exists, otherwise before SwapTx

Rationale

Stateless transaction validations

The existing validation logics in transaction pool (validateTx()) ensures that the transaction sender has sufficient KAIA to cover the gas fee. However, this KIP does not propose a comparable validation mechanism for GaslessTxs such as ERC-20 token balance checks. Instead, it suggests stateless transaction validation logics, specifically A{1-4}, AP1, S{1-3}, SP{1-3}.

While the computational overhead of performing stateful validations via EVM calls may not be significant, such validations inherently lack reliability. They may become obsolete and ineffective since the state database can change between transaction validation and execution. To address this, this KIP relies on KIP-245 where all transactions including LendTx will be excluded from the block when GaslessTx reverts. Thus, this KIP proposes the most basic format checks of GaslessTx in transaction pool.

Disabling GaslessTx

If many consensus nodes disabled GaslessTx feature, users may experience increased transaction latency. However, as long as there are still nodes supporting GaslessTx, the transactions will eventually be executed. Also, because this feature is enabled by default, few consensus nodes are expected to disable it.

Backward Compatibility

This does not affect the backward compatibility because this does not involve hardfork.

References

Copyright and related rights waived via CC0.