Replace serial per-block RPC calls with binary bulk fetch via getBlocksByHeight (2 RPCs per batch regardless of size). Falls back to parallel JSON fetch with bulk tx retrieval when binary endpoint unavailable. Adaptive batch sizing tracks ms/block trend across batches — doubles when fast, halves when slow, nudges up when stable. Starts at 10 blocks, ramps to 500.
salvium-js
JavaScript library for Salvium cryptocurrency - wallet generation, address handling, RPC clients, key derivation, and cryptographic utilities.
Features
- Wallet Generation - Generate seeds, derive keys, create addresses
- Mnemonic Support - 25-word seed phrases in 12 languages
- Key Derivation - CryptoNote and CARROT key derivation from seeds
- Address Creation - Create all 18 Salvium address types
- Subaddress Generation - Generate CryptoNote and CARROT subaddresses
- Integrated Addresses - Create and parse integrated addresses with payment IDs
- Address Validation - Parse and validate any Salvium address
- Transaction Scanning - Detect owned outputs, decrypt amounts, view tags
- Key Images - Generate and validate key images for spend detection
- Transaction Construction - Pedersen commitments, CLSAG/TCLSAG signatures, serialization
- Bulletproofs+ Range Proofs - Pure JS proof generation AND verification (mobile-friendly)
- RandomX Proof-of-Work - WASM-JIT implementation with official test vectors
- Stratum Mining Client - Connect to mining pools with multi-threaded hashing
- Wallet Class - Unified wallet management with view-only mode support
- UTXO Selection - Multiple strategies (minimize inputs, minimize change, etc.)
- Transaction Builder - High-level buildTransaction() and signTransaction() API
- Transaction Parser - Decode and analyze transaction data
- RPC Clients - Full daemon and wallet RPC implementations
- Signature Verification - Verify message signatures (V1 and V2 formats)
- Cryptographic Primitives - Blake2b, Keccak-256, Ed25519, Base58
- Multi-Network Support - Mainnet, Testnet, Stagenet
- TypeScript Support - Full type definitions included
- Minimal Dependencies - Only @noble/curves and @noble/hashes
Address Types Supported
| Network | Format | Standard | Integrated | Subaddress |
|---|---|---|---|---|
| Mainnet | Legacy | SaLv... | SaLvi... | SaLvs... |
| Mainnet | CARROT | SC1... | SC1i... | SC1s... |
| Testnet | Legacy | SaLvT... | SaLvTi... | SaLvTs... |
| Testnet | CARROT | SC1T... | SC1Ti... | SC1Ts... |
| Stagenet | Legacy | SaLvS... | SaLvSi... | SaLvSs... |
| Stagenet | CARROT | SC1S... | SC1Si... | SC1Ss... |
Installation
npm install salvium-js
Or include directly in browser:
<script type="module">
import salvium from './salvium-js/src/index.js';
</script>
Quick Start: Generate a Wallet
import {
generateSeed,
seedToMnemonic,
deriveKeys,
createAddress,
generateCNSubaddress,
bytesToHex
} from 'salvium-js';
// 1. Generate a cryptographically secure random seed
const seed = generateSeed(); // 32 random bytes
// 2. Convert to 25-word mnemonic for backup
const mnemonic = seedToMnemonic(seed, { language: 'english' });
console.log('Backup these words:', mnemonic);
// 3. Derive wallet keys from seed
const keys = deriveKeys(seed);
// {
// spendSecretKey: Uint8Array(32),
// spendPublicKey: Uint8Array(32),
// viewSecretKey: Uint8Array(32),
// viewPublicKey: Uint8Array(32)
// }
// 4. Create main wallet address
const address = createAddress({
network: 'mainnet',
format: 'legacy',
type: 'standard',
spendPublicKey: keys.spendPublicKey,
viewPublicKey: keys.viewPublicKey
});
console.log('Main address:', address); // SaLv...
// 5. Generate subaddresses
const subaddress = generateCNSubaddress({
network: 'mainnet',
spendPublicKey: keys.spendPublicKey,
viewSecretKey: keys.viewSecretKey,
major: 0, // account index
minor: 1 // address index
});
console.log('Subaddress:', subaddress.address); // SaLvs...
// 6. Display secret keys (keep these safe!)
console.log('Spend secret key:', bytesToHex(keys.spendSecretKey));
console.log('View secret key:', bytesToHex(keys.viewSecretKey));
Restore Wallet from Mnemonic
import { mnemonicToSeed, deriveKeys, createAddress } from 'salvium-js';
const mnemonic = 'abbey ability able about above absent ...'; // 25 words
const result = mnemonicToSeed(mnemonic, { language: 'english' });
if (result.valid) {
const keys = deriveKeys(result.seed);
const address = createAddress({
network: 'mainnet',
format: 'legacy',
type: 'standard',
spendPublicKey: keys.spendPublicKey,
viewPublicKey: keys.viewPublicKey
});
console.log('Restored address:', address);
} else {
console.error('Invalid mnemonic:', result.error);
}
CARROT Address Format
import {
generateSeed,
deriveKeys,
deriveCarrotKeys,
createAddress,
generateCarrotSubaddress,
hexToBytes,
scalarMultPoint
} from 'salvium-js';
const seed = generateSeed();
// Derive CryptoNote keys (for public keys)
const keys = deriveKeys(seed);
// Derive CARROT-specific keys
const carrotKeys = deriveCarrotKeys(seed);
// {
// masterSecret: '...', // hex string
// proveSpendKey: '...', // k_ps
// viewBalanceSecret: '...', // s_vb
// generateImageKey: '...', // k_gi
// viewIncomingKey: '...', // k_vi
// generateAddressSecret: '...' // s_ga
// }
// Create CARROT main address
const carrotAddress = createAddress({
network: 'mainnet',
format: 'carrot',
type: 'standard',
spendPublicKey: keys.spendPublicKey,
viewPublicKey: keys.viewPublicKey
});
console.log('CARROT address:', carrotAddress); // SC1...
// Generate CARROT subaddress
const carrotSub = generateCarrotSubaddress({
network: 'mainnet',
accountSpendPubkey: keys.spendPublicKey,
accountViewPubkey: keys.viewPublicKey,
generateAddressSecret: hexToBytes(carrotKeys.generateAddressSecret),
major: 0,
minor: 1
});
console.log('CARROT subaddress:', carrotSub.address); // SC1s...
Integrated Addresses (with Payment ID)
import {
toIntegratedAddress,
toStandardAddress,
getPaymentId,
generateRandomPaymentId,
createIntegratedAddressWithRandomId,
bytesToHex
} from 'salvium-js';
// Create integrated address with specific payment ID
const integrated = toIntegratedAddress('SaLv...', 'deadbeef12345678');
console.log('Integrated:', integrated); // SaLvi...
// Create with random payment ID
const result = createIntegratedAddressWithRandomId('SaLv...');
console.log('Integrated:', result.address);
console.log('Payment ID:', result.paymentIdHex);
// Extract payment ID from integrated address
const paymentId = getPaymentId(integrated);
console.log('Payment ID:', bytesToHex(paymentId));
// Get standard address from integrated
const standard = toStandardAddress(integrated);
Validate and Parse Addresses
import { isValidAddress, parseAddress, describeAddress } from 'salvium-js';
// Simple validation
if (isValidAddress('SC1...')) {
console.log('Valid!');
}
// Detailed parsing
const info = parseAddress('SC1...');
// {
// valid: true,
// network: 'mainnet',
// format: 'carrot',
// type: 'standard',
// prefix: 'SC1',
// spendPublicKey: Uint8Array(32),
// viewPublicKey: Uint8Array(32),
// paymentId: null,
// error: null
// }
// Human-readable description
console.log(describeAddress('SaLvi...'));
// "Mainnet Legacy integrated (Payment ID: abcd1234...)"
Check Address Properties
import {
isMainnet, isTestnet, isStagenet,
isCarrot, isLegacy,
isStandard, isIntegrated, isSubaddress
} from 'salvium-js';
const addr = 'SC1...';
isMainnet(addr); // true
isCarrot(addr); // true
isStandard(addr); // true
isSubaddress(addr); // false
Mnemonic Seeds
import {
seedToMnemonic,
mnemonicToSeed,
validateMnemonic,
detectLanguage,
getAvailableLanguages
} from 'salvium-js';
// Generate mnemonic from seed
const mnemonic = seedToMnemonic(seed, { language: 'english' });
// 25 words
// Convert mnemonic back to seed
const result = mnemonicToSeed(mnemonic, { language: 'english' });
if (result.valid) {
console.log('Seed:', result.seed);
}
// Auto-detect language
const detected = detectLanguage(mnemonic);
console.log('Language:', detected.language.name);
// Validate without converting
const validation = validateMnemonic(mnemonic, { language: 'auto' });
console.log('Valid:', validation.valid);
// Available languages
console.log(getAvailableLanguages());
// ['english', 'spanish', 'french', 'italian', 'german', 'portuguese',
// 'russian', 'japanese', 'chinese_simplified', 'dutch', 'esperanto', 'lojban']
RPC Clients
Full-featured RPC clients for Salvium daemon and wallet services.
Default Ports (from cryptonote_config.h)
| Service | Mainnet | Testnet | Stagenet |
|---|---|---|---|
| Daemon RPC | 19081 | 29081 | 39081 |
| ZMQ RPC | 19083 | 29083 | 39083 |
| Wallet RPC* | 19083 | 29083 | 39083 |
*Wallet RPC has no default in source - port is user-specified, conventionally daemon+1
Daemon RPC
import { createDaemonRPC } from 'salvium-js/rpc';
const daemon = createDaemonRPC({ url: 'http://localhost:19081' });
// Get node info
const info = await daemon.getInfo();
if (info.success) {
console.log('Height:', info.result.height);
console.log('Network hashrate:', info.result.difficulty / 120);
console.log('Synchronized:', info.result.synchronized);
}
// Get block by height
const block = await daemon.getBlockHeaderByHeight(100000);
// Get transactions
const txs = await daemon.getTransactions(['txhash1', 'txhash2']);
// Get fee estimate
const fee = await daemon.getFeeEstimate();
console.log('Fee per byte:', fee.result.fee);
// Mining
const template = await daemon.getBlockTemplate({
wallet_address: 'SaLv...',
reserve_size: 8
});
Wallet RPC
import { createWalletRPC, PRIORITY } from 'salvium-js/rpc';
const wallet = createWalletRPC({
url: 'http://localhost:19083',
username: 'user',
password: 'pass'
});
// Open wallet
await wallet.openWallet({ filename: 'mywallet', password: 'secret' });
// Get balance (amounts in atomic units, divide by 1e8 for SAL)
const balance = await wallet.getBalance();
if (balance.success) {
console.log('Balance:', balance.result.balance / 1e8, 'SAL');
console.log('Unlocked:', balance.result.unlocked_balance / 1e8, 'SAL');
}
// Get address
const addr = await wallet.getAddress();
console.log('Address:', addr.result.address);
// Send transaction (1 SAL = 100000000 atomic units)
const tx = await wallet.transfer({
destinations: [{ address: 'SaLv...', amount: 100000000 }],
priority: PRIORITY.NORMAL
});
// Get transaction history
const transfers = await wallet.getTransfers({ in: true, out: true });
// Create subaddress
const newAddr = await wallet.createAddress({ account_index: 0 });
// Close wallet
await wallet.closeWallet();
RPC Client Options
const daemon = createDaemonRPC({
url: 'http://localhost:19081',
timeout: 30000, // Request timeout in ms (default: 30000)
retries: 2, // Retry attempts (default: 2)
retryDelay: 1000, // Delay between retries in ms (default: 1000)
username: 'user', // HTTP basic auth username
password: 'pass' // HTTP basic auth password
});
Available Daemon RPC Methods
- Network:
getInfo,getHeight,syncInfo,hardForkInfo,getNetStats,getConnections,getPeerList - Blocks:
getBlockHash,getBlock,getBlockHeaderByHash,getBlockHeaderByHeight,getBlockHeadersRange,getLastBlockHeader - Transactions:
getTransactions,getTransactionPool,sendRawTransaction,relayTx - Outputs:
getOuts,getOutputHistogram,getOutputDistribution,isKeyImageSpent - Mining:
getBlockTemplate,submitBlock,getMinerData,calcPow - Fees:
getFeeEstimate,getBaseFeeEstimate,getCoinbaseTxSum
Available Wallet RPC Methods
- Wallet:
createWallet,openWallet,closeWallet,restoreDeterministicWallet,generateFromKeys - Accounts:
getAccounts,createAccount,labelAccount,getAddress,createAddress - Balance:
getBalance,getTransfers,getTransferByTxid,incomingTransfers - Transfers:
transfer,transferSplit,sweepAll,sweepSingle,sweepDust - Proofs:
getTxKey,checkTxKey,getTxProof,checkTxProof,getReserveProof - Keys:
queryKey,getMnemonic,exportOutputs,importOutputs,exportKeyImages - Signing:
sign,verify,signMultisig,submitMultisig
Transaction Scanning
Detect owned outputs in transactions and decrypt amounts.
import {
generateKeyDerivation,
derivePublicKey,
deriveSecretKey,
checkOutputOwnership,
scanOutput,
ecdhDecode,
deriveViewTag
} from 'salvium-js';
// Given a transaction with tx public key R and your view secret key a:
const derivation = generateKeyDerivation(txPubKey, viewSecretKey);
// Check if output at index n belongs to you
const outputKey = tx.outputs[n].target;
const owned = checkOutputOwnership(derivation, n, spendPublicKey, outputKey);
if (owned) {
// Decrypt the amount
const amount = ecdhDecode(encryptedAmount, derivation, n);
console.log('Received:', amount, 'atomic units');
// Derive the secret key to spend this output
const outputSecretKey = deriveSecretKey(derivation, n, spendSecretKey);
}
// Full output scan with view tag optimization
const result = scanOutput({
derivation,
outputIndex: 0,
outputPublicKey: tx.outputs[0].target,
spendPublicKey,
viewTag: tx.outputs[0].viewTag, // optional
encryptedAmount: tx.outputs[0].encryptedAmount
});
if (result.owned) {
console.log('Amount:', result.amount);
console.log('Output secret:', result.outputSecretKey);
}
Key Images
Generate key images for spend detection (required for full wallet functionality).
import {
hashToPoint,
generateKeyImage,
isValidKeyImage,
exportKeyImages,
importKeyImages
} from 'salvium-js';
// Generate key image for an output you own
// KI = x * H_p(P) where x is your output secret key, P is output public key
const keyImage = generateKeyImage(outputPublicKey, outputSecretKey);
// Validate a key image
if (isValidKeyImage(keyImage)) {
console.log('Valid key image');
}
// Export key images for view-only wallet sync
const exported = exportKeyImages([
{ keyImage: ki1, txHash: 'abc...', outputIndex: 0 },
{ keyImage: ki2, txHash: 'def...', outputIndex: 1 }
]);
// Import key images into a view-only wallet
const keyImageMap = importKeyImages(exported);
// Can now detect which outputs have been spent
Transaction Construction
Build transactions with Pedersen commitments and CLSAG/TCLSAG ring signatures.
import {
// Scalar operations
scAdd, scSub, scMul, scRandom, scInvert,
// Commitments
commit, zeroCommit, genCommitmentMask,
// Output creation
generateOutputKeys, createOutput,
// Signing (CLSAG for standard, TCLSAG for SalviumOne)
clsagSign, clsagVerify,
tclsagSign, tclsagVerify,
// Serialization
serializeTxPrefix, getTxPrefixHash, encodeVarint
} from 'salvium-js';
// Create a transaction output
const txSecretKey = scRandom(); // r - transaction secret key
const output = createOutput(
txSecretKey,
recipientViewPublicKey,
recipientSpendPublicKey,
1000000000n, // 10 SAL in atomic units
0, // output index
false // isSubaddress
);
// Returns: { outputPublicKey, txPublicKey, commitment, encryptedAmount, mask }
// Pedersen commitment: C = mask*G + amount*H
const mask = scRandom();
const commitment = commit(1000000000n, mask);
// Zero commitment for fees (public amount)
const feeCommitment = zeroCommit(10000000n); // 0.1 SAL fee
// CLSAG ring signature (standard RingCT)
const signature = clsagSign(
message, // Pre-MLSAG hash
ring, // Array of public keys (decoys + real)
secretKey, // Your secret key
commitments, // Ring member commitments
maskDiff, // Your mask - pseudo output mask
pseudoCommitment, // Pseudo output commitment
secretIndex // Your position in ring
);
// Verify CLSAG signature
const valid = clsagVerify(message, signature, ring, commitments, pseudoCommitment);
// TCLSAG ring signature (SalviumOne - uses dual generators G and T)
const tclsagSig = tclsagSign(
message, // Pre-MLSAG hash
ring, // Array of public keys
secretKeyX, // Spend secret key component
secretKeyY, // Auxiliary secret key component
commitments, // Ring member commitments
maskDiff, // Commitment mask difference
pseudoCommitment, // Pseudo output commitment
secretIndex // Your position in ring
);
// Returns { sx: [], sy: [], c1, I, D } - dual scalar arrays
// Verify TCLSAG signature
const tclsagValid = tclsagVerify(message, tclsagSig, ring, commitments, pseudoCommitment);
Bulletproofs+ Range Proofs
Generate and verify range proofs that prove transaction amounts are in valid range (0 to 2^64-1) without revealing the actual amounts. 100% pure JavaScript - no WASM required.
import {
// Proof generation
proveRange,
proveRangeMultiple,
randomScalar,
serializeProof,
// Verification
verifyBulletproofPlus,
verifyRangeProof,
parseProof,
initGenerators,
bytesToPoint
} from 'salvium-js';
// === PROOF GENERATION ===
// Generate a range proof for a single amount
const amount = 1000000000n; // 10 SAL in atomic units
const mask = randomScalar(); // Blinding factor (commitment mask)
const proof = proveRange(amount, mask);
// proof = { V, A, A1, B, r1, s1, d1, L, R }
// V[0] is the Pedersen commitment: mask*G + amount*H
// Generate proof for multiple amounts (batched, more efficient)
const amounts = [1000000000n, 500000000n];
const masks = [randomScalar(), randomScalar()];
const batchProof = proveRangeMultiple(amounts, masks);
// Serialize for transmission
const proofBytes = serializeProof(proof);
// === VERIFICATION ===
// Verify a generated proof
const valid = verifyBulletproofPlus(proof.V, proof);
// Verify from serialized bytes (e.g., from a transaction)
const isValid = verifyRangeProof(
tx.rctSig.outPk, // Array of commitment bytes
tx.rctSig.proofBytes // Serialized Bulletproof+ proof
);
// Parse and verify separately for more control
const parsedProof = parseProof(proofBytes);
const verified = verifyBulletproofPlus(commitments, parsedProof);
Performance (pure JavaScript, no WASM):
| Operation | Time |
|---|---|
| Generator init (1024 pts) | ~800ms (one-time, cached) |
| Single proof generation | ~1100ms |
| Proof verification | ~300ms |
| Round-trip (generate + verify) | ~1400ms |
This is fast enough for mobile wallets (React Native on iOS/Android).
// Serialize transaction
const tx = {
version: 2,
unlockTime: 0n,
inputs: [{ amount: 0n, keyOffsets: [100n, 50n], keyImage }],
outputs: [{ amount: 0n, target: output.outputPublicKey, viewTag: 0x42 }],
extra: { txPubKey: output.txPublicKey }
};
const serialized = serializeTxPrefix(tx);
const prefixHash = getTxPrefixHash(tx);
RandomX Proof-of-Work
WASM-JIT accelerated RandomX implementation for mining and verification. Validated against official test vectors.
import {
RandomXContext,
rxSlowHash,
verifyHash,
checkDifficulty,
mine
} from 'salvium-js';
// === BASIC HASHING ===
// One-shot hash (creates temporary context)
const hash = await rxSlowHash('block header hash', 'block blob');
// === REUSABLE CONTEXT (Recommended) ===
// For multiple hashes with same key, reuse context
const ctx = new RandomXContext();
await ctx.init('test key 000'); // Initialize 256MB cache
const hash1 = ctx.hash('This is a test');
const hash2 = ctx.hash('Another input'); // Much faster, reuses cache
// Verify against official test vectors
console.log(ctx.hashHex('This is a test'));
// '639183aae1bf4c9a35884cb46b09cad9175f04efd7684e7262a0ac1c2f0b4e3f'
// === VERIFICATION ===
// Verify a hash
const isValid = await verifyHash(key, input, expectedHash);
// Check if hash meets difficulty target
const meetsDifficulty = checkDifficulty(hash, difficulty);
// === MINING ===
// Find nonce that meets difficulty
const result = await mine(
key, // Cache key (prev block hash)
blockBlob, // Block blob with nonce placeholder
nonceOffset, // Byte offset of nonce in blob
difficulty, // Target difficulty (BigInt)
maxNonce // Max nonce to try (default: 2^32)
);
if (result) {
console.log('Found nonce:', result.nonce);
console.log('Hash:', result.hash);
}
Modes:
- Light mode (default) - 256MB cache per thread, suitable for verification and mining
- Full mode - 2GB shared dataset, faster hashing for dedicated miners
Multi-threaded Mining:
import { RandomXWorkerPool, getAvailableCores } from 'salvium-js';
const pool = new RandomXWorkerPool(getAvailableCores());
await pool.init(key);
const hash = await pool.hash(input);
pool.terminate();
Verify Message Signatures
import { verifySignature, parseSignature } from 'salvium-js';
// Verify a signature created with `sign` command in salvium-wallet-cli
const result = verifySignature(
'Hello, World!', // The original message
'SC1...', // The signer's address
'SigV2...' // The signature string
);
console.log(result);
// {
// valid: true,
// version: 2,
// keyType: 'spend', // or 'view'
// error: null
// }
Cryptographic Primitives
import { keccak256, keccak256Hex, blake2b, blake2bHex } from 'salvium-js';
// Keccak-256 (CryptoNote fast hash)
const hash = keccak256('hello'); // Uint8Array(32)
const hex = keccak256Hex('hello'); // "1c8aff950685..."
// Blake2b (CARROT key derivation)
const b2hash = blake2b(data, 32); // 32-byte output
const b2keyed = blake2b(data, 32, key); // Keyed hash (MAC)
API Reference
Wallet Generation
| Function | Description |
|---|---|
generateSeed() |
Generate 32-byte cryptographically secure random seed |
deriveKeys(seed) |
Derive CryptoNote keys from seed |
deriveCarrotKeys(seed) |
Derive CARROT keys from seed |
createAddress(options) |
Create address from public keys |
Mnemonic Functions
| Function | Description |
|---|---|
seedToMnemonic(seed, options) |
Convert 32-byte seed to 25-word mnemonic |
mnemonicToSeed(mnemonic, options) |
Convert mnemonic to seed |
validateMnemonic(mnemonic, options) |
Validate mnemonic without converting |
detectLanguage(mnemonic) |
Auto-detect mnemonic language |
getAvailableLanguages() |
List supported languages |
Subaddress Functions
| Function | Description |
|---|---|
generateCNSubaddress(options) |
Generate CryptoNote subaddress |
generateCarrotSubaddress(options) |
Generate CARROT subaddress |
generateRandomPaymentId() |
Generate 8-byte random payment ID |
Address Functions
| Function | Description |
|---|---|
parseAddress(addr) |
Parse address, returns detailed info object |
isValidAddress(addr) |
Returns true if valid |
isMainnet(addr) |
Check if mainnet address |
isTestnet(addr) |
Check if testnet address |
isStagenet(addr) |
Check if stagenet address |
isCarrot(addr) |
Check if CARROT format |
isLegacy(addr) |
Check if legacy CryptoNote format |
isStandard(addr) |
Check if standard address |
isIntegrated(addr) |
Check if integrated address |
isSubaddress(addr) |
Check if subaddress |
getSpendPublicKey(addr) |
Extract 32-byte spend public key |
getViewPublicKey(addr) |
Extract 32-byte view public key |
getPaymentId(addr) |
Extract 8-byte payment ID (integrated only) |
toIntegratedAddress(addr, paymentId) |
Create integrated from standard |
toStandardAddress(addr) |
Extract standard from integrated |
describeAddress(addr) |
Human-readable description |
Transaction Scanning Functions
| Function | Description |
|---|---|
generateKeyDerivation(pubKey, secretKey) |
Compute ECDH shared secret D = 8sP |
derivationToScalar(derivation, outputIndex) |
Derive scalar from derivation |
derivePublicKey(derivation, index, spendPub) |
Derive one-time public key |
deriveSecretKey(derivation, index, spendSec) |
Derive one-time secret key |
deriveViewTag(derivation, index) |
Compute view tag for fast filtering |
checkOutputOwnership(derivation, index, spendPub, outputPub) |
Check if output belongs to wallet |
scanOutput(params) |
Full output scan with amount decryption |
ecdhDecode(encrypted, derivation, index) |
Decrypt amount |
ecdhEncode(amount, derivation, index) |
Encrypt amount |
Key Image Functions
| Function | Description |
|---|---|
hashToPoint(data) |
Hash to curve point (Elligator 2) |
generateKeyImage(outputPub, outputSec) |
Generate key image KI = x*H_p(P) |
isValidKeyImage(keyImage) |
Validate key image is on curve |
exportKeyImages(outputs) |
Format key images for export |
importKeyImages(data) |
Create lookup map from exported data |
Transaction Construction Functions
| Function | Description |
|---|---|
scAdd(a, b) |
Add scalars mod L |
scSub(a, b) |
Subtract scalars mod L |
scMul(a, b) |
Multiply scalars mod L |
scRandom() |
Generate random scalar |
scInvert(a) |
Compute modular inverse |
commit(amount, mask) |
Create Pedersen commitment |
zeroCommit(amount) |
Create commitment with zero mask |
generateOutputKeys(txSec, viewPub, spendPub, index) |
Generate one-time output keys |
createOutput(txSec, viewPub, spendPub, amount, index) |
Create full transaction output |
clsagSign(message, ring, secret, commitments, mask, pseudo, index) |
Generate CLSAG signature |
clsagVerify(message, sig, ring, commitments, pseudo) |
Verify CLSAG signature |
tclsagSign(message, ring, secretX, secretY, commitments, mask, pseudo, index) |
Generate TCLSAG signature (SalviumOne) |
tclsagVerify(message, sig, ring, commitments, pseudo) |
Verify TCLSAG signature |
serializeTxPrefix(tx) |
Serialize transaction prefix |
getTxPrefixHash(tx) |
Compute transaction prefix hash |
encodeVarint(value) |
Encode integer as varint |
decodeVarint(bytes, offset) |
Decode varint from bytes |
Bulletproofs+ Functions
| Function | Description |
|---|---|
proveRange(amount, mask) |
Generate range proof for single amount |
proveRangeMultiple(amounts, masks) |
Generate batched range proof for multiple amounts |
randomScalar() |
Generate cryptographically secure random scalar |
serializeProof(proof) |
Serialize proof to bytes for transmission |
verifyBulletproofPlus(V, proof) |
Verify single range proof |
verifyBulletproofPlusBatch(proofs) |
Batch verify multiple proofs |
verifyRangeProof(commitments, proofBytes) |
Verify from raw bytes |
initGenerators(n) |
Initialize Gi/Hi generators (cached) |
parseProof(proofBytes) |
Parse proof from serialized bytes |
multiScalarMul(scalars, points) |
Multiscalar multiplication |
bytesToPoint(bytes) |
Decode compressed point |
bytesToScalar(bytes) |
Decode scalar from bytes |
RandomX Functions
| Function | Description |
|---|---|
RandomXContext |
Reusable context class for repeated hashing with same key |
rxSlowHash(key, input) |
One-shot RandomX hash computation |
randomxHash(key, input) |
Alias for rxSlowHash |
verifyHash(key, input, expected) |
Verify RandomX hash matches expected |
checkDifficulty(hash, difficulty) |
Check if hash meets difficulty target |
mine(key, blob, nonceOffset, difficulty, maxNonce, onProgress) |
Find nonce meeting difficulty |
calculateCommitment(input, hashIn) |
Calculate hash commitment |
RandomXCache |
Cache class for Argon2d-initialized memory |
initDatasetItem(cache, itemNumber) |
Generate single dataset item from cache |
RandomXNative |
WASM-JIT RandomX context (vendored randomx.js) |
Blake2Generator |
Pseudo-random byte generator using Blake2b |
generateSuperscalar(gen) |
Generate superscalar program |
executeSuperscalar(registers, program) |
Execute superscalar program on registers |
reciprocal(divisor) |
Compute reciprocal for IMUL_RCP instruction |
argon2d(password, salt, tCost, mCost, parallelism, outLen) |
Argon2d hash function |
argon2InitCache(key) |
Initialize RandomX cache with Argon2d |
Wallet Functions
| Function | Description |
|---|---|
Wallet |
Wallet class with full and view-only modes |
createWallet(options?) |
Create new wallet with mnemonic |
restoreWallet(mnemonic, options?) |
Restore wallet from mnemonic |
createViewOnlyWallet(options) |
Create view-only wallet from keys |
wallet.getAddress() |
Get main wallet address |
wallet.getSubaddress(major, minor) |
Generate subaddress |
wallet.getBalance() |
Get balance (total, unlocked, locked) |
wallet.canSign() |
Check if wallet can sign transactions |
wallet.canScan() |
Check if wallet can scan for outputs |
wallet.toJSON(includeSecrets?) |
Serialize wallet to JSON |
Wallet.fromJSON(json) |
Restore wallet from JSON |
UTXO Selection Functions
| Function | Description |
|---|---|
selectUTXOs(utxos, amount, options?) |
Select UTXOs for transaction |
UTXO_STRATEGY.MINIMIZE_INPUTS |
Strategy to minimize number of inputs |
UTXO_STRATEGY.MINIMIZE_CHANGE |
Strategy to minimize change amount |
UTXO_STRATEGY.OLDEST_FIRST |
Strategy to spend oldest UTXOs first |
UTXO_STRATEGY.NEWEST_FIRST |
Strategy to spend newest UTXOs first |
UTXO_STRATEGY.RANDOM |
Random UTXO selection |
Transaction Builder Functions
| Function | Description |
|---|---|
buildTransaction(options) |
Build complete transaction with signatures |
signTransaction(unsignedTx, spendSecretKey) |
Sign an unsigned transaction |
prepareInputs(utxos, viewSecretKey) |
Prepare inputs with ring members |
estimateTransactionFee(numInputs, numOutputs, feePerByte?) |
Estimate transaction fee |
validateTransaction(tx) |
Validate transaction structure |
serializeTransaction(tx) |
Serialize transaction for broadcast |
Transaction Parser Functions
| Function | Description |
|---|---|
parseTransaction(txData) |
Parse raw transaction bytes/hex |
parseExtra(extra) |
Parse transaction extra field |
extractTxPubKey(extra) |
Extract transaction public key |
extractPaymentId(extra) |
Extract payment ID from extra |
decodeAmount(encrypted, derivation, index) |
Decrypt encrypted amount |
summarizeTransaction(tx) |
Get transaction summary |
getTransactionHashFromParsed(tx) |
Compute hash from parsed transaction |
Utility Functions
| Function | Description |
|---|---|
bytesToHex(bytes) |
Convert Uint8Array to hex string |
hexToBytes(hex) |
Convert hex string to Uint8Array |
keccak256(data) |
Keccak-256 hash, returns Uint8Array |
keccak256Hex(data) |
Keccak-256 hash, returns hex string |
blake2b(data, outlen, key?) |
Blake2b hash with optional key |
Wallet Class
Unified wallet management with full and view-only modes.
import {
Wallet,
createWallet,
restoreWallet,
createViewOnlyWallet
} from 'salvium-js';
// Create a new wallet
const { wallet, mnemonic, seed } = createWallet({ network: 'mainnet' });
console.log('Backup:', mnemonic); // 25 words
console.log('Address:', wallet.getAddress());
// Restore from mnemonic
const restored = restoreWallet('abbey ability able about...');
// Create view-only wallet (can scan, cannot spend)
const viewOnly = createViewOnlyWallet({
network: 'mainnet',
viewSecretKey: '...',
spendPublicKey: '...'
});
console.log('Can scan:', viewOnly.canScan()); // true
console.log('Can sign:', viewOnly.canSign()); // false
// Generate subaddresses
const sub = wallet.getSubaddress(0, 1); // account 0, index 1
// Serialize/deserialize
const json = wallet.toJSON();
const loaded = Wallet.fromJSON(json);
UTXO Selection
Multiple strategies for selecting transaction inputs.
import { selectUTXOs, UTXO_STRATEGY } from 'salvium-js';
const utxos = [
{ txHash: 'abc...', outputIndex: 0, amount: 1000000000n, publicKey: ... },
{ txHash: 'def...', outputIndex: 1, amount: 500000000n, publicKey: ... },
// ...
];
const result = selectUTXOs(utxos, 800000000n, {
strategy: UTXO_STRATEGY.MINIMIZE_INPUTS, // or MINIMIZE_CHANGE, OLDEST_FIRST, etc.
feePerByte: 1000n
});
console.log('Selected:', result.selectedUTXOs.length);
console.log('Total:', result.totalAmount);
console.log('Change:', result.changeAmount);
console.log('Fee:', result.estimatedFee);
Transaction Builder
High-level API for building and signing transactions.
import { buildTransaction, signTransaction, validateTransaction } from 'salvium-js';
// Build a transaction
const tx = await buildTransaction({
utxos: myUTXOs,
destinations: [
{ address: 'SaLv...', amount: 1000000000n }
],
changeAddress: myChangeAddress,
viewSecretKey: keys.viewSecretKey,
spendSecretKey: keys.spendSecretKey,
ringSize: 16
});
console.log('TX Hash:', tx.txHash);
console.log('Fee:', tx.fee);
// Validate before broadcast
const validation = validateTransaction(tx.tx);
if (!validation.valid) {
console.error('Errors:', validation.errors);
}
Transaction Parser
Decode and analyze transaction data.
import {
parseTransaction,
parseExtra,
extractTxPubKey,
extractPaymentId,
summarizeTransaction
} from 'salvium-js';
// Parse raw transaction
const tx = parseTransaction(txHex);
// Get transaction summary
const summary = summarizeTransaction(tx);
console.log('Hash:', summary.hash);
console.log('Inputs:', summary.numInputs);
console.log('Outputs:', summary.numOutputs);
console.log('Coinbase:', summary.isCoinbase);
console.log('Key images:', summary.keyImages);
// Extract extra fields
const extra = parseExtra(tx.extra);
const txPubKey = extractTxPubKey(extra);
const paymentId = extractPaymentId(extra);
Stratum Mining
Connect to mining pools with the stratum protocol.
import { createMiner } from 'salvium-js';
const miner = createMiner({
host: 'pool.example.com',
port: 3333,
wallet: 'SaLv...',
password: 'x',
threads: 4
});
miner.on('hashrate', (rate) => console.log(`${rate} H/s`));
miner.on('share', (accepted) => console.log(accepted ? 'Share accepted' : 'Share rejected'));
await miner.start();
// ... mining ...
miner.stop();
Testing
# Run all tests
bun test/all.js
# Run with integration tests (requires running daemon)
bun test/all.js --integration
# Run against specific daemon
bun test/all.js --integration http://localhost:19081
Test coverage: 19 test suites including CLSAG/TCLSAG signing and official RandomX test vectors.
Contributing
Contributions welcome! Please read the Salvium source code for reference: https://github.com/salvium/salvium