# Integration Guide

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.md) and [Account Delegation](/concepts/delegation.md) 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.md)).

## 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.md) (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.md)).

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


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://docs.northstar.sonicsvm.org/build/migrate.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
