npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

northstar-eva-sdk

v0.18.0

Published

Run the eva program on a NorthStar ephemeral rollup (zero-fee hot path). A high-level class SDK over the NorthStar Portal/ER protocol + the eva Anchor program. Works localnet & devnet.

Readme

northstar-eva-sdk

A high-level TypeScript SDK to run the eva program on a NorthStar Ephemeral Rollup (ER) and cut fees. It speaks the NorthStar Portal/ER protocol directly with corrected encoders and is self-contained on the three standard Solana libraries — no @sonicsvm/northstar-sdk dependency.

Install

npm install northstar-eva-sdk @solana/web3.js @coral-xyz/anchor @solana/spl-token
import { NorthstarEva } from "northstar-eva-sdk";
// localnet with a Keypair:
const sdk = NorthstarEva.create({ network: "localnet", wallet });
// OR keep your existing AnchorProvider + read-only wallet + custom RPC (see 0.2.0):
const sdk = NorthstarEva.create({ provider, erRpc: ER_RPC_URL, evaProgramId });

Ships compiled JS + TypeScript types — works in any Node ≥18 project (JS or TS). Dual ESM + CommonJS: works with both import { NorthstarEva } from "northstar-eva-sdk" (ESM) and const { NorthstarEva } = require("northstar-eva-sdk") (CommonJS / NestJS backends). The three Solana libs are peer dependencies (install them alongside). Prefer a single drop-in file instead? See share/ (no npm). Publishing it yourself? See PUBLISHING.md.

Construction — one SDK, three ways (NorthStar or not)

The SDK splits two method families: eva-contract methods (initialize/createTrench/deposit/withdraw/finalize/buy/sell/…) always run on your program + provider; the NorthStar bridge methods (openSession/fundFeeToErPayer/withdrawFeeFromEr) need the L1 + ER lanes. Pick the construction that fits your service — sdk.isNorthStar tells you which mode you're in.

// (1) provider IS the ER, L1 passed separately — RECOMMENDED for NorthStar apps
const sdk = NorthstarEva.create({ program, provider, l1Endpoint: "https://api.devnet.solana.com" });
//   → provider.connection = ER, l1Endpoint = L1.  sdk.isNorthStar === true

// (2) NO NorthStar — the SAME SDK as a plain eva client (one chain, no ER/Portal)
const sdk = NorthstarEva.create({ program, provider });
//   → eva methods run on the provider; deposit/withdraw/session throw "requires NorthStar".  sdk.isNorthStar === false

// (3) legacy: provider = L1, ER passed as erRpc (still supported)
const sdk = NorthstarEva.create({ provider, erRpc: "https://ephemeral.devnet.sonic.game" });
  • l1Endpoint means "the provider's RPC is the ER; this is the L1." It's the cleanest fit when your app's provider already points at the ER. (L1Endpoint is accepted too.)
  • Same SDK, both services: with no l1Endpoint/erRpc, you get a normal eva client — one codebase covers the NorthStar and non-NorthStar deployments.
  • Withdraw returns to the sender: withdrawFeeFromEr({ lamports }) reclaims the fee to whoever funded the ER (the SDK signer) — no recipient needed.
  • Typed IDL straight from the SDK: import { EVA_IDL, type EvaArena } from "northstar-eva-sdk"new anchor.Program<EvaArena>(EVA_IDL, provider) (official eva v0.1.6 IDL; EVA_IDL is the value, EvaArena the type).
  • Session helpers: await sdk.isSessionOpen() (read-only boolean), await sdk.getSession() (decoded session struct — validator, ttlSlots, createdAt, expiresAtSlot, … or null), and await sdk.buildEnsureSessionIx() (returns the OpenSession instruction to compose/sign yourself, or null if the session already exists).

What's new in 0.4.0

Complete eva coverage — the SDK now mirrors all 11 eva instructions. Added the 5 that were missing: updateConfig, withdraw, closePool, claimTokens, distributePrize (each with a build…Ix twin):

// admin / lifecycle
await sdk.updateConfig({ biddingDuration: 120n, tradingDuration: 100_000n, feeRecipient });
await sdk.withdraw({ trenchId, amount: 20_000_000n });            // withdraw a bid deposit from a trench
await sdk.closePool({ trenchId });
await sdk.claimTokens({ trenchId, user });                        // release a user's tokens from the vault
await sdk.distributePrize({ trenchId, winners: [w1, w2], amounts: [a1, a2] });

Note: eva withdraw (withdraw a trench bid deposit) is different from the Portal withdrawFeeFromEr (bridge SOL ER→L1). They coexist.

Instruction builders — get the unsigned instruction instead of sending it. Every send-method now has a build…Ix twin with the same inputs that returns an unsigned TransactionInstruction, so your backend can compose it with its own instructions, sign, and submit however it needs.

import { NorthstarEva } from "northstar-eva-sdk";

// 1) build instructions (no signing, no network send — only the reads needed to resolve accounts)
const ataIx = sdk.buildUserAtaIx({ trenchId });
const buyIx = await sdk.buildBuyIx({ trenchId, solPayWithFee: 10_000_000n });

// 2) compose them (add your own instructions too), get an unsigned Transaction
const tx = await sdk.buildTransaction([ataIx, buyIx], { lane: "er" }); // feePayer + blockhash set

// 3) your backend signs + submits — the SDK never sends it
tx.sign(myKeypair);                       // or wallet.signTransaction(tx)
await connection.sendRawTransaction(tx.serialize());

Builders (each mirrors the send-method's inputs): buildOpenSessionIx(), buildCloseSessionIx(), buildDepositFeeIx(lamports, recipient?, sender?) (sender = the depositor/signer; defaults to the SDK signer), buildWithdrawFeeFromErIx({ lamports, sender?, toL1? }) (Portal SOL bridge), buildInitializeIx(p), buildUpdateConfigIx(p), buildCreateTrenchIx({ trenchId?, initialVirtualTokenReserves? }), buildDepositIx(p), buildWithdrawIx({ trenchId, amount, thinkId? }) (eva trench withdraw), buildFinalizeIx(p), buildUserAtaIx(p), buildBuyIx(p), buildSellIx(p), buildClosePoolIx(p), buildCloseTrenchIx({ trenchId, creator? }), buildClaimTokensIx(p), buildDistributePrizeIx(p), plus buildTransaction(ixs, { lane?, feePayer? }), nextTrenchId(), and the agentPda(id, user?) PDA helper.

The existing send-methods (buy, sell, fundFeeToErPayer, …) are unchanged — they now just call these builders internally. Builders perform the same state reads to resolve accounts (e.g. buildBuyIx reads global for feeRecipient) but never send.

What's new in 0.3.0

Construct from your existing program + provider, and an exported EVA_IDL — minimal code change.

  • Pass a pre-built Anchor program. NorthstarEva.create({ program, provider, erRpc }) mirrors the old new EvaArenaSDK(program, provider) shape. The SDK adopts program.programId as the single source of truth (all PDA derivations follow it) and reuses the program's provider.wallet + provider.connection — so the program id can never disagree between L1 and the ER, or across environments. (If you also pass a conflicting evaProgramId, the SDK throws instead of silently mismatching.)
  • EVA_IDL is now exported — build your program exactly like before:
    import { NorthstarEva, EVA_IDL } from "northstar-eva-sdk";
    
    const wallet   = new ReadOnlyWallet(userPubkey);                         // no Keypair (Turnkey)
    const provider = new anchor.AnchorProvider(connection, wallet, { commitment: "confirmed" });
    const programId = getProgramId();                                       // per-environment id
    const program  = new anchor.Program({ ...EVA_IDL, address: programId.toBase58() }, provider);
    
    const sdk = NorthstarEva.create({ program, provider, erRpc: ER_RPC_URL });
    // sdk.evaProgramId === programId  (adopted from your program)
  • The eva program is one address, identical on L1 and ER (the ER reanchors and executes the L1 program — there is no separate ER deployment), and both evaProgramId/portalProgramId remain configurable + readable via sdk.evaProgramId / sdk.portalProgramId.

Backwards compatible: { network, wallet: keypair } and { provider, erRpc, evaProgramId } both still work.

What's new in 0.2.0

Integration-friendly construction — no Keypair required, keep your provider, any RPC URL.

  • Read-only / remote wallets are supported. wallet now accepts any wallet adapter (publicKey + signTransaction) — e.g. a Turnkey ReadOnlyWallet — not just a Keypair. The SDK signs through signTransaction and never needs the secret key. (Fixes the Type 'Wallet' is missing … 'secretKey' error.)
  • Pass your AnchorProvider directly. NorthstarEva.create({ provider }) reuses the provider's .wallet (signer) and .connection (the L1 endpoint) — keep the exact provider/program setup you already have.
  • Any RPC URL (not just localhost). Pass l1Rpc + erRpc (or provider/connection + erRpc). network is now just an optional label (any string) and no longer rejects a URL. The ER is a separate endpoint — always pass erRpc (the SDK throws a clear error if a custom L1 is given without it).
  • Program ids are configurable + exposed. evaProgramId / portalProgramId are options and readable as sdk.evaProgramId / sdk.portalProgramId.
  • Dynamic recipients. Deposit credits any recipient (a PublicKey); withdraw's recipient accepts a Keypair or a wallet adapter (and defaults to the SDK signer).

Migration — your old new EvaArenaSDK(program, provider) shape, unchanged inputs:

const userPubkey = new PublicKey(turnkeyAddress);
const wallet     = new ReadOnlyWallet(userPubkey);                       // no Keypair
const provider   = new anchor.AnchorProvider(connection, wallet, { commitment: "confirmed" });
const sdk = NorthstarEva.create({
  provider,                    // keeps provider + read-only wallet
  erRpc: ER_RPC_URL,           // the NorthStar ER endpoint (any URL)
  evaProgramId: getProgramId(),// per-environment program id
});

Backwards compatible: NorthstarEva.create({ network, wallet: keypair }) still works (the Keypair is wrapped automatically). No method signatures changed.

What's new in 0.1.1

The deposit ("fund fee to ER payer") API now contains the open-session step, plus two helper additions:

  • fundFeeToErPayer / fundErFeePayer now open the session for you. Portal DepositFee requires an open session (the DepositReceipt PDA is derived from it). Before, you had to call openSession() yourself first; now the deposit call ensures it. Opt out with { ensureSession: false } if you already opened it.
    // 0.1.0 — two calls, deposit fails if you forgot openSession
    await sdk.openSession();
    await sdk.fundFeeToErPayer(200_000_000n);
    // 0.1.1 — one call; the session is opened automatically (idempotent)
    await sdk.fundFeeToErPayer(200_000_000n);
  • New ensureSession() — the named, idempotent "open the session if it isn't already" method (opens only when the session PDA is absent). This is the step fundFeeToErPayer performs internally; call it directly if you want the session open up front.
  • New withdrawFeeFromEr(...) — alias of requestWithdraw(...) that matches the "withdraw fee from ER" API name.

Backwards compatible: existing code that calls openSession() before depositing still works (the extra ensureSession is a no-op when the session already exists). No signatures or return types changed.

The three user-fund APIs: query balancequerySolBalance(account?) · depositfundFeeToErPayer(lamports, recipient?) · withdrawwithdrawFeeFromEr({ lamports, sender?, toL1? }).

What it is + the fee point

NorthStar is one validator process with two lanes:

  • L1 lane (:8899) — the base Solana chain. Trust, custody, money, finality. Normal per-signature + priority fees.
  • ER lane (:8910 RPC / :8911 WS / :8912 TPU) — an Ephemeral Rollup that lives inside the same process and reanchors to the L1 bank every block. Execution is zero-fee, but state is in-memory and only a narrow slice ever settles back to L1.

The fee-reduction play: do the cheap, one-time setup/control/custody on L1 (open session, fund the ER fee payer, create the game), then run the hot path — repeated buy/sell — on the ER for ~0 fee. This SDK handles the lane routing for you (it is otherwise manual: NorthStar has no auto-router):

| Operation | Lane | | --- | --- | | openSession/ensureSession, fundFeeToErPayer/fundErFeePayer/ensureErFunds, delegateKeypairAccount, createPortalOwnedAccount, closeSession | L1 | | eva initialize / createTrench / deposit / finalize / ensureUserAta / buy / sell | ER (zero fee) | | Reads (erGetAccount, getGlobal, getTrench, …) | Either — ER reads pinned to commitment processed so read-after-write is fresh |

Verified live on a local validator: Portal 5TeWSsjg2gbxCyWVniXeCmwM7UtHTCK7svzJr5xYJzHf, eva CQru6EauVg2UwepFCvY5qWuqCPEWYC4ta43wBbkFYtmo.

Why the corrected encoders? The published @sonicsvm/northstar-sdk has stale OpenSession and Delegate encoders. The deployed runtime expects OpenSession to carry validator + settlement_interval_slots (65-byte payload) and Delegate to place the session account at index 2 (the deployed IDL annotation wrongly lists it at index 6 — this SDK deliberately follows the runtime, not that stale IDL). Anyone regenerating a client from the Portal IDL would build a broken Delegate. This SDK gets it right.

Install

The SDK is self-contained on three standard Solana peer dependencies:

npm install @solana/web3.js@^1.95 @coral-xyz/anchor@^0.31 @solana/spl-token@^0.4

(The package's peerDependencies accept @coral-xyz/anchor ^0.31 || ^0.32, @solana/spl-token ^0.4, @solana/web3.js ^1.95.)

Running TypeScript:

  • Node >= 22.6 to run .ts directly via --experimental-strip-types (this is what the test scripts use), or
  • compile with tsc (npm run typecheck runs tsc --noEmit).

The package ships TypeScript sources — package.json exports maps ../src/index.ts, and files includes src, idl, and README.md. The eva IDL (idl/eva_arena.json) is bundled and loaded automatically unless you override it.

60-second quickstart (localnet buy/sell)

Copy-paste runnable against a running NorthStar validator (L1 :8899 + ER :8910). Save as quickstart.ts and run with node --experimental-strip-types quickstart.ts.

import { readFileSync } from "node:fs";
import { homedir } from "node:os";
import { resolve } from "node:path";
import { Keypair } from "@solana/web3.js";
import { NorthstarEva } from "northstar-eva-sdk"; // or: from "../src/index.ts"

// Load a funded local keypair (e.g. ~/.config/solana/id.json)
const path = resolve(homedir(), ".config/solana/id.json");
const wallet = Keypair.fromSecretKey(Uint8Array.from(JSON.parse(readFileSync(path, "utf8"))));

const sdk = NorthstarEva.create({ network: "localnet", wallet });

// 1. Health + sync (both lanes should be up)
const h = await sdk.health();
if (!h.l1 || !h.er) throw new Error(`validator not healthy: ${JSON.stringify(h)}`);

// 2. (Optional since 0.1.1) Open the Portal session on L1 — the deposit below ensures it for you.
await sdk.ensureSession(); // idempotent; no-op if already open

// 3. Fund the ER fee payer via Portal DepositFee on L1 (ER balance = these credits).
//    ensureErFunds/fundFeeToErPayer open the session automatically if step 2 was skipped.
await sdk.ensureErFunds(250_000_000n); // tops up + waits for the credit to land

// 4. Initialize the eva global state on the ER (idempotent)
//    NOTE: keep biddingDuration SMALL so finalize doesn't wait for many slots
await sdk.initialize({ biddingDuration: 120n, tradingDuration: 100_000n });

// 5. Create a trench (id auto-derived as totalTrenches + 1)
const { trenchId } = await sdk.createTrench();

// 6. Deposit into the bidding phase (ER)
await sdk.deposit({ trenchId, lamports: 50_000_000n }); // 0.05 SOL

// 7. Finalize — waits for ER slot >= biddingEndBlock, then seeds the AMM -> Trading
await sdk.finalize({ trenchId });

// 8. Pre-create the user's token ATA on the ER (buy/sell require it to exist)
await sdk.ensureUserAta({ trenchId });

// 9. BUY 0.01 SOL on the ER (ZERO fee). Offline quote matches on-chain mint exactly.
const trench = await sdk.getTrench(trenchId, "er", 5);
const quote = sdk.previewBuy(trench!.reserves, 10_000_000n);
const buy = await sdk.buy({ trenchId, solPayWithFee: 10_000_000n });
console.log("bought tokens:", buy.userTokens, "(offline quote:", quote?.tokensOut, ")");

// 10. SELL half of them back (ER, zero fee)
const sell = await sdk.sell({ trenchId, tokenAmount: buy.userTokens / 2n });
console.log("remaining tokens:", sell.userTokens);

All lamport/token amounts are bigint. The wallet must be a funded L1 keypair (it pays L1 setup fees and is recorded as the validator identity in the session unless you pass validatorIdentity).

Two usage modes

(a) Package import (recommended for a project):

import { NorthstarEva } from "northstar-eva-sdk";
// also available: NETWORKS, resolveConfig, previewBuy/previewSell,
// decodeTrench/decodeGlobalState, portal.*, evaPdas.*, and the AMM helpers.

(b) Single-file drop-in: dist-single/northstar-eva-sdk.single.ts is a self-contained file whose only dependencies are the three standard Solana libs. Copy it into any project and import from it directly — no package install of the SDK needed. Regenerate it with npm run bundle.

The committed dist-single/northstar-eva-sdk.single.ts is a build artifact and may be out of date — re-run npm run bundle before shipping the drop-in file.

Configuration

NorthstarEva.create(opts) takes:

| Option | Type | Default | Notes | | --- | --- | --- | --- | | wallet | Keypair | required | Signer / fee payer (L1). | | network | "localnet" \| "devnet" | "localnet" | Selects the preset endpoints/program-ids. | | l1Rpc / erRpc / erWs | string | from preset | Override individual endpoints. | | portalProgramId / evaProgramId | string \| PublicKey | Portal 5TeW…, eva CQru… | Override for a different deployment. | | evaIdl / evaIdlPath | Idl / path | bundled idl/eva_arena.json | Supply a custom IDL or path. | | validatorIdentity | string \| PublicKey | wallet.publicKey | Settlement signer recorded in the session. | | gridId | number \| bigint | 1n | OpenSession param. | | ttlSlots | number \| bigint | 1_000_000n | Session TTL (slots). | | feeCap | number \| bigint | 1_000_000_000n | Session fee cap. | | settlementIntervalSlots | number \| bigint | 50n | Settlement cadence (slots). | | pollIntervalMs | number | 500 | ms between status/read polls. |

Localnet

Zero config — the localnet preset points at the in-process validator:

const sdk = NorthstarEva.create({ network: "localnet", wallet });
// l1Rpc http://127.0.0.1:8899 · erRpc http://127.0.0.1:8910 · erWs ws://127.0.0.1:8911

Devnet

Public Solana devnet has NO NorthStar ER/Portal. "devnet" here means a NorthStar validator running in a devnet-style deployment. The devnet preset has empty l1Rpc/erRpc sentinelsresolveConfig throws fast unless you pass your node's endpoints:

const sdk = NorthstarEva.create({
  network: "devnet",
  l1Rpc: "https://your-northstar-node:8899",   // REQUIRED — your NorthStar node
  erRpc: "https://your-northstar-node:8910",    // REQUIRED — its ER lane
  erWs:  "wss://your-northstar-node:8911",       // optional
  // portalProgramId / evaProgramId default to the same ids; override if redeployed
  wallet,
});

The "works devnet later" claim is true only against a self-hosted NorthStar devnet deployment — there is no hosted default.

API reference

NorthstarEvasrc/client.ts, re-exported from src/index.ts. Lamports/tokens are bigint.

Construction

| Method | Lane | Description | Returns | | --- | --- | --- | --- | | static create(opts: NorthstarEvaOptions) | — | Build an instance (resolves config, binds an L1 connection at confirmed and an ER connection at processed, loads the eva IDL). | NorthstarEva |

Health / session reads

| Method | Lane | Description | Returns | | --- | --- | --- | --- | | health() | L1 + ER | Pings both lanes (getSlot on L1, getHealth on ER). | Promise<{ l1: boolean; er: boolean }> | | syncStatus() | ER | ER sync status (northstarSysGetSyncStatus). | Promise<{ isSyncing; latestSyncedSlot; latestL1Slot }> | | getSessionPdaFromEr() | ER | Session PDA as reported by the ER (getSessionPda). | Promise<any> | | getDelegatedAccounts() | ER | Accounts currently delegated in the session. | Promise<string[]> | | sessionPda() | — (pure) | Derives the Portal session PDA locally. | PublicKey |

Raw account reads

| Method | Lane | Description | Returns | | --- | --- | --- | --- | | erRpc(method, params?) | ER | Raw JSON-RPC to the ER (3 retries on transient network errors). | Promise<T> | | erGetAccount(pk) | ER | Read an account from the ER at processed. | Promise<AccountData \| null> | | l1GetAccount(pk) | L1 | Read an account from L1 at confirmed. | Promise<AccountData \| null> | | erBalance(pk) | ER | Lamport balance on the ER (getBalance, processed). | Promise<bigint> |

AccountData = { data: Uint8Array; lamports: bigint; owner: string }.

Portal control / custody (L1)

| Method | Lane | Description | Returns | | --- | --- | --- | --- | | openSession() | L1 | Open the global Portal session (idempotent — checks if the session PDA exists first). | Promise<{ signature: string \| null; sessionPda: PublicKey; alreadyOpen: boolean }> | | ensureSession() | L1 | The "open session" step that DepositFee depends on. Idempotent: opens the session only if its PDA is absent. Called for you by fundFeeToErPayer/fundErFeePayer. | Promise<{ sessionPda: PublicKey; opened: boolean; signature: string \| null }> | | closeSession() | L1 | Close the session. | Promise<string> (signature) | | depositReceiptPda(recipient?) | — (pure) | Derive the DepositReceipt PDA for a recipient (defaults to wallet). | PublicKey | | fundFeeToErPayer(lamports, recipient?, { ensureSession? }) | L1 | "Fund fee to ER payer" deposit. Contains the open-session step — calls ensureSession() first (DepositFee requires a session), then Portal DepositFee. Pass { ensureSession: false } to skip. | Promise<string> (DepositFee sig) | | fundErFeePayer(lamports, recipient?, { ensureSession? }) | L1 | Same as above (the underlying impl). Portal DepositFee — credits an ER fee payer (its ER balance = these credits, not the L1 balance); ensures the session first. | Promise<string> | | ensureErFunds(lamports, recipient?) | L1 + ER | Ensure the ER fee payer has >= lamports; tops up via DepositFee (which ensures the session) and polls up to 25×800ms (~20s) for the credit. | Promise<bigint> (resulting balance) | | delegateKeypairAccount(target: Keypair) | L1 | Delegate a plain System-owned keypair account into the ER (the "proper" delegation path). | Promise<string> | | createPortalOwnedAccount(space?) | L1 | Create a fresh Portal-owned account (delegation-target helper). | Promise<Keypair> |

delegateKeypairAccount / createPortalOwnedAccount are not exercised by the integration test (dev mode uses zero delegated accounts). Their encoders/account order are unit-tested and verified against the runtime, but the end-to-end delegation+settlement flow is unproven on the live validator.

eva PDA derivations (pure, no network)

| Method | Lane | Description | Returns | | --- | --- | --- | --- | | globalStatePda() | — | eva global state PDA (["global"]). | PublicKey | | trenchPda(id) | — | Trench PDA (["trench", id_le]). | PublicKey | | tokenMintPda(id) | — | Trench token mint PDA (["mint", trench]). | PublicKey | | trenchVault(id) | — | Trench vault ATA (mint, trench). | PublicKey | | userAta(id, user?) | — | User token ATA (mint, user; defaults to wallet). | PublicKey |

eva state reads

| Method | Lane | Description | Returns | | --- | --- | --- | --- | | getGlobal(source?="er") | ER or L1 | Decode the global state. | Promise<GlobalState \| null> | | getTrench(id, source?="er", retries?=1) | ER or L1 | Decode a trench (re-reads up to retries attempts, sleeping pollIntervalMs between). Param is really "attempts": retries=1 = a single read, no sleep. | Promise<Trench \| null> | | getUserTokenBalance(id, user?, source?="er") | ER or L1 | User's token balance (SPL amount @ offset 64). | Promise<bigint> |

GlobalState = { admin; feeRecipient; totalTrenches; biddingDuration; tradingDuration }. Trench = { id; status: "Bidding"|"Trading"|"Ended"|"Unknown"; totalDepositedSol; reserves: AmmReserves; biddingStartBlock; biddingEndBlock; tradingEndBlock; tokenMint; startTime; prizePoolReserves; bump }.

Off-chain quotes / decode (pure)

| Method | Lane | Description | Returns | | --- | --- | --- | --- | | previewBuy(reserves, solPayWithFee) | — | Offline buy quote (1% fee, then constant-product curve). Matches the on-chain mint exactly. | BuyQuote \| null | | previewSell(reserves, tokensIn) | — | Offline sell quote (curve output, then 1% fee). | SellQuote \| null | | decodeReserves(data) | — | Decode AMM reserves from raw trench bytes. | AmmReserves |

BuyQuote = { solPayWithFee; fee; solIntoCurve; tokensOut; reservesAfter }. SellQuote = { tokensIn; solOutGross; fee; netSolToUser; reservesAfter }.

eva game (executed on the ER, zero fee)

| Method | Lane | Description | Returns | | --- | --- | --- | --- | | initialize({ biddingDuration, tradingDuration }) | ER | Create the global state. Idempotent — returns null if already initialized. | Promise<string \| null> | | createTrench({ initialVirtualTokenReserves? }) | ER | Create the next trench; trenchId = totalTrenches + 1 (auto). Default initialVirtualTokenReserves = INITIAL_TOKEN_SUPPLY / 2. | Promise<{ trenchId: bigint; signature: string }> | | deposit({ trenchId, lamports, thinkId? }) | ER | Deposit SOL during the bidding phase. | Promise<string> | | finalize({ trenchId }) | ER | Waits for ER slot >= biddingEndBlock (polls up to 80×pollIntervalMs), then seeds the AMM → Trading. | Promise<string> | | ensureUserAta({ trenchId, user? }) | ER | Idempotently create the user's token ATA on the ER (buy/sell require it). | Promise<string> | | buy({ trenchId, solPayWithFee, thinkId? }) | ER | Buy tokens off the curve (1% fee). | Promise<{ signature; trench: Trench; userTokens: bigint }> | | sell({ trenchId, tokenAmount, minSolOutput?, thinkId? }) | ER | Sell tokens back to the curve (1% fee). | Promise<{ signature; trench: Trench; userTokens: bigint }> |

finalize sets biddingEndBlock = current_slot + biddingDuration. With a large biddingDuration (the test uses 120), the slot wait can hit its ~40s budget (80×500ms) and then attempt finalize anyway, failing on-chain with NotBiddingPhase. Use a small biddingDuration for fast demos.

Also exported from src/index.ts

  • NETWORKS, resolveConfig, DEFAULT_PORTAL_PROGRAM_ID, DEFAULT_EVA_PROGRAM_ID, INITIAL_TOKEN_SUPPLY, TOKEN_DECIMALS
  • AMM helpers (src/amm.ts): getBuyPrice, applyBuy, getSellPrice, applySell, type AmmReserves, TradeResult
  • Quote helpers: previewBuy, previewSell, FEE_RATE_BPS (100 = 1%), BASIS_POINTS (10000), types BuyQuote/SellQuote
  • Decoders: decodeTrench, decodeTrenchReserves, decodeGlobalState, decodeTokenAmount, TRENCH_OFFSETS, TRENCH_LEN (242), types Trench/TrenchStatus/GlobalState
  • portal.* (corrected Portal encoders + PDA derivations + instruction builders)
  • evaPdas.* (eva PDA/ATA derivations)
  • Types: NorthstarEvaOptions, Lane, AccountData, ErTxResult, NetworkName, NetworkPreset, ResolvedConfig, NorthstarEvaInput

Running the tests

# Unit tests (pure: AMM, codec, decode) — no validator needed (~200ms)
npm test                  # node --experimental-strip-types --test test/{amm,codec,decode}.test.ts

# Live integration test — requires a running validator
#   (L1 :8899 + ER :8910 up, eva deployed, ~/.config/solana/id.json funded)
npm run test:integration  # override the keypair with ADMIN_KEYPAIR=/path/to/id.json

# Typecheck
npm run typecheck         # tsc --noEmit

# Regenerate the single-file drop-in
npm run bundle            # writes dist-single/northstar-eva-sdk.single.ts

The integration test (test/integration.test.ts) drives the full lifecycle: health → sync → session → fund ER → initialize → createTrench → deposit → finalize → ATA → BUY → SELL, asserting that realSolReserves == totalDepositedSol * 20 / 100 after finalize and that the offline previewBuy quote exactly equals the on-chain tokens minted.

Validation status: unit 16/16 pass; integration all 11 steps pass against a live validator; tsc --noEmit clean (both the package and the single-file project). No correctness bugs found in a source review against the deployed runtime + IDL.

Limitations (read before using for anything real)

  • DEV-MODE ONLY / not trustless. eva runs on the ER with a session that has zero delegated accounts (the write-filter is bypassed) because eva's PDAs cannot sign Portal::Delegate (the "PDA-signer wall"). This is the only way it runs today. Single global session, single validator, ER state in-memory (non-durable, lost on restart). Not for real user funds — demo/devnet only.
  • ER fee-payer balance = Portal DepositFee credits, not the L1 balance. ensureErFunds polls ~20s for the credit; if it lags beyond that, the method returns the (insufficient) balance without throwing — assert the returned balance yourself.
  • Settlement is deliberately narrow: only same-size DATA diffs of pre-existing delegated accounts + Portal SOL rails settle back. eva's new ER accounts, lamport moves, and owner/size changes do not settle — its ER results are demo-grade, not committed custody.
  • The deployed Portal IDL annotation for Delegate is stale (session listed at index 6); this SDK follows the runtime (session at index 2). Do not regenerate a Portal client from that IDL.
  • devnet has no hosted endpoints — you must self-host a NorthStar node and pass l1Rpc/erRpc.
  • The single-file bundle is a build artifact — re-run npm run bundle before shipping it.

For the full system picture (the two lanes, dev mode, settlement), see local_docs/architecture/eva-on-northstar-how-it-works.md.