mcpField
A
assets

assets

github.com/stellar开发55354 次安装2 天前更新
代码仓库

关于 assets

面向 Stellar 开发者的资产与信任线 skill,涵盖经典资产发行、合规标志管理,以及通过 Stellar Asset Contract(SAC)在智能合约中使用经典资产。

主要功能

  1. 01创建和发行 Stellar 经典资产
  2. 02创建、检查和移除信任线
  3. 03配置授权、冻结与追回标志
  4. 04通过 SAC 将资产接入智能合约
  5. 05查询资产余额与统计信息
  6. 06发布资产元数据

适用场景

  1. 01发行稳定币或证券型代币
  2. 02设置客户端或合约信任线
  3. 03构建受监管资产流程
  4. 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 有两种代币机制:

  1. Stellar 资产(经典):内置、高效,获得完整生态系统支持
  2. 合约代币(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 可以删除信任线,但前提是信任线处于可删除状态。提交前:

  1. 通过资产代码和发行方识别资产。 钱包中显示的条目或可领取余额不是信任线——只有账户上实际存在的经典资产信任线(或流动性池份额)才能通过 ChangeTrust 移除。可领取余额需要通过领取或追回来移除,不能使用 ChangeTrust。
  2. 清空余额。 信任线余额必须恰好为 0——将剩余资金发送回发行方(资金会被销毁)或发送到其他账户。
  3. 清除报价/买入负债。 买入该资产的未结 DEX 报价会产生买入负债;移除前请先取消报价(使用 amount: "0" 调用 manageSellOffer,使用 buyAmount: "0" 调用 manageBuyOffer)。
  4. 退出流动性池仓位。 被流动性池引用的资产信任线(根据 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) -> 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

该使用哪一种

适用 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,然后查明谁持有管理员合约的角色

评论

支持 Markdown