# Introduction

A programmable Ephemeral Rollup platform on Sonic SVM.

NorthStar is a programmable Ephemeral Rollup (ER) platform on Sonic SVM. It lets a developer carve out a single-tenant, isolated runtime — a **session** — anchored to Solana L1, run logic at custom slot cadence inside it, and atomically settle state back to L1 when the session closes.

Same Solana programs. Same SDK shapes. Different latency, different fee policy, different blast radius.

## What problem does it solve

A single global blockchain is a bad fit for workloads that need:

* **Sub-50ms confirmation** — agents reasoning faster than human perception, market makers responding to mid-quote shifts, real-time games.
* **Per-app fee economics** — gasless reads for end users, custom fee schedules for high-frequency strategies, MEV-aware policies.
* **Isolated execution boundaries** — one tenant's congestion can't slow another's confirmations.

Today's options force a choice between L1 (general, secure, slow, fixed fee policy) and an offchain server (fast, but you've left the trust model behind). NorthStar splits the difference: an L1-anchored, single-tenant runtime that inherits Solana's security and gives the operator the dials.

## What you actually get

A session is a bounded execution context with five properties the operator chooses:

| Knob                   | What it controls                                         |
| ---------------------- | -------------------------------------------------------- |
| **Grid id**            | Identifier; one session per `(owner, grid_id)`           |
| **TTL**                | Maximum lifespan in slots before forced settlement       |
| **Fee cap**            | Lamport budget for internal accounting                   |
| **Fee structure**      | The schedule that prices instructions inside the session |
| **Delegated accounts** | The state the session is allowed to write                |

Inside the session, transactions execute against a private Solana validator, with confirmation at the operator's chosen slot cadence (current devnet default: 400ms; the path to 10ms is in the [Real-time confirmation](/architecture/real-time) docs). Outside the session, those same accounts are **locked on L1** — no transaction signed by anyone, including the original owner, can mutate them until the session closes. When it does, every change settles back atomically.

## Who it's for

* **dApp developers** who already ship Solana programs and want isolation, custom cadence, or programmable economics for a specific surface (orderbook, AMM, agent harness) without rewriting their stack.
* **Agent builders** running model-driven trading or coordination loops where end-to-end latency dominates and global slot cadence is a tax.
* **Protocol designers** who want application-specific MEV policies, custom fee curves, or tenant isolation as first-class primitives.

If you've shipped a Solana program before, the SDK is the same shape you already know. If you haven't, [Hello World](/getting-started/hello-world) is \~30 lines.

## Where to start

| If you want to...                   | Read                                                                                                              |
| ----------------------------------- | ----------------------------------------------------------------------------------------------------------------- |
| See it work end-to-end in 5 minutes | [Hello World](/getting-started/hello-world)                                                                       |
| Understand the formal model         | [Sessions](/concepts/sessions), [Account delegation](/concepts/delegation)                                        |
| Understand how it's wired           | [ER topology](/architecture/er-topology)                                                                          |
| Understand the trust model          | [Security model](/architecture/security)                                                                          |
| Migrate an existing Solana program  | [Migrate to NorthStar](https://github.com/mirrorworld-universe/northstar-docs/blob/main/content/build/migrate.md) |
| Look up Portal program instructions | [Portal program reference](/reference/portal-program)                                                             |

## What this isn't

* **Not a sidechain.** State lives on Solana L1 throughout. The ER is execution-only; settlement is L1-native.
* **Not multi-tenant.** A session is single-tenant by design — that's the property that makes the cadence + economics knobs safe.
* **Not a general L2.** It's purpose-built for short-lived, application-scoped sessions. Long-running shared state belongs on L1.

## Status

NorthStar is live on Sonic Devnet (the Sonic L1 surface anchored to Solana Devnet). The platform is feature-complete for single-owner sessions; per-user isolation, real-time slot overrides, and the formal challenge protocol are tracked under the public [Linear roadmap](https://linear.app/mirror-world). The [litepaper](https://github.com/mirrorworld-universe/reports) covers the formal model and the path to mainnet.


# Architecture

How NorthStar executes, confirms, and settles session state.

NorthStar splits execution between Solana L1 and an Ephemeral Rollup. This section covers the system behavior that makes sessions fast, isolated, and safe to settle back.

## In this section

* [ER Topology](/architecture/er-topology) — components and the data flow for a full session lifecycle.
* [Real-Time Confirmation](/architecture/real-time) — how confirmation reaches millisecond-scale latency.
* [Security Model](/architecture/security) — trust assumptions, custody guarantees, and the challenge protocol.

## Architectural focus

Use this section to understand:

* where execution happens
* when a transaction is considered confirmed
* how ER state becomes canonical on L1
* what holds when an operator misbehaves

For the user-facing model, start with [Concepts](/concepts). For protocol details, use [Reference](/reference).


# ER Topology

How a NorthStar session is wired across the user, the SDK, the L1 anchor, and the Ephemeral Rollup. This page covers the **components** and the **data path** for the canonical session lifecycle: open → delegate → execute → settle.

## The components

| Component                 | Where it lives                 | Role                                                                                                                                                    |
| ------------------------- | ------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **User / dApp**           | Browser, agent runtime, server | Initiates session lifecycle and submits transactions. Holds the owner keypair.                                                                          |
| **NorthStar SDK**         | Same process as the user       | Wraps the Portal program and the ER RPC. Translates `openSession`, `delegate`, `closeSession` into the corresponding Solana ixs.                        |
| **Router**                | Edge                           | Routes ER-bound traffic to the right validator instance (the `(portal, owner, grid_id)` tuple identifies the session).                                  |
| **Portal (L1)**           | Solana L1 program              | Source of truth for session metadata, delegation records, and settle-back. Locks delegated accounts on L1 for the session's lifespan.                   |
| **Validator**             | Operator infrastructure        | A single-tenant Solana validator dedicated to the session. Spawns on `SessionCreated`, processes ER txs, holds the canonical session state until close. |
| **Ephemeral Rollup (L2)** | Inside the validator           | The runtime where ER transactions execute. Same SVM semantics as L1 — different slot cadence, different fee policy.                                     |

## The data flow

The diagram below traces a full session lifecycle. The columns are actors; time runs top to bottom.

![NorthStar data flow](https://1650709205-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2Fh39xD1Lv8Pc2DHtpKMWm%2Fuploads%2Fgit-blob-3b16b667866bb01e3780facef2a44b45c2a7d1e2%2Fer-topology.png?alt=media)

Five message phases:

### 1. `openSession`

The owner calls `sdk.openSession({ gridId, ttlSlots, feeCap, feeStructure })`. The SDK assembles the Portal `OpenSession` ix and submits it to L1. The Portal:

* Creates the session PDA at `sessionPda(portal, owner, grid_id)`.
* Creates a per-owner `fee_vault` PDA (one-time per owner).
* Pulls the `feeCap` lamports from the owner into the fee vault.
* Emits a `SessionCreated` event.

The validator subscribes to that event and **spawns the ER runtime** for this session. From this point until close, every account delegated to this session is exclusive to this validator instance.

### 2. `delegateAccount`

For each account the session needs to write, the owner calls `sdk.delegate({ account, ownerProgram })`. The Portal:

* Reassigns the account's `owner` field to the Portal program. **This locks it on L1.**
* Creates a delegation record at `delegationRecordPda(portal, account)` with `(grid_id, owner_program, bump)`.
* Emits an `AccountDelegated` event.

The validator picks the event up and marks the account writable inside the ER. Reads come from L1 state at delegation time; writes accumulate locally.

### 3. `sendTransaction`

The owner — or any keypair the session-side program accepts — submits a transaction to the ER's RPC. The Router forwards it to the validator instance hosting this session. The validator:

* Validates the tx against ER state and the session's delegated set.
* Executes inside the SVM at the session's slot cadence.
* Confirms back to the SDK over WebSocket (`signatureSubscribe`).

End-to-end confirmation latency on the same machine as the validator is sub-50ms; over the public internet it floors at the network round-trip. See [real-time confirmation](/architecture/real-time) for the breakdown.

### 4. `closeSession`

When the owner calls `sdk.closeSession({ gridId })` (or the TTL expires), the Portal initiates settlement:

* The validator stashes the final state of every delegated account.
* The Portal walks the delegation set and applies each final state to its corresponding L1 account in a single multi-account write.
* Ownership is restored to the original program.
* Each delegation record is closed; rent flows back to the owner.

The settle is **all-or-nothing**: either every delegated account commits its final state, or none do and the session reverts to its pre-close state for retry. See [settle-back guarantees](/concepts/settle-back) for the failure modes.

### 5. State settled on L1

After the Portal finalizes settlement, the post-session state is the canonical L1 state. Anyone can read it; the original owners can write it again. The session is gone.

## What stays on L1, what's local to the ER

| State                                          | While session is active       | After close                                                 |
| ---------------------------------------------- | ----------------------------- | ----------------------------------------------------------- |
| **Delegated accounts**                         | Writable in ER, locked on L1  | L1 receives final ER state; ownership restored              |
| **Non-delegated accounts**                     | Read-only in ER, normal on L1 | Unaffected                                                  |
| **Session PDA, fee vault, delegation records** | Live on L1 throughout         | Reaped on close (rent → owner)                              |
| **ER block history**                           | Held by the validator         | Discarded on close (the L1 state is the canonical artifact) |

This is what makes the ER **ephemeral**: the canonical state lives on L1; the ER is a fast, scoped execution environment that exists only for the session's lifespan.

## Per-component failure handling

| Component fails               | What happens                                                                                                                                  |
| ----------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------- |
| User goes offline mid-session | No effect — the ER continues until close or TTL.                                                                                              |
| SDK process dies              | Same — restart and reconnect to the same `(owner, grid_id)`.                                                                                  |
| Router goes down              | ER traffic queues at the edge; recovers when the route is restored.                                                                           |
| Validator process crashes     | The ER stalls. State persists in the validator's WAL. The validator restarts and resumes; if it can't, the owner force-undelegates after TTL. |
| Validator host fails entirely | Same as above — the operator restores; if recovery is impossible, the owner force-undelegates.                                                |
| Portal or L1 down             | The session's existing ER work isn't blocked — execution continues — but new opens, delegations, and closes wait for L1.                      |

The single-tenant property limits blast radius: any failure is scoped to one session.

## See also

* [Sessions](/concepts/sessions) — the session as a first-class object.
* [Account delegation](/concepts/delegation) — what makes an account writable on the ER.
* [Real-time confirmation](/architecture/real-time) — slot cadence and the ER confirmation path.
* [Security model](/architecture/security) — how the trust assumptions hold up under adversarial conditions.
* [Portal program reference](/reference/portal-program) — the on-chain ix surface.


# Real-Time Confirmation

NorthStar can confirm transactions in milliseconds — not by waiting for a global slot to elapse, but by treating the ER session as a single-tenant runtime where the operator controls the cadence.

## Why a NorthStar session is real-time-capable

Three properties combine to make sub-perceptual confirmation feasible:

1. **Single-tenant execution.** Each session runs in isolation. There is no cross-app contention, no shared mempool, no fork choice across competing forks. Block production cadence is a local parameter, not a network-wide consensus negotiation.
2. **Configurable slot duration.** The validator's `slot_duration_ms` is a per-session knob — not the network-wide \~400ms Solana standard. Operators can run sessions at 10ms slots, 1ms slots, or any cadence the workload needs.
3. **`processed = confirmed` semantic equivalence.** With one sequencer and no fork ambiguity, the moment a transaction lands in the ER's local order, it's also confirmed. Browser code can rely on `processed` commitment as the final state.

## How sub-50ms confirmation works in practice

The confirmation pipeline:

```mermaid
sequenceDiagram
    participant U as Browser / agent
    participant ER as Ephemeral Rollup
    U->>ER: TPU-direct submission via QUIC
    Note over ER: Receives → orders → lands → emits to subscribers
    ER-->>U: signatureSubscribe (WS)
```

* **TPU-direct submission** — the SDK opens a QUIC connection to the validator's `EphemeralTpu` endpoint, bypassing the gateway forwarder.
* **Sub-slot wake-up** — `signatureSubscribe` over WebSocket fires the moment the transaction lands. No polling, no slot-cadence delay.
* **Local-order finality** — single-sequencer means the local ordering *is* the canonical ordering. `processed` commitment is sufficient.

End-to-end on the same machine as the validator, sub-50ms confirmation is reliable. On a separate machine over the public internet, latency floors at the network round-trip — typically 10–60ms depending on geography.

## Tracking sub-millisecond claims

The marketing-grade "real-time" copy describes the **mature platform's capability**, not today's measured devnet path. The slot duration knob currently defaults to 400ms in deployed validators; per-session overrides + the SDK helper that wires up TPU-direct submission are tracked under the NorthStar project. Three deliverables:

1. **SDK helper** — `sdk.session.confirm({ tpuDirect: true })` — wraps QUIC submission + `signatureSubscribe` with one call.
2. **Bench v2** — slot-delta latency instrumentation, replaces the current `t_send → blockTime` measurement which is unreliable on the ER.
3. **Per-session slot duration** — `OpenSession` accepts `slot_duration_ms` so each grid picks its cadence.

## See also

* [Sessions](/concepts/sessions)
* [ER topology](https://github.com/mirrorworld-universe/northstar-docs/blob/main/architecture/er-topology/README.md)
* [Confirmation lifecycle](https://github.com/mirrorworld-universe/northstar-docs/blob/main/architecture/confirmation-lifecycle/README.md)
* [Bench results](https://github.com/mirrorworld-universe/northstar-docs/blob/main/references/README.md) — measured numbers for the current path.


# Security Model

NorthStar's security model is **L1-anchored**: every guarantee a session offers traces back to a property the Portal program enforces on Solana, plus a fraud-proof challenge protocol that lets any uncensored party invalidate a dishonest sequencer's state.

This page covers the trust assumptions, the custody guarantees, the challenge protocol, and the failure modes.

## Trust assumptions

| Party                       | Assumption                                                                                                                                                             |
| --------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Owner**                   | Holds the keypair that opened the session. Trusted to act on their own behalf — no protection if their own keypair is compromised.                                     |
| **Portal program**          | Trusted as immutable, audited L1 code. Its deployed bytecode is verifiable on-chain.                                                                                   |
| **Sequencer / validator**   | **Untrusted for state honesty.** Trusted only to be live (provide service); state correctness is enforced by the challenge protocol below.                             |
| **Solana L1**               | Trusted as the canonical settlement layer. Inherits Solana's consensus security.                                                                                       |
| **At least one challenger** | The challenge protocol assumes at least one honest, uncensored party can submit fraud proofs. This is the same `1-of-N` honesty assumption optimistic rollups rely on. |

The combined assumption: **as long as L1 is live and one honest challenger exists, the sequencer cannot finalize a dishonest state.** Liveness depends on the operator; correctness does not.

## Custody guarantees

While a session is active, every delegated account has the following invariants — enforced by the Portal program at the L1 level:

1. **L1 write-lock.** No L1 transaction signed by anyone — including the original owner — can mutate a delegated account. The account's `owner` field is the Portal, and the Portal's CPI rules reject all writes that don't come through the settle-back path.
2. **ER exclusivity.** Exactly one validator instance is authorized to write the account inside the ER. Routing is enforced by the `(portal, owner, grid_id)` tuple in the delegation record.
3. **Atomic settle-back.** When the session closes (explicit or TTL), every delegated account commits its final ER state to L1 in one operation. Either all accounts settle, or none do — there is no intermediate state.
4. **Forced undelegation.** If the operator goes offline and the TTL expires, the **owner can unilaterally force-undelegate**. The state used for settle-back is whatever the ER had at expiry; if the operator has censored the latest state, the owner accepts the last published checkpoint instead. **The owner is never permanently locked out.**

These four properties hold under the trust assumptions above. They do not depend on the sequencer being honest; they depend only on the Portal program being correctly deployed and L1 being live.

## The challenge protocol

The sequencer's job is to publish the ER's state to L1 periodically as **checkpoint bonds**. Each checkpoint is a `(state_hash, bond)` tuple posted to L1 every \~30 seconds. The bond is collateral the sequencer forfeits if it publishes a dishonest checkpoint.

If any party — a challenger watching from outside, or the owner — sees a checkpoint they believe is wrong, they invoke the bisection protocol:

![NorthStar challenge protocol](https://1650709205-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2Fh39xD1Lv8Pc2DHtpKMWm%2Fuploads%2Fgit-blob-6e5e34bdbc30e287169adc106ac75fccb855bf9a%2Fsecurity.png?alt=media)

The flow:

### 1. Sequencer posts checkpoint bond

Every \~30 seconds, the sequencer publishes `(state_hash, bond)` to L1. This is the public claim about the ER's state at that point. Until challenged, it's accepted as canonical after the fraud-proof window.

### 2. Challenger submits a challenge

Any party can call the Portal's `Challenge` instruction with `(checkpoint_id, alleged_correct_hash)`. The Portal records the challenge and pauses settlement for the disputed range.

### 3. Bisection protocol

The challenger and sequencer enter an interactive game:

* The Portal asks: "Of the two halves of the disputed range, which half do you disagree on?"
* Both parties answer (left or right half).
* The losing half is discarded; the disagreement narrows.
* Repeat until a single transition is isolated — `O(log n)` steps for a checkpoint covering `n` transitions.

This is the classical [bisection protocol](https://medium.com/offchainlabs/optimistic-rollup-vs-multi-round-fraud-proofs-and-bisection-protocols-on-arbitrum-7e036ea3b4ff) used by optimistic rollups: it isolates the first divergent step without requiring on-chain replay of every intermediate state.

### 4. ZK proof for the isolated transition

Once a single transaction is isolated, the prover generates a **succinct ZK proof** for that one transition. The Portal verifies the proof on-chain — cheap, because it's a single-tx proof, not the full session.

### 5. Outcome

| Verdict                     | Effect                                                                                                                                                                                           |
| --------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| **Sequencer was dishonest** | The sequencer's bond is **slashed** (forfeit to the protocol / challenger). The Portal substitutes the correct state for the disputed checkpoint. Settle-back proceeds with the corrected state. |
| **Sequencer was honest**    | The challenge is rejected. The challenger forfeits their challenge bond (anti-spam). The original checkpoint stands.                                                                             |

In either case, the dispute terminates in bounded time and leaves L1 with the correct state.

## What this composes to

For an end-user with assets in a session:

* **Liveness:** depends on the sequencer. If the sequencer is offline, the session pauses; the user retrieves assets via forced undelegation after TTL.
* **Correctness:** does not depend on the sequencer. Dishonest checkpoints get caught by the challenge protocol; the correct state is restored before settle-back finalizes.
* **Custody:** does not depend on the sequencer. The Portal's L1 write-lock + forced undelegation guarantee that assets always return to the owner's control.

This is the "validity-with-liveness-degradation" model: in the worst case the user waits longer, but their assets are never at risk of being stolen by a misbehaving operator.

## Failure modes

| Scenario                                         | Effect                                                                                              | Recovery                                                                       |
| ------------------------------------------------ | --------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------ |
| Sequencer offline                                | Session stalls; in-flight ER txs unconfirmed.                                                       | Owner force-undelegates after TTL; settles to last published checkpoint.       |
| Sequencer publishes a bad checkpoint             | Challenge protocol triggers; sequencer's bond slashed; correct state restored.                      | No user action required if any honest challenger is watching.                  |
| Sequencer publishes nothing (silent withholding) | The fraud-proof window can't open without a checkpoint to challenge.                                | Owner force-undelegates after TTL; falls back to the last good checkpoint.     |
| Owner keypair compromised                        | Attacker can act as the owner — same as on L1. NorthStar provides no additional protection here.    | Standard key-rotation hygiene applies.                                         |
| L1 outage                                        | All session lifecycle ops (open / delegate / close) pause; ER execution continues until L1 returns. | Session resumes when L1 is back.                                               |
| Portal program bug                               | In principle catastrophic — the program is the security boundary.                                   | The Portal is audited and immutable; deployed bytecode is verifiable on-chain. |

## Implementation status

* **Portal L1 write-lock** — live on devnet.
* **Forced undelegation** — live on devnet (TTL-bound).
* **Atomic settle-back** — live on devnet.
* **Checkpoint bonds + bisection protocol** — specified in the [litepaper](https://github.com/mirrorworld-universe/reports); implementation tracked under the public roadmap. Devnet currently runs in honest-sequencer mode.
* **ZK proof verifier** — same.

The mature security model is the destination; the current devnet path is honest-sequencer with audited Portal bytecode. Production / mainnet brings the full fraud-proof game online.

## See also

* [Sessions](/concepts/sessions) — the lifecycle the security model protects.
* [Account delegation](/concepts/delegation) — the L1 write-lock primitive.
* [Settle-back guarantees](/concepts/settle-back) — atomicity model that depends on these properties.
* [ER topology](/architecture/er-topology) — where each component sits.


# Concepts

Core ideas behind sessions, delegation, fees, and settle-back.

NorthStar gives you a private Solana execution environment with clear bounds. These concepts explain how that model works, what it guarantees, and where the limits are.

## In this section

* [Sessions](/concepts/sessions) — the lifecycle, scope, and isolation model.
* [Account Delegation](/concepts/delegation) — how accounts become writable inside a session.
* [Programmable Fees](/concepts/programmable-fees) — how fee budgets and fee policy stay bounded.
* [Settle-Back Guarantees](/concepts/settle-back) — what commits to L1 when a session ends.

## Start here

Read [Sessions](/concepts/sessions) first. It frames the rest of the model.

Then read [Account Delegation](/concepts/delegation) and [Settle-Back Guarantees](/concepts/settle-back). Those two pages define the trust boundary.


# Account Delegation

Delegation is what makes a Solana account writable inside a NorthStar session. Without it, the ER can read the account (inherited from L1) but cannot mutate it. With it, the ER has exclusive write authority for the session's lifespan, and L1 cannot modify the account until the session closes.

## The two-sided rule

Every delegated account has the same two properties for the session's duration:

|           | On L1                     | On ER |
| --------- | ------------------------- | ----- |
| **Read**  | Yes                       | Yes   |
| **Write** | **No — locked by Portal** | Yes   |

This is the core property the Portal program enforces. No L1 transaction signed by anyone — including the original owner — can mutate a delegated account. The ER is the single source of writes.

## What can be delegated

| Account type                      | Delegated? | Notes                                                                                                                 |
| --------------------------------- | ---------- | --------------------------------------------------------------------------------------------------------------------- |
| **PDAs your program owns**        | Yes        | Pool PDA, vault PDA, custom state. Requires a `delegate_to_portal` hook in the owning program.                        |
| **Keypair-owned signer accounts** | Yes        | E.g. an agent keypair that signs `place_intent` ixs inside the ER. Delegated via the Portal's `Delegate` instruction. |
| **SPL token accounts**            | Yes        | Each token account gets its own delegation.                                                                           |
| **SPL token mints**               | No         | Mints are not delegated — inherited as read-only into the ER on first reference.                                      |
| **The owner keypair itself**      | No         | Owner stays system-owned on L1 so it can sign close/undelegate ixs.                                                   |

## How delegation lands on chain

Three coordinated state changes per delegation:

1. **Account ownership** — the account's `owner` field is reassigned from the original program (or `SystemProgram`) to the Portal program. This is what locks it on L1.
2. **A delegation record** — a Portal-owned PDA at `delegationRecordPda(portal, account)` is created with `(grid_id, owner_program, bump)`. This is the on-chain proof of delegation.
3. **A buffer account** — a transient account holding rent for the buffer-dance pattern that survives the ownership transfer.

## Undelegation

When the session closes (explicit `CloseSession` or TTL expiry), each delegated account undergoes the reverse:

1. Final state from the ER is committed to the L1 account.
2. Ownership is restored to the original program.
3. The delegation record is closed; rent goes back to the owner.

If the session expires without explicit close, the Portal allows **forced undelegation** by the owner — see [security model](/architecture/security) for the formal guarantees.

## Common patterns

* **Pool + vault delegation** — for an AMM running inside a session, both the pool PDA and the vault PDA delegate to the same `grid_id`. The agent keypair that signs `place_intent` also delegates so it can sign on the ER.
* **Token account delegation** — every SPL token account that holds delegated funds also delegates. The mints stay on L1.
* **Per-user delegation** (Phase 5) — a per-user session opens and delegates only that user's accounts; isolation between users is enforced at the session boundary.

## Constraints

* **Programs need a `delegate_to_portal` hook.** Portal's `Delegate` instruction only accepts accounts owned by the calling program. Third-party programs (Jupiter, Raydium) without this hook can't be delegated. Workaround: separate agent reasoning (in the ER) from external execution (on L1) — see the [Mach Sandbox architecture](https://github.com/mirrorworld-universe/mach-amm).
* **Re-delegation requires undelegate first.** An account already delegated to grid 1 cannot be re-delegated to grid 2 directly; close + reopen.

## See also

* [Sessions](https://github.com/mirrorworld-universe/northstar-docs/blob/main/concepts/sessions/README.md)
* [Settle-back guarantees](https://github.com/mirrorworld-universe/northstar-docs/blob/main/concepts/settle-back/README.md)
* [Portal program reference](https://github.com/mirrorworld-universe/northstar-docs/blob/main/reference/portal-program/README.md)


# Programmable Fees

A NorthStar session opens with a `FeeStructure` parameter that decides three things: which token pays for activity inside the session, how much each instruction costs, and what share of that revenue flows to the operator versus the protocol.

That's the whole concept. The default is gasless on devnet (`zero_fee_structure()`); production grids set their own.

## The three knobs

| Knob              | What it controls                                                                              |
| ----------------- | --------------------------------------------------------------------------------------------- |
| **Fee token**     | Any SPL mint — SOL, USDC, an operator's own token, or none.                                   |
| **Fee schedule**  | Per-instruction lamports / token units. Can be uniform, per-instruction, or identically zero. |
| **Revenue split** | Basis points of fee revenue that flow to the session operator (vs. the protocol).             |

Operators commit to the structure at `OpenSession` time; it can't change mid-session. Closing and reopening with a new structure is the supported path.

## Why per-session economics

A persistent L1 imposes one fee market on every workload. A per-session market localises the externality:

* A market-data feed publishing 10 quotes per second can charge sub-millicent per quote in its own token.
* A privacy-grade RFQ venue can charge premium fees for sealed-bid execution.
* An agent sandbox can run gasless to remove user-side friction.

None of these economics are achievable on a shared L1.

## Operators capture revenue

The same primitive that lets an operator set the fee schedule lets a wallet, aggregator, or super-app open grids on behalf of users and capture a share of every transaction in those grids. New revenue stream for incumbent integrators; no validator infrastructure required.

## See also

* [Programmable Economics](https://github.com/mirrorworld-universe/northstar-docs/blob/main/content/build/fees.md) — full surface area, code examples, and the production rollout plan.
* [Sessions](/concepts/sessions) — the lifecycle this fee structure attaches to.
* [Settle-back guarantees](/concepts/settle-back) — how fee-vault balances reconcile to L1 on close.


# Sessions

A NorthStar **session** is a bounded, isolated execution context anchored to a Solana account. It runs as an Ephemeral Rollup — single-tenant, with its own slot cadence, fee economics, and account scope — for a configurable lifespan, then settles atomically back to L1.

## Anatomy

Every session has:

* **An owner** — the Solana keypair that opened it. Sessions are derived from `(portal, owner, gridId)`.
* **A grid id** — an integer chosen by the owner. Two sessions for the same owner must have different grid ids.
* **A TTL** — the maximum lifespan in slots. After expiry, accounts force-undelegate.
* **A fee cap** — the maximum lamports the session can spend on internal accounting. Once depleted, the session terminates.
* **A fee structure** — the schedule that governs per-instruction fees inside the session. Defaults to gasless on devnet; arbitrary in production.
* **A set of delegated accounts** — the on-chain state the session can write. Everything else is read-only (inherited from L1).

## Lifecycle

```mermaid
stateDiagram-v2
    state "Forced Undelegate" as ForcedUndelegate

    [*] --> Created: OpenSession\nmints session PDA + fee_vault
    Created --> Delegated: Delegate\nwritable on ER, locked on L1
    Delegated --> Active: ER processes txs at\nsession's slot cadence
    Active --> Closed: explicit close
    Active --> Expired: TTL elapsed
    Closed --> ForcedUndelegate
    Expired --> ForcedUndelegate
    ForcedUndelegate --> [*]: accounts settle back to L1
```

Five distinct states: Created → Delegated → Active → (Closed or Expired) → settled.

## What's local to the session vs. visible from L1

| Account state                                       | While session is active                                               | After session closes                          |
| --------------------------------------------------- | --------------------------------------------------------------------- | --------------------------------------------- |
| **Delegated accounts** (pool, vault, agent keypair) | Writable on ER. **Locked** on L1 — no L1 transaction can modify them. | Settled atomically: all changes commit to L1. |
| **Non-delegated accounts** (mints, owner pubkey)    | Read-only on ER, inherited from L1                                    | Unaffected by session.                        |
| **Session metadata** (PDA, fee\_vault)              | Lives on L1 throughout                                                | Reaped on close (rent returned to owner).     |

## Constraints

* **One session per (owner, grid\_id)** — Portal won't open a duplicate.
* **Sessions don't compose** — a single transaction can't span two sessions; pick one.
* **Settle-back is all-or-nothing** — partial failures don't materialize.

## See also

* [Account delegation](https://github.com/mirrorworld-universe/northstar-docs/blob/main/concepts/delegation/README.md) — what makes an account writable on the ER.
* [Programmable economics](https://github.com/mirrorworld-universe/northstar-docs/blob/main/build/fees/README.md) — the FeeStructure parameter.
* [Settle-back guarantees](https://github.com/mirrorworld-universe/northstar-docs/blob/main/concepts/settle-back/README.md) — the atomicity model.
* [Real-time confirmation](https://github.com/mirrorworld-universe/northstar-docs/blob/main/architecture/real-time/README.md) — slot cadence inside a session.


# Settle-Back Guarantees

When a NorthStar session closes (explicit close or TTL expiry), every delegated account commits its final ER-side state back to L1 in one atomic step. This page explains the formal guarantees and the failure modes.

## The core guarantee

After `CloseSession` returns successfully, every delegated account's L1 state matches its final ER state. Either:

* **All delegated accounts settle**, in one atomic operation, **OR**
* **Nothing settles**, and the session reverts to its pre-close state for retry.

There is no intermediate state where some accounts settled and others didn't.

## Why it's atomic

The Portal program treats settle-back as a single multi-account write. The ER's final state is committed to L1 by the sequencer; any partial failure unwinds. This is enforced at the Portal level, not at the runtime level.

For the cryptographic / dispute-game guarantees that back this — checkpoint bonds, fraud proofs, ZK verification — see the [security model](/architecture/security).

## Failure modes

| Scenario                             | What happens                                                                                                |
| ------------------------------------ | ----------------------------------------------------------------------------------------------------------- |
| Owner explicitly closes (happy path) | All accounts settle atomically. Owner regains control.                                                      |
| Session TTL expires without close    | Portal allows **forced undelegation** by the owner. State is whatever the ER had at expiry; same atomicity. |
| Sequencer goes offline mid-session   | Session pauses; owner can force-undelegate after TTL expiry.                                                |
| Sequencer publishes a bad checkpoint | Fraud proof from any uncensored validator triggers slashing + state correction.                             |
| Network partition                    | Unaffected — the session's state is on L1 throughout.                                                       |

## What about in-flight transactions at close time?

When `CloseSession` is called, the ER stops accepting new transactions for that session. Any tx already submitted but not yet confirmed:

* **If it lands before close finalizes** — included in the final settle-back.
* **If it doesn't land before close finalizes** — dropped. Resubmit against a new session, or accept that it won't complete.

Practical guidance: if you have outstanding work that must complete, wait for `confirmed` on every in-flight tx before closing.

## See also

* [Security model](/architecture/security) — the cryptographic guarantees behind atomicity.
* [Sessions](https://github.com/mirrorworld-universe/northstar-docs/blob/main/concepts/sessions/README.md) — full lifecycle.
* [Account delegation](https://github.com/mirrorworld-universe/northstar-docs/blob/main/concepts/delegation/README.md) — what's settled.


# Getting Started

Install the SDK, run your first session, and explore common patterns.

Start with the shortest path to a working NorthStar session. Install the SDK, run a minimal flow, then branch into common patterns.

## Quick path

1. [Install the SDK](/getting-started/install)
2. [Hello World](/getting-started/hello-world)
3. [Recipes](/getting-started/recipes)

## What you'll learn

* How to open and close a session.
* How to delegate accounts into the ER.
* How to run standard Solana instructions against ER state.

{% hint style="info" %}
If you want the fastest route, go straight from [Install the SDK](/getting-started/install) to [Hello World](/getting-started/hello-world).
{% endhint %}


# Hello World

The shortest path from zero to a transaction running inside a NorthStar session. Five steps; \~30 lines of code.

## What we're going to build

Open a session, transfer SOL between two accounts inside the ER, settle back. Nothing fancy — but every step that matters is in there: session lifecycle, account delegation, ER-side execution, atomic settle-back.

## Setup

```typescript
import { NorthStarSDK } from "@sonicsvm/northstar-sdk";
import {
  Connection,
  Keypair,
  PublicKey,
  SystemProgram,
  Transaction,
} from "@solana/web3.js";

const PORTAL = new PublicKey("74iiMCqFw1afWyp3tdh9pUqfRfCRq7gfdC2YZoNGpovt");
const L1_RPC = new Connection("https://api.devnet.sonic.game", "confirmed");
const ER_RPC = new Connection("https://ephemeral.devnet.sonic.game", "confirmed");

const sdk = new NorthStarSDK({
  portalProgramId: PORTAL,
  customEndpoints: { solana: L1_RPC, ephemeralRollup: ER_RPC },
});

const owner = Keypair.generate();
const recipient = Keypair.generate();

// Fund the owner — devnet faucet, ~5 SOL is comfortable for the session.
// In your dev workflow, prefer the deploy payer at ~/.config/solana/id.json.
```

## Step 1: Open a session

```typescript
const gridId = 1n;
const ttlSlots = 2_000n;       // ~ 13 minutes at 400ms slots
const feeCap = 10_000_000n;     // 0.01 SOL fee budget

const { sessionAddress } = await sdk.openSession({
  owner,
  gridId,
  ttlSlots,
  feeCap,
});

console.log(`Session: ${sessionAddress.toBase58()}`);
```

The Portal program creates a session PDA derived from `(portal, owner, gridId)`. From here, anything you delegate to this session is locked on L1 and writable on the ER.

## Step 2: Delegate the recipient account

```typescript
await sdk.delegate({
  owner,
  gridId,
  account: recipient.publicKey,
  ownerProgram: SystemProgram.programId,
});
```

The Portal records that `recipient` is now bound to this session. After this returns, the account is read-only on L1 and writable inside the ER.

## Step 3: Run the transaction inside the ER

```typescript
const transferIx = SystemProgram.transfer({
  fromPubkey: owner.publicKey,
  toPubkey: recipient.publicKey,
  lamports: 1_000_000,            // 0.001 SOL
});

const tx = new Transaction().add(transferIx);
tx.feePayer = owner.publicKey;
tx.recentBlockhash = (await ER_RPC.getLatestBlockhash()).blockhash;
tx.sign(owner);

const sig = await ER_RPC.sendRawTransaction(tx.serialize());
await ER_RPC.confirmTransaction(sig, "confirmed");
console.log(`ER tx confirmed: ${sig}`);
```

Same `SystemProgram.transfer` instruction you'd send to L1 — different RPC. The ER processes it locally; confirmation lands in milliseconds (see [Real-time confirmation](https://github.com/mirrorworld-universe/northstar-docs/blob/main/architecture/real-time/README.md)).

## Step 4: Settle back

```typescript
await sdk.closeSession({ owner, gridId });
console.log(`Session closed; recipient balance is now visible on L1.`);
```

`CloseSession` undelegates every account bound to this session in one atomic step. Recipient's new balance shows up on L1.

## Step 5: Verify

```typescript
const balance = await L1_RPC.getBalance(recipient.publicKey);
console.log(`Recipient L1 balance: ${balance} lamports`);
// Expected: 1_000_000 (the transfer landed, settle-back committed it)
```

## What just happened

```mermaid
sequenceDiagram
    participant Owner
    participant Portal
    participant ER as ER session
    participant L1

    Owner->>Portal: open
    Owner->>Portal: delegate
    Portal->>ER: account marked writable
    Owner->>ER: transfer
    Owner->>Portal: close
    ER-->>Portal: atomic settle
    Portal->>L1: recipient updated
```

Five operations, two RPCs, one `Transfer` ix that runs identically to how it would on L1.

## Where to go from here

* [Recipes](https://github.com/mirrorworld-universe/northstar-docs/blob/main/getting-started/recipes/README.md) — patterns for agents, oracles, orderbooks.
* [Concepts: sessions](https://github.com/mirrorworld-universe/northstar-docs/blob/main/concepts/sessions/README.md) — the formal model.
* [Migrate to NorthStar](https://github.com/mirrorworld-universe/northstar-docs/blob/main/build/migrate/README.md) — for an existing Solana program.


# Install the SDK

```bash
bun add @sonicsvm/northstar-sdk @solana/web3.js
# or
npm install @sonicsvm/northstar-sdk @solana/web3.js
```

The SDK is a thin TypeScript wrapper around the Portal program plus a few helpers for session lifecycle, account delegation, and ER endpoint routing. It pairs with `@solana/web3.js` for the underlying transaction primitives.

## Requirements

* **Node 20+ or Bun 1.2+** — the SDK ships ESM only.
* **A funded keypair on devnet** — sessions are anchored on L1, so the owner needs SOL for the OpenSession + Delegate transactions. Get devnet SOL via `solana airdrop` or a faucet.
* **A local checkout of `@solana/web3.js`'s peer dep** — the SDK lists it as a peer, not a direct dep, to let your project pin its own version.

## Verify the install

Quickest sanity check — open a session against devnet:

```typescript
import { NorthStarSDK } from "@sonicsvm/northstar-sdk";
import { Connection, Keypair } from "@solana/web3.js";

const sdk = new NorthStarSDK({
  portalProgramId: "74iiMCqFw1afWyp3tdh9pUqfRfCRq7gfdC2YZoNGpovt",
  customEndpoints: {
    solana: new Connection("https://api.devnet.sonic.game"),
    ephemeralRollup: new Connection("https://ephemeral.devnet.sonic.game"),
  },
});

const owner = Keypair.generate();
console.log(`Owner: ${owner.publicKey.toBase58()}`);
```

If that imports without error, you're ready for [Hello World](https://github.com/mirrorworld-universe/northstar-docs/blob/main/getting-started/hello-world/README.md).

## Next steps

* [Hello World](https://github.com/mirrorworld-universe/northstar-docs/blob/main/getting-started/hello-world/README.md) — open a session, run a transaction inside it, settle back.
* [Recipes](https://github.com/mirrorworld-universe/northstar-docs/blob/main/getting-started/recipes/README.md) — common patterns for agents, oracles, orderbooks.
* [Concepts](https://github.com/mirrorworld-universe/northstar-docs/blob/main/concepts/sessions/README.md) — what's actually happening under the hood.


# Recipes

Working snippets for common NorthStar patterns. Each recipe extends the [Hello World](https://github.com/mirrorworld-universe/northstar-docs/blob/main/getting-started/hello-world/README.md) shape; copy-paste ready.

## 1. Always-on agent loop

A Bun service that opens a persistent session, runs a deterministic policy each tick, and re-emits each decision over Server-Sent Events. This is the shape the [Mach Sandbox](https://github.com/mirrorworld-universe/mach-amm) runs in production.

```typescript
import { AgentLoop, currentRatioBps, DEFAULT_POLICY } from "@mach/agent";

const loop = new AgentLoop({
  manifest, agent, owner, mintA, mintB,
  rpc: ER_RPC,
  intervalMs: 1_000,
  onCycle: (event) => {
    eventBus.publishTx({
      tSendUnixMs: event.timestampMs,
      signature: event.signature,
      direction: event.decision.kind === "swap" ? event.decision.direction : null,
      // …
    });
  },
});
loop.start();
```

The `onCycle` callback fires per tick with the policy decision, vault snapshot, and any submitted signature. Wire it to whatever consumer you need — SSE, NDJSON file, OpenTelemetry.

## 2. Confirm-status reconciliation

ER confirmation can be faster than the L1-tuned `confirmTransaction` timeout. Use a separate `getSignatureStatuses` poller to upgrade `pending` rows once the chain catches up:

```typescript
const reconciler = setInterval(async () => {
  for (const seq of pending) {
    const [status] = (await ER_RPC.getSignatureStatuses([seq.signature], {
      searchTransactionHistory: true,
    })).value;
    if (!status) continue;
    if (status.err) markFailed(seq, status.err);
    else if (status.confirmationStatus === "confirmed") markConfirmed(seq, status.slot);
  }
}, 1_500);
```

## 3. Settle on TTL approach

Sessions auto-expire when their TTL elapses. To rotate gracefully, open the next session in parallel a few minutes before expiry, warm it (delegate accounts), then swap your traffic over and close the old one:

```typescript
async function rotateBeforeTTL(currentSession: Session, currentSlot: bigint) {
  const slotsLeft = currentSession.expirySlot - currentSlot;
  const slotsBeforeRotate = 200n;          // ~ 80s buffer at 400ms slots

  if (slotsLeft > slotsBeforeRotate) return;

  const next = await sdk.openSession({ owner, gridId: currentSession.gridId + 1n, ttlSlots, feeCap });
  for (const account of currentSession.delegatedAccounts) {
    await sdk.delegate({ owner, gridId: next.gridId, account, ownerProgram });
  }
  await sdk.closeSession({ owner, gridId: currentSession.gridId });

  return next;
}
```

## 4. Custom-fee grid (USDC)

```typescript
import { FeeStructure } from "@sonicsvm/northstar-sdk";

const usdcMint = new PublicKey("4zMMC9srt5Ri5X14GAgXhaHii3GnPAEERYPJgZJDncDU");

await sdk.openSession({
  owner,
  gridId: 1n,
  ttlSlots: 86_400n,
  feeCap: 0n,                                  // gasless from the user's perspective
  feeStructure: FeeStructure.custom({
    token: usdcMint,
    perTxLamports: 10_000n,                    // ~ 0.001 USDC per tx
    revenueSplitBps: 5_000,                    // 50% to grid operator
  }),
});
```

See [Programmable Economics](https://github.com/mirrorworld-universe/northstar-docs/blob/main/build/fees/README.md) for the full schema.

## 5. Subscribe to live activity

The Portal's session account is just a Solana account; subscribe to it like any other:

```typescript
const subId = erRpcConnection.onAccountChange(sessionAddress, (acct) => {
  console.log(`Session bytes changed at slot ${acct.lamports} …`);
});
```

For per-tx feeds, use the program's instruction accounts:

```typescript
const programSubId = ER_RPC.onLogs(programId, (logs) => {
  console.log("ix logs:", logs);
});
```

## See also

* [Hello World](https://github.com/mirrorworld-universe/northstar-docs/blob/main/getting-started/hello-world/README.md)
* [Confirmation lifecycle](https://github.com/mirrorworld-universe/northstar-docs/blob/main/architecture/confirmation-lifecycle/README.md)
* [API reference](https://github.com/mirrorworld-universe/northstar-docs/blob/main/reference/sdk-typescript/README.md)


# Build

Practical guides for building on NorthStar — integration, custom economics, production deployment.

This section covers what you need to ship a NorthStar integration. The [Concepts](/concepts) section explains the formal model; this is the production-side counterpart.

## In this section

* [Integration Guide](/build/migrate) — adopt NorthStar in an existing Solana program. Covers the program-side `delegate_to_portal` hook, client wiring, runtime steps, the Mach AMM worked example, common gotchas, and a testing checklist.
* [Programmable Economics](/build/fees) — the full `FeeStructure` surface area. Custom fee tokens, per-instruction schedules, operator revenue capture.

## When to use what

* If you're standing up a new project, start with [Hello World](/getting-started/hello-world), then come here for production-grade fees + delegation patterns.
* If you have an existing Solana program, the [Integration Guide](/build/migrate) is the entry point.
* If you're building an aggregator or wallet that opens grids on behalf of users, [Programmable Economics](/build/fees) covers the revenue-capture model.


# Integration Guide

Integrate NorthStar into an existing Solana program — what to add, what to leave alone, and what to expect at runtime.

NorthStar runs your existing Solana program **unchanged** inside an Ephemeral Rollup session. The SBF binary is identical, the IDL is identical, the client-side TypeScript is identical. The only program-side change is one CPI hook so the Portal can lock your accounts on L1 for the session's duration.

This guide walks a team adopting NorthStar through the integration end-to-end: what to change in the program, what to wire in the client, what to test, and the gotchas that bite first-time adopters.

## Mental model

Three sentences:

1. A **session** is a single-tenant runtime anchored to Solana L1, with its own slot cadence, fee policy, and account scope.
2. **Delegation** moves an account's write authority to that runtime — locked on L1, writable on the ER — for the session's TTL.
3. On **close** (explicit or TTL expiry), every delegated account commits its final ER state back to L1 atomically. See [Sessions](/concepts/sessions) and [Account Delegation](/concepts/delegation) for the formal model.

Your program doesn't need to know any of this — it executes the same instructions against the same account schemas. The Portal program, the SDK, and the operator infrastructure handle everything else.

## Prerequisites

* An existing Solana program you control (you need to redeploy with one new CPI hook).
* Anchor or native-SBF — both work. The examples below use Anchor for brevity.
* `@sonicsvm/northstar-sdk` in your client project.
* A devnet Sonic SVM endpoint and a NorthStar ER endpoint (defaults documented in [Install the SDK](/getting-started/install)).

## What stays the same

| Layer                         | On L1     | Inside a NorthStar session |
| ----------------------------- | --------- | -------------------------- |
| Program binary (SBF)          | identical | identical                  |
| Compute units per instruction | identical | identical                  |
| IDL / Anchor types            | identical | identical                  |
| Account schemas               | identical | identical                  |
| Client-side TypeScript        | identical | identical                  |

If your program runs on Solana, it runs on NorthStar. There is no fork.

## What changes

| Concern                     | On L1                | Inside a NorthStar session                                                               |
| --------------------------- | -------------------- | ---------------------------------------------------------------------------------------- |
| **Where you submit txs**    | Solana RPC           | The session's ER endpoint (`ephemeral.devnet.sonic.game`)                                |
| **Account write authority** | Direct on L1         | Delegated; locked on L1 for session duration                                             |
| **Confirmation cadence**    | \~400ms slots        | Per-session knob (devnet default 400ms; path to 10ms in roadmap v2)                      |
| **Fee model**               | Standard Solana fees | Per-session [`FeeStructure`](/concepts/programmable-fees) (gasless by default on devnet) |
| **Settlement**              | Implicit, every tx   | Atomic settle-back at session close                                                      |

## Step 1 — add the `delegate_to_portal` hook to your program

The Portal's `Delegate` instruction only accepts accounts owned by the calling program. To make a program-owned PDA delegatable, add a CPI hook that re-owns the PDA to the Portal under the right conditions.

In Anchor, the shape is roughly:

```rust
use anchor_lang::prelude::*;
use northstar_portal::cpi::{delegate_account, accounts::DelegateAccount};

#[derive(Accounts)]
pub struct DelegateToPortal<'info> {
    /// The owner authorising the delegation.
    #[account(mut)]
    pub owner: Signer<'info>,

    /// The program-owned PDA being delegated.
    /// CHECK: validated against the program's PDA derivation below.
    #[account(mut)]
    pub target: AccountInfo<'info>,

    /// Portal's delegation record PDA — Portal mints it.
    /// CHECK: derived + validated by Portal CPI.
    #[account(mut)]
    pub delegation_record: AccountInfo<'info>,

    pub portal_program: Program<'info, NorthstarPortal>,
    pub system_program: Program<'info, System>,
}

pub fn delegate_to_portal(
    ctx: Context<DelegateToPortal>,
    grid_id: u64,
) -> Result<()> {
    // 1. Verify caller is the rightful owner of `target`.
    //    (Your program's existing authorisation rules go here.)

    // 2. CPI into Portal::Delegate. Portal handles the ownership transfer
    //    and creates the delegation record.
    delegate_account(
        CpiContext::new(
            ctx.accounts.portal_program.to_account_info(),
            DelegateAccount {
                owner: ctx.accounts.owner.to_account_info(),
                target: ctx.accounts.target.to_account_info(),
                delegation_record: ctx.accounts.delegation_record.to_account_info(),
                system_program: ctx.accounts.system_program.to_account_info(),
            },
        ),
        grid_id,
    )?;

    Ok(())
}
```

Same pattern for every PDA your program writes — pool PDAs, vault PDAs, registry PDAs, etc. Token accounts owned by your program also need their own delegate hook.

The Portal validates that the calling program is the legitimate owner of the target account before accepting the delegation, so end-users can't trick it into delegating arbitrary state.

## Step 2 — wire the SDK in your client

Same shape as a vanilla Solana program. The SDK accepts `customEndpoints` to route ER traffic separately from L1:

```typescript
import { NorthStarSDK } from "@sonicsvm/northstar-sdk";
import { Connection, PublicKey } from "@solana/web3.js";

const PORTAL = new PublicKey("74iiMCqFw1afWyp3tdh9pUqfRfCRq7gfdC2YZoNGpovt");

const sdk = new NorthStarSDK({
  portalProgramId: PORTAL,
  customEndpoints: {
    solana: new Connection("https://api.devnet.sonic.game", "confirmed"),
    ephemeralRollup: new Connection("https://ephemeral.devnet.sonic.game", "confirmed"),
  },
});
```

## Step 3 — bootstrap a session at runtime

Open the session and delegate every account your program will touch:

```typescript
const gridId = 1n;
const ttlSlots = 86_400n;        // ~ 9.6 hours at 400ms slots
const feeCap = 100_000_000n;     // 0.1 SOL fee budget

const { sessionAddress } = await sdk.openSession({
  owner,
  gridId,
  ttlSlots,
  feeCap,
  // feeStructure: omit for gasless; set for custom economics
});

// Delegate every PDA + signer your program writes inside the session.
await sdk.delegate({ owner, gridId, account: poolPda,  ownerProgram: yourProgramId });
await sdk.delegate({ owner, gridId, account: vaultPda, ownerProgram: yourProgramId });
await sdk.delegate({ owner, gridId, account: vaultTokenA, ownerProgram: yourProgramId });
await sdk.delegate({ owner, gridId, account: vaultTokenB, ownerProgram: yourProgramId });
await sdk.delegate({ owner, gridId, account: agentKeypair.publicKey, ownerProgram: SystemProgram.programId });
```

After this returns, those accounts are read-only on L1 and writable inside the ER. The session is live.

## Step 4 — submit instructions to the ER

Build instructions exactly as you would for L1, but submit them through the ER's `Connection`:

```typescript
const tx = new Transaction().add(yourProgramIx);
tx.feePayer = owner.publicKey;
tx.recentBlockhash = (await ER_RPC.getLatestBlockhash()).blockhash;
tx.sign(owner);

const sig = await ER_RPC.sendRawTransaction(tx.serialize());
await ER_RPC.confirmTransaction(sig, "confirmed");
```

Same instruction, same accounts, same signing flow — different RPC. The ER processes it locally; confirmation lands at the session's slot cadence (sub-50ms is the platform's mature target; current devnet path measures \~750ms p50, see [Real-time confirmation](/architecture/real-time)).

## Step 5 — settle back

```typescript
await sdk.closeSession({ owner, gridId });
```

The Portal walks every delegated account, applies its final ER-side state to the corresponding L1 account in one atomic multi-write, and restores ownership. After this returns, the post-session state is canonical L1 state. See [Settle-Back Guarantees](/concepts/settle-back) for the atomicity model.

If the TTL elapses without explicit close, the same settle-back path runs unilaterally — owners can never be locked out. See [Forced undelegation](/architecture/security#forced-undelegation).

## Worked example: Mach AMM

The [Mach AMM sandbox](https://github.com/mirrorworld-universe/mach-amm) is the canonical reference. It's a delegation-aware constant-product AMM that runs both ways:

| Path               | Venue                  | Cost per `place_intent`     | CU  |
| ------------------ | ---------------------- | --------------------------- | --- |
| **Vanilla Solana** | Solana Devnet          | 5,000 lamports              | 965 |
| **NorthStar ER**   | Session-scoped runtime | 0 lamports (gasless config) | 965 |

Same SBF binary, same instruction surface, same agent code. Only the venue + a one-time delegation step differ. The repo includes the program, the agent, the relayer, and the bench harness — clone it as a starting point.

## Common patterns

* **AI agent sandboxes.** Bound an autonomous agent's blast radius to one session. Hard caps on accounts, time, and fee budget. The session's TTL + fee cap are your safety rails.
* **Privacy-sensitive DeFi.** Single-tenancy means no other application can observe in-flight state — sealed-bid auctions, OTC RFQ matching, dark-pool execution.
* **High-frequency strategies.** Tune the per-session cadence + fee economics for the workload. Operator captures revenue from every tx in the grid.
* **DePIN networks.** Per-network sessions paying fees in the network's incentive token at sub-cent unit cost.

## Common gotchas

* **Token accounts owned by your delegated PDAs aren't auto-delegated.** Each SPL token account that holds delegated funds also needs a `Delegate` call. Skipping it returns `AccountNotFound` on the ER (the account exists on L1 but isn't bound to the session).
* **Mints stay on L1.** They're inherited as read-only into the ER on first reference — no separate delegation needed. Programs that read mint metadata work unchanged.
* **All vault token accounts must exist on L1 before delegation.** The ER inherits accounts lazily; if a target token account was never initialised on L1, the ER inherits nothing and instructions referencing it fail. Initialise both sides of any pair before opening the session.
* **Re-delegation requires undelegation first.** An account already delegated to grid 1 cannot be re-delegated to grid 2 directly. Close the first session (or wait for TTL + force-undelegate) before opening at a new grid id.
* **Session expiry is real.** Plan a refresh well before the deadline, or treat settle-back as a feature of your workflow.

## Local development

* For a single-process loop the [Mach AMM repo](https://github.com/mirrorworld-universe/mach-amm) covers — clone it, run `bun install`, follow the README's smoke flow.
* For larger integrations, point your client's `customEndpoints.ephemeralRollup` at `https://ephemeral.devnet.sonic.game`. The session lifecycle works the same against the public devnet.
* Open a session with a short TTL (e.g. `2_000n` slots) while iterating so settle-back is fast and you can re-bootstrap quickly.

## Testing checklist

Before shipping an integration:

* [ ] **Round-trip a single tx:** open session → delegate → submit one ix → close → verify L1 reflects the change.
* [ ] **Test forced undelegation:** open a short-TTL session, let it expire, run `Undelegate` from the owner, verify accounts are back on L1.
* [ ] **Verify L1 read-through:** reference a non-delegated account from inside the session — it should read fine, fail to write.
* [ ] **Fee accounting:** if you set a custom `FeeStructure`, verify the fee vault is debited correctly per ix and that the operator share lands.
* [ ] **Atomic settle-back:** force a session close mid-batch and verify either every change settled or none did. There is no partial state.

## Where to go next

* [Hello World](/getting-started/hello-world) — the shortest end-to-end loop, \~30 lines.
* [Programmable Economics](/build/fees) — full `FeeStructure` surface area for production grids.
* [Real-time confirmation](/architecture/real-time) — what the latency budget looks like and how to hit it.
* [Portal program reference](/reference/portal-program) — on-chain instruction surface.
* [Mach AMM sandbox](https://github.com/mirrorworld-universe/mach-amm) — the reference codebase.


# Programmable Economics

NorthStar sessions are gasless by default on devnet — but that's just one configuration of a fully-programmable fee market. Operators set their own economics per session: any fee schedule, any token, any revenue split.

## What's configurable per session

Every session opens with a `FeeStructure` parameter that decides:

* **Fee level** — gasless, sub-cent, or any amount the operator chooses.
* **Fee token** — SOL, USDC, the operator's own SPL token, or no token at all.
* **Revenue split** — the slice that goes to the grid operator vs. the protocol.
* **Per-instruction overrides** — different fee bands per workload (place\_intent vs. settle\_intent vs. data feeds).

The default `zero_fee_structure()` is a useful starting point for marketing demos and developer-experience flows. Production grids will run their own.

## Why programmable fees matter

Three direct consequences:

1. **Builders set their own runway.** A team running a market-data feed can charge per-read in their own token; a privacy-focused DeFi venue can charge premium fees for sealed-bid auctions; an agent-sandbox operator can subsidize the agent's transactions to remove user friction.
2. **Aggregators and wallets become operators.** A wallet that opens grids on behalf of users captures a share of the fees on every transaction in that grid. New revenue stream, no separate validator infrastructure.
3. **No protocol-level rent.** The protocol's take is a parameter, not a fixed tax. Operators can run thin margins for high-volume workloads or fat margins for premium ones.

## API surface

The `FeeStructure` is a parameter on the Portal program's `OpenSession` instruction. The full schema lives in the [SDK reference](https://github.com/mirrorworld-universe/northstar-docs/blob/main/reference/portal-program/README.md). A typical opening for a custom-fee grid looks like:

```typescript
import { NorthStarSDK, FeeStructure } from "@sonicsvm/northstar-sdk";

await sdk.openSession({
  gridId,
  ttlSlots: 86_400n,                    // ~ 24h at 400ms slots
  feeStructure: FeeStructure.custom({
    token: USDC_MINT,
    perTxLamports: 10_000n,             // ~ 0.001 USDC equivalent
    revenueSplitBps: 5_000,              // 50% to operator
  }),
});
```

## Roadmap

The SDK exposure of custom-fee configuration is tracked in [ENG-6886](https://linear.app/mirror-world/issue/ENG-6886). Three deliverables under that umbrella:

1. SDK + docs surface for what's already supported.
2. Custom-token fees (USDC + arbitrary SPL).
3. Revenue capture for grid operators.

## See also

* [Sessions](https://github.com/mirrorworld-universe/northstar-docs/blob/main/concepts/sessions/README.md) — what a session is and what's bound to one.
* [Settle-back guarantees](https://github.com/mirrorworld-universe/northstar-docs/blob/main/concepts/settle-back/README.md) — how state returns to L1 when a session closes.
* [Migrate from L1](https://github.com/mirrorworld-universe/northstar-docs/blob/main/build/migrate/README.md) — what changes when you move an existing program to a session.


# Reference

Protocol and program details for building against NorthStar.

Use this section when you need exact shapes, instruction details, and protocol-level behavior. It complements the conceptual pages with implementation-facing detail.

## In this section

* [Portal Program Reference](/reference/portal-program) — instruction set, account schemas, and PDA derivations.

## When to use reference docs

Open this section when you need:

* instruction parameters and failure modes
* account layouts and PDA formulas
* exact on-chain behavior for session management

If you're new to NorthStar, start in [Getting Started](/getting-started) first.


# Portal Program Reference

The Portal is the on-chain program that mints sessions, manages delegation, and enforces TTL + fee constraints. It's the warden — every session lives or dies by Portal's rules.

**Program ID (devnet):** `74iiMCqFw1afWyp3tdh9pUqfRfCRq7gfdC2YZoNGpovt`

## Instruction set

<table><thead><tr><th width="148.70703125">Discriminator</th><th width="186.4296875">Name</th><th>Purpose</th></tr></thead><tbody><tr><td><code>0</code></td><td><code>OpenSession</code></td><td>Mint a new session at <code>(owner, grid_id)</code>. Creates session PDA + fee_vault.</td></tr><tr><td><code>1</code></td><td><em>reserved</em></td><td></td></tr><tr><td><code>2</code></td><td><code>DepositFee</code></td><td>Add lamports to the <code>fee_vault</code> for a session.</td></tr><tr><td><code>3</code></td><td><code>Delegate</code></td><td>Bind an account to a session.</td></tr><tr><td><code>4</code></td><td><code>Undelegate</code></td><td>Release an account from a session (callable post-TTL by owner).</td></tr></tbody></table>

## OpenSession (discriminator 0)

```
data: u8(0) | u64 grid_id | u64 ttl_slots | u64 fee_cap

accounts (writable, signer):
  [0] owner          (signer, writable) — pays rent for session + fee_vault
  [1] session_pda    (writable)         — sessionPda(portal, owner, grid_id)
  [2] fee_vault_pda  (writable)         — feeVaultPda(portal, owner)
  [3] system_program
```

**Failure modes:**

* "account already in use" — fee\_vault already exists from a prior session for the same owner. Portal does not currently support reusing the fee\_vault; use a different owner or wait for `CloseSession` to release it.
* "invalid grid\_id" — grid\_id <= 0 is rejected.

## Delegate (discriminator 3)

Used both for PDAs (via the owning program's `delegate_to_portal` CPI) and for raw keypair-owned accounts (called directly).

For keypair accounts:

```
data: u8(3) | u64 grid_id

accounts:
  [0] owner               (signer, writable)
  [1] account             (signer, writable) — the keypair being delegated
  [2] system_program
  [3] delegation_record   (writable) — delegationRecordPda(portal, account)
  [4] system_program (again — Portal's calling convention)
  [5] buffer              (writable, ephemeral) — rent buffer for the dance
```

For PDAs delegated through their owning program (the `delegate_to_portal` hook), the program issues this CPI with the appropriate program-derived signer.

## Undelegate (discriminator 4)

```
data: u8(4)

accounts (for keypair accounts):
  [0] owner               (signer, writable)
  [1] account             (signer, writable)
  [2] system_program
  [3] delegation_record   (writable)
  [4] system_program
```

For PDA accounts the calling program issues the analogous CPI through its program-derived signer.

## Account schemas

### Session (`sessionPda(portal, owner, grid_id)`)

| Field           | Type     | Notes                                 |
| --------------- | -------- | ------------------------------------- |
| `discriminator` | `u8`     | Always `1`                            |
| `session_owner` | `Pubkey` | Owner keypair that opened the session |
| `grid_id`       | `u64`    |                                       |
| `ttl_slots`     | `u64`    |                                       |
| `fee_cap`       | `u64`    | Lamports                              |
| `created_at`    | `u64`    | Slot when OpenSession landed          |
| `nonce`         | `u64`    | Reserved                              |
| `bump`          | `u8`     |                                       |

Total: 82 bytes.

### DelegationRecord (`delegationRecordPda(portal, account)`)

| Field           | Type     | Notes                                                      |
| --------------- | -------- | ---------------------------------------------------------- |
| `discriminator` | `u8`     | Always `4` for delegation records (5 for deposit receipts) |
| `owner_program` | `Pubkey` | The program that originally owned the account              |
| `grid_id`       | `u64`    |                                                            |
| `bump`          | `u8`     |                                                            |

Total: 42 bytes.

### FeeVault (`feeVaultPda(portal, owner)`)

Lamports-only account; no decoded fields beyond standard `AccountInfo`.

## PDA derivations

```typescript
import { PublicKey } from "@solana/web3.js";

const SESSION_SEED = Buffer.from("session", "utf8");
const FEE_VAULT_SEED = Buffer.from("fee_vault", "utf8");
const DELEGATION_RECORD_SEED = Buffer.from("delegation", "utf8");

function sessionPda(portal: PublicKey, owner: PublicKey, gridId: bigint): PublicKey {
  const buf = Buffer.alloc(8);
  buf.writeBigUInt64LE(gridId);
  return PublicKey.findProgramAddressSync(
    [SESSION_SEED, owner.toBuffer(), buf],
    portal,
  )[0];
}

function feeVaultPda(portal: PublicKey, owner: PublicKey): PublicKey {
  return PublicKey.findProgramAddressSync(
    [FEE_VAULT_SEED, owner.toBuffer()],
    portal,
  )[0];
}

function delegationRecordPda(portal: PublicKey, account: PublicKey): PublicKey {
  return PublicKey.findProgramAddressSync(
    [DELEGATION_RECORD_SEED, account.toBuffer()],
    portal,
  )[0];
}
```

## See also

* [Sessions](https://github.com/mirrorworld-universe/northstar-docs/blob/main/concepts/sessions/README.md)
* [Account delegation](https://github.com/mirrorworld-universe/northstar-docs/blob/main/concepts/delegation/README.md)
* [Account schemas (full)](https://github.com/mirrorworld-universe/northstar-docs/blob/main/reference/account-schemas/README.md)


# FAQ

## Why an Ephemeral Rollup instead of a rollup-as-a-service?

NorthStar sessions are **single-tenant** by design. Each session is its own isolated execution context, with its own slot cadence, its own fee schedule, and atomic settle-back to Solana. Rollup-as-a-service products give you a shared chain you operate on; NorthStar gives you your own chain on demand, then takes it back.

## How is settle-back atomic?

When a session closes, the Portal program treats the commit of all delegated accounts as one multi-write operation. Either every account's final ER state lands on L1, or nothing changes. See [settle-back guarantees](/concepts/settle-back) and the [security model](/architecture/security).

## What's "bounded autonomy"?

The agent-sandbox primitive: an autonomous agent (AI or deterministic) runs inside a session that has hard limits on **which accounts** it can touch, **how long** it can run, and **how much** it can spend on fees. If any limit is reached, the session terminates and accounts settle back. No surprise bills, no escaped writes, no rollback drama.

## Mainnet timeline?

Devnet is live today (current page is documentation against devnet). Q2 2026 is the tentative rollout timeline for Mainnet.

## Can I run any Solana program inside a NorthStar session?

If the program ships a `delegate_to_portal` hook on its writable accounts — yes. If not (e.g. Jupiter, Raydium), the program itself can't be delegated, but you can still use NorthStar to **separate the agent's reasoning** (in the session, against delegation-aware programs) **from external execution** (on L1, via standard composability). See the [Mach Sandbox architecture](https://github.com/mirrorworld-universe/mach-amm) for the canonical example.

## What's the difference between a session and a "rollup"?

A session **is** a rollup — but a single-tenant, ephemeral one. It's a private execution context that exists for the session's TTL, then ceases to exist. The closer mental model is "rent a private chain for an hour" rather than "deploy on a chain that lives forever."

## How fast is real-time confirmation, exactly?

The mature platform's capability is sub-perceptual confirmation (think milliseconds). Today's deployed devnet path measures higher because the validator's slot duration defaults to 50ms. Per-session slot duration overrides + an SDK helper for TPU-direct submission are tracked in the pipeline. See [Real-time confirmation](/architecture/real-time) for the architecture.

## What happens if my session expires?

Session accounts force-undelegate; final ER state settles to L1. The session PDA stays on chain (Portal doesn't currently expose `CloseSession` to free it). If you re-open at the same `grid_id`, you'll need a different `grid_id` or a fresh owner keypair (the per-owner `fee_vault` is shared, and Portal's current `OpenSession` doesn't reuse it).

## How are fees programmable?

Each session opens with a `FeeStructure` parameter — pick the fee level, pick the fee token, pick the operator's revenue split. See [Programmable Economics](https://github.com/mirrorworld-universe/northstar-docs/blob/main/build/fees/README.md) for the schema.

## Where do I get help?

* Issues + bug reports: [github.com/mirrorworld-universe](https://github.com/mirrorworld-universe).
* Architecture questions: this docs site.
* Roadmap visibility: the public Linear project linked above.


