| name | assets |
| description | Stellar Assets (classic) + trustlines + Stellar Asset Contract (SAC) bridge to Soroban. Covers asset issuance, distribution, authorization flags, clawback, regulated assets, trustline management, and the SAC interop layer that exposes classic assets as Soroban tokens. Use when tokenizing real-world assets, issuing stablecoins, managing trustlines, or bridging classic assets to Soroban contracts. |
| user-invocable | true |
| argument-hint | [asset task] |
Stellar Assets, Trustlines, and SAC
Stellar's native token mechanism: classic asset issuance, trustlines, and the Stellar Asset Contract (SAC) bridge that makes classic assets usable from Soroban. Default to classic assets over custom Soroban tokens unless you need custom logic.
When to use this skill
- Issuing a new asset (stablecoin, security token, utility token)
- Setting up trustlines from a client or contract
- Managing issuer flags (auth required, auth revocable, clawback)
- Bridging a classic asset into a Soroban contract via SAC
- Building regulated-asset flows (compliance, KYC, freeze)
Related skills
- Custom token contracts (when classic isn't enough) →
../soroban/SKILL.md
- UI flows for trustline creation and asset display →
../dapp/SKILL.md
- Looking up balances and trustline state →
../data/SKILL.md
- Token-related SEPs (SEP-41, SEP-7, etc.) →
../standards/SKILL.md
Overview
Stellar has two token mechanisms:
- Stellar Assets (Classic): Built-in, highly efficient, full ecosystem support
- Soroban Tokens: Custom contracts with flexible logic
Recommendation: Prefer Stellar Assets unless you need custom token logic.
Stellar Assets (Classic)
Asset Types
| Type | Description |
|---|
| Native (XLM) | Stellar's native currency, no trustline needed |
| Credit | Issued by an account, requires trustline |
| Liquidity Pool Shares | Represent LP positions |
Asset Identifiers
import * as StellarSdk from "@stellar/stellar-sdk";
const xlm = StellarSdk.Asset.native();
const usdc = new StellarSdk.Asset(
"USDC",
"GA5ZSEJYB37JRC5AVCIA5MOP4RHTM335X2KGX3IHOJAPP5RE34K4KZVN"
);
Issuing Assets
Create Issuing Account
import * as StellarSdk from "@stellar/stellar-sdk";
const server = new StellarSdk.Horizon.Server("https://horizon-testnet.stellar.org");
const issuerKeypair = StellarSdk.Keypair.random();
const distributorKeypair = StellarSdk.Keypair.random();
await fetch(`https://friendbot.stellar.org?addr=${issuerKeypair.publicKey()}`);
await fetch(`https://friendbot.stellar.org?addr=${distributorKeypair.publicKey()}`);
Issue Asset
const asset = new StellarSdk.Asset("MYTOKEN", issuerKeypair.publicKey());
const distributorAccount = await server.loadAccount(distributorKeypair.publicKey());
const trustlineTx = new StellarSdk.TransactionBuilder(distributorAccount, {
fee: StellarSdk.BASE_FEE,
networkPassphrase: StellarSdk.Networks.TESTNET,
})
.addOperation(
StellarSdk.Operation.changeTrust({
asset: asset,
limit: "1000000",
})
)
.setTimeout(180)
.build();
trustlineTx.sign(distributorKeypair);
await server.submitTransaction(trustlineTx);
const issuerAccount = await server.loadAccount(issuerKeypair.publicKey());
const issueTx = new StellarSdk.TransactionBuilder(issuerAccount, {
fee: StellarSdk.BASE_FEE,
networkPassphrase: StellarSdk.Networks.TESTNET,
})
.addOperation(
StellarSdk.Operation.payment({
destination: distributorKeypair.publicKey(),
asset: asset,
amount: "1000000",
})
)
.setTimeout(180)
.build();
issueTx.sign(issuerKeypair);
await server.submitTransaction(issueTx);
Lock Issuing Account
For fixed-supply tokens, lock the issuer:
const lockTx = new StellarSdk.TransactionBuilder(issuerAccount, {
fee: StellarSdk.BASE_FEE,
networkPassphrase: StellarSdk.Networks.TESTNET,
})
.addOperation(
StellarSdk.Operation.setOptions({
masterWeight: 0,
})
)
.setTimeout(180)
.build();
lockTx.sign(issuerKeypair);
await server.submitTransaction(lockTx);
Asset Flags
Configure issuer account flags for compliance:
const setFlagsTx = new StellarSdk.TransactionBuilder(issuerAccount, {
fee: StellarSdk.BASE_FEE,
networkPassphrase: StellarSdk.Networks.TESTNET,
})
.addOperation(
StellarSdk.Operation.setOptions({
setFlags:
StellarSdk.AuthRequiredFlag |
StellarSdk.AuthRevocableFlag |
StellarSdk.AuthClawbackEnabledFlag,
})
)
.setTimeout(180)
.build();
Flag Descriptions
| Flag | Effect |
|---|
AUTH_REQUIRED | Users must get approval before receiving tokens |
AUTH_REVOCABLE | Issuer can freeze user balances |
AUTH_IMMUTABLE | Flags cannot be changed (permanent) |
AUTH_CLAWBACK_ENABLED | Issuer can clawback tokens from accounts |
Authorize Trustline
const authorizeTx = new StellarSdk.TransactionBuilder(issuerAccount, {
fee: StellarSdk.BASE_FEE,
networkPassphrase: StellarSdk.Networks.TESTNET,
})
.addOperation(
StellarSdk.Operation.setTrustLineFlags({
trustor: userPublicKey,
asset: asset,
flags: {
authorized: true,
},
})
)
.setTimeout(180)
.build();
Clawback Tokens
const clawbackTx = new StellarSdk.TransactionBuilder(issuerAccount, {
fee: StellarSdk.BASE_FEE,
networkPassphrase: StellarSdk.Networks.TESTNET,
})
.addOperation(
StellarSdk.Operation.clawback({
asset: asset,
from: targetAccountId,
amount: "100",
})
)
.setTimeout(180)
.build();
Trustlines
Create Trustline
const changeTrustTx = new StellarSdk.TransactionBuilder(userAccount, {
fee: StellarSdk.BASE_FEE,
networkPassphrase: StellarSdk.Networks.TESTNET,
})
.addOperation(
StellarSdk.Operation.changeTrust({
asset: asset,
limit: "10000",
})
)
.setTimeout(180)
.build();
Check Trustline Status
const account = await server.loadAccount(userPublicKey);
const trustline = account.balances.find(
(b) =>
b.asset_type !== "native" &&
b.asset_code === "USDC" &&
b.asset_issuer === usdcIssuer
);
if (trustline) {
console.log("Balance:", trustline.balance);
console.log("Limit:", trustline.limit);
console.log("Authorized:", trustline.is_authorized);
}
Stellar Asset Contract (SAC)
SAC provides Soroban interface for Stellar Assets, enabling smart contract interactions.
Deploy SAC for Existing Asset
stellar contract asset deploy \
--asset USDC:GA5ZSEJYB37JRC5AVCIA5MOP4RHTM335X2KGX3IHOJAPP5RE34K4KZVN \
--source alice \
--network testnet
SAC Address Derivation
import * as StellarSdk from "@stellar/stellar-sdk";
const asset = new StellarSdk.Asset("USDC", issuerPublicKey);
const contractId = asset.contractId(StellarSdk.Networks.TESTNET);
Using SAC in Soroban Contracts
use soroban_sdk::{token::Client as TokenClient, Address, Env};
pub fn transfer_asset(
env: Env,
from: Address,
to: Address,
asset_contract: Address,
amount: i128,
) {
from.require_auth();
let token = TokenClient::new(&env, &asset_contract);
token.transfer(&from, &to, &amount);
}
SAC vs Custom Token Interface
SAC implements the standard Soroban token interface:
balance(id: Address) -> i128
transfer(from: Address, to: Address, amount: i128)
approve(from: Address, spender: Address, amount: i128, expiration_ledger: u32)
allowance(from: Address, spender: Address) -> i128
decimals() -> u32
name() -> String
symbol() -> Symbol
When to Use What
Use Stellar Assets When:
- Standard fungible token (currency, stablecoin)
- Need full ecosystem support (wallets, exchanges)
- Regulatory compliance features (freeze, clawback)
- Performance critical (classic operations are cheaper)
- DEX integration via order book
Use Soroban Custom Tokens When:
- Complex transfer logic (royalties, fees, restrictions)
- Custom authorization schemes
- Non-standard token behaviors
- Integration with custom DeFi contracts
- NFTs or semi-fungible tokens
Use SAC When:
- Need Stellar Asset in Soroban contract
- Building DeFi protocols with existing assets
- Bridge between classic and smart contract operations
Querying Assets
Get Account Balances
const account = await server.loadAccount(publicKey);
for (const balance of account.balances) {
if (balance.asset_type === "native") {
console.log("XLM:", balance.balance);
} else {
console.log(`${balance.asset_code}:`, balance.balance);
}
}
Find Assets
const assets = await server
.assets()
.forCode("USDC")
.call();
const assetDetails = await server
.assets()
.forCode("USDC")
.forIssuer(issuerPublicKey)
.call();
Get Asset Statistics
const stats = await server
.assets()
.forCode("USDC")
.forIssuer(issuerPublicKey)
.call();
SEP Standards for Assets
SEP-0001 (stellar.toml)
Publish asset metadata in your domain's /.well-known/stellar.toml:
[[CURRENCIES]]
code = "MYTOKEN"
issuer = "GABC..."
display_decimals = 2
name = "My Token"
desc = "A description of my token"
image = "https://example.com/token-logo.png"
SEP-0010 (Web Authentication)
Authenticate users with their Stellar accounts:
SEP-0024 (Hosted Deposit/Withdrawal)
For fiat on/off ramps:
SEP-0045 (Web Auth for Contract Accounts)
Extends SEP-10 to support Soroban contract accounts (C... addresses) for web authentication. Required for smart wallet / passkey-based anchor integrations. See SEP-0045.
SEP-0050 (Non-Fungible Tokens)
Standard contract interface for NFTs on Soroban. Reference implementations available in OpenZeppelin Stellar Contracts with Base, Consecutive, and Enumerable variants. See SEP-0050.
Best Practices
Asset Issuance
- Use separate issuing and distribution accounts
- Lock issuer after initial distribution for fixed supply
- Publish stellar.toml with asset metadata
- Consider multisig for issuer account
Trustline Management
- Check trustline exists before sending payments
- Handle trustline creation in onboarding flow
- Respect trustline limits
- Monitor for frozen/deauthorized status
Security
- Validate asset issuer, not just code
- Be cautious of assets with clawback enabled
- Verify stellar.toml from authoritative source
- Use well-known asset lists for common tokens