
1. 先纠正一个认知ERC-20和Polkadot并不是同一套体系1.1 为什么这个问题经常被问错我隔三差五就会在私信里看到类似的问题“想发一个代币听说Polkadot很火能不能直接在Polkadot上部署ERC-20”说实话第一次看到这种组合时我愣了一下后来发现问的人还真不少。这里有个核心概念需要先掰扯清楚ERC-20是以太坊生态的Token标准它运行在EVM以太坊虚拟机上而不是一条公链的名字。Polkadot则是一条独立的Layer 0链它的核心是Substrate框架默认智能合约语言是Rust用的不是EVM。所以严格来说“在Polkadot上部署ERC-20”这个说法本身就是个伪命题。但这不代表你的需求无法满足只是需要把路线拆成三种不同的情况来看。第一种情况你想要的只是“发一个同质化代币”不在乎底层链叫什么那Polkadot生态里有自己的原生代币标准叫做PSP-22。它在功能上对标ERC-20代码写起来是Rust ink!发布流程也完全不同。第二种情况你非要用Solidity写的ERC-20合约代码那Polkadot生态里有一批EVM兼容平行链比如Moonbeam、Astar它们直接兼容EVM等于你把Solidity合约拿到这些链上跑部署方式和以太坊几乎一样。第三种情况你其实只是想快速发币体验一下流程那建议先在测试网上把ERC-20发一遍再决定到底上哪条主网。这篇文章我会把三条路线全部讲透但重点放在第三条——为什么说先跑通ERC-20再去看Polkadot是整个流程里最高效的路径。不管你是项目方想发治理代币还是个人开发者想搞个实验性项目或者纯粹想弄明白代币部署的底层逻辑这篇都能当一份可直接照做的速通手册。1.2 Polkadot里的“ERC-20”其实有两个意思在继续往下写之前得先把“Polkadot上的ERC-20”这句话翻译成人话。很多人被这句话卡住是因为没搞明白Polkadot的双层结构。Polkadot本身只负责中继链的功能——跨链消息传递、共享安全、共识。它不直接承载智能合约业务真正跑业务的是连接在它上面的平行链。每一条平行链都可以有自己的执行环境像Moonbeam这种直接用Substrate搭了个以太坊兼容层Solidity合约原封不动能跑。像Astar搞了个多虚拟机方案WASM和EVM都能跑。还有一批纯Substrate链比如早期的一些自定义平行链跑的是ink!原生合约。所以当你问“Polkadot能不能部署ERC-20”答案取决于你选哪条平行链。选了EVM兼容链你的ERC-20合约代码一行都不用改选了原生ink!链那就要写PSP-22合约。还有一个经常被忽略的点即使你走了EVM兼容链你的代币也不会自动变成“Polkadot原生资产”。它只是运行在Polkadot生态里某一条平行链上的普通合约代币。这个细节会在后文的具体操作里涉及先有个概念就好。2. 以太坊侧用OpenZeppelin模板10分钟发一个ERC-202.1 我为什么推荐Remix OpenZeppelin而不是自己写合约先别急着碰Polkadot我强烈建议你第一步先把ERC-20在以太坊测试网上发一遍。理由很简单ERC-20是你理解所有代币标准的基准线你只要把ERC-20搞明白后面看PSP-22会轻松非常多。发ERC-20最快的路径不是自己从零写而是用Remix IDE OpenZeppelin库。OpenZeppelin是智能合约界的标准库ERC-20、ERC-721、访问控制这些最常见的合约模板都是社区审计过无数遍的直接import比自己造轮子安全得多。你可能觉得“自己写一个ERC-20也就几十行代码”但凡是这么想的人基本都踩过同一个坑——transfer里忘了判断余额。ERC-20标准里最关键的三件事是谁拥有多少代币、转账时余额够不够、授权之后别人能花你多少。这三件事每件都有很多边界情况比如转账给零地址要报错授权额度不够要拒绝这些在OpenZeppelin里都是现成的你自己写容易漏。我当然不是说你不能自己写。如果你想深入理解ERC-20那自己写一遍绝对是好事。但如果你只是想快速部署上线用现成模板是唯一理性的选择。搞清楚“什么时候不造轮子”和“什么时候必须造轮子”本身也是区块链开发的重要能力。2.2 核心合约代码与参数详解打开Remixremix.ethereum.org在文件浏览器里新建一个合约文件比如MyToken.sol然后把下面这段粘进去// SPDX-License-Identifier: MIT pragma solidity ^0.8.20; import openzeppelin/contracts/token/ERC20/ERC20.sol; import openzeppelin/contracts/access/Ownable.sol; contract MyToken is ERC20, Ownable { constructor( string memory name_, string memory symbol_, uint256 initialSupply_ ) ERC20(name_, symbol_) Ownable(msg.sender) { _mint(msg.sender, initialSupply_ * 10 ** decimals()); } function mint(address to, uint256 amount) external onlyOwner { _mint(to, amount); } function burn(uint256 amount) external { _burn(msg.sender, amount); } }我逐段解释一下为什么这么写。import那两行是关键。ERC20.sol提供了标准代币的全部实现Ownable.sol是权限控制模块。有人问为什么发币还要带个权限控制因为绝大多数项目方都不希望币发出去之后自己完全插不上手——需要增发、销毁、暂停转账时得有一个管理员账户来操作。Ownable(msg.sender)这行把合约部署者设为管理员只有他能调用mint增发。构造函数里我定义了三个参数名称name、符号symbol、初始供应量initialSupply。注意第7行有个10 ** decimals()这是代币标准里最容易让人晕的地方。decimals()默认是18意思是1个代币实际上等于1 * 10^18个最小单位。你传initialSupply的时候传的是“人类可读的数量”比如100万那合约会把它乘以10的18次方变成链上真正存储的数值。_mint(msg.sender, initialSupply_ * 10 ** decimals());这行的意思是部署合约时把初始供应量全部铸造给部署者自己。实际项目里常见的做法是把一部分发给团队、一部分留给生态、一部分放进流动性池但最基础的玩法就是把100%先给部署者后面再用mint/transfer慢慢分配。mint和burn函数是我加的扩展。mint加了onlyOwner修饰符意思是只有合约owner能调用防止任何人随便增发导致代币通胀burn没有限制权限因为“任何人销毁自己的代币”本来就是合理的这也是ERC-20标准允许的操作。2.3 部署、验证、测试网试水代码写完后左侧面板切到“编译”页编译器版本选0.8.20以上取决于你用哪个版本点编译应该不会报错。然后切到“部署”页环境选Injected Provider - MetaMask钱包切到Goerli或者Sepolia测试网——Sepolia现在更常用一点测试币也相对好拿。部署时填三个参数name代币全名比如 “My First Token”symbol代币符号比如 “MFT”通常3到5个大写字母initialSupply初始供应量顺手填个 1000000填完点transact钱包弹窗确认等十几秒合约就上链了。在“已部署合约”列表里能看到你的合约地址点开就能看到name、symbol、totalSupply、balanceOf这些函数直接在Remix里就能调用。测试网跑通之后还有一步很容易被跳过但非常重要——到区块浏览器验证合约源码。Sepolia对应的浏览器是 sepolia.etherscan.io进去找到你合约的交易哈希打开合约页点“Contract”选项卡再点“Verify and Publish”把Remix里的源码原样贴上去编译器版本选一致验证通过之后你的合约就会在区块浏览器上显示完整的源码和ABI。这一步不只是“好看”它能让你后续在浏览器里直接读合约状态、看持币分布甚至调用部分函数对排查问题帮助极大。3. Polkadot原生路线ink! PSP-22才是“正统”3.1 PSP-22到底是什么回到标题里的“以Polkadot为例”如果你真想在Polkadot原生生态里发一个代币那你的目标是PSP-22不是ERC-20。PSP-22是Polkadot生态里由Web3 Foundation推动制定的同质化代币标准可以理解成“Polkadot世界的ERC-20”。为什么不能直接复用ERC-20的代码因为Polkadot原生平行链跑的是ink!合约ink!是Substrate团队开发的智能合约语言语法是Rust风格的编译目标是WASM。以太坊的Solidity合约跑在EVM里两套运行时完全不同代码不通。但值得庆幸的是PSP-22在接口设计上大量参考了ERC-20。比如它同样有transfer、approve、transfer_from、balance_of、total_supply这些核心方法。你只要理解了ERC-20的授权-转账模型PSP-22的代码看起来就非常亲切。这里要特别强调一个差异ERC-20里approve之后对方用transferFrom转你的钱这个授权额度是链上存储的一个数值。PSP-22在基础标准上还增加了一个可选的扩展接口叫PSP22Receiver用来处理“收到代币的合约是否需要回调通知”的问题。如果你只是发一个简单的代币这个扩展可以暂时不实现。3.2 环境准备Rust、cargo-contract、Swanky Studio在Polkadot原生链上开发没有Remix这种网页IDE帮你包办一切得先把本地环境搭起来。第一步安装Rust。打开终端执行curl --proto https --tlsv1.2 -sSf https://sh.rustup.rs | sh source $HOME/.cargo/env装完之后确认版本rustc --version cargo --version接下来装cargo-contract这是ink!合约的编译工具cargo install cargo-contract --locked --force装完验证cargo contract --version如果你遇到的是“明明装了但命令不存在”的情况多半是环境变量没刷新重新打开终端或者手动把$HOME/.cargo/bin加到PATH里就行。我个人的经验是与其手动一点点配环境不如直接用Swanky Studio。Swanky Studio是一套基于Node.js的ink!全栈开发脚手架自带合约模板、编译命令、前端交互面板。虽然早期版本有些小毛病比如依赖版本容易冲突但整体上它把“创建项目-编译合约-部署到测试网-前端调用”这条路打通了对新手非常友好。安装npx astar-network/swankylatest init my_psp22_project cd my_psp22_project它会交互式问你选哪个模板选PSP22就行。如果你不想用Swanky也可以手动建项目但Swanky会自动帮你把cargo-contract版本、ink!版本这些容易出错的依赖关系处理好省下不少事。3.3 用OpenBrush实现一个最小可用的PSP-22合约代码层面我建议直接基于OpenBrush库写。OpenBrush是Polkadot生态里的智能合约库地位类似以太坊里的OpenZeppelin里面封装好了PSP-22、PSP-34NFT等常用标准实现还提供可升级合约的支持。一个最简PSP-22合约大概长这样#![cfg_attr(not(feature std), no_std, no_main)] #[openbrush::contract] pub mod my_psp22 { use openbrush::contracts::psp22::*; use openbrush::traits::Storage; #[derive(Storage)] #[storage_item] pub struct MyPSP22 { #[storage_field] psp22: psp22::Data, } impl PSP22 for MyPSP22 {} impl PSP22Metadata for MyPSP22 { fn token_name(self) - OptionString { Some(String::from(MyPolkadotToken)) } fn token_symbol(self) - OptionString { Some(String::from(MPT)) } fn token_decimals(self) - u8 { 18 } } #[openbrush::constructor] pub fn new(initial_supply: Balance) - Self { let mut instance Self { psp22: psp22::initialize(), }; instance._mint(instance.env().caller(), initial_supply); instance } }拆开来看#[openbrush::contract]这个属性宏是ink!合约的入口标记告诉编译器“这是一个智能合约”。#[storage_item]宏把psp22::Data定义成合约的状态存储——这个Data内部包含了balances、allowances这些账本数据。impl PSP22 for MyPSP22 {}这行最神奇因为OpenBrush已经把全部标准方法的默认实现都写好了你只需要对它说“我要用这些默认实现”整个代币的转账、查询、授权逻辑就全都有了。如果你是第一次接触这种“trait默认实现”的写法可能会觉得不踏实但它和OpenZeppelin的is ERC20继承本质上是一回事只是Rust和Solidity在语法层面的表达方式不同。构造函数里psp22::initialize()负责初始化账本数据instance._mint(...)把初始供应量铸造给合约部署者。注意这里的_mint是PSP-22内部函数不是公共方法所以外部账户没法随便印钞。编译命令cd my_psp22_project cargo contract build编译完成之后在target/ink/目录下会生成.contract文件这是包含WASM字节码和ABI元数据的打包文件等于你拿到了一份可直接部署的合约产物。3.4 在Paseo测试网部署并交互部署PSP-22和部署ERC-20完全是两回事。以太坊有MetaMask钱包Polkadot生态用的是Polkadot.js插件钱包如果你之前没用过第一次打开它会觉得界面很简陋但习惯就好。流程是这样的安装Polkadot.js浏览器插件创建一个账户保存好助记词。领测试币。Polkadot生态的测试网目前比较常用的是Paseo之前还有Rococo但维护状态变动过务必以官方最新公告为准。进入Paseo测试网的领取测试币页面把你的账户地址粘贴进去点领取一般几分钟就能到账。打开Polkadot.js Apps界面apps.polkadot.js.org右上角切换网络到Paseo。进入“开发者 → 合约”页面先点击“上传合约代码”选择刚才编译出来的.contract文件确认WASM代码和Metadata都是可识别的。代码上传成功后再点“部署”输入初始供应量确认部署。部署同样需要支付gas费但测试网gas是领来的免费币所以随便造。部署完之后Polkadot.js Apps里可以直接读取合约的balance_of、total_supply也可以调用transfer给另一个账户转币。这部分交互体验确实没有Remix那么图形化但接口逻辑清清楚楚你甚至可以打开开发者工具看它是怎么构造outer call的对学习ink!很有帮助。4. 还有第三条路Polkadot上的EVM平行链直接部署ERC-204.1 Moonbeam、Astar这类EVM平行链是怎么回事如果你已经照着第2章在以太坊测试网上跑通了ERC-20又不想学Rust、不想折腾ink!工具链那第三条路最舒服挑一条支持EVM的Polkadot平行链把Solidity合约原封不动地部署上去。以Moonbeam为例它是Polkadot生态里最知名的EVM兼容平行链。它的设计思路是在Substrate运行时里嵌入了一个以太坊兼容层让以太坊开发者可以无缝迁移。这意味着钱包层面Metamask可以直接连接Moonbeam网络不需要装Polkadot.js。合约层面Solidity代码、ABI、地址格式全兼容。工具层面Remix、Hardhat、Foundry都直接支持。换句话说你不需要接受任何“新东西”只是把一个网络配方切换一下。如果你以后想发一个同时覆盖以太坊和Polkadot生态的代币完全可以同一个合约代码分别部署到以太坊主网和Moonbeam管理起来也不会多花太多精力。4.2 实际操作上和以太坊有什么区别操作上有几个区别值得提前知道。第一网络配置。在MetaMask里添加Moonbeam网络节点信息直接搜“Moonbeam network add metamask”就有现成的不用手填。第二gas费和速度。Moonbeam的gas机制和以太坊近似但gas费远低于以太坊主网交易速度也明显更快这对发币测试非常友好。第三部署工具。如果你在Remix里操作流程跟以太坊完全一致如果你用Hardhat只需要在hardhat.config.js里把network换成Moonbeam的RPC地址其他代码不用改。第四也是最重要的Moonbeam上部署的ERC-20代币和Polkadot原生生态的互操作性需要额外桥接工具处理。简单说你的ERC-20在Moonbeam上可以被任何EVM账户使用但它不会自动出现在其他平行链上。如果你想让代币跨链流动需要走XCM跨链消息格式或者借助第三方桥。对于大多数项目初期来说单链部署已经够用了这个点先了解就好不用纠结。我还想提醒一个很多人踩过的坑别在Moonbeam主网上随便测试部署。主网的gas虽然比以太坊便宜但真金白银还是钱。建议先部署到Moonbase Alpha测试网跑通流程确认没问题再上主网。5. 三条路线怎么选从“能跑”到“跑得好”的决策逻辑5.1 横向对比表维度ERC-20以太坊系PSP-22Polkadot原生ERC-20 on EVM平行链合约语言SolidityRust / ink!Solidity开发工具链Remix、Hardhat、Foundrycargo-contract、SwankyRemix、Hardhat钱包MetaMaskPolkadot.jsMetaMask部署门槛低中高低生态原生性以太坊原生Polkadot原生平行链内生态跨链能力需要专门桥天然XCM需要通过平行链的桥适合场景纯以太坊生态项目深度绑定Polkadot生态想同时覆盖两个生态5.2 选型逻辑和我的经验这张表看完可能你还是会纠结那到底选哪条我自己给项目的建议是这样分层看的。如果你的目标用户、合作伙伴、流动性都在以太坊生态那直接发ERC-20就行别折腾Polkadot。这是最稳妥的路径工具的成熟度、文档的丰富度、社区问答的活跃度都是最高的。我见过太多项目方为了蹭“Polkadot概念”而强行上原生链最后发现社区根本不用他们那条链的DApp项目冷启动难上加难。如果你的项目确实深度依赖Polkadot的跨链优势比如你要做跨链资产、桥接协议、或者借力Polkadot的共享安全模型那直接看PSP-22。虽然开发成本高但你能拿到Polkadot原生生态的支持。注意这里的“支持”不是嘴上说说而是你的代币可以相对顺畅地走XCM跨链这在技术架构上是实打实的加分项。如果你只是想快速验证一个想法又想让两边社区都看到我的建议是合约代码写Solidity版本在以太坊测试网和Moonbase Alpha上各发一遍。这样你既能跑通以太坊的工具链又能有个Polkadot生态的地址可以展示两边都不耽误。最后再强调一点选型时别只看“链的名气”或“标准的名气”。去看目标链上真实的DApp生态、真实活跃用户这不是一句空话你发的代币需要有人用才会有价值。技术上的“最好”和生态上的“最好”往往是两回事。6. 部署后别急着发空投这些坑我基本都踩过6.1 合约权限设计不当的坑第一个坑在ERC-20里最常见mint权限暴露了却忘了加权限修饰符。我见过有人写代码时图省事把mint函数写成外部可调用的结果部署完之后任何人都能调用mint给自己铸造无限量代币整个项目的经济模型直接崩了。这种问题在OpenZeppelin的模板里基本不会出现因为你继承的Ownable已经帮你把权限控制好了。但如果你自己写合约就一定要检查每个写操作的权限修饰符。PSP-22那边也有类似问题。OpenBrush默认的PSP22trait实现里_mint是内部函数外部不可见所以错误概率低一些。但如果你用了PSP22Mintable这个扩展却忘记配置onlyOwner之类的访问控制一样是灾难。6.2 测试不充分的坑第二个坑是只用Remix的“沙箱环境”测试没上真实测试网。沙箱环境Remix里那种JavaScript VM是内存模拟的每次刷新页面就没了。它能帮你快速验证合约逻辑有没有明显bug但解决不了这些问题交易竞态、真实的gas消耗、钱包交互体验、代币在区块浏览器里的显示是否正确。正确做法是至少走一遍Sepolia或Moonbase Alpha测试网领一些测试币把完整的流程——部署、转账、授权、查询——全部走完。我还会额外做一个测试部署一个新合约把旧合约的余额归零确认没有残留授权额度这一步在迁移代币时尤其重要但很容易被忘掉。6.3 工具链版本冲突的坑第三个坑是Cargo / ink! / OpenBrush三方版本互不兼容。这几乎是每个Rust新手都会撞上的墙。你照着某个教程装好了cargo-contract 3.x但项目里的ink!依赖还是4.x编译时直接报一堆类型不匹配的错。我在本地至少遇到过五六种不同的错误最后的经验只有一条全部以官方文档当前版本为准别抄旧教程的版本号。如果你用Swanky Studio初始化项目它会自动生成一份Cargo.toml和版本锁定配置推荐直接用它别自己手动改依赖版本。6.4 发币之后的安全审计与代码冻结合约部署完技术上你可能觉得大功告成但其实才走了一半。任何代币项目尤其是要面向社区开放的我都建议至少做一次基础的代码审视哪怕不请外部审计公司也要把这几件事确认掉合约里有没有隐藏的kill或pause功能谁会触发部署者私钥在谁手里密钥如何托管团队的锁仓代币有没有链上锁定还不是嘴上说锁三年如果不是完全开源社区凭什么信任你的代币另外一个更实际的建议是部署完尽快验证并发布合约源码。以太坊上走Etherscan验证PSP-22则要确保metadata文件已经上传让别人能通过Polkadot.js正常调用你的合约。我在实际操作中发现不少项目在测试阶段可以勉强用Remix或cargo-contract交互但到了主网就发现外部用户根本没法读合约因为源码没验证、metadata没公开。这种问题前期花五分钟就能解决后期别人问起来却非常尴尬。在我个人经验里发代币这事儿本身用“技术含量高”来形容其实不准确——它真正的门槛在部署前后的决策和安全习惯。你花一晚上就能跑通一条链的部署流程但要把它变成一个可信、可持续运转的项目需要花在验证、审计、密钥管理上的时间十倍不止。每次部署新代币我都会把上面这些坑再过一遍宁可多花几小时确认也不愿意事后处理一次那边理不清的账。