Skip to content

Node.js Scripts

Examples for server-side scripts, automation, and backend services.

Setup

bash
npm install @zkprivacy/sdk viem ws

Basic Script

typescript
// scripts/check-balance.ts
import { 
  PrivacyClient, 
  createProver, 
  setGlobalProver,
  DEPLOYMENTS,
} from '@zkprivacy/sdk';
import { readFileSync } from 'fs';
import { formatEther } from 'viem';
import WebSocket from 'ws';

// Required: WebSocket polyfill for Node.js
(globalThis as any).WebSocket = WebSocket;

async function main() {
  // Load prover from local circuit files
  const prover = await createProver({
    transferCircuit: JSON.parse(
      readFileSync('circuits/transfer.json', 'utf-8')
    ),
    unshieldCircuit: JSON.parse(
      readFileSync('circuits/unshield.json', 'utf-8')
    ),
  });
  setGlobalProver(prover);

  // Create client
  const client = new PrivacyClient({
    rpcUrl: DEPLOYMENTS.remote.rpcUrl,
    poolAddress: DEPLOYMENTS.remote.pool,
    quickSyncUrl: DEPLOYMENTS.remote.quickSyncUrl,
  });

  // Connect with spending key from environment
  await client.connect({
    mode: 'standalone',
    spendingKey: BigInt(process.env.SPENDING_KEY!),
  });

  // Sync and show balance
  await client.sync();
  
  console.log('Address:', client.getAddress());
  console.log('Balance:', formatEther(client.getBalance()), 'ETH');
  
  // Show all token balances
  const balances = client.getAllBalances();
  for (const [tokenId, balance] of balances) {
    console.log(`Token ${tokenId}: ${balance}`);
  }

  client.disconnect();
}

main().catch(console.error);

Run with:

bash
SPENDING_KEY=0x... npx tsx scripts/check-balance.ts

Automated Transfer Script

typescript
// scripts/scheduled-transfer.ts
import { 
  PrivacyClient, 
  createProver, 
  setGlobalProver,
  DEPLOYMENTS,
} from '@zkprivacy/sdk';
import { readFileSync } from 'fs';
import { parseEther, createPublicClient, http } from 'viem';
import WebSocket from 'ws';

(globalThis as any).WebSocket = WebSocket;

interface TransferJob {
  recipient: string;
  amount: string;
}

async function executeTransfers(jobs: TransferJob[]) {
  // Initialize
  const prover = await createProver({
    transferCircuit: JSON.parse(readFileSync('circuits/transfer.json', 'utf-8')),
    unshieldCircuit: JSON.parse(readFileSync('circuits/unshield.json', 'utf-8')),
  });
  setGlobalProver(prover);

  const client = new PrivacyClient({
    rpcUrl: DEPLOYMENTS.remote.rpcUrl,
    poolAddress: DEPLOYMENTS.remote.pool,
  });

  const publicClient = createPublicClient({
    transport: http(DEPLOYMENTS.remote.rpcUrl),
  });

  await client.connect({
    mode: 'standalone',
    spendingKey: BigInt(process.env.SPENDING_KEY!),
  });

  await client.sync();
  console.log('Starting balance:', client.getBalance());

  // Execute each transfer
  for (const job of jobs) {
    console.log(`Transferring ${job.amount} to ${job.recipient.slice(0, 20)}...`);
    
    try {
      const txHash = await client.transfer({
        recipient: job.recipient,
        amount: parseEther(job.amount),
      });
      
      console.log('TX submitted:', txHash);
      
      // Wait for confirmation
      await publicClient.waitForTransactionReceipt({ hash: txHash });
      console.log('Confirmed!');
      
      // Sync after each transfer
      await client.sync();
    } catch (error) {
      console.error('Transfer failed:', error);
    }
  }

  console.log('Final balance:', client.getBalance());
  client.disconnect();
}

// Example jobs
const jobs: TransferJob[] = [
  { recipient: 'zks1abc...', amount: '10' },
  { recipient: 'zks1def...', amount: '20' },
];

executeTransfers(jobs);

Deposit Service

Backend service that shields tokens on behalf of users:

typescript
// services/deposit-service.ts
import express from 'express';
import { 
  shieldTo, 
  DEPLOYMENTS,
  encryptNoteForRecipient,
  EMT_ABI,
} from '@zkprivacy/sdk';
import { 
  createWalletClient, 
  createPublicClient,
  http, 
  parseUnits,
} from 'viem';
import { privateKeyToAccount } from 'viem/accounts';
import { foundry } from 'viem/chains';

const app = express();
app.use(express.json());

// Service wallet (holds tokens to distribute)
const account = privateKeyToAccount(process.env.SERVICE_KEY as `0x${string}`);

const walletClient = createWalletClient({
  account,
  chain: foundry,
  transport: http(DEPLOYMENTS.remote.rpcUrl),
});

const publicClient = createPublicClient({
  chain: foundry,
  transport: http(DEPLOYMENTS.remote.rpcUrl),
});

app.post('/deposit', async (req, res) => {
  const { recipient, amount, token } = req.body;

  if (!recipient || !amount) {
    return res.status(400).json({ error: 'Missing recipient or amount' });
  }

  try {
    const tokenAddress = DEPLOYMENTS.remote.tokens[token as keyof typeof DEPLOYMENTS.remote.tokens];
    if (!tokenAddress) {
      return res.status(400).json({ error: 'Invalid token' });
    }

    // Shield tokens to recipient
    const result = await shieldTo(
      {
        rpcUrl: DEPLOYMENTS.remote.rpcUrl,
        poolAddress: DEPLOYMENTS.remote.pool,
      },
      {
        recipient,
        amount,
        tokenAddress,
        walletClient,
      }
    );

    // Wait for confirmation
    await publicClient.waitForTransactionReceipt({ hash: result.txHash });

    res.json({
      success: true,
      txHash: result.txHash,
      commitment: result.commitment.toString(),
    });
  } catch (error) {
    console.error('Deposit failed:', error);
    res.status(500).json({ error: 'Deposit failed' });
  }
});

app.listen(3000, () => {
  console.log('Deposit service running on :3000');
});

Environment Variables

bash
# .env
SPENDING_KEY=0x...          # For automated scripts
SERVICE_KEY=0x...           # For deposit service

Running with TSX

bash
# Direct execution
npx tsx scripts/check-balance.ts

# With env file
npx tsx --env-file=.env scripts/scheduled-transfer.ts

# Watch mode (for development)
npx tsx watch scripts/my-script.ts

Released under the MIT License.