关于 assets
面向 Stellar 开发者的资产与信任线 skill,涵盖经典资产发行、合规标志管理,以及通过 Stellar Asset Contract(SAC)在智能合约中使用经典资产。
主要功能
- 01创建和发行 Stellar 经典资产
- 02创建、检查和移除信任线
- 03配置授权、冻结与追回标志
- 04通过 SAC 将资产接入智能合约
- 05查询资产余额与统计信息
- 06发布资产元数据
适用场景
- 01发行稳定币或证券型代币
- 02设置客户端或合约信任线
- 03构建受监管资产流程
- 04在智能合约中使用现有 Stellar 资产
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ stellar/ stellar-dev-skill --skill assets安装后在 Claude Code 中输入/assets确认已加载。
详细介绍
Stellar 资产、信任线与 SAC
Stellar 的原生代币机制:经典资产发行、信任线,以及 Stellar 资产合约(SAC)桥接,让经典资产可供智能合约使用。除非需要自定义逻辑,否则优先使用经典资产,而非自定义合约代币。
何时使用此技能
- 发行新资产(稳定币、证券型代币、实用型代币)
- 从客户端或合约设置信任线
- 管理发行方标志(需要授权、授权可撤销、追回)
- 通过 SAC 将经典资产桥接到智能合约
- 构建受监管资产流程(合规、KYC、冻结)
相关技能
- 自定义代币合约(经典资产无法满足需求时)→
../smart-contracts/SKILL.md - 信任线创建和资产展示的 UI 流程 →
../dapp/SKILL.md - 查询余额和信任线状态 →
../data/SKILL.md - 代币相关 SEP(SEP-41、SEP-7 等)→
../standards/SKILL.md
概述
Stellar 有两种代币机制:
- Stellar 资产(经典):内置、高效,获得完整生态系统支持
- 合约代币(SEP-41):具有灵活逻辑的自定义合约
建议:除非需要自定义代币逻辑,否则优先使用 Stellar 资产。
Stellar 资产(经典)
资产类型
| 类型 | 描述 |
|---|---|
| 原生(XLM) | Stellar 的原生货币,无需信任线 |
| 信用资产 | 由账户发行,需要信任线 |
| 流动性池份额 | 代表 LP 仓位 |
资产标识符
import * as StellarSdk from "@stellar/stellar-sdk";
// Native XLM
const xlm = StellarSdk.Asset.native();
// Credit asset (code + issuer)
const usdc = new StellarSdk.Asset(
"USDC",
"GA5ZSEJYB37JRC5AVCIA5MOP4RHTM335X2KGX3IHOJAPP5RE34K4KZVN"
);
// Asset code rules:
// - 1-4 chars: alphanumeric (credit_alphanum4)
// - 5-12 chars: alphanumeric (credit_alphanum12)
发行资产
创建发行账户
import * as StellarSdk from "@stellar/stellar-sdk";
const server = new StellarSdk.Horizon.Server("https://horizon-testnet.stellar.org");
// 1. Create issuing account (should be separate from distribution)
const issuerKeypair = StellarSdk.Keypair.random();
const distributorKeypair = StellarSdk.Keypair.random();
// 2. Fund accounts (testnet)
await fetch(`https://friendbot.stellar.org?addr=${issuerKeypair.publicKey()}`);
await fetch(`https://friendbot.stellar.org?addr=${distributorKeypair.publicKey()}`);
发行资产
const asset = new StellarSdk.Asset("MYTOKEN", issuerKeypair.publicKey());
// 1. Distributor creates trustline to issuer
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", // Max amount to hold
})
)
.setTimeout(180)
.build();
trustlineTx.sign(distributorKeypair);
await server.submitTransaction(trustlineTx);
// 2. Issuer sends tokens to distributor
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);
锁定发行账户
对于固定供应量的代币,锁定发行方:
const lockTx = new StellarSdk.TransactionBuilder(issuerAccount, {
fee: StellarSdk.BASE_FEE,
networkPassphrase: StellarSdk.Networks.TESTNET,
})
.addOperation(
StellarSdk.Operation.setOptions({
masterWeight: 0, // Disable master key
})
)
.setTimeout(180)
.build();
lockTx.sign(issuerKeypair);
await server.submitTransaction(lockTx);
// Issuer can never issue more tokens
资产标志
配置发行方账户标志以满足合规要求:
const setFlagsTx = new StellarSdk.TransactionBuilder(issuerAccount, {
fee: StellarSdk.BASE_FEE,
networkPassphrase: StellarSdk.Networks.TESTNET,
})
.addOperation(
StellarSdk.Operation.setOptions({
setFlags:
StellarSdk.AuthRequiredFlag | // Trustlines require approval
StellarSdk.AuthRevocableFlag | // Can freeze trustlines
StellarSdk.AuthClawbackEnabledFlag, // Can clawback tokens
})
)
.setTimeout(180)
.build();
标志说明
| 标志 | 效果 |
|---|---|
AUTH_REQUIRED | 用户必须先获得批准才能接收代币 |
AUTH_REVOCABLE | 发行方可以冻结用户余额 |
AUTH_IMMUTABLE | 标志不可更改(永久) |
AUTH_CLAWBACK_ENABLED | 发行方可以从账户追回代币 |
授权信任线
// When AUTH_REQUIRED is set, approve trustlines:
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,
// authorizedToMaintainLiabilities: true, // Partial auth
},
})
)
.setTimeout(180)
.build();
追回代币
// Requires AUTH_CLAWBACK_ENABLED flag
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();
信任线
创建信任线
const changeTrustTx = new StellarSdk.TransactionBuilder(userAccount, {
fee: StellarSdk.BASE_FEE,
networkPassphrase: StellarSdk.Networks.TESTNET,
})
.addOperation(
StellarSdk.Operation.changeTrust({
asset: asset,
limit: "10000", // Max amount to hold (see "Remove Trustline" for limit: "0")
})
)
.setTimeout(180)
.build();
移除信任线
使用 limit: "0" 的 ChangeTrust 可以删除信任线,但前提是信任线处于可删除状态。提交前:
- 通过资产代码和发行方识别资产。 钱包中显示的条目或可领取余额不是信任线——只有账户上实际存在的经典资产信任线(或流动性池份额)才能通过
ChangeTrust移除。可领取余额需要通过领取或追回来移除,不能使用ChangeTrust。 - 清空余额。 信任线余额必须恰好为
0——将剩余资金发送回发行方(资金会被销毁)或发送到其他账户。 - 清除报价/买入负债。 买入该资产的未结 DEX 报价会产生买入负债;移除前请先取消报价(使用
amount: "0"调用manageSellOffer,使用buyAmount: "0"调用manageBuyOffer)。 - 退出流动性池仓位。 被流动性池引用的资产信任线(根据 CAP-0038,
liquidity_pool_use_count > 0)无法删除——请先从池中提取资金并移除池份额信任线。
// 1. Check the trustline is deletable
const account = await server.loadAccount(userPublicKey);
const trustline = account.balances.find(
(b) =>
b.asset_type !== "native" &&
b.asset_type !== "liquidity_pool_shares" &&
b.asset_code === asset.getCode() &&
b.asset_issuer === asset.getIssuer()
);
if (!trustline) throw new Error("No trustline (display/claimable state only?)");
if (parseFloat(trustline.balance) !== 0)
throw new Error("Balance must be 0 — send funds away or back to issuer");
if (parseFloat(trustline.buying_liabilities) !== 0)
throw new Error("Cancel open offers buying this asset first");
// Liquidity-pool usage (precondition 4) is not visible on this balance line —
// if the asset is still in one of the account's pools, the submit below fails
// with op_cannot_delete. Withdraw and remove the pool-share trustline first.
// 2. Submit removal and surface the specific result code
const removeTrustTx = new StellarSdk.TransactionBuilder(account, {
fee: StellarSdk.BASE_FEE,
networkPassphrase: StellarSdk.Networks.TESTNET,
})
.addOperation(
StellarSdk.Operation.changeTrust({
asset: asset,
limit: "0", // Delete the trustline
})
)
.setTimeout(180)
.build();
removeTrustTx.sign(userKeypair);
try {
await server.submitTransaction(removeTrustTx);
} catch (e) {
const codes = e.response?.data?.extras?.result_codes?.operations ?? [];
if (codes.includes("op_invalid_limit")) {
// CHANGE_TRUST_INVALID_LIMIT: balance or buying liabilities remain
throw new Error("Clear balance and open offers before removing trustline");
}
if (codes.includes("op_cannot_delete")) {
// CHANGE_TRUST_CANNOT_DELETE: trustline is used by a liquidity pool
throw new Error("Withdraw from liquidity pools referencing this asset first");
}
throw e;
}
检查信任线状态
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 资产合约(SAC)
SAC 为 Stellar 资产提供智能合约接口,让智能合约能够与其交互。
为现有资产部署 SAC
# Get the SAC address for an asset
stellar contract asset deploy \
--asset USDC:GA5ZSEJYB37JRC5AVCIA5MOP4RHTM335X2KGX3IHOJAPP5RE34K4KZVN \
--source-account alice \
--network testnet
SAC 地址推导
import * as StellarSdk from "@stellar/stellar-sdk";
const asset = new StellarSdk.Asset("USDC", issuerPublicKey);
const contractId = asset.contractId(StellarSdk.Networks.TESTNET);
// Returns the deterministic SAC contract address
在智能合约中使用 SAC
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();
// Use standard token interface
let token = TokenClient::new(&env, &asset_contract);
token.transfer(&from, &to, &amount);
}
SAC 与自定义代币接口的对比
SAC 实现了标准的 SEP-41 代币接口:
balance(id: Address) -> i128transfer(from: Address, to: Address, amount: i128)approve(from: Address, spender: Address, amount: i128, expiration_ledger: u32)allowance(from: Address, spender: Address) -> i128decimals() -> u32name() -> Stringsymbol() -> Symbol
该使用哪一种
适用 Stellar 资产的情况:
- 标准同质化代币(货币、稳定币)
- 需要完整的生态系统支持(钱包、交易所)
- 需要监管合规功能(冻结、撤销)
- 对性能要求高(经典操作成本更低)
- 通过订单簿集成 DEX
适用自定义合约代币的情况:
- 复杂的转账逻辑(版税、费用、限制)
- 自定义授权机制
- 非标准代币行为
- 集成自定义 DeFi 合约
- NFT 或半同质化代币
将完全自定义代币作为最后的选择。上面提到的许多情况 (版税、费用、转账限制)都可以通过常规的 Stellar 已发行资产和一个轻量的“SAC 管理”合约实现,该合约会调用 SAC 的
mint、burn和clawback函数。这样既能保留普通资产 与生态系统的兼容性(DEX、锚点、钱包、信任线), 又能叠加自定义逻辑。只有在确实需要 SAC 无法表达的代币行为时, 才优先考虑自定义合约代币。这已在生产环境中运行。 USDT0 (
USDT0:GATISXX6BZ6NC7IKQBY37CJD4SOZL3CYZJWXEDG6JVIY4WBS6KXJHN6Q)是一个 经典资产,其 SAC 管理员是一个合约:一个受角色控制的管理器, 会将mint、clawback、set_authorized和set_admin转发给 SAC, 跨链桥仅持有铸币者角色。请参阅 设置自定义 SAC 管理员 了解此模式,并参阅../cross-chain/layerzero.md了解该部署。一项硬性前提:锁定发行方(主密钥权重为
0)。 来自经典发行方的付款属于铸币,因此,若发行方未锁定,就能在合约外铸币, 完全绕过角色模型。按以下顺序操作:先调用
set_admin,再锁定发行方。 SAC 的 管理员最初是发行方账户,且只有当前管理员才能授权首次调用set_admin。 如果在调用前锁定发行方,管理员就会永远是已锁定的发行方,因为没有人能签署移交 (SAC 管理员指南)。 移交后,SAC 仍会由新的管理员铸币,因此应在此时锁定 发行方。
适用 SAC 的情况:
- 需要在智能合约中使用 Stellar 资产
- 使用现有资产构建 DeFi 协议
- 在经典操作与智能合约操作之间搭建桥接
查询资产
获取账户余额
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);
}
}
查找资产
// Search for assets by code
const assets = await server
.assets()
.forCode("USDC")
.call();
// Get specific asset details
const assetDetails = await server
.assets()
.forCode("USDC")
.forIssuer(issuerPublicKey)
.call();
获取资产统计信息
const stats = await server
.assets()
.forCode("USDC")
.forIssuer(issuerPublicKey)
.call();
// stats includes:
// - amount: total issued
// - num_accounts: trustline count
// - flags: issuer flags
资产相关 SEP 标准
SEP-0001(stellar.toml)
在你的域名 /.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 身份验证)
使用用户的 Stellar 账户进行身份验证。流程:服务端生成挑战交易,客户端使用其钱包签署交易,然后服务端验证签名。请参阅 SEP-0010。
SEP-0024(托管存款/提现)
用于法币入金/出金:通过交互式 WebView 流程,由锚点处理 KYC 和法币转账。请参阅 SEP-0024。
SEP-0045(合约账户的 Web 身份验证)
扩展 SEP-10,以支持合约账户(C... 地址)的 Web 身份验证。基于智能钱包或通行密钥的锚点集成需要此标准。该标准目前处于草案状态;请在 SEP-0045 中确认当前状态。
SEP-0050(非同质化代币)
Stellar 上 NFT 的标准合约接口。OpenZeppelin Stellar Contracts 提供 Base、Consecutive 和 Enumerable 变体的参考实现。该标准目前处于草案状态;请在 SEP-0050 中确认当前状态。
最佳实践
资产发行
- 使用独立的发行账户和分发账户
- 固定供应量的资产在首次分发后锁定发行方账户
- 发布包含资产元数据的 stellar.toml
- 考虑为发行方账户启用多重签名
信任线管理
- 发送付款前,检查信任线是否存在
- 在用户引导流程中处理信任线创建
- 遵守信任线限额
- 监控资产是否处于冻结或取消授权状态
- 移除信任线(
limit: "0")前:将余额清零、取消买入该资产的挂单,并退出引用该资产的流动性池
安全
- 验证资产发行方,而不只是资产代码
- 谨慎对待启用了赎回功能的资产
- 从权威来源核实 stellar.toml
- 常见代币使用知名资产列表进行验证
- 有些已上线的资产完全没有
stellar.toml。 缺少home_domain并不代表该资产是骗局——USDT0 就没有。应通过发行方和 SAC 推导进行验证,切勿依据是否存在home_domain或[[CURRENCIES]]条目来判断 - 当
AUTH_REVOCABLE和AUTH_CLAWBACK_ENABLED同时启用时,在列出资产前,检查发行方锁定状态 和 管理员角色。 这些标志意味着余额可以被冻结或追回。合约 SAC 管理员本身并不拥有这些权限:如果发行方的主密钥仍可签名,无论管理员合约允许什么,发行方都能直接铸造、冻结和追回资产。因此,请确认主密钥权重为0,然后查明谁持有管理员合约的角色