shield-bridge-sdk
Version:
1,194 lines (896 loc) • 34.1 kB
Markdown
<div align="center">
<img src="https://shieldbridge.xyz/logo.svg" alt="Shield Bridge Logo" width="200"/>
# Shield Bridge SDK
**A powerful TypeScript SDK for private transactions on Tezos using Sapling protocol**
[](https://www.npmjs.com/package/shield-bridge-sdk) [](https://opensource.org/licenses/MIT) [](https://www.typescriptlang.org/)
[Features](#-features) • [Installation](#-installation) • [Quick Start](#-quick-start) • [Documentation](#-documentation) • [Examples](#-examples)
</div>
## 📖 About
**Shield Bridge SDK** is a comprehensive TypeScript library that provides a simple, secure interface to interact with the Shield Bridge smart contract on Tezos. It enables users to shield their FA1.2 tokens, FA2 tokens, and tez into Sapling shielded pools for **private transactions**.
### 🎯 Why Shield Bridge?
- **🔒 Privacy**: All shielded transactions are obfuscated by the Sapling protocol
- **⚡ Efficiency**: Multiple sapling transactions in a single operation
- **🌐 Network Effects**: Single smart contract for all shielded pools
- **🔍 Auditability**: View-only mode for compliance and monitoring
- **📊 Real-time Updates**: Progress callbacks for transaction tracking
### ✨ Key Benefits
The Shield Bridge smart contract consolidates all underlying sapling shielded pools into a single contract, which:
- Encourages network effects and liquidity
- Reduces deployment overhead
- Enables batch operations across multiple tokens
- Simplifies the user experience
## 📚 Table of Contents
- [✨ Features](#-features)
- [📦 Installation](#-installation)
- [🚀 Quick Start](#-quick-start)
- [📘 Documentation](#-documentation)
- [Initialization](#initialization)
- [Shield Operations](#shield-operations)
- [Unshield Operations](#unshield-operations)
- [Transfer Operations](#transfer-operations)
- [Query Operations](#query-operations)
- [Factory View Methods](#factory-view-methods)
- [V1/V2 Architecture Switching](#v1v2-architecture-switching)
- [Progress Callbacks](#progress-callbacks)
- [View-Only Mode](#view-only-mode)
- [Operation Hash Tracking](#operation-hash-tracking)
- [💡 Examples](#-examples)
- [⚙️ Advanced Configuration](#️-advanced-configuration)
- [📦 Exported Types](#-exported-types)
- [🧪 Testing](#-testing)
- [❓ FAQ](#-faq)
- [📄 License](#-license)
## ✨ Features
- ✅ **Shield tokens** (FA1.2, FA2, and Tez) into Sapling pools
- ✅ **Unshield tokens** back to transparent addresses
- ✅ **Transfer** between shielded addresses privately
- ✅ **Batch operations** - multiple transactions in one call
- ✅ **View-only mode** - read-only access with viewing keys
- ✅ **Progress callbacks** - real-time transaction status updates
- ✅ **Operation tracking** - get operation hashes for all transactions
- ✅ **Query balances** - check shielded balances across all pools
- ✅ **Transaction history** - retrieve incoming/outgoing transactions
- ✅ **Factory views** - query on-chain registry (set addresses, registration status)
- ✅ **V1/V2 switching** - seamlessly switch between legacy and factory architectures
- ✅ **TypeScript support** - full type safety and IntelliSense
- ✅ **Flexible configuration** - customize confirmations, sapling-params URL, architecture, and more
- ✅ **Parallel proof generation** - enabled by default for faster operations
- ✅ **Incremental balance reads** - cached sapling-diff (default-on) + an opt-in decrypt-only-new layer; decrypted notes are encrypted at rest
- ✅ **Clean lifecycle** - `destroy()` method for SPA cleanup
- ✅ **Worker safety** - automatic cleanup of parallel workers on failure
## 📦 Installation
### Prerequisites
- Node.js >= v18
- npm or yarn
### Install via npm
```bash
npm install shield-bridge-sdk
```
### Install via yarn
```bash
yarn add shield-bridge-sdk
```
## 🚀 Quick Start
Get up and running in 3 simple steps:
### 1️⃣ Initialize the SDK
```typescript
import { TezosToolkit } from '@tezos-x/octez.js';
import { InMemorySigner } from '@tezos-x/octez.js-signer';
import { ShieldBridgeSDK } from 'shield-bridge-sdk';
const tezos = new TezosToolkit('https://mainnet.tezos.ecadinfra.com');
tezos.setSignerProvider(await InMemorySigner.fromSecretKey('edsk...'));
const shieldBridge = new ShieldBridgeSDK({
client: tezos,
saplingSecret: 'sask...',
});
// Wait for the SDK to initialize
await shieldBridge.ready;
```
### 2️⃣ Shield some tokens
```typescript
// Shield 5 tez into the shielded pool
const result = await shieldBridge.shield([{ amount: 5, memo: 'deposit' }]);
console.log(`Transaction confirmed! Op hash: ${result.opHash}`);
```
### 3️⃣ Check your balance
```typescript
const balance = await shieldBridge.getShieldedBalance({});
console.log('Shielded balance:', balance);
```
🎉 **That's it!** You're now using private transactions on Tezos.
> **Note:** `getShieldedBalance({})` takes a `SaplingTokenInfo` object — pass `{}` for XTZ, or `{ contract, tokenId }` for tokens.
## 📘 Documentation
### Initialization
The SDK supports three initialization modes:
#### Full Access Mode (with Spending Key)
```typescript
const shieldBridge = new ShieldBridgeSDK({
client: tezos,
saplingSecret: 'sask...', // Your sapling spending key
});
```
#### Full Access Mode (with Mnemonic)
```typescript
const shieldBridge = new ShieldBridgeSDK({
client: tezos,
saplingMnemonic: 'word1 word2 word3 ...', // 24-word mnemonic
});
```
#### View-Only Mode (with Viewing Key)
```typescript
const shieldBridge = new ShieldBridgeSDK({
client: tezos,
saplingViewingKey: 'abc123...', // Hex-encoded viewing key
});
console.log(shieldBridge.isViewOnlyMode); // true
```
> **Note**: View-only mode allows querying balances and transactions but **cannot perform shield/unshield/transfer operations**.
### Shield Operations
Shield (deposit) transparent tokens into the Sapling shielded pool for privacy.
#### Shield Tez
```typescript
const result = await shieldBridge.shield([
{
amount: 10, // Amount in tez (or mutez if useBaseUnits: true)
memo: 'deposit', // Optional memo (max 8 chars)
},
]);
console.log(`View on TzKT: https://tzkt.io/${result.opHash}`);
```
#### Shield FA2 Tokens
```typescript
const result = await shieldBridge.shield([
{
amount: 100,
contract: 'KT1LkNWZgVYh3zdaRkBb9aNgLEFCjVJwEKu2',
tokenId: 0,
memo: 'fa2-shld',
},
]);
```
#### Shield FA1.2 Tokens
```typescript
const result = await shieldBridge.shield([
{
amount: 50,
contract: 'KT1P8RdJ5MfHMK5phKJ5JsfNfask5v2b2NQS',
memo: 'fa12shld',
},
]);
```
#### Batch Shield (Multiple Assets at Once)
```typescript
const result = await shieldBridge.shield([
{ amount: 5 }, // 5 tez
{ amount: 100, contract: 'KT1...FA2...', tokenId: 0 }, // 100 FA2 tokens
{ amount: 50, contract: 'KT1...FA12...' }, // 50 FA1.2 tokens
]);
// All shielded in a single operation!
```
#### Shield to a Different Address
```typescript
const result = await shieldBridge.shield([
{
amount: 10,
shieldedAddress: 'zet13iuD1bKNE5MAEXN4JfVEnSKHyoCAwUhGz...', // Recipient's shielded address
memo: 'gift',
},
]);
```
### Unshield Operations
Unshield (withdraw) tokens from the Sapling pool back to transparent addresses.
#### Unshield Tez
```typescript
const result = await shieldBridge.unshield([
{
amount: 3, // Amount to unshield
// Unshields to your connected wallet address by default
},
]);
```
#### Unshield to a Different Address
```typescript
const result = await shieldBridge.unshield([
{
amount: 3,
unshieldedAddress: 'tz1VSUr8wwNhLAzempoch5d6hLRiTh8Cjcjb', // Recipient
},
]);
```
#### Unshield FA2 Tokens
```typescript
const result = await shieldBridge.unshield([
{
amount: 50,
contract: 'KT1LkNWZgVYh3zdaRkBb9aNgLEFCjVJwEKu2',
tokenId: 0,
},
]);
```
#### Batch Unshield
```typescript
const result = await shieldBridge.unshield([
{ amount: 2 }, // Unshield 2 tez
{ amount: 30, contract: 'KT1...', tokenId: 0 }, // Unshield 30 FA2 tokens
]);
```
### Transfer Operations
Transfer tokens privately between shielded addresses (both sender and recipient remain private).
#### Simple Transfer
```typescript
const result = await shieldBridge.transfer([
{
transfers: [
{
amount: 1,
to: 'zet13iuD1bKNE5MAEXN4JfVEnSKHyoCAwUhGzXFANZxmeof2DJXGX4F23jEW5zgf7LpdK',
memo: 'payment',
},
],
},
]);
```
#### Multiple Recipients (Split Payment)
```typescript
const result = await shieldBridge.transfer([
{
transfers: [
{
amount: 1,
to: 'zet13iuD1bKNE5...',
memo: 'alice',
},
{
amount: 2,
to: 'zet13Kuz6erK5...',
memo: 'bob',
},
{
amount: 0.5,
to: 'zet14abc123...',
memo: 'charlie',
},
],
},
]);
// Sends to 3 different recipients in one transaction!
```
#### Transfer FA2 Tokens
```typescript
const result = await shieldBridge.transfer([
{
transfers: [
{
amount: 25,
to: 'zet13iuD1bKNE5...',
memo: 'tokens',
},
],
contract: 'KT1LkNWZgVYh3zdaRkBb9aNgLEFCjVJwEKu2',
tokenId: 0,
},
]);
```
#### Batch Transfer (Multiple Assets)
```typescript
const result = await shieldBridge.transfer([
{
// Transfer tez
transfers: [{ amount: 1, to: 'zet13iuD1...' }],
},
{
// Transfer FA2 tokens
transfers: [{ amount: 50, to: 'zet13iuD1...' }],
contract: 'KT1...',
tokenId: 0,
},
]);
```
### Query Operations
#### Get Shielded Balance
```typescript
// Get tez balance
const tezBalance = await shieldBridge.getShieldedBalance({});
// Get FA2 token balance
const fa2Balance = await shieldBridge.getShieldedBalance({
contract: 'KT1LkNWZgVYh3zdaRkBb9aNgLEFCjVJwEKu2',
tokenId: 0,
});
// Get FA1.2 token balance
const fa12Balance = await shieldBridge.getShieldedBalance({
contract: 'KT1P8RdJ5MfHMK5phKJ5JsfNfask5v2b2NQS',
});
```
> **Tip:** pass `setAddress` and/or `decimals` if you already know them (e.g. from your own asset
> list). The SDK uses them directly and skips the per-token factory/`/v1/tokens` lookups —
> meaningful when reading many balances at once.
>
> ```typescript
> const balance = await shieldBridge.getShieldedBalance({
> contract: 'KT1LkNWZgVYh3zdaRkBb9aNgLEFCjVJwEKu2',
> tokenId: 0,
> setAddress: 'KT1...', // skips the factory set-address lookup
> decimals: 8, // skips the /v1/tokens decimals lookup
> });
> ```
#### Get All Shielded Balances
```typescript
const balances = await shieldBridge.getAllShieldedBalances();
console.log(balances);
// [
// { balance: 5.5, setAddress: 'KT1...' },
// { balance: 100, setAddress: 'KT1...', contract: 'KT1...', tokenId: 0 },
// { balance: 50, setAddress: 'KT1...', contract: 'KT1...' }
// ]
```
#### Get Transaction History
```typescript
// Get tez transaction history
const tezTxs = await shieldBridge.getShieldedTransactions();
// Get FA2 token transaction history
const fa2Txs = await shieldBridge.getShieldedTransactions(
'KT1LkNWZgVYh3zdaRkBb9aNgLEFCjVJwEKu2',
0,
);
// Get FA1.2 token transaction history
const fa12Txs = await shieldBridge.getShieldedTransactions(
'KT1P8RdJ5MfHMK5phKJ5JsfNfask5v2b2NQS',
);
console.log(tezTxs);
// {
// incoming: [{ value: 5, memo: 'Received payment', position: '0' }],
// outgoing: [{ value: 2, memo: 'Sent payment', position: '1' }]
// }
```
#### Get Shielded Address
```typescript
const shieldedAddress = await shieldBridge.getShieldedAddress();
console.log(`Your shielded address: ${shieldedAddress}`);
// zet13iuD1bKNE5MAEXN4JfVEnSKHyoCAwUhGzXFANZxmeof2DJXGX4F23jEW5zgf7LpdK
```
#### Get All Shielded Assets
```typescript
const assets = await shieldBridge.getAllShieldedAssets();
console.log(assets);
// [
// { setAddress: 'KT1...' }, // Tez
// { setAddress: 'KT1...', contract: 'KT1...', tokenId: 0, metadata: {...} }, // FA2
// { setAddress: 'KT1...', contract: 'KT1...', metadata: {...} } // FA1.2
// ]
```
### Factory View Methods
Query the on-chain factory contract registry to discover set addresses and check registration status. These call Tezos on-chain views directly.
#### Get the Tez Set Address
```typescript
const tezSet = await shieldBridge.getTezSetAddress();
console.log(`Tez sapling set: ${tezSet}`);
// KT1...
```
#### Get an FA1.2 Token's Set Address
```typescript
const fa12Set = await shieldBridge.getFA12SetAddress('KT1P8RdJ5MfHMK5...');
if (fa12Set) {
console.log(`FA1.2 set: ${fa12Set}`);
} else {
console.log('Token not registered yet');
}
```
#### Get an FA2 Token's Set Address
```typescript
const fa2Set = await shieldBridge.getFA2SetAddress('KT1LkNWZg...', 0);
if (fa2Set) {
console.log(`FA2 set: ${fa2Set}`);
} else {
console.log('Token not registered yet');
}
```
#### Check if a Set is Registered
```typescript
const isRegistered = await shieldBridge.isRegisteredSet('KT1SetAddr...');
console.log(`Registered: ${isRegistered}`); // true or false
```
### V1/V2 Architecture Switching
The SDK supports both the legacy V1 (Map contract) and V2 (Factory contract) architectures. You can switch between them at runtime without re-initializing your sapling keys.
#### Switch to V1 for Fund Migration
```typescript
const sdk = new ShieldBridgeSDK({
client: tezos,
saplingMnemonic: 'word1 word2 ...',
// Defaults to V2
});
await sdk.ready;
// Check V2 balance
const v2Balance = await sdk.getShieldedBalance({});
// Switch to V1 to access legacy funds
sdk.switchArchitecture('1');
const v1Balance = await sdk.getShieldedBalance({});
// Switch back to V2
sdk.switchArchitecture('2');
```
#### Check Current Architecture
```typescript
console.log(sdk.getArchitecture()); // '2' (default)
sdk.switchArchitecture('1');
console.log(sdk.getArchitecture()); // '1'
```
> **Note**: `switchArchitecture` throws if any operations are in flight. Wait for all pending operations before switching.
### Progress Callbacks
Monitor transaction progress in real-time with optional callbacks. Perfect for building UIs with loading states and progress bars!
#### Basic Progress Tracking
```typescript
const result = await shieldBridge.shield([{ amount: 5 }], {
onGenerating: (params) => {
console.log(`🔄 Generating ${params.length} transaction(s)...`);
},
onSigning: () => {
console.log('✍️ Signing transaction...');
},
onSubmitting: (data) => {
console.log(`📤 Submitted! Op hash: ${data.opHash}`);
},
onConfirmed: (data) => {
console.log(`✅ Confirmed! Op hash: ${data.opHash}`);
},
});
```
#### Progress Bar Integration
```typescript
let progress = 0;
const setProgress = (value: number) => {
progress = value;
updateProgressBar(progress); // Your UI update function
};
await shieldBridge.shield([{ amount: 5 }], {
onGenerating: () => setProgress(25),
onSigning: () => setProgress(50),
onSubmitting: () => setProgress(75),
onConfirmed: () => setProgress(100),
});
```
#### React/Vue Example
```typescript
const [status, setStatus] = useState('idle');
const [opHash, setOpHash] = useState('');
await shieldBridge.shield([{ amount: 5 }], {
onGenerating: () => setStatus('Generating proof...'),
onSigning: () => setStatus('Awaiting signature...'),
onSubmitting: (data) => {
setStatus('Broadcasting...');
setOpHash(data.opHash);
},
onConfirmed: () => setStatus('Confirmed! ✅'),
});
```
#### Advanced: Step-by-Step Tracking
```typescript
const steps: string[] = [];
await shieldBridge.transfer(
[
{
transfers: [
{ amount: 1, to: 'zet13iuD1...' },
{ amount: 2, to: 'zet13Kuz6...' },
],
},
],
{
onGenerating: (params) => {
// params contains the full array of transfer parameters
steps.push(
`Generating proof for ${params[0].transfers.length} recipients`,
);
},
onSigning: () => {
steps.push('Signing with wallet');
},
onSubmitting: (data) => {
steps.push(`Transaction submitted: ${data.opHash}`);
// Open block explorer
window.open(`https://tzkt.io/${data.opHash}`, '_blank');
},
onConfirmed: (data) => {
steps.push(`Confirmed! Op hash: ${data.opHash}`);
// Trigger success notification
showNotification('Transfer complete!');
},
},
);
```
#### Callbacks Available For
All transaction methods support callbacks:
- ✅ `shield(params, callbacks)`
- ✅ `unshield(params, callbacks)`
- ✅ `transfer(params, callbacks)`
> **Note**: All callbacks are **optional**. You can provide just the ones you need!
### View-Only Mode
View-only mode allows you to **monitor balances and transactions without the ability to spend**. Perfect for:
- 📊 **Auditing** - Monitor account activity
- 🔍 **Compliance** - Regulatory oversight
- 👀 **Read-only dashboards** - Display balances without risk
- 🔐 **Security** - Share viewing access without spending rights
#### Export a Viewing Key
First, export a viewing key from your spending key:
```typescript
// Initialize with spending key
const fullAccessSdk = new ShieldBridgeSDK({
client: tezos,
saplingSecret: 'sask...',
});
// Export viewing key
const viewingKey = await fullAccessSdk.getViewingKey();
console.log(`Viewing key: ${viewingKey}`);
// Save this securely - it's a hex-encoded string
```
#### Initialize with Viewing Key
```typescript
// Initialize in view-only mode
const viewOnlySdk = new ShieldBridgeSDK({
client: tezos,
saplingViewingKey: 'abc123...', // The exported viewing key
});
console.log(viewOnlySdk.isViewOnlyMode); // true
// ✅ Can query balances
const balance = await viewOnlySdk.getShieldedBalance({});
// ✅ Can view transactions
const txs = await viewOnlySdk.getShieldedTransactions();
// ❌ Cannot perform transactions
await viewOnlySdk.shield([{ amount: 1 }]);
// Error: Cannot shield tokens in view-only mode
```
#### Use Cases
**Compliance Dashboard**
```typescript
const complianceSdk = new ShieldBridgeSDK({
client: tezos,
saplingViewingKey: customerViewingKey,
});
// Monitor customer activity
const balance = await complianceSdk.getShieldedBalance({});
const transactions = await complianceSdk.getShieldedTransactions();
// Generate compliance reports without spending ability
generateComplianceReport(balance, transactions);
```
**Portfolio Tracker**
```typescript
const portfolioSdk = new ShieldBridgeSDK({
client: tezos,
saplingViewingKey: portfolioViewingKey,
});
// Track all assets
const allBalances = await portfolioSdk.getAllShieldedBalances();
const totalValue = calculateTotalValue(allBalances);
displayPortfolio(totalValue);
```
### Operation Hash Tracking
All transaction methods return an operation hash for easy tracking on block explorers.
#### Getting the Operation Hash
```typescript
const result = await shieldBridge.shield([{ amount: 5 }]);
console.log(`Operation hash: ${result.opHash}`);
console.log(`Block hash: ${result.block?.hash}`);
// Open in TzKT explorer
const explorerUrl = `https://tzkt.io/${result.opHash}`;
window.open(explorerUrl, '_blank');
```
## 💡 Examples
### Complete Example: Shield, Transfer, and Unshield
```typescript
import { TezosToolkit } from '@tezos-x/octez.js';
import { InMemorySigner } from '@tezos-x/octez.js-signer';
import { ShieldBridgeSDK } from 'shield-bridge-sdk';
async function main() {
// Initialize
const tezos = new TezosToolkit('https://mainnet.tezos.ecadinfra.com');
tezos.setSignerProvider(await InMemorySigner.fromSecretKey('edsk...'));
const shieldBridge = new ShieldBridgeSDK({
client: tezos,
saplingSecret: 'sask...',
});
await shieldBridge.ready;
// 1. Shield 10 tez
console.log('Shielding 10 tez...');
const shieldResult = await shieldBridge.shield([
{ amount: 10, memo: 'deposit' },
]);
console.log(`✅ Shielded! Op: ${shieldResult.opHash}`);
// 2. Check balance
const balance = await shieldBridge.getShieldedBalance({});
console.log(`💰 Balance: ${balance} tez`);
// 3. Transfer 3 tez privately
console.log('Transferring 3 tez...');
const transferResult = await shieldBridge.transfer([
{
transfers: [
{
amount: 3,
to: 'zet13iuD1bKNE5MAEXN4JfVEnSKHyoCAwUhGzXFANZxmeof2DJXGX4F23jEW5zgf7LpdK',
memo: 'payment',
},
],
},
]);
console.log(`✅ Transferred! Op: ${transferResult.opHash}`);
// 4. Unshield 5 tez
console.log('Unshielding 5 tez...');
const unshieldResult = await shieldBridge.unshield([{ amount: 5 }]);
console.log(`✅ Unshielded! Op: ${unshieldResult.opHash}`);
// 5. Final balance
const finalBalance = await shieldBridge.getShieldedBalance({});
console.log(`💰 Final balance: ${finalBalance} tez`);
}
main().catch(console.error);
```
### Example: Payment Splitting
```typescript
async function splitPayment() {
const shieldBridge = new ShieldBridgeSDK({
client: tezos,
saplingSecret: 'sask...',
});
await shieldBridge.ready;
// Pay multiple recipients in one transaction
await shieldBridge.transfer([
{
transfers: [
{ amount: 10, to: 'zet13Alice...', memo: 'alice' },
{ amount: 15, to: 'zet13Bob...', memo: 'bob' },
{ amount: 20, to: 'zet13Carol...', memo: 'carol' },
],
},
]);
console.log('✅ Payment split sent to 3 recipients!');
}
```
## ⚙️ Advanced Configuration
### Network Configuration (Shadownet)
```typescript
import { TezosToolkit } from '@tezos-x/octez.js';
import { InMemorySigner } from '@tezos-x/octez.js-signer';
import { ShieldBridgeSDK, shieldBridgeContract } from 'shield-bridge-sdk';
const tezos = new TezosToolkit('https://shadownet.tezos.ecadinfra.com');
tezos.setSignerProvider(await InMemorySigner.fromSecretKey('edsk...'));
const shieldBridge = new ShieldBridgeSDK({
client: tezos,
saplingSecret: 'sask...',
tzktApi: 'shadownet',
shieldBridgeContract: shieldBridgeContract.shadownet,
});
```
### Custom Configuration Options
```typescript
const shieldBridge = new ShieldBridgeSDK({
client: tezos,
saplingMnemonic: 'word1 word2 word3 ...', // Use mnemonic instead of secret key
// Network settings
tzktApi: 'mainnet', // or 'shadownet'
shieldBridgeContract: 'KT1...', // Custom contract (optional)
contractArchitecture: '2', // '2' for Factory (default), '1' for legacy Map
// Sapling params CDN
saplingParamsUrl: 'https://cdn.shieldbridge.xyz/sapling-params/', // Custom CDN URL
// Transaction settings
minConfirmations: 2, // Wait for 2 confirmations (default: 1)
// Display settings
useBaseUnits: true, // Use mutez instead of tez (default: false)
// Performance settings
parallelThreads: true, // Parallel proof generation (default: true)
// Incremental read caching
saplingDiffCache: true, // Cache the sapling-diff delta — far less RPC per read (default: true)
saplingBalanceCache: false, // Also cache decrypted notes (decrypt-only-new); opt-in (default: false)
// saplingDiffStore: new MemoryDiffStore(), // Node/Lambda only — browser auto-uses IndexedDB
});
```
### Configuration Details
#### `minConfirmations`
Number of confirmations to wait for before considering a transaction complete.
```typescript
// Wait for 3 confirmations for extra security
const shieldBridge = new ShieldBridgeSDK({
client: tezos,
saplingSecret: 'sask...',
minConfirmations: 3,
});
```
#### `useBaseUnits`
Display amounts in base units (mutez) instead of tez.
```typescript
const shieldBridge = new ShieldBridgeSDK({
client: tezos,
saplingSecret: 'sask...',
useBaseUnits: true, // Use 1000000 instead of 1 for 1 tez
});
// Shield 1 tez (specified as 1,000,000 mutez)
await shieldBridge.shield([{ amount: 1000000 }]);
```
#### `parallelThreads`
Enable parallel proof generation for faster batch operations.
```typescript
const shieldBridge = new ShieldBridgeSDK({
client: tezos,
saplingSecret: 'sask...',
parallelThreads: true, // Generate proofs in parallel
});
// This will generate proofs for all 5 shields simultaneously
await shieldBridge.shield([
{ amount: 1 },
{ amount: 2 },
{ amount: 3 },
{ amount: 4 },
{ amount: 5 },
]);
```
> **Note**: Parallel mode is enabled by default and uses Web Workers in the browser and Node.js `worker_threads` in Node (both run the same pooled proof-generation workers). Set `parallelThreads: false` for sequential mode to reduce memory usage — in Node this also skips spawning a worker and runs the sapling core directly (useful for AWS Lambda).
#### `saplingDiffCache` (v1 — fetch cache, default **on**)
Every balance/transaction read needs the pool's sapling-diff. Re-fetching the whole diff each time is wasteful and gets worse as a pool grows. With the diff cache on, the SDK persists the **finalized prefix** (everything up to `head~2`, which is immutable under Tenderbake) and on the next read fetches only the small unconfirmed **tail** plus any new finalized entries. The decrypt/spend path is unchanged, so balances are byte-for-byte identical — this is purely an RPC-traffic optimization.
- **Browser:** persists to **IndexedDB** automatically (shared across same-origin workers). Nothing to configure.
- **Node/Lambda:** there is no IndexedDB, so supply a store via `saplingDiffStore` (e.g. `new MemoryDiffStore()`, or your own `SaplingDiffStore`). Only applied in direct-execution mode (`parallelThreads: false`).
- The cache holds only **public** diff data (commitments, ciphertexts, nullifiers) — nothing account-specific — so it is safe to share and keyed by `(rpc host, set address)`.
```typescript
// Node / Lambda: enable the diff cache with an in-memory store
import { ShieldBridgeSDK, MemoryDiffStore } from 'shield-bridge-sdk';
const shieldBridge = new ShieldBridgeSDK({
client: tezos,
saplingSecret: 'sask...',
parallelThreads: false, // store injection requires direct mode
saplingDiffStore: new MemoryDiffStore(),
});
```
#### `saplingBalanceCache` (v2 — decrypt cache, opt-**in**)
Layers on top of `saplingDiffCache`. In addition to the public diff, it caches this **account's decrypted notes** and on a warm scan decrypts only the commitments added since the last read — `O(new)` work instead of `O(pool)`. This is the optimization that makes loading many shielded balances (e.g. a whole portfolio) cheap.
Because it reimplements the balance sum, it is **guarded**: a runtime self-check recomputes the full stock balance every few scans and, on any divergence, invalidates the cache and returns the trusted stock value — so a cache bug can never surface a wrong balance. Reorg-safe by construction: only the finalized prefix is persisted; a note spent in the unconfirmed tail is excluded from the current scan but not written as spent, so a reorg self-heals on the next read.
**Encrypted at rest.** The decrypted notes are never written in the clear. Each cache entry is sealed with authenticated **XSalsa20-Poly1305** under a key derived from the viewing key itself (domain-separated keyed BLAKE2b). It uses pure JS/WASM (no `crypto.subtle`), so it works in non-secure contexts too (e.g. an HTTP/LAN origin). A locked account (viewing key sealed behind its password) can't be read off disk; a session-only account leaves only undecryptable ciphertext once its key is gone. This is an **at-rest** defense only — while the SDK is unlocked, keys and balances live in memory as usual.
```typescript
const shieldBridge = new ShieldBridgeSDK({
client: tezos,
saplingMnemonic: 'word1 word2 ...',
saplingBalanceCache: true, // opt in (saplingDiffCache must be on — it is, by default)
});
```
#### `clearShieldedBalanceCache()`
Evicts the **current account's** v2 decrypt-cache entries. The persistent cache lives in IndexedDB and **survives `destroy()`** (which only tears down workers) — that's intentional, so a lock/unlock keeps the cache warm. Call this explicitly when **forgetting** an account so no (encrypted) decrypted data is left behind. It must run while the SDK is still live, i.e. **before** `destroy()`.
```typescript
// On "forget account":
await shieldBridge.clearShieldedBalanceCache();
await shieldBridge.destroy();
```
#### `destroy()`
Cleans up all workers and clears in-memory state. Call this in SPAs when the component using the SDK unmounts. Note: this does **not** clear the persistent IndexedDB diff/balance cache — use `clearShieldedBalanceCache()` for that (see above).
```typescript
// In a React useEffect cleanup, for example:
useEffect(() => {
return () => shieldBridge.destroy();
}, []);
```
## 📦 Exported Types
The SDK exports several TypeScript types for use in your application:
```typescript
import type {
// SDK configuration
ShieldBridgeSDKConfig,
SaplingTokenInfo,
ContractArchitecture, // '1' (legacy Map) | '2' (Factory)
AmountInput, // number | string | BigNumber
// Factory contract
FactoryStorage, // On-chain factory contract storage shape
ShieldedAssetInfo, // Asset info returned by getAllShieldedAssets()
// Token metadata
TokenMetadata, // TZIP-12/16 metadata (name, symbol, decimals, thumbnailUri)
TzKTTokenBalance, // Token balance from TzKT API
// Operations
ShieldParams, // Parameters for shield()
UnshieldParams, // Parameters for unshield()
TransferParams, // Parameters for transfer()
TransactionProgressCallbacks, // onGenerating/onSigning/onSubmitting/onConfirmed
// Incremental diff cache (Node/Lambda store injection)
SaplingDiffStore, // Store interface backing the diff cache
CachedSaplingDiff, // Persisted finalized-prefix entry
SaplingDiffResponse, // Shape of a single_sapling_get_diff response
} from 'shield-bridge-sdk';
```
The diff-cache stores and helper are exported as **values** (no heavy deps — they use only `fetch` + IndexedDB), so Node/Lambda consumers can inject one:
```typescript
import {
MemoryDiffStore, // in-memory store (tests, short-lived Lambda)
IndexedDbDiffStore, // explicit IndexedDB store (browser auto-uses this)
createDefaultDiffStore, // picks IndexedDB in the browser, else null
makeCachingReadProvider, // wraps an RPC read fn with the diff cache
} from 'shield-bridge-sdk';
```
## 🧪 Testing
The SDK uses [Vitest](https://vitest.dev/) for unit testing.
```bash
# Run tests
npm test
# Watch mode
npm run test:watch
# With coverage
npm run test:coverage
```
## ❓ FAQ
### How do I create a sapling secret key?
#### Option 1: Using Octez Client
```bash
./octez-client sapling gen key my-sapling-key --unencrypted
```
The secret key will be stored in `~/.tezos-client/sapling_keys`.
#### Option 2: Using a BIP39 Mnemonic
```typescript
import * as bip39 from 'bip39';
// Generate a 24-word mnemonic
const mnemonic = bip39.generateMnemonic(256);
console.log(mnemonic);
// Use it with the SDK
const shieldBridge = new ShieldBridgeSDK({
client: tezos,
saplingMnemonic: mnemonic,
});
```
### How do I add a new token to Shield Bridge?
To add a new token to Shield Bridge, use the `initTokenSaplingSet` method:
```typescript
// For FA2 tokens
const result = await shieldBridge.initTokenSaplingSet(
'KT1LkNWZgVYh3zdaRkBb9aNgLEFCjVJwEKu2', // Token contract
0, // Token ID
);
// For FA1.2 tokens
const result = await shieldBridge.initTokenSaplingSet(
'KT1P8RdJ5MfHMK5phKJ5JsfNfask5v2b2NQS',
);
console.log(`Token added! Op: ${result.opHash}`);
```
The contract automatically:
1. Verifies the token contract has the required entrypoints
2. Deploys a new Sapling Set contract for the token
3. Registers the Set contract in the factory's registry
### What's the difference between shield, unshield, and transfer?
| Operation | Description | Privacy Level |
| ------------ | ------------------------------------------------- | --------------------------------- |
| **Shield** | Deposit transparent tokens → Shielded pool | Sender visible, recipient private |
| **Unshield** | Withdraw from shielded pool → Transparent address | Sender private, recipient visible |
| **Transfer** | Send within shielded pool | Both sender & recipient private |
### Can I use Shield Bridge on mobile?
Yes! Shield Bridge SDK works in browser and Node.js environments:
- ✅ Node.js applications (including AWS Lambda via `parallelThreads: false`)
- ✅ React/Vue/Angular web apps (desktop and mobile browsers / PWAs)
- ✅ Electron desktop apps
- ⚠️ React Native is **not** supported — it has neither a Web Worker nor Node `worker_threads` runtime for sapling proof generation
### Is it secure?
Yes! Shield Bridge uses:
- 🔐 **Sapling protocol** - Zero-knowledge proofs (same as Zcash)
- 🔒 **No key custody** - You control your keys
- 🌐 **Decentralized** - No central authority
- 🗄️ **Encrypted cache at rest** - the optional balance cache seals decrypted notes with XSalsa20-Poly1305 under a viewing-key-derived key, so no decrypted data is stored in the clear
### Can I cancel a transaction?
No. Once a transaction is submitted to the network, it cannot be cancelled. Always verify amounts and addresses before confirming.
## 📄 License
This project is licensed under the **MIT License**.
## 🤝 Contributing
Contributions are welcome! Please feel free to submit a Pull Request.
## 📞 Support
- **Issues**: [GitHub Issues](https://github.com/AndrewKishino/shield-bridge-sdk/issues)
- **Discussions**: [GitHub Discussions](https://github.com/AndrewKishino/shield-bridge-sdk/discussions)
<div align="center">
**Made with ❤️ for the Tezos ecosystem**
[⭐ Star on GitHub](https://github.com/AndrewKishino/shield-bridge-sdk) • [📦 View on npm](https://www.npmjs.com/package/shield-bridge-sdk)
</div>