Understanding Payments in Smart Contracts
Introduction to Payments in Smart Contracts
This section aims to provide a comprehensive guide on how smart contracts handle payments, focusing on both receiving and sending tokens.
Note: On the Klever network, it is not possible to simultaneously send KLV and any KDA token. Consequently, you will not find any syntax supporting the transfer of both in either receiving or sending scenarios.
Methods of Receiving Payments
Smart contracts can receive payments in two primary ways:
- Directly, akin to regular accounts, without activating any contract code.
- Through specific endpoints.
Direct Receipt of Payments
Direct transfers of KLV and KDA tokens to smart contracts operate similarly to transfers to externally owned accounts (EOAs) – the tokens move from one account to another without engaging the VM.
However, a contract's ability to directly receive tokens is governed by a "payable" flag, a part of the code metadata set during the contract's deployment or upgrade.
This design choice stems from the Klever blockchain's approach to direct transfers, which prioritizes simplicity and consistent gas costs, foregoing mechanisms for contracts to respond to such transfers. Consequently, most contracts opt out of accepting direct payments to maintain control over their internal state.
Receipt of Payments via Endpoints
The prevalent method for contracts to accept payments is through endpoints marked with the #[payable(...)]
annotation.
Important: The "payable" flag in the code metadata is only relevant to direct transfers. It does not influence token transfers via contract endpoints.
Endpoints designed to accept only KLV should use the #[payable("KLV")]
annotation:
#[endpoint]
#[payable("KLV")]
fn accept_KLV(&self) {
// ...
}
To allow for any type of payment, use #[payable("*")]
:
#[endpoint]
#[payable("*")]
fn accept_any_payment(&self) {
// ...
}
Note: It's possible to hard-code a specific token identifier in the
payable
attribute, like#[payable("MYTOKEN-123456")]
. This is a less common practice, as tokens are typically configured in storage or at runtime.
You can impose additional restrictions on incoming tokens within the endpoint's body by utilizing the call value API, which provides various functions for managing expected payments.
Approaches to Sending Payments
After exploring how contracts receive tokens, let's look at how they can send them, primarily through methods in the self.send()
component.
Direct Token Transfers
Contracts can directly send tokens using several API methods:
- Methods like
self.send().direct_kda(to, token, nonce, amount)
enable direct token transfers to a specific address. - Multiple methods exist for different scenarios, including zero and non-zero amounts, KLV or KDA tokens, single or multiple KDA tokens, and more.
Sending Tokens to Contract Endpoints
To send tokens to contract endpoints, a contract call is necessary. While direct API methods exist for this purpose, it's recommended to create and send a ContractCall
. Details on this process can be found in the contract calls reference.
Low-Level API Usage
In cases where the standard methods don't suffice, self.send_raw()
offers more granular control for token transfers. This should be a last resort, as detailed in the contract calls page.