Integrate Stellar Assets Contracts
When interacting with assets in a smart contract, the Stellar Asset Contract is not different from any other token that implements the Stellar SEP-41 Token Interface.
Contract Code
The Rust SDK contains a pre-generated client for any contract that implements the token interface:
use soroban_sdk::{contract, contractimpl}
use soroban_sdk::token;
#[contract]
pub struct MyContract;
#[contractimpl]
impl MyContract {
pub fn token_fn(e: Env, id: Address) {
// Create a client instance for the provided token identifier. If the id
// value corresponds to an SAC contract, then SAC implementation is used.
let client = token::TokenClient::new(&env, &id);
// Call token functions part of the Stellar SEP-41 token interface
client.transfer(...);
}
}
The asset
parameter is not the address of the issuer of an asset, it corresponds to the deployed contract address for this asset.
stellar contract id asset \
--source G... \
--network testnet \
--asset [asset:issuer]
E.g. for USDC, it would be --asset USDC:G...
For the native asset, XLM, --asset native
. See the deploy SAC guide for more details.
A client created by token::TokenClient
implements the functions defined by any contract that implements the SEP-41 Token Interface. But with CAP-46-6 smart contract standardized asset, the Stellar Asset Contract exposes additional functions such as mint
. To access the additional functions, another client needs to be used: token::StellarAssetClient
. This client only implements the functions from CAP-46-6, which are not part of the SEP-41 interface.
let client = token::StellarAssetClient::new(&env, &id);
// Call token functions which are not part of the SEP-41 token interface
// but part of the CAP-46-6 Smart Contract Standardized Asset
client.mint(...);
Testing
Soroban Rust SDK provides an easy way to instantiate a Stellar Asset Contract tokens using register_stellar_asset_contract_v2
. This function can be seen as the deployment of a generic token. It also allows you to manipulate flags on the issuer account like AUTH_REVOCABLE
and AUTH_REQUIRED
. In the following example, we are following the best practices outlined in the Issuing and Distribution Accounts section:
#![cfg(test)]
use soroban_sdk::testutils::Address as _;
use soroban_sdk::{token, Address, Env};
use token::{StellarAssetClient, TokenClient};
#[test]
fn test() {
let e = Env::default();
e.mock_all_auths();
let issuer = Address::generate(&e);
let distributor = Address::generate(&e);
let sac = e.register_stellar_asset_contract_v2(issuer.clone());
let token_address = sac.address();
// client for SEP-41 functions
let token = TokenClient::new(&e, &token_address);
// client for Stellar Asset Contract functions
let token_sac = StellarAssetClient::new(&e, &token_address);
// note that you need to account for the difference between the minimal
// unit and the unit itself when working with amounts.
// E.g. to mint 1 TOKEN, we need to use 1*1e7 in the mint function.
let genesis_amount: i128 = 1_000_000_000 * 10_000_000;
token_sac.mint(&distributor, &genesis_amount);
assert_eq!(token.balance(&distributor), genesis_amount);
// Make issuer AuthRequired and AuthRevocable
sac.issuer().set_flag(IssuerFlags::RevocableFlag);
sac.issuer().set_flag(IssuerFlags::RequiredFlag);
}
Examples
See the full examples that utilize the token contract in various ways for more details:
- Timelock and single offer move token via
transfer
to and from the contract - Atomic swap uses
transfer
to transfer token on behalf of the user