mcpField
E
example-locker-dapp

example-locker-dapp

github.com/kali-decoder开发18122 次安装1 天前更新
代码仓库

关于 example-locker-dapp

用于构建 Midnight Network 锁仓 dApp 的开发技能,面向需要用 Compact 合约锁定 NIGHT 的开发者。模板实现到期前不可提取、到期后仅受益人可释放,并提供可运行的 Next.js 项目结构。

主要功能

  1. 01按指定时间锁定 NIGHT
  2. 02到期前禁止提取
  3. 03受益人验证后释放代币
  4. 04读取锁仓余额和状态
  5. 05提供 Next.js 可运行模板

适用场景

  1. 01实现固定期限存款锁仓
  2. 02按批次部署代币归属计划
  3. 03在约定期限内锁定流动性代币
  4. 04构建期限到达后可释放的托管

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/kali-decoder/midnight-skills --skill example-locker-dapp

安装后在 Claude Code 中输入/example-locker-dapp确认已加载。

详细介绍

Midnight Network 锁仓 DApp

锁仓 DApp 会将未屏蔽的 NIGHT 保存在 Compact 合约中,直到 unlockTime(Unix 秒,Uint<64>)。任何人(包括部署者)都无法提前提取。截止时间过后,只有受益人(通过 witness 证明)可以调用 release,将代币发送到收款地址。

**可运行模板:**复制 templates/locker-dapp/,获取完整的 Next.js 项目(合约 + UI)。安装 1AM 钱包后,运行 npm install && npm run compact && npm run dev。

此技能会生成:

  • contract/ — Compact 锁仓金库 + TypeScript witnesses + 编译脚本
  • app/locker/ — Next.js 客户端 UI(连接、部署、锁仓、释放、状态)
  • lib/midnight.ts — 钱包会话 + 修补后的索引器提供程序(复制自 references/midnight-session.md 或 templates/locker-dapp/lib/midnight.ts)
  • lib/locker.ts — 部署、lockTokens、release、账本解码
  • public/zk/locker/ — 从合约构建同步的 ZK 证明资产

共享参考资料(规范的提供程序 + 故障排查 — 请勿在提示词中重复):

  • references/midnight-session.md — createConnectedSession、索引器补丁、部署/调用辅助程序
  • references/gotchas.md — preprod 部署挂起、GraphQL offset: null、ZK 资产路径
  • references/versions.json — 固定的 @midnight-ntwrk/* 版本

主要参考资料:

  • example-payment-dapp/ — 提供程序配置、底层部署/调用、索引器补丁
  • 1am-wallet/ — 1AM 浏览器扩展、免尘流程
  • compact/ — blockTimeGte / blockTimeLt、disclose()、账本 ADT
  • token-transfers/ — 未屏蔽 NIGHT 单位(Stars)、UserAddress 收款方模式
  • indexer/ — 交易后轮询合约状态

关键架构说明:

  • 使用 blockTimeGte(unlockTime) — 绝不要在账本上比较原始区块时间戳(出于隐私和 API 设计考虑)
  • Unix 时间戳始终使用 Uint<64> — Uint<16> 无法容纳当前的纪元值
  • 使用 createUnprovenDeployTx + submitTxAsync — 不要使用 deployContract()(在 preprod 上会挂起)
  • 使用修补后的 queryContractState 包装 indexerPublicDataProvider(GraphQL offset: null 错误)
  • 代币金额以 Stars 为单位(1 NIGHT = 1_000_000 Stars);任何地方都使用 BigInt
  • 受益人的公钥字节在锁仓时存储到链上;beneficiaryKey witness 用于证明释放权限

工作流

帮助用户时,请按以下顺序进行:

  1. 合约 — 编译 locker.compact + witnesses
  2. 提供程序 — createConnectedSession(来自 references/midnight-session.md)
  3. 部署 — 底层部署,持久化私有状态 + 合约地址
  4. 锁仓 — lockTokens(amount, releaseTime, beneficiaryPkBytes)
  5. 轮询索引器 — 从账本读取 balance、unlockTime、lockActive
  6. 释放 — 在 blockTimeGte(unlockTime) 之后,由受益人调用 release(recipient)
  7. UI — 显示倒计时,并在解锁时间之前禁用释放(仅限 UX — 由合约强制执行)

1) 项目结构

locker-dapp/
├── package.json
├── next.config.mjs
├── lib/
│   ├── isomorphic-ws-fix.mjs
│   ├── midnight.ts                 # session, patched provider, hex helpers
│   └── locker.ts                   # deploy, lock, release, decode state
├── app/
│   ├── layout.tsx
│   └── locker/
│       └── LockerClient.tsx        # all wallet + contract UI (client component)
├── contract/
│   ├── package.json
│   ├── src/
│   │   ├── locker.compact
│   │   ├── witnesses.ts
│   │   ├── index.ts                # barrel → managed/locker
│   │   └── managed/locker/         # compiler output (gitignored)
│   └── tsconfig.json
└── public/zk/locker/               # synced keys + zkir (gitignored in dev, committed for deploy)

2) 先决条件

node --version   # 20+ for Next.js; 22+ if adding vitest contract tests
docker --version # optional local stack

# Compact compiler
curl --proto '=https' --tlsv1.2 -sSf \
  https://github.com/midnightntwrk/compact/releases/latest/download/compact-installer.sh | sh
source $HOME/.local/bin/env

浏览器:在 preprod 上使用 1AM 钱包扩展(或使用 Lace 作为备用方案)。


3) 根目录 package.json

{
  "name": "locker-dapp",
  "version": "0.1.0",
  "private": true,
  "type": "module",
  "scripts": {
    "dev": "next dev --webpack",
    "build": "npm run sync:assets && next build --webpack",
    "compact": "npm run compact --prefix contract",
    "sync:assets": "node scripts/sync-zk-assets.mjs",
    "postinstall": "npm install --prefix contract"
  },
  "dependencies": {
    "@midnight-ntwrk/compact-runtime": "0.16.0",
    "@midnight-ntwrk/ledger-v8": "8.0.3",
    "@midnight-ntwrk/midnight-js-contracts": "4.0.4",
    "@midnight-ntwrk/midnight-js-fetch-zk-config-provider": "4.0.4",
    "@midnight-ntwrk/midnight-js-indexer-public-data-provider": "4.0.4",
    "@midnight-ntwrk/midnight-js-network-id": "4.0.4",
    "@midnight-ntwrk/midnight-js-types": "4.0.4",
    "@midnight-ntwrk/wallet-sdk-address-format": "3.1.0",
    "next": "^15.0.0",
    "react": "^19.0.0",
    "react-dom": "^19.0.0"
  },
  "devDependencies": {
    "@types/node": "^22.0.0",
    "@types/react": "^19.0.0",
    "@types/react-dom": "^19.0.0",
    "typescript": "^5.7.0"
  }
}

4) contract/package.json

{
  "name": "@locker/contract",
  "version": "0.1.0",
  "private": true,
  "type": "module",
  "scripts": {
    "compact": "compact compile src/locker.compact src/managed/locker"
  },
  "dependencies": {
    "@midnight-ntwrk/compact-runtime": "0.16.0"
  }
}

编译:

npm run compact
# → contract/src/managed/locker/{contract,keys,zkir}/
npm run sync:assets
# → public/zk/locker/

5) contract/src/locker.compact

每个合约实例只能有一个活跃锁仓(简单金库)。部署多个合约地址以实现并行锁仓,或扩展为 Map<Bytes<32>, LockRecord>(参见 §18)。

pragma language_version >= 0.23;
import CompactStandardLibrary;

export ledger balance: Uint<128>;
export ledger unlockTime: Uint<64>;
export ledger beneficiary: Bytes<32>;
export ledger lockActive: Boolean;
export ledger totalLocked: Uint<128>;
export ledger totalReleased: Uint<128>;

witness beneficiaryKey(): Bytes<32>;

constructor() {
  balance = 0;
  unlockTime = 0;
  beneficiary = default<Bytes<32>>;
  lockActive = false;
  totalLocked = 0;
  totalReleased = 0;
}

// Deposit unshielded NIGHT and schedule release for beneficiaryPk at releaseTime (Unix seconds).
export circuit lockTokens(
  amount: Uint<128>,
  releaseTime: Uint<64>,
  beneficiaryPk: Bytes<32>
): [] {
  assert(!disclose(lockActive), "Vault already has an active lock");
  assert(disclose(amount) > 0, "Amount must be positive");
  receiveUnshielded(default<Bytes<32>>, disclose(amount));
  beneficiary = disclose(beneficiaryPk);
  unlockTime = disclose(releaseTime);
  balance = disclose(amount);
  lockActive = disclose(true);
  totalLocked = disclose((totalLocked + amount) as Uint<128>);
}

// Beneficiary releases locked tokens after unlockTime to recipient (unshielded UserAddress).
export circuit release(recipient: UserAddress): [] {
  assert(disclose(lockActive), "No active lock");
  assert(blockTimeGte(disclose(unlockTime)), "Lock period not finished");
  assert(deriveKey(beneficiaryKey()) == beneficiary, "Only beneficiary can release");
  const amount = disclose(balance);
  assert(amount > 0, "Nothing to release");
  sendUnshielded(
    default<Bytes<32>>,
    amount,
    right<ContractAddress, UserAddress>(disclose(recipient))
  );
  totalReleased = disclose((totalReleased + amount) as Uint<128>);
  balance = 0;
  lockActive = false;
}

pure circuit deriveKey(sk: Bytes<32>): Bytes<32> {
  return persistentHash<Vector<2, Bytes<32>>>([pad(32, "locker:beneficiary:v1"), sk]);
}

时间 API:blockTimeGte(t) 在 block_time >= t 时返回 true。使用 Uint<64> Unix 秒。不要在账本上存储原始 block_time。


6) contract/src/witnesses.ts

import type { WitnessContext } from '@midnight-ntwrk/compact-runtime';

export type LockerPrivateState = {
  beneficiarySecretKey: Uint8Array;
};

export const witnesses = {
  beneficiaryKey: (context: WitnessContext<LockerPrivateState>) =>
    [context.privateState, context.privateState.beneficiarySecretKey] as const,
};

受益人的 Zswap 秘密种子(32 字节)必须保存在私有状态中。如果为自己锁仓,请使用自己的屏蔽币秘密初始化私有状态。如果为他人锁仓,则对方必须自行部署锁仓合约,或使用多方流程 — 在释放时,witness 必须与链上的 beneficiary 字段匹配。


7) contract/src/index.ts

import { CompiledContract } from '@midnight-ntwrk/compact-runtime';
import { fileURLToPath } from 'node:url';
import path from 'node:path';
import { witnesses } from './witnesses.js';

export {
  Contract,
  ledger,
  pureCircuits,
  type Ledger,
} from './managed/locker/contract/index.js';
import { Contract } from './managed/locker/contract/index.js';

const currentDir = path.dirname(fileURLToPath(import.meta.url));
export const zkConfigPath = path.resolve(currentDir, 'managed', 'locker');

export const CompiledLockerContract = CompiledContract.make('locker', Contract).pipe(
  CompiledContract.withWitnesses(witnesses),
  CompiledContract.withCompiledFileAssets(zkConfigPath),
);

8) 提供方设置

从 example-payment-dapp/SKILL.md §“提供方设置”复制完整的提供方代码块:

  • ConnectedSession 类型
  • createConnectedSession(ZK 路径 → /zk/locker/)
  • coinPublicKeyToBytes
  • createPatchedPublicDataProvider
  • createPrivateStateProvider

只更改:

  • 将 ZK_ASSET_PATH / FetchZkConfigProvider 的 base 改为 /zk/locker
  • 在 api.getConfiguration() 之后调用 setNetworkId(config.networkId)

钱包检测(优先 1AM,Lace 作为后备):

const wallet =
  (window as any).midnight?.['1am'] ??
  Object.values((window as any).midnight ?? {})[0];

优先枚举 Object.values(window.midnight),不要硬编码 Lace 键 — 参见 react-wallet-connector/。


9) lib/locker.ts

import { createUnprovenDeployTx, submitCallTxAsync } from '@midnight-ntwrk/midnight-js-contracts';
import { ContractState, sampleSigningKey } from '@midnight-ntwrk/compact-runtime';
import { CompiledLockerContract, Contract, ledger } from '../contract/src/index.js';
import type { ConnectedSession } from './midnight.js';
import { fromHex, pollForState } from './midnight.js';

const PRIVATE_STATE_ID = 'LockerPrivateState';
const ZK_ASSET_PATH = '/zk/locker';

function makeCompiledContract() {
  return CompiledLockerContract as any;
}

export async function deployLocker(
  session: ConnectedSession,
  beneficiarySecretKey: Uint8Array,
): Promise<string> {
  const initialPrivateState = { beneficiarySecretKey };

  const deployTxData = await (createUnprovenDeployTx as any)(
    {
      zkConfigProvider: session.providers.zkConfigProvider,
      walletProvider: session.providers.walletProvider,
    },
    {
      compiledContract: makeCompiledContract(),
      args: [],
      privateStateId: PRIVATE_STATE_ID,
      initialPrivateState,
      signingKey: sampleSigningKey(),
    },
  );

  const contractAddress = deployTxData.public.contractAddress;
  await (submitTxAsync as any)(session.providers, {
    unprovenTx: deployTxData.private.unprovenTx,
  });

  await session.providers.privateStateProvider.setContractAddress(contractAddress);
  await session.providers.privateStateProvider.set(PRIVATE_STATE_ID, initialPrivateState);
  await session.providers.privateStateProvider.setSigningKey(
    contractAddress,
    deployTxData.private.signingKey,
  );

  return contractAddress;
}

export async function lockTokens(
  session: ConnectedSession,
  contractAddress: string,
  amount: bigint,
  releaseTimeUnix: bigint,
  beneficiaryPkBytes: Uint8Array,
) {
  await (submitCallTxAsync as any)(session.providers, {
    compiledContract: makeCompiledContract(),
    contractAddress,
    circuitId: 'lockTokens',
    args: [amount, releaseTimeUnix, { bytes: beneficiaryPkBytes }],
    privateStateId: PRIVATE_STATE_ID,
  });
}

export async function releaseTokens(
  session: ConnectedSession,
  contractAddress: string,
  recipientBytes: Uint8Array,
) {
  await (submitCallTxAsync as any)(session.providers, {
    compiledContract: makeCompiledContract(),
    contractAddress,
    circuitId: 'release',
    args: [{ bytes: recipientBytes }],
    privateStateId: PRIVATE_STATE_ID,
  });
}

export function decodeLockerState(stateHex: string) {
  const contractState = ContractState.deserialize(fromHex(stateHex));
  const l = ledger(contractState.data);
  return {
    balance: l.balance as unknown as bigint,
    unlockTime: l.unlockTime as unknown as bigint,
    beneficiary: l.beneficiary as unknown as Uint8Array,
    lockActive: l.lockActive as unknown as boolean,
    totalLocked: l.totalLocked as unknown as bigint,
    totalReleased: l.totalReleased as unknown as bigint,
  };
}

export async function fetchLockerState(queryUrl: string, contractAddress: string) {
  const hex = await pollForState(queryUrl, contractAddress);
  return decodeLockerState(hex);
}

10) 前端 — app/locker/LockerClient.tsx

客户端组件模式(与 payment dapp 相同)。核心 UI 状态:

状态UI
钱包未连接连接按钮
已连接,尚无合约部署储物柜
已部署,尚未上锁上锁表单(金额、解锁日期时间、受益人 = 自己)
有效锁定,尚未到解锁时间显示余额、解锁日期、倒计时,禁用释放按钮
有效锁定,已到解锁时间启用释放按钮 → 接收方 = 自己的未屏蔽地址字节
已释放显示总额,提供新锁定选项(部署新合约或扩展合约)
'use client';

import { useCallback, useEffect, useMemo, useState } from 'react';
import {
  deployLocker,
  fetchLockerState,
  lockTokens,
  releaseTokens,
} from '@/lib/locker';

const STARS_PER_NIGHT = 1_000_000n;

export default function LockerClient() {
  const [session, setSession] = useState<ConnectedSession | null>(null);
  const [contractAddress, setContractAddress] = useState<string | null>(null);
  const [ledger, setLedger] = useState<LockerLedgerView | null>(null);
  const [amountNight, setAmountNight] = useState('1');
  const [unlockAt, setUnlockAt] = useState(''); // datetime-local input
  const [busy, setBusy] = useState(false);
  const [error, setError] = useState<string | null>(null);

  const nowUnix = BigInt(Math.floor(Date.now() / 1000));
  const canRelease = ledger?.lockActive && ledger.unlockTime <= nowUnix;

  const refresh = useCallback(async () => {
    if (!session || !contractAddress) return;
    const state = await fetchLockerState(session.config.indexerUri, contractAddress);
    setLedger(state);
  }, [session, contractAddress]);

  useEffect(() => { void refresh(); }, [refresh]);

  async function onConnect() {
    setError(null);
    const wallet =
      (window as any).midnight?.['1am'] ??
      Object.values((window as any).midnight ?? {})[0];
    if (!wallet) throw new Error('Install a Midnight wallet extension');
    const api = await wallet.connect('preprod');
    const { createConnectedSession } = await import('@/lib/midnight');
    setSession(await createConnectedSession(api, '/zk/locker'));
  }

  async function onDeploy() {
    if (!session) return;
    setBusy(true);
    setError(null);
    try {
      const beneficiarySecretKey = session.coinPublicKeyBytes; // lock to self
      const addr = await deployLocker(session, beneficiarySecretKey);
      setContractAddress(addr);
      await refresh();
    } catch (e) {
      setError(String(e));
    } finally {
      setBusy(false);
    }
  }

  async function onLock() {
    if (!session || !contractAddress) return;
    setBusy(true);
    setError(null);
    try {
      const amount = BigInt(amountNight) * STARS_PER_NIGHT;
      const releaseTime = BigInt(Math.floor(new Date(unlockAt).getTime() / 1000));
      await lockTokens(
        session,
        contractAddress,
        amount,
        releaseTime,
        session.coinPublicKeyBytes,
      );
      await refresh();
    } catch (e) {
      setError(String(e));
    } finally {
      setBusy(false);
    }
  }

  async function onRelease() {
    if (!session || !contractAddress) return;
    setBusy(true);
    setError(null);
    try {
      const recipientBytes = unshieldedAddressToBytes(session.unshieldedAddress);
      await releaseTokens(session, contractAddress, recipientBytes);
      await refresh();
    } catch (e) {
      setError(String(e));
    } finally {
      setBusy(false);
    }
  }

  return (
    <div>
      <h1>Token Locker</h1>
      {!session ? (
        <button type="button" onClick={onConnect} disabled={busy}>Connect Wallet</button>
      ) : (
        <>
          <p>Connected: {session.unshieldedAddress}</p>
          {!contractAddress ? (
            <button type="button" onClick={onDeploy} disabled={busy}>Deploy Locker</button>
          ) : (
            <>
              <p>Contract: {contractAddress}</p>
              {ledger?.lockActive ? (
                <>
                  <p>Locked: {formatStars(ledger.balance)} Stars</p>
                  <p>Unlocks: {new Date(Number(ledger.unlockTime) * 1000).toLocaleString()}</p>
                  <button type="button" onClick={onRelease} disabled={busy || !canRelease}>
                    Release Tokens
                  </button>
                </>
              ) : (
                <>
                  <label>
                    Amount (NIGHT)
                    <input value={amountNight} onChange={(e) => setAmountNight(e.target.value)} />
                  </label>
                  <label>
                    Unlock at
                    <input type="datetime-local" value={unlockAt} onChange={(e) => setUnlockAt(e.target.value)} />
                  </label>
                  <button type="button" onClick={onLock} disabled={busy}>Lock Tokens</button>
                </>
              )}
            </>
          )}
        </>
      )}
      {error ? <p role="alert">{error}</p> : null}
    </div>
  );
}

function formatStars(n: bigint) {
  return n.toString();
}

// Implement: unshieldedAddressToBytes — decode mn_addr_* to 32-byte recipient for circuits
// Never use encodeUserAddress with raw coin pk; use bytes helper from 1am-wallet skill

按需添加样式(Tailwind/CSS)— 上述逻辑有意不添加样式,与 react-wallet-connector/ 一样。


11) ZK 资产同步 — scripts/sync-zk-assets.mjs

import { cpSync, mkdirSync, rmSync } from 'node:fs';
import { join, dirname } from 'node:path';
import { fileURLToPath } from 'node:url';

const root = join(dirname(fileURLToPath(import.meta.url)), '..');
const src = join(root, 'contract/src/managed/locker');
const dest = join(root, 'public/zk/locker');

rmSync(dest, { recursive: true, force: true });
mkdirSync(dest, { recursive: true });
for (const dir of ['keys', 'zkir']) {
  cpSync(join(src, dir), join(dest, dir), { recursive: true });
}

在排查 SDK 错误之前,请先验证: http://localhost:3000/zk/locker/keys/lockTokens.prover 必须返回 200。


12) Next.js 配置

与 example-payment-dapp/SKILL.md § Next.js 相同:

  • lib/isomorphic-ws-fix.mjs WebSocket shim
  • 配置 asyncWebAssembly、topLevelAwait 和 isomorphic-ws 别名的 next.config.mjs
  • 脚本必须使用 next dev --webpack 和 next build --webpack

13) 端到端流程

1. npm install && npm run compact && npm run sync:assets
2. npm run dev
3. Connect 1AM wallet (preprod, funded with tNIGHT)
4. Deploy locker contract
5. Lock 1 NIGHT until chosen datetime
6. UI shows locked balance + unlock time (poll indexer every 2s after tx)
7. Wait until unlock (or advance clock on local undeployed only)
8. Beneficiary clicks Release → tokens sent to unshielded address

14) 故障排查

症状原因解决方法
Lock period not finishedblockTimeGte 为 false等待链上时间 ≥ unlockTime;检查钱包网络
Only beneficiary can releaseWitness 密钥 ≠ 账本中的 beneficiary私有状态中的 beneficiarySecretKey 必须推导出与锁定时存储的 pk 相同的值
Vault already has an active lock合约仅支持单个锁定先释放,或部署另一个合约实例
时间戳出现 Uint<16> 编译错误整数位宽错误所有 Unix 时间都使用 Uint<64>
部署卡住 30–120 秒使用了 deployContract()使用 createUnprovenDeployTx + submitTxAsync
GraphQL offset: null使用默认索引器提供方使用修补后的 queryContractState
ZK 404资产尚未同步npm run sync:assets
Invalid character 'm' at position 0接收方地址编码错误向电路传入 { bytes: Uint8Array },而不是 encodeUserAddress(coinPk)
UI 中的释放按钮在时间到之前可用仅在 UI 中进行了检查合约仍会拒绝 — 修复 UX,但信任链上的 blockTimeGte

15) Agent 检查清单

为用户生成此 dApp 时:

  • 创建包含 lockTokens + release + blockTimeGte 的 locker.compact
  • 编译合约;将 ZK 资产同步到 public/zk/locker/
  • 使用修补后的索引器(来自 payment dapp)连接 createConnectedSession
  • 对锁定/释放使用低级部署 + submitCallTxAsync
  • 在私有状态中存储 beneficiarySecretKey;锁定时传入 coinPublicKeyBytes
  • 使用 ledger(contractState.data) 解码账本 — 不要直接使用原始 ContractState
  • 金额和 Unix 时间戳使用 BigInt
  • Next.js 使用 --webpack 标志支持 WASM
  • 说明:1 NIGHT = 1_000_000 Stars

16)用例映射

用例此模板的映射方式
定期存款用户锁定资产,直到 unlockTime,然后将其释放给自己
代币归属每个批次部署一个锁仓合约,或扩展为使用 Map 管理多个计划(§18)
流动性锁定锁定 LP 数量,直到 releaseTime;发布合约地址以供验证
托管将受益人设为交易对手的 pk;期限过后,在链下条件满足时释放
NFT 锁定此处未涵盖——对于屏蔽资产,请使用 nft/ 技能和自定义 receive/send 模式

17)读取锁定状态(索引器)

// After lockTokens tx — poll until balance > 0
const state = await fetchLockerState(indexerUrl, contractAddress);
console.log({
  locked: state.lockActive,
  amount: state.balance,
  unlockAt: new Date(Number(state.unlockTime) * 1000).toISOString(),
});

18)扩展

多重锁定(Map)

将单个 lockActive 单元格替换为:

struct LockRecord {
  amount: Uint<128>,
  unlockTime: Uint<64>,
  beneficiary: Bytes<32>,
  active: Boolean,
}

export ledger locks: Map<Bytes<32>, LockRecord>;
export ledger nextLockId: Counter;

为 release 添加 lockId 参数;每次调用 lockTokens 时递增计数器。

线性归属

部署多个 unlockTime 值各不相同的锁仓合约,或添加一个 vesting.compact,其中包含 List<VestTranche>,并使用 blockTimeGte(tranche.unlockTime) 电路单独实现 claimTranche(index)。

仅 React 钱包外壳

如果用户目前只需要连接 UI,请先使用 react-wallet-connector/ 搭建框架,然后从 references/midnight-session.md 添加提供程序接线。


20)故障排除

完整表格请参阅 references/gotchas.md。锁仓合约常见问题:

症状修复方法
unlockTime 溢出Unix 秒使用 Uint<64>,而不是 Uint<16>
截止时间前释放必须通过 blockTimeGte(unlockTime)——检查索引器区块时间
释放时受益人错误Witness beneficiaryKey 必须与锁定时存储的字节相匹配
在 preprod 上部署卡住使用 createUnprovenDeployTx + submitTxAsync,而不是 deployContract()

21)相关技能

后续步骤技能
仅连接钱包react-wallet-connector/
支付金库(无时间锁)example-payment-dapp/
CLI + 测试完整参考example-counter/
NFT 锁定nft/
隐私审计security/

评论

支持 Markdown