白名单证明
本页的纯哈希与证明构造规则按版本化预售源码核查。提交认购时按认购指南传入目标部署的 PackageVersion。
白名单预售存储一个 32 字节的 Merkle 根及叶节点数量。每个叶节点对应一个钱包,包含其索引和认购上限。钱包通过提交索引、上限和证明参与认购。
哈希计算
跳转到“哈希计算”leaf = blake2b256( 0x00 || "blast-presale-v1" // the byte 0x00, then ASCII || bcs(index: u64) // 8 bytes, little-endian || bcs(wallet: address) // 32 bytes || bcs(wallet_cap: u64) ) // 8 bytes, little-endian
node(left, right) = blake2b256( 0x01 || left || right )验证
跳转到“验证”合约从叶节点向上遍历,初始 i = index。依次处理每个兄弟节点:i 为偶数时计算 node(digest, sibling),为奇数时计算 node(sibling, digest),然后将 i 整除以二。最终结果必须等于根。
- 证明必须恰好包含
ceil(log2(leaf_count))个兄弟节点,每个 32 字节。只有一个叶节点时,证明为空,根即该叶节点。 index必须小于leaf_count,wallet_cap必须为正数。- 叶节点的
wallet必须是交易发送者。 - 合约按提交值处理兄弟节点,因此树构建器自行决定如何处理未配对节点。下方构建器将其与自身配对。
TypeScript 树构建器
跳转到“TypeScript 树构建器”此构建器使用 @noble/hashes 及 @mysten/sui 的 BCS 编码器。
import { blake2b } from "@noble/hashes/blake2.js";import { bcs } from "@mysten/sui/bcs";
export type Entry = { wallet: string; cap: bigint };
const LEAF_PREFIX = new Uint8Array([0x00, ...new TextEncoder().encode("blast-presale-v1")]);const hash = (...parts: Uint8Array[]) => { const bytes = new Uint8Array(parts.reduce((n, p) => n + p.length, 0)); let offset = 0; for (const p of parts) { bytes.set(p, offset); offset += p.length; } return blake2b(bytes, { dkLen: 32 });};
export const leaf = (index: number, { wallet, cap }: Entry) => hash( LEAF_PREFIX, bcs.u64().serialize(index).toBytes(), bcs.Address.serialize(wallet).toBytes(), bcs.u64().serialize(cap).toBytes(), );
const node = (left: Uint8Array, right: Uint8Array) => hash(new Uint8Array([0x01]), left, right);
/** Builds every level of the tree. An unpaired node is paired with itself. */export function buildTree(entries: Entry[]): Uint8Array[][] { const levels: Uint8Array[][] = [entries.map((entry, i) => leaf(i, entry))]; while (levels.at(-1)!.length > 1) { const below = levels.at(-1)!; const above: Uint8Array[] = []; for (let i = 0; i < below.length; i += 2) above.push(node(below[i], below[i + 1] ?? below[i])); levels.push(above); } return levels;}
export const root = (levels: Uint8Array[][]) => levels.at(-1)![0];
/** The siblings from the leaf up, in the order the contract consumes them. */export function proof(levels: Uint8Array[][], index: number): Uint8Array[] { const siblings: Uint8Array[] = []; for (let level = 0, i = index; level < levels.length - 1; level++, i = Math.floor(i / 2)) { const row = levels[level]; siblings.push(row[i ^ 1] ?? row[i]); } return siblings;}创建预售时,通过 allowlisted_eligibility(root, leaf_count) 发布根及叶节点数量,再向各钱包提供其索引、上限和证明,供应用执行认购。
上限可统一、分档或单独设置。白名单钱包不设最低认购额。