northstar-eva-sdk 0.4.0 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -16,6 +16,27 @@ const sdk = NorthstarEva.create({ provider, erRpc: ER_RPC_URL, evaProgramId });
16
16
  ```
17
17
  > Ships compiled JS + TypeScript types — works in any Node ≥18 project (JS or TS). 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](PUBLISHING.md).
18
18
 
19
+ ## Construction — one SDK, three ways (NorthStar or not)
20
+
21
+ 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.
22
+
23
+ ```ts
24
+ // (1) provider IS the ER, L1 passed separately — RECOMMENDED for NorthStar apps
25
+ const sdk = NorthstarEva.create({ program, provider, l1Endpoint: "https://api.devnet.solana.com" });
26
+ // → provider.connection = ER, l1Endpoint = L1. sdk.isNorthStar === true
27
+
28
+ // (2) NO NorthStar — the SAME SDK as a plain eva client (one chain, no ER/Portal)
29
+ const sdk = NorthstarEva.create({ program, provider });
30
+ // → eva methods run on the provider; deposit/withdraw/session throw "requires NorthStar". sdk.isNorthStar === false
31
+
32
+ // (3) legacy: provider = L1, ER passed as erRpc (still supported)
33
+ const sdk = NorthstarEva.create({ provider, erRpc: "https://ephemeral.devnet.sonic.game" });
34
+ ```
35
+
36
+ - **`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.)
37
+ - **Same SDK, both services:** with no `l1Endpoint`/`erRpc`, you get a normal eva client — one codebase covers the NorthStar and non-NorthStar deployments.
38
+ - **Withdraw returns to the sender:** `withdrawFeeFromEr({ lamports })` reclaims the fee to whoever funded the ER (the SDK signer) — no recipient needed.
39
+
19
40
  ## What's new in 0.4.0
20
41
 
21
42
  **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):
@@ -145,6 +145,12 @@ export interface NorthstarEvaInput {
145
145
  l1Rpc?: string;
146
146
  erRpc?: string;
147
147
  erWs?: string;
148
+ /**
149
+ * The L1 endpoint, for the "provider is the ER" convention: when set, the SDK treats the
150
+ * `provider`/`connection` RPC as the **ER** and uses `l1Endpoint` as the **L1** (Portal) lane.
151
+ * (Alternative to passing `erRpc` with a provider that points at L1.)
152
+ */
153
+ l1Endpoint?: string;
148
154
  portalProgramId?: string | PublicKey;
149
155
  evaProgramId?: string | PublicKey;
150
156
  gridId?: number | bigint;
@@ -283,11 +289,17 @@ export declare class NorthstarEva {
283
289
  readonly payer: PublicKey;
284
290
  readonly validatorIdentity: PublicKey;
285
291
  readonly program: anchor.Program<Idl>;
292
+ /** Whether the NorthStar ER + Portal bridge is configured (false = plain Solana, eva only). */
293
+ readonly northstar: boolean;
286
294
  private readonly pollMs;
287
295
  /** The eva program id in use (configurable per environment). */
288
296
  get evaProgramId(): PublicKey;
289
297
  /** The Portal program id in use. */
290
298
  get portalProgramId(): PublicKey;
299
+ /** True when deposit/withdraw/session (the NorthStar Portal/ER bridge) are available. */
300
+ get isNorthStar(): boolean;
301
+ /** Throws if the NorthStar bridge isn't configured (used to guard Portal-only methods). */
302
+ private requireNorthStar;
291
303
  constructor(opts: NorthstarEvaOptions);
292
304
  static create(opts: NorthstarEvaOptions): NorthstarEva;
293
305
  erRpc<T = any>(method: string, params?: unknown[]): Promise<T>;
@@ -475,13 +487,16 @@ export declare class NorthstarEva {
475
487
  /** The ER WithdrawalSink PDA for a recipient (where ER withdrawal requests are sent). */
476
488
  getWithdrawalSink(recipient?: PublicKey): PublicKey;
477
489
  /**
478
- * "Withdraw fee from ER" (path 2): an ER `system_transfer` from `recipient` → its
479
- * WithdrawalSink. The validator pays the L1 SOL ASYNCHRONOUSLY at the next settlement
480
- * (use {@link waitForL1Settlement} to await it). `recipient` signs the ER transfer and may
481
- * be a **Keypair OR a wallet adapter** (e.g. a Turnkey `ReadOnlyWallet`); the L1 receiver never signs.
482
- * Defaults to the SDK's own signer if omitted.
490
+ * "Withdraw fee from ER" (path 2): an ER `system_transfer` from the **sender** → its
491
+ * WithdrawalSink, returning the fee to whoever sent it. The validator pays the L1 SOL
492
+ * ASYNCHRONOUSLY at the next settlement (use {@link waitForL1Settlement} to await it).
493
+ *
494
+ * By default the **sender = the SDK's signer** (the account that funded the ER), so
495
+ * `withdrawFeeFromEr({ lamports })` reclaims to the sender — no recipient needed. You may
496
+ * still pass `recipient` (a Keypair OR a wallet adapter) to reclaim a different funded
497
+ * account; it signs the ER transfer (the L1 receiver never signs).
483
498
  *
484
- * NOTE: the L1 payout currently goes to the SAME account that deposited — an arbitrary
499
+ * NOTE: the L1 payout goes to the SAME account that deposited (the sender) — an arbitrary
485
500
  * `toL1` destination is not supported by the Portal program yet (pending protocol change).
486
501
  */
487
502
  requestWithdraw(p: {
@@ -2159,12 +2159,21 @@ export class NorthstarEva {
2159
2159
  /** The fee-payer / signer public key. */
2160
2160
  payer;
2161
2161
  validatorIdentity;
2162
- program; // eva, bound to the ER connection
2162
+ program; // eva, runs on the ER (= provider) connection
2163
+ /** Whether the NorthStar ER + Portal bridge is configured (false = plain Solana, eva only). */
2164
+ northstar;
2163
2165
  pollMs;
2164
2166
  /** The eva program id in use (configurable per environment). */
2165
2167
  get evaProgramId() { return this.cfg.evaProgramId; }
2166
2168
  /** The Portal program id in use. */
2167
2169
  get portalProgramId() { return this.cfg.portalProgramId; }
2170
+ /** True when deposit/withdraw/session (the NorthStar Portal/ER bridge) are available. */
2171
+ get isNorthStar() { return this.northstar; }
2172
+ /** Throws if the NorthStar bridge isn't configured (used to guard Portal-only methods). */
2173
+ requireNorthStar(op) {
2174
+ if (!this.northstar)
2175
+ throw new Error(`${op} requires NorthStar (the ER + Portal bridge), which is not configured. Pass \`l1Endpoint\` (with a provider whose RPC is the ER) or \`erRpc\` to enable deposit/withdraw/session. Without it, only the eva program methods work (plain Solana).`);
2176
+ }
2168
2177
  constructor(opts) {
2169
2178
  // A pre-built Anchor program (Bai's `(program, provider)` shape) is the source of truth
2170
2179
  // for the program id, and can also supply the provider's wallet + L1 connection.
@@ -2174,9 +2183,39 @@ export class NorthstarEva {
2174
2183
  if (passedProgram && opts.evaProgramId && new PublicKey(opts.evaProgramId).toBase58() !== programId) {
2175
2184
  throw new Error(`evaProgramId (${new PublicKey(opts.evaProgramId).toBase58()}) does not match the passed program.programId (${programId}). Pass one source of truth.`);
2176
2185
  }
2177
- // ── endpoints: a provider/connection/program can supply the L1 endpoint; ER is always separate ──
2186
+ // ── endpoints: resolve the L1 + ER lanes and whether NorthStar (ER+Portal) is active ──
2178
2187
  const providerConn = opts.provider?.connection ?? opts.connection ?? programProvider?.connection;
2179
- this.cfg = resolveConfig({ ...opts, l1Rpc: opts.l1Rpc ?? providerConn?.rpcEndpoint, evaProgramId: opts.evaProgramId ?? programId });
2188
+ const providerEndpoint = providerConn?.rpcEndpoint;
2189
+ const l1Endpoint = opts.l1Endpoint ?? opts.L1Endpoint; // accept Bai's casing too
2190
+ let l1RpcStr, erRpcStr, northstar;
2191
+ if (l1Endpoint) {
2192
+ // NEW convention: the provider's RPC IS the ER; l1Endpoint is the L1 (Portal) lane.
2193
+ northstar = true;
2194
+ l1RpcStr = l1Endpoint;
2195
+ erRpcStr = opts.erRpc ?? providerEndpoint;
2196
+ if (!erRpcStr)
2197
+ throw new Error("`l1Endpoint` was given but no ER RPC — pass a `provider`/`connection` (its RPC is the ER) or an explicit `erRpc`.");
2198
+ }
2199
+ else if (opts.erRpc) {
2200
+ // LEGACY convention: provider's RPC is L1, erRpc is the ER.
2201
+ northstar = true;
2202
+ l1RpcStr = opts.l1Rpc ?? providerEndpoint;
2203
+ erRpcStr = opts.erRpc;
2204
+ }
2205
+ else if (providerEndpoint || opts.l1Rpc) {
2206
+ // NO-NORTHSTAR: a single chain (provider / l1Rpc). eva runs there; no Portal/ER bridge.
2207
+ northstar = false;
2208
+ l1RpcStr = erRpcStr = opts.l1Rpc ?? providerEndpoint;
2209
+ }
2210
+ else {
2211
+ // Network preset (e.g. localnet has both lanes; devnet needs explicit endpoints).
2212
+ const presetEr = NETWORKS[opts.network === "devnet" ? "devnet" : "localnet"].erRpc;
2213
+ northstar = presetEr !== "";
2214
+ l1RpcStr = opts.l1Rpc;
2215
+ erRpcStr = opts.erRpc; // resolved from preset by resolveConfig below
2216
+ }
2217
+ this.northstar = northstar;
2218
+ this.cfg = resolveConfig({ ...opts, l1Rpc: l1RpcStr ?? opts.l1Rpc, erRpc: erRpcStr ?? opts.erRpc, evaProgramId: opts.evaProgramId ?? programId });
2180
2219
  // ── signer: accept a Keypair (wrap it), a wallet adapter, provider.wallet, or program.provider.wallet ──
2181
2220
  const w = opts.wallet;
2182
2221
  let signer;
@@ -2196,24 +2235,31 @@ export class NorthstarEva {
2196
2235
  ? (typeof opts.validatorIdentity === "string" ? new PublicKey(opts.validatorIdentity) : opts.validatorIdentity)
2197
2236
  : this.payer;
2198
2237
  this.pollMs = opts.pollIntervalMs ?? 500;
2199
- this.l1 = providerConn ?? new Connection(this.cfg.l1Rpc, "confirmed");
2200
- // ER provider reads at "processed" so account resolution / reads are fresh.
2201
- this.er = new Connection(this.cfg.erRpc, "processed");
2202
- // Guard: a custom (non-localhost) L1 with the default localhost ER is almost certainly a
2203
- // forgotten erRpc NorthStar's ER is a different URL than L1, fail loudly instead of hitting localhost.
2204
- const customL1 = !!(opts.l1Rpc || providerConn);
2205
- const erDefaulted = !opts.erRpc && opts.network !== "devnet";
2206
- if (customL1 && erDefaulted && !/127\.0\.0\.1|localhost/.test(this.cfg.l1Rpc)) {
2207
- throw new Error(`Custom L1 endpoint (${this.cfg.l1Rpc}) but no ER endpoint — NorthStar's ER is a SEPARATE URL. Pass erRpc (e.g. the :8910 endpoint).`);
2238
+ // Assign lanes per the resolved convention. `this.er` is where the eva program executes
2239
+ // (the provider's connection in the new/no-NorthStar modes); reads use "processed" so
2240
+ // read-after-write is fresh. `this.l1` is the Portal control lane.
2241
+ if (l1Endpoint) {
2242
+ // provider IS the ER; l1Endpoint is the L1.
2243
+ this.er = providerConn ?? new Connection(this.cfg.erRpc, "processed");
2244
+ this.l1 = new Connection(this.cfg.l1Rpc, "confirmed");
2245
+ }
2246
+ else if (!northstar) {
2247
+ // single chain: eva runs on the provider; no separate Portal/ER lane.
2248
+ this.l1 = this.er = providerConn ?? new Connection(this.cfg.l1Rpc, "confirmed");
2249
+ }
2250
+ else {
2251
+ // legacy / preset: provider (if any) is L1, ER is its own endpoint.
2252
+ this.l1 = providerConn ?? new Connection(this.cfg.l1Rpc, "confirmed");
2253
+ this.er = new Connection(this.cfg.erRpc, "processed");
2208
2254
  }
2209
2255
  if (passedProgram) {
2210
- // Use the caller's program as-is (only used to BUILD instructions; the SDK sends manually).
2256
+ // Use the caller's program (program + provider) as-is eva methods build from it.
2211
2257
  this.program = passedProgram;
2212
2258
  }
2213
2259
  else {
2214
2260
  const idl = opts.evaIdl ?? (opts.evaIdlPath ? JSON.parse(readFileSync(opts.evaIdlPath, "utf8")) : loadBundledIdl());
2215
2261
  idl.address = this.cfg.evaProgramId.toBase58(); // honor config / devnet override
2216
- // eva program is bound to the ER connection (it executes there) + signs through the same wallet.
2262
+ // eva program runs on `this.er` (= provider) + signs through the same wallet.
2217
2263
  const provider = new A.AnchorProvider(this.er, this.wallet, { commitment: "processed" });
2218
2264
  this.program = new A.Program(idl, provider);
2219
2265
  }
@@ -2455,6 +2501,7 @@ export class NorthstarEva {
2455
2501
  }
2456
2502
  // ───────────────────────── Portal control (L1) ─────────────────────────
2457
2503
  async openSession() {
2504
+ this.requireNorthStar("openSession");
2458
2505
  const pda = this.sessionPda();
2459
2506
  if (await this.l1GetAccount(pda))
2460
2507
  return { signature: null, sessionPda: pda, alreadyOpen: true };
@@ -2462,6 +2509,7 @@ export class NorthstarEva {
2462
2509
  return { signature, sessionPda: pda, alreadyOpen: false };
2463
2510
  }
2464
2511
  async closeSession() {
2512
+ this.requireNorthStar("closeSession");
2465
2513
  return this.sendOnL1([this.buildCloseSessionIx()], [this.wallet]);
2466
2514
  }
2467
2515
  /**
@@ -2487,6 +2535,7 @@ export class NorthstarEva {
2487
2535
  * Returns the DepositFee transaction signature.
2488
2536
  */
2489
2537
  async fundErFeePayer(lamports, recipient = this.wallet.publicKey, opts = {}) {
2538
+ this.requireNorthStar("fundFeeToErPayer (deposit L1→ER)");
2490
2539
  if (opts.ensureSession !== false)
2491
2540
  await this.ensureSession();
2492
2541
  return this.sendOnL1([this.buildDepositFeeIx(lamports, recipient)], [this.wallet]);
@@ -2577,19 +2626,23 @@ export class NorthstarEva {
2577
2626
  return portal.withdrawalSinkPda(this.cfg.portalProgramId, this.sessionPda(), recipient);
2578
2627
  }
2579
2628
  /**
2580
- * "Withdraw fee from ER" (path 2): an ER `system_transfer` from `recipient` → its
2581
- * WithdrawalSink. The validator pays the L1 SOL ASYNCHRONOUSLY at the next settlement
2582
- * (use {@link waitForL1Settlement} to await it). `recipient` signs the ER transfer and may
2583
- * be a **Keypair OR a wallet adapter** (e.g. a Turnkey `ReadOnlyWallet`); the L1 receiver never signs.
2584
- * Defaults to the SDK's own signer if omitted.
2629
+ * "Withdraw fee from ER" (path 2): an ER `system_transfer` from the **sender** → its
2630
+ * WithdrawalSink, returning the fee to whoever sent it. The validator pays the L1 SOL
2631
+ * ASYNCHRONOUSLY at the next settlement (use {@link waitForL1Settlement} to await it).
2632
+ *
2633
+ * By default the **sender = the SDK's signer** (the account that funded the ER), so
2634
+ * `withdrawFeeFromEr({ lamports })` reclaims to the sender — no recipient needed. You may
2635
+ * still pass `recipient` (a Keypair OR a wallet adapter) to reclaim a different funded
2636
+ * account; it signs the ER transfer (the L1 receiver never signs).
2585
2637
  *
2586
- * NOTE: the L1 payout currently goes to the SAME account that deposited — an arbitrary
2638
+ * NOTE: the L1 payout goes to the SAME account that deposited (the sender) — an arbitrary
2587
2639
  * `toL1` destination is not supported by the Portal program yet (pending protocol change).
2588
2640
  */
2589
2641
  async requestWithdraw(p) {
2590
- const recipient = p.recipient ?? this.wallet;
2591
- const ix = this.buildWithdrawFeeFromErIx({ lamports: p.lamports, recipient: recipient.publicKey, toL1: p.toL1 });
2592
- return this.sendOnEr([ix], [recipient]);
2642
+ this.requireNorthStar("withdrawFeeFromEr (withdraw ER→L1)");
2643
+ const sender = p.recipient ?? this.wallet; // default: the sender (SDK signer)
2644
+ const ix = this.buildWithdrawFeeFromErIx({ lamports: p.lamports, recipient: sender.publicKey, toL1: p.toL1 });
2645
+ return this.sendOnEr([ix], [sender]);
2593
2646
  }
2594
2647
  /** "Withdraw fee from ER" (the green-box withdraw API) — alias of {@link requestWithdraw}. */
2595
2648
  async withdrawFeeFromEr(p) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "northstar-eva-sdk",
3
- "version": "0.4.0",
3
+ "version": "0.5.0",
4
4
  "description": "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.",
5
5
  "type": "module",
6
6
  "license": "MIT",