KIP 247: Gasless Transaction
Author | Ian, Shiki, Ollie |
---|---|
Discussions-To | https://devforum.kaia.io/t/discussion-on-kip-247/8089 |
Status | Review |
Type | Core |
Created | 2025-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
isapprove(spender, amount)
. - A3:
spender
is a whitelistedGaslessSwapRouter
. - A4:
amount
is a nonzero. - A5:
nonce
isgetNonce(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 totxpool.pending
.
GaslessSwapTx
A GaslessSwapTx
must meet all the following conditions to be inserted to txpool.queue
:
- S1:
GaslessSwapTx.to
is a whitelistedGaslessSwapRouter
. - S2:
GaslessSwapTx.data
isswapForGas(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)
.
- (If GaslessApproveTx exists in
- 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
andamountRepay(GaslessSwapTx) = V1 + V3
whereV1 = LendTx.Fee() = SwapTx.GasPrice() * 21000
V2 = ApproveTx.Fee()
(if exists)V3 = SwapTx.Fee()
- (If GaslessApproveTx exists in
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
isGaslessSwapTx.from
- L4:
LendTx.value
isamountLend
amountLend
isV2 + V3
(from aforementionedamountRepay
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
whereamountReceived
is the swap output.
swapForGas()
must distribute the swap output (amountReceived
) as follows:
- The block proposer (
block.coinbase
) receivesamountRepay
. - 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
Copyright and related rights waived via CC0.