diff --git a/src/App.tsx b/src/App.tsx index f2a435a..b24f6c3 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -35,6 +35,9 @@ import { EthereumTransactionsLogsPage, EthereumTransactionsGasPage, EthereumTransactionsGasPricePage, + Layer2Page, + StateChannelsPage, + RollupsPage, } from './pages' function App() { @@ -66,6 +69,9 @@ function App() { } /> } /> } /> + } /> + } /> + } /> } /> } /> } /> diff --git a/src/config/navigation.ts b/src/config/navigation.ts index e7d1868..1bf5495 100644 --- a/src/config/navigation.ts +++ b/src/config/navigation.ts @@ -52,6 +52,14 @@ export const navConfig: NavCategory[] = [ ], }, { label: 'Blockspace', path: 'blockspace' }, + { + label: 'Layer 2 Scaling', + path: 'layer-2', + children: [ + { label: 'State Channels', path: 'layer-2/state-channels' }, + { label: 'Rollups', path: 'layer-2/rollups' }, + ], + }, { label: 'Transaction Inclusion', path: 'transaction-inclusion', diff --git a/src/pages/index.ts b/src/pages/index.ts index ff67018..25bb3c3 100644 --- a/src/pages/index.ts +++ b/src/pages/index.ts @@ -22,6 +22,9 @@ export { ConsensusPage } from './consensus/ConsensusPage' export { ConsensusProofOfWorkPage } from './consensus/ConsensusProofOfWorkPage' export { ConsensusProofOfStakePage } from './consensus/ConsensusProofOfStakePage' export { BlockspacePage } from './blockspace/BlockspacePage' +export { Layer2Page } from './layer-2/Layer2Page' +export { StateChannelsPage } from './layer-2/StateChannelsPage' +export { RollupsPage } from './layer-2/RollupsPage' export { TransactionInclusionPage } from './transaction-inclusion/TransactionInclusionPage' export { TransactionInclusionNoncePage } from './transaction-inclusion/TransactionInclusionNoncePage' export { TransactionInclusionReplacingPage } from './transaction-inclusion/TransactionInclusionReplacingPage' diff --git a/src/pages/layer-2/Layer2Page.tsx b/src/pages/layer-2/Layer2Page.tsx new file mode 100644 index 0000000..5cf08db --- /dev/null +++ b/src/pages/layer-2/Layer2Page.tsx @@ -0,0 +1,76 @@ +import { Link } from 'react-router-dom' + +export function Layer2Page() { + return ( +
+

Layer 2 Scaling

+

+ Layer 1 blockchains are intentionally slow. They cap throughput to keep every node in + sync and preserve decentralization. Layer 2 solutions run on top, processing transactions + off-chain and anchoring trust back to the base chain. +

+ +

+ + The scaling problem + +

+

+ Every node on a blockchain processes every transaction. This is what makes it trustless: + you don't have to trust any single operator because you can verify the chain yourself. But + it also means throughput is bounded by what the slowest full node can handle. +

+

+ Blockspace is capped to keep block production predictable. + When demand exceeds capacity, fees rise and transactions queue up. You can't simply raise + the block limit without making it harder to run a node, which weakens decentralization. +

+ +

+ + The trilemma + +

+

+ Blockchains face a tradeoff between three properties:{' '} + decentralization, security, and{' '} + scalability. Optimizing for any two typically degrades the third. A + network with very high throughput either relies on powerful hardware (centralizing who + can run a node) or weakens its security guarantees. +

+

+ Layer 2 approaches this differently: offload execution to a separate system, but inherit + the security of Layer 1 for the final settlement. The L2 can be faster and cheaper because + it doesn't require global consensus for every transaction; only the final result needs to + be verifiable on-chain. +

+ +

+ + Two main approaches + +

+

There are two broad families of Layer 2 solutions:

+
    +
  • + State Channels: two or more parties lock + funds on-chain, then exchange signed state updates directly off-chain. Only the opening + and closing transactions touch the base chain. +
  • +
  • + Rollups: a sequencer batches many transactions, + posts compressed data to L1, and includes a proof that the state transition was valid. + Suitable for general-purpose applications with many users. +
  • +
+ +

+ State Channels + {' · '} + Rollups + {' · '} + Blockspace +

+
+ ) +} diff --git a/src/pages/layer-2/RollupsDemo.css b/src/pages/layer-2/RollupsDemo.css new file mode 100644 index 0000000..ff5011d --- /dev/null +++ b/src/pages/layer-2/RollupsDemo.css @@ -0,0 +1,212 @@ +.rollups-demo { + display: flex; + flex-direction: column; + gap: 1rem; + padding: 1.5rem; + margin: 1rem 0; + background: rgba(30, 41, 59, 0.4); + border-radius: 8px; + border: 1px solid rgba(6, 182, 212, 0.3); +} + +.rollups-mode-toggle { + display: flex; + gap: 0.5rem; +} + +.rollups-mode-btn { + padding: 0.3rem 0.85rem; + font-size: 12px; + font-weight: 600; + border-radius: 4px; + border: 1px solid rgba(51, 65, 85, 0.8); + background: transparent; + color: rgba(148, 163, 184, 0.8); + cursor: pointer; + transition: all 0.15s; +} + +.rollups-mode-btn.active { + background: rgba(6, 182, 212, 0.15); + border-color: rgba(6, 182, 212, 0.5); + color: #06b6d4; +} + +.rollups-zones { + display: flex; + align-items: flex-start; + gap: 0.75rem; +} + +.rollups-zone { + flex: 1; + display: flex; + flex-direction: column; + gap: 0.5rem; + padding: 0.75rem; + background: rgba(15, 23, 42, 0.5); + border-radius: 6px; + border: 1px solid rgba(51, 65, 85, 0.6); + min-height: 150px; +} + +.rollups-zone-title { + font-size: 11px; + font-weight: 600; + color: rgba(148, 163, 184, 0.85); + text-transform: uppercase; + letter-spacing: 0.05em; +} + +.rollups-tx-list { + display: flex; + flex-direction: column; + gap: 0.25rem; + flex: 1; +} + +.rollups-tx { + padding: 0.2rem 0.5rem; + font-size: 12px; + color: #e2e8f0; + background: rgba(51, 65, 85, 0.4); + border-radius: 3px; + border: 1px solid rgba(71, 85, 105, 0.5); +} + +.rollups-empty { + font-size: 12px; + color: rgba(148, 163, 184, 0.5); + font-style: italic; +} + +.rollups-zone-actions { + display: flex; + gap: 0.4rem; + flex-wrap: wrap; +} + +.rollups-arrow { + align-self: center; + font-size: 20px; + color: rgba(6, 182, 212, 0.6); + flex: 0 0 auto; +} + +.rollups-zone-l1 { + border-color: rgba(139, 92, 246, 0.3); +} + +.rollups-post-list { + display: flex; + flex-direction: column; + gap: 0.4rem; + flex: 1; +} + +.rollups-post { + padding: 0.5rem 0.6rem; + border-radius: 4px; + border: 1px solid; + display: flex; + flex-direction: column; + gap: 0.2rem; +} + +.rollups-post-optimistic { + background: rgba(234, 179, 8, 0.08); + border-color: rgba(234, 179, 8, 0.3); +} + +.rollups-post-zk { + background: rgba(139, 92, 246, 0.1); + border-color: rgba(139, 92, 246, 0.35); +} + +.rollups-post-header { + display: flex; + align-items: center; + justify-content: space-between; + gap: 0.5rem; +} + +.rollups-post-count { + font-size: 13px; + font-weight: 600; + color: #e2e8f0; +} + +.rollups-finality-badge { + font-size: 10px; + font-weight: 600; + padding: 0.15rem 0.4rem; + border-radius: 3px; +} + +.rollups-finality-optimistic { + background: rgba(234, 179, 8, 0.15); + color: #eab308; +} + +.rollups-finality-zk { + background: rgba(139, 92, 246, 0.15); + color: #a78bfa; +} + +.rollups-post-root { + font-size: 10px; + color: rgba(148, 163, 184, 0.7); + font-family: monospace; +} + +.rollups-btn { + padding: 0.3rem 0.7rem; + font-size: 12px; + font-weight: 600; + border-radius: 4px; + border: 1px solid rgba(51, 65, 85, 0.8); + background: rgba(51, 65, 85, 0.3); + color: rgba(203, 213, 225, 0.9); + cursor: pointer; + transition: background 0.15s; +} + +.rollups-btn:hover:not(:disabled) { + background: rgba(71, 85, 105, 0.5); +} + +.rollups-btn:disabled { + opacity: 0.4; + cursor: not-allowed; +} + +.rollups-btn-primary { + background: rgba(6, 182, 212, 0.12); + border-color: rgba(6, 182, 212, 0.4); + color: #06b6d4; +} + +.rollups-btn-primary:hover:not(:disabled) { + background: rgba(6, 182, 212, 0.22); +} + +.rollups-stat { + font-size: 13px; + font-weight: 600; + color: rgba(148, 163, 184, 0.9); + padding: 0.5rem 0.75rem; + background: rgba(15, 23, 42, 0.4); + border-radius: 4px; + text-align: center; +} + +.rollups-controls { + display: flex; + gap: 0.5rem; +} + +.rollups-caption { + margin: 0; + font-size: 12px; + color: rgba(148, 163, 184, 0.9); +} diff --git a/src/pages/layer-2/RollupsDemo.tsx b/src/pages/layer-2/RollupsDemo.tsx new file mode 100644 index 0000000..00c4141 --- /dev/null +++ b/src/pages/layer-2/RollupsDemo.tsx @@ -0,0 +1,159 @@ +import { useState } from 'react' +import './RollupsDemo.css' + +type RollupMode = 'optimistic' | 'zk' + +interface Tx { + id: number + label: string +} + +interface BatchPost { + id: number + txCount: number + stateRoot: string + mode: RollupMode +} + +const TX_LABELS = ['Transfer', 'Swap', 'Mint', 'Approve', 'Stake', 'Bridge', 'Vote', 'Burn'] +const INITIAL_TXS: Tx[] = TX_LABELS.slice(0, 5).map((label, i) => ({ id: i, label })) + +function shortHash(seed: number): string { + const chars = '0123456789abcdef' + let h = '0x' + for (let i = 0; i < 8; i++) { + h += chars[((seed * 31 + i * 17) >>> 0) % 16] + } + return h + '...' +} + +export function RollupsDemo() { + const [mode, setMode] = useState('optimistic') + const [pendingTxs, setPendingTxs] = useState(INITIAL_TXS) + const [l1Posts, setL1Posts] = useState([]) + const [nextId, setNextId] = useState(INITIAL_TXS.length) + + function addTx() { + setNextId((id) => { + const label = TX_LABELS[id % TX_LABELS.length] + setPendingTxs((txs) => [...txs, { id, label }]) + return id + 1 + }) + } + + function batchAndPost() { + if (pendingTxs.length === 0) return + const count = pendingTxs.length + setL1Posts((posts) => [ + ...posts, + { + id: posts.length, + txCount: count, + stateRoot: shortHash(nextId + posts.length), + mode, + }, + ]) + setPendingTxs([]) + } + + function reset() { + setPendingTxs(INITIAL_TXS) + setL1Posts([]) + setNextId(INITIAL_TXS.length) + } + + const batchedTxCount = l1Posts.reduce((sum, p) => sum + p.txCount, 0) + + return ( +
+
+ + +
+ +
+
+
L2 Pending
+
+ {pendingTxs.length === 0 && ( + No pending transactions + )} + {pendingTxs.map((tx) => ( +
+ {tx.label} +
+ ))} +
+
+ + +
+
+ + + +
+
L1 Chain
+
+ {l1Posts.length === 0 && ( + No batches posted yet + )} + {l1Posts.map((post) => ( +
+
+ {post.txCount} txs + + {post.mode === 'zk' ? 'Instant finality' : '~7 day window'} + +
+
State root: {post.stateRoot}
+
+ ))} +
+
+
+ + {batchedTxCount > 0 && ( +
+ {batchedTxCount} transaction{batchedTxCount !== 1 ? 's' : ''} → {l1Posts.length} L1 + post{l1Posts.length !== 1 ? 's' : ''} +
+ )} + +
+ +
+ +

+ {mode === 'optimistic' + ? 'Optimistic: batches are assumed valid. A 7-day challenge window allows fraud proofs before finality.' + : 'ZK: a validity proof is generated for each batch. The L1 verifies it instantly, with no waiting period needed.'} +

+
+ ) +} diff --git a/src/pages/layer-2/RollupsPage.tsx b/src/pages/layer-2/RollupsPage.tsx new file mode 100644 index 0000000..745e76d --- /dev/null +++ b/src/pages/layer-2/RollupsPage.tsx @@ -0,0 +1,119 @@ +import { Link } from 'react-router-dom' +import { RollupsDemo } from './RollupsDemo' + +export function RollupsPage() { + return ( +
+

Rollups

+

+ A rollup executes transactions off-chain in bulk, then posts compressed data and a + correctness proof to Layer 1. One on-chain post covers thousands of transactions. +

+ +

+ + The core idea + +

+

+ On a plain Layer 1, every node re-executes every transaction. Rollups flip this: a{' '} + sequencer batches many transactions and executes them off-chain. It then + posts to L1 only what's needed to verify the result: +

+
    +
  • Compressed transaction data, so anyone can reconstruct the L2 state independently
  • +
  • The new state root (a hash representing all account balances after the batch)
  • +
  • A proof that the state transition from old root to new root was correct
  • +
+

+ The L1 doesn't re-execute the transactions. It only verifies the proof and stores the + data. This is dramatically cheaper per transaction than running everything on L1. +

+ + + +

+ + Optimistic rollups + +

+

+ Optimistic rollups assume every batch is valid and don't require an upfront proof. Instead, + they open a challenge window (typically seven days) during which anyone + watching the chain can submit a fraud proof if they detect an invalid + state transition. +

+

+ If no fraud proof is submitted within the window, the batch is finalized. If a valid fraud + proof is accepted, the invalid batch is reverted and the sequencer is penalized. The system + relies on at least one honest party monitoring the chain. +

+

+ The tradeoff: batches are cheap to produce, but users must wait for the challenge period to + expire before they can withdraw funds to L1. Liquidity providers often abstract this away + by advancing funds immediately and collecting the bridged funds later. +

+ +

+ + ZK rollups + +

+

+ ZK rollups generate a cryptographic validity proof alongside every batch. + The L1 verifies this proof, which takes milliseconds, and rejects any batch where the + proof fails. There is no challenge window: if the proof verifies, the state transition + is guaranteed correct. +

+

+ Because correctness is proved upfront, withdrawals can be finalized as soon as the proof + is verified on L1, often within minutes. The tradeoff: generating a validity proof is + computationally expensive, which increases the hardware requirements for sequencers and + can create centralization pressure. +

+ +

+ + Comparison + +

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Optimistic rollupZK rollup
Withdrawal finality~7 days (challenge window)Minutes (after proof verification)
Security modelFraud proofs (requires at least one honest watcher)Validity proofs (cryptographic guarantee)
Sequencer computeLowHigh (proof generation)
EVM compatibilityEasier (run the EVM directly)Harder (must prove EVM execution in a circuit)
+ +

+ Back to Layer 2 Scaling + {' · '} + State Channels +

+
+ ) +} diff --git a/src/pages/layer-2/StateChannelDemo.css b/src/pages/layer-2/StateChannelDemo.css new file mode 100644 index 0000000..e86687c --- /dev/null +++ b/src/pages/layer-2/StateChannelDemo.css @@ -0,0 +1,204 @@ +.sc-demo { + display: flex; + flex-direction: column; + gap: 1rem; + padding: 1.5rem; + margin: 1rem 0; + background: rgba(30, 41, 59, 0.4); + border-radius: 8px; + border: 1px solid rgba(6, 182, 212, 0.3); +} + +.sc-parties { + display: flex; + align-items: center; + gap: 1rem; +} + +.sc-party { + display: flex; + flex-direction: column; + align-items: center; + gap: 0.5rem; + flex: 0 0 auto; + min-width: 110px; +} + +.sc-party-name { + font-size: 14px; + font-weight: 600; + color: #e2e8f0; +} + +.sc-party-balance { + font-size: 24px; + font-weight: 700; + color: #22c55e; +} + +.sc-unit { + font-size: 12px; + font-weight: 400; + color: rgba(148, 163, 184, 0.9); +} + +.sc-pay-btn { + padding: 0.3rem 0.75rem; + font-size: 12px; + font-weight: 600; + color: #0f172a; + background: #06b6d4; + border: none; + border-radius: 4px; + cursor: pointer; + transition: opacity 0.15s; +} + +.sc-pay-btn:hover:not(:disabled) { + opacity: 0.85; +} + +.sc-pay-btn:disabled { + opacity: 0.35; + cursor: not-allowed; +} + +.sc-channel-middle { + flex: 1; + display: flex; + flex-direction: column; + align-items: center; + gap: 0.4rem; +} + +.sc-channel-line { + width: 100%; + height: 2px; + background: rgba(148, 163, 184, 0.2); + border-radius: 1px; + transition: background 0.3s; +} + +.sc-channel-active { + background: rgba(6, 182, 212, 0.6); +} + +.sc-channel-label { + font-size: 11px; + color: rgba(148, 163, 184, 0.8); + text-align: center; +} + +.sc-chain-panel { + display: flex; + flex-direction: column; + gap: 0.5rem; + padding: 0.75rem 1rem; + background: rgba(15, 23, 42, 0.5); + border-radius: 6px; + border: 1px solid rgba(51, 65, 85, 0.6); +} + +.sc-chain-label { + font-size: 11px; + font-weight: 600; + color: rgba(148, 163, 184, 0.85); + text-transform: uppercase; + letter-spacing: 0.05em; +} + +.sc-chain-txs { + display: flex; + flex-direction: column; + gap: 0.35rem; + min-height: 24px; +} + +.sc-chain-tx { + display: flex; + align-items: center; + gap: 0.5rem; + font-size: 13px; + color: #e2e8f0; +} + +.sc-tx-tag { + font-size: 11px; + font-weight: 700; + padding: 0.1rem 0.35rem; + border-radius: 3px; + flex: 0 0 auto; +} + +.sc-tx-open .sc-tx-tag { + background: rgba(34, 197, 94, 0.15); + color: #22c55e; + border: 1px solid rgba(34, 197, 94, 0.3); +} + +.sc-tx-close .sc-tx-tag { + background: rgba(6, 182, 212, 0.12); + color: #06b6d4; + border: 1px solid rgba(6, 182, 212, 0.3); +} + +.sc-chain-empty { + font-size: 12px; + color: rgba(148, 163, 184, 0.5); + font-style: italic; +} + +.sc-stat { + font-size: 12px; + font-weight: 600; + color: rgba(148, 163, 184, 0.9); + padding-top: 0.25rem; + border-top: 1px solid rgba(51, 65, 85, 0.5); +} + +.sc-controls { + display: flex; + gap: 0.75rem; +} + +.sc-btn { + padding: 0.45rem 1rem; + font-size: 13px; + font-weight: 600; + border-radius: 4px; + border: 1px solid rgba(6, 182, 212, 0.5); + background: rgba(6, 182, 212, 0.1); + color: #06b6d4; + cursor: pointer; + transition: background 0.15s; +} + +.sc-btn:hover { + background: rgba(6, 182, 212, 0.2); +} + +.sc-btn-primary { + background: rgba(34, 197, 94, 0.15); + border-color: rgba(34, 197, 94, 0.5); + color: #22c55e; +} + +.sc-btn-primary:hover { + background: rgba(34, 197, 94, 0.25); +} + +.sc-btn-danger { + background: rgba(239, 68, 68, 0.1); + border-color: rgba(239, 68, 68, 0.4); + color: #ef4444; +} + +.sc-btn-danger:hover { + background: rgba(239, 68, 68, 0.2); +} + +.sc-caption { + margin: 0; + font-size: 12px; + color: rgba(148, 163, 184, 0.9); +} diff --git a/src/pages/layer-2/StateChannelDemo.tsx b/src/pages/layer-2/StateChannelDemo.tsx new file mode 100644 index 0000000..c308b35 --- /dev/null +++ b/src/pages/layer-2/StateChannelDemo.tsx @@ -0,0 +1,169 @@ +import { useState } from 'react' +import './StateChannelDemo.css' + +const INITIAL_BALANCE = 5 + +type ChannelStatus = 'closed' | 'open' | 'settled' + +interface ChannelState { + status: ChannelStatus + aliceBalance: number + bobBalance: number + offChainTxCount: number +} + +export function StateChannelDemo() { + const [channel, setChannel] = useState({ + status: 'closed', + aliceBalance: INITIAL_BALANCE, + bobBalance: INITIAL_BALANCE, + offChainTxCount: 0, + }) + const [onChainTxCount, setOnChainTxCount] = useState(0) + + function openChannel() { + setChannel({ + status: 'open', + aliceBalance: INITIAL_BALANCE, + bobBalance: INITIAL_BALANCE, + offChainTxCount: 0, + }) + setOnChainTxCount(1) + } + + function pay(from: 'alice' | 'bob') { + setChannel((prev) => { + if (prev.status !== 'open') return prev + const newAlice = from === 'alice' ? prev.aliceBalance - 1 : prev.aliceBalance + 1 + const newBob = from === 'bob' ? prev.bobBalance - 1 : prev.bobBalance + 1 + if (newAlice < 0 || newBob < 0) return prev + return { + ...prev, + aliceBalance: newAlice, + bobBalance: newBob, + offChainTxCount: prev.offChainTxCount + 1, + } + }) + } + + function closeChannel() { + setChannel((prev) => (prev.status === 'open' ? { ...prev, status: 'settled' } : prev)) + setOnChainTxCount(2) + } + + function reset() { + setChannel({ + status: 'closed', + aliceBalance: INITIAL_BALANCE, + bobBalance: INITIAL_BALANCE, + offChainTxCount: 0, + }) + setOnChainTxCount(0) + } + + const { status, aliceBalance, bobBalance, offChainTxCount } = channel + const isOpen = status === 'open' + const isSettled = status === 'settled' + const isClosed = status === 'closed' + + return ( +
+
+
+ Alice + + {aliceBalance} coins + + {isOpen && ( + + )} +
+ +
+
+
+ {isClosed && 'No channel'} + {isOpen && + `${offChainTxCount} off-chain tx${offChainTxCount !== 1 ? 's' : ''}`} + {isSettled && 'Settled'} +
+
+ +
+ Bob + + {bobBalance} coins + + {isOpen && ( + + )} +
+
+ +
+
On-chain activity
+
+ {onChainTxCount === 0 && ( + No transactions yet + )} + {onChainTxCount >= 1 && ( +
+ Open + + Channel funded: {INITIAL_BALANCE} + {INITIAL_BALANCE} locked + +
+ )} + {onChainTxCount >= 2 && ( +
+ Close + + Settled: Alice {aliceBalance} · Bob {bobBalance} + +
+ )} +
+
+ {offChainTxCount} off-chain · {onChainTxCount} on-chain +
+
+ +
+ {isClosed && ( + + )} + {isOpen && ( + + )} + {isSettled && ( + + )} +
+ +

+ {isClosed && 'Open a channel to start exchanging off-chain payments.'} + {isOpen && 'Payments between Alice and Bob happen instantly, off-chain.'} + {isSettled && + `${offChainTxCount} payment${offChainTxCount !== 1 ? 's' : ''} settled with only 2 on-chain transactions.`} +

+
+ ) +} diff --git a/src/pages/layer-2/StateChannelsPage.tsx b/src/pages/layer-2/StateChannelsPage.tsx new file mode 100644 index 0000000..34be572 --- /dev/null +++ b/src/pages/layer-2/StateChannelsPage.tsx @@ -0,0 +1,96 @@ +import { Link } from 'react-router-dom' +import { StateChannelDemo } from './StateChannelDemo' + +export function StateChannelsPage() { + return ( +
+

State Channels

+

+ Two parties can exchange thousands of signed state updates off-chain, then settle the final + result with only two on-chain transactions: one to open the channel, one to close it. +

+ +

+ + Opening a channel + +

+

+ To open a channel, both parties submit an on-chain transaction that locks their funds into + a shared contract. This is the funding transaction. From this point, the + blockchain holds the funds as collateral, but the parties interact directly with each + other, not through the chain. +

+ +

+ + Off-chain payments + +

+

+ Each payment is a signed message: a new state update reflecting who owns what. Both parties + sign each update. The latest co-signed state is always the valid one; older states are + superseded and can be rejected. None of these messages touch the chain; they are instant + and cost nothing to exchange. +

+

+ The parties don't need a miner, validator, or any third party to process their payments. + They only need each other, and the chain as a backstop if something goes wrong. +

+ + + +

+ + Closing a channel + +

+

+ Either party can submit the latest co-signed state to the chain at any time to close the + channel cooperatively. The contract verifies the signatures and distributes the locked funds + according to the final balances. This is the second and last on-chain transaction. +

+ +

+ + Dispute resolution + +

+

+ If one party goes offline or tries to close the channel using an outdated state (to reclaim + funds they already spent), the other party has a challenge period to + submit a newer co-signed state as evidence. The contract accepts the most recent valid state + and penalizes the dishonest party. +

+

+ This means both parties must remain able to respond within the challenge window. If you go + offline for too long, a dishonest counterparty could finalize an old state unchallenged. +

+ +

+ + Limitations + +

+

+ State channels excel at repeated bilateral interactions, like a streaming payment between + two known parties. They are not well suited for: +

+
    +
  • Open-ended interactions with unknown or many counterparties
  • +
  • Applications requiring global shared state, such as a public marketplace
  • +
  • Users who need to go offline for extended periods
  • +
+

+ For general-purpose scaling, where any user can interact with any contract,{' '} + rollups are the more practical solution. +

+ +

+ Back to Layer 2 Scaling + {' · '} + Rollups +

+
+ ) +}