Loading...
Loading...
Compare original and translation side by side
docs.midnight.network/tutorials/counter/counter-cligithub.com/midnightntwrk/example-countergithub.com/webisoftSoftware/1AM-starter-templatedocs.midnight.network/tutorials/counter/counter-cligithub.com/midnightntwrk/example-countergithub.com/webisoftSoftware/1AM-starter-template| Package | Purpose |
|---|---|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| 包 | 用途 |
|---|---|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
setNetworkIdimport { setNetworkId, getNetworkId } from '@midnight-ntwrk/midnight-js-network-id';
// Call once at startup, in the config constructor or before building providers
setNetworkId('preprod'); // 'preprod' | 'preview' | 'mainnet' | 'undeployed'| Network | Indexer HTTP | Indexer WS | RPC |
|---|---|---|---|
| | | |
| | | |
| | | |
| | | |
setNetworkIdimport { setNetworkId, getNetworkId } from '@midnight-ntwrk/midnight-js-network-id';
// 在启动时调用一次,可在配置构造函数中或构建provider之前调用
setNetworkId('preprod'); // 'preprod' | 'preview' | 'mainnet' | 'undeployed'| 网络 | 索引器HTTP | 索引器WS | RPC |
|---|---|---|---|
| | | |
| | | |
| | | |
| | | |
import { CompiledContract } from '@midnight-ntwrk/compact-js';
import { Contract, witnesses } from './managed/counter'; // generated by compact compiler
// Node.js: load ZK assets from filesystem
import { NodeZkConfigProvider } from '@midnight-ntwrk/midnight-js-node-zk-config-provider';
const zkConfigPath = path.resolve('contract/src/managed/counter');
const compiledContract = CompiledContract.make('counter', Contract).pipe(
CompiledContract.withVacantWitnesses,
CompiledContract.withCompiledFileAssets(zkConfigPath),
);
// Browser / CDN: load ZK assets via fetch
import { FetchZkConfigProvider } from '@midnight-ntwrk/midnight-js-fetch-zk-config-provider';
const compiledContract = CompiledContract.make('YourContract', Contract).pipe(
CompiledContract.withVacantWitnesses,
CompiledContract.withCompiledFileAssets('/zk/your-contract'),
);import { CompiledContract } from '@midnight-ntwrk/compact-js';
import { Contract, witnesses } from './managed/counter'; // 由compact编译器生成
// Node.js:从文件系统加载ZK资产
import { NodeZkConfigProvider } from '@midnight-ntwrk/midnight-js-node-zk-config-provider';
const zkConfigPath = path.resolve('contract/src/managed/counter');
const compiledContract = CompiledContract.make('counter', Contract).pipe(
CompiledContract.withVacantWitnesses,
CompiledContract.withCompiledFileAssets(zkConfigPath),
);
// 浏览器 / CDN:通过fetch加载ZK资产
import { FetchZkConfigProvider } from '@midnight-ntwrk/midnight-js-fetch-zk-config-provider';
const compiledContract = CompiledContract.make('YourContract', Contract).pipe(
CompiledContract.withVacantWitnesses,
CompiledContract.withCompiledFileAssets('/zk/your-contract'),
);import type { MidnightProviders } from '@midnight-ntwrk/midnight-js-types';
import type { DeployedContract, FoundContract } from '@midnight-ntwrk/midnight-js-contracts';
import type { ImpureCircuitId } from '@midnight-ntwrk/compact-js';
import { Counter, type CounterPrivateState } from './managed/counter';
// Extract circuit IDs from the contract type
export type CounterCircuits = ImpureCircuitId<Counter.Contract<CounterPrivateState>>;
// Private state key (string literal type)
export const CounterPrivateStateId = 'counterPrivateState' as const;
export type CounterPrivateStateId = typeof CounterPrivateStateId;
// Full providers type for this contract
export type CounterProviders = MidnightProviders<
CounterCircuits,
CounterPrivateStateId,
CounterPrivateState
>;
// Contract instance types
export type CounterContract = Counter.Contract<CounterPrivateState>;
export type DeployedCounterContract = DeployedContract<CounterContract> | FoundContract<CounterContract>;import type { MidnightProviders } from '@midnight-ntwrk/midnight-js-types';
import type { DeployedContract, FoundContract } from '@midnight-ntwrk/midnight-js-contracts';
import type { ImpureCircuitId } from '@midnight-ntwrk/compact-js';
import { Counter, type CounterPrivateState } from './managed/counter';
// 从合约类型中提取电路ID
export type CounterCircuits = ImpureCircuitId<Counter.Contract<CounterPrivateState>>;
// 私有状态键(字符串字面量类型)
export const CounterPrivateStateId = 'counterPrivateState' as const;
export type CounterPrivateStateId = typeof CounterPrivateStateId;
// 此合约的完整provider类型
export type CounterProviders = MidnightProviders<
CounterCircuits,
CounterPrivateStateId,
CounterPrivateState
>;
// 合约实例类型
export type CounterContract = Counter.Contract<CounterPrivateState>;
export type DeployedCounterContract = DeployedContract<CounterContract> | FoundContract<CounterContract>;import { HDWallet, generateRandomSeed, Roles } from '@midnight-ntwrk/wallet-sdk-hd';
import * as ledger from '@midnight-ntwrk/ledger-v8';
import { Buffer } from 'buffer';
// Generate a fresh random seed (returns Uint8Array)
const seed = generateRandomSeed();
const seedHex = Buffer.from(seed).toString('hex'); // save this
// Or restore from existing hex seed
const seedHex = '...'; // from user input
const hdWallet = HDWallet.fromSeed(Buffer.from(seedHex, 'hex'));
if (hdWallet.type !== 'seedOk') throw new Error('Invalid seed');
const derivationResult = hdWallet.hdWallet
.selectAccount(0)
.selectRoles([Roles.Zswap, Roles.NightExternal, Roles.Dust])
.deriveKeysAt(0);
if (derivationResult.type !== 'keysDerived') throw new Error('Key derivation failed');
hdWallet.hdWallet.clear(); // wipe secret material from memory
const keys = derivationResult.keys;
const shieldedSecretKeys = ledger.ZswapSecretKeys.fromSeed(keys[Roles.Zswap]);
const dustSecretKey = ledger.DustSecretKey.fromSeed(keys[Roles.Dust]);import { HDWallet, generateRandomSeed, Roles } from '@midnight-ntwrk/wallet-sdk-hd';
import * as ledger from '@midnight-ntwrk/ledger-v8';
import { Buffer } from 'buffer';
// 生成新的随机种子(返回Uint8Array)
const seed = generateRandomSeed();
const seedHex = Buffer.from(seed).toString('hex'); // 保存此值
// 或从现有十六进制种子恢复
const seedHex = '...'; // 来自用户输入
const hdWallet = HDWallet.fromSeed(Buffer.from(seedHex, 'hex'));
if (hdWallet.type !== 'seedOk') throw new Error('无效种子');
const derivationResult = hdWallet.hdWallet
.selectAccount(0)
.selectRoles([Roles.Zswap, Roles.NightExternal, Roles.Dust])
.deriveKeysAt(0);
if (derivationResult.type !== 'keysDerived') throw new Error('密钥派生失败');
hdWallet.hdWallet.clear(); // 从内存中清除机密材料
const keys = derivationResult.keys;
const shieldedSecretKeys = ledger.ZswapSecretKeys.fromSeed(keys[Roles.Zswap]);
const dustSecretKey = ledger.DustSecretKey.fromSeed(keys[Roles.Dust]);import { ShieldedWallet } from '@midnight-ntwrk/wallet-sdk-shielded';
import { UnshieldedWallet, createKeystore, PublicKey, InMemoryTransactionHistoryStorage } from '@midnight-ntwrk/wallet-sdk-unshielded-wallet';
import { DustWallet } from '@midnight-ntwrk/wallet-sdk-dust-wallet';
import { WalletFacade } from '@midnight-ntwrk/wallet-sdk-facade';
const unshieldedKeystore = createKeystore(keys[Roles.NightExternal], getNetworkId());
const shieldedWallet = ShieldedWallet({
networkId: getNetworkId(),
indexerClientConnection: { indexerHttpUrl: indexer, indexerWsUrl: indexerWS },
provingServerUrl: new URL(proofServer),
relayURL: new URL(node.replace(/^http/, 'ws')), // convert http → ws
}).startWithSecretKeys(shieldedSecretKeys);
const unshieldedWallet = UnshieldedWallet({
networkId: getNetworkId(),
indexerClientConnection: { indexerHttpUrl: indexer, indexerWsUrl: indexerWS },
txHistoryStorage: new InMemoryTransactionHistoryStorage(),
}).startWithPublicKey(PublicKey.fromKeyStore(unshieldedKeystore));
const dustWallet = DustWallet({
networkId: getNetworkId(),
costParameters: {
additionalFeeOverhead: 300_000_000_000_000n,
feeBlocksMargin: 5,
},
indexerClientConnection: { indexerHttpUrl: indexer, indexerWsUrl: indexerWS },
provingServerUrl: new URL(proofServer),
relayURL: new URL(node.replace(/^http/, 'ws')),
}).startWithSecretKey(dustSecretKey, ledger.LedgerParameters.initialParameters().dust);
const wallet = new WalletFacade(shieldedWallet, unshieldedWallet, dustWallet);
await wallet.start(shieldedSecretKeys, dustSecretKey);import { ShieldedWallet } from '@midnight-ntwrk/wallet-sdk-shielded';
import { UnshieldedWallet, createKeystore, PublicKey, InMemoryTransactionHistoryStorage } from '@midnight-ntwrk/wallet-sdk-unshielded-wallet';
import { DustWallet } from '@midnight-ntwrk/wallet-sdk-dust-wallet';
import { WalletFacade } from '@midnight-ntwrk/wallet-sdk-facade';
const unshieldedKeystore = createKeystore(keys[Roles.NightExternal], getNetworkId());
const shieldedWallet = ShieldedWallet({
networkId: getNetworkId(),
indexerClientConnection: { indexerHttpUrl: indexer, indexerWsUrl: indexerWS },
provingServerUrl: new URL(proofServer),
relayURL: new URL(node.replace(/^http/, 'ws')), // 将http转换为ws
}).startWithSecretKeys(shieldedSecretKeys);
const unshieldedWallet = UnshieldedWallet({
networkId: getNetworkId(),
indexerClientConnection: { indexerHttpUrl: indexer, indexerWsUrl: indexerWS },
txHistoryStorage: new InMemoryTransactionHistoryStorage(),
}).startWithPublicKey(PublicKey.fromKeyStore(unshieldedKeystore));
const dustWallet = DustWallet({
networkId: getNetworkId(),
costParameters: {
additionalFeeOverhead: 300_000_000_000_000n,
feeBlocksMargin: 5,
},
indexerClientConnection: { indexerHttpUrl: indexer, indexerWsUrl: indexerWS },
provingServerUrl: new URL(proofServer),
relayURL: new URL(node.replace(/^http/, 'ws')),
}).startWithSecretKey(dustSecretKey, ledger.LedgerParameters.initialParameters().dust);
const wallet = new WalletFacade(shieldedWallet, unshieldedWallet, dustWallet);
await wallet.start(shieldedSecretKeys, dustSecretKey);import * as Rx from 'rxjs';
// Wait until wallet is fully synced
const syncedState = await Rx.firstValueFrom(
wallet.state().pipe(
Rx.throttleTime(5_000),
Rx.filter((state) => state.isSynced),
),
);
// Wait until wallet has non-zero unshielded balance
import { unshieldedToken } from '@midnight-ntwrk/ledger-v8';
const balance = await Rx.firstValueFrom(
wallet.state().pipe(
Rx.throttleTime(10_000),
Rx.filter((s) => s.isSynced),
Rx.map((s) => s.unshielded.balances[unshieldedToken().raw] ?? 0n),
Rx.filter((balance) => balance > 0n),
),
);import { WebSocket } from 'ws';
// Required for GraphQL subscriptions (wallet sync) to work in Node.js
globalThis.WebSocket = WebSocket as unknown as typeof globalThis.WebSocket;
// Put this at the very top of your entry file, before any wallet importsimport * as Rx from 'rxjs';
// 等待钱包完全同步
const syncedState = await Rx.firstValueFrom(
wallet.state().pipe(
Rx.throttleTime(5_000),
Rx.filter((state) => state.isSynced),
),
);
// 等待钱包有非零的未屏蔽余额
import { unshieldedToken } from '@midnight-ntwrk/ledger-v8';
const balance = await Rx.firstValueFrom(
wallet.state().pipe(
Rx.throttleTime(10_000),
Rx.filter((s) => s.isSynced),
Rx.map((s) => s.unshielded.balances[unshieldedToken().raw] ?? 0n),
Rx.filter((balance) => balance > 0n),
),
);import { WebSocket } from 'ws';
// 为了让GraphQL订阅(钱包同步)在Node.js中正常工作,这是必需的
globalThis.WebSocket = WebSocket as unknown as typeof globalThis.WebSocket;
// 将此代码放在入口文件的最顶部,在任何钱包导入之前// 1. Wait for sync
const state = await Rx.firstValueFrom(wallet.state().pipe(Rx.filter((s) => s.isSynced)));
// 2. Check if DUST already available
if (state.dust.availableCoins.length > 0) {
console.log('DUST already available:', state.dust.walletBalance(new Date()));
return;
}
// 3. Register unregistered NIGHT UTXOs
const nightUtxos = state.unshielded.availableCoins.filter(
(coin: any) => coin.meta?.registeredForDustGeneration !== true,
);
if (nightUtxos.length > 0) {
const recipe = await wallet.registerNightUtxosForDustGeneration(
nightUtxos,
unshieldedKeystore.getPublicKey(),
(payload) => unshieldedKeystore.signData(payload),
);
const finalized = await wallet.finalizeRecipe(recipe);
await wallet.submitTransaction(finalized);
}
// 4. Wait for DUST balance > 0 (may take a few minutes)
await Rx.firstValueFrom(
wallet.state().pipe(
Rx.throttleTime(5_000),
Rx.filter((s) => s.isSynced),
Rx.filter((s) => s.dust.walletBalance(new Date()) > 0n),
),
);pendingCoins > 0 && availableCoins === 0// 1. 等待同步完成
const state = await Rx.firstValueFrom(wallet.state().pipe(Rx.filter((s) => s.isSynced)));
// 2. 检查是否已有可用DUST
if (state.dust.availableCoins.length > 0) {
console.log('已有可用DUST:', state.dust.walletBalance(new Date()));
return;
}
// 3. 注册未注册的NIGHT UTXO
const nightUtxos = state.unshielded.availableCoins.filter(
(coin: any) => coin.meta?.registeredForDustGeneration !== true,
);
if (nightUtxos.length > 0) {
const recipe = await wallet.registerNightUtxosForDustGeneration(
nightUtxos,
unshieldedKeystore.getPublicKey(),
(payload) => unshieldedKeystore.signData(payload),
);
const finalized = await wallet.finalizeRecipe(recipe);
await wallet.submitTransaction(finalized);
}
// 4. 等待DUST余额大于0(可能需要几分钟)
await Rx.firstValueFrom(
wallet.state().pipe(
Rx.throttleTime(5_000),
Rx.filter((s) => s.isSynced),
Rx.filter((s) => s.dust.walletBalance(new Date()) > 0n),
),
);pendingCoins > 0 && availableCoins === 0import { indexerPublicDataProvider } from '@midnight-ntwrk/midnight-js-indexer-public-data-provider';
import { httpClientProofProvider } from '@midnight-ntwrk/midnight-js-http-client-proof-provider';
import { NodeZkConfigProvider } from '@midnight-ntwrk/midnight-js-node-zk-config-provider';
import { levelPrivateStateProvider } from '@midnight-ntwrk/midnight-js-level-private-state-provider';
const zkConfigProvider = new NodeZkConfigProvider<CounterCircuits>(zkConfigPath);
const providers: CounterProviders = {
privateStateProvider: levelPrivateStateProvider<CounterPrivateStateId>({
privateStateStoreName: 'counter-private-state', // LevelDB store name
walletProvider: walletAndMidnightProvider,
}),
publicDataProvider: indexerPublicDataProvider(indexerHttp, indexerWs),
zkConfigProvider,
proofProvider: httpClientProofProvider(proofServerUrl, zkConfigProvider),
walletProvider: walletAndMidnightProvider,
midnightProvider: walletAndMidnightProvider,
};import { indexerPublicDataProvider } from '@midnight-ntwrk/midnight-js-indexer-public-data-provider';
import { httpClientProofProvider } from '@midnight-ntwrk/midnight-js-http-client-proof-provider';
import { NodeZkConfigProvider } from '@midnight-ntwrk/midnight-js-node-zk-config-provider';
import { levelPrivateStateProvider } from '@midnight-ntwrk/midnight-js-level-private-state-provider';
const zkConfigProvider = new NodeZkConfigProvider<CounterCircuits>(zkConfigPath);
const providers: CounterProviders = {
privateStateProvider: levelPrivateStateProvider<CounterPrivateStateId>({
privateStateStoreName: 'counter-private-state', // LevelDB存储名称
walletProvider: walletAndMidnightProvider,
}),
publicDataProvider: indexerPublicDataProvider(indexerHttp, indexerWs),
zkConfigProvider,
proofProvider: httpClientProofProvider(proofServerUrl, zkConfigProvider),
walletProvider: walletAndMidnightProvider,
midnightProvider: walletAndMidnightProvider,
};import { FetchZkConfigProvider } from '@midnight-ntwrk/midnight-js-fetch-zk-config-provider';
import { indexerPublicDataProvider } from '@midnight-ntwrk/midnight-js-indexer-public-data-provider';
import { createProofProvider } from '@midnight-ntwrk/midnight-js-types';
const config = await connectedAPI.getConfiguration();
setNetworkId(config.networkId);
const zkConfigProvider = new FetchZkConfigProvider(
new URL(zkAssetBasePath, window.location.origin).toString(),
window.fetch.bind(window),
);
const provingProvider = await connectedAPI.getProvingProvider(zkConfigProvider);
const providers = {
publicDataProvider: indexerPublicDataProvider(config.indexerUri, config.indexerWsUri),
zkConfigProvider,
proofProvider: createProofProvider(provingProvider), // wraps 1AM proving provider
walletProvider: { /* see 1AM skill */ },
midnightProvider: { /* see 1AM skill */ },
privateStateProvider: createPrivateStateProvider(), // in-memory for browser
};import { FetchZkConfigProvider } from '@midnight-ntwrk/midnight-js-fetch-zk-config-provider';
import { indexerPublicDataProvider } from '@midnight-ntwrk/midnight-js-indexer-public-data-provider';
import { createProofProvider } from '@midnight-ntwrk/midnight-js-types';
const config = await connectedAPI.getConfiguration();
setNetworkId(config.networkId);
const zkConfigProvider = new FetchZkConfigProvider(
new URL(zkAssetBasePath, window.location.origin).toString(),
window.fetch.bind(window),
);
const provingProvider = await connectedAPI.getProvingProvider(zkConfigProvider);
const providers = {
publicDataProvider: indexerPublicDataProvider(config.indexerUri, config.indexerWsUri),
zkConfigProvider,
proofProvider: createProofProvider(provingProvider), // 包装1AM证明provider
walletProvider: { /* 请查看1AM技能 */ },
midnightProvider: { /* 请查看1AM技能 */ },
privateStateProvider: createPrivateStateProvider(), // 浏览器中使用内存存储
};import type { WalletProvider, MidnightProvider } from '@midnight-ntwrk/midnight-js-types';
import * as ledger from '@midnight-ntwrk/ledger-v8';
const state = await Rx.firstValueFrom(wallet.state().pipe(Rx.filter((s) => s.isSynced)));
const walletAndMidnightProvider: WalletProvider & MidnightProvider = {
getCoinPublicKey() {
return state.shielded.coinPublicKey.toHexString();
},
getEncryptionPublicKey() {
return state.shielded.encryptionPublicKey.toHexString();
},
async balanceTx(tx, ttl?) {
const recipe = await wallet.balanceUnboundTransaction(
tx,
{ shieldedSecretKeys, dustSecretKey },
{ ttl: ttl ?? new Date(Date.now() + 30 * 60 * 1000) },
);
// ⚠️ KNOWN BUG WORKAROUND: wallet SDK signRecipe hardcodes 'pre-proof' but
// proven (UnboundTransaction) intents contain 'proof' data → "Failed to clone intent"
// Sign intents manually with the correct proof marker (see signTransactionIntents below)
signTransactionIntents(recipe.baseTransaction, signFn, 'proof');
if (recipe.balancingTransaction) {
signTransactionIntents(recipe.balancingTransaction, signFn, 'pre-proof');
}
return wallet.finalizeRecipe(recipe);
},
submitTx(tx) {
return wallet.submitTransaction(tx) as any;
},
};import type { WalletProvider, MidnightProvider } from '@midnight-ntwrk/midnight-js-types';
import * as ledger from '@midnight-ntwrk/ledger-v8';
const state = await Rx.firstValueFrom(wallet.state().pipe(Rx.filter((s) => s.isSynced)));
const walletAndMidnightProvider: WalletProvider & MidnightProvider = {
getCoinPublicKey() {
return state.shielded.coinPublicKey.toHexString();
},
getEncryptionPublicKey() {
return state.shielded.encryptionPublicKey.toHexString();
},
async balanceTx(tx, ttl?) {
const recipe = await wallet.balanceUnboundTransaction(
tx,
{ shieldedSecretKeys, dustSecretKey },
{ ttl: ttl ?? new Date(Date.now() + 30 * 60 * 1000) },
);
// ⚠️ 已知BUG解决方法:wallet SDK的signRecipe硬编码了'pre-proof',但
// 已验证的(UnboundTransaction)意图包含'proof'数据 → "Failed to clone intent"
// 使用正确的proof标记手动签署意图(请参见下面的signTransactionIntents)
signTransactionIntents(recipe.baseTransaction, signFn, 'proof');
if (recipe.balancingTransaction) {
signTransactionIntents(recipe.balancingTransaction, signFn, 'pre-proof');
}
return wallet.finalizeRecipe(recipe);
},
submitTx(tx) {
return wallet.submitTransaction(tx) as any;
},
};/**
* Workaround for wallet SDK bug where signRecipe hardcodes 'pre-proof',
* causing failures when signing proven (UnboundTransaction) intents.
* Call with proofMarker='proof' for baseTransaction, 'pre-proof' for balancingTransaction.
*/
const signTransactionIntents = (
tx: { intents?: Map<number, any> },
signFn: (payload: Uint8Array) => ledger.Signature,
proofMarker: 'proof' | 'pre-proof',
): void => {
if (!tx.intents || tx.intents.size === 0) return;
for (const segment of tx.intents.keys()) {
const intent = tx.intents.get(segment);
if (!intent) continue;
const cloned = ledger.Intent.deserialize<
ledger.SignatureEnabled, ledger.Proofish, ledger.PreBinding
>('signature', proofMarker, 'pre-binding', intent.serialize());
const signature = signFn(cloned.signatureData(segment));
if (cloned.fallibleUnshieldedOffer) {
const sigs = cloned.fallibleUnshieldedOffer.inputs.map(
(_: ledger.UtxoSpend, i: number) =>
cloned.fallibleUnshieldedOffer!.signatures.at(i) ?? signature,
);
cloned.fallibleUnshieldedOffer = cloned.fallibleUnshieldedOffer.addSignatures(sigs);
}
if (cloned.guaranteedUnshieldedOffer) {
const sigs = cloned.guaranteedUnshieldedOffer.inputs.map(
(_: ledger.UtxoSpend, i: number) =>
cloned.guaranteedUnshieldedOffer!.signatures.at(i) ?? signature,
);
cloned.guaranteedUnshieldedOffer = cloned.guaranteedUnshieldedOffer.addSignatures(sigs);
}
tx.intents.set(segment, cloned);
}
};/**
* wallet SDK错误的解决方法:signRecipe硬编码了'pre-proof',
* 导致签署已验证的(UnboundTransaction)意图时失败。
* 对baseTransaction使用proofMarker='proof',对balancingTransaction使用'pre-proof'。
*/
const signTransactionIntents = (
tx: { intents?: Map<number, any> },
signFn: (payload: Uint8Array) => ledger.Signature,
proofMarker: 'proof' | 'pre-proof',
): void => {
if (!tx.intents || tx.intents.size === 0) return;
for (const segment of tx.intents.keys()) {
const intent = tx.intents.get(segment);
if (!intent) continue;
const cloned = ledger.Intent.deserialize<
ledger.SignatureEnabled, ledger.Proofish, ledger.PreBinding
>('signature', proofMarker, 'pre-binding', intent.serialize());
const signature = signFn(cloned.signatureData(segment));
if (cloned.fallibleUnshieldedOffer) {
const sigs = cloned.fallibleUnshieldedOffer.inputs.map(
(_: ledger.UtxoSpend, i: number) =>
cloned.fallibleUnshieldedOffer!.signatures.at(i) ?? signature,
);
cloned.fallibleUnshieldedOffer = cloned.fallibleUnshieldedOffer.addSignatures(sigs);
}
if (cloned.guaranteedUnshieldedOffer) {
const sigs = cloned.guaranteedUnshieldedOffer.inputs.map(
(_: ledger.UtxoSpend, i: number) =>
cloned.guaranteedUnshieldedOffer!.signatures.at(i) ?? signature,
);
cloned.guaranteedUnshieldedOffer = cloned.guaranteedUnshieldedOffer.addSignatures(sigs);
}
tx.intents.set(segment, cloned);
}
};import { deployContract } from '@midnight-ntwrk/midnight-js-contracts';
const deployed = await deployContract(providers, {
compiledContract,
privateStateId: CounterPrivateStateId,
initialPrivateState: { privateCounter: 0 },
});
console.log('Contract address:', deployed.deployTxData.public.contractAddress);import { deployContract } from '@midnight-ntwrk/midnight-js-contracts';
const deployed = await deployContract(providers, {
compiledContract,
privateStateId: CounterPrivateStateId,
initialPrivateState: { privateCounter: 0 },
});
console.log('合约地址:', deployed.deployTxData.public.contractAddress);// Using callTx (recommended — automatically builds, proves, balances, submits)
const result = await deployed.callTx.increment();
console.log('txId:', result.public.txId);
console.log('blockHeight:', result.public.blockHeight);// 使用callTx(推荐 —— 自动构建、验证、平衡、提交)
const result = await deployed.callTx.increment();
console.log('txId:', result.public.txId);
console.log('区块高度:', result.public.blockHeight);import { findDeployedContract } from '@midnight-ntwrk/midnight-js-contracts';
const contract = await findDeployedContract(providers, {
contractAddress: '09dbe05f...', // hex string
compiledContract,
privateStateId: CounterPrivateStateId,
initialPrivateState: { privateCounter: 0 },
});
// Now call circuits on it
await contract.callTx.increment();import { findDeployedContract } from '@midnight-ntwrk/midnight-js-contracts';
const contract = await findDeployedContract(providers, {
contractAddress: '09dbe05f...', // 十六进制字符串
compiledContract,
privateStateId: CounterPrivateStateId,
initialPrivateState: { privateCounter: 0 },
});
// 现在可以在其上调用电路
await contract.callTx.increment();import { ContractState } from '@midnight-ntwrk/compact-runtime';
import { Counter } from './managed/counter';
const contractState = await providers.publicDataProvider.queryContractState(contractAddress);
if (contractState === null) {
console.log('Contract not found');
} else {
// Apply generated ledger() function to deserialize typed state
const ledgerState = Counter.ledger(contractState.data);
console.log('Counter value:', ledgerState.round);
}import { ContractState } from '@midnight-ntwrk/compact-runtime';
import { Counter } from './managed/counter';
const contractState = await providers.publicDataProvider.queryContractState(contractAddress);
if (contractState === null) {
console.log('未找到合约');
} else {
// 应用生成的ledger()函数来反序列化类型化状态
const ledgerState = Counter.ledger(contractState.data);
console.log('计数器值:', ledgerState.round);
}import type {
PrivateStateProvider, PrivateStateId, PrivateStateExport,
SigningKeyExport,
} from '@midnight-ntwrk/midnight-js-types';
function createPrivateStateProvider() {
let scope = '';
const stateStore = new Map<string, unknown>();
const signingKeyStore = new Map<string, unknown>();
const key = (id: string) => `${scope}:${id}`;
return {
setContractAddress(address: string) { scope = address; },
async set(id: string, state: unknown) { stateStore.set(key(id), state); },
async get(id: string) { return stateStore.get(key(id)) ?? null; },
async remove(id: string) { stateStore.delete(key(id)); },
async clear() { stateStore.clear(); },
async setSigningKey(addr: string, k: unknown) { signingKeyStore.set(addr, k); },
async getSigningKey(addr: string) { return signingKeyStore.get(addr) ?? null; },
async removeSigningKey(addr: string) { signingKeyStore.delete(addr); },
async clearSigningKeys() { signingKeyStore.clear(); },
async exportPrivateStates(): Promise<PrivateStateExport> { throw new Error('Not implemented'); },
async importPrivateStates() { throw new Error('Not implemented'); },
async exportSigningKeys(): Promise<SigningKeyExport> { throw new Error('Not implemented'); },
async importSigningKeys() { throw new Error('Not implemented'); },
};
}levelPrivateStateProviderimport type {
PrivateStateProvider, PrivateStateId, PrivateStateExport,
SigningKeyExport,
} from '@midnight-ntwrk/midnight-js-types';
function createPrivateStateProvider() {
let scope = '';
const stateStore = new Map<string, unknown>();
const signingKeyStore = new Map<string, unknown>();
const key = (id: string) => `${scope}:${id}`;
return {
setContractAddress(address: string) { scope = address; },
async set(id: string, state: unknown) { stateStore.set(key(id), state); },
async get(id: string) { return stateStore.get(key(id)) ?? null; },
async remove(id: string) { stateStore.delete(key(id)); },
async clear() { stateStore.clear(); },
async setSigningKey(addr: string, k: unknown) { signingKeyStore.set(addr, k); },
async getSigningKey(addr: string) { return signingKeyStore.get(addr) ?? null; },
async removeSigningKey(addr: string) { signingKeyStore.delete(addr); },
async clearSigningKeys() { signingKeyStore.clear(); },
async exportPrivateStates(): Promise<PrivateStateExport> { throw new Error('未实现'); },
async importPrivateStates() { throw new Error('未实现'); },
async exportSigningKeys(): Promise<SigningKeyExport> { throw new Error('未实现'); },
async importSigningKeys() { throw new Error('未实现'); },
};
}levelPrivateStateProviderimport { getTestEnvironment } from '@midnight-ntwrk/testkit-js';
import pino from 'pino';
const logger = pino({ level: 'info' });
let testEnvironment: Awaited<ReturnType<typeof getTestEnvironment>>;
beforeAll(async () => {
testEnvironment = getTestEnvironment(logger);
const environmentConfig = await testEnvironment.start();
// environmentConfig contains indexer, node, proofServer URLs
});
afterAll(async () => {
await testEnvironment.shutdown();
});
// Get wallet providers for testing
const walletProvider = await testEnvironment.getMidnightWalletProvider();
// or multiple wallets (max 4 on local):
const [wallet1, wallet2] = await testEnvironment.startMidnightWalletProviders(2);undefinedimport { getTestEnvironment } from '@midnight-ntwrk/testkit-js';
import pino from 'pino';
const logger = pino({ level: 'info' });
let testEnvironment: Awaited<ReturnType<typeof getTestEnvironment>>;
beforeAll(async () => {
testEnvironment = getTestEnvironment(logger);
const environmentConfig = await testEnvironment.start();
// environmentConfig包含索引器、节点、证明服务器的URL
});
afterAll(async () => {
await testEnvironment.shutdown();
});
// 获取用于测试的钱包provider
const walletProvider = await testEnvironment.getMidnightWalletProvider();
// 或多个钱包(本地环境最多4个):
const [wallet1, wallet2] = await testEnvironment.startMidnightWalletProviders(2);undefined
---
---undefinedundefined
Proof server URL: `http://127.0.0.1:6300`
**Mac ARM (Apple Silicon) fix:** If the proof server hangs, go to Docker Desktop → Settings → General → Virtual Machine Options → select **Docker VMM** → restart Docker.
---
证明服务器URL:`http://127.0.0.1:6300`
**Mac ARM(Apple Silicon)修复:** 如果证明服务器挂起,请进入Docker Desktop → 设置 → 通用 → 虚拟机选项 → 选择 **Docker VMM** → 重启Docker。
---setNetworkIdglobalThis.WebSocket = WebSocketzkConfigPathkeys/zkir/sync:assetssignTransactionIntentsUnboundTransaction'pre-proof''proof'mn_addr_preprod1...relayURLhttps://wss://http://ws://CompiledContract.makelevelPrivateStateProvidercreatePrivateStateProvidersetNetworkIdglobalThis.WebSocket = WebSocketzkConfigPathkeys/zkir/sync:assetssignTransactionIntentsUnboundTransaction'pre-proof''proof'mn_addr_preprod1...relayURLhttps://wss://http://ws://CompiledContract.makelevelPrivateStateProvidercreatePrivateStateProvider{
"@midnight-ntwrk/compact-runtime": "0.15.0",
"@midnight-ntwrk/ledger-v8": "8.0.3",
"@midnight-ntwrk/midnight-js-contracts": "4.0.2",
"@midnight-ntwrk/midnight-js-http-client-proof-provider": "4.0.2",
"@midnight-ntwrk/midnight-js-indexer-public-data-provider": "4.0.2",
"@midnight-ntwrk/midnight-js-level-private-state-provider": "4.0.2",
"@midnight-ntwrk/midnight-js-network-id": "4.0.2",
"@midnight-ntwrk/midnight-js-node-zk-config-provider": "4.0.2",
"@midnight-ntwrk/midnight-js-types": "4.0.2",
"@midnight-ntwrk/midnight-js-utils": "4.0.2",
"@midnight-ntwrk/wallet-sdk-address-format": "3.1.0",
"@midnight-ntwrk/wallet-sdk-dust-wallet": "3.0.0",
"@midnight-ntwrk/wallet-sdk-facade": "3.0.0",
"@midnight-ntwrk/wallet-sdk-hd": "3.0.1",
"@midnight-ntwrk/wallet-sdk-shielded": "2.1.0",
"@midnight-ntwrk/wallet-sdk-unshielded-wallet": "2.1.0",
"@midnight-ntwrk/compact-js": "2.5.0"
}{
"@midnight-ntwrk/compact-runtime": "0.15.0",
"@midnight-ntwrk/ledger-v8": "8.0.3",
"@midnight-ntwrk/midnight-js-contracts": "4.0.2",
"@midnight-ntwrk/midnight-js-http-client-proof-provider": "4.0.2",
"@midnight-ntwrk/midnight-js-indexer-public-data-provider": "4.0.2",
"@midnight-ntwrk/midnight-js-level-private-state-provider": "4.0.2",
"@midnight-ntwrk/midnight-js-network-id": "4.0.2",
"@midnight-ntwrk/midnight-js-node-zk-config-provider": "4.0.2",
"@midnight-ntwrk/midnight-js-types": "4.0.2",
"@midnight-ntwrk/midnight-js-utils": "4.0.2",
"@midnight-ntwrk/wallet-sdk-address-format": "3.1.0",
"@midnight-ntwrk/wallet-sdk-dust-wallet": "3.0.0",
"@midnight-ntwrk/wallet-sdk-facade": "3.0.0",
"@midnight-ntwrk/wallet-sdk-hd": "3.0.1",
"@midnight-ntwrk/wallet-sdk-shielded": "2.1.0",
"@midnight-ntwrk/wallet-sdk-unshielded-wallet": "2.1.0",
"@midnight-ntwrk/compact-js": "2.5.0"
}