如何在Aleo上构建和部署Leo程序
本文在Aleo测试网上构建了一个完全私有的代币,使用Leo 4.0语言实现两个功能:mint_private(创建加密记录)和transfer_private(私密转移)。所有状态以加密记录形式存在,网络只验证零知识证明,不暴露发送方、接收方或金额。文章涵盖了从Quicknode端点配置、Leo环境搭建、编写程序、测试到部署和执行交易的全流程,最后比较了链上可见性:区块浏览器只能看到加密的输入输出,无法获取任何隐私信息。
概述
在入门指南中,我们介绍了 Aleo 的架构:链下执行、链上验证,以及作为私有状态的记录(records)。本指南将把这些概念付诸实践。你将用 Leo 4.0 编写一个私有代币,对其进行测试,并通过 Quicknode 端点部署到 Aleo 测试网。
我们构建的代币特意设计得很简单:两个函数,没有公开状态。你以加密记录的形式铸造代币,并私下转移它们。网络用零知识证明验证每笔交易,但永远不会看到谁发送了代币、谁接收了它们、或者转移了多少。这就是 Aleo 的核心主张,也是我们这里要构建的目标。
TL;DR
- 我们构建一个具有两个函数的私有代币:
mint_private和transfer_private - 所有状态都存在于加密记录中,因此链上不可见任何信息
- 所有代码使用 Leo 4.0 语法:
fn用于函数,record用于私有状态类型 - 通过 Quicknode 端点,使用
leo deploy --broadcast部署到 Aleo 测试网
你将学到什么
- 如何编写一个使用记录实现完全私有状态的 Leo 程序
- 如何使用
@test注解和leo test进行测试 - 如何通过
leo deploy部署到 Aleo 测试网 - 如何执行私有交易并理解链上可见的内容(无)
你需要准备什么
- 熟悉 Aleo 和 Leo 入门 指南中的概念
- Rust 1.94.1 或更新版本(Leo 需要此版本。如有需要,运行
rustup update升级) - 一个 Quicknode 账户,并拥有 Aleo 测试网端点
- 来自 Aleo 水龙头 的测试网 ALEO 积分
- 终端访问权限
创建 Quicknode 端点
为了在 Aleo 网络上部署和与程序交互,你需要访问一个 Aleo RPC 端点。你可以运行自己的节点,但 Quicknode 处理基础设施,这样你就不必了。
如果还没有,注册一个 Quicknode 账户。登录后,点击 Create an endpoint,然后选择 Aleo 链和 Testnet 网络。

创建端点后,复制 HTTP Provider URL。稍后你将把它添加到项目配置中。
注意:在你的 Quicknode 端点 URL 末尾追加 /v2。Leo 在发出 API 调用时会自动附加网络路径(/testnet/...),而 Quicknode 的 Aleo 端点需要 /v2 前缀。
搭建开发环境
通过 cargo 安装 Leo 编译器:
cargo install leo-lang
验证安装:
leo --version
创建一个新项目:
leo new private_token
cd private_token
这会创建以下项目结构:
| 文件/目录 | 用途 |
|---|---|
src/main.leo |
你的程序源代码 |
tests/ |
包含 @test 注解函数的测试文件 |
program.json |
项目元数据:程序名称、版本、依赖项 |
你还需要一个用于部署配置的 .env 文件。在项目根目录下创建它:
警告:将 .env 添加到你的 .gitignore 中。永远不要将你的私钥提交到版本控制。
touch .env
添加以下内容:
NETWORK=testnet
PRIVATE_KEY=APrivateKey1zkp... # 使用 leo account new 生成一个
ENDPOINT=https://your-quicknode-aleo-endpoint.quiknode.pro/your-auth-token/v2
如果你还没有私钥,用 leo account new 生成一个并粘贴进去。对于端点,使用你之前复制的 Quicknode URL,并追加 /v2。
编写代币程序
我们正在构建一个私有代币。没有公开的余额账本,没有任何人可读的链上映射。每个代币都作为一个加密记录存在,只有所有者才能看到。当有人转移代币时,旧记录被消耗,新记录被创建,这一切都在链下完成。网络只看到一个证明,表明规则得到了遵守。
两个函数处理所有事情:
mint_private创建一个加密记录形式的新代币transfer_private将代币从一个地址转移到另一个地址,私下进行
将 src/main.leo 的内容替换为:
program private_token.aleo {
record Token {
owner: address,
amount: u64,
}
fn mint_private(receiver: address, amount: u64) -> Token {
return Token {
owner: receiver,
amount: amount,
};
}
fn transfer_private(sender: Token, receiver: address, amount: u64) -> (Token, Token) {
let difference: u64 = sender.amount - amount;
let remaining: Token = Token {
owner: sender.owner,
amount: difference,
};
let transferred: Token = Token {
owner: receiver,
amount: amount,
};
return (remaining, transferred);
}
@noupgrade
constructor() {}
}
逐项分析这里的代码:
record Token 定义了私有状态。每个代币都是账本上的一个加密记录,包含 owner 和 amount。只有所有者的查看密钥才能解密它。没有公开的余额表。
mint_private 创建一个新的 Token 记录。它完全在链下运行。网络验证证明,但永远不会看到接收者或金额。
transfer_private 接收一个现有的 Token 记录,消耗它,并产生两个新记录:一个给接收者,包含转移的金额;一个给发送者,包含剩余余额。这类似于比特币的 UTXO 模型,只是所有内容都是加密的。没有显式的余额检查,因为 Leo 的整数在溢出时不会回绕。如果 sender.amount - amount 变为负数,交易会自动失败。
@noupgrade constructor() {} —— 所有 Aleo 程序都需要一个构造函数。@noupgrade 注解锁定程序,使其在部署后无法更改。对于可升级的程序,你应该编写一个带有授权逻辑的构造函数,并使用 leo upgrade 推送新版本。
测试程序
Leo 有一个内置的测试框架。测试函数位于 tests/ 目录中,并使用 @test 注解。测试文件本身也是 Leo 程序,因此它们需要一个 program 块和一个针对被测试程序的 import。
打开现有的 tests/test_private_token.leo 并将其内容替换为:
import private_token.aleo;
program test_private_token.aleo {
@test
fn test_mint_private() {
let receiver: address = aleo1qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq3ljyzc;
let result: private_token.aleo::Token = private_token.aleo::mint_private(receiver, 100u64);
assert_eq(result.owner, receiver);
assert_eq(result.amount, 100u64);
}
@test
fn test_transfer_private() {
let alice: address = self.signer;
let bob: address = aleo1qgqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqanmpl0;
let minted: private_token.aleo::Token = private_token.aleo::mint_private(alice, 100u64);
let (remaining, transferred): (private_token.aleo::Token, private_token.aleo::Token) = private_token.aleo::transfer_private(minted, bob, 30u64);
assert_eq(transferred.owner, bob);
assert_eq(transferred.amount, 30u64);
assert_eq(remaining.owner, alice);
assert_eq(remaining.amount, 70u64);
}
@test
@should_fail
fn test_transfer_more_than_balance() {
let alice: address = aleo1qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq3ljyzc;
let bob: address = aleo1qgqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqanmpl0;
let minted: private_token.aleo::Token = private_token.aleo::mint_private(alice, 50u64);
// 这应该会失败:从只有50的记录中转移100
let (remaining, transferred): (private_token.aleo::Token, private_token.aleo::Token) = private_token.aleo::transfer_private(minted, bob, 100u64);
}
@noupgrade
constructor() {}
}
运行测试:
leo test

关于测试文件需要注意的几点:
import private_token.aleo;将主程序引入作用域- 类型和函数使用完整路径:
private_token.aleo::Token、private_token.aleo::mint_private() - 在
test_transfer_private中,我们使用self.signer作为铸造地址,因为虚拟机要求输入记录属于交易签名者 @should_fail告诉 Leo 该测试预期会失败。如果函数无错误完成,测试本身将失败- 每个程序(包括测试程序)都需要一个构造函数。我们在这里使用
@noupgrade以保持简单
你可以按名称过滤测试:
leo test mint
这只运行名称中包含 "mint" 的测试。
部署到 Aleo 测试网
程序编译通过且测试通过后,就可以部署了。
获取测试网积分
访问 Aleo 水龙头 并为你的账户地址请求测试网积分。
如何找到你的 Aleo 地址
运行 leo account import,使用 .env 中的私钥:
leo account import APrivateKey1zkp...
这将打印你的地址、查看密钥和私钥。复制该地址。
选择程序名称
你的程序名称在整个网络中必须是唯一的。如果 private_token.aleo 已被占用,请更新 src/main.leo 和 program.json 中的名称。短于10个字符的名称会产生名称空间费用,名称越短费用越高,类似于短域名成本更高。10个或更多字符的名称没有名称空间费用。
部署
leo deploy --broadcast --consensus-version 14
--consensus-version 标志告诉 Leo 要针对哪个版本的 Aleo 共识规则。如果没有该标志,Leo 会尝试从你的端点自动检测版本,但这并不总是有效。
Leo 会在要求确认之前显示费用明细:
| 费用组件 | 涵盖内容 |
|---|---|
| 存储 | 每字节程序数据1 millicredit |
| 合成 | 与电路复杂度成正比(num_variables + num_constraints) |
| 名称空间 | 针对短于10个字符的程序名称的额外费用(10个或更多字符为0) |
| 优先级 | 可选的小费,用于更快包含(默认为0) |
确认后,Leo 广播部署交易,并监控网络最多12个区块以确认包含。

你可以在 Aleo 浏览器 上验证部署。
执行私有交易
程序部署后,让我们实际使用它。以下每一步都在你的机器上链下运行,网络只看到一个证明。
铸造代币
leo execute mint_private <RECEIVER_ADDRESS> 1000u64 --broadcast --consensus-version 14
将 <RECEIVER_ADDRESS> 替换为你的 Aleo 地址。这会创建一个加密给接收者的 Token 记录。交易上链,但记录内容(所有者、金额)是加密的。监视链的人无法知道铸造了什么或给谁。
Leo 在执行后打印输出记录。它看起来像这样:
{
owner: aleo1abc...def.private,
amount: 1000u64.private,
_nonce: 6068899261705353026606683251009719039686976057706338283793929594435531277185group.public,
_version: 1u8.public
}
owner 和 amount 字段标记为 .private,意味着它们在账本上是加密的。保存完整记录,因为你需要将其作为输入传递给 transfer_private。
转移代币
leo execute transfer_private "{ owner: aleo1abc...def.private, amount: 1000u64.private, _nonce: 6068...185group.public, _version: 1u8.public }" <RECEIVER_ADDRESS> 300u64 --broadcast --consensus-version 14
将记录替换为铸造步骤中的完整输出,用引号括起来。整个 { owner: ..., amount: ..., _nonce: ..., _version: ... } 块是一个参数。这会消耗你现有的记录并创建两个新记录:300个代币给接收者,700个给你(更新后的金额)。
以下是网络看到的内容:一个有效的证明。仅此而已。没有发送者地址,没有接收者地址,没有金额。验证者确认数学计算正确,而不知道数学是关于什么的。
链上可见的内容
这就是整个练习的重点。以下是你在终端本地看到的内容:

你的终端显示两个输出记录(300个代币给接收者,700个返回给你)和一个交易 ID。你确切知道发生了什么。
现在,取那个交易 ID 并在 Aleo 浏览器 上搜索。概述选项卡确认交易已上链:

相同的交易 ID,已在网络上确认。但点击交易选项卡:

输入和输出都已加密。没有发送者、接收者、金额。验证者确认了证明有效,而不知道转移的内容。
与此相比,标准的 EVM 代币转账中,发送者、接收者、金额和完整交易历史任何人都可读。
结论
我们从空项目开始,到在 Aleo 测试网上部署了一个私有代币。该程序有两个函数:mint_private 创建代币,transfer_private 转移代币。所有状态都存在于加密记录中。代币余额或转移历史在链上不可见任何内容。
这是一个最小示例,但它展示了核心模式。生产代币会添加公共余额(使用映射和 final 块)、在公共和私有之间转换的函数,以及铸造的访问控制。入门指南 涵盖了映射和 final 块的工作原理,如果你想进一步扩展的话。
关于跨程序调用、组合的 Futures 和更高级的模式,请查阅 Aleo 开发者文档 和 Leo 示例 部分。
常见问题
在 Aleo 上部署 Leo 程序需要多少费用?
部署费用有三个组成部分:每字节程序数据1 millicredit 的存储成本,与电路复杂度成正比的合成成本(基于变量和约束的数量),以及短于10个字符的程序名称的名称空间费用。优先级费用是可选的,默认为0。Leo CLI 会在你确认部署之前显示完整的费用明细。
leo run 和 leo execute 有什么区别?
leo run 在本地编译和执行函数,不生成证明。它对于开发期间的快速测试很有用。leo execute 执行相同的本地执行,但还会合成电路并生成 zk-SNARK 证明,这是部署和链上执行所需的。
我可以在 Aleo 上更新已部署的 Leo 程序吗?
可以,如果程序是以可升级方式部署的,你可以使用 leo upgrade 命令部署新版本。升级会替换链上的程序字节码,同时保持相同的程序名称和地址。
为什么我在区块浏览器上看不到我的代币余额?
私有代币使用记录,而不是公共映射。记录在账本上是加密的,只有所有者可以用他们的查看密钥解密。区块浏览器只能显示公共状态(映射),因此私有代币余额对它们不可见。你只能通过 Leo CLI 在本地看到自己的记录。
我需要了解哪些 Leo 4.0 语法变化?
Leo 4.0 替换了几个关键字。transition 关键字现在是 fn,async function/finalize 被嵌入在 return 语句内的 final 块替代(return final { ... };)。具有链上逻辑的函数返回 Final 类型。在 final 块中使用的参数必须标记为 public。旧语法将无法在 Leo 4.0 或更高版本中编译。
我们 ❤️ 反馈!
告诉我们 如果你有任何反馈或新主题请求。我们期待听到你的声音。
- 原文链接: quicknode.com/guides/ale...
- 登链社区 AI 助手,为大家转译优秀英文文章,如有翻译不通的地方,还请包涵~