Phase 2 – Coming Soon. This tutorial describes synchronous composability, which requires the extended Computation DAG with per-account modeling. Phase 1 (standalone vProgs) must ship first. The concepts and architecture described here are based on the published research and are subject to change.
Compose multiple vProgs in a single atomic L1 transaction: borrow from a lending vProg, swap on a DEX vProg, and stake on a staking vProg. No bridges, no delays, no fragmentation.
What You Will Build
A single transaction that atomically:
- Borrows 10,000 KAS from a lending vProg
- Swaps KAS for a stablecoin on a DEX vProg
- Stakes the stablecoin on a yield vProg
- Repays the lending vProg with interest
All four operations execute as one atomic unit. If any step fails, the entire transaction reverts.
Why This Matters
Without synchronous composability, this flow requires:
Traditional L2 approach:
1. Borrow on Rollup A -> Wait for finality
2. Bridge to Rollup B -> Wait for bridge (minutes to hours)
3. Swap on Rollup B -> Wait for finality
4. Bridge to Rollup C -> Wait for bridge
5. Stake on Rollup C -> Wait for finality
Total: 5 transactions, bridge risk, liquidity fragmentation
With vProgs synchronous composability:
vProgs approach:
1. Compute all operations off-chain
2. Generate combined ZK proof (proof stitching)
3. Submit single atomic transaction to L1
4. Instant finality via DagKnight
Total: 1 transaction, zero bridge risk, unified liquidity
Prerequisites
- Completed all previous tutorials
- Understanding of the vProgs account model
- Familiarity with concise witnesses (see below)
Key Concept: Concise Witnesses
Synchronous composability is enabled by concise witnesses:
- Each vProg maintains state as hierarchical Merkle roots
- The commitment
C_p^tis a Merkle root over per-step state roots since the last proof - Any intermediate account state can be proven with a compact Merkle inclusion proof
- vProg A can instantly verify vProg B’s state in the same transaction
This means cross-vProg reads are cheap: just a Merkle inclusion proof, not a full state transfer.
Step 1: Declare Read/Write Sets
Every vProgs transaction declares its account dependencies upfront, similar to Solana’s account model:
// [Phase 2 -- Coming Soon]
// Pseudocode illustrating the intended API
let tx = CrossVprogTransaction::new()
// Read accounts (r(x)) -- state we need to inspect
.read(lending_vprog, borrower_account)
.read(dex_vprog, kas_stable_pool)
.read(staking_vprog, stable_vault)
// Write accounts (w(x)) -- state we will modify
// Constraint: w(x) must be a subset of r(x)
.write(lending_vprog, borrower_account)
.write(dex_vprog, kas_stable_pool)
.write(staking_vprog, stable_vault)
// Witness set -- dependency resolution data
.witness(lending_vprog, lending_state_proof)
.witness(dex_vprog, dex_state_proof)
.witness(staking_vprog, staking_state_proof);
Pre-declaring read/write sets enables parallel processing on the BlockDAG. Transactions with non-overlapping write sets can execute concurrently.
Step 2: Define the Composite Operation
// [Phase 2 -- Coming Soon]
/// Execute a borrow-swap-stake operation across three vProgs.
/// All operations are computed off-chain; only the proof goes to L1.
fn borrow_swap_stake(
// Current state of all involved accounts
lending_state: &LendingAccountState,
dex_state: &DexPoolState,
staking_state: &StakingVaultState,
// Operation parameters
borrow_amount: u64,
min_stable_out: u64,
) -> Result<CompositeTransition, TransitionError> {
// Step 1: Borrow from lending vProg
let (new_lending_state, borrowed_kas) = lending_transition(
lending_state,
LendingAction::Borrow {
amount: borrow_amount,
collateral_ratio: 150, // 150% collateralized
},
)?;
// Step 2: Swap KAS -> stablecoin on DEX vProg
let (new_dex_state, stable_received) = dex_transition(
dex_state,
DexAction::Swap {
input_token: Token::KAS,
input_amount: borrowed_kas,
output_token: Token::KUSD,
min_output: min_stable_out,
},
)?;
// Step 3: Stake stablecoin on yield vProg
let (new_staking_state, stake_receipt) = staking_transition(
staking_state,
StakingAction::Stake {
token: Token::KUSD,
amount: stable_received,
},
)?;
// Return the composite transition
Ok(CompositeTransition {
transitions: vec![
(LENDING_VPROG_ID, new_lending_state),
(DEX_VPROG_ID, new_dex_state),
(STAKING_VPROG_ID, new_staking_state),
],
receipt: stake_receipt,
})
}
Step 3: Generate the Combined Proof
// [Phase 2 -- Coming Soon]
/// Generate a single ZK proof covering all three state transitions.
/// Proof stitching combines individual transition proofs into one
/// composite proof that L1 can verify in constant time.
fn prove_composite(
composite: &CompositeTransition,
witnesses: &[ConciseWitness],
) -> Result<Proof, ProvingError> {
let prover = Prover::new(ZkBackend::RiscZero);
// Each vProg transition is proven independently
let lending_proof = prover.prove_transition(
&composite.transitions[0],
&witnesses[0],
)?;
let dex_proof = prover.prove_transition(
&composite.transitions[1],
&witnesses[1],
)?;
let staking_proof = prover.prove_transition(
&composite.transitions[2],
&witnesses[2],
)?;
// Stitch proofs together into a single composite proof
let composite_proof = prover.stitch(vec![
lending_proof,
dex_proof,
staking_proof,
])?;
Ok(composite_proof)
}
Step 4: Submit to L1
// [Phase 2 -- Coming Soon]
async fn submit_composite(
client: &VprogsClient,
tx: CrossVprogTransaction,
proof: Proof,
) -> Result<TxId, SubmitError> {
// L1 validates:
// 1. All read/write set declarations are consistent
// 2. Concise witnesses prove correct prior state
// 3. ZK proof attests to correct execution of ALL transitions
// 4. No conflicting writes with concurrent transactions
let tx_id = client.submit_composite_proof(tx, proof).await?;
// DagKnight provides instant finality
println!("Composite transaction finalized: {}", tx_id);
Ok(tx_id)
}
Safety Mechanisms
Cross-vProg transactions include two critical safety features:
Read-Fail Safeguards
- Transactions must begin by reading all declared-read accounts
- If any read fails (e.g., stale witness), the entire transaction is rejected before any writes occur
Gas Commitments
- Gas must be paid to each vProg whose state is modified
- Each vProg controls its own gas scales and STORM constants
- Gas commitments prevent “failure to write” from causing negative consequences
Architecture Requirements (Phase 2)
Synchronous composability requires infrastructure beyond Phase 1:
| Component | Status | Description |
|---|---|---|
| Extended Computation DAG | Research | Per-account modeling in the CDAG |
| Concise witnesses | Specified | Merkle inclusion proofs for cross-vProg reads |
| Proof stitching | Research | Combining multiple transition proofs |
| CD-based scope gas | Research | Gas calculations based on computational scope |
| Global vProg registry | Research | Registration and discovery of vProgs |
| Transaction v2 | Research | New transaction format with read/write declarations |
Next Steps
- API Reference – RPC endpoints for vProgs interaction
- Example Projects – Reference implementations
- Silverscript Reference – Covenant programming for local state