@wireio/stake 0.1.3 → 0.2.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.
@@ -6,9 +6,9 @@ import {
6
6
  Transaction,
7
7
  TransactionSignature,
8
8
  } from '@solana/web3.js';
9
- import { AnchorProvider } from '@coral-xyz/anchor';
9
+ import { AnchorProvider, BN } from '@coral-xyz/anchor';
10
10
  import { BaseSignerWalletAdapter } from '@solana/wallet-adapter-base';
11
- import { getAssociatedTokenAddressSync, TOKEN_2022_PROGRAM_ID } from '@solana/spl-token';
11
+ import { ASSOCIATED_TOKEN_PROGRAM_ID, getAssociatedTokenAddressSync, TOKEN_2022_PROGRAM_ID } from '@solana/spl-token';
12
12
 
13
13
  import { ChainID, ExternalNetwork, KeyType, PublicKey } from '@wireio/core';
14
14
  import { IStakingClient, Portfolio, StakerConfig } from '../../staker/types';
@@ -20,8 +20,9 @@ import {
20
20
  deriveReservePoolPda,
21
21
  deriveVaultPda,
22
22
  } from './constants';
23
- import { SolanaTransaction, UserRecord } from './types';
23
+ import { SolanaTransaction } from './types';
24
24
  import { LeaderboardClient } from './clients/leaderboard.client';
25
+ import { OutpostClient } from './clients/outpost.client';
25
26
 
26
27
  const commitment: Commitment = 'confirmed';
27
28
 
@@ -33,18 +34,15 @@ export class SolanaStakingClient implements IStakingClient {
33
34
  private depositClient: DepositClient;
34
35
  private distributionClient: DistributionClient;
35
36
  private leaderboardClient: LeaderboardClient;
37
+ private outpostClient: OutpostClient;
36
38
 
37
39
  get solPubKey(): SolPubKey { return new SolPubKey(this.pubKey.data.array); }
38
40
  get network() { return this.config.network; }
39
41
 
40
42
  constructor(private config: StakerConfig) {
41
43
  const adapter = config.provider as BaseSignerWalletAdapter;
42
- if (!adapter.publicKey) {
43
- throw new Error('Solana wallet adapter not connected');
44
- }
45
- if (!config.network.rpcUrls.length) {
46
- throw new Error('No RPC URLs provided');
47
- }
44
+ if (!adapter?.publicKey) throw new Error('Solana wallet adapter not connected');
45
+ if (!config.network.rpcUrls.length) throw new Error('No RPC URLs provided');
48
46
 
49
47
  const publicKey = adapter.publicKey;
50
48
  const wirePub = new PublicKey(KeyType.ED, publicKey.toBytes());
@@ -57,9 +55,6 @@ export class SolanaStakingClient implements IStakingClient {
57
55
  opts.wsEndpoint = config.network.rpcUrls[1];
58
56
  }
59
57
 
60
- this.pubKey = wirePub;
61
- this.connection = new Connection(config.network.rpcUrls[0], opts);
62
-
63
58
  const anchorWallet = {
64
59
  publicKey,
65
60
  async signTransaction<T extends SolanaTransaction>(tx: T): Promise<T> {
@@ -70,147 +65,180 @@ export class SolanaStakingClient implements IStakingClient {
70
65
  },
71
66
  };
72
67
 
68
+ this.pubKey = wirePub;
69
+ this.connection = new Connection(config.network.rpcUrls[0], opts);
73
70
  this.anchor = new AnchorProvider(this.connection, anchorWallet, { commitment });
74
71
 
75
72
  this.depositClient = new DepositClient(this.anchor);
76
73
  this.distributionClient = new DistributionClient(this.anchor);
77
74
  this.leaderboardClient = new LeaderboardClient(this.anchor);
75
+ this.outpostClient = new OutpostClient(this.anchor);
78
76
  }
79
77
 
78
+ // ---------------------------------------------------------------------
79
+ // Public IStakingClient Interface Methods
80
+ // ---------------------------------------------------------------------
81
+
80
82
  /**
81
- * Resolve the user's liqSOL ATA and balances + key protocol balances.
82
- * native = SOL in wallet
83
- * actual = liqSOL token balance (from ATA)
84
- * tracked = liqSOL tracked balance (from Distribution.userRecord)
83
+ * Deposit SOL into liqSOL protocol (liqsol_core deposit flow).
84
+ * @param amountLamports Amount of SOL to deposit (smallest unit)
85
+ * @return Transaction signature
85
86
  */
86
- async getPortfolio(): Promise<Portfolio> {
87
- try {
88
- const user = this.solPubKey;
89
- const reservePoolPDA = deriveReservePoolPda();
90
- const vaultPDA = deriveVaultPda();
91
- const liqsolMint = deriveLiqsolMintPda();
92
- const userLiqsolAta = getAssociatedTokenAddressSync(
93
- liqsolMint,
94
- user,
95
- false,
96
- TOKEN_2022_PROGRAM_ID,
97
- );
98
-
99
- // Fetch balances and user record in parallel
100
- const [nativeLamports, actualBalResp, userRecord] = await Promise.all([
101
- this.connection.getBalance(user),
102
- this.connection.getTokenAccountBalance(userLiqsolAta).catch(() => null),
103
- this.distributionClient.getUserRecord(user).catch(() => null),
104
- ]);
105
-
106
- const actualAmountStr = actualBalResp?.value?.amount ?? '0';
107
- const actualDecimals = actualBalResp?.value?.decimals ?? 9;
108
-
109
- const trackedAmountStr = userRecord?.trackedBalance ? userRecord.trackedBalance.toString() : '0';
110
- const trackedDecimals = actualDecimals;
111
-
112
- const nativeSymbol = 'SOL';
113
- const liqSymbol = 'LiqSOL';
114
-
115
- const portfolio: Portfolio = {
116
- native: {
117
- amount: BigInt(nativeLamports),
118
- symbol: nativeSymbol,
119
- decimals: 9,
120
- },
121
- liq: {
122
- amount: BigInt(actualAmountStr),
123
- symbol: liqSymbol,
124
- decimals: actualDecimals,
125
- },
126
- staked: { // TODO: fetch staked balance from outpost
127
- amount: BigInt(0),
128
- symbol: liqSymbol,
129
- decimals: actualDecimals,
130
- },
131
- tracked: { // SOL ONLY
132
- amount: BigInt(trackedAmountStr),
133
- symbol: liqSymbol,
134
- decimals: trackedDecimals,
135
- },
136
- extras: {
137
- userLiqsolAta: userLiqsolAta.toBase58(),
138
- reservePoolPDA: reservePoolPDA.toBase58(),
139
- vaultPDA: vaultPDA.toBase58(),
140
- },
141
- chainID: this.network.chainId
142
- };
143
-
144
- // console.log('SOL PORTFOLIO', portfolio);
145
- return portfolio;
146
- }
147
- catch (error) {
148
- console.log('Error in getPortfolio:', error);
149
- throw error;
150
- }
87
+ async deposit(amountLamports: bigint): Promise<string> {
88
+ if (amountLamports <= BigInt(0)) throw new Error('Deposit amount must be greater than zero.');
89
+ // const amount = new BN(amountLamports.toString());
90
+ const tx = await this.depositClient.buildDepositTx(amountLamports);
91
+ const { tx: prepared, blockhash, lastValidBlockHeight } = await this.prepareTx(tx);
92
+ const signed = await this.signTransaction(prepared);
93
+ const result = await this.sendAndConfirmHttp(signed, { blockhash, lastValidBlockHeight });
94
+ return result.signature;
151
95
  }
152
96
 
153
97
  /**
154
- * Optional: fetch your Distribution program user record
155
- * (often contains per-user deposit/claim state).
156
- * @returns UserRecord or null
98
+ * Withdraw SOL from liqSOL protocol.
99
+ * (Wire up once you have DepositClient.buildWithdrawTx or equivalent.)
157
100
  */
158
- async getUserRecord() {
159
- return this.distributionClient.getUserRecord(this.solPubKey);
101
+ async withdraw(amountLamports: bigint): Promise<string> {
102
+ throw new Error('Withdraw method not yet implemented.');
160
103
  }
161
104
 
162
- getProtocolFee() {
163
- // TODO: wire to pay-rate math once we finalize protocol fee API
105
+ /**
106
+ * Stake liqSOL into Outpost (liqSOL -> pool).
107
+ * Matches deposit flow: build -> prepare -> sign -> send/confirm.
108
+ * @param amountLamports Amount of liqSOL to stake (smallest unit)
109
+ */
110
+ async stake(amountLamports: bigint): Promise<string> {
111
+ if (amountLamports <= BigInt(0)) throw new Error('Stake amount must be greater than zero.');
112
+ // const amount = new BN(amountLamports.toString());
113
+ const preIxs = await this.outpostClient.maybeBuildCreateUserAtaIx(this.solPubKey);
114
+ const stakeIx = await this.outpostClient.buildStakeLiqsolIx(amountLamports);
115
+ const tx = new Transaction().add(...preIxs, stakeIx);
116
+ const prepared = await this.prepareTx(tx);
117
+ const signed = await this.signTransaction(prepared.tx);
118
+ const result = await this.sendAndConfirmHttp(signed, prepared);
119
+ return result.signature;
164
120
  }
165
121
 
166
122
  /**
167
- * Deposit funds into the staking pool.
168
- * @param lamports The amount to deposit (in lamports).
169
- * @returns The transaction signature.
123
+ * Unstake liqSOL from Outpost (pool -> liqSOL).
124
+ * Matches deposit flow: build -> prepare -> sign -> send/confirm.
125
+ * @param amountLamports Amount of liqSOL principal to unstake (smallest unit)
170
126
  */
171
- async deposit(lamports: number): Promise<string> {
172
- const { transaction } = await this.depositClient.buildDepositTx(this.solPubKey, lamports);
173
- const { tx, blockhash, lastValidBlockHeight } = await this.prepareTx(transaction);
174
- const signed = await this.signTransaction(tx);
175
- const result = await this.sendAndConfirmHttp(signed, { blockhash, lastValidBlockHeight });
127
+ async unstake(amountLamports: bigint): Promise<string> {
128
+ if (amountLamports <= BigInt(0)) throw new Error('Unstake amount must be greater than zero.');
129
+ const user = this.solPubKey;
130
+ // const amount = new BN(amountLamports.toString());
131
+ const preIxs = await this.outpostClient.maybeBuildCreateUserAtaIx(user);
132
+ const withdrawIx = await this.outpostClient.buildWithdrawStakeIx(amountLamports);
133
+ const tx = new Transaction().add(...preIxs, withdrawIx);
134
+ const prepared = await this.prepareTx(tx);
135
+ const signed = await this.signTransaction(prepared.tx);
136
+ const result = await this.sendAndConfirmHttp(signed, prepared);
176
137
  return result.signature;
177
138
  }
178
139
 
179
140
  /**
180
- * Build, sign, and submit a single transaction that:
181
- * - Corrects other users (if needed) to free available balance, then
182
- * - Registers the caller’s untracked liqSOL.
183
- *
184
- * @param amount Optional: register a smaller amount than your full untracked balance.
185
- * @returns signature string
141
+ * native = SOL in wallet
142
+ * liq = liqSOL token balance (from Token-2022 ATA)
143
+ * staked = Outpost staked liqSOL principal (from wireReceipt.stakedLiqsol)
144
+ * tracked = liqSOL tracked balance (from Distribution.userRecord)
186
145
  */
146
+ async getPortfolio(): Promise<Portfolio> {
147
+ const user = this.solPubKey;
148
+
149
+ const reservePoolPDA = deriveReservePoolPda();
150
+ const vaultPDA = deriveVaultPda();
151
+ const liqsolMint = deriveLiqsolMintPda();
152
+
153
+ const userLiqsolAta = getAssociatedTokenAddressSync(
154
+ liqsolMint,
155
+ user,
156
+ false,
157
+ TOKEN_2022_PROGRAM_ID,
158
+ ASSOCIATED_TOKEN_PROGRAM_ID,
159
+ );
160
+
161
+ // IMPORTANT: use the SAME read primitive the outpost tests rely on
162
+ const [nativeLamports, actualBalResp, userRecord, snapshot] = await Promise.all([
163
+ this.connection.getBalance(user, "confirmed"),
164
+ this.connection.getTokenAccountBalance(userLiqsolAta, "confirmed").catch(() => null),
165
+ this.distributionClient.getUserRecord(user).catch(() => null),
166
+ this.outpostClient.getWireStateSnapshot(user).catch(() => null),
167
+ ]);
168
+
169
+ const LIQSOL_DECIMALS = 9;
170
+
171
+ const actualAmountStr = actualBalResp?.value?.amount ?? "0";
172
+
173
+ const trackedAmountStr =
174
+ userRecord?.trackedBalance ? userRecord.trackedBalance.toString() : "0";
175
+
176
+ // Snapshot is canonical; receipt may be null if user never staked
177
+ const wireReceipt = snapshot?.wireReceipt ?? null;
178
+
179
+ const stakedAmountStr =
180
+ wireReceipt?.stakedLiqsol ? wireReceipt.stakedLiqsol.toString() : "0";
181
+
182
+ return {
183
+ native: {
184
+ amount: BigInt(nativeLamports),
185
+ symbol: "SOL",
186
+ decimals: 9,
187
+ },
188
+ liq: {
189
+ amount: BigInt(actualAmountStr),
190
+ symbol: "LiqSOL",
191
+ decimals: LIQSOL_DECIMALS,
192
+ ata: userLiqsolAta,
193
+ },
194
+ staked: {
195
+ amount: BigInt(stakedAmountStr),
196
+ symbol: "LiqSOL",
197
+ decimals: LIQSOL_DECIMALS,
198
+ },
199
+ tracked: {
200
+ amount: BigInt(trackedAmountStr),
201
+ symbol: "LiqSOL",
202
+ decimals: LIQSOL_DECIMALS,
203
+ },
204
+ extras: {
205
+ userLiqsolAta: userLiqsolAta.toBase58(),
206
+ reservePoolPDA: reservePoolPDA.toBase58(),
207
+ vaultPDA: vaultPDA.toBase58(),
208
+ wireReceipt,
209
+ },
210
+ chainID: this.network.chainId as ChainID,
211
+ };
212
+ }
213
+
214
+ // ---------------------------------------------------------------------
215
+ // SOL-only extras
216
+ // ---------------------------------------------------------------------
217
+
218
+ async getUserRecord() {
219
+ return this.distributionClient.getUserRecord(this.solPubKey);
220
+ }
221
+
222
+ getProtocolFee() {
223
+ // TODO
224
+ }
225
+
187
226
  async correctBalance(amount?: bigint): Promise<string> {
188
- try {
189
- const build = await this.distributionClient.buildCorrectRegisterTx({ amount });
190
- if (!build.canSucceed || !build.transaction) {
191
- throw new Error(build.reason ?? 'Unable to build Correct&Register transaction');
192
- }
193
-
194
- const { tx, blockhash, lastValidBlockHeight } = await this.prepareTx(build.transaction);
195
- const signed = await this.signTransaction(tx);
196
- const result = await this.sendAndConfirmHttp(signed, { blockhash, lastValidBlockHeight });
197
-
198
- console.log('Registered:', {
199
- needToRegister: build.needToRegister,
200
- freed: build.plan.willFree,
201
- corrected: build.plan.selected.map((c) => ({
202
- owner: c.owner?.toBase58(),
203
- delta: c.delta?.toString(),
204
- })),
205
- });
206
-
207
- return result.signature;
208
- } catch (error) {
209
- console.error('Error in register:', error);
210
- throw error;
227
+ const build = await this.distributionClient.buildCorrectRegisterTx({ amount });
228
+ if (!build.canSucceed || !build.transaction) {
229
+ throw new Error(build.reason ?? 'Unable to build Correct&Register transaction');
211
230
  }
231
+
232
+ const { tx, blockhash, lastValidBlockHeight } = await this.prepareTx(build.transaction);
233
+ const signed = await this.signTransaction(tx);
234
+ const result = await this.sendAndConfirmHttp(signed, { blockhash, lastValidBlockHeight });
235
+ return result.signature;
212
236
  }
213
237
 
238
+ // ---------------------------------------------------------------------
239
+ // Tx helpers
240
+ // ---------------------------------------------------------------------
241
+
214
242
  private async sendAndConfirmHttp(
215
243
  signed: SolanaTransaction,
216
244
  ctx: { blockhash: string; lastValidBlockHeight: number },
@@ -226,10 +254,10 @@ export class SolanaStakingClient implements IStakingClient {
226
254
  commitment,
227
255
  );
228
256
 
229
- const ok = !conf.value.err;
230
- if (!ok) {
257
+ if (conf.value.err) {
231
258
  throw new Error(`Transaction failed: ${JSON.stringify(conf.value.err)}`);
232
259
  }
260
+
233
261
  return { signature, slot: conf.context.slot, confirmed: true };
234
262
  }
235
263
 
@@ -247,7 +275,6 @@ export class SolanaStakingClient implements IStakingClient {
247
275
  const { blockhash, lastValidBlockHeight } = await this.connection.getLatestBlockhash('confirmed');
248
276
  tx.recentBlockhash = blockhash;
249
277
  tx.feePayer = this.solPubKey;
250
-
251
278
  return { tx, blockhash, lastValidBlockHeight };
252
279
  }
253
280
  }
@@ -1,20 +1,20 @@
1
- import { PublicKey, StakeActivationData, TokenAmount, Transaction, VersionedTransaction } from "@solana/web3.js";
1
+ import { PublicKey, TokenAmount, Transaction, VersionedTransaction } from "@solana/web3.js";
2
+ import { BN } from '@coral-xyz/anchor';
2
3
 
3
4
  export type SolanaTransaction = Transaction | VersionedTransaction
4
5
 
6
+ export type WalletLike = {
7
+ publicKey: PublicKey;
8
+ signTransaction: (tx: Transaction) => Promise<Transaction>;
9
+ signAllTransactions?: (txs: Transaction[]) => Promise<Transaction[]>;
10
+ };
11
+
5
12
  export type UserRecord = {
6
13
  userAta: PublicKey;
7
14
  trackedBalance: bigint; // What we think they have (for reward calculations)
8
15
  claimBalance: bigint; // Accumulated unclaimed rewards
9
16
  lastClaimTimestamp: bigint; // When they last claimed (unix timestamp)
10
17
  bump: number;
11
- }
12
-
13
- export type DistributionState = {
14
- liqsolMint: PublicKey;
15
- availableBalance: bigint;
16
- totalTrackedBalance: bigint;
17
- bump: number;
18
18
  }
19
19
 
20
20
  export type ParsedAccountInfo = {
@@ -44,4 +44,91 @@ export interface CorrectRegisterBuildResult {
44
44
  reason?: string;
45
45
  transaction?: Transaction;
46
46
  plan: CorrectRegisterPlan;
47
+ }
48
+
49
+ /**
50
+ * Wallet interface compatible with AnchorProvider.
51
+ * (Phantom, Solflare, Backpack, etc.)
52
+ */
53
+ export type OutpostWireStateSnapshot = {
54
+ globalState: GlobalState;
55
+ distributionState: DistributionState;
56
+
57
+ wireReceipt: WireReceipt | null;
58
+ trancheState: TrancheState | null;
59
+ userWarrantRecord: UserWarrantRecord | null;
60
+
61
+ // balances
62
+ liqsolPoolBalance: BN; // token amount in pool ATA
63
+ solBucketLamports: number; // lamports in sol bucket PDA
64
+ userLiqsolBalance: BN; // token amount in user ATA
65
+ };
66
+
67
+ export type DistributionState = {
68
+ liqsolMint: PublicKey;
69
+ availableBalance: bigint;
70
+ totalTrackedBalance: bigint;
71
+ bump: number;
72
+ }
73
+
74
+ export type SharesPreview = {
75
+ amountLamports: BN;
76
+ currentIndex: BN;
77
+ indexScale: BN;
78
+ expectedShares: BN;
79
+ };
80
+
81
+ export type GlobalState = {
82
+ admin: PublicKey;
83
+ dDay: boolean;
84
+ refundActive: boolean;
85
+ deployedAt: BN;
86
+ refundDelaySeconds: BN;
87
+ paused: boolean;
88
+ totalStakedLiqsol: BN;
89
+ totalPurchasedLiqsol: BN;
90
+ totalPurchasedSol: BN;
91
+ totalShares: BN;
92
+ currentIndex: BN;
93
+ lastPoolLiqsolBalance: BN;
94
+ lastPoolSolBalance: BN;
95
+ yieldAccumulatedSolEq: BN;
96
+ bump: number;
97
+ }
98
+
99
+ export type UserWarrantRecord = {
100
+ user: PublicKey;
101
+ totalSolDeposited: BN;
102
+ totalWarrantsPurchased: BN;
103
+ lastTrancheNumber: BN;
104
+ lastTranchePriceUsd: BN;
105
+ bump: number;
106
+ }
107
+
108
+ export type TrancheState = {
109
+ admin: PublicKey;
110
+ currentTrancheNumber: BN;
111
+ currentTrancheSupply: BN;
112
+ currentTranchePriceUsd: BN;
113
+ totalWarrantsSold: BN;
114
+ initialTrancheSupply: BN;
115
+ supplyGrowthBps: number;
116
+ priceGrowthBps: number;
117
+ minPriceUsd: BN;
118
+ maxPriceUsd: BN;
119
+ maxStalenessSeconds: BN;
120
+ chainlinkProgram: PublicKey;
121
+ chainlinkFeed: PublicKey;
122
+ bump: number;
123
+ }
124
+
125
+ export type WireReceipt = {
126
+ user: PublicKey;
127
+ stakedLiqsol: BN;
128
+ stakedShares: BN;
129
+ purchasedLiqsol: BN;
130
+ purchasedShares: BN;
131
+ purchasedWithSol: BN;
132
+ purchasedSolShares: BN;
133
+ bump: number;
47
134
  }