@wireio/stake 0.0.6 → 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 (48) hide show
  1. package/README.md +260 -13
  2. package/lib/stake.browser.js +4861 -4218
  3. package/lib/stake.browser.js.map +1 -1
  4. package/lib/stake.d.ts +434 -6484
  5. package/lib/stake.js +5059 -4371
  6. package/lib/stake.js.map +1 -1
  7. package/lib/stake.m.js +4861 -4218
  8. package/lib/stake.m.js.map +1 -1
  9. package/package.json +2 -2
  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 +296 -250
  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 +296 -250
  16. package/src/index.ts +2 -5
  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 +92 -139
  21. package/src/networks/solana/clients/distribution.client.ts +302 -178
  22. package/src/networks/solana/clients/leaderboard.client.ts +40 -160
  23. package/src/networks/solana/constants.ts +238 -69
  24. package/src/networks/solana/program.ts +27 -93
  25. package/src/networks/solana/solana.ts +181 -36
  26. package/src/networks/solana/types.ts +47 -0
  27. package/src/networks/solana/utils.ts +522 -93
  28. package/src/scripts/fetch-artifacts.sh +24 -0
  29. package/src/scripts/tsconfig.json +17 -0
  30. package/src/staker/staker.ts +35 -30
  31. package/src/staker/types.ts +25 -22
  32. package/src/assets/solana/idl/deposit.json +0 -260
  33. package/src/assets/solana/idl/distribution.json +0 -736
  34. package/src/assets/solana/idl/liq_sol_token.json +0 -275
  35. package/src/assets/solana/idl/stake_controller.json +0 -1788
  36. package/src/assets/solana/idl/stake_registry.json +0 -435
  37. package/src/assets/solana/idl/treasury.json +0 -336
  38. package/src/assets/solana/idl/validator_registry.json +0 -418
  39. package/src/assets/solana/idl/yield_oracle.json +0 -32
  40. package/src/assets/solana/types/deposit.ts +0 -266
  41. package/src/assets/solana/types/distribution.ts +0 -742
  42. package/src/assets/solana/types/liq_sol_token.ts +0 -281
  43. package/src/assets/solana/types/stake_controller.ts +0 -1794
  44. package/src/assets/solana/types/stake_registry.ts +0 -441
  45. package/src/assets/solana/types/treasury.ts +0 -342
  46. package/src/assets/solana/types/validator_registry.ts +0 -424
  47. package/src/assets/solana/types/yield_oracle.ts +0 -38
  48. package/src/utils.ts +0 -9
@@ -1,113 +1,47 @@
1
- // src/solana/programService.ts
2
-
3
1
  import { AnchorProvider, Program } from '@coral-xyz/anchor';
4
- import { PublicKey } from '@solana/web3.js';
5
2
 
6
- // 1) pull in all your on-chain IDLs (must match lowercase `src/assets/solana/idl`)
7
- import depositJson from '../../assets/solana/idl/deposit.json';
8
- import distributionJson from '../../assets/solana/idl/distribution.json';
9
- import liqSolTokenJson from '../../assets/solana/idl/liq_sol_token.json';
10
- import stakeControllerJson from '../../assets/solana/idl/stake_controller.json';
11
- import stakeRegistryJson from '../../assets/solana/idl/stake_registry.json';
12
- import treasuryJson from '../../assets/solana/idl/treasury.json';
3
+ import liqsolCoreJson from '../../assets/solana/idl/liqsol_core.json';
4
+ import liqsolTokenJson from '../../assets/solana/idl/liqsol_token.json';
13
5
  import validatorLeaderboardJson from '../../assets/solana/idl/validator_leaderboard.json';
14
- import validatorRegistryJson from '../../assets/solana/idl/validator_registry.json';
15
- import yieldOracleJson from '../../assets/solana/idl/yield_oracle.json';
16
6
 
17
- // 2) import their companion TS types
18
- import type { Deposit } from '../../assets/solana/types/deposit';
19
- import type { Distribution } from '../../assets/solana/types/distribution';
20
- import type { LiqSolToken } from '../../assets/solana/types/liq_sol_token';
21
- import type { StakeController } from '../../assets/solana/types/stake_controller';
22
- import type { StakeRegistry } from '../../assets/solana/types/stake_registry';
23
- import type { Treasury } from '../../assets/solana/types/treasury';
7
+ import type { LiqsolCore } from '../../assets/solana/types/liqsol_core';
8
+ import type { LiqsolToken } from '../../assets/solana/types/liqsol_token';
24
9
  import type { ValidatorLeaderboard } from '../../assets/solana/types/validator_leaderboard';
25
- import type { ValidatorRegistry } from '../../assets/solana/types/validator_registry';
26
- import type { YieldOracle } from '../../assets/solana/types/yield_oracle';
27
10
 
28
- type IdlEntry<IDL> = {
11
+ import { PROGRAM_IDS } from './constants';
12
+
13
+ type Entry<IDL> = {
29
14
  idl: IDL & { address: string };
30
- address: PublicKey;
15
+ address: string;
31
16
  };
32
17
 
33
- export const PROGRAM_IDLS = {
34
- deposit: {
35
- idl: depositJson,
36
- address: new PublicKey(depositJson.address),
37
- } as IdlEntry<Deposit>,
38
-
39
- distribution: {
40
- idl: distributionJson,
41
- address: new PublicKey(distributionJson.address),
42
- } as IdlEntry<Distribution>,
43
-
44
- liqSolToken: {
45
- idl: liqSolTokenJson,
46
- address: new PublicKey(liqSolTokenJson.address),
47
- } as IdlEntry<LiqSolToken>,
48
-
49
- stakeController: {
50
- idl: stakeControllerJson,
51
- address: new PublicKey(stakeControllerJson.address),
52
- } as IdlEntry<StakeController>,
53
-
54
- stakeRegistry: {
55
- idl: stakeRegistryJson,
56
- address: new PublicKey(stakeRegistryJson.address),
57
- } as IdlEntry<StakeRegistry>,
58
-
59
- treasury: {
60
- idl: treasuryJson,
61
- address: new PublicKey(treasuryJson.address),
62
- } as IdlEntry<Treasury>,
63
-
18
+ const PROGRAMS = {
19
+ liqsolCore: {
20
+ idl: liqsolCoreJson,
21
+ address: PROGRAM_IDS.LIQSOL_CORE.toBase58(),
22
+ } as Entry<LiqsolCore>,
23
+ liqsolToken: {
24
+ idl: liqsolTokenJson,
25
+ address: PROGRAM_IDS.LIQSOL_TOKEN.toBase58(),
26
+ } as Entry<LiqsolToken>,
64
27
  validatorLeaderboard: {
65
28
  idl: validatorLeaderboardJson,
66
- address: new PublicKey(validatorLeaderboardJson.address),
67
- } as IdlEntry<ValidatorLeaderboard>,
68
-
69
- validatorRegistry: {
70
- idl: validatorRegistryJson,
71
- address: new PublicKey(validatorRegistryJson.address),
72
- } as IdlEntry<ValidatorRegistry>,
29
+ address: PROGRAM_IDS.VALIDATOR_LEADERBOARD.toBase58(),
30
+ } as Entry<ValidatorLeaderboard>,
31
+ } as const;
73
32
 
74
- yieldOracle: {
75
- idl: yieldOracleJson,
76
- address: new PublicKey(yieldOracleJson.address),
77
- } as IdlEntry<YieldOracle>,
78
- };
33
+ export type SolanaProgramName = keyof typeof PROGRAMS;
79
34
 
80
- /**
81
- * Factory for Anchor Program clients.
82
- *
83
- * Anchor v0.28+ reads the program ID from `idl.address`, so
84
- * we call the 2-arg constructor: `new Program(idl, provider)`.
85
- */
86
35
  export class SolanaProgramService {
87
36
  constructor(private provider: AnchorProvider) { }
88
37
 
89
- /**
90
- * Returns a typed Anchor Program instance for the given key.
91
- */
92
- getProgram<K extends keyof typeof PROGRAM_IDLS>(
93
- name: K
94
- ): Program<(typeof PROGRAM_IDLS)[K]['idl']> {
95
- const entry = PROGRAM_IDLS[name];
96
- // stamp the correct address into a fresh copy of the IDL
97
- const idlWithAddr = { ...entry.idl, address: entry.address.toString() };
98
- // use the two-arg signature: (idl, provider)
99
- return new Program(idlWithAddr as any, this.provider) as Program<
100
- (typeof PROGRAM_IDLS)[K]['idl']
101
- >;
102
- }
103
-
104
- /** List all program keys */
105
- listProgramNames(): Array<keyof typeof PROGRAM_IDLS> {
106
- return Object.keys(PROGRAM_IDLS) as Array<keyof typeof PROGRAM_IDLS>;
38
+ getProgram<K extends SolanaProgramName>(name: K): Program<(typeof PROGRAMS)[K]['idl']> {
39
+ const { idl, address } = PROGRAMS[name];
40
+ const idlWithAddr = { ...idl, address };
41
+ return new Program(idlWithAddr, this.provider) as Program<(typeof PROGRAMS)[K]['idl']>;
107
42
  }
108
43
 
109
- /** Raw PublicKey of a program */
110
- getProgramId(name: keyof typeof PROGRAM_IDLS): PublicKey {
111
- return PROGRAM_IDLS[name].address;
44
+ listProgramNames(): SolanaProgramName[] {
45
+ return Object.keys(PROGRAMS) as SolanaProgramName[];
112
46
  }
113
47
  }
@@ -1,57 +1,69 @@
1
1
  import {
2
+ Commitment,
2
3
  Connection,
4
+ ConnectionConfig,
3
5
  PublicKey as SolPubKey,
4
6
  Transaction,
5
- VersionedTransaction,
6
7
  TransactionSignature,
7
- Commitment,
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, SolanaTransaction, 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
17
  import { DistributionClient } from './clients/distribution.client';
15
- 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';
16
25
 
17
26
  const commitment: Commitment = 'confirmed';
18
27
 
19
28
  export class SolanaStakingClient implements IStakingClient {
20
29
  public pubKey: PublicKey;
21
- public network: ExternalNetwork;
22
-
23
30
  public connection: Connection;
24
31
  public anchor: AnchorProvider;
25
32
 
26
33
  private depositClient: DepositClient;
27
34
  private distributionClient: DistributionClient;
28
- private leaderboardClient: ValidatorLeaderboardClient;
29
-
30
- balanceNative: number = 0;
31
- balanceLiquid: number = 0;
35
+ private leaderboardClient: LeaderboardClient;
32
36
 
33
37
  get solPubKey(): SolPubKey {
34
38
  return new SolPubKey(this.pubKey.data.array);
35
39
  }
36
40
 
41
+ get network() {
42
+ return this.config.network;
43
+ }
44
+
37
45
  constructor(private config: StakerConfig) {
38
- // 1) unwrap & validate wallet adapter
39
46
  const adapter = config.provider as BaseSignerWalletAdapter;
40
47
  if (!adapter.publicKey) {
41
48
  throw new Error('Solana wallet adapter not connected');
42
49
  }
50
+ if (!config.network.rpcUrls.length) {
51
+ throw new Error('No RPC URLs provided');
52
+ }
43
53
 
44
- // 2) sanity‐check wire ↔ solana pubkey
45
54
  const publicKey = adapter.publicKey;
46
55
  const wirePub = new PublicKey(KeyType.ED, publicKey.toBytes());
47
56
  if (!wirePub.equals(config.pubKey)) {
48
- throw new Error('Passed-in pubKey doesn\'t match adapter.publicKey');
57
+ throw new Error("Passed-in pubKey doesn't match adapter.publicKey");
58
+ }
59
+
60
+ const opts: ConnectionConfig = { commitment };
61
+ if (config.network.rpcUrls.length > 1 && config.network.rpcUrls[1].startsWith('ws')) {
62
+ opts.wsEndpoint = config.network.rpcUrls[1];
49
63
  }
50
64
 
51
- this.network = config.network;
52
65
  this.pubKey = wirePub;
53
- this.connection = new Connection(config.network.rpcUrls[0], commitment);
54
- this.setBalances();
66
+ this.connection = new Connection(config.network.rpcUrls[0], opts);
55
67
 
56
68
  const anchorWallet = {
57
69
  publicKey,
@@ -63,27 +75,123 @@ export class SolanaStakingClient implements IStakingClient {
63
75
  },
64
76
  };
65
77
 
66
- this.anchor = new AnchorProvider(
67
- this.connection,
68
- anchorWallet,
69
- { commitment }
70
- );
78
+ this.anchor = new AnchorProvider(this.connection, anchorWallet, { commitment });
71
79
 
72
- // 4) staking clients
73
80
  this.depositClient = new DepositClient(this.anchor);
74
81
  this.distributionClient = new DistributionClient(this.anchor);
75
- this.leaderboardClient = new ValidatorLeaderboardClient(this.anchor);
82
+ this.leaderboardClient = new LeaderboardClient(this.anchor);
76
83
  }
77
84
 
78
85
  /**
79
- * Update the native and liquid balances.
80
- * @returns The updated balances.
86
+ * Resolve the user's liqSOL ATA and balances + key protocol balances.
87
+ * native = SOL in wallet
88
+ * actual = liqSOL token balance (from ATA)
89
+ * tracked = liqSOL tracked balance (from Distribution.userRecord)
81
90
  */
82
- async setBalances() {
83
- this.balanceNative = await this.connection.getBalance(this.solPubKey);
84
- this.balanceLiquid = 0; // TODO fetch liq balance
91
+ async getPortfolio(): Promise<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
+ }
147
+ }
85
148
 
86
- return { native: this.balanceNative, liquid: this.balanceLiquid };
149
+ /**
150
+ * Optional: fetch your Distribution program user record
151
+ * (often contains per-user deposit/claim state).
152
+ * @returns UserRecord or null
153
+ */
154
+ async getUserRecord() {
155
+ return this.distributionClient.getUserRecord(this.solPubKey);
156
+ }
157
+
158
+ getProtocolFee() {
159
+ // TODO: wire to pay-rate math once we finalize protocol fee API
160
+ }
161
+
162
+ /**
163
+ * Build, sign, and submit a single transaction that:
164
+ * - Corrects other users (if needed) to free available balance, then
165
+ * - Registers the caller’s untracked liqSOL.
166
+ *
167
+ * @param amount Optional: register a smaller amount than your full untracked balance.
168
+ * @returns signature string
169
+ */
170
+ async register(amount?: bigint): Promise<string> {
171
+ try {
172
+ const build = await this.distributionClient.buildCorrectRegisterTx({ amount });
173
+ if (!build.canSucceed || !build.transaction) {
174
+ throw new Error(build.reason ?? 'Unable to build Correct&Register transaction');
175
+ }
176
+
177
+ const { tx, blockhash, lastValidBlockHeight } = await this.prepareTx(build.transaction);
178
+ const signed = await this.signTransaction(tx);
179
+ const result = await this.sendAndConfirmHttp(signed, { blockhash, lastValidBlockHeight });
180
+
181
+ console.log('Registered:', {
182
+ needToRegister: build.needToRegister,
183
+ freed: build.plan.willFree,
184
+ corrected: build.plan.selected.map((c) => ({
185
+ owner: c.owner?.toBase58(),
186
+ delta: c.delta?.toString(),
187
+ })),
188
+ });
189
+
190
+ return result.signature;
191
+ } catch (error) {
192
+ console.error('Error in register:', error);
193
+ throw error;
194
+ }
87
195
  }
88
196
 
89
197
  /**
@@ -92,19 +200,56 @@ export class SolanaStakingClient implements IStakingClient {
92
200
  * @returns The transaction signature.
93
201
  */
94
202
  async deposit(lamports: number): Promise<string> {
95
- const tx = await this.depositClient.buildDepositTx(
96
- this.solPubKey,
97
- lamports,
203
+ const { transaction } = await this.depositClient.buildDepositTx(this.solPubKey, lamports);
204
+ const { tx, blockhash, lastValidBlockHeight } = await this.prepareTx(transaction);
205
+ const signed = await this.signTransaction(tx);
206
+ const result = await this.sendAndConfirmHttp(signed, { blockhash, lastValidBlockHeight });
207
+ return result.signature;
208
+ }
209
+
210
+ private async sendAndConfirmHttp(
211
+ signed: SolanaTransaction,
212
+ ctx: { blockhash: string; lastValidBlockHeight: number },
213
+ ): Promise<TxResult> {
214
+ const signature = await this.connection.sendRawTransaction(signed.serialize(), {
215
+ skipPreflight: false,
216
+ preflightCommitment: commitment,
217
+ maxRetries: 3,
218
+ });
219
+
220
+ const conf = await this.connection.confirmTransaction(
221
+ { signature, blockhash: ctx.blockhash, lastValidBlockHeight: ctx.lastValidBlockHeight },
222
+ commitment,
98
223
  );
99
- const signed = await this.signTransaction(tx.transaction);
100
- return this.sendTransaction(signed);
224
+
225
+ const ok = !conf.value.err;
226
+ if (!ok) {
227
+ throw new Error(`Transaction failed: ${JSON.stringify(conf.value.err)}`);
228
+ }
229
+ return { signature, slot: conf.context.slot, confirmed: true };
101
230
  }
102
231
 
103
232
  async signTransaction(tx: SolanaTransaction): Promise<SolanaTransaction> {
104
- return await this.anchor.wallet.signTransaction(tx);
233
+ return this.anchor.wallet.signTransaction(tx);
105
234
  }
106
235
 
107
236
  async sendTransaction(signed: SolanaTransaction): Promise<TransactionSignature> {
108
- return await this.anchor.sendAndConfirm(signed);
237
+ return this.anchor.sendAndConfirm(signed);
109
238
  }
239
+
240
+ async prepareTx(
241
+ tx: Transaction,
242
+ ): Promise<{ tx: Transaction; blockhash: string; lastValidBlockHeight: number }> {
243
+ const { blockhash, lastValidBlockHeight } = await this.connection.getLatestBlockhash('confirmed');
244
+ tx.recentBlockhash = blockhash;
245
+ tx.feePayer = this.solPubKey;
246
+
247
+ return { tx, blockhash, lastValidBlockHeight };
248
+ }
249
+ }
250
+
251
+ export interface TxResult {
252
+ signature: string;
253
+ slot: number;
254
+ confirmed: boolean;
110
255
  }
@@ -0,0 +1,47 @@
1
+ import { PublicKey, StakeActivationData, TokenAmount, Transaction, VersionedTransaction } from "@solana/web3.js";
2
+
3
+ export type SolanaTransaction = Transaction | VersionedTransaction
4
+
5
+ export type UserRecord = {
6
+ userAta: PublicKey;
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
27
+ };
28
+ export interface MismatchCandidate {
29
+ owner: PublicKey;
30
+ actual: bigint;
31
+ tracked: bigint;
32
+ delta: bigint; // tracked - actual
33
+ }
34
+
35
+ export interface CorrectRegisterPlan {
36
+ deficit: bigint;
37
+ willFree: bigint;
38
+ selected: MismatchCandidate[];
39
+ }
40
+
41
+ export interface CorrectRegisterBuildResult {
42
+ needToRegister: boolean;
43
+ canSucceed: boolean;
44
+ reason?: string;
45
+ transaction?: Transaction;
46
+ plan: CorrectRegisterPlan;
47
+ }