northstar-eva-sdk 0.2.0 → 0.4.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 +56 -0
- package/dist/northstar-eva-sdk.d.ts +135 -0
- package/dist/northstar-eva-sdk.js +166 -51
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -16,6 +16,62 @@ 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
|
+
## What's new in 0.4.0
|
|
20
|
+
|
|
21
|
+
**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):
|
|
22
|
+
|
|
23
|
+
```ts
|
|
24
|
+
// admin / lifecycle
|
|
25
|
+
await sdk.updateConfig({ biddingDuration: 120n, tradingDuration: 100_000n, feeRecipient });
|
|
26
|
+
await sdk.withdraw({ trenchId, amount: 20_000_000n }); // withdraw a bid deposit from a trench
|
|
27
|
+
await sdk.closePool({ trenchId });
|
|
28
|
+
await sdk.claimTokens({ trenchId, user }); // release a user's tokens from the vault
|
|
29
|
+
await sdk.distributePrize({ trenchId, winners: [w1, w2], amounts: [a1, a2] });
|
|
30
|
+
```
|
|
31
|
+
> Note: eva `withdraw` (withdraw a trench bid deposit) is **different** from the Portal `withdrawFeeFromEr` (bridge SOL ER→L1). They coexist.
|
|
32
|
+
|
|
33
|
+
**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.
|
|
34
|
+
|
|
35
|
+
```ts
|
|
36
|
+
import { NorthstarEva } from "northstar-eva-sdk";
|
|
37
|
+
|
|
38
|
+
// 1) build instructions (no signing, no network send — only the reads needed to resolve accounts)
|
|
39
|
+
const ataIx = sdk.buildUserAtaIx({ trenchId });
|
|
40
|
+
const buyIx = await sdk.buildBuyIx({ trenchId, solPayWithFee: 10_000_000n });
|
|
41
|
+
|
|
42
|
+
// 2) compose them (add your own instructions too), get an unsigned Transaction
|
|
43
|
+
const tx = await sdk.buildTransaction([ataIx, buyIx], { lane: "er" }); // feePayer + blockhash set
|
|
44
|
+
|
|
45
|
+
// 3) your backend signs + submits — the SDK never sends it
|
|
46
|
+
tx.sign(myKeypair); // or wallet.signTransaction(tx)
|
|
47
|
+
await connection.sendRawTransaction(tx.serialize());
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
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.
|
|
51
|
+
|
|
52
|
+
> 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**.
|
|
53
|
+
|
|
54
|
+
## What's new in 0.3.0
|
|
55
|
+
|
|
56
|
+
**Construct from your existing `program` + `provider`, and an exported `EVA_IDL` — minimal code change.**
|
|
57
|
+
|
|
58
|
+
- **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.)
|
|
59
|
+
- **`EVA_IDL` is now exported** — build your program exactly like before:
|
|
60
|
+
```ts
|
|
61
|
+
import { NorthstarEva, EVA_IDL } from "northstar-eva-sdk";
|
|
62
|
+
|
|
63
|
+
const wallet = new ReadOnlyWallet(userPubkey); // no Keypair (Turnkey)
|
|
64
|
+
const provider = new anchor.AnchorProvider(connection, wallet, { commitment: "confirmed" });
|
|
65
|
+
const programId = getProgramId(); // per-environment id
|
|
66
|
+
const program = new anchor.Program({ ...EVA_IDL, address: programId.toBase58() }, provider);
|
|
67
|
+
|
|
68
|
+
const sdk = NorthstarEva.create({ program, provider, erRpc: ER_RPC_URL });
|
|
69
|
+
// sdk.evaProgramId === programId (adopted from your program)
|
|
70
|
+
```
|
|
71
|
+
- 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`.
|
|
72
|
+
|
|
73
|
+
> Backwards compatible: `{ network, wallet: keypair }` and `{ provider, erRpc, evaProgramId }` both still work.
|
|
74
|
+
|
|
19
75
|
## What's new in 0.2.0
|
|
20
76
|
|
|
21
77
|
Integration-friendly construction — **no Keypair required, keep your provider, any RPC URL.**
|
|
@@ -249,6 +249,16 @@ export interface NorthstarEvaOptions extends NorthstarEvaInput {
|
|
|
249
249
|
provider?: anchor.AnchorProvider;
|
|
250
250
|
/** Explicit L1 connection (alternative to `provider`/`l1Rpc`). */
|
|
251
251
|
connection?: Connection;
|
|
252
|
+
/**
|
|
253
|
+
* A pre-built Anchor `Program` for eva (as you already build with your IDL + program id).
|
|
254
|
+
* If given, it becomes the SINGLE SOURCE OF TRUTH for the program id — the SDK adopts
|
|
255
|
+
* `program.programId` (so all PDA derivations follow it), and reuses the program's
|
|
256
|
+
* `provider.wallet`/`provider.connection` for the signer + L1 endpoint. Use the exported
|
|
257
|
+
* `EVA_IDL` to build it: `new anchor.Program({ ...EVA_IDL, address }, provider)`.
|
|
258
|
+
*/
|
|
259
|
+
program?: anchor.Program<Idl>;
|
|
260
|
+
/** Alias of `program`. */
|
|
261
|
+
evaProgram?: anchor.Program<Idl>;
|
|
252
262
|
/** eva IDL object. If omitted, loaded from `evaIdlPath` or the bundled IDL. */
|
|
253
263
|
evaIdl?: Idl;
|
|
254
264
|
evaIdlPath?: string;
|
|
@@ -257,6 +267,12 @@ export interface NorthstarEvaOptions extends NorthstarEvaInput {
|
|
|
257
267
|
/** ms between status/read polls. */
|
|
258
268
|
pollIntervalMs?: number;
|
|
259
269
|
}
|
|
270
|
+
/**
|
|
271
|
+
* The eva Anchor IDL, exported as a constant so you can build your own `Program`:
|
|
272
|
+
* `new anchor.Program({ ...EVA_IDL, address: programId.toBase58() }, provider)` and pass it
|
|
273
|
+
* to `NorthstarEva.create({ program, provider, erRpc })`.
|
|
274
|
+
*/
|
|
275
|
+
export declare const EVA_IDL: Idl;
|
|
260
276
|
export declare class NorthstarEva {
|
|
261
277
|
readonly cfg: ResolvedConfig;
|
|
262
278
|
readonly l1: Connection;
|
|
@@ -301,6 +317,97 @@ export declare class NorthstarEva {
|
|
|
301
317
|
private requireGlobal;
|
|
302
318
|
private get methods();
|
|
303
319
|
private bn;
|
|
320
|
+
/** Portal OpenSession instruction (L1). */
|
|
321
|
+
buildOpenSessionIx(): TransactionInstruction;
|
|
322
|
+
/** Portal CloseSession instruction (L1). */
|
|
323
|
+
buildCloseSessionIx(): TransactionInstruction;
|
|
324
|
+
/** Portal DepositFee instruction (L1) — "fund fee to ER payer". (Caller must ensure a session exists.) */
|
|
325
|
+
buildDepositFeeIx(lamports: bigint, recipient?: PublicKey): TransactionInstruction;
|
|
326
|
+
/** "Withdraw fee from ER" instruction (ER) — system transfer from `recipient` (default: signer) → its WithdrawalSink. (Portal SOL bridge; distinct from the eva `withdraw` instruction.) */
|
|
327
|
+
buildWithdrawFeeFromErIx(p: {
|
|
328
|
+
lamports: bigint;
|
|
329
|
+
recipient?: PublicKey;
|
|
330
|
+
toL1?: PublicKey;
|
|
331
|
+
}): TransactionInstruction;
|
|
332
|
+
/** Next sequential trench id (= total_trenches + 1) read from the ER global state. */
|
|
333
|
+
nextTrenchId(): Promise<bigint>;
|
|
334
|
+
/** eva initialize instruction (ER). */
|
|
335
|
+
buildInitializeIx(p: {
|
|
336
|
+
biddingDuration: bigint;
|
|
337
|
+
tradingDuration: bigint;
|
|
338
|
+
}): Promise<TransactionInstruction>;
|
|
339
|
+
/** eva createTrench instruction (ER). `trenchId` defaults to {@link nextTrenchId}. */
|
|
340
|
+
buildCreateTrenchIx(p?: {
|
|
341
|
+
trenchId?: bigint;
|
|
342
|
+
initialVirtualTokenReserves?: bigint;
|
|
343
|
+
}): Promise<TransactionInstruction>;
|
|
344
|
+
/** eva deposit instruction (ER). */
|
|
345
|
+
buildDepositIx(p: {
|
|
346
|
+
trenchId: bigint;
|
|
347
|
+
lamports: bigint;
|
|
348
|
+
thinkId?: bigint;
|
|
349
|
+
user?: PublicKey;
|
|
350
|
+
}): Promise<TransactionInstruction>;
|
|
351
|
+
/** eva finalize instruction (ER). Reads global for feeRecipient; does NOT wait for the bidding window. */
|
|
352
|
+
buildFinalizeIx(p: {
|
|
353
|
+
trenchId: bigint;
|
|
354
|
+
}): Promise<TransactionInstruction>;
|
|
355
|
+
/** ATA-create instruction (ER) for a user's trench token account (idempotent). */
|
|
356
|
+
buildUserAtaIx(p: {
|
|
357
|
+
trenchId: bigint;
|
|
358
|
+
user?: PublicKey;
|
|
359
|
+
}): TransactionInstruction;
|
|
360
|
+
/** eva buy instruction (ER). Reads global for feeRecipient. */
|
|
361
|
+
buildBuyIx(p: {
|
|
362
|
+
trenchId: bigint;
|
|
363
|
+
solPayWithFee: bigint;
|
|
364
|
+
thinkId?: bigint;
|
|
365
|
+
user?: PublicKey;
|
|
366
|
+
}): Promise<TransactionInstruction>;
|
|
367
|
+
/** eva sell instruction (ER). Reads global for feeRecipient. */
|
|
368
|
+
buildSellIx(p: {
|
|
369
|
+
trenchId: bigint;
|
|
370
|
+
tokenAmount: bigint;
|
|
371
|
+
minSolOutput?: bigint;
|
|
372
|
+
thinkId?: bigint;
|
|
373
|
+
user?: PublicKey;
|
|
374
|
+
}): Promise<TransactionInstruction>;
|
|
375
|
+
/** eva updateConfig instruction (ER, admin) — change bidding/trading durations + fee recipient. */
|
|
376
|
+
buildUpdateConfigIx(p: {
|
|
377
|
+
biddingDuration: bigint;
|
|
378
|
+
tradingDuration: bigint;
|
|
379
|
+
feeRecipient: PublicKey;
|
|
380
|
+
}): Promise<TransactionInstruction>;
|
|
381
|
+
/** eva withdraw instruction (ER) — withdraw a bid deposit from a trench (inverse of {@link buildDepositIx}). NOT the Portal SOL bridge ({@link buildWithdrawFeeFromErIx}). */
|
|
382
|
+
buildWithdrawIx(p: {
|
|
383
|
+
trenchId: bigint;
|
|
384
|
+
amount: bigint;
|
|
385
|
+
thinkId?: bigint;
|
|
386
|
+
user?: PublicKey;
|
|
387
|
+
}): Promise<TransactionInstruction>;
|
|
388
|
+
/** eva closePool instruction (ER, admin) — close a finished trench's pool. */
|
|
389
|
+
buildClosePoolIx(p: {
|
|
390
|
+
trenchId: bigint;
|
|
391
|
+
}): Promise<TransactionInstruction>;
|
|
392
|
+
/** eva claimTokens instruction (ER, admin) — release `user`'s tokens from the trench vault to their ATA. */
|
|
393
|
+
buildClaimTokensIx(p: {
|
|
394
|
+
trenchId: bigint;
|
|
395
|
+
user?: PublicKey;
|
|
396
|
+
}): Promise<TransactionInstruction>;
|
|
397
|
+
/** eva distributePrize instruction (ER, admin) — pay out the prize pool to `winners` by `amounts` (lamports). */
|
|
398
|
+
buildDistributePrizeIx(p: {
|
|
399
|
+
trenchId: bigint;
|
|
400
|
+
winners: PublicKey[];
|
|
401
|
+
amounts: bigint[];
|
|
402
|
+
}): Promise<TransactionInstruction>;
|
|
403
|
+
/**
|
|
404
|
+
* Bundle instruction(s) into an UNSIGNED `Transaction` (feePayer + recent blockhash set) for the
|
|
405
|
+
* given lane (`"er"` default, or `"l1"`). The backend signs + submits it (or adds more instructions first).
|
|
406
|
+
*/
|
|
407
|
+
buildTransaction(instructions: TransactionInstruction | TransactionInstruction[], opts?: {
|
|
408
|
+
lane?: Lane;
|
|
409
|
+
feePayer?: PublicKey;
|
|
410
|
+
}): Promise<Transaction>;
|
|
304
411
|
openSession(): Promise<{
|
|
305
412
|
signature: string | null;
|
|
306
413
|
sessionPda: PublicKey;
|
|
@@ -342,6 +449,7 @@ export declare class NorthstarEva {
|
|
|
342
449
|
tokenMintPda(id: bigint): anchor.web3.PublicKey;
|
|
343
450
|
trenchVault(id: bigint): anchor.web3.PublicKey;
|
|
344
451
|
userAta(id: bigint, user?: PublicKey): anchor.web3.PublicKey;
|
|
452
|
+
agentPda(id: bigint, user?: PublicKey): anchor.web3.PublicKey;
|
|
345
453
|
getGlobal(source?: Lane): Promise<GlobalState | null>;
|
|
346
454
|
getTrench(id: bigint, source?: Lane, retries?: number): Promise<Trench | null>;
|
|
347
455
|
getUserTokenBalance(id: bigint, user?: PublicKey, source?: Lane): Promise<bigint>;
|
|
@@ -433,4 +541,31 @@ export declare class NorthstarEva {
|
|
|
433
541
|
trench: Trench;
|
|
434
542
|
userTokens: bigint;
|
|
435
543
|
}>;
|
|
544
|
+
/** eva updateConfig (ER, admin) — set bidding/trading durations + fee recipient on the global config. */
|
|
545
|
+
updateConfig(p: {
|
|
546
|
+
biddingDuration: bigint;
|
|
547
|
+
tradingDuration: bigint;
|
|
548
|
+
feeRecipient: PublicKey;
|
|
549
|
+
}): Promise<string>;
|
|
550
|
+
/** eva withdraw (ER) — withdraw a bid deposit from a trench (inverse of {@link deposit}; NOT the Portal SOL bridge {@link withdrawFeeFromEr}). */
|
|
551
|
+
withdraw(p: {
|
|
552
|
+
trenchId: bigint;
|
|
553
|
+
amount: bigint;
|
|
554
|
+
thinkId?: bigint;
|
|
555
|
+
}): Promise<string>;
|
|
556
|
+
/** eva closePool (ER, admin) — close a finished trench's pool. */
|
|
557
|
+
closePool(p: {
|
|
558
|
+
trenchId: bigint;
|
|
559
|
+
}): Promise<string>;
|
|
560
|
+
/** eva claimTokens (ER, admin) — release `user`'s tokens from the trench vault to their ATA. */
|
|
561
|
+
claimTokens(p: {
|
|
562
|
+
trenchId: bigint;
|
|
563
|
+
user?: PublicKey;
|
|
564
|
+
}): Promise<string>;
|
|
565
|
+
/** eva distributePrize (ER, admin) — pay the prize pool to `winners` by `amounts` (lamports). */
|
|
566
|
+
distributePrize(p: {
|
|
567
|
+
trenchId: bigint;
|
|
568
|
+
winners: PublicKey[];
|
|
569
|
+
amounts: bigint[];
|
|
570
|
+
}): Promise<string>;
|
|
436
571
|
}
|
|
@@ -2144,6 +2144,12 @@ const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
|
2144
2144
|
function loadBundledIdl() {
|
|
2145
2145
|
return __EVA_IDL__;
|
|
2146
2146
|
}
|
|
2147
|
+
/**
|
|
2148
|
+
* The eva Anchor IDL, exported as a constant so you can build your own `Program`:
|
|
2149
|
+
* `new anchor.Program({ ...EVA_IDL, address: programId.toBase58() }, provider)` and pass it
|
|
2150
|
+
* to `NorthstarEva.create({ program, provider, erRpc })`.
|
|
2151
|
+
*/
|
|
2152
|
+
export const EVA_IDL = loadBundledIdl();
|
|
2147
2153
|
export class NorthstarEva {
|
|
2148
2154
|
cfg;
|
|
2149
2155
|
l1;
|
|
@@ -2160,10 +2166,18 @@ export class NorthstarEva {
|
|
|
2160
2166
|
/** The Portal program id in use. */
|
|
2161
2167
|
get portalProgramId() { return this.cfg.portalProgramId; }
|
|
2162
2168
|
constructor(opts) {
|
|
2163
|
-
//
|
|
2164
|
-
|
|
2165
|
-
|
|
2166
|
-
|
|
2169
|
+
// A pre-built Anchor program (Bai's `(program, provider)` shape) is the source of truth
|
|
2170
|
+
// for the program id, and can also supply the provider's wallet + L1 connection.
|
|
2171
|
+
const passedProgram = opts.program ?? opts.evaProgram;
|
|
2172
|
+
const programProvider = passedProgram?.provider;
|
|
2173
|
+
const programId = passedProgram?.programId?.toBase58();
|
|
2174
|
+
if (passedProgram && opts.evaProgramId && new PublicKey(opts.evaProgramId).toBase58() !== programId) {
|
|
2175
|
+
throw new Error(`evaProgramId (${new PublicKey(opts.evaProgramId).toBase58()}) does not match the passed program.programId (${programId}). Pass one source of truth.`);
|
|
2176
|
+
}
|
|
2177
|
+
// ── endpoints: a provider/connection/program can supply the L1 endpoint; ER is always separate ──
|
|
2178
|
+
const providerConn = opts.provider?.connection ?? opts.connection ?? programProvider?.connection;
|
|
2179
|
+
this.cfg = resolveConfig({ ...opts, l1Rpc: opts.l1Rpc ?? providerConn?.rpcEndpoint, evaProgramId: opts.evaProgramId ?? programId });
|
|
2180
|
+
// ── signer: accept a Keypair (wrap it), a wallet adapter, provider.wallet, or program.provider.wallet ──
|
|
2167
2181
|
const w = opts.wallet;
|
|
2168
2182
|
let signer;
|
|
2169
2183
|
if (isWalletLike(w))
|
|
@@ -2172,8 +2186,10 @@ export class NorthstarEva {
|
|
|
2172
2186
|
signer = new A.Wallet(w); // a Keypair → wrap (NodeWallet)
|
|
2173
2187
|
else if (opts.provider?.wallet)
|
|
2174
2188
|
signer = opts.provider.wallet;
|
|
2189
|
+
else if (programProvider?.wallet)
|
|
2190
|
+
signer = programProvider.wallet;
|
|
2175
2191
|
if (!signer)
|
|
2176
|
-
throw new Error("Pass `wallet` (a Keypair or a wallet adapter with signTransaction)
|
|
2192
|
+
throw new Error("Pass `wallet` (a Keypair or a wallet adapter with signTransaction), a `provider` (AnchorProvider), or a `program` whose provider has a wallet.");
|
|
2177
2193
|
this.wallet = signer;
|
|
2178
2194
|
this.payer = signer.publicKey;
|
|
2179
2195
|
this.validatorIdentity = opts.validatorIdentity
|
|
@@ -2190,11 +2206,17 @@ export class NorthstarEva {
|
|
|
2190
2206
|
if (customL1 && erDefaulted && !/127\.0\.0\.1|localhost/.test(this.cfg.l1Rpc)) {
|
|
2191
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).`);
|
|
2192
2208
|
}
|
|
2193
|
-
|
|
2194
|
-
|
|
2195
|
-
|
|
2196
|
-
|
|
2197
|
-
|
|
2209
|
+
if (passedProgram) {
|
|
2210
|
+
// Use the caller's program as-is (only used to BUILD instructions; the SDK sends manually).
|
|
2211
|
+
this.program = passedProgram;
|
|
2212
|
+
}
|
|
2213
|
+
else {
|
|
2214
|
+
const idl = opts.evaIdl ?? (opts.evaIdlPath ? JSON.parse(readFileSync(opts.evaIdlPath, "utf8")) : loadBundledIdl());
|
|
2215
|
+
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.
|
|
2217
|
+
const provider = new A.AnchorProvider(this.er, this.wallet, { commitment: "processed" });
|
|
2218
|
+
this.program = new A.Program(idl, provider);
|
|
2219
|
+
}
|
|
2198
2220
|
}
|
|
2199
2221
|
static create(opts) { return new NorthstarEva(opts); }
|
|
2200
2222
|
// ───────────────────────── ER transport (raw RPC, retries, processed) ─────────────────────────
|
|
@@ -2330,21 +2352,117 @@ export class NorthstarEva {
|
|
|
2330
2352
|
}
|
|
2331
2353
|
get methods() { return this.program.methods; }
|
|
2332
2354
|
bn(v) { return new A.BN(v.toString()); }
|
|
2355
|
+
// ───────────────────────── instruction builders (build, DON'T send) ─────────────────────────
|
|
2356
|
+
// Same inputs as the matching send-methods, but each RETURNS the unsigned instruction instead
|
|
2357
|
+
// of signing + submitting — so a backend can compose it with its own instructions, sign, and
|
|
2358
|
+
// send however it wants. State reads needed to resolve accounts still happen; only the network
|
|
2359
|
+
// SEND is omitted. Pair with `buildTransaction()` to get a ready-to-sign Transaction.
|
|
2360
|
+
/** Portal OpenSession instruction (L1). */
|
|
2361
|
+
buildOpenSessionIx() {
|
|
2362
|
+
return portal.openSessionIx({
|
|
2363
|
+
programId: this.cfg.portalProgramId, payer: this.payer,
|
|
2364
|
+
gridId: this.cfg.gridId, ttlSlots: this.cfg.ttlSlots, feeCap: this.cfg.feeCap,
|
|
2365
|
+
validator: this.validatorIdentity, settlementIntervalSlots: this.cfg.settlementIntervalSlots,
|
|
2366
|
+
});
|
|
2367
|
+
}
|
|
2368
|
+
/** Portal CloseSession instruction (L1). */
|
|
2369
|
+
buildCloseSessionIx() {
|
|
2370
|
+
return portal.closeSessionIx({ programId: this.cfg.portalProgramId, closer: this.payer });
|
|
2371
|
+
}
|
|
2372
|
+
/** Portal DepositFee instruction (L1) — "fund fee to ER payer". (Caller must ensure a session exists.) */
|
|
2373
|
+
buildDepositFeeIx(lamports, recipient = this.payer) {
|
|
2374
|
+
return portal.depositFeeIx({ programId: this.cfg.portalProgramId, depositor: this.payer, recipient, lamports });
|
|
2375
|
+
}
|
|
2376
|
+
/** "Withdraw fee from ER" instruction (ER) — system transfer from `recipient` (default: signer) → its WithdrawalSink. (Portal SOL bridge; distinct from the eva `withdraw` instruction.) */
|
|
2377
|
+
buildWithdrawFeeFromErIx(p) {
|
|
2378
|
+
const recipient = p.recipient ?? this.payer;
|
|
2379
|
+
if (p.toL1 && !p.toL1.equals(recipient))
|
|
2380
|
+
throw new Error("Arbitrary L1 withdrawal destination is not supported yet (Portal pays the deposit recipient). Omit `toL1` or set it equal to recipient.");
|
|
2381
|
+
return SystemProgram.transfer({ fromPubkey: recipient, toPubkey: this.getWithdrawalSink(recipient), lamports: Number(p.lamports) });
|
|
2382
|
+
}
|
|
2383
|
+
/** Next sequential trench id (= total_trenches + 1) read from the ER global state. */
|
|
2384
|
+
async nextTrenchId() { return (await this.requireGlobal()).totalTrenches + 1n; }
|
|
2385
|
+
/** eva initialize instruction (ER). */
|
|
2386
|
+
buildInitializeIx(p) {
|
|
2387
|
+
return this.methods.initialize(this.bn(p.biddingDuration), this.bn(p.tradingDuration)).accounts({ admin: this.payer }).instruction();
|
|
2388
|
+
}
|
|
2389
|
+
/** eva createTrench instruction (ER). `trenchId` defaults to {@link nextTrenchId}. */
|
|
2390
|
+
async buildCreateTrenchIx(p = {}) {
|
|
2391
|
+
const trenchId = p.trenchId ?? await this.nextTrenchId();
|
|
2392
|
+
const ivtr = p.initialVirtualTokenReserves ?? INITIAL_TOKEN_SUPPLY / 2n;
|
|
2393
|
+
return this.methods.createTrench(this.bn(trenchId), this.bn(ivtr)).accounts({ globalState: this.globalStatePda(), creator: this.payer }).instruction();
|
|
2394
|
+
}
|
|
2395
|
+
/** eva deposit instruction (ER). */
|
|
2396
|
+
buildDepositIx(p) {
|
|
2397
|
+
return this.methods.deposit(this.bn(p.lamports), this.bn(p.thinkId ?? 0n)).accounts({ trench: this.trenchPda(p.trenchId), user: p.user ?? this.payer }).instruction();
|
|
2398
|
+
}
|
|
2399
|
+
/** eva finalize instruction (ER). Reads global for feeRecipient; does NOT wait for the bidding window. */
|
|
2400
|
+
async buildFinalizeIx(p) {
|
|
2401
|
+
const g = await this.requireGlobal();
|
|
2402
|
+
return this.methods.finalize().accounts({ trench: this.trenchPda(p.trenchId), globalState: this.globalStatePda(), feeRecipient: new PublicKey(g.feeRecipient), admin: this.payer }).instruction();
|
|
2403
|
+
}
|
|
2404
|
+
/** ATA-create instruction (ER) for a user's trench token account (idempotent). */
|
|
2405
|
+
buildUserAtaIx(p) {
|
|
2406
|
+
const user = p.user ?? this.payer;
|
|
2407
|
+
return createAssociatedTokenAccountIdempotentInstruction(this.payer, this.userAta(p.trenchId, user), user, this.tokenMintPda(p.trenchId), TOKEN_PROGRAM_ID, ASSOCIATED_TOKEN_PROGRAM_ID);
|
|
2408
|
+
}
|
|
2409
|
+
/** eva buy instruction (ER). Reads global for feeRecipient. */
|
|
2410
|
+
async buildBuyIx(p) {
|
|
2411
|
+
const g = await this.requireGlobal();
|
|
2412
|
+
const user = p.user ?? this.payer;
|
|
2413
|
+
return this.methods.buy(this.bn(p.solPayWithFee), this.bn(p.thinkId ?? 0n)).accounts({ trench: this.trenchPda(p.trenchId), globalState: this.globalStatePda(), feeRecipient: new PublicKey(g.feeRecipient), user, trenchTokenVault: this.trenchVault(p.trenchId), userTokenAccount: this.userAta(p.trenchId, user), systemProgram: SystemProgram.programId, tokenProgram: TOKEN_PROGRAM_ID }).instruction();
|
|
2414
|
+
}
|
|
2415
|
+
/** eva sell instruction (ER). Reads global for feeRecipient. */
|
|
2416
|
+
async buildSellIx(p) {
|
|
2417
|
+
const g = await this.requireGlobal();
|
|
2418
|
+
const user = p.user ?? this.payer;
|
|
2419
|
+
return this.methods.sell(this.bn(p.tokenAmount), this.bn(p.minSolOutput ?? 0n), this.bn(p.thinkId ?? 0n)).accounts({ trench: this.trenchPda(p.trenchId), globalState: this.globalStatePda(), feeRecipient: new PublicKey(g.feeRecipient), user, trenchTokenVault: this.trenchVault(p.trenchId), userTokenAccount: this.userAta(p.trenchId, user), tokenProgram: TOKEN_PROGRAM_ID }).instruction();
|
|
2420
|
+
}
|
|
2421
|
+
/** eva updateConfig instruction (ER, admin) — change bidding/trading durations + fee recipient. */
|
|
2422
|
+
buildUpdateConfigIx(p) {
|
|
2423
|
+
return this.methods.updateConfig(this.bn(p.biddingDuration), this.bn(p.tradingDuration), p.feeRecipient).accounts({ globalState: this.globalStatePda(), admin: this.payer }).instruction();
|
|
2424
|
+
}
|
|
2425
|
+
/** eva withdraw instruction (ER) — withdraw a bid deposit from a trench (inverse of {@link buildDepositIx}). NOT the Portal SOL bridge ({@link buildWithdrawFeeFromErIx}). */
|
|
2426
|
+
buildWithdrawIx(p) {
|
|
2427
|
+
const user = p.user ?? this.payer;
|
|
2428
|
+
return this.methods.withdraw(this.bn(p.amount), this.bn(p.thinkId ?? 0n)).accounts({ trench: this.trenchPda(p.trenchId), agent: this.agentPda(p.trenchId, user), user }).instruction();
|
|
2429
|
+
}
|
|
2430
|
+
/** eva closePool instruction (ER, admin) — close a finished trench's pool. */
|
|
2431
|
+
async buildClosePoolIx(p) {
|
|
2432
|
+
const g = await this.requireGlobal();
|
|
2433
|
+
return this.methods.closePool().accounts({ trench: this.trenchPda(p.trenchId), globalState: this.globalStatePda(), feeRecipient: new PublicKey(g.feeRecipient), admin: this.payer }).instruction();
|
|
2434
|
+
}
|
|
2435
|
+
/** eva claimTokens instruction (ER, admin) — release `user`'s tokens from the trench vault to their ATA. */
|
|
2436
|
+
buildClaimTokensIx(p) {
|
|
2437
|
+
const user = p.user ?? this.payer;
|
|
2438
|
+
return this.methods.claimTokens().accounts({ globalState: this.globalStatePda(), trench: this.trenchPda(p.trenchId), agent: this.agentPda(p.trenchId, user), user, admin: this.payer, trenchTokenVault: this.trenchVault(p.trenchId), userTokenAccount: this.userAta(p.trenchId, user), tokenProgram: TOKEN_PROGRAM_ID }).instruction();
|
|
2439
|
+
}
|
|
2440
|
+
/** eva distributePrize instruction (ER, admin) — pay out the prize pool to `winners` by `amounts` (lamports). */
|
|
2441
|
+
async buildDistributePrizeIx(p) {
|
|
2442
|
+
const g = await this.requireGlobal();
|
|
2443
|
+
return this.methods.distributePrize(p.winners, p.amounts.map((a) => this.bn(a))).accounts({ trench: this.trenchPda(p.trenchId), globalState: this.globalStatePda(), feeRecipient: new PublicKey(g.feeRecipient), admin: this.payer }).instruction();
|
|
2444
|
+
}
|
|
2445
|
+
/**
|
|
2446
|
+
* Bundle instruction(s) into an UNSIGNED `Transaction` (feePayer + recent blockhash set) for the
|
|
2447
|
+
* given lane (`"er"` default, or `"l1"`). The backend signs + submits it (or adds more instructions first).
|
|
2448
|
+
*/
|
|
2449
|
+
async buildTransaction(instructions, opts = {}) {
|
|
2450
|
+
const conn = opts.lane === "l1" ? this.l1 : this.er;
|
|
2451
|
+
const { blockhash, lastValidBlockHeight } = await conn.getLatestBlockhash(opts.lane === "l1" ? "confirmed" : "processed");
|
|
2452
|
+
const tx = new Transaction({ feePayer: opts.feePayer ?? this.payer, blockhash, lastValidBlockHeight });
|
|
2453
|
+
tx.add(...(Array.isArray(instructions) ? instructions : [instructions]));
|
|
2454
|
+
return tx;
|
|
2455
|
+
}
|
|
2333
2456
|
// ───────────────────────── Portal control (L1) ─────────────────────────
|
|
2334
2457
|
async openSession() {
|
|
2335
2458
|
const pda = this.sessionPda();
|
|
2336
2459
|
if (await this.l1GetAccount(pda))
|
|
2337
2460
|
return { signature: null, sessionPda: pda, alreadyOpen: true };
|
|
2338
|
-
const
|
|
2339
|
-
programId: this.cfg.portalProgramId, payer: this.wallet.publicKey,
|
|
2340
|
-
gridId: this.cfg.gridId, ttlSlots: this.cfg.ttlSlots, feeCap: this.cfg.feeCap,
|
|
2341
|
-
validator: this.validatorIdentity, settlementIntervalSlots: this.cfg.settlementIntervalSlots,
|
|
2342
|
-
});
|
|
2343
|
-
const signature = await this.sendOnL1([ix], [this.wallet]);
|
|
2461
|
+
const signature = await this.sendOnL1([this.buildOpenSessionIx()], [this.wallet]);
|
|
2344
2462
|
return { signature, sessionPda: pda, alreadyOpen: false };
|
|
2345
2463
|
}
|
|
2346
2464
|
async closeSession() {
|
|
2347
|
-
return this.sendOnL1([
|
|
2465
|
+
return this.sendOnL1([this.buildCloseSessionIx()], [this.wallet]);
|
|
2348
2466
|
}
|
|
2349
2467
|
/**
|
|
2350
2468
|
* Ensure the Portal session is open (idempotent). Opens it via {@link openSession} only if
|
|
@@ -2371,7 +2489,7 @@ export class NorthstarEva {
|
|
|
2371
2489
|
async fundErFeePayer(lamports, recipient = this.wallet.publicKey, opts = {}) {
|
|
2372
2490
|
if (opts.ensureSession !== false)
|
|
2373
2491
|
await this.ensureSession();
|
|
2374
|
-
return this.sendOnL1([
|
|
2492
|
+
return this.sendOnL1([this.buildDepositFeeIx(lamports, recipient)], [this.wallet]);
|
|
2375
2493
|
}
|
|
2376
2494
|
/** Ensure the ER fee payer has >= `lamports`; tops up via DepositFee + waits for the credit. */
|
|
2377
2495
|
async ensureErFunds(lamports, recipient = this.wallet.publicKey) {
|
|
@@ -2409,6 +2527,7 @@ export class NorthstarEva {
|
|
|
2409
2527
|
tokenMintPda(id) { return evaPdas.tokenMintPda(this.cfg.evaProgramId, this.trenchPda(id)); }
|
|
2410
2528
|
trenchVault(id) { return evaPdas.trenchTokenVault(this.tokenMintPda(id), this.trenchPda(id)); }
|
|
2411
2529
|
userAta(id, user = this.wallet.publicKey) { return evaPdas.userTokenAccount(this.tokenMintPda(id), user); }
|
|
2530
|
+
agentPda(id, user = this.wallet.publicKey) { return evaPdas.agentPda(this.cfg.evaProgramId, this.trenchPda(id), user); }
|
|
2412
2531
|
// ───────────────────────── eva reads (processed by default) ─────────────────────────
|
|
2413
2532
|
async getGlobal(source = "er") {
|
|
2414
2533
|
const info = source === "er" ? await this.erGetAccount(this.globalStatePda()) : await this.l1GetAccount(this.globalStatePda());
|
|
@@ -2469,11 +2588,7 @@ export class NorthstarEva {
|
|
|
2469
2588
|
*/
|
|
2470
2589
|
async requestWithdraw(p) {
|
|
2471
2590
|
const recipient = p.recipient ?? this.wallet;
|
|
2472
|
-
|
|
2473
|
-
throw new Error("Arbitrary L1 withdrawal destination is not supported yet (Portal pays the deposit recipient). Omit `toL1` or set it equal to recipient.");
|
|
2474
|
-
}
|
|
2475
|
-
const sink = this.getWithdrawalSink(recipient.publicKey);
|
|
2476
|
-
const ix = SystemProgram.transfer({ fromPubkey: recipient.publicKey, toPubkey: sink, lamports: Number(p.lamports) });
|
|
2591
|
+
const ix = this.buildWithdrawFeeFromErIx({ lamports: p.lamports, recipient: recipient.publicKey, toL1: p.toL1 });
|
|
2477
2592
|
return this.sendOnEr([ix], [recipient]);
|
|
2478
2593
|
}
|
|
2479
2594
|
/** "Withdraw fee from ER" (the green-box withdraw API) — alias of {@link requestWithdraw}. */
|
|
@@ -2497,30 +2612,20 @@ export class NorthstarEva {
|
|
|
2497
2612
|
async initialize(p) {
|
|
2498
2613
|
if (await this.getGlobal("er"))
|
|
2499
2614
|
return null; // already initialized
|
|
2500
|
-
|
|
2501
|
-
.accounts({ admin: this.wallet.publicKey }).instruction();
|
|
2502
|
-
return this.sendEva(ix, "initialize");
|
|
2615
|
+
return this.sendEva(await this.buildInitializeIx(p), "initialize");
|
|
2503
2616
|
}
|
|
2504
2617
|
/** createTrench with the next sequential id (id == total_trenches + 1). */
|
|
2505
2618
|
async createTrench(p = {}) {
|
|
2506
|
-
const
|
|
2507
|
-
|
|
2508
|
-
throw new Error("global state not initialized — call initialize() first");
|
|
2509
|
-
const trenchId = g.totalTrenches + 1n;
|
|
2510
|
-
const ivtr = p.initialVirtualTokenReserves ?? INITIAL_TOKEN_SUPPLY / 2n;
|
|
2511
|
-
const ix = await this.methods.createTrench(this.bn(trenchId), this.bn(ivtr))
|
|
2512
|
-
.accounts({ globalState: this.globalStatePda(), creator: this.wallet.publicKey }).instruction();
|
|
2619
|
+
const trenchId = await this.nextTrenchId();
|
|
2620
|
+
const ix = await this.buildCreateTrenchIx({ trenchId, initialVirtualTokenReserves: p.initialVirtualTokenReserves });
|
|
2513
2621
|
const signature = await this.sendEva(ix, "createTrench");
|
|
2514
2622
|
return { trenchId, signature };
|
|
2515
2623
|
}
|
|
2516
2624
|
async deposit(p) {
|
|
2517
|
-
|
|
2518
|
-
.accounts({ trench: this.trenchPda(p.trenchId), user: this.wallet.publicKey }).instruction();
|
|
2519
|
-
return this.sendEva(ix, "deposit");
|
|
2625
|
+
return this.sendEva(await this.buildDepositIx(p), "deposit");
|
|
2520
2626
|
}
|
|
2521
2627
|
/** finalize — waits for the ER slot to pass biddingEndBlock, then seeds the AMM. */
|
|
2522
2628
|
async finalize(p) {
|
|
2523
|
-
const g = await this.requireGlobal();
|
|
2524
2629
|
const t = await this.getTrench(p.trenchId, "er", 10);
|
|
2525
2630
|
if (t)
|
|
2526
2631
|
for (let i = 0; i < 80; i++) {
|
|
@@ -2529,28 +2634,38 @@ export class NorthstarEva {
|
|
|
2529
2634
|
break;
|
|
2530
2635
|
await sleep(this.pollMs);
|
|
2531
2636
|
}
|
|
2532
|
-
|
|
2533
|
-
.accounts({ trench: this.trenchPda(p.trenchId), globalState: this.globalStatePda(), feeRecipient: new PublicKey(g.feeRecipient), admin: this.wallet.publicKey }).instruction();
|
|
2534
|
-
return this.sendEva(ix, "finalize");
|
|
2637
|
+
return this.sendEva(await this.buildFinalizeIx(p), "finalize");
|
|
2535
2638
|
}
|
|
2536
2639
|
/** Create the user's token ATA on the ER (buy/sell require it to pre-exist). */
|
|
2537
2640
|
async ensureUserAta(p) {
|
|
2538
|
-
|
|
2539
|
-
const ix = createAssociatedTokenAccountIdempotentInstruction(this.wallet.publicKey, this.userAta(p.trenchId, user), user, this.tokenMintPda(p.trenchId), TOKEN_PROGRAM_ID, ASSOCIATED_TOKEN_PROGRAM_ID);
|
|
2540
|
-
return this.sendEva(ix, "ensureUserAta");
|
|
2641
|
+
return this.sendEva(this.buildUserAtaIx(p), "ensureUserAta");
|
|
2541
2642
|
}
|
|
2542
2643
|
async buy(p) {
|
|
2543
|
-
const
|
|
2544
|
-
const ix = await this.methods.buy(this.bn(p.solPayWithFee), this.bn(p.thinkId ?? 0n))
|
|
2545
|
-
.accounts({ trench: this.trenchPda(p.trenchId), globalState: this.globalStatePda(), feeRecipient: new PublicKey(g.feeRecipient), user: this.wallet.publicKey, trenchTokenVault: this.trenchVault(p.trenchId), userTokenAccount: this.userAta(p.trenchId), systemProgram: SystemProgram.programId, tokenProgram: TOKEN_PROGRAM_ID }).instruction();
|
|
2546
|
-
const signature = await this.sendEva(ix, "buy");
|
|
2644
|
+
const signature = await this.sendEva(await this.buildBuyIx(p), "buy");
|
|
2547
2645
|
return { signature, trench: (await this.getTrench(p.trenchId, "er", 5)), userTokens: await this.getUserTokenBalance(p.trenchId) };
|
|
2548
2646
|
}
|
|
2549
2647
|
async sell(p) {
|
|
2550
|
-
const
|
|
2551
|
-
const ix = await this.methods.sell(this.bn(p.tokenAmount), this.bn(p.minSolOutput ?? 0n), this.bn(p.thinkId ?? 0n))
|
|
2552
|
-
.accounts({ trench: this.trenchPda(p.trenchId), globalState: this.globalStatePda(), feeRecipient: new PublicKey(g.feeRecipient), user: this.wallet.publicKey, trenchTokenVault: this.trenchVault(p.trenchId), userTokenAccount: this.userAta(p.trenchId), tokenProgram: TOKEN_PROGRAM_ID }).instruction();
|
|
2553
|
-
const signature = await this.sendEva(ix, "sell");
|
|
2648
|
+
const signature = await this.sendEva(await this.buildSellIx(p), "sell");
|
|
2554
2649
|
return { signature, trench: (await this.getTrench(p.trenchId, "er", 5)), userTokens: await this.getUserTokenBalance(p.trenchId) };
|
|
2555
2650
|
}
|
|
2651
|
+
/** eva updateConfig (ER, admin) — set bidding/trading durations + fee recipient on the global config. */
|
|
2652
|
+
async updateConfig(p) {
|
|
2653
|
+
return this.sendEva(await this.buildUpdateConfigIx(p), "updateConfig");
|
|
2654
|
+
}
|
|
2655
|
+
/** eva withdraw (ER) — withdraw a bid deposit from a trench (inverse of {@link deposit}; NOT the Portal SOL bridge {@link withdrawFeeFromEr}). */
|
|
2656
|
+
async withdraw(p) {
|
|
2657
|
+
return this.sendEva(await this.buildWithdrawIx(p), "withdraw");
|
|
2658
|
+
}
|
|
2659
|
+
/** eva closePool (ER, admin) — close a finished trench's pool. */
|
|
2660
|
+
async closePool(p) {
|
|
2661
|
+
return this.sendEva(await this.buildClosePoolIx(p), "closePool");
|
|
2662
|
+
}
|
|
2663
|
+
/** eva claimTokens (ER, admin) — release `user`'s tokens from the trench vault to their ATA. */
|
|
2664
|
+
async claimTokens(p) {
|
|
2665
|
+
return this.sendEva(await this.buildClaimTokensIx(p), "claimTokens");
|
|
2666
|
+
}
|
|
2667
|
+
/** eva distributePrize (ER, admin) — pay the prize pool to `winners` by `amounts` (lamports). */
|
|
2668
|
+
async distributePrize(p) {
|
|
2669
|
+
return this.sendEva(await this.buildDistributePrizeIx(p), "distributePrize");
|
|
2670
|
+
}
|
|
2556
2671
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "northstar-eva-sdk",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.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",
|