shadowlend-sdk
v1.0.1
Published
TypeScript SDK for ShadowLend - Privacy-preserving lending protocol on Solana
Maintainers
Readme
ShadowLend SDK
TypeScript SDK for interacting with the ShadowLend privacy-preserving lending protocol on Solana
Features
- Privacy-First: Generate ZK proofs client-side to keep your financial data private
- Full Protocol Support: Deposit, borrow, repay, withdraw, and liquidate
- Merkle Tree Management: Built-in incremental Merkle tree for commitment tracking
- Type-Safe: Full TypeScript support with comprehensive type definitions
- Flexible: Works with any Solana wallet adapter
Installation
npm install shadowlend-sdk
# or
yarn add shadowlend-sdk
# or
pnpm add shadowlend-sdkQuick Start
import { createShadowLendDevnet, Asset, BN } from 'shadowlend-sdk';
async function main() {
// Create SDK instance for devnet
const shadowlend = createShadowLendDevnet();
// Initialize (loads Poseidon, Merkle tree, ZK circuits)
await shadowlend.initialize();
// Connect your wallet
shadowlend.connect(wallet);
// Deposit 1 SOL as collateral
const deposit = await shadowlend.depositCollateral(
Asset.SOL,
new BN(1_000_000_000) // 1 SOL in lamports
);
// Sign and send transaction
const signature = await shadowlend.sendTransaction(deposit.transaction);
console.log('Deposit successful:', signature);
console.log('Commitment:', deposit.commitment);
// IMPORTANT: Save these - needed for withdrawal!
console.log('Secret:', deposit.secret);
console.log('Leaf Index:', deposit.leafIndex);
}API Reference
Creating an Instance
import {
ShadowLend,
createShadowLend,
createShadowLendDevnet,
createShadowLendMainnet
} from 'shadowlend-sdk';
import { Connection } from '@solana/web3.js';
// Option 1: Quick setup for devnet
const sdk = createShadowLendDevnet();
// Option 2: Quick setup for mainnet
const sdk = createShadowLendMainnet();
// Option 3: Custom configuration
const connection = new Connection('https://api.devnet.solana.com');
const sdk = createShadowLend(connection, {
programId: new PublicKey('BzabuYsVF1mkW2oEiAb7gyG8euJWkr4zZjsgRDZNSdGG'),
merkleTreeDepth: 20,
minCollateralRatio: 150,
liquidationThreshold: 125,
});
// Always initialize before use
await sdk.initialize();Wallet Connection
// Connect wallet (compatible with @solana/wallet-adapter)
sdk.connect(wallet);
// Get connected wallet
const wallet = sdk.getWallet();
// Disconnect
sdk.disconnect();Depositing Collateral
import { Asset, BN } from 'shadowlend-sdk';
// Deposit SOL
const deposit = await sdk.depositCollateral(
Asset.SOL,
new BN(5_000_000_000) // 5 SOL
);
// Transaction ready to sign
const signature = await sdk.sendTransaction(deposit.transaction);
// Save these for later operations!
const { commitment, secret, nullifierHash, leafIndex } = deposit;What happens under the hood:
- Generates random
secretandnullifier - Computes
commitment = Poseidon(nullifier, amount) - Inserts commitment into local Merkle tree
- Builds transaction with encrypted amount
Borrowing Against Collateral
// Borrow USDC against your deposited collateral
const borrow = await sdk.borrow(
Asset.USDC,
new BN(500_000_000), // 500 USDC (6 decimals)
deposit.commitment,
{
secret: deposit.secret,
nullifier: deposit.nullifierHash,
collateralAmount: BigInt(5_000_000_000), // 5 SOL
collateralAsset: Asset.SOL,
}
);
const signature = await sdk.sendTransaction(borrow.transaction);
console.log('Position ID:', borrow.positionId);
console.log('Interest Rate:', borrow.interestRate);ZK Proof Generation: The SDK automatically generates a Groth16 proof that verifies:
- You own the collateral (know the secret)
- Collateral is in the Merkle tree
collateralValue * LTV >= loanAmount
Without revealing the actual collateral amount!
Repaying a Loan
const repay = await sdk.repay(
new BN(250_000_000), // Repay 250 USDC
borrow.positionId
);
const signature = await sdk.sendTransaction(repay.transaction);
console.log('Remaining Debt:', repay.remainingDebt.toString());
console.log('Interest Paid:', repay.interestPaid.toString());Withdrawing Collateral
// After repaying your loan, withdraw collateral
const withdraw = await sdk.generateAndWithdraw({
commitment: deposit.commitment,
secret: deposit.secret,
nullifier: deposit.nullifierHash,
amount: BigInt(5_000_000_000), // Full amount
recipientAddress: wallet.publicKey,
});
const signature = await sdk.sendTransaction(withdraw.transaction);
console.log('Withdrawn:', withdraw.withdrawnAmount.toString());Checking Health Factor
const health = await sdk.getHealthFactor(deposit.commitment);
console.log('Health Factor:', health.healthFactor);
console.log('Collateral Value:', health.collateralValue.toString());
console.log('Debt Value:', health.debtValue.toString());
console.log('Liquidation Price:', health.liquidationPrice.toString());
console.log('Is Liquidatable:', health.isLiquidatable);Getting Pool State
const poolState = await sdk.getPoolState(Asset.SOL);
console.log('Total Deposits:', poolState.totalDeposits.toString());
console.log('Total Borrows:', poolState.totalBorrows.toString());
console.log('Utilization:', poolState.utilizationRate);
console.log('Supply APY:', poolState.supplyAPY);
console.log('Borrow APY:', poolState.borrowAPY);Getting Asset Prices
const price = await sdk.getAssetPrice(Asset.SOL);
console.log('Price:', price.price.toString());
console.log('Confidence:', price.confidence.toString());
console.log('Source:', price.source);Merkle Tree Operations
// Get current Merkle root
const root = sdk.getMerkleRoot();
// Get proof for a commitment
const proof = await sdk.getMerkleProof(commitment);
console.log('Path Elements:', proof.pathElements);
console.log('Path Indices:', proof.pathIndices);
// Restore commitments after page reload
await sdk.restoreCommitments([commitment1, commitment2]);
// Check if commitment exists
const exists = sdk.hasCommitment(commitment);Types
Supported Assets
enum Asset {
SOL = 'SOL',
USDC = 'USDC',
USDT = 'USDT',
mSOL = 'mSOL',
stSOL = 'stSOL',
ETH = 'ETH',
BTC = 'BTC',
}Position Interface
interface Position {
commitment: string;
collateralAsset: Asset;
collateralAmount: BN;
borrowedAsset: Asset;
borrowedAmount: BN;
interestAccrued: BN;
healthFactor: number;
status: LoanStatus;
createdAt: number;
lastUpdated: number;
}Loan Status
enum LoanStatus {
Active = 'Active',
Repaid = 'Repaid',
Liquidated = 'Liquidated',
}Cryptographic Utilities
The SDK exports cryptographic utilities for advanced use cases:
import {
initPoseidon,
poseidonHash,
generateSecret,
generateNullifier,
computeCommitment,
computeNullifierHash,
encryptNote,
decryptNote,
} from 'shadowlend-sdk';
// Initialize Poseidon (required before hashing)
await initPoseidon();
// Generate cryptographic secrets
const secret = generateSecret();
const nullifier = generateNullifier();
// Compute commitment
const commitment = await computeCommitment(
secret,
nullifier,
BigInt(1_000_000_000), // amount
0 // asset ID
);
// Hash arbitrary inputs
const hash = await poseidonHash([BigInt(123), BigInt(456)]);ZK Prover
Generate proofs manually for advanced use cases:
import { ZKProver, createProver } from 'shadowlend-sdk';
const prover = createProver();
// Preload circuits for faster proof generation
await prover.preloadCircuits();
// Generate collateral proof
const proof = await prover.generateCollateralProof(
secret,
nullifier,
collateralAmount,
minCollateralRequired,
oraclePrice,
merkleProof,
leafIndex
);
// Export for Solana transaction
const solanaProof = prover.exportProofForSolana(proof.proof);Constants
import {
SHADOWLEND_PROGRAM_ID,
MERKLE_TREE_DEPTH,
MIN_COLLATERAL_RATIO,
LIQUIDATION_THRESHOLD,
TOKEN_MINTS,
TOKEN_DECIMALS,
PYTH_ORACLES,
} from 'shadowlend-sdk';
// Program ID
console.log(SHADOWLEND_PROGRAM_ID.toBase58());
// BzabuYsVF1mkW2oEiAb7gyG8euJWkr4zZjsgRDZNSdGG
// Merkle tree supports 2^20 = 1,048,576 deposits
console.log(MERKLE_TREE_DEPTH); // 20
// Risk parameters
console.log(MIN_COLLATERAL_RATIO); // 150 (150%)
console.log(LIQUIDATION_THRESHOLD); // 125 (125%)
// Token mints (devnet)
console.log(TOKEN_MINTS[Asset.SOL].toBase58());
console.log(TOKEN_MINTS[Asset.USDC].toBase58());Error Handling
try {
await sdk.depositCollateral(Asset.SOL, new BN(100));
} catch (error) {
if (error.message.includes('not initialized')) {
await sdk.initialize();
// Retry
} else if (error.message.includes('Wallet not connected')) {
// Prompt user to connect wallet
} else if (error.message.includes('insufficient funds')) {
// User doesn't have enough SOL
}
}Session Recovery
After page reload, restore your local state:
// Save to localStorage after deposit
localStorage.setItem('shadowlend_positions', JSON.stringify([{
commitment: deposit.commitment,
secret: deposit.secret,
nullifier: deposit.nullifierHash,
leafIndex: deposit.leafIndex,
amount: '5000000000',
asset: 'SOL',
}]));
// Restore on page load
const saved = JSON.parse(localStorage.getItem('shadowlend_positions') || '[]');
const commitments = saved.map(p => p.commitment);
await sdk.restoreCommitments(commitments);Advanced: Direct Transaction Building
For advanced integrations, you can use lower-level methods:
// Get PDAs
const programId = sdk.getProgramId();
const connection = sdk.getConnection();
// Get supported assets
const assets = sdk.getSupportedAssets();
const solConfig = sdk.getAssetConfig(Asset.SOL);
// Sync with on-chain state
await sdk.syncMerkleTree();
const onChainRoot = await sdk.getOnChainMerkleRoot();Security Considerations
Secret Management
CRITICAL: The secret returned from depositCollateral() is required to withdraw your funds. If lost, your collateral is permanently locked.
Recommendations:
- Store secrets securely (encrypted local storage, hardware wallet derivation)
- Never expose secrets in logs or error messages
- Consider implementing secret recovery mechanisms
Transaction Simulation
Always simulate transactions before sending in production:
const signature = await sdk.sendTransaction(tx, {
skipPreflight: false, // Enable simulation
commitment: 'confirmed',
maxRetries: 3,
});Devnet vs Mainnet
The SDK uses fallback prices on devnet when oracles aren't available. In production:
- Ensure Pyth oracles are properly configured
- Monitor price staleness
- Implement circuit breakers for extreme price movements
Building from Source
# Clone repository
git clone https://github.com/kamalbuilds/shadowlend
cd shadowlend/sdk
# Install dependencies
npm install
# Build
npm run build
# Run tests
npm test
# Lint
npm run lint
# Format
npm run formatRequirements
- Node.js >= 18.0.0
- npm >= 8.0.0
Dependencies
| Package | Version | Purpose | |---------|---------|---------| | @coral-xyz/anchor | ^0.29.0 | Solana program interaction | | @solana/web3.js | ^1.91.0 | Solana blockchain connection | | @solana/spl-token | ^0.4.14 | SPL token operations | | snarkjs | ^0.7.3 | ZK proof generation | | circomlibjs | ^0.1.7 | Poseidon hashing | | ffjavascript | ^0.3.0 | Finite field arithmetic |
Contributing
- Fork the repository
- Create your feature branch (
git checkout -b feature/amazing-feature) - Commit changes (
git commit -m 'Add amazing feature') - Push to branch (
git push origin feature/amazing-feature) - Open a Pull Request
License
MIT License - see LICENSE for details.
Links
Built for the Solana Privacy Hackathon 2026
