@wireio/stake 0.1.0 → 0.1.2

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 (104) hide show
  1. package/README.md +57 -0
  2. package/lib/stake.browser.js +11836 -4103
  3. package/lib/stake.browser.js.map +1 -1
  4. package/lib/stake.d.ts +374 -556
  5. package/lib/stake.js +12089 -4303
  6. package/lib/stake.js.map +1 -1
  7. package/lib/stake.m.js +11836 -4103
  8. package/lib/stake.m.js.map +1 -1
  9. package/package.json +1 -1
  10. package/src/assets/ethereum/ABI/liqEth/DepositManager.sol/DepositManager.dbg.json +4 -0
  11. package/src/assets/ethereum/ABI/liqEth/DepositManager.sol/DepositManager.json +1153 -0
  12. package/src/assets/ethereum/ABI/liqEth/LiqEthCommon.sol/IAccounting.dbg.json +4 -0
  13. package/src/assets/ethereum/ABI/liqEth/LiqEthCommon.sol/IAccounting.json +172 -0
  14. package/src/assets/ethereum/ABI/liqEth/LiqEthCommon.sol/IDepositContract.dbg.json +4 -0
  15. package/src/assets/ethereum/ABI/liqEth/LiqEthCommon.sol/IDepositContract.json +39 -0
  16. package/src/assets/ethereum/ABI/liqEth/LiqEthCommon.sol/IDepositManager.dbg.json +4 -0
  17. package/src/assets/ethereum/ABI/liqEth/LiqEthCommon.sol/IDepositManager.json +64 -0
  18. package/src/assets/ethereum/ABI/liqEth/LiqEthCommon.sol/ILiqEthBurn.dbg.json +4 -0
  19. package/src/assets/ethereum/ABI/liqEth/LiqEthCommon.sol/ILiqEthBurn.json +24 -0
  20. package/src/assets/ethereum/ABI/liqEth/LiqEthCommon.sol/ILiqEthMint.dbg.json +4 -0
  21. package/src/assets/ethereum/ABI/liqEth/LiqEthCommon.sol/ILiqEthMint.json +35 -0
  22. package/src/assets/ethereum/ABI/liqEth/LiqEthCommon.sol/IRewardsERC20.dbg.json +4 -0
  23. package/src/assets/ethereum/ABI/liqEth/LiqEthCommon.sol/IRewardsERC20.json +213 -0
  24. package/src/assets/ethereum/ABI/liqEth/LiqEthCommon.sol/IStakingModule.dbg.json +4 -0
  25. package/src/assets/ethereum/ABI/liqEth/LiqEthCommon.sol/IStakingModule.json +138 -0
  26. package/src/assets/ethereum/ABI/liqEth/LiqEthCommon.sol/IValidatorBalanceVerifier.dbg.json +4 -0
  27. package/src/assets/ethereum/ABI/liqEth/LiqEthCommon.sol/IValidatorBalanceVerifier.json +70 -0
  28. package/src/assets/ethereum/ABI/liqEth/LiqEthCommon.sol/IWithdrawalRecord.dbg.json +4 -0
  29. package/src/assets/ethereum/ABI/liqEth/LiqEthCommon.sol/IWithdrawalRecord.json +64 -0
  30. package/src/assets/ethereum/ABI/liqEth/LiqEthCommon.sol/LiqEthCommon.dbg.json +4 -0
  31. package/src/assets/ethereum/ABI/liqEth/LiqEthCommon.sol/LiqEthCommon.json +10 -0
  32. package/src/assets/ethereum/ABI/liqEth/RewardsERC20.sol/RewardsERC20.dbg.json +4 -0
  33. package/src/assets/ethereum/ABI/liqEth/RewardsERC20.sol/RewardsERC20.json +749 -0
  34. package/src/assets/ethereum/ABI/liqEth/RewardsERC20Pausable.sol/RewardsERC20Pausable.dbg.json +4 -0
  35. package/src/assets/ethereum/ABI/liqEth/RewardsERC20Pausable.sol/RewardsERC20Pausable.json +812 -0
  36. package/src/assets/ethereum/ABI/liqEth/ValidatorBalanceVerifier.sol/BeaconRoots.dbg.json +4 -0
  37. package/src/assets/ethereum/ABI/liqEth/ValidatorBalanceVerifier.sol/BeaconRoots.json +10 -0
  38. package/src/assets/ethereum/ABI/liqEth/ValidatorBalanceVerifier.sol/SSZ.dbg.json +4 -0
  39. package/src/assets/ethereum/ABI/liqEth/ValidatorBalanceVerifier.sol/SSZ.json +10 -0
  40. package/src/assets/ethereum/ABI/liqEth/ValidatorBalanceVerifier.sol/ValidatorBalanceVerifier.dbg.json +4 -0
  41. package/src/assets/ethereum/ABI/liqEth/ValidatorBalanceVerifier.sol/ValidatorBalanceVerifier.json +225 -0
  42. package/src/assets/ethereum/ABI/liqEth/Yield.sol/BeaconRoots.dbg.json +4 -0
  43. package/src/assets/ethereum/ABI/liqEth/Yield.sol/BeaconRoots.json +10 -0
  44. package/src/assets/ethereum/ABI/liqEth/Yield.sol/SSZ.dbg.json +4 -0
  45. package/src/assets/ethereum/ABI/liqEth/Yield.sol/SSZ.json +10 -0
  46. package/src/assets/ethereum/ABI/liqEth/Yield.sol/YieldOracle.dbg.json +4 -0
  47. package/src/assets/ethereum/ABI/liqEth/Yield.sol/YieldOracle.json +813 -0
  48. package/src/assets/ethereum/ABI/liqEth/accounting.sol/Accounting.dbg.json +4 -0
  49. package/src/assets/ethereum/ABI/liqEth/accounting.sol/Accounting.json +651 -0
  50. package/src/assets/ethereum/ABI/liqEth/liqEth.sol/LiqEthToken.dbg.json +4 -0
  51. package/src/assets/ethereum/ABI/liqEth/liqEth.sol/LiqEthToken.json +1110 -0
  52. package/src/assets/ethereum/ABI/liqEth/liqEthBurn.sol/LiqEthBurn.dbg.json +4 -0
  53. package/src/assets/ethereum/ABI/liqEth/liqEthBurn.sol/LiqEthBurn.json +391 -0
  54. package/src/assets/ethereum/ABI/liqEth/liqEthMint.sol/LiqEthMint.dbg.json +4 -0
  55. package/src/assets/ethereum/ABI/liqEth/liqEthMint.sol/LiqEthMint.json +402 -0
  56. package/src/assets/ethereum/ABI/liqEth/stakingModule.sol/StakingModule.dbg.json +4 -0
  57. package/src/assets/ethereum/ABI/liqEth/stakingModule.sol/StakingModule.json +1225 -0
  58. package/src/assets/ethereum/ABI/liqEth/withdrawalQueue.sol/WithdrawalQueue.dbg.json +4 -0
  59. package/src/assets/ethereum/ABI/liqEth/withdrawalQueue.sol/WithdrawalQueue.json +927 -0
  60. package/src/assets/ethereum/ABI/liqEth/withdrawalVault.sol/Uint64BE.dbg.json +4 -0
  61. package/src/assets/ethereum/ABI/liqEth/withdrawalVault.sol/Uint64BE.json +10 -0
  62. package/src/assets/ethereum/ABI/liqEth/withdrawalVault.sol/WithdrawalVault.dbg.json +4 -0
  63. package/src/assets/ethereum/ABI/liqEth/withdrawalVault.sol/WithdrawalVault.json +447 -0
  64. package/src/assets/solana/idl/liqsol_core.json +4239 -0
  65. package/src/assets/solana/idl/liqsol_token.json +183 -0
  66. package/src/assets/solana/idl/validator_leaderboard.json +270 -265
  67. package/src/assets/solana/types/liqsol_core.ts +4245 -0
  68. package/src/assets/solana/types/liqsol_token.ts +189 -0
  69. package/src/assets/solana/types/validator_leaderboard.ts +270 -265
  70. package/src/index.ts +1 -3
  71. package/src/networks/ethereum/contract.ts +101 -36
  72. package/src/networks/ethereum/ethereum.ts +141 -45
  73. package/src/networks/ethereum/types.ts +30 -2
  74. package/src/networks/solana/clients/deposit.client.ts +71 -109
  75. package/src/networks/solana/clients/distribution.client.ts +256 -383
  76. package/src/networks/solana/clients/leaderboard.client.ts +38 -133
  77. package/src/networks/solana/constants.ts +214 -130
  78. package/src/networks/solana/program.ts +25 -38
  79. package/src/networks/solana/solana.ts +120 -105
  80. package/src/networks/solana/types.ts +37 -47
  81. package/src/networks/solana/utils.ts +551 -0
  82. package/src/scripts/tsconfig.json +17 -0
  83. package/src/staker/staker.ts +10 -6
  84. package/src/staker/types.ts +14 -9
  85. package/src/assets/solana/idl/deposit.json +0 -296
  86. package/src/assets/solana/idl/distribution.json +0 -768
  87. package/src/assets/solana/idl/liq_sol_token.json +0 -298
  88. package/src/assets/solana/idl/mint_helper.json +0 -110
  89. package/src/assets/solana/idl/read_tracked_balance.json +0 -140
  90. package/src/assets/solana/idl/stake_controller.json +0 -2149
  91. package/src/assets/solana/idl/treasury.json +0 -110
  92. package/src/assets/solana/idl/validator_registry.json +0 -487
  93. package/src/assets/solana/idl/yield_oracle.json +0 -32
  94. package/src/assets/solana/types/deposit.ts +0 -302
  95. package/src/assets/solana/types/distribution.ts +0 -774
  96. package/src/assets/solana/types/liq_sol_token.ts +0 -304
  97. package/src/assets/solana/types/mint_helper.ts +0 -116
  98. package/src/assets/solana/types/read_tracked_balance.ts +0 -146
  99. package/src/assets/solana/types/stake_controller.ts +0 -2155
  100. package/src/assets/solana/types/stake_registry.ts +0 -441
  101. package/src/assets/solana/types/treasury.ts +0 -116
  102. package/src/assets/solana/types/validator_registry.ts +0 -493
  103. package/src/assets/solana/types/yield_oracle.ts +0 -38
  104. package/src/common/utils.ts +0 -9
@@ -0,0 +1,551 @@
1
+ // src/networks/solana/utils.ts
2
+
3
+ import { Program, BN, AnchorProvider } from '@coral-xyz/anchor';
4
+
5
+ import {
6
+ Connection,
7
+ Keypair,
8
+ PublicKey,
9
+ SystemProgram,
10
+ StakeProgram,
11
+ } from '@solana/web3.js';
12
+ import {
13
+ TOKEN_2022_PROGRAM_ID,
14
+ ASSOCIATED_TOKEN_PROGRAM_ID,
15
+ getAssociatedTokenAddress,
16
+ } from '@solana/spl-token';
17
+
18
+ import {
19
+ LIQSOL_CORE,
20
+ LIQSOL_TOKEN,
21
+ PAY_RATE_SCALE_FACTOR,
22
+ DEFAULT_AVERAGE_PAY_RATE,
23
+ EPHEMERAL_RENT_EXEMPTION,
24
+ LAMPORTS_PER_SOL,
25
+ lamportsToSol,
26
+ solToLamports,
27
+ deriveDepositAuthorityPda,
28
+ deriveLiqsolMintPda,
29
+ deriveLiqsolMintAuthorityPda,
30
+ deriveVaultPda,
31
+ deriveReservePoolPda,
32
+ deriveStakeControllerStatePda,
33
+ derivePayoutStatePda,
34
+ deriveBucketAuthorityPda,
35
+ deriveDistributionStatePda,
36
+ deriveUserRecordPda,
37
+ derivePayRateHistoryPda,
38
+ deriveStakeControllerVaultPda,
39
+ deriveEphemeralStakeAddress,
40
+ DEFAULT_PAY_RATE_LOOKBACK,
41
+ } from './constants';
42
+
43
+ import liqsolCoreIDL from '../../assets/solana/idl/liqsol_core.json';
44
+
45
+ // -----------------------------------------------------------------------------
46
+ // Read-only liqsol_core Program helper
47
+ // -----------------------------------------------------------------------------
48
+
49
+ let _liqsolCoreProgram: Program | null = null;
50
+
51
+ export function getLiqsolCoreProgram(
52
+ connection: Connection,
53
+ ): Program {
54
+ if (_liqsolCoreProgram && _liqsolCoreProgram.provider.connection === connection) {
55
+ return _liqsolCoreProgram;
56
+ }
57
+
58
+ // Dummy wallet – we're only doing read-only account fetches here.
59
+ const tmpKeypair = Keypair.generate();
60
+ const wallet : any = { publicKey: tmpKeypair.publicKey, signAllTransactions: async () => [], signTransaction: async () => tmpKeypair };
61
+
62
+ const provider = new AnchorProvider(connection, wallet, {
63
+ commitment: 'confirmed',
64
+ });
65
+
66
+ const program = new Program(
67
+ liqsolCoreIDL,
68
+ provider,
69
+ ) as Program;
70
+
71
+ _liqsolCoreProgram = program;
72
+ return program;
73
+ }
74
+
75
+ // -----------------------------------------------------------------------------
76
+ // Deposit accounts bundle
77
+ // -----------------------------------------------------------------------------
78
+
79
+ export interface DepositAccounts {
80
+ user: PublicKey;
81
+ depositAuthority: PublicKey;
82
+ liqsolMint: PublicKey;
83
+ liqsolMintAuthority: PublicKey;
84
+ userAta: PublicKey;
85
+ stakeControllerVault: PublicKey;
86
+ stakeControllerReservePool: PublicKey;
87
+ stakeControllerState: PublicKey;
88
+ payoutState: PublicKey;
89
+ bucketAuthority: PublicKey;
90
+ bucketTokenAccount: PublicKey;
91
+ distributionState: PublicKey;
92
+ userRecord: PublicKey;
93
+ payRateHistory: PublicKey;
94
+ ephemeralStake: PublicKey;
95
+ ephemeralSeed: string;
96
+ }
97
+
98
+ /**
99
+ * Build a complete DepositAccounts set for a given user, matching the
100
+ * on-chain PDAs used by the liqSOL core program.
101
+ *
102
+ * The optional `seed` lets you make deposit flows replayable/deterministic.
103
+ * If omitted, a random u32 seed is generated.
104
+ */
105
+ export async function buildDepositAccounts(
106
+ connection: Connection,
107
+ user: PublicKey,
108
+ ): Promise<DepositAccounts> {
109
+ const depositAuthority = deriveDepositAuthorityPda();
110
+ const liqsolMint = deriveLiqsolMintPda();
111
+ const liqsolMintAuthority = deriveLiqsolMintAuthorityPda();
112
+ const stakeControllerVault = deriveStakeControllerVaultPda();
113
+ const stakeControllerReservePool = deriveReservePoolPda();
114
+ const stakeControllerState = deriveStakeControllerStatePda();
115
+ const payoutState = derivePayoutStatePda();
116
+ const bucketAuthority = deriveBucketAuthorityPda();
117
+ const distributionState = deriveDistributionStatePda();
118
+ const userRecord = deriveUserRecordPda(user);
119
+ const payRateHistory = derivePayRateHistoryPda();
120
+
121
+ const userAta = await getAssociatedTokenAddress(
122
+ liqsolMint,
123
+ user,
124
+ false,
125
+ TOKEN_2022_PROGRAM_ID,
126
+ );
127
+
128
+ const bucketTokenAccount = await getAssociatedTokenAddress(
129
+ liqsolMint,
130
+ bucketAuthority,
131
+ true,
132
+ TOKEN_2022_PROGRAM_ID,
133
+ );
134
+
135
+ // -------------------------------------------------------------
136
+ // Ephemeral stake
137
+ // -------------------------------------------------------------
138
+ const seed = Math.floor(Math.random() * 2 ** 32);
139
+ const ephemeralSeed = `ephemeral_${seed}`;
140
+ const ephemeralStake = await deriveEphemeralStakeAddress(user, ephemeralSeed);
141
+
142
+ // `connection` is currently unused, but we keep it in the signature
143
+ // so this helper can evolve to preflight/validate accounts if needed.
144
+ void connection;
145
+
146
+ return {
147
+ user,
148
+ depositAuthority,
149
+ liqsolMint,
150
+ liqsolMintAuthority,
151
+ userAta,
152
+ stakeControllerVault,
153
+ stakeControllerReservePool,
154
+ stakeControllerState,
155
+ payoutState,
156
+ bucketAuthority,
157
+ bucketTokenAccount,
158
+ distributionState,
159
+ userRecord,
160
+ payRateHistory,
161
+ ephemeralStake,
162
+ ephemeralSeed,
163
+ };
164
+ }
165
+
166
+ // -----------------------------------------------------------------------------
167
+ // Balance + state getters (UI-friendly)
168
+ // -----------------------------------------------------------------------------
169
+
170
+ export async function getUserLiqSolBalance(
171
+ connection: Connection,
172
+ user: PublicKey,
173
+ ): Promise<number> {
174
+ const liqsolMint = deriveLiqsolMintPda();
175
+ const ata = await getAssociatedTokenAddress(
176
+ liqsolMint,
177
+ user,
178
+ false,
179
+ TOKEN_2022_PROGRAM_ID,
180
+ );
181
+
182
+ try {
183
+ const bal = await connection.getTokenAccountBalance(ata);
184
+ return Number(bal.value.amount); // raw lamports, not decimals-adjusted
185
+ } catch {
186
+ return 0;
187
+ }
188
+ }
189
+
190
+ export async function getBucketLiqSolBalance(
191
+ connection: Connection,
192
+ ): Promise<number> {
193
+ const liqsolMint = deriveLiqsolMintPda();
194
+ const bucketAuthority = deriveBucketAuthorityPda();
195
+ const ata = await getAssociatedTokenAddress(
196
+ liqsolMint,
197
+ bucketAuthority,
198
+ true,
199
+ TOKEN_2022_PROGRAM_ID,
200
+ );
201
+
202
+ try {
203
+ const bal = await connection.getTokenAccountBalance(ata);
204
+ return Number(bal.value.amount);
205
+ } catch {
206
+ return 0;
207
+ }
208
+ }
209
+
210
+ export async function getReservePoolBalance(
211
+ connection: Connection,
212
+ ): Promise<number> {
213
+ const reservePool = deriveReservePoolPda();
214
+ try {
215
+ const lamports = await connection.getBalance(reservePool);
216
+ return lamports;
217
+ } catch {
218
+ return 0;
219
+ }
220
+ }
221
+
222
+ /**
223
+ * Raw account info for the stake_controller state (Anchor decode is left
224
+ * to the caller so the SDK can stay IDL-agnostic at this layer).
225
+ */
226
+ export async function getStakeControllerStateRaw(
227
+ connection: Connection,
228
+ ): Promise<Uint8Array | null> {
229
+ const pda = deriveStakeControllerStatePda();
230
+ const info = await connection.getAccountInfo(pda);
231
+ return info?.data ?? null;
232
+ }
233
+
234
+ /**
235
+ * Raw account info for the payout-state account.
236
+ */
237
+ export async function getPayoutStateRaw(
238
+ connection: Connection,
239
+ ): Promise<Uint8Array | null> {
240
+ const pda = derivePayoutStatePda();
241
+ const info = await connection.getAccountInfo(pda);
242
+ return info?.data ?? null;
243
+ }
244
+
245
+ /**
246
+ * Raw account info for a user's distribution user_record.
247
+ */
248
+ export async function getUserRecordRaw(
249
+ connection: Connection,
250
+ user: PublicKey,
251
+ ): Promise<Uint8Array | null> {
252
+ const pda = deriveUserRecordPda(user);
253
+ const info = await connection.getAccountInfo(pda);
254
+ return info?.data ?? null;
255
+ }
256
+
257
+ // -----------------------------------------------------------------------------
258
+ // Pay-rate & fee utilities (on-chain compatible)
259
+ // -----------------------------------------------------------------------------
260
+
261
+ export async function getAveragePayRate(
262
+ connection: Connection,
263
+ lookback: number = DEFAULT_PAY_RATE_LOOKBACK,
264
+ ): Promise<bigint> {
265
+ const program = getLiqsolCoreProgram(connection);
266
+ const payRateHistoryPda = derivePayRateHistoryPda();
267
+
268
+ try {
269
+ const anyProgram = program as any;
270
+ const payRateHistoryAccount = await anyProgram.account.payRateHistory.fetch(
271
+ payRateHistoryPda,
272
+ );
273
+
274
+ const entries: any[] = payRateHistoryAccount.entries ?? [];
275
+ const totalEntriesAdded = Number(
276
+ payRateHistoryAccount.totalEntriesAdded ?? 0,
277
+ );
278
+ const currentIndex: number = payRateHistoryAccount.currentIndex ?? 0;
279
+ const maxEntries: number =
280
+ payRateHistoryAccount.maxEntries ?? entries.length;
281
+
282
+ if (!entries.length) {
283
+ return DEFAULT_AVERAGE_PAY_RATE;
284
+ }
285
+
286
+ const entriesToFetch = Math.min(lookback, maxEntries, entries.length);
287
+
288
+ let idx: number;
289
+ if (totalEntriesAdded === 0) {
290
+ idx = 0;
291
+ } else if (currentIndex === 0) {
292
+ idx = maxEntries - 1;
293
+ } else {
294
+ idx = currentIndex - 1;
295
+ }
296
+
297
+ let sum = BigInt(0);
298
+ let validCount = BigInt(0);
299
+
300
+ for (let i = 0; i < entriesToFetch; i++) {
301
+ const entry = entries[idx];
302
+ if (entry && typeof entry.scaledRate !== 'undefined') {
303
+ const rate = BigInt(entry.scaledRate.toString());
304
+ if (rate > BigInt(0)) {
305
+ sum += rate;
306
+ validCount += BigInt(1);
307
+ }
308
+ }
309
+
310
+ if (totalEntriesAdded === 0) {
311
+ idx = (idx + 1) % maxEntries;
312
+ } else {
313
+ idx = idx === 0 ? maxEntries - 1 : idx - 1;
314
+ }
315
+ }
316
+
317
+ if (validCount === BigInt(0)) {
318
+ return DEFAULT_AVERAGE_PAY_RATE;
319
+ }
320
+
321
+ return sum / validCount;
322
+ } catch (err) {
323
+ // Fallback to default when history missing/unavailable
324
+ return DEFAULT_AVERAGE_PAY_RATE;
325
+ }
326
+ }
327
+ /**
328
+ * On-chain fee formula:
329
+ * fee = (average_pay_rate * 4 * deposit_amount_lamports) / 10^12
330
+ */
331
+ export function calculateExpectedFee(
332
+ depositAmountLamports: BN,
333
+ averagePayRate: BN,
334
+ ): BN {
335
+ return averagePayRate
336
+ .mul(new BN(4))
337
+ .mul(depositAmountLamports)
338
+ .div(new BN(1_000_000_000_000)); // 10^12
339
+ }
340
+
341
+ /**
342
+ * Convenience helper to preview how a deposit splits between user + bucket
343
+ * and how much goes into the reserve, assuming the simple model:
344
+ *
345
+ * - userLiqSol = amount - fee + EPHEMERAL_RENT_EXEMPTION
346
+ * - bucketLiqSol = fee
347
+ * - reserveLamports = amount + EPHEMERAL_RENT_EXEMPTION
348
+ */
349
+ export function previewDepositEffects(params: {
350
+ depositAmountLamports: BN;
351
+ averagePayRate: BN;
352
+ rentExemptionLamports?: number;
353
+ }): {
354
+ feeLamports: BN;
355
+ userLiqSolLamports: BN;
356
+ bucketLiqSolLamports: BN;
357
+ reserveIncreaseLamports: BN;
358
+ } {
359
+ const { depositAmountLamports, averagePayRate } = params;
360
+ const rent = new BN(
361
+ params.rentExemptionLamports ?? EPHEMERAL_RENT_EXEMPTION,
362
+ );
363
+
364
+ const fee = calculateExpectedFee(depositAmountLamports, averagePayRate);
365
+ const userLiqSol = depositAmountLamports.sub(fee).add(rent);
366
+ const bucketLiqSol = fee;
367
+ const reserveIncrease = depositAmountLamports.add(rent);
368
+
369
+ return {
370
+ feeLamports: fee,
371
+ userLiqSolLamports: userLiqSol,
372
+ bucketLiqSolLamports: bucketLiqSol,
373
+ reserveIncreaseLamports: reserveIncrease,
374
+ };
375
+ }
376
+
377
+ // -----------------------------------------------------------------------------
378
+ // Epoch / scheduling helpers
379
+ // -----------------------------------------------------------------------------
380
+
381
+ export type EpochSnapshot = {
382
+ epoch: number;
383
+ slot: number;
384
+ firstSlot: number;
385
+ slotsInEpoch: number;
386
+ slotMs: number;
387
+ };
388
+
389
+ export async function getEpochSnapshot(
390
+ connection: Connection,
391
+ ): Promise<EpochSnapshot> {
392
+ const info = await connection.getEpochInfo();
393
+
394
+ // Fallback slot time
395
+ let slotTimeMs = Number(process.env.SLOT_TIME_MS_FALLBACK ?? 400);
396
+
397
+ try {
398
+ const samples = await connection.getRecentPerformanceSamples(16);
399
+ if (samples.length) {
400
+ const avgMs =
401
+ samples.reduce(
402
+ (acc, s) => acc + (s.samplePeriodSecs * 1000) / s.numSlots,
403
+ 0,
404
+ ) / samples.length;
405
+ if (isFinite(avgMs) && avgMs > 0) {
406
+ slotTimeMs = avgMs;
407
+ }
408
+ }
409
+ } catch {
410
+ // ignore; keep fallback
411
+ }
412
+
413
+ return {
414
+ epoch: info.epoch,
415
+ slot: info.slotIndex,
416
+ firstSlot: info.absoluteSlot - info.slotIndex,
417
+ slotsInEpoch: info.slotsInEpoch,
418
+ slotMs: slotTimeMs,
419
+ };
420
+ }
421
+
422
+ export function msToEpochEnd(snapshot: EpochSnapshot): number {
423
+ const remainingSlots = snapshot.slotsInEpoch - snapshot.slot;
424
+ return remainingSlots * snapshot.slotMs;
425
+ }
426
+
427
+ /**
428
+ * Generic "execute around epoch boundaries" helper:
429
+ *
430
+ * - If current progress is within [early, late], run immediately.
431
+ * - If too early, sleep until `early` percentage into the epoch.
432
+ * - If too late, sleep until `early` percentage into the *next* epoch.
433
+ *
434
+ * This is generic; you can wrap any instruction builder in here (including
435
+ * deposit flows) without baking in program-specific logic.
436
+ */
437
+ export async function scheduledInstruction<T>(
438
+ connection: Connection,
439
+ config: ScheduleConfig,
440
+ instruction: () => Promise<T>,
441
+ ): Promise<T> {
442
+ const early = config.early ?? 0.10;
443
+ const late = config.late ?? 0.90;
444
+
445
+ const snapshot = await getEpochSnapshot(connection);
446
+ const progress = snapshot.slot / snapshot.slotsInEpoch;
447
+
448
+ // Case 1: Already in safe zone
449
+ if (progress >= early && progress <= late) {
450
+ return instruction();
451
+ }
452
+
453
+ // Case 2: Early in epoch → wait until `early`
454
+ if (progress < early) {
455
+ const targetSlot = snapshot.slotsInEpoch * early;
456
+ const slotsRemaining = targetSlot - snapshot.slot;
457
+ const msToWait = slotsRemaining * snapshot.slotMs;
458
+
459
+ console.log(
460
+ `Epoch early (${(progress * 100).toFixed(
461
+ 2,
462
+ )}%). Sleeping ${(msToWait / 1000).toFixed(1)}s until ${early * 100
463
+ }%...`,
464
+ );
465
+
466
+ await sleep(Math.max(1000, msToWait));
467
+ return instruction();
468
+ }
469
+
470
+ // Case 3: Late in epoch → wait for next epoch + early window
471
+ const msToNextEpoch = msToEpochEnd(snapshot);
472
+ const earlyWaitTime = snapshot.slotsInEpoch * early * snapshot.slotMs;
473
+ const totalWaitTime = msToNextEpoch + earlyWaitTime + 1000;
474
+
475
+ console.log(
476
+ `Epoch late (${(progress * 100).toFixed(
477
+ 2,
478
+ )}%). Sleeping ${(totalWaitTime / 1000).toFixed(
479
+ 1,
480
+ )}s until next epoch + ${early * 100}%...`,
481
+ );
482
+
483
+ await sleep(totalWaitTime);
484
+ return instruction();
485
+ }
486
+
487
+
488
+ // -----------------------------------------------------------------------------
489
+ // Generic helpers (used in tests + can be useful in apps)
490
+ // -----------------------------------------------------------------------------
491
+
492
+ export function getErrorMessage(error: any): string {
493
+ return (
494
+ error?.error?.errorCode?.code ||
495
+ error?.error?.errorMessage ||
496
+ error?.message ||
497
+ ''
498
+ );
499
+ }
500
+
501
+ export function generateRandomDepositAmount(
502
+ minSol = 2,
503
+ maxSol = 100,
504
+ ): BN {
505
+ const randomSol = Math.random() * (maxSol - minSol) + minSol;
506
+ return new BN(solToLamports(randomSol));
507
+ }
508
+
509
+ export function generateTestKeypair(): Keypair {
510
+ return Keypair.generate();
511
+ }
512
+
513
+ export async function airdropSol(
514
+ connection: Connection,
515
+ publicKey: PublicKey,
516
+ amountSol: number,
517
+ ): Promise<void> {
518
+ const lamports = solToLamports(amountSol);
519
+ const sig = await connection.requestAirdrop(publicKey, lamports);
520
+ await connection.confirmTransaction(sig, 'confirmed');
521
+ }
522
+
523
+ export async function waitForConfirmation(
524
+ connection: Connection,
525
+ signature: string,
526
+ ): Promise<void> {
527
+ await connection.confirmTransaction(signature, 'confirmed');
528
+ }
529
+
530
+ export function sleep(ms: number): Promise<void> {
531
+ return new Promise((resolve) => setTimeout(resolve, ms));
532
+ }
533
+
534
+ /**
535
+ * Simple helper used in tests: wait until safe zone, no-op.
536
+ */
537
+ export async function waitUntilSafeToExecuteFunction(
538
+ connection: Connection,
539
+ config: ScheduleConfig = {},
540
+ ): Promise<void> {
541
+ await scheduledInstruction(connection, config, async () => {
542
+ // no-op
543
+ return;
544
+ });
545
+ }
546
+
547
+ export interface ScheduleConfig {
548
+ early?: number; // fraction of epoch, default 0.10
549
+ late?: number; // fraction of epoch, default 0.90
550
+ }
551
+
@@ -0,0 +1,17 @@
1
+ {
2
+ "extends": "../../tsconfig.json",
3
+ "compilerOptions": {
4
+ "module": "commonjs",
5
+ "target": "es2019",
6
+ "moduleResolution": "node",
7
+ "types": [
8
+ "node"
9
+ ],
10
+ "isolatedModules": false
11
+ },
12
+ "include": [
13
+ "./**/*",
14
+ "../networks/**/*",
15
+ "../assets/**/*"
16
+ ]
17
+ }
@@ -1,8 +1,9 @@
1
1
  // src/staker/staker.ts
2
2
 
3
- import { ChainID, SolChainID } from '@wireio/core';
3
+ import { ChainID, EvmChainID, SolChainID } from '@wireio/core';
4
4
  import { IStakingClient, StakerConfig } from './types';
5
5
  import { SolanaStakingClient } from '../networks/solana/solana';
6
+ import { EthereumStakingClient } from '../networks/ethereum/ethereum';
6
7
 
7
8
  export class Staker {
8
9
  public selectedChainID?: ChainID;
@@ -31,18 +32,21 @@ export class Staker {
31
32
  if (!config) throw new Error('StakerConfig is required');
32
33
  if (!Array.isArray(config)) config = [config];
33
34
 
35
+ // console.log('STAKER INIT', config);
36
+
34
37
  config.forEach((cfg) => {
35
38
  switch (cfg.network.chainId) {
36
39
  case SolChainID.WireTestnet:
37
40
  this.clients.set(cfg.network.chainId, new SolanaStakingClient(cfg));
38
41
  break;
39
42
 
40
- // case EvmChainID.Sepolia:
41
- // this.clients.set(EvmChainID.Sepolia, new EthereumStakingClient(cfg));
42
- // break;
43
+ case EvmChainID.Hoodi:
44
+ this.clients.set(EvmChainID.Hoodi, new EthereumStakingClient(cfg));
45
+ break;
46
+
43
47
  default:
44
- // console.log(`No staking client available for chain ${cfg.network.chainId}`);
45
- // throw new Error(`Unsupported network curve: ${cfg.network.name}`);
48
+ console.log(`No staking client available for chain ${cfg.network.chainId}`);
49
+ throw new Error(`Unsupported network curve: ${cfg.network.name}`);
46
50
  }
47
51
  });
48
52
 
@@ -2,8 +2,8 @@
2
2
 
3
3
  import { BaseSignerWalletAdapter } from '@solana/wallet-adapter-base';
4
4
  import { PublicKey as SolPubKey } from '@solana/web3.js';
5
- import { ExternalNetwork, PublicKey } from '@wireio/core';
6
- import { ethers } from 'ethers';
5
+ import { ChainID, ExternalNetwork, PublicKey } from '@wireio/core';
6
+ import { BigNumberish, ethers } from 'ethers';
7
7
 
8
8
  export interface IStakingClient {
9
9
  pubKey: PublicKey;
@@ -13,7 +13,7 @@ export interface IStakingClient {
13
13
  deposit(amount: number): Promise<string>;
14
14
 
15
15
  /** Register any untracked LIQ staked tokens */
16
- register(): Promise<string>;
16
+ // register(): Promise<string>;
17
17
 
18
18
  /** Fetch the portfolio for the LIQ stake user */
19
19
  getPortfolio(): Promise<Portfolio>;
@@ -26,18 +26,23 @@ export type StakerConfig = {
26
26
  }
27
27
 
28
28
  export interface Portfolio {
29
- /** Native SOL balance on chain */
29
+ /** Native balance on chain: ETH, SOL */
30
30
  native: BalanceView;
31
- /** Actual liquid SOL balance from ATA */
32
- actual: BalanceView;
33
- /** Tracked liquid SOL balance from distribution program */
34
- tracked: BalanceView;
31
+ /** Actual Liquid balance of LiqETH, LiqSOL*/
32
+ liq: BalanceView;
33
+ /** Outpost Staked balance */
34
+ staked: BalanceView
35
+ /** SOL ONLY!
36
+ * Tracked liqSOL balance from distribution program */
37
+ tracked?: BalanceView;
35
38
  /** Extra PDAs and account addresses */
36
39
  extras?: Record<string, any>;
40
+ /** Chain ID of the network for which this portfolio is from */
41
+ chainID: ChainID;
37
42
  }
38
43
 
39
44
  export type BalanceView = {
40
- amount: bigint; // raw on-chain integer value
45
+ amount: BigNumberish; // raw on-chain integer value
41
46
  decimals: number; // number of decimal places
42
47
  symbol?: string; // optional token symbol identifier
43
48
  ata?: SolPubKey; // associated token account address