Solana 程序库代币(SPL Token)是 Solana 上的代币标准:它规定了如何创建代币,以及代币应如何行为。SPL Token 相当于以太坊上的代币标准,例如 ERC-20(同质化代币)和 ERC-721(NFT)。
与以太坊不同——以太坊为每种代币标准使用独立的智能合约——所有 SPL 代币在 Solana 上都使用同一个程序。这意味着 Solana 上的所有代币共享相同的底层逻辑,而代币特有的参数(如名称、精度、供应量等)是在创建时设定的,而不是通过不同的程序代码实现的。SPL 代币程序只包含逻辑,所有代币数据则单独存储。这与 Solana 将逻辑与状态分离到不同账户的设计理念一致。
可以这样理解 SPL 代币与以太坊代币的区别:
在以太坊上,你通常需要为每个新代币部署一个全新的智能合约(比如一个 ERC-20 合约);而在 Solana 上,你无需部署新代码,而是与这个单一的 SPL 程序交互,该程序已经包含了定义代币、铸造、转账、授权和销毁等所有所需指令。
在以太坊上,每个代币都是一个带有自定义代码的独立智能合约,这意味着 USDC 处理授权的方式可能与 DAI 不同。这种设计在灵活性上有优势,但也可能导致意外行为。而在 Solana 上,所有 SPL 代币使用相同的转账函数、相同的授权机制和相同的安全检查。
本文将解释 SPL 代币的核心概念,以及 Solana 如何将代币逻辑与代币数据分离。内容包括:
Solana 的代币架构与以太坊有何不同;
支撑 SPL 代币运作的三个关键账户;
为何 Solana 对所有代币使用同一个程序;
Solana 如何追踪用户的代币余额。
在下一篇文章《使用 Anchor 创建 SPL 代币》中,我们将演示如何创建和转账 SPL 代币。
要理解这些机制在实践中如何运作,我们首先从 SPL 代币程序本身入手。有时我们会简称为“代币程序”(token program),两者指的是同一个东西。
SPL 代币程序是负责管理 SPL 代币功能的核心链上程序。它包含创建 SPL 代币的逻辑,并定义了代币的行为规则。该程序部署在一个固定的地址上:TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA
该代币程序拥有所有存储 SPL 代币状态的账户(我们将在后文逐一介绍这些账户)。这种“所有权”意味着只有代币程序有权修改这些账户中的数据。
如果你需要回顾 Solana 的账户所有权模型,请参阅《理解 Solana 中的账户所有权》。
接下来,我们将介绍与 SPL 代币相关的几种账户类型:Mint 账户(铸币账户)、Token 账户(代币账户)以及关联代币账户(Associated Token Account, ATA)。每种账户在代币记账和转账过程中都扮演着特定角色。
每个独立的 SPL 代币都有一个唯一的 Mint 账户,用于存储该代币的全局信息。它保存的数据包括:
代币的总供应量(total supply);
小数位数(number of decimals);
具有铸币权限(mint authority)的地址(如有);
具有冻结账户权限(freeze authority)的地址(即“黑名单”功能)。
如前所述,代币的核心逻辑仍由 SPL 代币程序提供,而非存储在 Mint 账户中。
每个 Mint 账户都是唯一的,并在初始化一个新的 SPL 代币时通过 SPL 代币程序创建。在 Solana 中,当我们提到某个“代币地址”时,实际上指的就是它的 Mint 账户地址,因为二者是同一回事。
例如,以下是 USDC 和 USDT 的代币地址(即它们的 Mint 账户地址):EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v and Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB.


下图显示了代币计划与铸币账户之间的关系。

Mint 账户详情
和所有 Solana 账户一样,Mint 账户也包含标准的元数据字段,包括:
Lamports(用于支付租金)
Owner(在此情况下为代币程序的地址)
Executable 状态(值为 false,因为该账户仅用于存储数据)
如需了解更多关于 Solana 账户的内容,请参阅《在 Solana 和 Anchor 中初始化账户》。
除了这些标准字段外,Mint 账户还包含定义代币本身的特定数据字段:
mint_authority:被授权铸造新代币的地址。
freeze_authority:被授权冻结持有该代币的账户的地址(即“黑名单”功能)。
decimals:代币使用的小数位数(0–9)。
supply:已创建的代币总量。
is_initialized:布尔标志,防止重复初始化。
需要注意的是,Mint 账户仅存储代币的全局信息,并不记录各个用户的余额——用户余额由我们稍后将介绍的其他账户来管理。
下图展示了 Mint 账户的属性。

如上图所示,Mint 账户的 mint_authority 和 freeze_authority 在创建时分配,通常设为交易签名者的地址。我们将在“代币程序指令”部分看到相关的创建指令。
Mint 账户的一个重要特性是它如何控制代币的供应量,这通过 mint_authority 字段实现。下面我们将详细说明。
SPL 代币通过移除权限而非显式设置上限的方式来实现最大供应量。这一设计源于 Solana 与以太坊在状态管理上的根本差异。
Mint 账户的数据结构中没有“max supply”(最大供应量)字段。因此,若要创建固定供应量的代币,你需要将 mint_authority 设置为 None(空值),从而永久禁用铸币权限。一旦 mint_authority 被设为 None,就没有任何账户可以再铸造新代币,当前的总供应量也就成为最终的固定上限。
相比之下,在以太坊上,限制代币总供应量的传统做法是显式存储一个最大值,并在尝试超发时阻止铸造操作。而 SPL 代币没有“总供应上限”的概念,也不会在任何地方存储这样的数值。
在 SPL 中,如果你希望总供应量为 100 万枚代币,你只需先铸造全部 100 万枚并分发给持有者,然后将 mint_authority 设为 None。或者,正如我们将在后续教程中看到的那样,你也可以指定另一个程序作为 mint_authority,并让该程序在达到供应上限后停止铸造。
如前所述,Mint 账户只存储代币本身的元信息。为了追踪每个用户的余额,Solana 使用一种称为 Token Account(代币账户)的独立账户。
代币账户是 Solana 账户的一种,用于存储:
用户持有的某一代币的余额;
该账户所关联的 Mint 地址;
可授权转账的账户所有者(owner);
以及其他我们稍后会详细介绍的字段。
根据设计,一个用户可以为同一种代币拥有多个代币账户,这些账户位于不同的地址上。这带来了 Solana 程序库文档中指出的一个挑战:
“一个用户可能拥有任意多个属于同一 Mint 的代币账户,这使得其他用户难以知道应该向哪个账户发送代币。”
这意味着,用户的某一代币余额可能分散在多个账户中,而不是集中在一个地方。
例如:
Alice 在一个代币账户中有 5 枚代币,在另一个账户中有 15 枚;
这两个账户都属于同一个 Mint,因此她总共拥有 20 枚该代币;
但如果 Bob 想给 Alice 转账,他无法轻易知道 Alice 希望接收代币的具体账户是哪一个。
这就是 SPL 文档所说的“任意多个代币账户”带来的问题——这些余额不是冗余副本,而是总余额被拆分到多个账户中的碎片。
为了解决这个问题,Solana 引入了 关联代币账户(Associated Token Account, ATA)。
与普通代币账户不同,ATA 是一种特殊的代币账户,其地址通过确定性规则生成,并强制在用户钱包地址与代币 Mint 之间建立一对一关系。这确保了:
每个用户对每种代币有且仅有一个可预测的 ATA;
任何应用程序都可以在无需预先配置的情况下,轻松找到用户的代币余额,因为地址是确定性生成的(下文将解释原理)。
由于上述普通代币账户带来的用户体验问题,ATA 已成为 Solana 上管理代币的标准方式,本文也将主要聚焦于 ATA。
ATA 地址是一种 程序派生地址(Program Derived Address, PDA),由以下两个输入确定性地推导而来:
用户/签名者钱包的地址(即预期的 authority);
代币的 Mint 账户地址。
我们可以将 ATA 类比为以太坊 ERC-20 合约中的 mapping(address => uint256) public balanceOf,因为两者都用于追踪用户拥有的代币数量。
但由于一个用户可能持有多种 SPL 代币,仅用用户地址作为键不足以区分不同代币的余额。因此,Mint 地址也被纳入地址推导过程。通过组合用户钱包地址和代币 Mint 地址,Solana 确保每个 (用户, 代币) 组合都对应一个唯一的 ATA 地址。
这种设计避免了冲突,并建立了统一的结构:
user_wallet_address + token_mint_address ⇒ associated_token_account_address
为更清晰起见,下表对比了以太坊和 Solana 管理代币余额的方式:
| Aspect | Ethereum (ERC-20) | Solana (ATAs) |
| Storage Model | One central contract stores balances in a mapping | Each user has a separate account (ATA) for each token |
| Balance Location | Stored inside the token contract | Stored inside the user’s ATA |
| Who Pays for Storage | The contract owner (deployment cost) | The user pay for their account |
| Lookup | Call balanceOf(user) | Derive ATA address → read balance |
| Parallel Access | Limited by contract | Fully parallel |
两者目标一致——追踪代币所有权——但 Solana 的设计支持并行处理,因为每个余额都位于独立的账户中。
下图展示了关联代币账户的字段结构。

ATA 存储了用户对某一特定代币(Mint)的余额详情,其关键字段包括:
mint:该账户所代表的代币的 Mint 账户地址。例如,如果是 USDC 余额,则此处为 USDC 的 Mint 地址。
owner:虽然名为“owner”,但实际上是该 ATA 的授权方(authority)。请注意,每个 ATA 的真正所有者(Owner)始终是代币程序,因为它执行所有规则。这里的 owner 字段告诉代币程序:谁必须签名才能执行转账或更新操作。
回顾《Owner 与 Authority 的区别》一文:账户的 Owner 负责执行规则,而 Authority 是唯一有权发送指令修改账户的签名者(除非 Authority 通过代币程序委托了签名权)。
amount:该账户中持有的代币数量。
delegate:被授权代表用户转账的委托地址。每个代币账户只能有一个 delegate(因为只有一个 delegate 字段),这与 ERC-20 不同(ERC-20 允许 owner 授权多个 spender)。
state:账户状态,是一个枚举值,可为 Uninitialized(未初始化)、Initialized(已初始化)或 Frozen(已冻结)。
close_authority:被授权关闭该账户的地址。默认与 owner 相同,但 owner 可指定其他地址。当账户余额归零时,owner 可关闭账户以收回用于支付租金的 SOL。许多工具(如 Solflare 钱包、Sol-Incinerator)提供了便捷的空账户清理功能。
下图展示了代币程序、Mint 账户与代币账户之间的关系。

(从此以后,当我们提到“代币账户”时,也包括 ATA,因为 ATA 只是代币账户的一种特殊类型。)
关联代币账户程序的固定地址为:ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL
这是一个链上程序,用于为给定的用户-代币对查找或创建正确的 ATA。它负责确定性地址推导,并在需要时通过跨程序调用(CPI)调用代币程序来创建新的 ATA。
具体来说,ATA 程序协调的创建流程如下:

与以太坊不同——在以太坊中,余额隐式存在于合约存储中——Solana 要求显式创建账户。这带来了一个根本性的用户体验挑战:你无法向尚未创建接收账户的用户发送代币,因为代币余额实际上就存储在 ATA 中。
因此,在发送任何代币之前,我们必须为该用户-代币对创建 ATA。
首先,离线(off-chain)使用用户钱包地址和 Mint 地址确定性地推导出 ATA 地址;
然后,如果该 ATA 尚未存在,就通过关联代币账户程序在链上创建它。
这引发了一个重要的安全问题:如果任何人都能为他人创建 ATA,他们是否也能将自己设为该 ATA 的 owner 或 close_authority?
幸运的是,不会。
当为另一个钱包创建 ATA 时,ATA 程序强制规定:
owner 和 close_authority 字段必须设为该 ATA 所属的钱包地址,
而不是交易签名者的地址。
这一安全保证内置于 ATA 程序的代码中,确保只有合法的钱包所有者才能控制其代币和关闭账户的权利。
举例说明:
当 Alice 想给 Bob 发送代币时:
她先推导出 Bob 的 ATA 地址;
如果该 ATA 不存在,就在链上创建它;
然后调用代币程序的 Transfer 指令,将 Bob 的 ATA 作为目标地址。
实际开发中,像
@solana/spl-token这样的客户端库提供了辅助函数,可自动完成 ATA 推导与创建步骤。
下图展示了 ATA 程序的内容。

我们已经讨论了创建和管理 SPL 代币所涉及的各类账户。接下来,我们将介绍代币程序和ATA 程序的指令。这些指令使你能够:
创建并铸造新代币;
在 ATA 之间转账;
授权他人代为支出你的代币;
销毁代币以减少供应量;
关闭空账户以收回租金。
这些功能共同构成了 Solana 上代币生态的基石。
让我们来探索代币计划提供的公共功能,这些功能允许您与 SPL 代币进行交互。
请注意,在以下指令参数中,当我们提及令牌账户时,它们既可以是普通令牌账户,也可以是关联令牌账户 (ATA),如前文“令牌账户和关联令牌账户”部分所述。当需要区分时,我们会明确指出指的是普通令牌账户还是关联令牌账户。
代币程序具有以下公共功能:
InitializeMint:此指令创建一个新的铸币账户,该账户代表链上的新 SPL 代币。
pub fn initialize_mint( mint_pubkey: &Pubkey, // The mint account to initialize decimals: u8, // The number of decimal places for the token mint_authority: &Pubkey, // The account with permission to create new tokens freeze_authority: Option<&Pubkey> // Optional: The account that can freeze token accounts ) -> Instruction
这 mint_pubkey 可以是未使用的密钥对账户的地址 ,也可以是 计划初始化为铸币账户的PDA的地址。我们将在下一篇教程中实际演示这个过程。
InitializeAccount:此指令初始化一个新的常规代币账户(非ATA),用于保存用户在特定SPL代币铸造期间的余额。
pub fn initialize_account( account_pubkey: &Pubkey, // The token account to initialize mint_pubkey: &Pubkey, // The mint for the new token account owner_pubkey: &Pubkey // The owner of the new token account ) -> Instruction
ATA 由 ATA 程序初始化,该程序在InitializeAccount底层执行 CPI 指令。我们稍后会看到具体细节。
Transfer此指令用于将 SPL 代币从一个用户的代币账户(源账户)转移到另一个用户的代币账户(目标账户)。“余额”是指存储在关联代币账户中的一个数字,只有 SPL 程序才能修改该数字。请注意,在MintTo 调用此指令之前,铸币账户和代币账户必须存在或已创建(MintTo以下指令也适用)。我们将在下一个教程中使用 Anchor 进行演示。
pub fn transfer( source_pubkey: &Pubkey, // The token account sending tokens (typically the sender's ATA; not the mint account) destination_pubkey: &Pubkey, // The destination token account (to which tokens are received) authority_pubkey: &Pubkey, // The owner or delegate authorized to spend from the sending token account amount: u64 // The number of tokens to transfer ) -> Instruction
MintTo:此指令创建新的代币单位,并将其添加到指定的代币帐户。
pub fn mint_to( mint_pubkey: &Pubkey, // The token mint address account_pubkey: &Pubkey, // The token account to mint to authority_pubkey: &Pubkey, // The mint's minting authority amount: u64 // The amount to mint ) -> Instruction
Burn此指令会从代币账户中销毁指定数量的 SPL 代币,从而减少代币总供应量。其工作原理类似于 ERC20 的销毁功能。
pub fn burn( account_pubkey: &Pubkey, // The token account to burn from mint_pubkey: &Pubkey, // The token mint authority_pubkey: &Pubkey, // The token account's owner/delegate amount: u64 // The amount to burn ) -> Instruction
Approve此指令将代币账户所有者的支出权限委托给指定的代理人,并设定最大金额限制。它会设置代币账户的授权码 delegate 和账户的授权金额;同一时间只能存在一个代理人。在授权金额限制内,代理人可以代表所有者转移代币。
与将授权值存储在合约映射中的 ERC-20 不同,SPL 直接将批准记录在所有者的代币账户(通常是 ATA)中。这种设计使得批准和转账可以在单个交易中完成,因为仅修改了代币账户的状态。
pub fn approve( source_pubkey: &Pubkey, // The token account granting approval delegate_pubkey: &Pubkey, // The delegate account owner_pubkey: &Pubkey, // The owner of the token account granting approval amount: u64 // The maximum number of tokens the delegate can transfer ) -> Instruction
Revoke此指令会取消之前授予的任何委托批准(通过该指令授予的)。它会将令牌帐户的 字段设置为 “无委托”,Approve从而彻底移除委托 。delegateNone
由于审批额度不能部分减少,如果您想降低限额,则必须设置一个金额较小的新审批额度,类似于 ERC20 减少限额的方式。
pub fn revoke( source_pubkey: &Pubkey, // The token account revoking approval (same account that previously granted approval) owner_pubkey: &Pubkey // The owner of the token account revoking approval ) -> Instruction
FreezeAccount此指令用于冻结代币账户,暂时阻止任何涉及该账户中代币的转账或交易,直至账户解冻。换句话说,SPL 支持将用户的代币账户地址列入黑名单。
pub fn freeze_account( account_pubkey: &Pubkey, // The token account to freeze mint_pubkey: &Pubkey, // The token mint authority_pubkey: &Pubkey // The mint's freeze authority ) -> Instruction
ThawAccount此指令可解冻先前冻结的代币账户,从而恢复代币转移和交易。
pub fn thaw_account( account_pubkey: &Pubkey, // The token account to unfreeze mint_pubkey: &Pubkey, // The token mint authority_pubkey: &Pubkey // The mint's freeze authority ) -> Instruction
SetAuthority:此指令更改了铸币和代币账户中某些权限角色的持有者。
请注意,铸币账户有两个字段具有“权限”:
mint_authority
freeze_authority
关联的令牌账户有两个字段名为“authority”。
owner是代币的所有者,而不是 PDA 的“Solana 运行时所有者”(这种命名容易引起混淆)。
delegate一个可以代表所有者花费代币的公钥
下面的“ account_pubkeyin”set_authority既可以指铸币账户,也可以指代币账户。
指定的权限authority_type必须与该账户所拥有的权限类型相符。
Solana的 SPL 源代码为四种授权类型中的每一种都给出了一个枚举名称:
MintTokens
FreezeAccount
AccountOwner
CloseAccount
请注意,SPL 程序在代币账户中没有以一致的方式提及权限角色,并且令人困惑地将代币所有者称为“所有者”——这不应与 PDA 的所有者混淆。
pub fn set_authority( account_pubkey: &Pubkey, // The mint or token account current_authority_pubkey: &Pubkey, // The current authority authority_type: AuthorityType, // The type of authority to change (e.g., MintTokens, FreezeAccount) new_authority_pubkey: Option<&Pubkey> // The new authority, or None to disable ) -> Instruction
Revoke 与 具有相同的效果,因为 SetAuthority 两者都会改变谁拥有权限。 Revoke 清除代币账户的委托(设置 delegate 为 None),而 SetAuthority 更改铸币/账户权限(MintTokens, FreezeAccount, AccountOwner) CloseAccount。
CloseAccount此指令将永久关闭关联的代币账户,并收回用于使该账户免租的 SOL 导入余额。但是,ATA 中基础铸造代币的余额必须正好为零,否则将返回错误。
pub fn close_account( account_pubkey: &Pubkey, // The account to close destination_pubkey: &Pubkey, // The account to receive the reclaimed SOL owner_pubkey: &Pubkey // The closing account's owner ) -> Instruction
现在我们来讨论ATA程序的关键指令。
ATA程序与Token程序配合使用,其主要指令如下:
Create此指令会在由钱包地址和代币铸造地址组合而成的确定性PDA地址处创建一个ATA 。如果该地址已存在账户,则此指令会失败。
pub fn create_associated_token_account( payer: &Pubkey, // The account funding the creation wallet_address: &Pubkey, // The wallet address for the ATA token_mint: &Pubkey // The token mint ) -> Instruction
CreateIdempotent确保派生的PDA地址处存在正确的ATA。如果需要,则创建帐户。但与该Create指令不同的是,即使正确的帐户已存在,它也能成功执行而不会出错。
pub fn create_associated_token_account_idempotent( payer: &Pubkey, // The account funding the creation wallet_address: &Pubkey, // The wallet address for the ATA token_mint: &Pubkey // The token mint ) -> Instruction
两者 Create 都 CreateIdempotent 导出 ATA 地址,然后执行 CPI 到令牌程序的 InitializeAccount指令(我们之前已经看到过),以设置关联的令牌帐户。
总而言之,Solana 的 SPL 代币架构基于程序逻辑与代币数据的基本分离。与以太坊为每个代币部署新合约不同,Solana 上的所有代币都由同一个核心代币程序管理。
以下是需要记住的最重要几点:
逻辑与状态:单一代币程序包含所有规则(转账、铸造、销毁),但本身不保存任何代币数据(余额、小数位数、供应量)。它作为所有 SPL 代币的通用逻辑引擎。
铸币账户即代币:铸币账户定义了一个唯一的代币。它存储代币的全局信息,例如总供应量、小数位数以及拥有增发权限的机构。铸币账户的地址即为代币的地址(例如,USDC 的铸币地址)。
余额存储在代币账户/关联代币账户 (ATA) 中: 用户余额存储在独立的代币账户或关联代币账户 (ATA) 中。与使用一个大型合约将地址映射到余额不同,每个用户都会为其拥有的每种代币类型创建一个单独的账户。ATA 的地址由用户的钱包地址和代币的铸造地址共同生成,这是推荐的解决方案。
SPL架构的一些优势
并行处理:由于每个用户的余额都在单独的账户中,网络可以同时处理数千笔转账,而不会互相干扰。
标准化:所有 SPL 代币,无论是稳定币还是概念币,都遵循核心代币计划的完全相同的安全逻辑。这降低了自定义代币合约可能出现的漏洞风险。
原文:https://rareskills.io/post/spl-token
免责声明:本文为c2e Labs的第三方内容,仅供信息分享与传播之目的,不代表我们的立场或观点且不构成任何投资及应用建议。版权归原作者或来源方所有,如内容或素材有所争议请和我们取得联系。