revenue
Abstract
This document specifies the internal x/revenue
module of the DEP Hub.
The x/revenue
module enables the DEP Hub to support splitting transaction fees
between block proposer and smart contract deployers.
As a part of the DEP Token Model,
this mechanism aims to increase the adoption of the DEP Hub
by offering a new stable source of income for smart contract deployers.
Developers can register their smart contracts and everytime someone interacts with a registered smart contract,
the contract deployer or their assigned withdrawal account receives a part of the transaction fees.
Together, all registered smart contracts make up the DEP dApp Store: paying developers and network operators for their services via built-in shared fee revenue model.
Contents
Concepts
DEP dApp Store
The DEP dApp store is a revenue-per-transaction model, which allows developers
to get payed for deploying their decentralized application (dApps) on DEP.
Developers generate revenue, every time a user interacts with their dApp in the dApp store, gaining them a steady income.
Users can discover new applications in the dApp store and pay for the transaction fees that finance the dApp's revenue.
This value-reward exchange of dApp services for transaction fees is implemented by the x/revenue
module.
Registration
Developers register their application in the dApp store by registering their application's smart contracts. Any contract can be registered by a developer by submitting a signed transaction. The signer of this transaction must match the address of the deployer of the contract in order for the registration to succeed. After the transaction is executed successfully, the developer will start receiving a portion of the transaction fees paid when a user interacts with the registered contract.
NOTE: If your contract is part of a developer project, please ensure that the deployer of the contract (or the factory that deployes the contract) is an account that is owned by that project. This avoids the situtation, that an individual deployer who leaves your project could become malicious.
Fee Distribution
As described above, developers will earn a portion of the transaction fee after registering their contracts. To understand how transaction fees are distributed, we look at the following two things in detail:
- The transactions eligible are only EVM transactions (
MsgEthereumTx
). Cosmos SDK transactions are not eligible at this time. - The registration of factory contracts (smart contracts that have been deployed by other contracts) requires the identification original contract's deployer. This is done through address derivation.
EVM Transaction Fees
Users pay transaction fees to pay interact with smart contracts using the EVM.
When a transaction is executed, the entire fee amount (gasLimit * gasPrice
)
is sent to the FeeCollector
module account
during the Cosmos SDK AnteHandler execution.
After the EVM executes the transaction, the user receives a refund of (gasLimit - gasUsed) * gasPrice
.
In result a user pays a total transaction fee of txFee = gasUsed * gasPrice
for the execution.
This transaction fee is distributed between developers and validators,
in accordance with the x/revenue
module parameters: DeveloperShares
, ValidatorShares
.
This distribution is handled through the EVM's PostTxProcessing
Hook.
Address Derivation
dApp developers might use a factory pattern to implement their application logic through smart contracts. In this case a smart contract can be either deployed by an Externally Owned Account (EOA: an account controlled by a private key, that can sign transactions) or through another contract.
In both cases, the fee distribution requires the identification a deployer address that is an EOA address, unless a withdrawal address is set by the contract deployer during registration to receive transaction fees for a registered smart contract. If a withdrawal address is not set, it defaults to the deployer’s address.
The identification of the deployer address is done through address derivation. When registering a smart contract, the deployer provides an array of nonces, used to derive the contract’s address:
- If
MyContract
is deployed directly byDeployerEOA
, in a transaction sent with nonce5
, then the array of nonces is[5]
. - If the contract was created by a smart contract, through the
CREATE
opcode, we need to provide all the nonces from the creation path. E.g. ifDeployerEOA
deploys aFactoryA
smart contract with nonce5
. Then,DeployerEOA
sends a transaction toFactoryA
through which aFactoryB
smart contract is created. If we assumeFactoryB
is the second contract created byFactoryA
, thenFactoryA
's nonce is2
. Then,DeployerEOA
sends a transaction to theFactoryB
contract, through whichMyContract
is created. If this is the first contract created byFactoryB
- the nonce is1
. We now have an address derivation path ofDeployerEOA
->FactoryA
->FactoryB
->MyContract
. To be able to verify thatDeployerEOA
can registerMyContract
, we need to provide the following nonces:[5, 2, 1]
.
Note: Even if MyContract
is created from FactoryB
through a transaction
sent by an account different from DeployerEOA
, only DeployerEOA
can register MyContract
.
State
State Objects
The x/revenue
module keeps the following objects in state:
State Object | Description | Key | Value | Store |
---|---|---|---|---|
Revenue | Fee split bytecode | []byte{1} + []byte(contract_address) | []byte{revenue} | KV |
DeployerRevenues | Contract by deployer address bytecode | []byte{2} + []byte(deployer_address) + []byte(contract_address) | []byte{1} | KV |
WithdrawerRevenues | Contract by withdraw address bytecode | []byte{3} + []byte(withdraw_address) + []byte(contract_address) | []byte{1} | KV |
Revenue
A Revenue defines an instance that organizes fee distribution conditions for the owner of a given smart contract
type Revenue struct {
// hex address of registered contract
ContractAddress string `protobuf:"bytes,1,opt,name=contract_address,json=contractAddress,proto3" json:"contract_address,omitempty"`
// bech32 address of contract deployer
DeployerAddress string `protobuf:"bytes,2,opt,name=deployer_address,json=deployerAddress,proto3" json:"deployer_address,omitempty"`
// bech32 address of account receiving the transaction fees it defaults to
// deployer_address
WithdrawerAddress string `protobuf:"bytes,3,opt,name=withdrawer_address,json=withdrawerAddress,proto3" json:"withdrawer_address,omitempty"`
}
ContractAddress
ContractAddress
defines the contract address that has been registered for fee distribution.
DeployerAddress
A DeployerAddress
is the EOA address for a registered contract.
WithdrawerAddress
The WithdrawerAddress
is the address that receives transaction fees for a registered contract.
Genesis State
The x/revenue
module's GenesisState
defines the state necessary for initializing the chain from a previous exported height.
It contains the module parameters and the revenues for registered contracts:
// GenesisState defines the module's genesis state.
type GenesisState struct {
// module parameters
Params Params `protobuf:"bytes,1,opt,name=params,proto3" json:"params"`
// active registered contracts for fee distribution
Revenues []Revenue `protobuf:"bytes,2,rep,name=revenues,json=revenues,proto3" json:"revenues"`
}
State Transitions
The x/revenue
module allows for three types of state transitions:
RegisterRevenue
, UpdateRevenue
and CancelRevenue
.
The logic for distributing transaction fees is handled through Hooks.
Register Fee Split
A developer registers a contract for receiving transaction fees, defining the contract address, an array of nonces for address derivation and an optional withdraw address for receiving fees. If the withdraw address is not set, the fees are sent to the deployer address by default.
- User submits a
RegisterRevenue
to register a contract address, along with a withdraw address that they would like to receive the fees to - Check if the following conditions pass:
x/revenue
module is enabled- the contract was not previously registered
- deployer has a valid account (it has done at least one transaction) and is not a smart contract
- an account corresponding to the contract address exists, with a non-empty bytecode
- contract address can be derived from the deployer’s address and provided nonces using the
CREATE
operation - contract is already deployed
- Store an instance of the provided fee.
All transactions sent to the registered contract occurring after registration
will have their fees distributed to the developer, according to the global DeveloperShares
parameter.
Update Fee Split
A developer updates the withdraw address for a registered contract, defining the contract address and the new withdraw address.
- User submits a
UpdateRevenue
- Check if the following conditions pass:
x/revenue
module is enabled- the contract is registered
- the signer of the transaction is the same as the contract deployer
- Update the fee with the new withdraw address.
Note that if withdraw address is empty or the same as deployer address, then the withdraw address is set to
""
.
After this update, the developer receives the fees on the new withdraw address.
Cancel Fee Split
A developer cancels receiving fees for a registered contract, defining the contract address.
- User submits a
CancelRevenue
- Check if the following conditions pass:
x/revenue
module is enabled- the contract is registered
- the signer of the transaction is the same as the contract deployer
- Remove fee from storage
The developer no longer receives fees from transactions sent to this contract.
Transactions
This section defines the sdk.Msg
concrete types that result in the state transitions defined on the previous section.
MsgRegisterRevenue
Defines a transaction signed by a developer to register a contract for transaction fee distribution. The sender must be an EOA that corresponds to the contract deployer address.
type MsgRegisterRevenue struct {
// contract hex address
ContractAddress string `protobuf:"bytes,1,opt,name=contract_address,json=contractAddress,proto3" json:"contract_address,omitempty"`
// bech32 address of message sender, must be the same as the origin EOA
// sending the transaction which deploys the contract
DeployerAddress string `protobuf:"bytes,2,opt,name=deployer_address,json=deployerAddress,proto3" json:"deployer_address,omitempty"`
// bech32 address of account receiving the transaction fees
WithdrawerAddress string `protobuf:"bytes,3,opt,name=withdraw_address,json=withdrawerAddress,proto3" json:"withdraw_address,omitempty"`
// array of nonces from the address path, where the last nonce is
// the nonce that determines the contract's address - it can be an EOA nonce
// or a factory contract nonce
Nonces []uint64 `protobuf:"varint,4,rep,packed,name=nonces,proto3" json:"nonces,omitempty"`
}
The message content stateless validation fails if:
- Contract hex address is invalid
- Contract hex address is zero
- Deployer bech32 address is invalid
- Withdraw bech32 address is invalid
- Nonces array is empty
MsgUpdateRevenue
Defines a transaction signed by a developer to update the withdraw address of a contract registered for transaction fee distribution. The sender must be an EOA that corresponds to the contract deployer address.
type MsgUpdateRevenue struct {
// contract hex address
ContractAddress string `protobuf:"bytes,1,opt,name=contract_address,json=contractAddress,proto3" json:"contract_address,omitempty"`
// deployer bech32 address
DeployerAddress string `protobuf:"bytes,2,opt,name=deployer_address,json=deployerAddress,proto3" json:"deployer_address,omitempty"`
// new withdraw bech32 address for receiving the transaction fees
WithdrawerAddress string `protobuf:"bytes,3,opt,name=withdraw_address,json=withdrawerAddress,proto3" json:"withdraw_address,omitempty"`
}
The message content stateless validation fails if:
- Contract hex address is invalid
- Contract hex address is zero
- Deployer bech32 address is invalid
- Withdraw bech32 address is invalid
- Withdraw bech32 address is same as deployer address
MsgCancelRevenue
Defines a transaction signed by a developer to remove the information for a registered contract. Transaction fees will no longer be distributed to the developer, for this smart contract. The sender must be an EOA that corresponds to the contract deployer address.
type MsgCancelRevenue struct {
// contract hex address
ContractAddress string `protobuf:"bytes,1,opt,name=contract_address,json=contractAddress,proto3" json:"contract_address,omitempty"`
// deployer bech32 address
DeployerAddress string `protobuf:"bytes,2,opt,name=deployer_address,json=deployerAddress,proto3" json:"deployer_address,omitempty"`
}
The message content stateless validation fails if:
- Contract hex address is invalid
- Contract hex address is zero
- Deployer bech32 address is invalid
Hooks
The fees module implements one transaction hook from the x/evm
module
in order to distribute fees between developers and validators.
EVM Hook
A PostTxProcessing
EVM hook executes custom logic
after each successful EVM transaction.
All fees paid by a user for transaction execution are sent to the FeeCollector
module account
during the AnteHandler
execution before being distributed to developers and validators.
If the x/revenue
module is disabled or the EVM transaction targets an unregistered contract,
the EVM hook returns nil
, without performing any actions.
In this case, 100% of the transaction fees remain in the FeeCollector
module, to be distributed to the block proposer.
If the x/revenue
module is enabled and a EVM transaction targets a registered contract,
the EVM hook sends a percentage of the transaction fees (paid by the user)
to the withdraw address set for that contract, or to the contract deployer.
User submits EVM transaction (
MsgEthereumTx
) to a smart contract and transaction is executed successfullyCheck if
- fees module is enabled
- smart contract is registered to receive fees
Calculate developer fees according to the
DeveloperShares
parameter. The initial transaction message includes the gas price paid by the user and the transaction receipt, which includes the gas used by the transaction.devFees := receipt.GasUsed * msg.GasPrice * params.DeveloperShares
Transfer developer fee from the
FeeCollector
(Cosmos SDKauth
module account) to the registered withdraw address for that contract. If there is no withdraw address, fees are sent to contract deployer's address.Distribute the remaining amount in the
FeeCollector
to validators according to the SDK Distribution Scheme.
Events
The x/revenue
module emits the following events:
Register Fee Split
Type | Attribute Key | Attribute Value |
---|---|---|
register_revenue | "contract" | {msg.ContractAddress} |
register_revenue | "sender" | {msg.DeployerAddress} |
register_revenue | "withdrawer_address" | {msg.WithdrawerAddress} |
Update Fee Split
Type | Attribute Key | Attribute Value |
---|---|---|
update_revenue | "contract" | {msg.ContractAddress} |
update_revenue | "sender" | {msg.DeployerAddress} |
update_revenue | "withdrawer_address" | {msg.WithdrawerAddress} |
Cancel Fee Split
Type | Attribute Key | Attribute Value |
---|---|---|
cancel_revenue | "contract" | {msg.ContractAddress} |
cancel_revenue | "sender" | {msg.DeployerAddress} |
Parameters
The fees module contains the following parameters:
Key | Type | Default Value |
---|---|---|
EnableRevenue | bool | true |
DeveloperShares | sdk.Dec | 50% |
AddrDerivationCostCreate | uint64 | 50 |
Enable Revenue Module
The EnableRevenue
parameter toggles all state transitions in the module.
When the parameter is disabled, it will prevent any transaction fees from being distributed to contract deployers
and it will disallow contract registrations, updates or cancellations.
Developer Shares Amount
The DeveloperShares
parameter is the percentage of transaction fees that is sent to the contract deployers.
Address Derivation Cost with CREATE opcode
The AddrDerivationCostCreate
parameter is the gas value charged
for performing an address derivation in the contract registration process.
A flat gas fee is charged for each address derivation iteration.
We allow a maximum number of 20 iterations, and therefore a maximum number of 20 nonces can be given
for deriving the smart contract address from the deployer's address.
Clients
CLI
Find below a list of dep
commands added with the x/revenue
module.
You can obtain the full list by using the dep -h
command.
A CLI command can look like this:
dep query revenue params
Queries
Command | Subcommand | Description |
---|---|---|
query revenue | params | Get revenue params |
query revenue | contract | Get the revenue for a given contract |
query revenue | contracts | Get all revenues |
query revenue | deployer-contracts | Get all revenues of a given deployer |
query revenue | withdrawer-contracts | Get all revenues of a given withdrawer |
Transactions
Command | Subcommand | Description |
---|---|---|
tx revenue | register | Register a contract for receiving revenue |
tx revenue | update | Update the withdraw address for a contract |
tx revenue | cancel | Remove the revenue for a contract |
gRPC
Queries
Verb | Method | Description |
---|---|---|
gRPC | dep.revenue.v1.Query/Params | Get revenue params |
gRPC | dep.revenue.v1.Query/Revenue | Get the revenue for a given contract |
gRPC | dep.revenue.v1.Query/Revenues | Get all revenues |
gRPC | dep.revenue.v1.Query/DeployerRevenues | Get all revenues of a given deployer |
gRPC | dep.revenue.v1.Query/WithdrawerRevenues | Get all revenues of a given withdrawer |
GET | /dep/revenue/v1/params | Get revenue params |
GET | /dep/revenue/v1/revenues/{contract_address} | Get the revenue for a given contract |
GET | /dep/revenue/v1/revenues | Get all revenues |
GET | /dep/revenue/v1/revenues/{deployer_address} | Get all revenues of a given deployer |
GET | /dep/revenue/v1/revenues/{withdraw_address} | Get all revenues of a given withdrawer |
Transactions
Verb | Method | Description |
---|---|---|
gRPC | dep.revenue.v1.Msg/RegisterRevenue | Register a contract for receiving revenue |
gRPC | dep.revenue.v1.Msg/UpdateRevenue | Update the withdraw address for a contract |
gRPC | dep.revenue.v1.Msg/CancelRevenue | Remove the revenue for a contract |
POST | /dep/revenue/v1/tx/register_revenue | Register a contract for receiving revenue |
POST | /dep/revenue/v1/tx/update_revenue | Update the withdraw address for a contract |
POST | /dep/revenue/v1/tx/cancel_revenue | Remove the revenue for a contract |
Future Improvements
- The fee distribution registration could be extended to register the withdrawal address to the owner of the contract according to EIP173.
- Extend the supported message types for the transaction fee distribution to Cosmos transactions that interact with the EVM (eg: ERC20 module, IBC transactions).
- Distribute fees for internal transaction calls to other registered contracts.
At this time, we only send transaction fees to the deployer of the smart contract
represented by the
to
field of the transaction request (MyContract
). We do not distribute fees to smart contracts called internally byMyContract
. CREATE2
opcode support for address derivation. When registering a smart contract, we verify that its address is derived from the deployer’s address. At this time, we only support the derivation path using theCREATE
opcode, which accounts for most cases.