VM31 UTXO Vault (SDK)
The VM31 vault is Obelysk's second privacy track. Instead of ElGamal-encrypted balances, it uses a UTXO model with Poseidon2 hashing over the Mersenne-31 (M31) field. This provides stronger correlation resistance by splitting deposits into fixed denominations, making all UTXOs of the same denomination indistinguishable.
How It Works
When you deposit into the VM31 vault, your amount is split into standard denominations (like physical cash). Each denomination becomes a UTXO committed to a Merkle tree using Poseidon2-M31 hashing. Withdrawals and transfers consume UTXOs by revealing nullifiers, without linking back to the original deposit.
A relayer service processes VM31 operations. Your transaction data is encrypted with ECIES before being sent to the relayer, so even the relayer cannot see the details of your operation.
Deposit
const result = await obelysk.vm31.deposit({
token: 'eth',
amount: '1.0',
});
// Save all vault notes --- each represents a UTXO
for (const note of result.notes) {
console.log(`UTXO: ${note.denomination} ${note.token}`);
console.log(` Secret: ${note.secret}`);
console.log(` Nullifier: ${note.nullifier}`);
}
Save your vault notes securely. Each note is needed to spend the corresponding UTXO.
Withdraw
const result = await obelysk.vm31.withdraw({
token: 'eth',
amount: '0.5',
notes: savedNotes, // The UTXO notes to spend
recipient: '0xYOUR_ADDRESS',
});
console.log('Batch ID:', result.batchId);
console.log('Status:', result.status);
Transfer Between UTXOs
Transfer UTXOs to a new owner without withdrawing from the vault. This creates new UTXOs for the recipient while nullifying the old ones.
const result = await obelysk.vm31.transfer({
token: 'sage',
amount: '1000',
notes: savedNotes,
recipientPublicKey: '0xRECIPIENT_PUB_KEY',
});
console.log('Transfer batch:', result.batchId);
Check Batch Status
VM31 operations are processed in batches by the relayer. You can poll for completion.
const status = await obelysk.vm31.getBatchStatus(result.batchId);
console.log('Status:', status.status); // 'pending' | 'confirmed' | 'failed'
console.log('Tx hash:', status.transactionHash);
Denomination Splitting
The SDK provides a helper to split any amount into valid denominations. Each token has a predefined set of denominations.
import { splitIntoDenominations, getDenominations } from '@bitsage/sdk/obelysk';
// See available denominations for ETH
const denoms = getDenominations('eth');
console.log('ETH denominations:', denoms);
// Split 1.5 ETH into standard denominations
const splits = splitIntoDenominations('1.5', 'eth');
for (const s of splits) {
console.log(`${s.count}x ${s.denomination} ETH`);
}
Denomination validation ensures you only create UTXOs with amounts that match the pool's accepted set, preserving the anonymity set.
Bridge to Confidential Transfer
The VM31Bridge connects the UTXO track (Track 2) with the ElGamal-encrypted balance track (Track 1). You can bridge VM31 withdrawals into ConfidentialTransfer balances and vice versa.
// Bridge from VM31 vault to encrypted balance
const bridgeResult = await obelysk.bridge.bridgeToConfidential({
token: 'eth',
amount: '0.5',
notes: savedNotes,
});
console.log('Bridged to CT balance:', bridgeResult.transactionHash);
This is useful when you want to move funds between the two privacy tracks --- for example, depositing via VM31 for maximum correlation resistance, then bridging to an encrypted balance for use in shielded swaps or confidential transfers.
Supported Tokens
| Token | Asset ID | Denominations |
|---|---|---|
| wBTC | 0 | Predefined BTC-scale denominations |
| SAGE | 1 | Predefined SAGE-scale denominations |
| ETH | 2 | Predefined ETH-scale denominations |
| STRK | 3 | Predefined STRK-scale denominations |
| USDC | 4 | Predefined USDC-scale denominations |
Next Steps
- Privacy Architecture --- how VM31 and ElGamal tracks connect
- Privacy Features --- ElGamal-based privacy pools and stealth payments
- Deployed Contracts --- VM31Pool, VM31Bridge, and all mainnet addresses