northstar-eva-sdk 0.6.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
 
@@ -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,6 +382,13 @@ 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
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.) */
@@ -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,6 +2466,18 @@ 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 });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "northstar-eva-sdk",
3
- "version": "0.6.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",