Skip to content

EXPERIMENTAL A high-level language that compiles to Arkade Script for building Bitcoin-native smart contracts on Arkade OS

Notifications You must be signed in to change notification settings

arkade-os/compiler

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

9 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Arkade Compiler

Arkade Language is a high-level contract language that compiles down to Arkade Script, an extended version of Bitcoin Script designed for the Arkade OS. Arkade Language lets developers write expressive, stateful smart contracts that compile to scripts executable by Arkade’s Virtual Machine.

Arkade Script supports advanced primitives for arithmetic, introspection, and asset flows across Virtual Transaction Outputs (VTXOs), enabling rich offchain transaction logic with unilateral onchain exit guarantees. Contracts are verified and executed inside secure Trusted Execution Environments (TEEs) and signed by the Arkade Signer, ensuring verifiable and tamper-proof execution.

This language significantly lowers the barrier for Bitcoin-native app development, allowing contracts to be written in a structured, Ivy-like syntax and compiled into Arkade-native scripts.

Basic Usage

arkadec contract.ark

This will compile your Arkade Script contract to a JSON file that can be used with Bitcoin Taproot libraries.

Compiler Options

The Arkade Compiler supports several command-line options:

# Output assembly instead of bytecode arkadec --output=asm contract.ark # Generate debug information arkadec --debug contract.ark # Specify output file arkadec --output-file=contract.json contract.ark

Compilation Artifacts

The compiler produces a JSON file containing:

  • Contract metadata (name, version, etc.)
  • Constructor parameters
  • Function definitions
  • Generated script for each function (both cooperative and unilateral paths)
  • Source map for debugging

Example output:

{ "contractName": "MyContract", "constructorInputs": [ { "name": "user", "type": "pubkey" }, { "name": "server", "type": "pubkey" } ], "functions": [ { "name": "spend", "functionInputs": [ { "name": "userSig", "type": "signature" } ], "serverVariant": true, "require": [ { "type": "signature" }, { "type": "serverSignature" } ], "asm": [ "<user>", "<userSig>", "OP_CHECKSIG", "<SERVER_KEY>", "<serverSig>", "OP_CHECKSIG" ] }, { "name": "spend", "functionInputs": [ { "name": "userSig", "type": "signature" } ], "serverVariant": false, "require": [ { "type": "signature" }, { "type": "older", "message": "Exit timelock of 144 blocks" } ], "asm": [ "<user>", "<userSig>", "OP_CHECKSIG", "144", "OP_CHECKLOCKTIMEVERIFY", "OP_DROP" ] } ], "source": "...", "compiler": { "name": "arkade-script", "version": "0.1.0" }, "updatedAt": "2023-03-06T01:27:51.391557+00:00" }

Examples

Basic VTXO Contract

// Contract configuration options options { // Server key parameter from contract parameters server = server; // Exit timelock: 24 hours (144 blocks) exit = 144; } contract BareVTXO( pubkey user, pubkey server ) { // Single signature spend path // This will automatically be compiled into: // 1. Cooperative path: checkSig(user) && checkSig(server) // 2. Exit path: checkSig(user) && after 144 blocks function spend(signature userSig) { require(checkSig(userSig, user)); } }

HTLC Contract

// Contract configuration options options { // Server key parameter from contract parameters server = server; // Exit timelock: 24 hours (144 blocks) exit = 144; } contract HTLC( pubkey sender, pubkey receiver, bytes hash, int refundTime, pubkey server ) { // Cooperative close path function together(signature senderSig, signature receiverSig) { require(checkMultisig([sender, receiver], [senderSig, receiverSig])); } // Refund path function refund(signature senderSig) { require(checkSig(senderSig, sender)); require(tx.time >= refundTime); } // Claim path function claim(signature receiverSig, bytes preimage) { require(checkSig(receiverSig, receiver)); require(sha256(preimage) == hash); } }

Fuji Safe Contract

// Contract configuration options options { // Server key parameter from contract parameters server = treasuryPk; // Exit timelock: 24 hours (144 blocks) exit = 144; } // Fuji Safe Contract contract FujiSafe( // The asset commitment hash (client-side validated) bytes assetCommitmentHash, // The amount being borrowed int borrowAmount, // The borrower's public key pubkey borrowerPk, // The treasury's public key pubkey treasuryPk, // The expiration timeout in blocks int expirationTimeout, // The price level for liquidation int priceLevel, // The setup timestamp int setupTimestamp, // The oracle's public key pubkey oraclePk, // The asset pair identifier bytes assetPair ) { // Helper function to verify Fuji token burning via Taproot output // Takes the pubkey to use as the internal key for the P2TR output function verifyFujiBurning(pubkey internalKey) internal { // In Taproot, we verify the output is a P2TR that commits to our asset // Using the provided pubkey as the internal key bytes p2trScript = new P2TR(internalKey, assetCommitmentHash); // Verify output 0 has the correct P2TR scriptPubKey and value require(tx.outputs[0].scriptPubKey == p2trScript, "P2TR output mismatch"); require(tx.outputs[0].value == borrowAmount, "Value mismatch"); } // Claim: Treasury can unlock all collateral after expiration when burning Fuji function claim(signature treasurySig) { // Check that expiration timeout has passed require(tx.time >= expirationTimeout, "Expiration timeout not reached"); // Verify burning of Fuji token using treasury key verifyFujiBurning(treasuryPk); // Require treasury signature require(checkSig(treasurySig, treasuryPk), "Invalid treasury signature"); } // Liquidation: Treasury can unlock all collateral with attestation price below the liquidation target function liquidate(int currentPrice, signature oracleSig, signature treasurySig) { // Check price is below liquidation threshold require(currentPrice < priceLevel, "Price not below liquidation threshold"); // Verify timestamp is after setup require(tx.time >= setupTimestamp, "Timestamp before setup"); // Create message for oracle signature verification bytes message = sha256(assetPair); // Verify oracle signature on price data require(checkSigFromStack(oracleSig, oraclePk, message), "Invalid oracle signature"); // Verify burning of Fuji token using treasury key verifyFujiBurning(treasuryPk); // Require treasury signature require(checkSig(treasurySig, treasuryPk), "Invalid treasury signature"); } // Private Redemption: Only owner can unlock all collateral with key when burning Fuji function redeem(signature borrowerSig) { // Verify burning of Fuji token using borrower key verifyFujiBurning(borrowerPk); // Require borrower signature require(checkSig(borrowerSig, borrowerPk), "Invalid borrower signature"); } // Treasury Renew: Treasury can unilaterally renew the expiration time function renew(signature treasurySig) { // For renewal, we ensure the output is another P2TR with the same key and value // This preserves the Taproot commitment structure // Using the new tx.input.current syntax to access the current input's properties bytes currentScript = tx.input.current.scriptPubKey; int currentValue = tx.input.current.value; // Verify that output 0 has the same P2TR script as the current input require(tx.outputs[0].scriptPubKey == currentScript, "P2TR output mismatch"); require(tx.outputs[0].value == currentValue, "Value mismatch"); // Require treasury signature require(checkSig(treasurySig, treasuryPk), "Invalid treasury signature"); } }

Language Reference

TapLang is a domain-specific language for writing Bitcoin Taproot contracts with a focus on readability and safety.

Data Types

TapLang supports the following data types:

  • pubkey: Bitcoin public key
  • signature: Bitcoin signature
  • bytes: Arbitrary byte array
  • bytes20: 20-byte array (useful for hashes)
  • bytes32: 32-byte array (useful for hashes)
  • int: Integer value
  • bool: Boolean value
  • asset: Taproot Asset (for asset-aware contracts)

Contract Structure

A TapLang contract consists of:

  1. An optional options block for configuration
  2. A contract declaration with parameters
  3. One or more function declarations that define spending paths

Example:

// Optional configuration options { server = treasuryPk; exit = 144; } // Contract declaration with parameters contract MyContract( pubkey user, pubkey server ) { // Function declarations (spending paths) function spend(signature userSig) { require(checkSig(userSig, user)); } }

Options Block

The options block configures contract-wide settings:

options { // Server key parameter from contract parameters server = server; // Renewal timelock: 7 days (1008 blocks) renew = 1008; // Exit timelock: 24 hours (144 blocks) exit = 144; }

Available options:

  • server: Specifies which parameter contains the server public key
  • renew: Specifies the renewal timelock in blocks
  • exit: Specifies the exit timelock in blocks

Functions

Functions define spending paths for the contract:

function spend(signature userSig) { require(checkSig(userSig, user)); }

Functions can be marked as internal to indicate they are helper functions and not spending paths:

function verifyCondition() internal { // Helper logic }

Expressions

Arkade Script supports various expressions:

Signature Verification

// Single signature verification require(checkSig(userSig, user)); // Multi-signature verification require(checkMultisig([user, admin], [userSig, adminSig])); // Signature verification from stack require(checkSigFromStack(oracleSig, oraclePk, message));

Hash Verification

// SHA-256 hash verification require(sha256(preimage) == hash);

Timelock Verification

// Absolute timelock require(tx.time >= expirationTime);

Transaction Introspection

TapLang provides access to transaction data:

// Access transaction time require(tx.time >= lockTime); // Access outputs require(tx.outputs[0].value == amount); require(tx.outputs[0].scriptPubKey == script); // Access inputs require(tx.inputs[0].value == amount); require(tx.inputs[0].scriptPubKey == script); // Access the current input (new syntax) require(tx.input.current.value == amount); require(tx.input.current.scriptPubKey == script);

Current Input Access

TapLang provides a special syntax for accessing the current input being spent:

// Access the current input's value int currentValue = tx.input.current.value; // Access the current input's scriptPubKey bytes currentScript = tx.input.current.scriptPubKey; // Access the current input's sequence number int sequence = tx.input.current.sequence; // Access the current input's outpoint bytes outpoint = tx.input.current.outpoint;

This is more intuitive than using an index variable:

// Old approach (less intuitive) int currentIndex = 0; // Assume the current input is at index 0 int currentValue = tx.inputs[currentIndex].value; // New approach (more intuitive) int currentValue = tx.input.current.value;

Variable Declarations

You can declare variables to store intermediate values:

bytes message = sha256(timestamp + currentPrice + assetPair); bytes p2trScript = new P2TR(internalKey, assetCommitmentHash);

Error Messages

You can provide custom error messages for require statements:

require(tx.time >= expirationTimeout, "Expiration timeout not reached");

Artifact Format

TapLang compiles contracts to a JSON format that can be used with Bitcoin Taproot libraries.

JSON Structure

{ "contractName": "MyContract", "constructorInputs": [ { "name": "user", "type": "pubkey" }, { "name": "server", "type": "pubkey" } ], "functions": [ { "name": "spend", "functionInputs": [ { "name": "userSig", "type": "signature" } ], "serverVariant": true, "require": [ { "type": "signature" }, { "type": "serverSignature" } ], "asm": [ "<user>", "<userSig>", "OP_CHECKSIG", "<SERVER_KEY>", "<serverSig>", "OP_CHECKSIG" ] }, { "name": "spend", "functionInputs": [ { "name": "userSig", "type": "signature" } ], "serverVariant": false, "require": [ { "type": "signature" }, { "type": "older", "message": "Exit timelock of 144 blocks" } ], "asm": [ "<user>", "<userSig>", "OP_CHECKSIG", "144", "OP_CHECKLOCKTIMEVERIFY", "OP_DROP" ] } ], "source": "...", "compiler": { "name": "taplang", "version": "0.1.0" }, "updatedAt": "2023-03-06T01:27:51.391557+00:00" }

Key Components

  • contractName: The name of the contract
  • constructorInputs: The parameters required to instantiate the contract
  • functions: The spending paths of the contract
    • Each function has two variants:
      • serverVariant: true: Requires server signature (cooperative path)
      • serverVariant: false: Requires timelock (exit path)
  • require: The requirements for each spending path
  • asm: The Bitcoin Script assembly code for each spending path

About

EXPERIMENTAL A high-level language that compiles to Arkade Script for building Bitcoin-native smart contracts on Arkade OS

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages