northstar-eva-sdk 0.5.0 → 0.7.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
@@ -36,6 +36,7 @@ const sdk = NorthstarEva.create({ provider, erRpc: "https://ephemeral.devnet.son
36
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
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
38
  - **Withdraw returns to the sender:** `withdrawFeeFromEr({ lamports })` reclaims the fee to whoever funded the ER (the SDK signer) — no recipient needed.
39
+ - **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).
39
40
 
40
41
  ## What's new in 0.4.0
41
42
 
@@ -68,7 +69,7 @@ tx.sign(myKeypair); // or wallet.signTransaction(tx)
68
69
  await connection.sendRawTransaction(tx.serialize());
69
70
  ```
70
71
 
71
- Builders (each mirrors the send-method's inputs): `buildOpenSessionIx()`, `buildCloseSessionIx()`, `buildDepositFeeIx(lamports, recipient?)`, `buildWithdrawFeeFromErIx({ lamports, recipient?, 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)`, `buildClaimTokensIx(p)`, `buildDistributePrizeIx(p)`, plus `buildTransaction(ixs, { lane?, feePayer? })`, `nextTrenchId()`, and the `agentPda(id, user?)` PDA helper.
72
+ Builders (each mirrors the send-method's inputs): `buildOpenSessionIx()`, `buildCloseSessionIx()`, `buildDepositFeeIx(lamports, recipient?)`, `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)`, `buildClaimTokensIx(p)`, `buildDistributePrizeIx(p)`, plus `buildTransaction(ixs, { lane?, feePayer? })`, `nextTrenchId()`, and the `agentPda(id, user?)` PDA helper.
72
73
 
73
74
  > 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**.
74
75
 
@@ -133,7 +134,7 @@ The deposit ("fund fee to ER payer") API now **contains the open-session step**,
133
134
 
134
135
  > **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.
135
136
 
136
- The three user-fund APIs: **query balance** → `querySolBalance(account?)` · **deposit** → `fundFeeToErPayer(lamports, recipient?)` · **withdraw** → `withdrawFeeFromEr({ lamports, recipient, toL1? })`.
137
+ The three user-fund APIs: **query balance** → `querySolBalance(account?)` · **deposit** → `fundFeeToErPayer(lamports, recipient?)` · **withdraw** → `withdrawFeeFromEr({ lamports, sender?, toL1? })`.
137
138
 
138
139
  ## What it is + the fee point
139
140
 
@@ -101,6 +101,46 @@ export interface GlobalState {
101
101
  tradingDuration: bigint;
102
102
  }
103
103
  export declare function decodeGlobalState(data: Uint8Array): GlobalState;
104
+ export declare const SESSION_LEN = 219;
105
+ export declare const SESSION_DISCRIMINATOR = 1;
106
+ export declare const SESSION_OFFSETS: {
107
+ readonly discriminator: 0;
108
+ readonly gridId: 1;
109
+ readonly ttlSlots: 9;
110
+ readonly feeCap: 17;
111
+ readonly createdAt: 25;
112
+ readonly nonce: 33;
113
+ readonly authority: 49;
114
+ readonly validator: 81;
115
+ readonly settlementIntervalSlots: 113;
116
+ readonly lastSettledL1Slot: 121;
117
+ readonly lastSettledErSlot: 129;
118
+ readonly settlementStatus: 137;
119
+ readonly settlementErSlot: 138;
120
+ readonly settlementStartedL1Slot: 210;
121
+ readonly bump: 218;
122
+ };
123
+ export type SettlementStatus = "Idle" | "InProgress" | "Unknown";
124
+ export interface SessionAccount {
125
+ discriminator: number;
126
+ gridId: bigint;
127
+ ttlSlots: bigint;
128
+ feeCap: bigint;
129
+ createdAt: bigint;
130
+ /** created_at + ttl_slots — the session is expired once the L1 slot passes this (matches the program's `is_expired`). */
131
+ expiresAtSlot: bigint;
132
+ nonce: bigint;
133
+ authority: Uint8Array;
134
+ validator: Uint8Array;
135
+ settlementIntervalSlots: bigint;
136
+ lastSettledL1Slot: bigint;
137
+ lastSettledErSlot: bigint;
138
+ settlementStatus: SettlementStatus;
139
+ settlementErSlot: bigint;
140
+ settlementStartedL1Slot: bigint;
141
+ bump: number;
142
+ }
143
+ export declare function decodeSession(data: Uint8Array): SessionAccount;
104
144
  export declare function decodeTokenAmount(data: Uint8Array): bigint;
105
145
  /**
106
146
  * Network presets + resolved config. Works for localnet now and devnet later —
@@ -319,6 +359,15 @@ export declare class NorthstarEva {
319
359
  getSessionPdaFromEr(): Promise<any>;
320
360
  getDelegatedAccounts(): Promise<string[]>;
321
361
  sessionPda(): anchor.web3.PublicKey;
362
+ /**
363
+ * Read + decode the Portal session account from L1 (the single global session for this Portal).
364
+ * Returns `null` if no valid session exists. Read-only — no transaction, no fee. Use the returned
365
+ * `ttlSlots`/`createdAt`/`expiresAtSlot`/`validator` for details (compare `expiresAtSlot` to the
366
+ * current L1 slot — `await this.l1.getSlot()` — to tell if it's expired).
367
+ */
368
+ getSession(): Promise<SessionAccount | null>;
369
+ /** True if a valid Portal session is open on L1 (read-only; no tx). */
370
+ isSessionOpen(): Promise<boolean>;
322
371
  /** Sign `tx` with a mixed set of signers: raw Keypairs are partial-signed, wallet adapters sign via signTransaction. */
323
372
  private signTx;
324
373
  private sendOnL1;
@@ -333,12 +382,19 @@ export declare class NorthstarEva {
333
382
  buildOpenSessionIx(): TransactionInstruction;
334
383
  /** Portal CloseSession instruction (L1). */
335
384
  buildCloseSessionIx(): TransactionInstruction;
385
+ /**
386
+ * Build version of {@link ensureSession}: returns the OpenSession instruction **only if the
387
+ * session isn't created yet**, or **`null`** if it already exists. Async (it reads the session
388
+ * on L1). The caller composes the returned ix into a tx, signs, and submits — e.g.:
389
+ * `const ix = await sdk.buildEnsureSessionIx(); if (ix) tx.add(ix);`
390
+ */
391
+ buildEnsureSessionIx(): Promise<TransactionInstruction | null>;
336
392
  /** Portal DepositFee instruction (L1) — "fund fee to ER payer". (Caller must ensure a session exists.) */
337
393
  buildDepositFeeIx(lamports: bigint, recipient?: PublicKey): TransactionInstruction;
338
- /** "Withdraw fee from ER" instruction (ER) — system transfer from `recipient` (default: signer) → its WithdrawalSink. (Portal SOL bridge; distinct from the eva `withdraw` instruction.) */
394
+ /** "Withdraw fee from ER" instruction (ER) — system transfer from `sender` (default: the signer) → its WithdrawalSink. (Portal SOL bridge; distinct from the eva `withdraw` instruction.) */
339
395
  buildWithdrawFeeFromErIx(p: {
340
396
  lamports: bigint;
341
- recipient?: PublicKey;
397
+ sender?: PublicKey;
342
398
  toL1?: PublicKey;
343
399
  }): TransactionInstruction;
344
400
  /** Next sequential trench id (= total_trenches + 1) read from the ER global state. */
@@ -491,23 +547,23 @@ export declare class NorthstarEva {
491
547
  * WithdrawalSink, returning the fee to whoever sent it. The validator pays the L1 SOL
492
548
  * ASYNCHRONOUSLY at the next settlement (use {@link waitForL1Settlement} to await it).
493
549
  *
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).
550
+ * `sender` is a **PublicKey** (default: the SDK signer's pubkey). This send-method signs with
551
+ * the SDK's wallet adapter — so `sender` must be the SDK's own account (the user's wallet, which
552
+ * signs remotely; no secret key needed). To withdraw for a different account whose signature you
553
+ * collect elsewhere, use {@link buildWithdrawFeeFromErIx} and have that account sign the tx.
498
554
  *
499
555
  * NOTE: the L1 payout goes to the SAME account that deposited (the sender) — an arbitrary
500
556
  * `toL1` destination is not supported by the Portal program yet (pending protocol change).
501
557
  */
502
558
  requestWithdraw(p: {
503
559
  lamports: bigint;
504
- recipient?: TxSigner;
560
+ sender?: PublicKey;
505
561
  toL1?: PublicKey;
506
562
  }): Promise<ErTxResult>;
507
563
  /** "Withdraw fee from ER" (the green-box withdraw API) — alias of {@link requestWithdraw}. */
508
564
  withdrawFeeFromEr(p: {
509
565
  lamports: bigint;
510
- recipient?: TxSigner;
566
+ sender?: PublicKey;
511
567
  toL1?: PublicKey;
512
568
  }): Promise<ErTxResult>;
513
569
  /** Poll an account's L1 balance until it rises by >= `expectedDelta` (settlement is async). Returns the final L1 balance. */
@@ -1938,6 +1938,41 @@ export function decodeGlobalState(data) {
1938
1938
  tradingDuration: data.length >= 96 ? uLE(data, 88, 8) : 0n,
1939
1939
  };
1940
1940
  }
1941
+ // ---- Portal Session (portal/src/state.rs struct Session; Borsh, LEN 219, discriminator 1) ----
1942
+ export const SESSION_LEN = 219;
1943
+ export const SESSION_DISCRIMINATOR = 1;
1944
+ export const SESSION_OFFSETS = {
1945
+ discriminator: 0, gridId: 1, ttlSlots: 9, feeCap: 17, createdAt: 25, nonce: 33,
1946
+ authority: 49, validator: 81, settlementIntervalSlots: 113, lastSettledL1Slot: 121,
1947
+ lastSettledErSlot: 129, settlementStatus: 137, settlementErSlot: 138,
1948
+ settlementStartedL1Slot: 210, bump: 218,
1949
+ };
1950
+ const SETTLEMENT_STATUS = ["Idle", "InProgress"];
1951
+ export function decodeSession(data) {
1952
+ if (data.length < SESSION_LEN)
1953
+ throw new Error(`Session data too short: ${data.length} < ${SESSION_LEN}`);
1954
+ const o = SESSION_OFFSETS;
1955
+ const createdAt = uLE(data, o.createdAt, 8);
1956
+ const ttlSlots = uLE(data, o.ttlSlots, 8);
1957
+ return {
1958
+ discriminator: data[o.discriminator],
1959
+ gridId: uLE(data, o.gridId, 8),
1960
+ ttlSlots,
1961
+ feeCap: uLE(data, o.feeCap, 8),
1962
+ createdAt,
1963
+ expiresAtSlot: createdAt + ttlSlots,
1964
+ nonce: uLE(data, o.nonce, 16),
1965
+ authority: data.slice(o.authority, o.authority + 32),
1966
+ validator: data.slice(o.validator, o.validator + 32),
1967
+ settlementIntervalSlots: uLE(data, o.settlementIntervalSlots, 8),
1968
+ lastSettledL1Slot: uLE(data, o.lastSettledL1Slot, 8),
1969
+ lastSettledErSlot: uLE(data, o.lastSettledErSlot, 8),
1970
+ settlementStatus: SETTLEMENT_STATUS[data[o.settlementStatus]] ?? "Unknown",
1971
+ settlementErSlot: uLE(data, o.settlementErSlot, 8),
1972
+ settlementStartedL1Slot: uLE(data, o.settlementStartedL1Slot, 8),
1973
+ bump: data[o.bump],
1974
+ };
1975
+ }
1941
1976
  // ---- SPL token account amount (u64 @ 64) ----
1942
1977
  export function decodeTokenAmount(data) {
1943
1978
  if (data.length < 72)
@@ -2321,6 +2356,22 @@ export class NorthstarEva {
2321
2356
  getSessionPdaFromEr() { return this.erRpc("getSessionPda"); }
2322
2357
  getDelegatedAccounts() { return this.erRpc("getDelegatedAccounts"); }
2323
2358
  sessionPda() { return portal.sessionPda(this.cfg.portalProgramId); }
2359
+ /**
2360
+ * Read + decode the Portal session account from L1 (the single global session for this Portal).
2361
+ * Returns `null` if no valid session exists. Read-only — no transaction, no fee. Use the returned
2362
+ * `ttlSlots`/`createdAt`/`expiresAtSlot`/`validator` for details (compare `expiresAtSlot` to the
2363
+ * current L1 slot — `await this.l1.getSlot()` — to tell if it's expired).
2364
+ */
2365
+ async getSession() {
2366
+ const info = await this.l1GetAccount(this.sessionPda());
2367
+ if (!info || info.data.length < SESSION_LEN || info.data[0] !== SESSION_DISCRIMINATOR)
2368
+ return null;
2369
+ return decodeSession(info.data);
2370
+ }
2371
+ /** True if a valid Portal session is open on L1 (read-only; no tx). */
2372
+ async isSessionOpen() {
2373
+ return (await this.getSession()) !== null;
2374
+ }
2324
2375
  // ───────────────────────── signing (adapter-based, no Keypair required) ─────────────────────────
2325
2376
  /** Sign `tx` with a mixed set of signers: raw Keypairs are partial-signed, wallet adapters sign via signTransaction. */
2326
2377
  async signTx(tx, signers) {
@@ -2415,16 +2466,28 @@ export class NorthstarEva {
2415
2466
  buildCloseSessionIx() {
2416
2467
  return portal.closeSessionIx({ programId: this.cfg.portalProgramId, closer: this.payer });
2417
2468
  }
2469
+ /**
2470
+ * Build version of {@link ensureSession}: returns the OpenSession instruction **only if the
2471
+ * session isn't created yet**, or **`null`** if it already exists. Async (it reads the session
2472
+ * on L1). The caller composes the returned ix into a tx, signs, and submits — e.g.:
2473
+ * `const ix = await sdk.buildEnsureSessionIx(); if (ix) tx.add(ix);`
2474
+ */
2475
+ async buildEnsureSessionIx() {
2476
+ this.requireNorthStar("buildEnsureSessionIx");
2477
+ if (await this.l1GetAccount(this.sessionPda()))
2478
+ return null; // already created
2479
+ return this.buildOpenSessionIx();
2480
+ }
2418
2481
  /** Portal DepositFee instruction (L1) — "fund fee to ER payer". (Caller must ensure a session exists.) */
2419
2482
  buildDepositFeeIx(lamports, recipient = this.payer) {
2420
2483
  return portal.depositFeeIx({ programId: this.cfg.portalProgramId, depositor: this.payer, recipient, lamports });
2421
2484
  }
2422
- /** "Withdraw fee from ER" instruction (ER) — system transfer from `recipient` (default: signer) → its WithdrawalSink. (Portal SOL bridge; distinct from the eva `withdraw` instruction.) */
2485
+ /** "Withdraw fee from ER" instruction (ER) — system transfer from `sender` (default: the signer) → its WithdrawalSink. (Portal SOL bridge; distinct from the eva `withdraw` instruction.) */
2423
2486
  buildWithdrawFeeFromErIx(p) {
2424
- const recipient = p.recipient ?? this.payer;
2425
- if (p.toL1 && !p.toL1.equals(recipient))
2426
- throw new Error("Arbitrary L1 withdrawal destination is not supported yet (Portal pays the deposit recipient). Omit `toL1` or set it equal to recipient.");
2427
- return SystemProgram.transfer({ fromPubkey: recipient, toPubkey: this.getWithdrawalSink(recipient), lamports: Number(p.lamports) });
2487
+ const sender = p.sender ?? this.payer;
2488
+ if (p.toL1 && !p.toL1.equals(sender))
2489
+ throw new Error("Arbitrary L1 withdrawal destination is not supported yet (Portal pays the sender). Omit `toL1` or set it equal to sender.");
2490
+ return SystemProgram.transfer({ fromPubkey: sender, toPubkey: this.getWithdrawalSink(sender), lamports: Number(p.lamports) });
2428
2491
  }
2429
2492
  /** Next sequential trench id (= total_trenches + 1) read from the ER global state. */
2430
2493
  async nextTrenchId() { return (await this.requireGlobal()).totalTrenches + 1n; }
@@ -2630,19 +2693,18 @@ export class NorthstarEva {
2630
2693
  * WithdrawalSink, returning the fee to whoever sent it. The validator pays the L1 SOL
2631
2694
  * ASYNCHRONOUSLY at the next settlement (use {@link waitForL1Settlement} to await it).
2632
2695
  *
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).
2696
+ * `sender` is a **PublicKey** (default: the SDK signer's pubkey). This send-method signs with
2697
+ * the SDK's wallet adapter — so `sender` must be the SDK's own account (the user's wallet, which
2698
+ * signs remotely; no secret key needed). To withdraw for a different account whose signature you
2699
+ * collect elsewhere, use {@link buildWithdrawFeeFromErIx} and have that account sign the tx.
2637
2700
  *
2638
2701
  * NOTE: the L1 payout goes to the SAME account that deposited (the sender) — an arbitrary
2639
2702
  * `toL1` destination is not supported by the Portal program yet (pending protocol change).
2640
2703
  */
2641
2704
  async requestWithdraw(p) {
2642
2705
  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]);
2706
+ const ix = this.buildWithdrawFeeFromErIx({ lamports: p.lamports, sender: p.sender ?? this.payer, toL1: p.toL1 });
2707
+ return this.sendOnEr([ix], [this.wallet]); // signed by the SDK's wallet adapter (the user)
2646
2708
  }
2647
2709
  /** "Withdraw fee from ER" (the green-box withdraw API) — alias of {@link requestWithdraw}. */
2648
2710
  async withdrawFeeFromEr(p) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "northstar-eva-sdk",
3
- "version": "0.5.0",
3
+ "version": "0.7.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",