@wireio/stake 0.1.0 → 0.1.1

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.
Files changed (50) hide show
  1. package/README.md +57 -0
  2. package/lib/stake.browser.js +4623 -3451
  3. package/lib/stake.browser.js.map +1 -1
  4. package/lib/stake.d.ts +372 -537
  5. package/lib/stake.js +4801 -3574
  6. package/lib/stake.js.map +1 -1
  7. package/lib/stake.m.js +4623 -3451
  8. package/lib/stake.m.js.map +1 -1
  9. package/package.json +1 -1
  10. package/src/assets/solana/idl/liqsol_core.json +4239 -0
  11. package/src/assets/solana/idl/liqsol_token.json +183 -0
  12. package/src/assets/solana/idl/validator_leaderboard.json +270 -265
  13. package/src/assets/solana/types/liqsol_core.ts +4245 -0
  14. package/src/assets/solana/types/liqsol_token.ts +189 -0
  15. package/src/assets/solana/types/validator_leaderboard.ts +270 -265
  16. package/src/index.ts +1 -3
  17. package/src/networks/ethereum/contract.ts +138 -36
  18. package/src/networks/ethereum/ethereum.ts +167 -38
  19. package/src/networks/ethereum/types.ts +32 -1
  20. package/src/networks/solana/clients/deposit.client.ts +71 -109
  21. package/src/networks/solana/clients/distribution.client.ts +256 -383
  22. package/src/networks/solana/clients/leaderboard.client.ts +38 -133
  23. package/src/networks/solana/constants.ts +214 -130
  24. package/src/networks/solana/program.ts +25 -38
  25. package/src/networks/solana/solana.ts +100 -89
  26. package/src/networks/solana/types.ts +37 -47
  27. package/src/networks/solana/utils.ts +551 -0
  28. package/src/scripts/tsconfig.json +17 -0
  29. package/src/staker/staker.ts +5 -4
  30. package/src/staker/types.ts +2 -2
  31. package/src/assets/solana/idl/deposit.json +0 -296
  32. package/src/assets/solana/idl/distribution.json +0 -768
  33. package/src/assets/solana/idl/liq_sol_token.json +0 -298
  34. package/src/assets/solana/idl/mint_helper.json +0 -110
  35. package/src/assets/solana/idl/read_tracked_balance.json +0 -140
  36. package/src/assets/solana/idl/stake_controller.json +0 -2149
  37. package/src/assets/solana/idl/treasury.json +0 -110
  38. package/src/assets/solana/idl/validator_registry.json +0 -487
  39. package/src/assets/solana/idl/yield_oracle.json +0 -32
  40. package/src/assets/solana/types/deposit.ts +0 -302
  41. package/src/assets/solana/types/distribution.ts +0 -774
  42. package/src/assets/solana/types/liq_sol_token.ts +0 -304
  43. package/src/assets/solana/types/mint_helper.ts +0 -116
  44. package/src/assets/solana/types/read_tracked_balance.ts +0 -146
  45. package/src/assets/solana/types/stake_controller.ts +0 -2155
  46. package/src/assets/solana/types/stake_registry.ts +0 -441
  47. package/src/assets/solana/types/treasury.ts +0 -116
  48. package/src/assets/solana/types/validator_registry.ts +0 -493
  49. package/src/assets/solana/types/yield_oracle.ts +0 -38
  50. package/src/common/utils.ts +0 -9
@@ -1,20 +1,27 @@
1
1
  import {
2
- Connection,
3
- PublicKey as SolPubKey,
4
- TransactionSignature,
5
2
  Commitment,
3
+ Connection,
6
4
  ConnectionConfig,
5
+ PublicKey as SolPubKey,
7
6
  Transaction,
7
+ TransactionSignature,
8
8
  } from '@solana/web3.js';
9
9
  import { AnchorProvider } from '@coral-xyz/anchor';
10
10
  import { BaseSignerWalletAdapter } from '@solana/wallet-adapter-base';
11
- import { IStakingClient, Portfolio, StakerConfig } from '../../staker/types';
11
+ import { getAssociatedTokenAddressSync, TOKEN_2022_PROGRAM_ID } from '@solana/spl-token';
12
+
12
13
  import { ChainID, ExternalNetwork, KeyType, PublicKey } from '@wireio/core';
14
+ import { IStakingClient, Portfolio, StakerConfig } from '../../staker/types';
15
+
13
16
  import { DepositClient } from './clients/deposit.client';
14
- import { deriveStakeControllerReservePoolPDA, deriveStakeControllerVaultPDA, getUserLiqsolATA } from './constants';
15
17
  import { DistributionClient } from './clients/distribution.client';
16
- import { SolanaTransaction } from './types';
17
- // import { ValidatorLeaderboardClient } from './clients/leaderboard.client';
18
+ import {
19
+ deriveLiqsolMintPda,
20
+ deriveReservePoolPda,
21
+ deriveVaultPda,
22
+ } from './constants';
23
+ import { SolanaTransaction, UserRecord } from './types';
24
+ import { LeaderboardClient } from './clients/leaderboard.client';
18
25
 
19
26
  const commitment: Commitment = 'confirmed';
20
27
 
@@ -25,29 +32,32 @@ export class SolanaStakingClient implements IStakingClient {
25
32
 
26
33
  private depositClient: DepositClient;
27
34
  private distributionClient: DistributionClient;
28
- // private leaderboardClient: ValidatorLeaderboardClient;
35
+ private leaderboardClient: LeaderboardClient;
29
36
 
30
37
  get solPubKey(): SolPubKey {
31
38
  return new SolPubKey(this.pubKey.data.array);
32
39
  }
33
40
 
34
- get network() { return this.config.network; }
41
+ get network() {
42
+ return this.config.network;
43
+ }
35
44
 
36
45
  constructor(private config: StakerConfig) {
37
- // 1) unwrap & validate wallet adapter
38
46
  const adapter = config.provider as BaseSignerWalletAdapter;
39
- if (!adapter.publicKey) throw new Error('Solana wallet adapter not connected');
40
- if (!config.network.rpcUrls.length) throw new Error('No RPC URLs provided');
47
+ if (!adapter.publicKey) {
48
+ throw new Error('Solana wallet adapter not connected');
49
+ }
50
+ if (!config.network.rpcUrls.length) {
51
+ throw new Error('No RPC URLs provided');
52
+ }
41
53
 
42
- // 2) sanity‐check wire ↔ solana pubkey
43
54
  const publicKey = adapter.publicKey;
44
55
  const wirePub = new PublicKey(KeyType.ED, publicKey.toBytes());
45
56
  if (!wirePub.equals(config.pubKey)) {
46
- throw new Error('Passed-in pubKey doesn\'t match adapter.publicKey');
57
+ throw new Error("Passed-in pubKey doesn't match adapter.publicKey");
47
58
  }
48
59
 
49
- // build connection config
50
- let opts: ConnectionConfig = { commitment }
60
+ const opts: ConnectionConfig = { commitment };
51
61
  if (config.network.rpcUrls.length > 1 && config.network.rpcUrls[1].startsWith('ws')) {
52
62
  opts.wsEndpoint = config.network.rpcUrls[1];
53
63
  }
@@ -67,10 +77,9 @@ export class SolanaStakingClient implements IStakingClient {
67
77
 
68
78
  this.anchor = new AnchorProvider(this.connection, anchorWallet, { commitment });
69
79
 
70
- // 4) staking clients
71
80
  this.depositClient = new DepositClient(this.anchor);
72
81
  this.distributionClient = new DistributionClient(this.anchor);
73
- // this.leaderboardClient = new ValidatorLeaderboardClient(this.anchor);
82
+ this.leaderboardClient = new LeaderboardClient(this.anchor);
74
83
  }
75
84
 
76
85
  /**
@@ -80,67 +89,74 @@ export class SolanaStakingClient implements IStakingClient {
80
89
  * tracked = liqSOL tracked balance (from Distribution.userRecord)
81
90
  */
82
91
  async getPortfolio(): Promise<Portfolio> {
83
- const user = this.solPubKey;
84
-
85
- // Handy PDAs & ATA
86
- const [reservePoolPDA] = deriveStakeControllerReservePoolPDA();
87
- const [vaultPDA] = deriveStakeControllerVaultPDA();
88
- const userLiqsolAta = getUserLiqsolATA(user);
89
-
90
- // Pull balances in parallel; ATA may not exist yet
91
- const [nativeLamports, actualBalResp, userRecord] = await Promise.all([
92
- this.connection.getBalance(user),
93
- this.connection.getTokenAccountBalance(userLiqsolAta).catch(() => null),
94
- this.distributionClient.getUserRecord(user).catch(() => null),
95
- ]);
96
-
97
- // Actual (LiqSOL) balance + decimals (fallback to 9 if ATA missing)
98
- const actualAmountStr = actualBalResp?.value?.amount ?? '0';
99
- const actualDecimals = actualBalResp?.value?.decimals ?? 9;
100
-
101
- // Tracked (from userRecord in Distribution program)
102
- const trackedAmountStr = userRecord?.trackedBalance
103
- ? (userRecord.trackedBalance as any).toString()
104
- : '0';
105
- const trackedDecimals = actualDecimals; // same mint as liqSOL
106
-
107
- // Assemble the portfolio
108
- const portfolio: Portfolio = {
109
- native: {
110
- symbol: 'SOL',
111
- decimals: 9,
112
- amount: BigInt(nativeLamports),
113
- },
114
- actual: {
115
- symbol: 'LiqSOL',
116
- decimals: actualDecimals,
117
- amount: BigInt(actualAmountStr),
118
- },
119
- tracked: {
120
- symbol: 'LiqSOL',
121
- decimals: trackedDecimals,
122
- amount: BigInt(trackedAmountStr),
123
- },
124
- extras: {
125
- userLiqsolAta: userLiqsolAta.toBase58(),
126
- reservePoolPDA: reservePoolPDA.toBase58(),
127
- vaultPDA: vaultPDA.toBase58(),
128
- },
129
- };
130
-
131
- // console.log('>> PORTFOLIO SET', this.network.name, this.portfolio);
132
- return portfolio;
92
+ try {
93
+ const user = this.solPubKey;
94
+ const reservePoolPDA = deriveReservePoolPda();
95
+ const vaultPDA = deriveVaultPda();
96
+ const liqsolMint = deriveLiqsolMintPda();
97
+ const userLiqsolAta = getAssociatedTokenAddressSync(
98
+ liqsolMint,
99
+ user,
100
+ false,
101
+ TOKEN_2022_PROGRAM_ID,
102
+ );
103
+
104
+ const [nativeLamports, actualBalResp, userRecord] = await Promise.all([
105
+ this.connection.getBalance(user),
106
+ this.connection.getTokenAccountBalance(userLiqsolAta).catch(() => null),
107
+ this.distributionClient.getUserRecord(user).catch(() => null),
108
+ ]);
109
+
110
+ const actualAmountStr = actualBalResp?.value?.amount ?? '0';
111
+ const actualDecimals = actualBalResp?.value?.decimals ?? 9;
112
+
113
+ const trackedAmountStr = userRecord?.trackedBalance
114
+ ? userRecord.trackedBalance.toString()
115
+ : '0';
116
+ const trackedDecimals = actualDecimals;
117
+
118
+ const portfolio: Portfolio = {
119
+ native: {
120
+ symbol: 'SOL',
121
+ decimals: 9,
122
+ amount: BigInt(nativeLamports),
123
+ },
124
+ actual: {
125
+ symbol: 'LiqSOL',
126
+ decimals: actualDecimals,
127
+ amount: BigInt(actualAmountStr),
128
+ },
129
+ tracked: {
130
+ symbol: 'LiqSOL',
131
+ decimals: trackedDecimals,
132
+ amount: BigInt(trackedAmountStr),
133
+ },
134
+ extras: {
135
+ userLiqsolAta: userLiqsolAta.toBase58(),
136
+ reservePoolPDA: reservePoolPDA.toBase58(),
137
+ vaultPDA: vaultPDA.toBase58(),
138
+ },
139
+ };
140
+
141
+ return portfolio;
142
+ }
143
+ catch (error) {
144
+ console.log('Error in getPortfolio:', error);
145
+ throw error;
146
+ }
133
147
  }
148
+
134
149
  /**
135
150
  * Optional: fetch your Distribution program user record
136
151
  * (often contains per-user deposit/claim state).
152
+ * @returns UserRecord or null
137
153
  */
138
- async getUserRecord(): Promise<any | null> {
154
+ async getUserRecord() {
139
155
  return this.distributionClient.getUserRecord(this.solPubKey);
140
156
  }
141
157
 
142
158
  getProtocolFee() {
143
-
159
+ // TODO: wire to pay-rate math once we finalize protocol fee API
144
160
  }
145
161
 
146
162
  /**
@@ -153,27 +169,22 @@ export class SolanaStakingClient implements IStakingClient {
153
169
  */
154
170
  async register(amount?: bigint): Promise<string> {
155
171
  try {
156
- console.log('Building CorrectAndRegister transaction with amount:', amount);
157
-
158
- // Build the transaction using the Distribution client (self = this.solPubKey)
159
172
  const build = await this.distributionClient.buildCorrectRegisterTx({ amount });
160
- if (!build.canSucceed || !build.transaction)
173
+ if (!build.canSucceed || !build.transaction) {
161
174
  throw new Error(build.reason ?? 'Unable to build Correct&Register transaction');
162
-
163
- console.log('buildCorrectRegisterTx:', build);
175
+ }
164
176
 
165
177
  const { tx, blockhash, lastValidBlockHeight } = await this.prepareTx(build.transaction);
166
178
  const signed = await this.signTransaction(tx);
167
179
  const result = await this.sendAndConfirmHttp(signed, { blockhash, lastValidBlockHeight });
168
180
 
169
- // Optionally refresh portfolio after success (non-blocking)
170
181
  console.log('Registered:', {
171
182
  needToRegister: build.needToRegister,
172
183
  freed: build.plan.willFree,
173
- corrected: build.plan.selected.map(c => ({
174
- owner: c.owner.toBase58(),
175
- delta: c.delta.toString(),
176
- }))
184
+ corrected: build.plan.selected.map((c) => ({
185
+ owner: c.owner?.toBase58(),
186
+ delta: c.delta?.toString(),
187
+ })),
177
188
  });
178
189
 
179
190
  return result.signature;
@@ -198,19 +209,17 @@ export class SolanaStakingClient implements IStakingClient {
198
209
 
199
210
  private async sendAndConfirmHttp(
200
211
  signed: SolanaTransaction,
201
- ctx: { blockhash: string; lastValidBlockHeight: number }
212
+ ctx: { blockhash: string; lastValidBlockHeight: number },
202
213
  ): Promise<TxResult> {
203
- // sendRawTransaction is HTTP
204
214
  const signature = await this.connection.sendRawTransaction(signed.serialize(), {
205
215
  skipPreflight: false,
206
216
  preflightCommitment: commitment,
207
217
  maxRetries: 3,
208
218
  });
209
219
 
210
- // Poll confirmation using blockhash/lastValidBlockHeight
211
220
  const conf = await this.connection.confirmTransaction(
212
221
  { signature, blockhash: ctx.blockhash, lastValidBlockHeight: ctx.lastValidBlockHeight },
213
- commitment
222
+ commitment,
214
223
  );
215
224
 
216
225
  const ok = !conf.value.err;
@@ -221,14 +230,16 @@ export class SolanaStakingClient implements IStakingClient {
221
230
  }
222
231
 
223
232
  async signTransaction(tx: SolanaTransaction): Promise<SolanaTransaction> {
224
- return await this.anchor.wallet.signTransaction(tx);
233
+ return this.anchor.wallet.signTransaction(tx);
225
234
  }
226
235
 
227
236
  async sendTransaction(signed: SolanaTransaction): Promise<TransactionSignature> {
228
- return await this.anchor.sendAndConfirm(signed);
237
+ return this.anchor.sendAndConfirm(signed);
229
238
  }
230
239
 
231
- async prepareTx(tx: Transaction): Promise<{ tx: Transaction; blockhash: string; lastValidBlockHeight: number }> {
240
+ async prepareTx(
241
+ tx: Transaction,
242
+ ): Promise<{ tx: Transaction; blockhash: string; lastValidBlockHeight: number }> {
232
243
  const { blockhash, lastValidBlockHeight } = await this.connection.getLatestBlockhash('confirmed');
233
244
  tx.recentBlockhash = blockhash;
234
245
  tx.feePayer = this.solPubKey;
@@ -241,4 +252,4 @@ export interface TxResult {
241
252
  signature: string;
242
253
  slot: number;
243
254
  confirmed: boolean;
244
- };
255
+ }
@@ -1,57 +1,47 @@
1
- import { PublicKey, Transaction, VersionedTransaction } from "@solana/web3.js";
1
+ import { PublicKey, StakeActivationData, TokenAmount, Transaction, VersionedTransaction } from "@solana/web3.js";
2
2
 
3
3
  export type SolanaTransaction = Transaction | VersionedTransaction
4
4
 
5
- /** Raw mismatch row (per user with a userRecord) */
6
- export type MismatchCandidate = {
7
- /** Wallet that owns the ATA (decoded from token account) */
8
- owner: PublicKey;
9
- /** user_record PDA */
10
- userRecordPda: PublicKey;
11
- /** user’s ATA for liqSOL */
5
+ export type UserRecord = {
12
6
  userAta: PublicKey;
13
- /** protocol tracked balance (u64) */
14
- tracked: bigint;
15
- /** actual on-chain token balance (u64) */
16
- actual: bigint;
17
- /** tracked - actual (positive means “freeable”) */
18
- delta: bigint;
7
+ trackedBalance: bigint; // What we think they have (for reward calculations)
8
+ claimBalance: bigint; // Accumulated unclaimed rewards
9
+ lastClaimTimestamp: bigint; // When they last claimed (unix timestamp)
10
+ bump: number;
11
+ }
12
+
13
+ export type DistributionState = {
14
+ liqsolMint: PublicKey;
15
+ availableBalance: bigint;
16
+ totalTrackedBalance: bigint;
17
+ bump: number;
18
+ }
19
+
20
+ export type ParsedAccountInfo = {
21
+ extensions: Array<{ extension: string }>;
22
+ isNative: boolean;
23
+ mint: string;
24
+ owner: string;
25
+ state: string;
26
+ tokenAmount: TokenAmount
19
27
  };
28
+ export interface MismatchCandidate {
29
+ owner: PublicKey;
30
+ actual: bigint;
31
+ tracked: bigint;
32
+ delta: bigint; // tracked - actual
33
+ }
20
34
 
21
- /** Output when choosing candidates to free liquidity */
22
- export type CorrectionPlan = {
23
- /** selected candidates sorted by delta desc */
24
- selected: MismatchCandidate[];
25
- /** total delta we’ll free by correcting selected */
26
- willFree: bigint;
27
- /** how much still missing after selection (0 if we can meet the target) */
35
+ export interface CorrectRegisterPlan {
28
36
  deficit: bigint;
29
- };
37
+ willFree: bigint;
38
+ selected: MismatchCandidate[];
39
+ }
30
40
 
31
- /** What the builder returns to your caller/UI */
32
- export type CorrectAndRegisterBuild = {
33
- /** The ready-to-send transaction (if buildable) */
34
- transaction?: Transaction;
35
- /** True if the tx can succeed with current state */
41
+ export interface CorrectRegisterBuildResult {
42
+ needToRegister: boolean;
36
43
  canSucceed: boolean;
37
- /** Explanation if not buildable */
38
44
  reason?: string;
39
-
40
- /** Current liqSOL mint; useful for UI */
41
- liqsolMint: PublicKey;
42
- /** Amount you need to register (actual - tracked if positive) */
43
- needToRegister: bigint;
44
- /** Distribution “availableBalance” before this action */
45
- availableBefore: bigint;
46
-
47
- /** Candidates we scanned (already sorted by delta desc) */
48
- candidates: MismatchCandidate[];
49
- /** Subset we’d correct (maybe empty) */
50
- plan: CorrectionPlan;
51
-
52
- /** Convenience for caller */
53
- accounts: {
54
- selfUserRecordPda: PublicKey;
55
- selfUserAta: PublicKey;
56
- };
57
- };
45
+ transaction?: Transaction;
46
+ plan: CorrectRegisterPlan;
47
+ }