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.
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
payableattribute, like#[payable("MYTOKEN-123456")]. This is a less common practice, as tokens are typically configured in storage or at runtime.
To read the payment details on single-token transfers, you can use:
let payment = self.call_value().single_kda();
To read the payment details on multi-token transfers, you can use:
let payments = self.call_value().all_kda_transfers();
payments.iter().for_each(|payment| {
// payment handling...
});
Payment objects (KdaTokenPayment) store three variables that you can use to assert the token and also gather the amount:
let amount = payment.amount;
let token = payment.token_identifier;
let token_nonce = payment.token_nonce;
If your endpoint only receives KLV, we provide a shortcut to access the transfer amount directly:
let amount = self.call_value().klv_value();
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.