Smart Contracts

Architecture: Thin L2, Fat L1

BINST follows a "Thin L2, Fat L1" architecture. Bitcoin L1 is the source of truth for identity (institutions) and definitions (process templates). The L2 only holds execution state.

Two contracts make up the L2 layer:

ContractPurpose
BINSTProcessFactoryThin factory — deploys process instances, indexes by user and template
BINSTProcessSelf-contained instance — embedded steps, L1 anchor, migration-ready

Bitcoin owns identity and definitions; L2 only owns execution.


Architecture

BINSTProcessFactory

A thin factory deployed once per L2 chain. Creates self-contained process instances and maintains indexing by user and by L1 template.

Deployed addresses:

NetworkAddressChain ID
Citrea testnet0x6a1d2adbac8682773ed6700d2118c709c8ce50005115
Hardhat local0x9fe46736679d2d9a65f0992f2272de9f3c7fa6e031337

Key functions:

FunctionSelectorPurpose
createInstance(string, string[], string[])0x6f794b70Deploy a new BINSTProcess instance
getTemplateInstances(string)0xb43bed00All instances for a given L1 template inscription ID
getUserInstances(address)0xfceaae17All instances created by an address
getInstanceCount()0xae34325cTotal instance count
allInstances(uint256)0x9b0dc489Instance address by global index

Events:

EventTopic0
InstanceCreated(address indexed, address indexed, string, uint256)0xa3de24f67beb…0700f

BINSTProcess

A self-contained process instance anchored to a Bitcoin L1 inscription. Each instance carries its own step definitions — no dependency on separate contracts on L2.

Why self-contained? Cross-chain migration. When a partially-executed process migrates from Citrea to another L2 (via LayerZero), the receiving chain only needs one message:

{templateInscriptionId, steps[], currentStepIndex, stepStates[], creator}

No pre-deployed contracts are required on the destination chain.

Provenance chain (L2 → L1):

BINSTProcess.templateInscriptionId
  → look up on Bitcoin (mempool.space / ord indexer)
  → read inscription body → institution_id, steps, admin pubkey
  → verify the L1 hierarchy: institution → template → (this instance)

Key state:

string public templateInscriptionId;   // L1 anchor — Bitcoin inscription ID
Step[] public steps;                    // Step definitions (name + actionType)
address public creator;                 // Who created this instance
uint256 public currentStepIndex;        // Current step (0-based)
bool public completed;                  // True when all steps done
mapping(uint256 => StepState) public stepStates;

// Migration fields (future — Phase 65)
uint32 public sourceChainId;           // 0 if native, non-zero if migrated
address public sourceAddress;          // Address on source chain

Key functions:

FunctionSelectorPurpose
executeStep(uint8, string)0xf16e3a23Execute the current step with status + evidence
currentStepIndex()0x334f45ecRead current step index
completed()0x9d9a7fe9Check if all steps are done
totalSteps()0x6931b3aeTotal number of steps
creator()0x02d05d3fAddress of the instance creator
templateInscriptionId()0x0270a0b3The L1 inscription this instance is anchored to

Step status enum: 0 = Pending, 1 = Completed, 2 = Rejected

Events:

EventTopic0
StepExecuted(uint256 indexed, address indexed, uint8, string)0x5b89aea5…1b82
ProcessCompleted(address indexed, uint256)(keccak256 of signature)

Instance Lifecycle

1. Factory deployed once per L2 chain
   └─ Citrea testnet: 0x6a1d…5000

2. User creates instance from the Execute view
   └─ factory.createInstance(inscriptionId, stepNames[], stepActionTypes[])
   └─ InstanceCreated event → instance address parsed from receipt
   └─ Instance address saved to localStorage and shown in the UI

3. Steps executed sequentially
   └─ instance.executeStep(Completed, evidenceData)
   └─ StepExecuted event emitted per step
   └─ currentStepIndex advances; UI reloads state from contract

4. Process completes
   └─ instance.completed == true after final step
   └─ ProcessCompleted event emitted

5. Bitcoin settlement (automatic — no user action)
   └─ Soft Confirmation: sequencer signs the L2 block (~instant)
   └─ Committed: sequencer commitment inscribed on Bitcoin
   └─ Proven: ZK batch proof inscribed on Bitcoin
   └─ All L2 state is now part of the Bitcoin permanent record

6. Migration (future — Phase 65)
   └─ LayerZero _lzSend() carries full state to destination L2
   └─ Destination deploys new BINSTProcess pre-loaded with state

Bitcoin Settlement Finality

Every L2 transaction goes through three finality stages before reaching Bitcoin permanence:

StageMeaningVerification
Soft ConfirmationSequencer has ordered the tx in an L2 blockTransaction receipt
CommittedSequencer commitment inscribed on Bitcoincitrea_getLastCommittedL2Height
ProvenZK proof of correct execution inscribed on Bitcoincitrea_getLastProvenL2Height

The webapp tracks these stages per-transaction (instance creation and each step execution) and displays finality badges in the Execute view. See ZK Batch Proofs for the full model.


EVM Configuration

Citrea does not support Cancun opcodes. Target Shanghai:

solidity: {
  version: "0.8.24",
  settings: { evmVersion: "shanghai", optimizer: { enabled: true, runs: 200 } }
}

Solidity Tests

24 tests in test/BINSTPilot.ts (Hardhat 3, node:test):

  • 10 active contract tests (BINSTProcessFactory, BINSTProcess)
  • 14 additional tests covering extended contract lifecycles