
Tempo Testnet 节点搭建完整教程
Kaito 新人如何撸空投,从注册到写作入门指南(嘴撸)
“生命的货币不是金钱,甚至也不是时间,而是注意力。” ——Kaito 白皮书开篇如此说道。创作者创造内容,用户贡献注意力,品牌付出广告费——价值却流向平台大户。 Kaito 创新性地把注意力变成资产,通过 AI 驱动的 Yap 机制,赋能内容创作生态。如果你还没参与,现在也不晚!一、什么是 Kaito Yaps?Kaito Yaps 是 Kaito 的 AI 驱动算法系统,用来衡量内容贡献价值:不只是点赞转发,而是分析高质量推文、Smart Followers、互动深度等多维评分;越多高质量互动,Yap 分越高,排行榜排名越靠前。多数项目空投目标都面向前 100–200 名 Yapper。二、InfoFi 网络 + Launchpad 背后的生态逻辑Kaito Connect(InfoFi 网络):品牌可直接将空投奖励发放给表现优秀的 Yapper,无需中介参与,公平透明;Launchpad 启动板:基于 Yaps 排行机制,项目可以筛选高质量内容贡献者提前参与测试、营销或领取 Token 空投。三、Kaito 第一季空投结果回顾Yap 与 NFT Holder 共分空投份额,权...
🚀 Nockchain 矿池挖矿完整指南
什么是 Nockchain?Nockchain 是一个面向重计算的轻量级 ZK L1,采用 zkPoW(解 ZKP 谜题来获得 $NOCK)。主网已经上线,支持个人矿工参与,CPU 也能挖。参考:Nockchain GitHub https://mirror.xyz/pangdong.eth/4Sgzv7BIrKWfWRF_uidM3s92XlKy3iyihWJA8CFKx2Y第 1 步:生成钱包地址下载或编译 nockchain-wallet运行以下命令生成密钥对:nockchain-wallet keygen 你会得到:New Private Key → 私钥(只能自己保存,千万不要泄露)New Public Key → 公钥(Base58,大约 132 个字符)👉 在 Nockchain 生态中,这个 Base58 公钥就是钱包地址,用于绑定矿池。第 2 步:创建账户令牌 (Account Token)打开矿池后台页面 https://nockpool.com/signup输入你的 132 字符 Base58 公钥地址点击 创建账户令牌保存 YOUR_ACCOUNT_TO...

Tempo Testnet 节点搭建完整教程
Kaito 新人如何撸空投,从注册到写作入门指南(嘴撸)
“生命的货币不是金钱,甚至也不是时间,而是注意力。” ——Kaito 白皮书开篇如此说道。创作者创造内容,用户贡献注意力,品牌付出广告费——价值却流向平台大户。 Kaito 创新性地把注意力变成资产,通过 AI 驱动的 Yap 机制,赋能内容创作生态。如果你还没参与,现在也不晚!一、什么是 Kaito Yaps?Kaito Yaps 是 Kaito 的 AI 驱动算法系统,用来衡量内容贡献价值:不只是点赞转发,而是分析高质量推文、Smart Followers、互动深度等多维评分;越多高质量互动,Yap 分越高,排行榜排名越靠前。多数项目空投目标都面向前 100–200 名 Yapper。二、InfoFi 网络 + Launchpad 背后的生态逻辑Kaito Connect(InfoFi 网络):品牌可直接将空投奖励发放给表现优秀的 Yapper,无需中介参与,公平透明;Launchpad 启动板:基于 Yaps 排行机制,项目可以筛选高质量内容贡献者提前参与测试、营销或领取 Token 空投。三、Kaito 第一季空投结果回顾Yap 与 NFT Holder 共分空投份额,权...
🚀 Nockchain 矿池挖矿完整指南
什么是 Nockchain?Nockchain 是一个面向重计算的轻量级 ZK L1,采用 zkPoW(解 ZKP 谜题来获得 $NOCK)。主网已经上线,支持个人矿工参与,CPU 也能挖。参考:Nockchain GitHub https://mirror.xyz/pangdong.eth/4Sgzv7BIrKWfWRF_uidM3s92XlKy3iyihWJA8CFKx2Y第 1 步:生成钱包地址下载或编译 nockchain-wallet运行以下命令生成密钥对:nockchain-wallet keygen 你会得到:New Private Key → 私钥(只能自己保存,千万不要泄露)New Public Key → 公钥(Base58,大约 132 个字符)👉 在 Nockchain 生态中,这个 Base58 公钥就是钱包地址,用于绑定矿池。第 2 步:创建账户令牌 (Account Token)打开矿池后台页面 https://nockpool.com/signup输入你的 132 字符 Base58 公钥地址点击 创建账户令牌保存 YOUR_ACCOUNT_TO...
Subscribe to pangdong
Subscribe to pangdong
<100 subscribers
<100 subscribers
用最小可用的 Demo,带你从零实现:密文加法 + 明文解密展示。 技术栈:Solidity + Hardhat + React/Vite + Zama FHEVM SDK。 目标链:Sepolia 测试网。
一个可以接受加密输入(+1)的合约 PrivateCounter
一个前端 DApp:
先连接任意 EIP-1193 钱包(MetaMask/OKX/Bitget 等均可)
使用 Zama Relayer 注册加密输入,调用合约的 add()
通过合约发起解密 requestReveal(),等待回调后读取 totalPlain
一键部署到 Vercel 的网站链接
Node.js ≥ 18(推荐 20+)
pnpm(也可用 npm/yarn)
npm i -g pnpm
一个可用的 Sepolia RPC(公共节点即可)
推荐先测试延迟/可用性:
# 任选其一可用即可
for u in https://ethereum-sepolia.publicnode.com https://rpc.sepolia.org; do
printf "%-38s" "$u"
r=$(curl -s -m 6 -H "Content-Type: application/json" -d '{"jsonrpc":"2.0","method":"eth_blockNumber","params":[],"id":1}' "$u")
bn=$(echo "$r" | sed -n 's/.*"result":"\([^"]*\)".*/\1/p');
if [ -n "$bn" ]; then echo " OK block=$bn"; else echo " FAIL resp=$(echo "$r" | cut -c1-80)"; fi
done
钱包(浏览器扩展),建议 MetaMask
mkdir -p hello-fhevm && cd hello-fhevm
pnpm init -y
mkdir hardhat && cd hardhat
pnpm add -D hardhat typescript ts-node @types/node @nomicfoundation/hardhat-toolbox \
@fhevm/solidity @fhevm/hardhat-plugin dotenv
pnpm dlx hardhat # 选“Create a TypeScript project”
生成的 hardhat.config.ts 替换为(适配 FHEVM 插件并禁用它在非本地链的 remapping):
// hardhat/hardhat.config.ts
import { HardhatUserConfig } from "hardhat/config";
import "@nomicfoundation/hardhat-toolbox";
import * as dotenv from "dotenv";
dotenv.config();
const SEPOLIA_RPC_URL = process.env.SEPOLIA_RPC_URL || "https://ethereum-sepolia.publicnode.com";
const PRIVATE_KEY = (process.env.PRIVATE_KEY || "").replace(/^"|"$/g, "");
const config: HardhatUserConfig = {
solidity: {
version: "0.8.24",
settings: { viaIR: false, optimizer: { enabled: true, runs: 200 } },
},
networks: {
sepolia: {
url: SEPOLIA_RPC_URL,
accounts: PRIVATE_KEY ? [PRIVATE_KEY] : [],
},
},
};
export default config;
说明:FHEVM 的
@fhevm/hardhat-plugin只用于本地模拟开发,本教程直接用 Zama 提供的链上 KMS/Relayer,不需要在编译阶段做 remapping,所以上面的配置不启用插件扩展。
.env(放在 hardhat/ 目录下):
SEPOLIA_RPC_URL=https://ethereum-sepolia.publicnode.com
PRIVATE_KEY=0x你的私钥
// hardhat/contracts/PrivateCounter.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;
import "@fhevm/solidity/lib/FHE.sol";
import { SepoliaConfig } from "@fhevm/solidity/config/ZamaConfig.sol";
/**
* 私密计数器:
* - add(): 接收加密的 euint32,累计到密文 total
* - requestReveal(): 发起一次解密请求(走 Zama KMS)
* - onReveal(): KMS 回调,验签 & 更新明文 totalPlain
*/
contract PrivateCounter is SepoliaConfig {
euint32 private total;
uint32 public totalPlain;
bool private pending;
uint256 private lastReqId;
constructor() {
total = FHE.asEuint32(0);
FHE.allowThis(total);
}
function add(
externalEuint32 encryptedDelta,
bytes calldata inputProof
) external {
euint32 delta = FHE.fromExternal(encryptedDelta, inputProof);
total = FHE.add(total, delta);
FHE.allowThis(total);
}
function requestReveal() external {
require(!pending, "Decrypting in progress");
bytes32;
// 某些版本是 toBytes(total);新版本为 toBytes32(total)
handles[0] = FHE.toBytes32(total);
lastReqId = FHE.requestDecryption(handles, this.onReveal.selector);
pending = true;
}
function onReveal(
uint256 requestId,
bytes memory cleartexts,
bytes memory decryptionProof
) external returns (bool) {
require(requestId == lastReqId, "Invalid request id");
FHE.checkSignatures(requestId, cleartexts, decryptionProof);
(uint32 value) = abi.decode(cleartexts, (uint32));
totalPlain = value;
pending = false;
return true;
}
}
编译:
pnpm hardhat clean && pnpm hardhat compile
scripts/deploy.ts:
import { ethers } from "hardhat";
async function main() {
const [deployer] = await ethers.getSigners();
console.log("Deployer:", deployer.address);
const Factory = await ethers.getContractFactory("PrivateCounter");
const c = await Factory.deploy();
await c.waitForDeployment();
console.log("✅ PrivateCounter deployed at:", await c.getAddress());
}
main().catch((e)=>{ console.error(e); process.exit(1); });
部署到 Sepolia:
pnpm hardhat run scripts/deploy.ts --network sepolia
记下输出的合约地址,后面前端要用。文中示例:
0x9F8069282814a1177C1f6b8D7d8f7cC11A663554
此 Demo 不强制验证。若你需要,在 Etherscan 开个 API Key 后,使用 @nomicfoundation/hardhat-verify 即可。
在 hello-fhevm/ 下创建 frontend/:
cd ../
pnpm create vite@latest frontend -- --template react-ts
cd frontend
pnpm add ethers
.env(部署时在 Vercel 里配置同名变量):
VITE_COUNTER_ADDRESS=0x你的合约地址
VITE_RPC_URL=https://ethereum-sepolia.publicnode.com
# (可选,若你的网络环境对某些域名有限制)
# VITE_RELAYER_URL=https://relayer.testnet.zama.cloud
# VITE_GATEWAY_URL=https://gateway.testnet.zama.cloud
mkdir -p src/types
src/types/zama-relayer-cdn.d.ts:
declare module "https://cdn.zama.ai/relayer-sdk-js/0.2.0/relayer-sdk-js.js" {
export function initSDK(): Promise<void>;
export function createInstance(cfg: any): Promise<any>;
export const SepoliaConfig: any;
}
确保 tsconfig.json 包含 src 与 src/types/**/*.d.ts(Vite 默认模板基本 OK):
{
"compilerOptions": {
"target": "ES2020",
"useDefineForClassFields": true,
"lib": ["ES2020", "DOM", "DOM.Iterable"],
"module": "ESNext",
"skipLibCheck": true,
"moduleResolution": "Bundler",
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
"jsx": "react-jsx",
"allowJs": true
},
"include": ["src", "src/types/**/*.d.ts"]
}
这版逻辑是:先连接钱包(任何 EIP-1193 钱包都可以) → 切到 Sepolia → initSDK() → createInstance({...SepoliaConfig, network: provider, chainId: 11155111})。
开发预览:
pnpm i
pnpm dev
# 打开提示的本地域名即可
New Project → 选择你的仓库 → Root Directory = frontend
Build Command:pnpm build
Output Directory:dist
Install Command:pnpm install
Environment Variables:
VITE_COUNTER_ADDRESS=0x你的合约地址
VITE_RPC_URL=https://ethereum-sepolia.publicnode.com
(可选)VITE_RELAYER_URL、VITE_GATEWAY_URL
点 Deploy,完成后打开域名。
打开你的 Vercel 域名(https)
浏览器钱包解锁
点击页面上的 “🔌 连接钱包并初始化” → 同意授权/切换到 Sepolia
点击 “➕ 加 1(加密提交)”,确认交易
点击 “🔓 解密总数”,状态会显示“等待回调(~30s)…”,随后明文更新
Invalid JSON-RPC response received: invalid project id → 你用了 Infura/Alchemy 但没带 Project ID。换公共节点或换成自己的完整 RPC URL。
KMS contract address is not valid or empty / Impossible to fetch public key: wrong relayer url. → 当前 Relayer/Gateway 与链/KMS 不匹配或不可达。 → 试试教程里前端的候选端点重试逻辑,或换浏览器/网络(关闭广告拦截、公司代理、无痕模式)。
wallet must has at least one account / ACTION_REJECTED → 钱包没给站点授权、或被其它钱包扩展“抢占”。 → 先只启用 MetaMask,删除 Connected sites 中的站点记录 → 刷新 → 重新连接。
Relayer didn't response correctly. REQUEST FAILED RESPONSE → 多半是浏览器/网络拦截了 *.zama.ai / *.zama.cloud。 → 换正常网络 + Chrome 正常窗口,或在 Vercel 上设置自定义 VITE_RELAYER_URL / VITE_GATEWAY_URL。
hello-fhevm/
├─ hardhat/
│ ├─ contracts/PrivateCounter.sol
│ ├─ scripts/deploy.ts
│ ├─ hardhat.config.ts
│ └─ .env
└─ frontend/
├─ src/App.tsx
├─ src/types/zama-relayer-cdn.d.ts
├─ .env
└─ ...
用最小可用的 Demo,带你从零实现:密文加法 + 明文解密展示。 技术栈:Solidity + Hardhat + React/Vite + Zama FHEVM SDK。 目标链:Sepolia 测试网。
一个可以接受加密输入(+1)的合约 PrivateCounter
一个前端 DApp:
先连接任意 EIP-1193 钱包(MetaMask/OKX/Bitget 等均可)
使用 Zama Relayer 注册加密输入,调用合约的 add()
通过合约发起解密 requestReveal(),等待回调后读取 totalPlain
一键部署到 Vercel 的网站链接
Node.js ≥ 18(推荐 20+)
pnpm(也可用 npm/yarn)
npm i -g pnpm
一个可用的 Sepolia RPC(公共节点即可)
推荐先测试延迟/可用性:
# 任选其一可用即可
for u in https://ethereum-sepolia.publicnode.com https://rpc.sepolia.org; do
printf "%-38s" "$u"
r=$(curl -s -m 6 -H "Content-Type: application/json" -d '{"jsonrpc":"2.0","method":"eth_blockNumber","params":[],"id":1}' "$u")
bn=$(echo "$r" | sed -n 's/.*"result":"\([^"]*\)".*/\1/p');
if [ -n "$bn" ]; then echo " OK block=$bn"; else echo " FAIL resp=$(echo "$r" | cut -c1-80)"; fi
done
钱包(浏览器扩展),建议 MetaMask
mkdir -p hello-fhevm && cd hello-fhevm
pnpm init -y
mkdir hardhat && cd hardhat
pnpm add -D hardhat typescript ts-node @types/node @nomicfoundation/hardhat-toolbox \
@fhevm/solidity @fhevm/hardhat-plugin dotenv
pnpm dlx hardhat # 选“Create a TypeScript project”
生成的 hardhat.config.ts 替换为(适配 FHEVM 插件并禁用它在非本地链的 remapping):
// hardhat/hardhat.config.ts
import { HardhatUserConfig } from "hardhat/config";
import "@nomicfoundation/hardhat-toolbox";
import * as dotenv from "dotenv";
dotenv.config();
const SEPOLIA_RPC_URL = process.env.SEPOLIA_RPC_URL || "https://ethereum-sepolia.publicnode.com";
const PRIVATE_KEY = (process.env.PRIVATE_KEY || "").replace(/^"|"$/g, "");
const config: HardhatUserConfig = {
solidity: {
version: "0.8.24",
settings: { viaIR: false, optimizer: { enabled: true, runs: 200 } },
},
networks: {
sepolia: {
url: SEPOLIA_RPC_URL,
accounts: PRIVATE_KEY ? [PRIVATE_KEY] : [],
},
},
};
export default config;
说明:FHEVM 的
@fhevm/hardhat-plugin只用于本地模拟开发,本教程直接用 Zama 提供的链上 KMS/Relayer,不需要在编译阶段做 remapping,所以上面的配置不启用插件扩展。
.env(放在 hardhat/ 目录下):
SEPOLIA_RPC_URL=https://ethereum-sepolia.publicnode.com
PRIVATE_KEY=0x你的私钥
// hardhat/contracts/PrivateCounter.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;
import "@fhevm/solidity/lib/FHE.sol";
import { SepoliaConfig } from "@fhevm/solidity/config/ZamaConfig.sol";
/**
* 私密计数器:
* - add(): 接收加密的 euint32,累计到密文 total
* - requestReveal(): 发起一次解密请求(走 Zama KMS)
* - onReveal(): KMS 回调,验签 & 更新明文 totalPlain
*/
contract PrivateCounter is SepoliaConfig {
euint32 private total;
uint32 public totalPlain;
bool private pending;
uint256 private lastReqId;
constructor() {
total = FHE.asEuint32(0);
FHE.allowThis(total);
}
function add(
externalEuint32 encryptedDelta,
bytes calldata inputProof
) external {
euint32 delta = FHE.fromExternal(encryptedDelta, inputProof);
total = FHE.add(total, delta);
FHE.allowThis(total);
}
function requestReveal() external {
require(!pending, "Decrypting in progress");
bytes32;
// 某些版本是 toBytes(total);新版本为 toBytes32(total)
handles[0] = FHE.toBytes32(total);
lastReqId = FHE.requestDecryption(handles, this.onReveal.selector);
pending = true;
}
function onReveal(
uint256 requestId,
bytes memory cleartexts,
bytes memory decryptionProof
) external returns (bool) {
require(requestId == lastReqId, "Invalid request id");
FHE.checkSignatures(requestId, cleartexts, decryptionProof);
(uint32 value) = abi.decode(cleartexts, (uint32));
totalPlain = value;
pending = false;
return true;
}
}
编译:
pnpm hardhat clean && pnpm hardhat compile
scripts/deploy.ts:
import { ethers } from "hardhat";
async function main() {
const [deployer] = await ethers.getSigners();
console.log("Deployer:", deployer.address);
const Factory = await ethers.getContractFactory("PrivateCounter");
const c = await Factory.deploy();
await c.waitForDeployment();
console.log("✅ PrivateCounter deployed at:", await c.getAddress());
}
main().catch((e)=>{ console.error(e); process.exit(1); });
部署到 Sepolia:
pnpm hardhat run scripts/deploy.ts --network sepolia
记下输出的合约地址,后面前端要用。文中示例:
0x9F8069282814a1177C1f6b8D7d8f7cC11A663554
此 Demo 不强制验证。若你需要,在 Etherscan 开个 API Key 后,使用 @nomicfoundation/hardhat-verify 即可。
在 hello-fhevm/ 下创建 frontend/:
cd ../
pnpm create vite@latest frontend -- --template react-ts
cd frontend
pnpm add ethers
.env(部署时在 Vercel 里配置同名变量):
VITE_COUNTER_ADDRESS=0x你的合约地址
VITE_RPC_URL=https://ethereum-sepolia.publicnode.com
# (可选,若你的网络环境对某些域名有限制)
# VITE_RELAYER_URL=https://relayer.testnet.zama.cloud
# VITE_GATEWAY_URL=https://gateway.testnet.zama.cloud
mkdir -p src/types
src/types/zama-relayer-cdn.d.ts:
declare module "https://cdn.zama.ai/relayer-sdk-js/0.2.0/relayer-sdk-js.js" {
export function initSDK(): Promise<void>;
export function createInstance(cfg: any): Promise<any>;
export const SepoliaConfig: any;
}
确保 tsconfig.json 包含 src 与 src/types/**/*.d.ts(Vite 默认模板基本 OK):
{
"compilerOptions": {
"target": "ES2020",
"useDefineForClassFields": true,
"lib": ["ES2020", "DOM", "DOM.Iterable"],
"module": "ESNext",
"skipLibCheck": true,
"moduleResolution": "Bundler",
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
"jsx": "react-jsx",
"allowJs": true
},
"include": ["src", "src/types/**/*.d.ts"]
}
这版逻辑是:先连接钱包(任何 EIP-1193 钱包都可以) → 切到 Sepolia → initSDK() → createInstance({...SepoliaConfig, network: provider, chainId: 11155111})。
开发预览:
pnpm i
pnpm dev
# 打开提示的本地域名即可
New Project → 选择你的仓库 → Root Directory = frontend
Build Command:pnpm build
Output Directory:dist
Install Command:pnpm install
Environment Variables:
VITE_COUNTER_ADDRESS=0x你的合约地址
VITE_RPC_URL=https://ethereum-sepolia.publicnode.com
(可选)VITE_RELAYER_URL、VITE_GATEWAY_URL
点 Deploy,完成后打开域名。
打开你的 Vercel 域名(https)
浏览器钱包解锁
点击页面上的 “🔌 连接钱包并初始化” → 同意授权/切换到 Sepolia
点击 “➕ 加 1(加密提交)”,确认交易
点击 “🔓 解密总数”,状态会显示“等待回调(~30s)…”,随后明文更新
Invalid JSON-RPC response received: invalid project id → 你用了 Infura/Alchemy 但没带 Project ID。换公共节点或换成自己的完整 RPC URL。
KMS contract address is not valid or empty / Impossible to fetch public key: wrong relayer url. → 当前 Relayer/Gateway 与链/KMS 不匹配或不可达。 → 试试教程里前端的候选端点重试逻辑,或换浏览器/网络(关闭广告拦截、公司代理、无痕模式)。
wallet must has at least one account / ACTION_REJECTED → 钱包没给站点授权、或被其它钱包扩展“抢占”。 → 先只启用 MetaMask,删除 Connected sites 中的站点记录 → 刷新 → 重新连接。
Relayer didn't response correctly. REQUEST FAILED RESPONSE → 多半是浏览器/网络拦截了 *.zama.ai / *.zama.cloud。 → 换正常网络 + Chrome 正常窗口,或在 Vercel 上设置自定义 VITE_RELAYER_URL / VITE_GATEWAY_URL。
hello-fhevm/
├─ hardhat/
│ ├─ contracts/PrivateCounter.sol
│ ├─ scripts/deploy.ts
│ ├─ hardhat.config.ts
│ └─ .env
└─ frontend/
├─ src/App.tsx
├─ src/types/zama-relayer-cdn.d.ts
├─ .env
└─ ...
import { useRef, useState } from "react";
import { ethers } from "ethers";
/** PrivateCounter ABI */
const ABI = [
"function add(bytes32 encryptedDelta, bytes inputProof)",
"function requestReveal()",
"function totalPlain() view returns (uint32)",
] as const;
const COUNTER_ADDR =
(import.meta.env.VITE_COUNTER_ADDRESS as `0x${string}`) ||
"0x9F8069282814a1177C1f6b8D7d8f7cC11A663554";
function pickProvider(): any {
const eth = (window as any).ethereum;
if (!eth) return null;
if (Array.isArray(eth?.providers)) {
const anyProv = eth.providers.find((p: any) => typeof p?.request === "function");
if (anyProv) return anyProv;
}
return eth;
}
const RPC_FALLBACK =
(import.meta.env.VITE_RPC_URL as string) || "https://ethereum-sepolia.publicnode.com";
const RELAYER_CANDIDATES = [
import.meta.env.VITE_RELAYER_URL as string,
"https://relayer.testnet.zama.cloud",
"https://relayer.fhevm.zama.ai",
"https://relayer.zama.ai",
"https://devnet.zama.ai/relayer",
].filter(Boolean);
const GATEWAY_CANDIDATES = [
import.meta.env.VITE_GATEWAY_URL as string,
"https://gateway.testnet.zama.cloud",
"https://gateway.fhevm.zama.ai",
].filter(Boolean);
export default function App() {
const [status, setStatus] = useState("尚未连接钱包");
const [addr, setAddr] = useState<string | null>(null);
const [plain, setPlain] = useState<number | null>(null);
const [chainId, setChainId] = useState<number | null>(null);
const [usingRelayer, setUsingRelayer] = useState<string | null>(null);
const [usingGateway, setUsingGateway] = useState<string | null>(null);
const providerRef = useRef<any>(null);
const fheRef = useRef<any>(null);
async function connectAndInit() {
try {
const eth = pickProvider();
if (!eth) throw new Error("未检测到钱包。请安装或启用一个 EIP-1193 钱包扩展(如 MetaMask)。");
providerRef.current = eth;
setStatus("请求账户授权…");
const accounts: string[] = await eth.request({ method: "eth_requestAccounts" });
if (!accounts?.length) throw new Error("未授权任何账户。");
setAddr(accounts[0]);
const hexId: string = await eth.request({ method: "eth_chainId" });
let id = parseInt(hexId, 16);
if (id !== 11155111) {
setStatus("切换到 Sepolia…");
try {
await eth.request({ method: "wallet_switchEthereumChain", params: [{ chainId: "0xaa36a7" }] });
id = 11155111;
} catch (e: any) {
if (e?.code === 4902) {
await eth.request({
method: "wallet_addEthereumChain",
params: [{
chainId: "0xaa36a7",
chainName: "Sepolia",
nativeCurrency: { name: "Sepolia ETH", symbol: "ETH", decimals: 18 },
rpcUrls: [RPC_FALLBACK],
blockExplorerUrls: ["https://sepolia.etherscan.io/"]
}]
});
id = 11155111;
} else {
throw e;
}
}
}
setChainId(id);
setStatus("加载 FHEVM SDK…");
const mod = await import(
/* @vite-ignore */ "https://cdn.zama.ai/relayer-sdk-js/0.2.0/relayer-sdk-js.js"
);
const { initSDK, createInstance, SepoliaConfig } = mod as any;
await initSDK();
let lastErr: any = null;
for (const r of RELAYER_CANDIDATES) {
for (const g of GATEWAY_CANDIDATES) {
try {
setStatus(`创建 FHE 实例…(尝试 ${r} / ${g})`);
const cfg = {
...SepoliaConfig,
relayerUrl: r,
gatewayUrl: g,
network: providerRef.current,
chainId: 11155111,
};
const inst = await createInstance(cfg);
if (typeof inst.init === "function") await inst.init();
fheRef.current = inst;
setUsingRelayer(r);
setUsingGateway(g);
setStatus("✅ 已连接 & SDK 就绪");
return;
} catch (e: any) {
lastErr = e;
}
}
}
throw new Error(`所有 relayer/gateway 组合均失败。最后错误:${lastErr?.message || lastErr}`);
} catch (e: any) {
console.error(e);
setStatus("❌ 连接/初始化失败: " + (e?.message || e));
}
}
async function getSignerAndContract() {
const eth = providerRef.current || pickProvider();
if (!eth) throw new Error("未检测到钱包。");
const browserProvider = new ethers.BrowserProvider(eth);
await browserProvider.send("eth_requestAccounts", []);
const signer = await browserProvider.getSigner();
const contract = new ethers.Contract(COUNTER_ADDR, ABI, signer);
return { signer, contract, addr: await signer.getAddress() };
}
async function handleAddOne() {
try {
if (!fheRef.current) throw new Error("SDK 未就绪,请先连接钱包。");
setStatus("注册加密输入…");
const { addr, contract } = await getSignerAndContract();
const buf = fheRef.current.createEncryptedInput(COUNTER_ADDR, addr);
buf.add32(1n);
const cipher = await buf.encrypt();
setStatus("发送交易 add(+1)…");
const tx = await contract.add(cipher.handles[0], cipher.inputProof);
await tx.wait();
setStatus("✅ 已提交 +1");
} catch (e: any) {
console.error(e);
setStatus("❌ 失败: " + (e?.message || e));
}
}
async function handleReveal() {
try {
if (!fheRef.current) throw new Error("SDK 未就绪,请先连接钱包。");
setStatus("请求解密…");
const { contract } = await getSignerAndContract();
const tx = await contract.requestReveal();
await tx.wait();
setStatus("等待回调(~30s)…");
await new Promise((r) => setTimeout(r, 30_000));
const v: bigint = await contract.totalPlain();
setPlain(Number(v));
setStatus("✅ 完成");
} catch (e: any) {
console.error(e);
setStatus("❌ 失败: " + (e?.message || e));
}
}
return (
<div style={{ maxWidth: 880, margin: "48px auto", fontFamily: "system-ui" }}>
<h1>Hello FHEVM: 私密计数器</h1>
<p>合约地址:<code>{COUNTER_ADDR}</code></p>
{(usingRelayer || usingGateway) && (
<p style={{ fontSize: 12, opacity: .7 }}>
relayer: <code>{usingRelayer}</code> · gateway: <code>{usingGateway}</code>
</p>
)}
<div style={{ display: "flex", gap: 12, marginTop: 16, flexWrap: "wrap" }}>
{!addr ? (
<button onClick={connectAndInit} style={{ background: "#e5f0ff" }}>
🔌 连接钱包并初始化
</button>
) : (
<>
<button onClick={handleAddOne}>➕ 加 1(加密提交)</button>
<button onClick={handleReveal}>🔓 解密总数</button>
<button onClick={connectAndInit} style={{ background: "#f3f4f6" }}>
♻️ 重新连接/重新初始化
</button>
</>
)}
</div>
<p style={{ marginTop: 16 }}>
状态:{status}
{addr && <> | 账户:<code>{addr}</code></>}
{chainId !== null && <> | ChainId:<code>{chainId}</code></>}
</p>
<p>明文总数:{plain === null ? "(未解密)" : plain}</p>
</div>
);
}
import { useRef, useState } from "react";
import { ethers } from "ethers";
/** PrivateCounter ABI */
const ABI = [
"function add(bytes32 encryptedDelta, bytes inputProof)",
"function requestReveal()",
"function totalPlain() view returns (uint32)",
] as const;
const COUNTER_ADDR =
(import.meta.env.VITE_COUNTER_ADDRESS as `0x${string}`) ||
"0x9F8069282814a1177C1f6b8D7d8f7cC11A663554";
function pickProvider(): any {
const eth = (window as any).ethereum;
if (!eth) return null;
if (Array.isArray(eth?.providers)) {
const anyProv = eth.providers.find((p: any) => typeof p?.request === "function");
if (anyProv) return anyProv;
}
return eth;
}
const RPC_FALLBACK =
(import.meta.env.VITE_RPC_URL as string) || "https://ethereum-sepolia.publicnode.com";
const RELAYER_CANDIDATES = [
import.meta.env.VITE_RELAYER_URL as string,
"https://relayer.testnet.zama.cloud",
"https://relayer.fhevm.zama.ai",
"https://relayer.zama.ai",
"https://devnet.zama.ai/relayer",
].filter(Boolean);
const GATEWAY_CANDIDATES = [
import.meta.env.VITE_GATEWAY_URL as string,
"https://gateway.testnet.zama.cloud",
"https://gateway.fhevm.zama.ai",
].filter(Boolean);
export default function App() {
const [status, setStatus] = useState("尚未连接钱包");
const [addr, setAddr] = useState<string | null>(null);
const [plain, setPlain] = useState<number | null>(null);
const [chainId, setChainId] = useState<number | null>(null);
const [usingRelayer, setUsingRelayer] = useState<string | null>(null);
const [usingGateway, setUsingGateway] = useState<string | null>(null);
const providerRef = useRef<any>(null);
const fheRef = useRef<any>(null);
async function connectAndInit() {
try {
const eth = pickProvider();
if (!eth) throw new Error("未检测到钱包。请安装或启用一个 EIP-1193 钱包扩展(如 MetaMask)。");
providerRef.current = eth;
setStatus("请求账户授权…");
const accounts: string[] = await eth.request({ method: "eth_requestAccounts" });
if (!accounts?.length) throw new Error("未授权任何账户。");
setAddr(accounts[0]);
const hexId: string = await eth.request({ method: "eth_chainId" });
let id = parseInt(hexId, 16);
if (id !== 11155111) {
setStatus("切换到 Sepolia…");
try {
await eth.request({ method: "wallet_switchEthereumChain", params: [{ chainId: "0xaa36a7" }] });
id = 11155111;
} catch (e: any) {
if (e?.code === 4902) {
await eth.request({
method: "wallet_addEthereumChain",
params: [{
chainId: "0xaa36a7",
chainName: "Sepolia",
nativeCurrency: { name: "Sepolia ETH", symbol: "ETH", decimals: 18 },
rpcUrls: [RPC_FALLBACK],
blockExplorerUrls: ["https://sepolia.etherscan.io/"]
}]
});
id = 11155111;
} else {
throw e;
}
}
}
setChainId(id);
setStatus("加载 FHEVM SDK…");
const mod = await import(
/* @vite-ignore */ "https://cdn.zama.ai/relayer-sdk-js/0.2.0/relayer-sdk-js.js"
);
const { initSDK, createInstance, SepoliaConfig } = mod as any;
await initSDK();
let lastErr: any = null;
for (const r of RELAYER_CANDIDATES) {
for (const g of GATEWAY_CANDIDATES) {
try {
setStatus(`创建 FHE 实例…(尝试 ${r} / ${g})`);
const cfg = {
...SepoliaConfig,
relayerUrl: r,
gatewayUrl: g,
network: providerRef.current,
chainId: 11155111,
};
const inst = await createInstance(cfg);
if (typeof inst.init === "function") await inst.init();
fheRef.current = inst;
setUsingRelayer(r);
setUsingGateway(g);
setStatus("✅ 已连接 & SDK 就绪");
return;
} catch (e: any) {
lastErr = e;
}
}
}
throw new Error(`所有 relayer/gateway 组合均失败。最后错误:${lastErr?.message || lastErr}`);
} catch (e: any) {
console.error(e);
setStatus("❌ 连接/初始化失败: " + (e?.message || e));
}
}
async function getSignerAndContract() {
const eth = providerRef.current || pickProvider();
if (!eth) throw new Error("未检测到钱包。");
const browserProvider = new ethers.BrowserProvider(eth);
await browserProvider.send("eth_requestAccounts", []);
const signer = await browserProvider.getSigner();
const contract = new ethers.Contract(COUNTER_ADDR, ABI, signer);
return { signer, contract, addr: await signer.getAddress() };
}
async function handleAddOne() {
try {
if (!fheRef.current) throw new Error("SDK 未就绪,请先连接钱包。");
setStatus("注册加密输入…");
const { addr, contract } = await getSignerAndContract();
const buf = fheRef.current.createEncryptedInput(COUNTER_ADDR, addr);
buf.add32(1n);
const cipher = await buf.encrypt();
setStatus("发送交易 add(+1)…");
const tx = await contract.add(cipher.handles[0], cipher.inputProof);
await tx.wait();
setStatus("✅ 已提交 +1");
} catch (e: any) {
console.error(e);
setStatus("❌ 失败: " + (e?.message || e));
}
}
async function handleReveal() {
try {
if (!fheRef.current) throw new Error("SDK 未就绪,请先连接钱包。");
setStatus("请求解密…");
const { contract } = await getSignerAndContract();
const tx = await contract.requestReveal();
await tx.wait();
setStatus("等待回调(~30s)…");
await new Promise((r) => setTimeout(r, 30_000));
const v: bigint = await contract.totalPlain();
setPlain(Number(v));
setStatus("✅ 完成");
} catch (e: any) {
console.error(e);
setStatus("❌ 失败: " + (e?.message || e));
}
}
return (
<div style={{ maxWidth: 880, margin: "48px auto", fontFamily: "system-ui" }}>
<h1>Hello FHEVM: 私密计数器</h1>
<p>合约地址:<code>{COUNTER_ADDR}</code></p>
{(usingRelayer || usingGateway) && (
<p style={{ fontSize: 12, opacity: .7 }}>
relayer: <code>{usingRelayer}</code> · gateway: <code>{usingGateway}</code>
</p>
)}
<div style={{ display: "flex", gap: 12, marginTop: 16, flexWrap: "wrap" }}>
{!addr ? (
<button onClick={connectAndInit} style={{ background: "#e5f0ff" }}>
🔌 连接钱包并初始化
</button>
) : (
<>
<button onClick={handleAddOne}>➕ 加 1(加密提交)</button>
<button onClick={handleReveal}>🔓 解密总数</button>
<button onClick={connectAndInit} style={{ background: "#f3f4f6" }}>
♻️ 重新连接/重新初始化
</button>
</>
)}
</div>
<p style={{ marginTop: 16 }}>
状态:{status}
{addr && <> | 账户:<code>{addr}</code></>}
{chainId !== null && <> | ChainId:<code>{chainId}</code></>}
</p>
<p>明文总数:{plain === null ? "(未解密)" : plain}</p>
</div>
);
}
Share Dialog
Share Dialog
No activity yet