@wireio/stake 0.2.1 → 0.2.3

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.
@@ -4,6 +4,7 @@ import {
4
4
  ConnectionConfig,
5
5
  PublicKey as SolPubKey,
6
6
  Transaction,
7
+ TransactionInstruction,
7
8
  TransactionSignature,
8
9
  } from '@solana/web3.js';
9
10
  import { AnchorProvider, BN } from '@coral-xyz/anchor';
@@ -11,7 +12,7 @@ import { BaseSignerWalletAdapter } from '@solana/wallet-adapter-base';
11
12
  import { ASSOCIATED_TOKEN_PROGRAM_ID, getAssociatedTokenAddressSync, TOKEN_2022_PROGRAM_ID } from '@solana/spl-token';
12
13
 
13
14
  import { ChainID, ExternalNetwork, KeyType, PublicKey } from '@wireio/core';
14
- import { IStakingClient, Portfolio, StakerConfig } from '../../staker/types';
15
+ import { IStakingClient, Portfolio, PurchaseAsset, PurchaseQuote, StakerConfig, TrancheSnapshot } from '../../types';
15
16
 
16
17
  import { DepositClient } from './clients/deposit.client';
17
18
  import { DistributionClient } from './clients/distribution.client';
@@ -23,6 +24,7 @@ import {
23
24
  import { SolanaTransaction } from './types';
24
25
  import { LeaderboardClient } from './clients/leaderboard.client';
25
26
  import { OutpostClient } from './clients/outpost.client';
27
+ import { TokenClient } from './clients/token.client';
26
28
 
27
29
  const commitment: Commitment = 'confirmed';
28
30
 
@@ -35,6 +37,7 @@ export class SolanaStakingClient implements IStakingClient {
35
37
  private distributionClient: DistributionClient;
36
38
  private leaderboardClient: LeaderboardClient;
37
39
  private outpostClient: OutpostClient;
40
+ private tokenClient: TokenClient;
38
41
 
39
42
  get solPubKey(): SolPubKey { return new SolPubKey(this.pubKey.data.array); }
40
43
  get network() { return this.config.network; }
@@ -73,6 +76,7 @@ export class SolanaStakingClient implements IStakingClient {
73
76
  this.distributionClient = new DistributionClient(this.anchor);
74
77
  this.leaderboardClient = new LeaderboardClient(this.anchor);
75
78
  this.outpostClient = new OutpostClient(this.anchor);
79
+ this.tokenClient = new TokenClient(this.anchor);
76
80
  }
77
81
 
78
82
  // ---------------------------------------------------------------------
@@ -137,11 +141,64 @@ export class SolanaStakingClient implements IStakingClient {
137
141
  return result.signature;
138
142
  }
139
143
 
144
+
145
+ /** Buy pretoken (warrants) using specified asset.
146
+ * @param amount Amount in smallest units (lamports / wei / token units).
147
+ * Required for SOL / LIQSOL / ETH / LIQETH.
148
+ * Ignored for YIELD (the program uses tracked yield).
149
+ * @param purchaseAsset Asset used to buy pretoken.
150
+ * @returns Transaction signature
151
+ */
152
+ async buy(amountLamports: bigint, purchaseAsset: PurchaseAsset): Promise<string> {
153
+ const user = this.solPubKey;
154
+ let ix: TransactionInstruction;
155
+ let preIxs: TransactionInstruction[] = [];
156
+
157
+ switch (purchaseAsset) {
158
+ case PurchaseAsset.SOL: {
159
+ if (!amountLamports || amountLamports <= BigInt(0))
160
+ throw new Error("SOL pretoken purchase requires a positive amount.");
161
+
162
+ ix = await this.tokenClient.buildPurchaseWithSolIx(amountLamports, user);
163
+ break;
164
+ }
165
+
166
+ case PurchaseAsset.LIQSOL: {
167
+ if (!amountLamports || amountLamports <= BigInt(0))
168
+ throw new Error("liqSOL pretoken purchase requires a positive amount.");
169
+
170
+ preIxs = await this.outpostClient.maybeBuildCreateUserAtaIx(user);
171
+ ix = await this.tokenClient.buildPurchaseWithLiqsolIx(amountLamports, user);
172
+ break;
173
+ }
174
+
175
+ case PurchaseAsset.YIELD: {
176
+ ix = await this.tokenClient.buildPurchaseFromYieldIx(user);
177
+ break;
178
+ }
179
+
180
+ case PurchaseAsset.ETH:
181
+ case PurchaseAsset.LIQETH: {
182
+ throw new Error("ETH / LIQETH pretoken purchases are not supported on Solana.");
183
+ }
184
+
185
+ default:
186
+ throw new Error(`Unsupported pretoken purchase asset: ${String(purchaseAsset)}`);
187
+ }
188
+
189
+ const tx = new Transaction().add(...preIxs, ix);
190
+ const prepared = await this.prepareTx(tx);
191
+ const signed = await this.signTransaction(prepared.tx);
192
+ const res = await this.sendAndConfirmHttp(signed, prepared);
193
+ return res.signature;
194
+ }
195
+
140
196
  /**
141
197
  * native = SOL in wallet
142
198
  * liq = liqSOL token balance (from Token-2022 ATA)
143
199
  * staked = Outpost staked liqSOL principal (from wireReceipt.stakedLiqsol)
144
200
  * tracked = liqSOL tracked balance (from Distribution.userRecord)
201
+ * wire = prelaunch WIRE “shares” (UserWarrantRecord.totalWarrantsPurchased, 1e8)
145
202
  */
146
203
  async getPortfolio(): Promise<Portfolio> {
147
204
  const user = this.solPubKey;
@@ -173,12 +230,21 @@ export class SolanaStakingClient implements IStakingClient {
173
230
  const trackedAmountStr =
174
231
  userRecord?.trackedBalance ? userRecord.trackedBalance.toString() : "0";
175
232
 
176
- // Snapshot is canonical; receipt may be null if user never staked
233
+ // Snapshot is canonical; receipt may be null if user never staked/purchased
177
234
  const wireReceipt = snapshot?.wireReceipt ?? null;
235
+ const userWarrantRecord = snapshot?.userWarrantRecord ?? null;
236
+ const trancheState = snapshot?.trancheState ?? null;
237
+ const globalState = snapshot?.globalState ?? null;
178
238
 
179
239
  const stakedAmountStr =
180
240
  wireReceipt?.stakedLiqsol ? wireReceipt.stakedLiqsol.toString() : "0";
181
241
 
242
+ // Prelaunch WIRE "shares" = total warrants purchased by this user (1e8 precision)
243
+ const wireSharesStr =
244
+ userWarrantRecord?.totalWarrantsPurchased
245
+ ? userWarrantRecord.totalWarrantsPurchased.toString()
246
+ : "0";
247
+
182
248
  return {
183
249
  native: {
184
250
  amount: BigInt(nativeLamports),
@@ -196,6 +262,12 @@ export class SolanaStakingClient implements IStakingClient {
196
262
  symbol: "LiqSOL",
197
263
  decimals: LIQSOL_DECIMALS,
198
264
  },
265
+ wire: {
266
+ // Prelaunch pretokens / WIRE shares (1e8)
267
+ amount: BigInt(wireSharesStr),
268
+ symbol: "$WIRE",
269
+ decimals: 8,
270
+ },
199
271
  tracked: {
200
272
  amount: BigInt(trackedAmountStr),
201
273
  symbol: "LiqSOL",
@@ -206,8 +278,123 @@ export class SolanaStakingClient implements IStakingClient {
206
278
  reservePoolPDA: reservePoolPDA.toBase58(),
207
279
  vaultPDA: vaultPDA.toBase58(),
208
280
  wireReceipt,
281
+ userWarrantRecord,
282
+ // global pretoken context (handy for UI + quoting)
283
+ globalIndex: globalState?.currentIndex?.toString(),
284
+ totalShares: globalState?.totalShares?.toString(),
285
+ currentTrancheNumber: trancheState?.currentTrancheNumber?.toString(),
286
+ currentTranchePriceUsd: trancheState?.currentTranchePriceUsd?.toString(), // 1e8 USD
209
287
  },
210
- chainID: this.network.chainId as ChainID,
288
+ chainID: this.network.chainId,
289
+ };
290
+ }
291
+
292
+ /**
293
+ * Program-level prelaunch WIRE / tranche snapshot for Solana.
294
+ * Uses the same OutpostWireStateSnapshot primitive as getPortfolio().
295
+ */
296
+ async getTrancheSnapshot(): Promise<TrancheSnapshot | null> {
297
+ const snapshot = await this.outpostClient.getWireStateSnapshot(this.solPubKey);
298
+ const global = snapshot.globalState;
299
+ const tranche = snapshot.trancheState;
300
+
301
+ if (!global || !tranche) return null;
302
+
303
+ return {
304
+ chainID: this.network.chainId,
305
+ totalShares: BigInt(global.totalShares.toString()),
306
+ currentIndex: BigInt(global.currentIndex.toString()),
307
+ currentTrancheNumber: BigInt(tranche.currentTrancheNumber.toString()),
308
+ currentTrancheSupply: BigInt(tranche.currentTrancheSupply.toString()),
309
+ totalWarrantsSold: BigInt(tranche.totalWarrantsSold.toString()),
310
+ currentTranchePriceUsd: BigInt(tranche.currentTranchePriceUsd.toString()),
311
+ };
312
+ }
313
+
314
+ // -------------------------------------------------------------
315
+ // Prelaunch WIRE quote (pretokens / warrants) – Solana side
316
+ // -------------------------------------------------------------
317
+ async getBuyQuote(amount: bigint, purchaseAsset: PurchaseAsset): Promise<PurchaseQuote> {
318
+ if (amount <= BigInt(0) && purchaseAsset !== PurchaseAsset.YIELD)
319
+ throw new Error("Buy amount must be greater than zero for non-YIELD purchases.");
320
+
321
+ const user = this.solPubKey;
322
+ const snapshot = await this.outpostClient.getWireStateSnapshot(user);
323
+ const tranche = snapshot.trancheState;
324
+
325
+ if (!tranche) {
326
+ throw new Error("TrancheState not initialized; cannot quote WIRE purchase.");
327
+ }
328
+
329
+ // WIRE pretoken price in USD (1e8 precision) from liqsol_core.TrancheState
330
+ const wirePriceUsd = tranche.currentTranchePriceUsd; // BN
331
+ const wireDecimals = 8;
332
+
333
+ let notionalUsd: BN; // 1e8 USD
334
+ let wireSharesBn: BN; // 1e8 WIRE shares
335
+
336
+ switch (purchaseAsset) {
337
+ case PurchaseAsset.SOL: {
338
+ // SOL price in USD (1e8) – you wire this up in TokenClient using Chainlink / PriceHistory
339
+ const solPriceUsd = await this.tokenClient.getSolPriceUsd();
340
+
341
+ // amount is lamports (1e9). Convert to USD notional:
342
+ // usdValue = lamports * solPriceUsd / 1e9
343
+ notionalUsd = new BN(amount)
344
+ .mul(solPriceUsd)
345
+ .div(new BN(1_000_000_000)); // 10^9
346
+
347
+ wireSharesBn = this.calculateExpectedWarrants(notionalUsd, wirePriceUsd);
348
+ break;
349
+ }
350
+
351
+ case PurchaseAsset.LIQSOL: {
352
+ // liqSOL price in USD (1e8). In many setups this == SOL price, but you keep it explicit.
353
+ const liqsolPriceUsd = await this.tokenClient.getSolPriceUsd();
354
+
355
+ // liqSOL has 9 decimals as well; treat `amount` as raw token units.
356
+ notionalUsd = new BN(amount)
357
+ .mul(liqsolPriceUsd)
358
+ .div(new BN(1_000_000_000)); // 10^9
359
+
360
+ wireSharesBn = this.calculateExpectedWarrants(notionalUsd, wirePriceUsd);
361
+ break;
362
+ }
363
+
364
+ case PurchaseAsset.YIELD: {
365
+ // For purchase_warrants_from_yield, the on-chain logic effectively consumes
366
+ // tracked yield (claimBalance / availableBalance) at face value.
367
+ // You have two options for UI semantics:
368
+ // - treat `amount` as a virtual SOL amount and reuse the SOL path, or
369
+ // - ignore `amount` and quote based on the user's actual claimBalance.
370
+ //
371
+ // Here we keep it simple: `amount` is lamports-equivalent of SOL yield.
372
+ const solPriceUsd = await this.tokenClient.getSolPriceUsd();
373
+
374
+ notionalUsd = new BN(amount)
375
+ .mul(solPriceUsd)
376
+ .div(new BN(1_000_000_000));
377
+
378
+ wireSharesBn = this.calculateExpectedWarrants(notionalUsd, wirePriceUsd);
379
+ break;
380
+ }
381
+
382
+ case PurchaseAsset.ETH:
383
+ case PurchaseAsset.LIQETH:
384
+ throw new Error("getBuyQuote for ETH/LIQETH is not supported on Solana.");
385
+
386
+ default:
387
+ // TS safety – should never hit for your enum
388
+ throw new Error(`Unsupported purchase asset: ${String(purchaseAsset)}`);
389
+ }
390
+
391
+ return {
392
+ purchaseAsset,
393
+ amountIn: amount,
394
+ wireShares: BigInt(wireSharesBn.toString()),
395
+ wireDecimals,
396
+ wirePriceUsd: BigInt(wirePriceUsd.toString()), // 1e8
397
+ notionalUsd: BigInt(notionalUsd.toString()), // 1e8
211
398
  };
212
399
  }
213
400
 
@@ -215,12 +402,23 @@ export class SolanaStakingClient implements IStakingClient {
215
402
  // SOL-only extras
216
403
  // ---------------------------------------------------------------------
217
404
 
218
- async getUserRecord() {
219
- return this.distributionClient.getUserRecord(this.solPubKey);
405
+ /**
406
+ * Exact warrant math reused from your old utils, just phrased in terms
407
+ * of already-computed USD notional:
408
+ *
409
+ * expectedWarrants = (notionalUsd * 1e8) / wirePriceUsd
410
+ *
411
+ * where:
412
+ * - notionalUsd, wirePriceUsd are 1e8-scaled USD
413
+ * - result is 1e8-scaled WIRE "shares"
414
+ */
415
+ private calculateExpectedWarrants(notionalUsd: BN, wirePriceUsd: BN): BN {
416
+ const SCALE = new BN("100000000"); // 1e8
417
+ return notionalUsd.mul(SCALE).div(wirePriceUsd);
220
418
  }
221
419
 
222
- getProtocolFee() {
223
- // TODO
420
+ async getUserRecord() {
421
+ return this.distributionClient.getUserRecord(this.solPubKey);
224
422
  }
225
423
 
226
424
  async correctBalance(amount?: bigint): Promise<string> {
@@ -131,4 +131,14 @@ export type WireReceipt = {
131
131
  purchasedWithSol: BN;
132
132
  purchasedSolShares: BN;
133
133
  bump: number;
134
- }
134
+ }
135
+
136
+
137
+ export type PriceHistory = {
138
+ windowSize: number;
139
+ prices: BN[];
140
+ count: number;
141
+ nextIndex: number;
142
+ bump: number;
143
+ }
144
+
@@ -52,6 +52,7 @@ import {
52
52
 
53
53
  import liqsolCoreIDL from '../../assets/solana/idl/liqsol_core.json';
54
54
  import { LiqsolCore } from '../../assets/solana/types/liqsol_core';
55
+ import { TrancheState } from './types';
55
56
 
56
57
  // -----------------------------------------------------------------------------
57
58
  // Read-only liqsol_core Program helper
@@ -68,7 +69,7 @@ export function getLiqsolCoreProgram(
68
69
 
69
70
  // Dummy wallet – we're only doing read-only account fetches here.
70
71
  const tmpKeypair = Keypair.generate();
71
- const wallet : any = { publicKey: tmpKeypair.publicKey, signAllTransactions: async () => [], signTransaction: async () => tmpKeypair };
72
+ const wallet: any = { publicKey: tmpKeypair.publicKey, signAllTransactions: async () => [], signTransaction: async () => tmpKeypair };
72
73
 
73
74
  const provider = new AnchorProvider(connection, wallet, {
74
75
  commitment: 'confirmed',
@@ -364,8 +365,21 @@ export async function buildOutpostAccounts(
364
365
  );
365
366
 
366
367
  // Chainlink program feeds
367
- const chainLinkFeed = CHAINLINK_FEED;
368
- const chainLinkProgram = CHAINLINK_PROGRAM
368
+ let chainLinkFeed = CHAINLINK_FEED;
369
+ let chainLinkProgram = CHAINLINK_PROGRAM
370
+
371
+ try {
372
+ const program = getLiqsolCoreProgram(connection);
373
+ const ts : TrancheState = await program.account.trancheState.fetch(trancheState);
374
+ if (ts.chainlinkFeed && ts.chainlinkProgram) {
375
+ chainLinkFeed = ts.chainlinkFeed as PublicKey;
376
+ chainLinkProgram = ts.chainlinkProgram as PublicKey;
377
+ }
378
+ } catch {
379
+ // If trancheState isn't initialized yet, we fall back to the constants.
380
+ // In that case, pretoken instructions will still fail, which is the correct
381
+ // behavior until admin initializes TrancheState on-chain.
382
+ }
369
383
 
370
384
  void connection; // reserved for future extra validation
371
385
 
@@ -2,8 +2,8 @@
2
2
 
3
3
  import { ChainID, EvmChainID, SolChainID } from '@wireio/core';
4
4
  import { IStakingClient, StakerConfig } from './types';
5
- import { SolanaStakingClient } from '../networks/solana/solana';
6
- import { EthereumStakingClient } from '../networks/ethereum/ethereum';
5
+ import { SolanaStakingClient } from './networks/solana/solana';
6
+ import { EthereumStakingClient } from './networks/ethereum/ethereum';
7
7
 
8
8
  export class Staker {
9
9
  public selectedChainID?: ChainID;
package/src/types.ts ADDED
@@ -0,0 +1,105 @@
1
+ import { BaseSignerWalletAdapter } from '@solana/wallet-adapter-base';
2
+ import { PublicKey as SolPubKey } from '@solana/web3.js';
3
+ import { ChainID, ExternalNetwork, PublicKey } from '@wireio/core';
4
+ import { ethers } from 'ethers';
5
+
6
+ export type StakerConfig = {
7
+ network: ExternalNetwork;
8
+ provider: BaseSignerWalletAdapter | ethers.providers.Web3Provider;
9
+ pubKey: PublicKey;
10
+ }
11
+
12
+ export interface IStakingClient {
13
+ pubKey: PublicKey;
14
+ network: ExternalNetwork;
15
+
16
+ /** Amount is in the chain's smallest unit (lamports/wei, etc.) */
17
+ deposit(amount: bigint): Promise<string>;
18
+ withdraw(amount: bigint): Promise<string>;
19
+ stake(amount: bigint): Promise<string>;
20
+ unstake(amount: bigint): Promise<string>;
21
+ buy(amount: bigint, asset: PurchaseAsset): Promise<string>;
22
+
23
+ /** Fetch the complete user portfolio */
24
+ getPortfolio(): Promise<Portfolio>;
25
+
26
+ /**
27
+ * Program-level prelaunch WIRE/tranche snapshot for this chain.
28
+ *
29
+ * Returns:
30
+ * - `TrancheSnapshot` when the chain supports pretoken/tranches
31
+ * - `null` if this chain has no WIRE/pretoken integration
32
+ */
33
+ getTrancheSnapshot(): Promise<TrancheSnapshot | null>;
34
+
35
+ /** */
36
+ getBuyQuote(amount: bigint, asset: PurchaseAsset): Promise<PurchaseQuote>;
37
+ }
38
+
39
+ export interface Portfolio {
40
+ /** Native balance on chain: ETH, SOL */
41
+ native: BalanceView;
42
+ /** Actual Liquid balance of LiqETH, LiqSOL*/
43
+ liq: BalanceView;
44
+ /** Outpost Staked balance */
45
+ staked: BalanceView
46
+ /** Prelaunch WIRE “shares” (warrants/pretokens) */
47
+ wire: BalanceView;
48
+ /** SOL ONLY!
49
+ * Tracked liqSOL balance from distribution program */
50
+ tracked?: BalanceView;
51
+ /** Extra PDAs and account addresses */
52
+ extras?: Record<string, any>;
53
+ /** Chain ID of the network for which this portfolio is from */
54
+ chainID: ChainID;
55
+ }
56
+
57
+ export type BalanceView = {
58
+ amount: bigint; // raw on-chain integer value
59
+ decimals: number; // number of decimal places
60
+ symbol: string; // optional token symbol identifier
61
+ ata?: SolPubKey; // associated token account address
62
+ };
63
+
64
+ /**
65
+ * Program-global prelaunch WIRE / tranche state for a single chain.
66
+ *
67
+ * All integer values are raw on-chain integers:
68
+ * - `currentTranchePriceUsd`: 1e8 USD
69
+ * - supplies / warrants / shares: 1e8 WIRE units (per liqsol_core/ETH analog)
70
+ * - index: same scale the program uses (INDEX_SCALE = 1e12 on Solana today)
71
+ */
72
+ export interface TrancheSnapshot {
73
+ chainID: ChainID;
74
+
75
+ // From GlobalState
76
+ totalShares: bigint; // globalState.totalShares
77
+ currentIndex: bigint; // globalState.currentIndex
78
+
79
+ // From TrancheState
80
+ currentTrancheNumber: bigint; // trancheState.currentTrancheNumber
81
+ currentTrancheSupply: bigint; // trancheState.currentTrancheSupply
82
+ totalWarrantsSold: bigint; // trancheState.totalWarrantsSold
83
+ currentTranchePriceUsd: bigint; // trancheState.currentTranchePriceUsd (1e8)
84
+ }
85
+
86
+ // Enum describing which asset is being used to buy pretoken
87
+ export enum PurchaseAsset {
88
+ SOL = "SOL",
89
+ LIQSOL = "LIQSOL",
90
+ ETH = "ETH",
91
+ LIQETH = "LIQETH",
92
+ YIELD = "YIELD",
93
+ }
94
+
95
+ export interface PurchaseQuote {
96
+ purchaseAsset: PurchaseAsset;
97
+ amountIn: bigint; // smallest unit (lamports / wei / token units)
98
+ // share info (prelaunch WIRE)
99
+ wireShares: bigint; // 1e8 precision
100
+ wireDecimals: number; // always 8
101
+ // pricing info
102
+ wirePriceUsd: bigint; // 1e8 precision
103
+ notionalUsd: bigint; // 1e8 precision
104
+ }
105
+
@@ -1,51 +0,0 @@
1
- import { BaseSignerWalletAdapter } from '@solana/wallet-adapter-base';
2
- import { PublicKey as SolPubKey } from '@solana/web3.js';
3
- import { ChainID, ExternalNetwork, PublicKey } from '@wireio/core';
4
- import { ethers } from 'ethers';
5
-
6
- export interface IStakingClient {
7
- pubKey: PublicKey;
8
- network: ExternalNetwork;
9
-
10
- /** Amount is in the chain's smallest unit (lamports/wei, etc.) */
11
- deposit(amount: bigint): Promise<string>;
12
- withdraw(amount: bigint): Promise<string>;
13
- stake(amount: bigint): Promise<string>;
14
- unstake(amount: bigint): Promise<string>;
15
-
16
- // REMOVED from shared client, SOLANA ONLY
17
- /** Register any untracked LIQ staked tokens */
18
- // register(): Promise<string>;
19
-
20
- /** Fetch the portfolio for the LIQ stake user */
21
- getPortfolio(): Promise<Portfolio>;
22
- }
23
-
24
- export type StakerConfig = {
25
- network: ExternalNetwork;
26
- provider: BaseSignerWalletAdapter | ethers.providers.Web3Provider;
27
- pubKey: PublicKey;
28
- }
29
-
30
- export interface Portfolio {
31
- /** Native balance on chain: ETH, SOL */
32
- native: BalanceView;
33
- /** Actual Liquid balance of LiqETH, LiqSOL*/
34
- liq: BalanceView;
35
- /** Outpost Staked balance */
36
- staked: BalanceView
37
- /** SOL ONLY!
38
- * Tracked liqSOL balance from distribution program */
39
- tracked?: BalanceView;
40
- /** Extra PDAs and account addresses */
41
- extras?: Record<string, any>;
42
- /** Chain ID of the network for which this portfolio is from */
43
- chainID: ChainID;
44
- }
45
-
46
- export type BalanceView = {
47
- amount: bigint; // raw on-chain integer value
48
- decimals: number; // number of decimal places
49
- symbol?: string; // optional token symbol identifier
50
- ata?: SolPubKey; // associated token account address
51
- };