Quick Start

Complete, runnable examples for the most common OP_NET Transaction Library operations. All examples target regtest for local development. Replace networks.regtest with networks.bitcoin or networks.testnet for other environments.

Prerequisites

Before running these examples, make sure you have the library installed:

Install using npmbash
npm install @btc-vision/transaction @btc-vision/bitcoin

For comprehensive installation instructions, refer to the Installing the Libraries section in the Quick Start guide.

1. Creating a Wallet from Mnemonic

Generate a new mnemonic phrase and derive a wallet with both classical (secp256k1) and quantum (ML-DSA) key pairs.

Creating a wallet from mnemonictypescript
import {
    Mnemonic,
    MnemonicStrength,
    MLDSASecurityLevel,
    Wallet,
} from '@btc-vision/transaction';
import { networks } from '@btc-vision/bitcoin';

// --- Generate a new mnemonic ---
const mnemonic = Mnemonic.generate(
    MnemonicStrength.MAXIMUM,       // 24 words (256-bit entropy)
    '',                              // No BIP39 passphrase
    networks.regtest,                // Network
    MLDSASecurityLevel.LEVEL2,       // ML-DSA-44 (BIP360 recommended default)
);

console.log('Mnemonic phrase:', mnemonic.phrase);
// => "abandon abandon abandon ... about"

// --- Derive the first wallet (account 0, index 0) ---
const wallet: Wallet = mnemonic.derive(0);

console.log('P2TR address:', wallet.p2tr);           // bcrt1p...
console.log('P2WPKH address:', wallet.p2wpkh);       // bcrt1q...
console.log('Legacy address:', wallet.legacy);        // m/n...
console.log('OPNet address:', wallet.address.toHex()); // 0x-prefixed hex (SHA-256 of ML-DSA pubkey)

// --- Derive multiple wallets ---
const wallets: Wallet[] = mnemonic.deriveMultiple(
    5,      // count
    0,      // startIndex
    0,      // account
    false,  // isChange
);

for (let i = 0; i < wallets.length; i++) {
    const w = wallets[i]!;
    console.log(`Wallet ${i}: ${w.p2tr}`);
}

// --- Load an existing mnemonic ---
const existingPhrase = 'abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about';
const restored = new Mnemonic(
    existingPhrase,
    '',
    networks.regtest,
    MLDSASecurityLevel.LEVEL2,
);
const restoredWallet = restored.derive(0);
console.log('Restored P2TR:', restoredWallet.p2tr);

// --- Clean up sensitive material ---
mnemonic.zeroize();
wallet.zeroize();

Wallet Properties Reference

Property Type Description
wallet.p2tr string Taproot address (bc1p...) -- primary for OP_NET
wallet.p2wpkh string Native SegWit address (bc1q...)
wallet.legacy string Legacy P2PKH address1...
wallet.segwitLegacy string 3...
wallet.keypair UniversalSigner secp256k1 key pair for signing
wallet.mldsaKeypair QuantumBIP32Interface ML-DSA key pair for quantum signing
wallet.address Address 32-byte OPNet address
wallet.publicKey Uint8Array Compressed secp256k1 public key (33 bytes)
wallet.quantumPublicKey Uint8Array ML-DSA public key (1312 bytes for LEVEL2)

2. Sending BTC (Funding Transaction)

Create a simple BTC transfer using TransactionFactory.createBTCTransfer(). This produces a single Bitcoin transaction.

Sending BTC (Funding Transaction)typescript
import {
    Mnemonic,
    TransactionFactory,
    OPNetLimitedProvider,
    type UTXO,
} from '@btc-vision/transaction';
import { networks } from '@btc-vision/bitcoin';

// --- Setup ---
const network = networks.regtest;
const mnemonic = new Mnemonic(
    'your twelve word mnemonic phrase goes here abandon abandon abandon abandon about',
    '',
    network,
);
const wallet = mnemonic.derive(0);

// --- Fetch UTXOs from an OPNet node ---
const provider = new OPNetLimitedProvider('https://regtest.opnet.org');
const utxos: UTXO[] = await provider.fetchUTXO({
    address: wallet.p2tr,
    minAmount: 10_000n,
    requestedAmount: 100_000n,
});

// --- Build and sign the transfer ---
const factory = new TransactionFactory();

const result = await factory.createBTCTransfer({
    // Signer configuration
    signer: wallet.keypair,
    mldsaSigner: wallet.mldsaKeypair,
    network: network,

    // Transaction details
    from: wallet.p2tr,
    to: 'bcrt1p...recipient_taproot_address...',
    amount: 50_000n,         // 50,000 satoshis
    utxos: utxos,

    // Fee configuration
    feeRate: 2,              // sat/vB
    priorityFee: 0n,         // Additional priority fee (satoshis)
    gasSatFee: 0n,           // OPNet gas fee (0 for simple transfers)
});

console.log('Transaction hex:', result.tx);
console.log('Estimated fees:', result.estimatedFees, 'satoshis');
console.log('Remaining UTXOs:', result.nextUTXOs.length);

// --- Broadcast the transaction ---
// Use provider.broadcastTransaction(result.tx) or your own RPC node

// --- Clean up ---
mnemonic.zeroize();
wallet.zeroize();

Transfer Parameters Reference

Parameter Type Required Description
signer Signer | UniversalSigner Yes secp256k1 key pair for signing transaction inputs.
mldsaSigner QuantumBIP32Interface | null Yes ML-DSA key pair for quantum-resistant signing, or null to skip.
network Network Yes Bitcoin network configuration.
from string Yes Sender's P2TR address.
to string Yes Recipient's address.
amount bigint Yes Amount to send in satoshis.
utxos UTXO[] Yes Available UTXOs to fund the transaction.
feeRate number Yes Fee rate in satoshis per virtual byte.
priorityFee bigint Yes Additional priority fee in satoshis.
gasSatFee bigint Yes OP_NET gas fee in satoshis.

3. Deploying a Contract

Deploy an OP_NET smart contract using TransactionFactory.signDeployment(). This produces two transactions: a funding TX and a deployment TX.

Deploying a Contracttypescript
import {
    Mnemonic,
    TransactionFactory,
    ChallengeSolution,
    OPNetLimitedProvider,
    type UTXO,
    type RawChallenge,
} from '@btc-vision/transaction';
import { networks } from '@btc-vision/bitcoin';
import * as fs from 'fs';

// --- Setup ---
const network = networks.regtest;
const mnemonic = new Mnemonic(
    'your twelve word mnemonic phrase goes here abandon abandon abandon abandon about',
    '',
    network,
);
const wallet = mnemonic.derive(0);

// --- Fetch UTXOs ---
const provider = new OPNetLimitedProvider('https://regtest.opnet.org');
const utxos: UTXO[] = await provider.fetchUTXO({
    address: wallet.p2tr,
    minAmount: 100_000n,
    requestedAmount: 500_000n,
});

// --- Load contract bytecode ---
const bytecode: Uint8Array = fs.readFileSync('./my-contract.wasm');

// --- Obtain a challenge solution from the OPNet network ---
// The challenge is obtained from the OPNet API and proves the transaction
// is valid for the current epoch. This is a simplified representation;
// in practice, you fetch this from the OPNet node API.
const rawChallenge: RawChallenge = {
    epochNumber: '1',
    mldsaPublicKey: toHex(wallet.quantumPublicKey),
    legacyPublicKey: toHex(wallet.address.originalPublicKey!),
    solution: '0x...challenge_solution_hex...',
    salt: '0x...salt_hex...',
    graffiti: '0x00',
    difficulty: 1,
    verification: {
        epochHash: '0x...epoch_hash...',
        epochRoot: '0x...epoch_root...',
        targetHash: '0x...target_hash...',
        targetChecksum: '0x...target_checksum...',
        startBlock: '0',
        endBlock: '100',
        proofs: [],
    },
};
const challenge = new ChallengeSolution(rawChallenge);

// --- Build and sign the deployment ---
const factory = new TransactionFactory();

const result = await factory.signDeployment({
    // Signer configuration
    signer: wallet.keypair,
    mldsaSigner: wallet.mldsaKeypair,
    network: network,

    // Deployment details
    from: wallet.p2tr,
    bytecode: bytecode,
    utxos: utxos,
    challenge: challenge,

    // Fee configuration
    feeRate: 2,
    priorityFee: 330n,
    gasSatFee: 330n,
});

console.log('Funding TX:', result.transaction[0]);
console.log('Deployment TX:', result.transaction[1]);
console.log('Contract address:', result.contractAddress);
console.log('Contract public key:', result.contractPubKey);
console.log('Remaining UTXOs:', result.utxos.length);

// Broadcast both transactions in order:
// 1. Broadcast result.transaction[0] (funding)
// 2. Broadcast result.transaction[1] (deployment)

// --- Clean up ---
mnemonic.zeroize();
wallet.zeroize();

Deployment Result

Field Type Description
transaction [string, string] [fundingTxHex, deploymentTxHex]
contractAddress string The deployed contract's address
contractPubKey string The contract's public key
challenge RawChallenge The challenge used
utxos UTXO[] Remaining UTXOs (change)
inputUtxos UTXO[] UTXOs consumed as inputs

4. Calling a Contract Function

Interact with a deployed contract using TransactionFactory.signInteraction(). Encode the function calldata with BinaryWriter, then sign and broadcast.

Creating a wallet from mnemonictypescript
import {
    Mnemonic,
    TransactionFactory,
    BinaryWriter,
    ChallengeSolution,
    OPNetLimitedProvider,
    ABICoder,
    Address,
    type UTXO,
    type RawChallenge,
} from '@btc-vision/transaction';
import { networks, fromHex } from '@btc-vision/bitcoin';

// --- Setup ---
const network = networks.regtest;
const mnemonic = new Mnemonic(
    'your twelve word mnemonic phrase goes here abandon abandon abandon abandon about',
    '',
    network,
);
const wallet = mnemonic.derive(0);
const contractAddress = 'bcrt1p...the_contract_taproot_address...';

// --- Fetch UTXOs ---
const provider = new OPNetLimitedProvider('https://regtest.opnet.org');
const utxos: UTXO[] = await provider.fetchUTXO({
    address: wallet.p2tr,
    minAmount: 100_000n,
    requestedAmount: 500_000n,
});

// --- Encode calldata ---
// Example: calling a "transfer" function with (Address to, uint256 amount)
const abiCoder = new ABICoder();
const selectorHex = abiCoder.encodeSelector('transfer');   // First 4 bytes of SHA-256("transfer")
const selector = parseInt(selectorHex, 16);

const calldata = new BinaryWriter();
calldata.writeSelector(selector);

// Write the recipient address (32 bytes -- OPNet address)
const recipientAddress = Address.fromString(
    '0x...recipient_mldsa_pubkey_hash_hex...',    // ML-DSA public key hash
    '0x...recipient_legacy_pubkey_hex...',         // Legacy public key
);
calldata.writeAddress(recipientAddress);

// Write the amount (uint256)
calldata.writeU256(1_000_000_000n);    // 1 billion smallest units

// --- Obtain challenge from OPNet API ---
const rawChallenge: RawChallenge = {
    epochNumber: '1',
    mldsaPublicKey: toHex(wallet.quantumPublicKey),
    legacyPublicKey: toHex(wallet.address.originalPublicKey!),
    solution: '0x...challenge_solution_hex...',
    salt: '0x...salt_hex...',
    graffiti: '0x00',
    difficulty: 1,
    verification: {
        epochHash: '0x...epoch_hash...',
        epochRoot: '0x...epoch_root...',
        targetHash: '0x...target_hash...',
        targetChecksum: '0x...target_checksum...',
        startBlock: '0',
        endBlock: '100',
        proofs: [],
    },
};
const challenge = new ChallengeSolution(rawChallenge);

// --- Build and sign the interaction ---
const factory = new TransactionFactory();

const result = await factory.signInteraction({
    // Signer configuration
    signer: wallet.keypair,
    mldsaSigner: wallet.mldsaKeypair,
    network: network,

    // Interaction details
    from: wallet.p2tr,
    to: contractAddress,
    calldata: calldata.getBuffer(),
    utxos: utxos,
    challenge: challenge,

    // Fee configuration
    feeRate: 2,
    priorityFee: 330n,
    gasSatFee: 330n,
});

console.log('Funding TX:', result.fundingTransaction);
console.log('Interaction TX:', result.interactionTransaction);
console.log('Estimated fees:', result.estimatedFees, 'satoshis');
console.log('Next UTXOs:', result.nextUTXOs.length);

// Broadcast both transactions in order:
// 1. Broadcast result.fundingTransaction
// 2. Broadcast result.interactionTransaction

// --- Clean up ---
mnemonic.zeroize();
wallet.zeroize();
        

Interaction Result

Field Type Description
fundingTransaction string | null Funding TX hex (null for P2WDA)
interactionTransaction string Interaction TX hex
estimatedFees bigint Total fees in satoshis
nextUTXOs UTXO[] Remaining UTXOs (change)
fundingUTXOs UTXO[] UTXOs used in the funding TX
fundingInputUtxos UTXO[] Original input UTXOs
challenge RawChallenge The challenge used
interactionAddress string | null One-time Taproot script address
compiledTargetScript string | null Compiled Tapscript hex

Two-Transaction Model Flow

Two-Transaction Model Flow Your Application BinaryWriter TransactionFactory Bitcoin Network Encode calldata (selector + args) signInteraction(params) 1. Estimate funding amount 2. Build funding TX 3. Build interaction TX 4. Sign both > App (dashed return) ==================== --> { fundingTransaction, interactionTransaction } Broadcast funding TX Broadcast interaction TX Your Application BinaryWriter TransactionFactory Bitcoin Network

5. Message Signing

Sign messages using both classical (Schnorr) and quantum (ML-DSA) signatures. The Auto methods automatically detect browser wallet extensions.

Schnorr Signing (Backend)

Schnorr Signingtypescript
import { Mnemonic, MessageSigner } from '@btc-vision/transaction';
import { networks } from '@btc-vision/bitcoin';

const network = networks.regtest;
const mnemonic = new Mnemonic(
    'your twelve word mnemonic phrase goes here abandon abandon abandon abandon about',
    '',
    network,
);
const wallet = mnemonic.derive(0);

// --- Sign a message with Schnorr ---
const message = 'Hello, OPNet!';
const signed = MessageSigner.signMessage(wallet.keypair, message);

console.log('Signature:', toHex(signed.signature));
console.log('Message hash:', toHex(signed.message));

// --- Verify the signature ---
const isValid = MessageSigner.verifySignature(
    wallet.publicKey,
    message,
    signed.signature,
);
console.log('Schnorr signature valid:', isValid);    // true

// --- Tweaked Schnorr signing (Taproot-compatible) ---
const tweakedSigned = MessageSigner.tweakAndSignMessage(
    wallet.keypair,
    message,
    network,
);

const isTweakedValid = MessageSigner.tweakAndVerifySignature(
    wallet.publicKey,
    message,
    tweakedSigned.signature,
);
console.log('Tweaked signature valid:', isTweakedValid);    // true

mnemonic.zeroize();
wallet.zeroize();

ML-DSA Signing (Backend)

ML-DSA Signing (Backend)typescript
import { Mnemonic, MessageSigner } from '@btc-vision/transaction';
import { networks } from '@btc-vision/bitcoin';

const network = networks.regtest;
const mnemonic = new Mnemonic(
    'your twelve word mnemonic phrase goes here abandon abandon abandon abandon about',
    '',
    network,
);
const wallet = mnemonic.derive(0);

// --- Sign with ML-DSA (quantum-resistant) ---
const message = 'Quantum-resistant message';
const mldsaSigned = MessageSigner.signMLDSAMessage(
    wallet.mldsaKeypair,
    message,
);

console.log('ML-DSA signature length:', mldsaSigned.signature.length, 'bytes');
console.log('ML-DSA public key length:', mldsaSigned.publicKey.length, 'bytes');
console.log('Security level:', mldsaSigned.securityLevel);

// --- Verify ML-DSA signature ---
const isQuantumValid = MessageSigner.verifyMLDSASignature(
    wallet.mldsaKeypair,
    message,
    mldsaSigned.signature,
);
console.log('ML-DSA signature valid:', isQuantumValid);    // true

mnemonic.zeroize();
wallet.zeroize();

Auto Signing (Browser + Backend)

The Auto methods try the browser wallet extension (OP_WALLET) first, then fall back to the provided key pair. This allows the same code to work in both environments.

Creating a wallet from mnemonictypescript
import { Mnemonic, MessageSigner } from '@btc-vision/transaction';
import { networks } from '@btc-vision/bitcoin';

const network = networks.regtest;

// In a browser, the wallet keypair may be undefined (the extension handles signing).
// In Node.js, you must provide the keypair.
const wallet = /* your Wallet instance, or undefined in browser */ undefined;

// --- Auto Schnorr signing ---
// Browser: delegates to window.opnet.web3.signSchnorr()
// Backend: signs with the provided keypair
const schnorrResult = await MessageSigner.signMessageAuto(
    'Sign this message',
    wallet?.keypair,      // undefined in browser => uses OP_WALLET
);
console.log('Schnorr signature:', schnorrResult.signature);

// --- Auto tweaked Schnorr signing ---
const tweakedResult = await MessageSigner.tweakAndSignMessageAuto(
    'Sign this tweaked message',
    wallet?.keypair,
    network,               // Required when signing with a local keypair
);
console.log('Tweaked signature:', tweakedResult.signature);

// --- Auto ML-DSA signing ---
// Browser: delegates to window.opnet.web3.signMLDSAMessage()
// Backend: signs with the provided ML-DSA keypair
const mldsaResult = await MessageSigner.signMLDSAMessageAuto(
    'Quantum-resistant auto-sign',
    wallet?.mldsaKeypair,  // undefined in browser => uses OP_WALLET
);
console.log('ML-DSA signature:', mldsaResult.signature);

// --- Check if OP_WALLET is available ---
if (MessageSigner.isOPWalletAvailable()) {
    console.log('OP_WALLET extension detected');

    // Get ML-DSA public key directly from the wallet extension
    const pubKey = await MessageSigner.getMLDSAPublicKeyFromOPWallet();
    if (pubKey) {
        console.log('OP_WALLET ML-DSA public key length:', pubKey.length, 'bytes');
    }
}

Signing Method Summary

Method Signature Type Auto-detects OP_WALLET Use Case
signMessage Schnorr No Backend-only signing
tweakAndSignMessage Schnorr (tweaked) No Taproot-compatible backend signing
signMLDSAMessage ML-DSA No Backend-only quantum signing
signMessageAuto Schnorr Yes Universal (browser + backend)
tweakAndSignMessageAuto Schnorr (tweaked) Yes Universal Taproot signing
signMLDSAMessageAuto ML-DSA Yes Universal quantum signing
verifySignature Schnorr No Verify classical signatures
tweakAndVerifySignature Schnorr (tweaked) No Verify tweaked signatures
verifyMLDSASignature ML-DSA No Verify quantum signatures