@wireio/stake 0.3.1 → 0.4.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 (36) hide show
  1. package/lib/stake.browser.js +12887 -10017
  2. package/lib/stake.browser.js.map +1 -1
  3. package/lib/stake.d.ts +3305 -1364
  4. package/lib/stake.js +16298 -13436
  5. package/lib/stake.js.map +1 -1
  6. package/lib/stake.m.js +12887 -10017
  7. package/lib/stake.m.js.map +1 -1
  8. package/package.json +3 -1
  9. package/src/assets/solana/idl/liqsol_core.json +2327 -887
  10. package/src/assets/solana/idl/liqsol_token.json +1 -1
  11. package/src/assets/solana/idl/transfer_hook.json +192 -0
  12. package/src/assets/solana/idl/validator_leaderboard.json +147 -4
  13. package/src/assets/solana/types/liqsol_core.ts +2327 -887
  14. package/src/assets/solana/types/liqsol_token.ts +1 -1
  15. package/src/assets/solana/types/transfer_hook.ts +198 -0
  16. package/src/assets/solana/types/validator_leaderboard.ts +147 -4
  17. package/src/networks/ethereum/clients/{deposit.client.ts → convert.client.ts} +36 -4
  18. package/src/networks/ethereum/clients/opp.client.ts +390 -0
  19. package/src/networks/ethereum/clients/pretoken.client.ts +88 -49
  20. package/src/networks/ethereum/clients/receipt.client.ts +129 -0
  21. package/src/networks/ethereum/clients/stake.client.ts +1 -148
  22. package/src/networks/ethereum/contract.ts +7 -4
  23. package/src/networks/ethereum/ethereum.ts +44 -70
  24. package/src/networks/ethereum/types.ts +1 -0
  25. package/src/networks/ethereum/utils.ts +1 -1
  26. package/src/networks/solana/clients/deposit.client.ts +154 -8
  27. package/src/networks/solana/clients/distribution.client.ts +72 -291
  28. package/src/networks/solana/clients/leaderboard.client.ts +59 -14
  29. package/src/networks/solana/clients/outpost.client.ts +188 -359
  30. package/src/networks/solana/clients/token.client.ts +85 -100
  31. package/src/networks/solana/constants.ts +155 -64
  32. package/src/networks/solana/solana.ts +273 -154
  33. package/src/networks/solana/types.ts +532 -71
  34. package/src/networks/solana/utils.ts +68 -51
  35. package/src/types.ts +161 -17
  36. package/src/networks/ethereum/clients/liq.client.ts +0 -47
@@ -1,237 +1,173 @@
1
1
  import { AnchorProvider, BN, Program } from '@coral-xyz/anchor';
2
2
  import {
3
- Connection,
4
3
  PublicKey,
5
4
  SystemProgram,
6
- Transaction,
7
5
  TransactionInstruction,
8
6
  } from '@solana/web3.js';
9
7
  import {
10
8
  ASSOCIATED_TOKEN_PROGRAM_ID,
11
9
  TOKEN_2022_PROGRAM_ID,
12
- createAssociatedTokenAccountInstruction,
13
10
  } from '@solana/spl-token';
11
+
12
+ import liqsolCoreIDL from '../../../assets/solana/idl/liqsol_core.json';
13
+ import { LiqsolCore } from '../../../assets/solana/types/liqsol_core';
14
+
14
15
  import {
16
+ OutpostAccounts,
15
17
  buildOutpostAccounts,
16
- getLiqsolCoreProgram,
17
- getErrorMessage,
18
18
  } from '../utils';
19
- import { SolanaProgramService } from '../program';
20
- import { LiqsolCore } from '../../../assets/solana/types/liqsol_core';
21
- import { WalletLike, OutpostWireStateSnapshot, SharesPreview, UserWarrantRecord, TrancheState, WireReceipt } from '../types';
19
+
20
+ import {
21
+ OutpostWireStateSnapshot,
22
+ SharesPreview,
23
+ TrancheState,
24
+ WalletLike,
25
+ } from '../types';
26
+ import { INDEX_SCALE, PROGRAM_IDS } from '../constants';
22
27
 
23
28
  /**
24
- * Minimal, user-flow focused OutpostClient.
25
- * - Read state
26
- * - Build + (optionally) send user instructions
29
+ * OutpostClient
27
30
  *
28
- * No admin/bootstrap/reset helpers by design.
31
+ * Read-only + preview helper for:
32
+ * - Outpost config + per-user pretokens
33
+ * - Tranche ladder snapshot
34
+ * - Pretoken preview math
35
+ *
36
+ * Write helpers:
37
+ * - buildStakeIx → liqsol_core::synd
38
+ * - buildUnstakeIx → liqsol_core::desynd
39
+ *
40
+ * All PDAs / ATAs are sourced via buildOutpostAccounts, which is kept
41
+ * in sync with the on-chain PDAs and capital-staking Wire utils.
29
42
  */
30
43
  export class OutpostClient {
31
- private readonly program: Program<LiqsolCore>;
32
-
33
- // keep consistent with your tests
34
- static readonly INDEX_SCALE = new BN('1000000000000');
44
+ private readonly program: Program<LiqsolCore>;
35
45
 
36
- get connection(): Connection { return this.provider.connection; }
37
- get wallet(): WalletLike { return this.provider.wallet; }
38
-
39
- constructor(private provider: AnchorProvider) {
40
- const svc = new SolanaProgramService(provider);
41
- this.program = svc.getProgram('liqsolCore');
46
+ constructor(
47
+ private readonly provider: AnchorProvider,
48
+ ) {
49
+ this.program = new Program(liqsolCoreIDL as any, provider);
42
50
  }
43
51
 
44
- /**
45
- * Read-only Program helper (dummy wallet), useful if you want to do pure reads
46
- * without requiring a wallet. Kept here as sugar for callers.
47
- */
48
- getReadOnlyProgram(): Program<LiqsolCore> {
49
- return getLiqsolCoreProgram(this.connection);
52
+ get connection() {
53
+ return this.provider.connection;
50
54
  }
51
55
 
52
- // ---------------------------------------------------------------------------
53
- // General-purpose formatting + math (SDK sugar)
54
- // ---------------------------------------------------------------------------
55
-
56
- /**
57
- * Same rounding logic from your tests: ceil(amount * INDEX_SCALE / currentIndex)
58
- */
59
- static tokensToShares(amount: BN, currentIndex: BN): BN {
60
- const numerator = amount.mul(OutpostClient.INDEX_SCALE);
61
- const shares = numerator.div(currentIndex);
62
- const remainder = numerator.mod(currentIndex);
63
- return remainder.eqn(0) ? shares : shares.addn(1);
56
+ get wallet(): WalletLike {
57
+ return this.provider.wallet as unknown as WalletLike;
64
58
  }
65
59
 
66
60
  /**
67
- * UI helper: formats a BN assumed to be scale=1e8 into a decimal string.
68
- * (Matches your test helper; useful for tranche price, USD price, etc.)
61
+ * Expose the raw Program in case callers need to build custom
62
+ * instructions outside this client.
69
63
  */
70
- static formatScale8(value: BN): string {
71
- const decimals = 8;
72
- const valueStr = value.toString();
73
- if (valueStr.length <= decimals) {
74
- const padding = '0'.repeat(decimals - valueStr.length);
75
- return `0.${padding}${valueStr}`;
76
- }
77
- const intPart = valueStr.slice(0, valueStr.length - decimals);
78
- const decPart = valueStr.slice(valueStr.length - decimals);
79
- return `${intPart}.${decPart}`;
80
- }
81
-
82
- /**
83
- * Previews expected shares for a purchase/stake flow that mints shares
84
- * relative to globalState.currentIndex.
85
- */
86
- async previewExpectedShares(params: {
87
- amountLamports: BN;
88
- user?: PublicKey; // optional; defaults to wallet.publicKey
89
- }): Promise<SharesPreview> {
90
- const user = params.user ?? this.wallet.publicKey;
91
- const accounts = await buildOutpostAccounts(this.connection, user);
92
- const globalState = await this.program.account.globalState.fetch(accounts.globalState);
93
- const currentIndex = new BN(globalState.currentIndex.toString());
94
- const expectedShares = OutpostClient.tokensToShares(
95
- params.amountLamports,
96
- currentIndex,
97
- );
98
-
99
- return {
100
- amountLamports: params.amountLamports,
101
- currentIndex,
102
- indexScale: OutpostClient.INDEX_SCALE,
103
- expectedShares,
104
- };
64
+ get liqsolCoreProgram(): Program<LiqsolCore> {
65
+ return this.program;
105
66
  }
106
67
 
107
- // ---------------------------------------------------------------------------
108
- // Account helpers (user-facing, not admin bootstrap)
109
- // ---------------------------------------------------------------------------
110
-
111
68
  /**
112
- * Returns whether the user's liqSOL ATA exists.
113
- * Useful for UX (show "Create token account" prompt).
69
+ * Build all outpost-related PDAs / ATAs for a given user.
70
+ * Defaults to the connected wallet.
114
71
  */
115
- async doesUserAtaExist(user: PublicKey = this.wallet.publicKey): Promise<boolean> {
116
- const accounts = await buildOutpostAccounts(this.connection, user);
117
- const info = await this.connection.getAccountInfo(accounts.userAta);
118
- return !!info;
72
+ async buildAccounts(user?: PublicKey): Promise<OutpostAccounts> {
73
+ const userPk = user ?? this.wallet.publicKey;
74
+ if (!userPk) {
75
+ throw new Error('OutpostClient.buildAccounts: wallet not connected');
76
+ }
77
+ return buildOutpostAccounts(this.connection, userPk);
119
78
  }
120
79
 
121
80
  /**
122
- * Builds an instruction to create the user's liqSOL ATA (Token-2022).
123
- * This is general-purpose sugar for any flow that needs the ATA.
81
+ * Internal helper: get raw token balance (BN) for a given ATA.
124
82
  */
125
- async buildCreateUserAtaIx(user: PublicKey = this.wallet.publicKey): Promise<TransactionInstruction> {
126
- const accounts = await buildOutpostAccounts(this.connection, user);
127
-
128
- return createAssociatedTokenAccountInstruction(
129
- this.wallet.publicKey, // payer
130
- accounts.userAta, // ata
131
- user, // owner
132
- accounts.liqsolMint, // mint
133
- TOKEN_2022_PROGRAM_ID,
134
- ASSOCIATED_TOKEN_PROGRAM_ID,
135
- );
83
+ private async getTokenBalance(ata: PublicKey): Promise<BN> {
84
+ try {
85
+ const bal = await this.connection.getTokenAccountBalance(ata);
86
+ return new BN(bal.value.amount);
87
+ } catch {
88
+ return new BN(0);
89
+ }
136
90
  }
137
91
 
138
92
  /**
139
- * Convenience: ensures ATA exists by returning either [] or [createATAIx].
140
- * (Does NOT send; caller composes tx)
93
+ * Fetch the core "Wire state" for Outpost / pretokens for a given user.
141
94
  */
142
- async maybeBuildCreateUserAtaIx(user: PublicKey = this.wallet.publicKey): Promise<TransactionInstruction[]> {
143
- const accounts = await buildOutpostAccounts(this.connection, user);
144
- const info = await this.connection.getAccountInfo(accounts.userAta);
145
- if (info) return [];
146
- return [await this.buildCreateUserAtaIx(user)];
147
- }
148
-
149
- // ---------------------------------------------------------------------------
150
- // Reads (UI-first)
151
- // ---------------------------------------------------------------------------
95
+ async fetchWireState(user?: PublicKey): Promise<OutpostWireStateSnapshot> {
96
+ const userPk = user ?? this.wallet.publicKey;
97
+ if (!userPk) {
98
+ throw new Error('OutpostClient.fetchWireState: wallet not connected');
99
+ }
152
100
 
153
- async getWireStateSnapshot(user: PublicKey = this.wallet.publicKey): Promise<OutpostWireStateSnapshot> {
154
- const accounts = await buildOutpostAccounts(this.connection, user);
101
+ const pdas = await this.buildAccounts(userPk);
155
102
 
156
103
  const [
157
104
  globalState,
158
- wireReceipt,
105
+ outpostAccount,
159
106
  distributionState,
160
- userWarrantRecord,
107
+ userPretokenRecord,
161
108
  trancheState,
162
109
  ] = await Promise.all([
163
- this.program.account.globalState.fetch(accounts.globalState),
164
- this.program.account.wireReceipt.fetchNullable(accounts.wireReceipt),
165
- this.program.account.distributionState.fetch(accounts.distributionState),
166
- this.program.account.userWarrantRecord.fetchNullable(accounts.userWarrantRecord),
167
- this.program.account.trancheState.fetchNullable(accounts.trancheState),
110
+ this.program.account.globalState.fetch(pdas.globalState),
111
+ this.program.account.outpostAccount.fetchNullable(pdas.outpostAccount),
112
+ this.program.account.distributionState.fetchNullable(pdas.distributionState),
113
+ this.program.account.userPretokenRecord.fetchNullable(pdas.userPretokenRecord),
114
+ this.program.account.trancheState.fetchNullable(pdas.trancheState),
168
115
  ]);
169
116
 
170
- const [
171
- liqsolPoolBalance,
172
- solBucketLamports,
173
- userLiqsolBalance,
174
- ] = await Promise.all([
175
- this.getTokenBalanceSafe(accounts.liqsolPoolAta),
176
- this.connection.getBalance(accounts.solBucket),
177
- this.getTokenBalanceSafe(accounts.userAta),
117
+ const [liqsolPoolBalance, userLiqsolBalance] = await Promise.all([
118
+ this.getTokenBalance(pdas.liqsolPoolAta),
119
+ this.getTokenBalance(pdas.userAta),
178
120
  ]);
179
121
 
180
122
  return {
181
123
  globalState,
124
+ outpostAccount,
182
125
  distributionState,
183
- wireReceipt,
184
126
  trancheState,
185
- userWarrantRecord,
127
+ userPretokenRecord,
186
128
  liqsolPoolBalance,
187
- solBucketLamports,
188
129
  userLiqsolBalance,
189
130
  };
190
131
  }
191
132
 
192
- async getWireReceipt(user: PublicKey = this.wallet.publicKey): Promise<WireReceipt | null> {
193
- const accounts = await buildOutpostAccounts(this.connection, user);
194
- return this.program.account.wireReceipt.fetchNullable(accounts.wireReceipt);
195
- }
196
-
197
- async getTrancheState(): Promise<TrancheState | null> {
198
- const accounts = await buildOutpostAccounts(this.connection, this.wallet.publicKey);
199
- return this.program.account.trancheState.fetchNullable(accounts.trancheState);
200
- }
201
-
202
- async getUserWarrantRecord(user: PublicKey = this.wallet.publicKey): Promise<UserWarrantRecord | null> {
203
- const accounts = await buildOutpostAccounts(this.connection, user);
204
- return this.program.account.userWarrantRecord.fetchNullable(accounts.userWarrantRecord);
205
- }
206
-
207
- // ---------------------------------------------------------------------------
208
- // Instruction builders (source of truth: your test utils)
209
- // ---------------------------------------------------------------------------
133
+ // -------------------------------------------------------------------------
134
+ // Outpost stake / unstake builders (synd / desynd)
135
+ // -------------------------------------------------------------------------
210
136
 
211
137
  /**
212
- * stakeLiqsol(amount)
213
- * (Outpost staking: liqSOL -> warrant pool)
138
+ * Build the `synd` (stake) instruction:
139
+ * user liqSOL ATA liqSOL pool ATA
214
140
  */
215
- async buildStakeLiqsolIx(amountLamports: bigint, userPubKey = this.wallet.publicKey): Promise<TransactionInstruction> {
216
- const a = await buildOutpostAccounts(this.connection, userPubKey);
217
- const amount = new BN(amountLamports.toString());
141
+ async buildStakeIx(
142
+ amountLamports: bigint,
143
+ user?: PublicKey,
144
+ ): Promise<TransactionInstruction> {
145
+ const userPk = user ?? this.wallet.publicKey;
146
+ if (!userPk) {
147
+ throw new Error('OutpostClient.buildStakeIx: wallet not connected');
148
+ }
149
+
150
+ const a = await this.buildAccounts(userPk);
218
151
 
219
152
  return this.program.methods
220
- .stakeLiqsol(amount)
153
+ .synd(new BN(amountLamports.toString()))
221
154
  .accounts({
222
- user: a.user,
155
+ user: userPk,
223
156
  liqsolMint: a.liqsolMint,
224
157
  globalState: a.globalState,
158
+ distributionState: a.distributionState,
225
159
  userAta: a.userAta,
226
160
  poolAuthority: a.poolAuthority,
227
- liqsolPoolAta: a.liqsolPoolAta,
228
- warrantDepositRecord: a.wireReceipt,
229
- liqsolPoolUserRecord: a.poolUserRecord,
230
- distributionState: a.distributionState,
231
- payRateHistory: a.payRateHistory,
232
161
  bucketAuthority: a.bucketAuthority,
233
162
  bucketTokenAccount: a.bucketTokenAccount,
234
- solBucket: a.solBucket,
163
+ bucketUserRecord: a.bucketUserRecord,
164
+ senderUserRecord: a.userUserRecord,
165
+ receiverUserRecord: a.liqsolPoolUserRecord,
166
+ extraAccountMetaList: a.extraAccountMetaList,
167
+ liqsolCoreProgram: PROGRAM_IDS.LIQSOL_CORE,
168
+ transferHookProgram: PROGRAM_IDS.TRANSFER_HOOK,
169
+ liqsolPoolAta: a.liqsolPoolAta,
170
+ outpostAccount: a.outpostAccount,
235
171
  tokenProgram: TOKEN_2022_PROGRAM_ID,
236
172
  associatedTokenProgram: ASSOCIATED_TOKEN_PROGRAM_ID,
237
173
  systemProgram: SystemProgram.programId,
@@ -240,235 +176,128 @@ export class OutpostClient {
240
176
  }
241
177
 
242
178
  /**
243
- * withdrawStake(amount)
244
- * (Outpost unstake: warrant pool -> user liqSOL)
179
+ * Build the `desynd` (unstake) instruction:
180
+ * liqSOL pool ATA user liqSOL ATA
245
181
  */
246
- async buildWithdrawStakeIx(amountLamports: bigint, userPubKey = this.wallet.publicKey): Promise<TransactionInstruction> {
247
- const a = await buildOutpostAccounts(this.connection, userPubKey);
248
- const amount = new BN(amountLamports.toString());
182
+ async buildUnstakeIx(
183
+ amountLamports: bigint,
184
+ user?: PublicKey,
185
+ ): Promise<TransactionInstruction> {
186
+ const userPk = user ?? this.wallet.publicKey;
187
+ if (!userPk) {
188
+ throw new Error('OutpostClient.buildUnstakeIx: wallet not connected');
189
+ }
190
+
191
+ const a = await this.buildAccounts(userPk);
249
192
 
250
193
  return this.program.methods
251
- .withdrawStake(amount)
194
+ .desynd(new BN(amountLamports.toString()))
252
195
  .accounts({
253
- user: a.user,
196
+ user: userPk,
254
197
  liqsolMint: a.liqsolMint,
255
198
  globalState: a.globalState,
199
+ distributionState: a.distributionState,
256
200
  userAta: a.userAta,
257
201
  poolAuthority: a.poolAuthority,
258
- liqsolPoolAta: a.liqsolPoolAta,
259
- warrantDepositRecord: a.wireReceipt,
260
- liqsolPoolUserRecord: a.poolUserRecord,
261
- distributionState: a.distributionState,
262
- payRateHistory: a.payRateHistory,
263
202
  bucketAuthority: a.bucketAuthority,
264
203
  bucketTokenAccount: a.bucketTokenAccount,
265
- solBucket: a.solBucket,
266
- tokenProgram: TOKEN_2022_PROGRAM_ID,
267
- systemProgram: SystemProgram.programId,
268
- })
269
- .instruction();
270
- }
271
-
272
- /**
273
- * purchaseWithLiqsol(amount)
274
- */
275
- async buildPurchaseWithLiqsolIx(amountLamports: bigint, userPubKey = this.wallet.publicKey): Promise<TransactionInstruction> {
276
- const a = await buildOutpostAccounts(this.connection, userPubKey);
277
- const amount = new BN(amountLamports.toString());
278
-
279
- return this.program.methods
280
- .purchaseWithLiqsol(amount)
281
- .accounts({
282
- user: a.user,
283
- liqsolMint: a.liqsolMint,
284
- globalState: a.globalState,
285
- buyerAta: a.userAta,
286
- poolAuthority: a.poolAuthority,
204
+ bucketUserRecord: a.bucketUserRecord,
205
+ senderUserRecord: a.liqsolPoolUserRecord,
206
+ receiverUserRecord: a.userUserRecord,
207
+ extraAccountMetaList: a.extraAccountMetaList,
208
+ liqsolCoreProgram: PROGRAM_IDS.LIQSOL_CORE,
209
+ transferHookProgram: PROGRAM_IDS.TRANSFER_HOOK,
287
210
  liqsolPoolAta: a.liqsolPoolAta,
288
- warrantDepositRecord: a.wireReceipt,
289
- liqsolPoolUserRecord: a.poolUserRecord,
290
- distributionState: a.distributionState,
291
- payRateHistory: a.payRateHistory,
292
- bucketAuthority: a.bucketAuthority,
293
- bucketTokenAccount: a.bucketTokenAccount,
294
- solBucket: a.solBucket,
295
- trancheState: a.trancheState,
296
- userWarrantRecord: a.userWarrantRecord,
297
- chainlinkFeed: a.chainLinkFeed,
298
- chainlinkProgram: a.chainLinkProgram,
211
+ outpostAccount: a.outpostAccount,
299
212
  tokenProgram: TOKEN_2022_PROGRAM_ID,
300
- associatedTokenProgram: ASSOCIATED_TOKEN_PROGRAM_ID,
301
213
  systemProgram: SystemProgram.programId,
302
214
  })
303
215
  .instruction();
304
216
  }
305
217
 
306
- /**
307
- * purchaseWithSol(amount)
308
- */
309
- async buildPurchaseWithSolIx(amountLamports: bigint, userPubKey = this.wallet.publicKey): Promise<TransactionInstruction> {
310
- const a = await buildOutpostAccounts(this.connection, userPubKey);
311
- const amount = new BN(amountLamports.toString());
312
-
313
- return this.program.methods
314
- .purchaseWithSol(amount)
315
- .accounts({
316
- user: a.user,
317
- liqsolMint: a.liqsolMint,
318
- globalState: a.globalState,
319
- poolAuthority: a.poolAuthority,
320
- liqsolPoolAta: a.liqsolPoolAta,
321
- liqsolPoolUserRecord: a.poolUserRecord,
322
- distributionState: a.distributionState,
323
- payRateHistory: a.payRateHistory,
324
- bucketAuthority: a.bucketAuthority,
325
- bucketTokenAccount: a.bucketTokenAccount,
326
- solBucket: a.solBucket,
327
- warrantDepositRecord: a.wireReceipt,
328
- trancheState: a.trancheState,
329
- userWarrantRecord: a.userWarrantRecord,
330
- chainlinkFeed: a.chainLinkFeed,
331
- chainlinkProgram: a.chainLinkProgram,
332
- tokenProgram: TOKEN_2022_PROGRAM_ID,
333
- systemProgram: SystemProgram.programId,
334
- })
335
- .instruction();
336
- }
218
+ // -------------------------------------------------------------------------
219
+ // Pretoken preview helper (unchanged)
220
+ // -------------------------------------------------------------------------
337
221
 
338
222
  /**
339
- * purchaseWarrantsFromYield()
223
+ * Local preview for "how many pretokens would I get for this liqSOL
224
+ * amount at the current tranche price and SOL/USD price?"
225
+ *
226
+ * SCALE = 1e8
227
+ * usdValue = solAmountLamports * solPriceUsd / 1e9
228
+ * pretokens = usdValue * SCALE / pretokenPriceUsd
340
229
  *
341
- * Source of truth: your test util executePurchaseFromYield (note: it passed
342
- * liqsolPoolUserRecord: pdas.userUserRecord, not poolUserRecord).
230
+ * All values are BN integers; solPriceUsd and pretokenPriceUsd are 1e8 scaled.
343
231
  */
344
- async buildPurchaseFromYieldIx(userPubKey = this.wallet.publicKey): Promise<TransactionInstruction> {
345
- const a = await buildOutpostAccounts(this.connection, userPubKey);
232
+ previewPretokens(params: {
233
+ liqsolAmountLamports: BN;
234
+ solPriceUsd: BN; // 1e8 scale
235
+ trancheState: TrancheState;
236
+ }): SharesPreview {
237
+ const { liqsolAmountLamports, solPriceUsd, trancheState } = params;
238
+
239
+ // Current tranche price in USD (1e8 scale)
240
+ const pretokenPriceUsd = new BN(
241
+ trancheState.currentTranchePriceUsd.toString(),
242
+ );
346
243
 
347
- return this.program.methods
348
- .purchaseWarrantsFromYield()
349
- .accounts({
350
- user: a.user,
351
- globalState: a.globalState,
352
- liqsolMint: a.liqsolMint,
353
- poolAuthority: a.poolAuthority,
354
- liqsolPoolAta: a.liqsolPoolAta,
355
- solBucket: a.solBucket,
356
- liqsolPoolUserRecord: a.userUserRecord,
357
- distributionState: a.distributionState,
358
- payRateHistory: a.payRateHistory,
359
- bucketAuthority: a.bucketAuthority,
360
- bucketTokenAccount: a.bucketTokenAccount,
361
- tokenProgram: TOKEN_2022_PROGRAM_ID,
362
- systemProgram: SystemProgram.programId,
363
- trancheState: a.trancheState,
364
- userWarrantRecord: a.userWarrantRecord,
365
- chainlinkFeed: a.chainLinkFeed,
366
- chainlinkProgram: a.chainLinkProgram,
367
- })
368
- .instruction();
369
- }
244
+ const SCALE = new BN('100000000'); // 1e8
370
245
 
371
- // ---------------------------------------------------------------------------
372
- // Optional send helpers (wallet-facing sugar)
373
- // ---------------------------------------------------------------------------
246
+ // usdValue = (lamports * solPriceUsd) / 1e9
247
+ const usdValue = liqsolAmountLamports
248
+ .mul(solPriceUsd)
249
+ .div(new BN(1_000_000_000));
374
250
 
375
- /**
376
- * Builds and sends a single-instruction transaction.
377
- * (Hub can ignore this and use builder methods only.)
378
- */
379
- async sendIx(ix: TransactionInstruction, opts?: {
380
- additionalIxs?: TransactionInstruction[];
381
- }): Promise<string> {
382
- const tx = new Transaction();
383
- if (opts?.additionalIxs?.length) {
384
- tx.add(...opts.additionalIxs);
385
- }
386
- tx.add(ix);
251
+ const estimatedPretokens = usdValue.mul(SCALE).div(pretokenPriceUsd);
387
252
 
388
- try {
389
- const sig = await this.provider.sendAndConfirm(tx, [], { commitment: 'confirmed' });
390
- return sig;
391
- } catch (e: any) {
392
- const msg = getErrorMessage(e);
393
- throw new Error(`OutpostClient sendIx failed: ${msg}`);
394
- }
253
+ return {
254
+ liqsolAmountLamports,
255
+ solPriceUsd,
256
+ pretokenPriceUsd,
257
+ estimatedPretokens,
258
+ };
395
259
  }
396
260
 
397
261
  /**
398
- * Convenience: stake liqSOL and (optionally) create ATA first.
262
+ * Convert liqSOL tokens (lamports) Outpost “shares”, rounding UP
263
+ * when there is any remainder.
264
+ *
265
+ * This matches the on-chain logic and the capital-staking helper:
266
+ *
267
+ * INDEX_SCALE = 1e12
268
+ * numerator = amount * INDEX_SCALE
269
+ * shares = numerator / currentIndex
270
+ * remainder = numerator % currentIndex
271
+ * if remainder > 0 → shares + 1
399
272
  */
400
- async stakeLiqsol(params: {
401
- amount: BN;
402
- user?: PublicKey;
403
- ensureAta?: boolean; // default true
404
- }): Promise<string> {
405
- const user = params.user ?? this.wallet.publicKey;
406
- const pre: TransactionInstruction[] = [];
407
- if (params.ensureAta ?? true) {
408
- pre.push(...(await this.maybeBuildCreateUserAtaIx(user)));
409
- }
410
- const ix = await this.buildStakeLiqsolIx(params.amount, user);
411
- return this.sendIx(ix, { additionalIxs: pre });
412
- }
413
-
414
- async withdrawStake(params: {
415
- amount: BN;
416
- user?: PublicKey;
417
- ensureAta?: boolean; // default true (usually exists, but harmless)
418
- }): Promise<string> {
419
- const user = params.user ?? this.wallet.publicKey;
420
- const pre: TransactionInstruction[] = [];
421
- if (params.ensureAta ?? true) {
422
- pre.push(...(await this.maybeBuildCreateUserAtaIx(user)));
423
- }
424
- const ix = await this.buildWithdrawStakeIx(params.amount, user);
425
- return this.sendIx(ix, { additionalIxs: pre });
426
- }
427
-
428
- async purchaseWithLiqsol(params: {
429
- amount: BN;
430
- user?: PublicKey;
431
- ensureAta?: boolean; // default true
432
- }): Promise<string> {
433
- const user = params.user ?? this.wallet.publicKey;
434
- const pre: TransactionInstruction[] = [];
435
- if (params.ensureAta ?? true) {
436
- pre.push(...(await this.maybeBuildCreateUserAtaIx(user)));
437
- }
438
- const ix = await this.buildPurchaseWithLiqsolIx(params.amount, user);
439
- return this.sendIx(ix, { additionalIxs: pre });
440
- }
441
-
442
- async purchaseWithSol(params: {
443
- amount: BN;
444
- user?: PublicKey;
445
- }): Promise<string> {
446
- const user = params.user ?? this.wallet.publicKey;
447
- const ix = await this.buildPurchaseWithSolIx(params.amount, user);
448
- return this.sendIx(ix);
449
- }
450
-
451
- async purchaseFromYield(params?: {
452
- user?: PublicKey;
453
- }): Promise<string> {
454
- const user = params?.user ?? this.wallet.publicKey;
455
- const ix = await this.buildPurchaseFromYieldIx(user);
456
- return this.sendIx(ix);
273
+ static tokensToShares(amount: BN, currentIndex: BN): BN {
274
+ const numerator = amount.mul(INDEX_SCALE);
275
+ const shares = numerator.div(currentIndex);
276
+ const remainder = numerator.mod(currentIndex);
277
+ return remainder.eqn(0) ? shares : shares.addn(1);
457
278
  }
458
279
 
459
- // ---------------------------------------------------------------------------
460
- // Internals
461
- // ---------------------------------------------------------------------------
462
-
463
280
  /**
464
- * Token balance helper returning BN(0) if ATA missing.
281
+ * Format a 1e8-scaled integer (e.g. USD price, pretokens, etc.)
282
+ * into a fixed 8-decimal string.
283
+ *
284
+ * Examples:
285
+ * 0 -> "0.00000000"
286
+ * 1 -> "0.00000001"
287
+ * 100000000 -> "1.00000000"
288
+ * 123456789 -> "1.23456789"
465
289
  */
466
- private async getTokenBalanceSafe(ata: PublicKey): Promise<BN> {
467
- try {
468
- const bal = await this.connection.getTokenAccountBalance(ata);
469
- return new BN(bal.value.amount);
470
- } catch {
471
- return new BN(0);
290
+ static formatScale8(value: BN): string {
291
+ const decimals = 8;
292
+ const valueStr = value.toString();
293
+
294
+ if (valueStr.length <= decimals) {
295
+ const padding = '0'.repeat(decimals - valueStr.length);
296
+ return `0.${padding}${valueStr}`;
472
297
  }
298
+
299
+ const intPart = valueStr.slice(0, valueStr.length - decimals);
300
+ const decPart = valueStr.slice(valueStr.length - decimals);
301
+ return `${intPart}.${decPart}`;
473
302
  }
474
303
  }