Skip to content

Shield (Deposit)

Shielding converts public tokens into private notes in the pool.

Basic Shield

After syncing, shield tokens to yourself:

typescript
await client.sync();

const txHash = await client.shield({
  amount: parseEther('100'),
  tokenId: BigInt(deployment.tokens.zkUSD),  // Optional, 0n for ETH
});

Shield to Another User

Use shieldTo() to deposit directly to someone else's ZK address:

typescript
import { shieldTo, DEPLOYMENTS } from '@zkprivacy/sdk';

await shieldTo(
  {
    rpcUrl: DEPLOYMENTS.remote.rpcUrl,
    poolAddress: DEPLOYMENTS.remote.pool,
  },
  {
    recipient: 'zks1...',  // Their ZK address
    amount: '100',          // Human-readable amount
    tokenAddress: DEPLOYMENTS.remote.tokens.zkUSD,
    walletClient,
  }
);

This is perfect for:

  • Payment links
  • Deposit widgets
  • Onramps

Shield Module (Lightweight)

For deposit-only apps, use the shield submodule (~150KB vs ~2MB):

typescript
import { shieldTo, encryptNoteForRecipient } from '@zkprivacy/sdk/shield';

No prover initialization needed!

Token Approval

For ERC-20 tokens, the SDK handles approval automatically. But you can pre-approve:

typescript
import { EMT_ABI } from '@zkprivacy/sdk';

// Pre-approve pool to spend tokens
await walletClient.writeContract({
  address: tokenAddress,
  abi: EMT_ABI,
  functionName: 'approve',
  args: [poolAddress, maxUint256],
});

// Now shield without approval tx
await client.shield({ amount: parseEther('100') });

Low-Level: Manual Shielding

For advanced use cases, you can construct the shield transaction manually:

typescript
import { encryptNoteForRecipient, EMT_ABI } from '@zkprivacy/sdk';

// 1. Encrypt note for recipient
const { encryptedNote, commitment } = encryptNoteForRecipient(
  recipientZkAddress,
  amount,
  tokenId,  // MUST include for correct commitment
);

// 2. Format commitment
const commitmentBytes = `0x${commitment.toString(16).padStart(64, '0')}`;

// 3. Call EMT token's shield function
await walletClient.writeContract({
  address: tokenAddress,
  abi: EMT_ABI,
  functionName: 'shield',
  args: [amount, commitmentBytes, encryptedNote],
});

Token ID

Always pass tokenId to encryptNoteForRecipient(). Omitting it creates invalid notes.

After Shielding

After the transaction confirms, sync to see your new balance:

typescript
const txHash = await client.shield({ amount: parseEther('100') });

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

// Sync to pick up the new note
await client.sync();

console.log('New balance:', client.getBalance());

Gas Considerations

Shield transactions:

  • Gas cost: ~150,000 gas (token approval + shield)
  • Who pays: The sender's EOA
  • No ZK proof: Shielding doesn't require proof generation

For gasless shielding, users would need to use a relayer that supports shield operations.

Released under the MIT License.