@yattacorp/nexus-sdk
v1.0.3
Published
TypeScript SDK for NEXUS Protocol — deploy and call WASM smart contracts on Zcash, Bitcoin, and Dogecoin
Maintainers
Readme
@yattacorp/nexus-sdk
TypeScript SDK for NEXUS Protocol — deploy and call WASM smart contracts on Zcash (live), Bitcoin, and Dogecoin (coming soon).
Installation
npm install @yattacorp/nexus-sdkWhy You Must Use a Mnemonic
Always create your wallet with a mnemonic phrase — never from a raw private key alone.
NEXUS vaults use a 3-path security model: your key, a renewal key, and the protocol key. The renewal key is deterministically derived from your mnemonic (BIP-32 path m/86'/133'/0'/1/0). It is required every time you call getVaultAddress(), and it is the key that lets you reclaim your ZEC unilaterally if NEXUS is ever unavailable.
Without a mnemonic, renewal key derivation fails and vault creation is blocked. There is no workaround.
Back up your mnemonic phrase. Losing it means losing the ability to derive your renewal key, which means losing access to the emergency escape path of your vault.
Quick Start
import { NexusClient, Wallet } from '@yattacorp/nexus-sdk';
// First time: generate a mnemonic and save it securely
const mnemonic = Wallet.generateMnemonic(); // 12-word BIP-39 phrase
console.log('SAVE THIS:', mnemonic); // back it up — it controls your vault
const wallet = Wallet.fromMnemonic(mnemonic, 'mainnet', undefined, 'zcash');
// Returning user: restore from saved mnemonic
const wallet = Wallet.fromMnemonic(process.env.MNEMONIC!, 'mainnet', undefined, 'zcash');
// Connect to NEXUS — pass wallet directly in constructor (no separate connect() call)
const client = new NexusClient(
{ rpcUrl: 'https://api.yattacorp.xyz', network: 'mainnet', chain: 'zcash' },
wallet
);
const height = await client.getBlockHeight();
console.log('Block height:', height);Network values:
'mainnet'(live Zcash),'regtest'(local dev). Pass the same value to bothWallet.fromMnemonicandNexusClientconfig.
Wallet
import { Wallet } from '@yattacorp/nexus-sdk';
// 1. Generate a fresh wallet (mnemonic-based — required for vaults)
const mnemonic = Wallet.generateMnemonic(); // returns 12-word string
const wallet = Wallet.fromMnemonic(mnemonic, 'mainnet', undefined, 'zcash');
// 2. Restore from mnemonic
const wallet = Wallet.fromMnemonic(
'word1 word2 ... word12',
'mainnet', // 'mainnet' | 'testnet' | 'regtest'
undefined, // BIP-32 path — omit to use default (m/86'/133'/0'/0/0 for zcash)
'zcash' // 'zcash' | 'bitcoin' | 'dogecoin'
);
// 3. Restore from WIF private key (no renewal key — vault creation will fail)
const wallet = Wallet.fromWIF('L1abc...', 'mainnet', 'zcash');
// Key exports
wallet.getMnemonic(); // string | undefined — only if created with mnemonic
wallet.hasMnemonic(); // boolean — false means vault creation is blocked
wallet.getPublicKeyHex(); // 33-byte compressed pubkey as hex (66 chars)
wallet.getXOnlyPubkeyHex(); // 32-byte x-only pubkey as hex (64 chars)
wallet.getCompressedPubkey(); // Uint8Array (33 bytes)
wallet.getNexusAddress(); // your NEXUS address — set by node after first vault call
wallet.exportWIF(); // WIF-encoded private key string
// Signing
const sig = await wallet.sign(messageBytes); // Promise<Uint8Array> — 65-byte ECDSA (SHA256(msg))
// Renewal key derivation (requires mnemonic — used internally by getVaultAddress)
const renewalPubkey = wallet.deriveRenewalKey(); // Uint8Array (33 bytes)
const renewalPrivkey = wallet.deriveRenewalPrivateKey(); // Uint8Array (32 bytes)Connect to NEXUS
NexusClient accepts a Wallet instance, a mnemonic string, or a WIF string as the second argument — no separate connect() call needed.
import { NexusClient, Wallet } from '@yattacorp/nexus-sdk';
const wallet = Wallet.fromMnemonic(process.env.MNEMONIC!, 'mainnet', undefined, 'zcash');
// Option A: pass Wallet instance
const client = new NexusClient(
{ rpcUrl: 'https://api.yattacorp.xyz', network: 'mainnet', chain: 'zcash', timeout: 30000 },
wallet
);
// Option B: pass mnemonic string directly (client derives wallet internally)
const client = new NexusClient(
{ rpcUrl: 'https://api.yattacorp.xyz', network: 'mainnet', chain: 'zcash' },
'word1 word2 ... word12'
);Vault Deposits and Withdrawals
Every user gets a personal vault address on Zcash. Deposit ZEC to receive vZEC on NEXUS.
Vault address derivation requires a mnemonic-based wallet. Internally, getVaultAddress() derives your renewal key from the mnemonic and calls nexus_deriveVaultFromPubkey on the node with three keys: your pubkey, your renewal pubkey, and your renewal private key. The node registers the vault and returns your NEXUS address and Zcash deposit address.
// Step 1: Get your Zcash vault deposit address
// Returns: { vaultAddress, nexusAddress, redeemScriptHex, protocolKey, timelock }
const vault = await client.getVaultAddress(); // default timelock: 16,128 blocks (~2 weeks)
console.log('Send ZEC to:', vault.vaultAddress);
// regtest → "tmXxx..." mainnet → "t1xxx..."
console.log('Your NEXUS address:', vault.l2Address);# Step 2: Send ZEC to the vault address
# regtest / local testing:
zcash-cli sendtoaddress "<vault.vaultAddress>" 1.0
# mainnet: use any Zcash-compatible wallet (ZecWallet, Nighthawk, zcashd)// Step 3: Wait ~10 confirmations (~12.5 min on mainnet), then check balance
// Returns zatoshi as number (1 ZEC = 100,000,000 zatoshi)
const balance = await client.getBalance();
console.log('vZEC balance (zatoshi):', balance);
console.log('vZEC balance (ZEC):', balance / 1e8);
// Step 4: Withdraw vZEC back to Zcash
// Returns: { success, txid, status, path, confirmed? }
const result = await client.withdrawFunds(
't1abc...', // destination Zcash t-address (mainnet) or 'tm...' (regtest)
10_000_000, // amount in zatoshi (0.1 ZEC)
{ waitForConfirmation: true }
);
console.log('Withdrawal txid:', result.txid);
console.log('Status:', result.status); // 'AwaitingUserSignature' | 'Broadcast' | 'Queued'Deploy a Contract
import { NexusClient } from '@yattacorp/nexus-sdk';
import { readFileSync } from 'fs';
const wasm = readFileSync('target/wasm32-unknown-unknown/release/my_contract.wasm');
// Returns: { contractId, txHash, gasUsed, blockHeight }
const result = await client.deployContract(wasm);
console.log('Contract deployed at:', result.contractId);
console.log('Gas used:', result.gasUsed);Call a Contract
// State-changing call — goes through encrypted mempool, mines on-chain
// Returns: { success, txHash, result, error, gasUsed, events, logs }
const result = await client.callContract(
contractId, // contract address hex
'increment', // function name
[], // args array
1_000_000 // optional gas limit
);
console.log('Success:', result.success);
console.log('Return value:', result.result);
// Read-only query — no transaction, no gas cost
// Returns the raw hex return value from the contract
const count = await client.queryContract(contractId, 'get_count', []);
// Simulate a call (read-only but needs wallet for signing the sim tx)
const sim = await client.simulateContractCall(contractId, 'increment', []);Transfer vZEC
// Transfer vZEC to another NEXUS user.
// 'to' can be: hex pubkey (64 chars), Zcash t-address, or Bitcoin P2TR address.
// Stealth meta-address registration is auto-handled before first transfer.
// Returns txHash string
const txHash = await client.transfer(
'tm1abc...', // recipient address or x-only pubkey hex
50_000_000 // amount in zatoshi (0.5 ZEC)
);
console.log('Transfer tx:', txHash);Stealth Calls (Privacy-Preserving)
The caller's real address is hidden behind a one-time stealth address. The contract still sees the real msg_sender (balances, ownership, approvals all work correctly) — only on-chain observers see the unlinkable one-time address.
// Stealth contract call — hides caller identity on-chain
// Returns same ContractCallResult as callContract()
const result = await client.stealthCallContract(
contractId,
'transfer',
[recipientAddress, '1000000']
);
// Stealth transfer via stealth meta-address (encoded scan + spend pubkeys)
// Returns: { txHash, ephemeralPubkey, viewTag }
const result = await client.stealthTransfer(
stealthMetaAddress, // encoded stealth meta-address string
500_000 // amount in zatoshi
);NEP-20 Token Contracts
// Deploy a NEP-20 token (use nexus-contract-template for the WASM)
const deploy = await client.deployContract(nep20Wasm, [
'My Token', // name (init arg)
'MTK', // symbol
18, // decimals
'1000000000000000000000000' // initial supply
]);
const tokenId = deploy.contractId;
// Call token methods
await client.callContract(tokenId, 'transfer', [recipient, '1000']);
const balance = await client.queryContract(tokenId, 'balanceOf', [address]);
const name = await client.queryContract(tokenId, 'name', []);
const supply = await client.queryContract(tokenId, 'totalSupply', []);Event Subscription
// Historical events with pagination
const events = await client.getEvents(
{ contractId, eventName: 'Transfer', fromBlock: 1000, toBlock: 2000 },
100, // limit (default 100)
0 // offset (default 0)
);
// Real-time via WebSocket
const wsClient = new NexusClient(
{ rpcUrl: 'https://api.yattacorp.xyz', wsUrl: 'wss://api.yattacorp.xyz', network: 'mainnet' },
wallet
);
const subId = wsClient.subscribeToEvents({ contractId, eventName: 'Transfer' }, (e) => {
console.log('Transfer:', e.data);
});
wsClient.unsubscribeFromEvents(subId);API Reference
NexusClient
new NexusClient(config: NexusConfig, wallet?: Wallet | string | Uint8Array)NexusConfig: { rpcUrl, network, chain, timeout?, rpcUrls?, wsUrl?, maxRequestsPerSecond? }
network:'mainnet'|'testnet'|'regtest'(default:'regtest')chain:'zcash'|'bitcoin'|'dogecoin'(default:'zcash')
| Method | Params | Returns | Description |
|--------|--------|---------|-------------|
| getBlockHeight() | — | Promise<number> | Current NEXUS block height |
| getBlock(height) | number | Promise<BlockInfo> | Block by height |
| getTransaction(hash) | string | Promise<any> | Transaction details |
| getBalance(address?) | string? | Promise<number> | vZEC balance in zatoshi |
| getVaultAddress(timelock?) | number? (default 16128) | Promise<{ vaultAddress, l2Address, redeemScriptHex, protocolKey, timelock }> | Zcash vault deposit address — requires mnemonic wallet |
| withdrawFunds(to, amount, opts?) | string, number, { waitForConfirmation?, confirmations?, timeout? }? | Promise<{ success, txid, status, path, confirmed? }> | Withdraw vZEC to Zcash |
| transfer(to, amount, opts?) | string, number\|bigint, { gas?, gasPrice? }? | Promise<string> | Transfer vZEC on NEXUS (returns txHash) |
| deployContract(wasm, initArgs?, gas?) | Uint8Array, any[]?, number? | Promise<{ contractId, txHash, gasUsed, blockHeight }> | Deploy a WASM contract |
| callContract(id, fn, args?, gas?) | string, string, any[]?, number? | Promise<ContractCallResult> | State-changing contract call |
| queryContract(id, fn, args?) | string, string, any[]? | Promise<T> | Read-only query (no gas) |
| simulateContractCall(id, fn, args?, gas?) | string, string, any[]?, number? | Promise<ContractCallResult> | Simulate call with wallet (no state change) |
| stealthCallContract(id, fn, args?, opts?) | string, string, any[]? | Promise<ContractCallResult> | Privacy-preserving contract call |
| stealthTransfer(metaAddr, amount, opts?) | string, number\|bigint | Promise<{ txHash, ephemeralPubkey, viewTag }> | Transfer via stealth meta-address |
| estimateGas(tx) | any | Promise<number> | Estimate gas for a transaction |
| waitForTransaction(hash, confs?, timeout?) | string, number?, number? | Promise<any> | Wait for confirmation |
| resolveAddress(address) | string | Promise<string> | Resolve t-addr/P2TR → hex pubkey |
| getEvents(filter, limit?, offset?) | EventFilter, number?, number? | Promise<ContractEvent[]> | Historical events |
| subscribeToEvents(filter, cb) | EventFilter, fn | string | Real-time subscription (returns sub ID) |
| unsubscribeFromEvents(id) | string | void | Cancel subscription |
| getNodeInfo() | — | Promise<{ chain, network, protocol_public_key, ... }> | Node metadata |
| registerStealthMetaAddress() | — | Promise<void> | Register stealth keys (auto-called before first transfer) |
| rawRpcCall(method, params?) | string, any[]? | Promise<T> | Raw JSON-RPC call |
| disconnect() | — | void | Cleanup WebSocket/event listeners |
Wallet
| Method | Params | Returns | Description |
|--------|--------|---------|-------------|
| Wallet.generateMnemonic(strength?) | 128 \| 256 (default 128) | string | Generate a new BIP-39 mnemonic phrase |
| Wallet.fromMnemonic(m, network, path?, chain) | string, NetworkType, string?, string | Wallet | Restore wallet from mnemonic |
| Wallet.fromWIF(wif, network, chain) | string, NetworkType, string | Wallet | Restore from WIF — no renewal key, vault blocked |
| new Wallet(undefined, network, chain) | NetworkType, string | Wallet | Generate random wallet with mnemonic |
| getMnemonic() | — | string \| undefined | BIP-39 mnemonic (undefined if WIF/raw import) |
| hasMnemonic() | — | boolean | false = vault creation blocked |
| getNexusAddress() | — | string | Your NEXUS address (set by node on first vault call) |
| getAddress() | — | string | Alias for getNexusAddress() |
| getPublicKeyHex() | — | string | 33-byte compressed pubkey (66 hex chars) |
| getXOnlyPubkeyHex() | — | string | 32-byte x-only pubkey (64 hex chars) |
| getCompressedPubkey() | — | Uint8Array | 33-byte compressed pubkey |
| exportWIF() | — | string | WIF-encoded private key |
| exportPrivateKeyHex() | — | string | Raw private key as hex |
| sign(message) | Uint8Array | Promise<Uint8Array> | ECDSA sign SHA256(message) → 65-byte sig |
| signRawHash(sighash) | Uint8Array (32 bytes) | Promise<string> | Sign raw 32-byte hash → DER hex with SIGHASH_ALL |
| deriveRenewalKey() | — | Uint8Array | Renewal pubkey (33 bytes) — requires mnemonic |
| deriveRenewalPrivateKey() | — | Uint8Array | Renewal privkey (32 bytes) — requires mnemonic |
| setL2Address(addr) | string | void | Set node-provided NEXUS address (called internally) |
| getNetwork() | — | NetworkType | 'mainnet' \| 'testnet' \| 'regtest' |
| getChain() | — | string | 'zcash' \| 'bitcoin' \| 'dogecoin' |
Building Contracts
Use the nexus-contract-template to write and build WASM contracts:
git clone https://github.com/yattacorp/nexus-contract-template
cd nexus-contract-template
cargo xtask buildRequirements
- Node.js 18+
- For contract builds: Rust +
wasm32-unknown-unknowntarget (rustup target add wasm32-unknown-unknown)
Links
License
MIT
