Installation
npm install @yattacorp/nexus-sdk
# or
yarn add @yattacorp/nexus-sdk
# or
pnpm add @yattacorp/nexus-sdk
Requirements: Node.js 18+
NexusClient
The main object for talking to a NEXUS node. All interactions — reading balances, deploying contracts, sending transactions — go through NexusClient.
Constructor
import { NexusClient, Wallet } from '@yattacorp/nexus-sdk';
const client = new NexusClient(config: NexusConfig, wallet?: Wallet | string);
Parameters:
config — connection settings (see below)
wallet — optional: a Wallet instance
Configuration
interface NexusConfig {
rpcUrl: string; // Primary node URL
rpcUrls?: string[]; // Backup node URLs (automatic failover)
wsUrl?: string; // WebSocket URL for real-time events
network?: 'mainnet' | 'testnet' | 'regtest';
timeout?: number; // Request timeout in milliseconds (default: 30000)
retries?: number; // How many times to retry on failure
maxRequestsPerSecond?: number; // Rate limit (default: 100)
}
Core Methods
getBlockHeight()
Get the current NEXUS block number.
const height = await client.getBlockHeight();
// Returns: number
getBalance(address?)
Get an account’s native balance (vZEC on Zcash, denominated in zatoshi).
const balance = await client.getBalance('bcrt1p...');
// Returns: number (zatoshi for Zcash)
getTokenBalance(tokenContract, address?)
Get a NEP-20 token balance.
const { balance, balanceDecimal } = await client.getTokenBalance(
'0x1234...', // token contract address
'bcrt1p...' // wallet address (optional — uses connected wallet if omitted)
);
deployContract(wasm, initArgs?, gas?)
Deploy a compiled WASM smart contract to NEXUS.
const result = await client.deployContract(
wasmBytes, // Uint8Array — your compiled .wasm file
['arg1', 100], // initialization arguments (optional)
2000000 // gas limit (optional)
);
// Returns:
interface DeploymentResult {
contractId: string; // The contract's permanent address
txHash: string;
gasUsed: number;
blockHeight: number;
}
callContract(contractId, functionName, args?, gas?)
Call a contract function that changes state (costs gas, requires signature).
const result = await client.callContract(
'0x1234...', // contract address
'transfer', // function name
['0xabcd...', '1000'], // arguments
100000 // gas limit
);
// Returns:
interface ContractCallResult<T> {
success: boolean;
result?: T;
error?: string;
gasUsed: number;
events: ContractEvent[];
logs: string[];
}
queryContract(contractId, functionName, args?)
Read contract state without changing anything (free, no gas).
const balance = await client.queryContract(
'0x1234...',
'balanceOf',
['0xabcd...']
);
getVaultAddress(timelock?)
Get your personal Zcash vault address (t-addr) for depositing ZEC.
// Zcash vault — default timelock 16,128 blocks (~2 weeks at 75s/block)
const vaultInfo = await client.getVaultAddress();
// Returns:
interface VaultInfo {
vaultAddress: string; // Send your ZEC here (e.g. "tmXxx..." on regtest)
nexusAddress: string; // Your NEXUS address
redeemScriptHex: string; // P2SH redeem script
protocolKey: string; // Protocol's public key
timelock: number; // Escape hatch waiting period in blocks (16,128 for Zcash)
}
Zcash vault addresses are derived server-side via the node (nexus_deriveVaultFromPubkey). The wallet’s local deriveVaultAddress() method is for Bitcoin only — it throws for Zcash.
sendTransaction(signedTx)
Broadcast a signed transaction to the network.
const txHash = await client.sendTransaction(signedTx);
waitForTransaction(txHash, confirmations?, timeout?)
Wait until a transaction is confirmed.
const tx = await client.waitForTransaction(
'0x1234...',
1, // number of confirmations to wait for
60000 // maximum wait time in milliseconds
);
getEvents(filter, limit?, offset?)
Query historical contract events.
const events = await client.getEvents({
contractId: '0x1234...',
eventName: 'Transfer',
fromBlock: 100,
toBlock: 200,
}, 100, 0);
subscribeToEvents(filter, callback)
Subscribe to real-time events via WebSocket.
const subscriptionId = client.subscribeToEvents(
{ contractId: '0x1234...' },
(event) => {
console.log('New event:', event);
}
);
// Stop listening:
client.unsubscribeFromEvents(subscriptionId);
Wallet
Manages your private key and generates Bitcoin-compatible Taproot addresses.
Create or Import
import { Wallet } from '@yattacorp/nexus-sdk';
// Generate a new mnemonic and create wallet (always mnemonic-based — required for vaults)
const mnemonic = Wallet.generateMnemonic(128); // 12 words (256 = 24 words)
console.log('SAVE THIS:', mnemonic); // back it up — controls your vault
const wallet = Wallet.fromMnemonic(mnemonic, 'mainnet', undefined, 'zcash');
// Restore from existing mnemonic
// fromMnemonic(mnemonic, network, path?, chain)
// network: 'mainnet' | 'testnet' | 'regtest'
// chain: 'zcash' | 'bitcoin' | 'dogecoin'
const wallet = Wallet.fromMnemonic(
'word1 word2 ... word12',
'mainnet',
undefined, // BIP-32 path — omit for default (m/86'/133'/0'/0/0 for zcash)
'zcash'
);
// Restore from WIF (no renewal key — vault creation will fail)
const wallet = Wallet.fromWIF('L1abc...', 'mainnet', 'zcash');
Instance Methods
// Get your Taproot address (bc1p... on mainnet, bcrt1p... on regtest)
const address = wallet.getAddress();
// Get 32-byte x-only public key
const pubkey = wallet.getXOnlyPubkey(); // Uint8Array
const pubkeyHex = wallet.getXOnlyPubkeyHex(); // string (64 hex chars)
// Derive your vault deposit address (Bitcoin only — Zcash uses client.getVaultAddress())
const vaultAddress = wallet.deriveVaultAddress({
protocolKey: protocolKeyBytes, // 32-byte Uint8Array
timelock: 2016, // Bitcoin escape hatch (2,016 blocks = ~2 weeks)
});
// Sign a message
const signature = await wallet.sign(messageBytes); // 65-byte Uint8Array
// Export mnemonic for backup (keep this secret — never share it)
const mnemonic = wallet.getMnemonic();
TokenContract
A helper class for interacting with NEP-20 tokens.
import { TokenContract } from '@yattacorp/nexus-sdk';
const token = new TokenContract(client, '0x1234...');
// Read token info
const name = await token.name();
const symbol = await token.symbol();
const decimals = await token.decimals();
const totalSupply = await token.totalSupply();
// Check balance
const { balance, balanceDecimal } = await token.balanceOf('bcrt1p...');
// Send tokens
await token.transfer('bcrt1p...', '1000000');
// Approve another address to spend your tokens
await token.approve('0xspender...', '1000000');
// Transfer on behalf of another address (requires prior approval)
await token.transferFrom('0xfrom...', '0xto...', '500000');
Transaction Building
ContractCallBuilder
Build and sign contract transactions manually.
import { ContractCallBuilder } from '@yattacorp/nexus-sdk';
const builder = new ContractCallBuilder(contractId, 'transfer', wallet)
.withArgs('0xto...', '1000')
.gas(100000)
.value(0);
const signedTx = await builder.buildAndSign();
const txHash = await client.sendTransaction(signedTx);
DeploymentBuilder
Build and sign deployment transactions manually.
import { DeploymentBuilder } from '@yattacorp/nexus-sdk';
const builder = new DeploymentBuilder(wallet)
.code(wasmBytes)
.initArgs('TokenName', 'TKN', 18, 1000000)
.gas(2000000);
const signedTx = await builder.buildAndSign();
Privacy: Stealth Addresses
Stealth addresses let you receive funds or contract calls without linking transactions to your identity. Every sender generates a unique one-time address just for you — nobody watching the chain can tell which transactions belong to the same wallet.
See Stealth Addresses for a full explanation of how this works.
Set Up Your Stealth Keypair
import { deriveStealthKeyPair, encodeStealthMetaAddress } from '@yattacorp/nexus-sdk';
// Derive scan + spend keys from your master private key
const stealthKeys = deriveStealthKeyPair(myMasterPrivkey);
// This is your stealth meta-address — safe to publish publicly
// It lets anyone send to you privately
const metaAddress = encodeStealthMetaAddress(stealthKeys);
console.log('My stealth address:', metaAddress);
// e.g. "02abc...def02ghi...jkl" (66 bytes hex = scan pubkey + spend pubkey)
Send to Someone Privately (Sender Side)
import { generateStealthAddress, decodeStealthMetaAddress } from '@yattacorp/nexus-sdk';
// Decode recipient's stealth meta-address
const { scanPubkey, spendPubkey } = decodeStealthMetaAddress(recipientMetaAddress);
// Generate a unique one-time address for this transaction
const { stealthAddress, ephemeralPubkey, viewTag } = generateStealthAddress(
scanPubkey,
spendPubkey
);
// Use stealthAddress as tx.from in your transaction
// Attach ephemeralPubkey and viewTag to the transaction so recipient can find it
Scan for Incoming Transactions (Recipient Side)
import { getMyStealthTransactions, deriveStealthPrivkey } from '@yattacorp/nexus-sdk';
// Fetch all stealth transactions from the node (you don't reveal which are yours)
const allStealthTxs = await client.getStealthTransactions();
// Scan locally — your scan key never leaves your device
const myTxs = getMyStealthTransactions(
allStealthTxs,
stealthKeys.scanPrivkey, // private — stays local
stealthKeys.spendPubkey, // public — used for verification
);
console.log(`Found ${myTxs.length} transactions addressed to me`);
// Derive spending key for each received transaction
for (const tx of myTxs) {
const privkey = deriveStealthPrivkey(stealthKeys.spendPrivkey, tx.sharedSecretScalar);
// Use privkey to sign transactions spending from this stealth address
}
Stealth Contract Calls
Stealth addresses work for contract calls too — not just transfers. NEXUS resolves the stealth address to your real address before contract execution, so the contract sees your real msg.sender. Approvals, ownership checks, and balances all work correctly. The public chain only shows the unlinkable one-time address.
import { generateStealthContractCall } from '@yattacorp/nexus-sdk';
const { stealthAddress, ephemeralPubkey, viewTag } = generateStealthContractCall(
recipientScanPubkey,
recipientSpendPubkey
);
// Build your contract call transaction with stealthAddress as the sender
Privacy: Encrypted Mempool
Encrypt transactions before submitting them to protect against MEV (miners reading your transaction and trading against you) and front-running attacks on AMM swaps.
See Encrypted Mempool for a full explanation.
Encrypt and Submit a Transaction
import {
encryptTransaction,
serializeEncryptedTransaction,
encryptedTxIdHex
} from '@yattacorp/nexus-sdk';
// Step 1: Get the protocol's public encryption key
const { pubkey: protocolPubkeyHex } = await client.getProtocolPubkey();
// Step 2: Build and serialize your transaction normally
const rawTx = serializeTransaction(myTransaction); // Uint8Array
// Step 3: Encrypt it — the mempool will store the encrypted blob
const encrypted = encryptTransaction(rawTx, protocolPubkeyHex);
// Step 4: Serialize the encrypted envelope for submission
const payload = serializeEncryptedTransaction(encrypted);
// Step 5: Submit — the miner decrypts only at inclusion time
await client.sendEncryptedTransaction(signedTx);
// Note: sendTransaction() also encrypts automatically — sendEncryptedTransaction()
// is the low-level API for manual control
// Track your transaction by its encrypted ID
console.log('Transaction ID:', encryptedTxIdHex(encrypted));
What’s Protected
Encrypted mempool protects your transaction content from being read before inclusion. It does not change transaction ordering. For full protection on high-value AMM swaps, combine with private RPC submission (direct to a trusted operator node).
| Attack | Protected? |
|---|
| Sandwich attacks on AMM swaps | ✅ |
| Front-running profitable contract calls | ✅ |
| Transaction content snooping | ✅ |
| Replay attacks | ✅ |
Events
EventListener (WebSocket — Real-Time)
import { EventListener } from '@yattacorp/nexus-sdk';
const listener = new EventListener('ws://localhost:9945');
listener.connect();
// Subscribe to events from a specific contract
const subId = listener.subscribe(
{ contractId: '0x1234...' },
(event) => console.log('New event:', event)
);
// Clean up
listener.unsubscribe(subId);
listener.disconnect();
PollingEventListener (HTTP Fallback)
import { PollingEventListener } from '@yattacorp/nexus-sdk';
const listener = new PollingEventListener(
(filter) => client.getEvents(filter),
5000 // check every 5 seconds
);
listener.start();
const subId = listener.subscribe(filter, callback);
listener.stop();
Types Reference
interface ContractEvent {
contractId: string;
eventName: string;
data: any;
blockHeight: number;
txHash: string;
logIndex: number;
}
interface EventFilter {
contractId?: string;
eventName?: string;
fromBlock?: number;
toBlock?: number;
}
interface SignedTransaction {
tx_id: string;
from: string;
tx_type: TransactionType;
gas_limit: number;
gas_price: number;
nonce: number;
signature: string;
}
Error Handling
try {
await client.callContract(contractId, 'transfer', [to, amount]);
} catch (error) {
if (error.code === 'INSUFFICIENT_BALANCE') {
console.log('Not enough funds');
} else if (error.code === 'INVALID_NONCE') {
console.log('Nonce mismatch — retry');
} else if (error.code === 'GAS_LIMIT_EXCEEDED') {
console.log('Transaction ran out of gas');
} else {
throw error;
}
}