Skip to content

Latest commit

 

History

History
846 lines (643 loc) · 22.9 KB

File metadata and controls

846 lines (643 loc) · 22.9 KB

Blockchain API Reference

The Blockchain singleton provides access to the runtime environment, storage, and blockchain operations.

Import

import { Blockchain } from '@btc-vision/btc-runtime/runtime';

Properties

Block Context

Property Type Description
Blockchain.block Block Current block information
Blockchain.block.number u64 Block height
Blockchain.block.numberU256 u256 Block height as u256
Blockchain.block.hash Uint8Array 32-byte block hash
Blockchain.block.medianTimestamp u64 Median timestamp (seconds)
const blockNum = Blockchain.block.number;
const timestamp = Blockchain.block.medianTimestamp;

Solidity Comparison:

Solidity OP_NET
block.number Blockchain.block.number
block.timestamp Blockchain.block.medianTimestamp

Transaction Context

Property Type Description
Blockchain.tx Transaction Current transaction info
Blockchain.tx.sender Address Immediate caller
Blockchain.tx.origin ExtendedAddress Original signer
Blockchain.tx.txId Uint8Array Transaction ID
Blockchain.tx.hash Uint8Array Transaction hash
Blockchain.tx.inputs TransactionInput[] Transaction inputs (UTXOs)
Blockchain.tx.outputs TransactionOutput[] Transaction outputs (UTXOs)
Blockchain.tx.consensus ConsensusRules Consensus rules
const caller = Blockchain.tx.sender;      // Immediate caller
const signer = Blockchain.tx.origin;      // Original signer
const unsafeAllowed = Blockchain.tx.consensus.unsafeSignaturesAllowed();

Solidity Comparison:

Solidity OP_NET
msg.sender Blockchain.tx.sender
tx.origin Blockchain.tx.origin

Contract Context

Property Type Description
Blockchain.contract OP_NET Current contract instance
Blockchain.contractAddress Address This contract's address
Blockchain.contractDeployer Address Deployer's address
const self = Blockchain.contractAddress;
const deployer = Blockchain.contractDeployer;

Solidity Comparison:

Solidity OP_NET
address(this) Blockchain.contractAddress

Network Context

Property Type Description
Blockchain.network Networks Network identifier
Blockchain.chainId Uint8Array 32-byte chain ID
Blockchain.protocolId Uint8Array 32-byte protocol ID
Blockchain.DEAD_ADDRESS ExtendedAddress Burn address
if (Blockchain.network === Networks.Mainnet) {
    // Mainnet-specific logic
}

Solidity Comparison:

Solidity OP_NET
block.chainid Blockchain.chainId

Storage Architecture

Storage Pointers

Property Type Description
Blockchain.nextPointer u16 Get next storage pointer
private myPointer: u16 = Blockchain.nextPointer;

The storage system uses a pointer-based architecture where each storage slot is identified by a unique hash:

graph LR
    subgraph "Pointer Allocation"
        A[Contract] -->|Allocate| B[Blockchain.nextPointer]
        B -->|Returns u16<br/>0-65535| C[Pointer ID]
    end

    subgraph "Key Generation"
        C -->|Pointer u16| F[encodePointer]
        D[SubPointer<br/>30 bytes] -->|Address/Key Data| F
        F -->|SHA-256 Hash| G[32-byte Storage Key]
    end

    subgraph "Storage Operations"
        G -->|Read| H[getStorageAt<br/>Returns 32 bytes]
        G -->|Write| I[setStorageAt<br/>Stores value]
        G -->|Check| J[hasStorageAt<br/>Returns boolean]
    end

    H --> K[Persistent Storage]
    I --> K
    J --> K
Loading

Storage Methods

encodePointer

Generates a storage key hash from a pointer and subPointer.

encodePointer(pointer: u16, subPointer: Uint8Array): Uint8Array
Parameter Type Description
pointer u16 Storage slot identifier (0-65535)
subPointer Uint8Array Sub-index bytes (typically 30 bytes)
Returns Uint8Array 32-byte storage key hash
import { encodePointer } from '@btc-vision/btc-runtime/runtime';

const pointer: u16 = Blockchain.nextPointer;
const subPointer = address.toBytes(); // or u256.toUint8Array(true)

// Generate storage key
const pointerHash = encodePointer(pointer, subPointer);

getStorageAt

Reads from persistent storage.

getStorageAt(pointerHash: Uint8Array): Uint8Array
Parameter Type Description
pointerHash Uint8Array 32-byte storage key (from encodePointer)
Returns Uint8Array 32-byte value (zeros if unset)
import { encodePointer } from '@btc-vision/btc-runtime/runtime';

// Create storage key
const pointerHash = encodePointer(pointer, subPointer);

// Read from storage
const stored = Blockchain.getStorageAt(pointerHash);
const value = u256.fromUint8ArrayBE(stored);

setStorageAt

Writes to persistent storage.

setStorageAt(pointerHash: Uint8Array, value: Uint8Array): void
Parameter Type Description
pointerHash Uint8Array 32-byte storage key (from encodePointer)
value Uint8Array Value to store (auto-padded to 32 bytes)
import { encodePointer } from '@btc-vision/btc-runtime/runtime';

// Create storage key
const pointerHash = encodePointer(pointer, subPointer);

// Write to storage
Blockchain.setStorageAt(pointerHash, value.toUint8Array(true));

hasStorageAt

Checks if storage slot has non-zero value.

hasStorageAt(pointerHash: Uint8Array): bool
const pointerHash = encodePointer(pointer, subPointer);
if (Blockchain.hasStorageAt(pointerHash)) {
    // Slot has a value
}

Storage Flow

The following sequence diagram illustrates the complete flow of storage operations:

sequenceDiagram
    participant C as Contract
    participant B as Blockchain
    participant S as Storage Layer

    Note over C,S: Storage Pointer Allocation
    C->>B: Blockchain.nextPointer
    B->>C: Returns u16 (e.g., 5)

    Note over C,S: Write Operation
    C->>C: address.toBytes() -> subPointer
    C->>B: encodePointer(5, subPointer)
    B->>B: SHA-256(pointer + subPointer)
    B->>C: Returns 32-byte hash
    C->>B: setStorageAt(hash, value)
    B->>S: Persist to storage

    Note over C,S: Read Operation
    C->>B: encodePointer(5, subPointer)
    B->>C: Returns 32-byte hash
    C->>B: getStorageAt(hash)
    S->>B: Retrieve value
    B->>C: Returns 32-byte value
Loading

Storage Pattern Example

Complete example showing the correct pattern:

import { u256 } from '@btc-vision/as-bignum/assembly';
import {
    Blockchain,
    encodePointer,
    Address
} from '@btc-vision/btc-runtime/runtime';

// Allocate pointer
private balancesPointer: u16 = Blockchain.nextPointer;

// Write balance
public setBalance(address: Address, amount: u256): void {
    const pointerHash = encodePointer(this.balancesPointer, address.toBytes());
    Blockchain.setStorageAt(pointerHash, amount.toUint8Array(true));
}

// Read balance
public getBalance(address: Address): u256 {
    const pointerHash = encodePointer(this.balancesPointer, address.toBytes());
    const stored = Blockchain.getStorageAt(pointerHash);
    return u256.fromUint8ArrayBE(stored);
}

Solidity Comparison:

Solidity OP_NET
mapping(address => uint256) balances AddressMemoryMap with pointer
balances[addr] = value Blockchain.setStorageAt(pointerHash, value)
balances[addr] Blockchain.getStorageAt(pointerHash)

Transient Storage (Experimental)

Warning: Transient storage is NOT enabled in production. Only available in testing.

getTransientStorageAt(pointerHash: Uint8Array): Uint8Array
setTransientStorageAt(pointerHash: Uint8Array, value: Uint8Array): void
hasTransientStorageAt(pointerHash: Uint8Array): bool

Cross-Contract Calls

call

Calls another contract.

call(
    destinationContract: Address,
    calldata: BytesWriter,
    stopExecutionOnFailure: boolean = true
): CallResult
Parameter Type Description
destinationContract Address Target contract
calldata BytesWriter Encoded call data
stopExecutionOnFailure boolean Revert on failure (default: true)
Returns CallResult Success flag and response data

The following diagram shows the two call patterns - standard calls that revert on failure, and try-catch style calls that handle failures gracefully:

flowchart LR
    subgraph StdPattern["stopOnFailure = true"]
        A1["Call"] --> B1{"Success?"}
        B1 -->|"Yes"| C1["Continue"]
        B1 -->|"No"| D1["REVERT TX"]
    end

    subgraph TryPattern["stopOnFailure = false"]
        A2["Call"] --> B2{"Success?"}
        B2 -->|"Yes"| C2["Continue"]
        B2 -->|"No"| D2["Handle error"]
    end
Loading
// Standard call - reverts on failure
const result = Blockchain.call(tokenAddress, calldata);
const balance = result.data.readU256();

// Try-catch pattern - handles failure gracefully
const result = Blockchain.call(tokenAddress, calldata, false);
if (result.success) {
    // Process response
} else {
    // Handle failure without reverting
}

The complete call flow with both success and failure scenarios:

sequenceDiagram
    participant Caller as Calling Contract
    participant BC as Blockchain
    participant Target as Target Contract

    Note over Caller,Target: Standard Call (stopExecutionOnFailure=true)
    Caller->>Caller: Encode calldata (BytesWriter)
    Caller->>BC: call(targetAddress, calldata, true)
    BC->>Target: Execute method

    alt Success Case
        Target->>Target: Process request
        Target->>BC: Return BytesWriter response
        BC->>Caller: CallResult{success: true, data}
        Caller->>Caller: Process response.data
    else Failure Case
        Target->>BC: Throw Revert
        BC->>Caller: Transaction reverts
        Note over Caller: Execution stops
    end

    Note over Caller,Target: Try-Catch Pattern (stopExecutionOnFailure=false)
    Caller->>BC: call(targetAddress, calldata, false)
    BC->>Target: Execute method

    alt Success Case
        Target->>BC: Return response
        BC->>Caller: CallResult{success: true, data}
        Caller->>Caller: Handle success
    else Failure Case
        Target->>BC: Throw Revert
        BC->>Caller: CallResult{success: false, data}
        Caller->>Caller: Handle failure gracefully
        Note over Caller: Execution continues
    end
Loading

Solidity Comparison:

Solidity OP_NET
target.call(data) Blockchain.call(target, calldata, false)
target.functionCall(args) Blockchain.call(target, calldata, true)
try target.call() { } catch { } Blockchain.call(target, calldata, false) + check result.success

CallResult

class CallResult {
    readonly success: boolean;
    readonly data: BytesReader;
}

deployContractFromExisting

Deploys a new contract from a template.

deployContractFromExisting(
    existingAddress: Address,
    salt: u256,
    calldata: BytesWriter
): Address
Parameter Type Description
existingAddress Address Template contract
salt u256 Unique salt for address
calldata BytesWriter Constructor parameters
Returns Address New contract address
const salt = u256.fromBytes(Blockchain.sha256(uniqueData));
const newContract = Blockchain.deployContractFromExisting(
    templateAddress,
    salt,
    constructorData
);

updateContractFromExisting

Updates the calling contract's bytecode from another deployed contract. The new bytecode takes effect at the next block.

updateContractFromExisting(
    sourceAddress: Address,
    calldata?: BytesWriter | null
): void
Parameter Type Description
sourceAddress Address Contract containing new bytecode
calldata BytesWriter | null Optional calldata passed to VM (default: empty)
// Basic update (not recommended without access control)
Blockchain.updateContractFromExisting(newBytecodeAddress);

// With calldata
const updateData = new BytesWriter(32);
updateData.writeU256(migrationVersion);
Blockchain.updateContractFromExisting(newBytecodeAddress, updateData);

Warning: This is a privileged operation. Always implement access control (e.g., onlyDeployer) and consider using the Updatable base class or UpdatablePlugin for timelock protection. See Contract Updates for details.

Cryptographic Operations

sha256

Computes SHA-256 hash.

sha256(buffer: Uint8Array): Uint8Array
const hash = Blockchain.sha256(data);  // 32 bytes

hash256

Computes double SHA-256 (Bitcoin standard).

hash256(buffer: Uint8Array): Uint8Array
const txHash = Blockchain.hash256(txData);  // 32 bytes

Signature Verification

verifySignature

Verifies signature based on current consensus rules.

import { SignaturesMethods } from '@btc-vision/btc-runtime/runtime';

verifySignature(
    address: ExtendedAddress,
    signature: Uint8Array,
    hash: Uint8Array,
    signatureType: SignaturesMethods = SignaturesMethods.Schnorr
): boolean
Parameter Type Description
address ExtendedAddress Signer's address
signature Uint8Array Signature bytes (64 for Schnorr, 2420+ for ML-DSA)
hash Uint8Array 32-byte message hash
signatureType SignaturesMethods Signature type: Schnorr (default), MLDSA, or ECDSA
Returns boolean True if valid

The signature verification selects the appropriate algorithm based on the signatureType parameter and consensus rules:

flowchart LR
    subgraph "verifySignature Flow"
        Start([Verify Signature]) --> GetInput[Receive address,<br/>signature, hash]
        GetInput --> CheckType{signatureType?}
        CheckType -->|Schnorr| CheckConsensus{unsafeSignatures<br/>Allowed?}
        CheckConsensus -->|Yes| Schnorr[Schnorr Path]
        CheckConsensus -->|No| MLDSA
        CheckType -->|MLDSA| MLDSA[ML-DSA Path]
        CheckType -->|ECDSA| ECDSAErr[Error: Use dedicated<br/>ECDSA methods]
    end

    subgraph "Schnorr Verification"
        Schnorr --> SchnorrVerify[Verify Schnorr]
        SchnorrVerify --> SchnorrResult{Valid?}
    end

    subgraph "ML-DSA Verification"
        MLDSA --> MLDSAVerify[Verify ML-DSA-44<br/>Level2]
        MLDSAVerify --> MLDSAResult{Valid?}
    end

    MLDSAResult -->|Yes| Success([Return true])
    MLDSAResult -->|No| Fail([Return false])
    SchnorrResult -->|Yes| Success
    SchnorrResult -->|No| Fail
Loading
const isValid = Blockchain.verifySignature(
    signerAddress,
    signatureBytes,
    messageHash
);
if (!isValid) {
    throw new Revert('Invalid signature');
}

The complete EIP-712 style signature verification flow:

sequenceDiagram
    participant C as Contract
    participant BC as Blockchain
    participant Cons as Consensus Layer
    participant Crypto as Crypto Module

    Note over C,Crypto: EIP-712 Style Signature Verification
    C->>C: Build domain separator
    C->>C: Hash struct data
    C->>C: Combine: 0x1901 + domain + structHash
    C->>C: messageHash = SHA-256(combined)

    C->>BC: verifySignature(address, signature, messageHash, signatureType)

    BC->>Cons: Check consensus flags
    Cons->>BC: unsafeSignaturesAllowed()

    alt signatureType == Schnorr AND unsafeSignaturesAllowed()
        BC->>Crypto: verifySchnorr(tweakedPublicKey, sig, hash)
        Crypto->>BC: Valid/Invalid
    else signatureType == MLDSA OR !unsafeSignaturesAllowed()
        BC->>BC: Use ML-DSA Level2 (ML-DSA-44)
        BC->>Crypto: verifyMLDSA(Level2, mldsaPublicKey, sig, hash)
        Crypto->>BC: Valid/Invalid
    end

    BC->>C: Return boolean
Loading

verifyECDSASignature (Deprecated)

Verifies an ECDSA signature using the Ethereum ecrecover model (secp256k1).

verifyECDSASignature(
    publicKey: Uint8Array,
    signature: Uint8Array,
    hash: Uint8Array
): boolean
Parameter Type Description
publicKey Uint8Array secp256k1 public key (33, 64, or 65 bytes)
signature Uint8Array 65-byte signature: r(32) || s(32) || v(1)
hash Uint8Array 32-byte message hash (typically keccak256)
Returns boolean True if ecrecover produces matching key

Warning: Only available when UNSAFE_QUANTUM_SIGNATURES_ALLOWED consensus flag is set. Throws Revert otherwise.

verifyBitcoinECDSASignature (Deprecated)

Verifies an ECDSA signature using the Bitcoin direct verification model (secp256k1).

verifyBitcoinECDSASignature(
    publicKey: Uint8Array,
    signature: Uint8Array,
    hash: Uint8Array
): boolean
Parameter Type Description
publicKey Uint8Array secp256k1 public key (33, 64, or 65 bytes)
signature Uint8Array 64-byte compact signature: r(32) || s(32)
hash Uint8Array 32-byte message hash (typically SHA-256 double hash)
Returns boolean True if signature is valid

Warning: Only available when UNSAFE_QUANTUM_SIGNATURES_ALLOWED consensus flag is set. Throws Revert otherwise. Enforces BIP-0062 low-S normalization.

verifyMLDSASignature

Verifies ML-DSA (quantum-resistant) signature.

verifyMLDSASignature(
    level: MLDSASecurityLevel,
    publicKey: Uint8Array,
    signature: Uint8Array,
    hash: Uint8Array
): boolean
Parameter Type Description
level MLDSASecurityLevel Security level (Level2, Level3, Level5)
publicKey Uint8Array ML-DSA public key
signature Uint8Array ML-DSA signature
hash Uint8Array 32-byte message hash
Returns boolean True if valid
import { MLDSASecurityLevel } from '@btc-vision/btc-runtime/runtime';

const isValid = Blockchain.verifyMLDSASignature(
    MLDSASecurityLevel.Level2,  // ML-DSA-44
    address.mldsaPublicKey,     // 1312 bytes
    signature,                   // 2420 bytes
    messageHash                  // 32 bytes
);

verifySchnorrSignature (Deprecated)

verifySchnorrSignature(
    publicKey: ExtendedAddress,
    signature: Uint8Array,
    hash: Uint8Array
): boolean

Note: Use verifySignature() instead for automatic consensus migration.

Keccak-256 Hashing

Ethereum-compatible Keccak-256 hash functions (original Keccak, not NIST SHA-3-256).

keccak256

import { keccak256 } from '@btc-vision/btc-runtime/runtime';
keccak256(data: Uint8Array): Uint8Array  // 32-byte digest

keccak256Concat

Hash two concatenated byte arrays. Common for abi.encodePacked patterns.

import { keccak256Concat } from '@btc-vision/btc-runtime/runtime';
keccak256Concat(a: Uint8Array, b: Uint8Array): Uint8Array  // 32-byte digest

functionSelector

Compute Ethereum-style 4-byte function selector.

import { functionSelector } from '@btc-vision/btc-runtime/runtime';
functionSelector(signature: string): Uint8Array  // 4 bytes
// e.g. functionSelector('transfer(address,uint256)') => 0xa9059cbb

ethAddressFromPubKey

Derive Ethereum address from 64-byte uncompressed public key.

import { ethAddressFromPubKey } from '@btc-vision/btc-runtime/runtime';
ethAddressFromPubKey(publicKey: Uint8Array): Uint8Array  // 20-byte Ethereum address

Utility Methods

validateBitcoinAddress

Validates a Bitcoin address string.

validateBitcoinAddress(address: string): bool
if (!Blockchain.validateBitcoinAddress(userAddress)) {
    throw new Revert('Invalid Bitcoin address');
}

isContract

Checks if an address is a contract.

isContract(address: Address): boolean
if (Blockchain.isContract(targetAddress)) {
    // Is a contract, not EOA
}

Solidity Comparison:

Solidity OP_NET
address.code.length > 0 Blockchain.isContract(address)

getAccountType

Gets account type code.

getAccountType(address: Address): u32
Return Value Meaning
0 EOA or uninitialized
>0 Contract (type code)

getBlockHash

Retrieves historical block hash.

getBlockHash(blockNumber: u64): Uint8Array
const hash = Blockchain.getBlockHash(Blockchain.block.number - 10);

Warning: Only ~256 recent blocks available. Older blocks return zeros.

Solidity Comparison:

Solidity OP_NET
blockhash(blockNumber) Blockchain.getBlockHash(blockNumber)

Event Methods

emit

Emits an event.

emit(event: NetEvent): void
Blockchain.emit(new TransferEvent(from, to, amount));

Solidity Comparison:

Solidity OP_NET
emit Transfer(from, to, amount) Blockchain.emit(new TransferEvent(from, to, amount))

log

Logs debug message (testing only).

log(data: string): void

Warning: Only available in unit testing framework.

Blockchain.log('Debug: operation started');

Lifecycle Hooks

These are called by the runtime:

Method When Called
onDeployment(calldata) Contract deployment
onUpdate(calldata) Contract bytecode update (via updateContractFromExisting)
onExecutionStarted(selector, calldata) Before method execution
onExecutionCompleted(selector, calldata) After successful execution

onUpdate

Called when the contract's bytecode is updated via updateContractFromExisting. Use this hook to perform storage migrations or initialization logic when upgrading.

public override onUpdate(calldata: Calldata): void {
    super.onUpdate(calldata); // Call plugins first

    // Perform migration logic
    const migrationVersion = calldata.readU64();
    if (migrationVersion === 2) {
        // Migrate from v1 to v2
        this.migrateToV2();
    }
}

Note: The calldata is the same data passed to Blockchain.updateContractFromExisting(sourceAddress, calldata). If no calldata was provided, an empty reader is passed.


Navigation: