想在 Solana 上用你最熟的 Solidity 写程序?跟着本文 30 分钟搭建首个 Scoreboard 打分应用,从本地编译、测试到 主网部署 一步到位!
约 16 分钟完成阅读与实操
本文你将掌握
- Solang 编译器基础知识
- 用 Solidity 编写 Solana 程序账户(program) 与 PDA
- 通过 Anchor 完整跑通本地测试
- 一键迁移到 Solana 主网
准备清单
- 基本 Solidity 语法知识(非必须)
- Rust、cargo、Node.js ≥ 16.15、@latest Anchor CLI
- Solana CLI ≥ 1.16、TypeScript ≥ 5
- VS Code + Solang 高亮扩展(可选)
⚠️ Anchor 若要支持新的 Solidity 语法,需安装 main 分支版本
cargo install --git https://github.com/coral-xyz/anchor anchor-cli --locked --force
验证版本
solana --version
anchor --versionSolang 速通:从 EVM 到 SVM
核心关键词:Solang、账户模型、Program Derived Address(PDA)、IDL
账户模型
- 程序账户:仅存放代码,纯计算无状态
- 数据账户:状态与数据载体
- 构造函数:按需支付租金、确定种子,创建 PDA
示例起始定义
@program_id("F1ipperKF9EfD821ZbbYjS319LXYiBmjhzkkf5a26rC")
contract scoreboard { ... }Program Derived Address(PDA)
通过 种子 + bump 寻址,唯一且确定。使用注解:
@seed、@bump、@space、@payer- 构造函数内一次性创建
IDL 自动生成
Anchor 会在 ./target/types/*.ts 里生成 IDL,等同 ABI,前端、测试一应俱全。
创建 Scoreboard 程序(实战)
1. 初始化项目
anchor init scoreboard --solidity
cd scoreboardsolidity/scoreboard.sol:源码tests/scoreboard.ts:测试脚本
确认 Anchor.toml 钱包与 Localnet:
[provider]
cluster = "Localnet"
wallet = "/path/to/id.json"2. 定义数据结构
solidity/scoreboard.sol 头部写入:
@program_id("F1ipperKF9EfD821ZbbYjS319LXYiBmjhzkkf5a26rC")
contract scoreboard {
struct UserScore {
address player;
uint64 currentScore;
uint64 highestScore;
bytes1 bump;
}
UserScore private accountData;
...
}3. 构造函数 – 创建 PDA
紧跟结构体后面:
@payer(payer)
@seed("seed")
constructor(
@seed bytes _payer,
@bump bytes1 _bump,
address player
) {
print("New UserScore account initialized");
accountData = UserScore(player, 0, 0, _bump);
}4. 业务方法 – 增改查
function addPoints(uint8 numPoints) public {
require(numPoints > 0 && numPoints < 100, "INVALID_POINTS");
accountData.currentScore += numPoints;
if (accountData.currentScore > accountData.highestScore) {
accountData.highestScore = accountData.currentScore;
}
}
function resetScore() public {
accountData.currentScore = 0;
}
function getCurrentScore() public view returns (uint64) {
return accountData.currentScore;
}
function getHighScore() public view returns (uint64) {
return accountData.highestScore;
}一键编译
anchor build成功后会看到 LLVM IR + IDL 已生成。
测试用例全覆盖
替换 tests/scoreboard.ts 为完整脚本:
import * as anchor from "@coral-xyz/anchor";
import { Program } from "@coral-xyz/anchor";
import { PublicKey } from "@solana/web3.js";
import { expect } from "chai";
import { Scoreboard } from "../target/types/scoreboard";
const randomPoints = (min = 1, max = 100) =>
Math.floor(Math.random() * (max - min + 1)) + min;
describe("Scoreboard", () => {
const provider = anchor.AnchorProvider.env();
anchor.setProvider(provider);
const program = anchor.workspace.Scoreboard as Program<Scoreboard>;
const wallet = provider.wallet;
const seed = Buffer.from("seed");
const [dataAccount, bump] = PublicKey.findProgramAddressSync(
[seed, wallet.publicKey.toBuffer()],
program.programId
);
it("1. 初始化 PDA", async () => {
await program.methods
.new(wallet.publicKey.toBuffer(), [bump], wallet.publicKey)
.accounts({ dataAccount })
.rpc();
const score = await program.methods
.getCurrentScore()
.accounts({ dataAccount })
.view();
expect(score.toNumber()).to.eq(0);
});
it("2. 增加分数", async () => {
const points = randomPoints();
await program.methods
.addPoints(points)
.accounts({ dataAccount })
.rpc();
const score = await program.methods
.getCurrentScore()
.accounts({ dataAccount })
.view();
expect(score.toNumber()).to.eq(points);
});
it("3. 重置分数,高分保留", async () => {
const prep = randomPoints();
await program.methods
.addPoints(prep)
.accounts({ dataAccount })
.rpc();
await program.methods
.resetScore()
.accounts({ dataAccount })
.rpc();
const current = await program.methods
.getCurrentScore()
.accounts({ dataAccount })
.view();
const highest = await program.methods
.getHighScore()
.accounts({ dataAccount })
.view();
expect(current.toNumber()).to.eq(0);
expect(highest.toNumber()).to.eq(prep);
});
});运行测试
anchor test --skip-local-validator3 个用例全部绿钩 ✅
常见问题(FAQ)
Q1:Anchor --solidity 命令报错怎么办?
A:确保 Anchor 来自 main 分支;若已全局安装旧版本,先 cargo uninstall anchor-cli 再按上文装主分支。
Q2:为什么测试打印“failed to send transaction”?
A:本地 validator 未启动。可手动 solana-test-validator 或去掉 --skip-local-validator 让 Anchor 自动启动。
Q3:合约升级会更改 program ID 吗?
A:Solana 程序 不原生支持升级。需通过 BPF Loader 或使用 Squads / Goki SDK 实现“upgrade authority”,但普通项目建议重新部署并更新前端配置。
Q4:如何在前端调用合约?
A:使用 Anchor 生成的 IDL + @coral-xyz/anchor;下载后在 React/Vite 中直接 useProgram、useAccount hooks 就能跑。
Q5:PDA 种子可以修改吗?
A:种子生成后就 固定为该地址。若想换参数,需要新建不同种子的 PDA 或将数据迁移。
Q6:部署主网需要多少 SOL?
A:合约大小决定 rent exemption,几 KB 的小程序通常 0.04–0.08 SOL 即可。
将程序推上 主网
只差 3 步:
- 准备主网 HTTP RPC
👉 5 秒内获得的超快 Solana 节点地址,免运维即可直连主网 更新程序 ID
solana program deploy ./target/deploy/scoreboard.so -u mainnet-beta # 记录返回的新 program id修改
Anchor.toml[programs.mainnet] scoreboard = "<NEW_PROGRAM_ID>" [provider] cluster = "mainnet-beta" wallet = "/path/to/mainnet-wallet.json"
确认余额充足后直接 anchor deploy --provider.cluster mainnet-beta。首次部署约 15 秒完成。
下一步,进阶练练手
- 为
UserScore增加activated: bool并标记生命周期 - 重载测试:限制分数 1–𒊹100 并防复用账户
- 打通 Next.js DApp,实现实时排行榜
- NFT 绑定分数 → 链上成就勋章
👉 一站式查看链上数据、监控 TPS 和实时 RPC 延迟
你已完成 Solidity → Solana 的无缝迁移!把项目发到社区,或继续改良功能吧。任何疑问欢迎留言交流。祝你开发愉快!