@wireio/stake 0.5.0 → 0.5.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.
- package/lib/stake.browser.js +702 -587
- package/lib/stake.browser.js.map +1 -1
- package/lib/stake.d.ts +1165 -208
- package/lib/stake.js +794 -661
- package/lib/stake.js.map +1 -1
- package/lib/stake.m.js +702 -587
- package/lib/stake.m.js.map +1 -1
- package/package.json +1 -1
- package/src/assets/ethereum/ABI/outpost/Aggregator.sol/Aggregator.json +82 -0
- package/src/assets/solana/idl/liqsol_core.json +132 -182
- package/src/assets/solana/types/liqsol_core.ts +132 -182
- package/src/networks/ethereum/clients/pretoken.client.ts +2 -5
- package/src/networks/ethereum/clients/stake.client.ts +2 -5
- package/src/networks/ethereum/contract.ts +13 -13
- package/src/networks/ethereum/ethereum.ts +89 -13
- package/src/networks/ethereum/utils.ts +8 -8
- package/src/networks/solana/clients/deposit.client.ts +25 -7
- package/src/networks/solana/clients/distribution.client.ts +84 -1
- package/src/networks/solana/clients/outpost.client.ts +34 -28
- package/src/networks/solana/constants.ts +0 -3
- package/src/networks/solana/solana.ts +191 -89
- package/src/networks/solana/types.ts +3 -9
- package/src/networks/solana/utils.ts +14 -7
- package/src/staker.ts +1 -1
- package/src/types.ts +14 -10
- package/src/staker/types.ts +0 -62
|
@@ -3,7 +3,9 @@ import {
|
|
|
3
3
|
ComputeBudgetProgram,
|
|
4
4
|
Connection,
|
|
5
5
|
ConnectionConfig,
|
|
6
|
+
PerfSample,
|
|
6
7
|
PublicKey as SolPubKey,
|
|
8
|
+
SystemProgram,
|
|
7
9
|
Transaction,
|
|
8
10
|
TransactionSignature,
|
|
9
11
|
} from '@solana/web3.js';
|
|
@@ -39,15 +41,20 @@ import { TokenClient } from './clients/token.client';
|
|
|
39
41
|
import {
|
|
40
42
|
deriveLiqsolMintPda,
|
|
41
43
|
deriveReservePoolPda,
|
|
44
|
+
deriveStakeMetricsPda,
|
|
42
45
|
deriveVaultPda,
|
|
43
46
|
INDEX_SCALE,
|
|
47
|
+
PAY_RATE_SCALE_FACTOR,
|
|
44
48
|
} from './constants';
|
|
45
49
|
|
|
46
|
-
import { buildSolanaTrancheSnapshot } from './utils';
|
|
47
|
-
import { PayRateEntry, SolanaTransaction } from './types';
|
|
50
|
+
import { buildSolanaTrancheSnapshot, ceilDiv } from './utils';
|
|
51
|
+
import { GlobalConfig, PayRateEntry, SolanaTransaction } from './types';
|
|
52
|
+
import { SolanaProgramService } from './program';
|
|
48
53
|
|
|
49
54
|
const commitment: Commitment = 'confirmed';
|
|
50
55
|
|
|
56
|
+
export const SCALE = new BN('1000000000000');
|
|
57
|
+
|
|
51
58
|
/**
|
|
52
59
|
* Solana implementation of IStakingClient.
|
|
53
60
|
*
|
|
@@ -69,6 +76,7 @@ export class SolanaStakingClient implements IStakingClient {
|
|
|
69
76
|
public leaderboardClient: LeaderboardClient;
|
|
70
77
|
public outpostClient: OutpostClient;
|
|
71
78
|
public tokenClient: TokenClient;
|
|
79
|
+
public program: SolanaProgramService
|
|
72
80
|
|
|
73
81
|
get solPubKey(): SolPubKey {
|
|
74
82
|
if (!this.pubKey) throw new Error('pubKey is undefined');
|
|
@@ -182,6 +190,7 @@ export class SolanaStakingClient implements IStakingClient {
|
|
|
182
190
|
this.leaderboardClient = new LeaderboardClient(this.anchor);
|
|
183
191
|
this.outpostClient = new OutpostClient(this.anchor);
|
|
184
192
|
this.tokenClient = new TokenClient(this.anchor);
|
|
193
|
+
this.program = new SolanaProgramService(this.anchor);
|
|
185
194
|
}
|
|
186
195
|
|
|
187
196
|
// ---------------------------------------------------------------------
|
|
@@ -473,10 +482,112 @@ export class SolanaStakingClient implements IStakingClient {
|
|
|
473
482
|
// READ-ONLY Public Methods
|
|
474
483
|
// ---------------------------------------------------------------------
|
|
475
484
|
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
485
|
+
/**
|
|
486
|
+
* Returns the system APY (percent) for Solana,
|
|
487
|
+
* using compound interest per epoch and a
|
|
488
|
+
* cluster-derived epochs-per-year.
|
|
489
|
+
*/
|
|
490
|
+
async getSystemAPY(): Promise<number> {
|
|
491
|
+
// 1) Per-epoch rate (decimal) from on-chain stakeMetrics
|
|
492
|
+
const ratePerEpoch = await this.getEpochRateDecimalFromProgram();
|
|
493
|
+
console.log('epochRateDecimal', ratePerEpoch);
|
|
494
|
+
|
|
495
|
+
// 2) Live epochs-per-year estimate from cluster
|
|
496
|
+
const epochsPerYear = await this.getEpochsPerYearFromCluster();
|
|
497
|
+
console.log('epochsPerYear', epochsPerYear);
|
|
498
|
+
|
|
499
|
+
// 3) Compound: (1 + r)^N - 1
|
|
500
|
+
const apyDecimal = Math.pow(1 + ratePerEpoch, epochsPerYear) - 1;
|
|
501
|
+
console.log('apyDecimal', apyDecimal);
|
|
502
|
+
|
|
503
|
+
// 4) Convert to percent
|
|
504
|
+
const apyPercent = apyDecimal * 100;
|
|
505
|
+
console.log('apyPercent', apyPercent);
|
|
506
|
+
|
|
507
|
+
return apyPercent;
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
/**
|
|
511
|
+
* Reads the liqsol_core stakeMetrics account and returns the
|
|
512
|
+
* Solana per-epoch system rate as a **decimal** (not BPS),
|
|
513
|
+
* de-scaled using PAY_RATE_SCALE_FACTOR (1e12).
|
|
514
|
+
*/
|
|
515
|
+
private async getEpochRateDecimalFromProgram(): Promise<number> {
|
|
516
|
+
const liqSolCoreProgram = this.program.getProgram('liqsolCore');
|
|
517
|
+
const stakeMetricsPda = deriveStakeMetricsPda();
|
|
518
|
+
const stakeMetrics =
|
|
519
|
+
await liqSolCoreProgram.account.stakeMetrics.fetch(stakeMetricsPda);
|
|
520
|
+
|
|
521
|
+
// solSystemPayRate is stored on-chain with PAY_RATE_SCALE_FACTOR (1e12)
|
|
522
|
+
const raw = BigInt(stakeMetrics.solSystemPayRate.toString());
|
|
523
|
+
|
|
524
|
+
// Convert to JS number in **decimal per epoch** units
|
|
525
|
+
const rateDecimal = Number(raw) / Number(PAY_RATE_SCALE_FACTOR);
|
|
526
|
+
|
|
527
|
+
console.log('solSystemPayRate(raw)', raw.toString());
|
|
528
|
+
console.log('epochRateDecimal(computed)', rateDecimal);
|
|
529
|
+
|
|
530
|
+
return rateDecimal;
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
// Simple cache so we don’t hammer RPC
|
|
534
|
+
private epochsPerYearCache?: { value: number; fetchedAt: number };
|
|
535
|
+
private static readonly EPOCHS_PER_YEAR_TTL_MS = 10 * 60 * 1000; // 10 minutes
|
|
536
|
+
|
|
537
|
+
/**
|
|
538
|
+
* Derive "epochs per year" from the live Solana cluster.
|
|
539
|
+
*
|
|
540
|
+
* Uses:
|
|
541
|
+
* - getRecentPerformanceSamples() to estimate slots/second
|
|
542
|
+
* - getEpochInfo() to read slotsInEpoch
|
|
543
|
+
*/
|
|
544
|
+
private async getEpochsPerYearFromCluster(): Promise<number> {
|
|
545
|
+
const now = Date.now();
|
|
546
|
+
|
|
547
|
+
if (
|
|
548
|
+
this.epochsPerYearCache &&
|
|
549
|
+
now - this.epochsPerYearCache.fetchedAt <
|
|
550
|
+
SolanaStakingClient.EPOCHS_PER_YEAR_TTL_MS
|
|
551
|
+
) {
|
|
552
|
+
return this.epochsPerYearCache.value;
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
const connection = this.anchor.connection;
|
|
556
|
+
|
|
557
|
+
// 1) Estimate slots/second from recent performance samples
|
|
558
|
+
const samples: PerfSample[] = await connection.getRecentPerformanceSamples(
|
|
559
|
+
60,
|
|
560
|
+
);
|
|
561
|
+
if (!samples.length) {
|
|
562
|
+
throw new Error('No performance samples available from cluster');
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
const totalSlots = samples.reduce((acc, s) => acc + s.numSlots, 0);
|
|
566
|
+
const totalSecs = samples.reduce((acc, s) => acc + s.samplePeriodSecs, 0);
|
|
567
|
+
|
|
568
|
+
if (totalSecs === 0) {
|
|
569
|
+
throw new Error(
|
|
570
|
+
'Cluster returned zero samplePeriodSecs in performance samples',
|
|
571
|
+
);
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
const slotsPerSecond = totalSlots / totalSecs;
|
|
575
|
+
|
|
576
|
+
// 2) Slots per epoch from cluster
|
|
577
|
+
const epochInfo = await connection.getEpochInfo(); // finalized commitment by default
|
|
578
|
+
const slotsPerEpoch = epochInfo.slotsInEpoch;
|
|
579
|
+
|
|
580
|
+
const secondsPerEpoch = slotsPerEpoch / slotsPerSecond;
|
|
581
|
+
const secondsPerYear = 365 * 24 * 60 * 60;
|
|
582
|
+
|
|
583
|
+
const epochsPerYear = secondsPerYear / secondsPerEpoch;
|
|
584
|
+
|
|
585
|
+
this.epochsPerYearCache = {
|
|
586
|
+
value: epochsPerYear,
|
|
587
|
+
fetchedAt: now,
|
|
588
|
+
};
|
|
589
|
+
|
|
590
|
+
return epochsPerYear;
|
|
480
591
|
}
|
|
481
592
|
|
|
482
593
|
// ---------------------------------------------
|
|
@@ -501,87 +612,17 @@ export class SolanaStakingClient implements IStakingClient {
|
|
|
501
612
|
return BigInt(0);
|
|
502
613
|
}
|
|
503
614
|
|
|
504
|
-
const [
|
|
505
|
-
this.distributionClient.
|
|
615
|
+
const [avgPayRate, globalConfig]: [BN, GlobalConfig] = await Promise.all([
|
|
616
|
+
this.distributionClient.getAverageScaledPayRate(windowSize),
|
|
506
617
|
this.distributionClient.getGlobalConfig(),
|
|
507
618
|
]);
|
|
508
619
|
|
|
509
|
-
if (!
|
|
510
|
-
return BigInt(0);
|
|
511
|
-
}
|
|
512
|
-
|
|
513
|
-
const entries = history.entries ?? [];
|
|
514
|
-
if (!entries.length) {
|
|
515
|
-
return BigInt(0);
|
|
516
|
-
}
|
|
517
|
-
|
|
518
|
-
const maxEntries =
|
|
519
|
-
typeof history.maxEntries === 'number'
|
|
520
|
-
? history.maxEntries
|
|
521
|
-
: entries.length;
|
|
522
|
-
|
|
523
|
-
const totalEntriesAdded = new BN(
|
|
524
|
-
history.totalEntriesAdded.toString(),
|
|
525
|
-
);
|
|
526
|
-
|
|
527
|
-
const COUNT = Math.max(1, Math.min(windowSize, maxEntries, entries.length));
|
|
528
|
-
|
|
529
|
-
// ---------------------------------------------
|
|
530
|
-
// Walk the circular buffer from "most recent"
|
|
531
|
-
// back through COUNT entries, using the same
|
|
532
|
-
// logic as your coworker script.
|
|
533
|
-
// ---------------------------------------------
|
|
534
|
-
|
|
535
|
-
let avgPayRate = new BN(0);
|
|
536
|
-
|
|
537
|
-
if (COUNT > 0) {
|
|
538
|
-
let idx: number;
|
|
539
|
-
|
|
540
|
-
if (totalEntriesAdded.isZero()) {
|
|
541
|
-
// "Not wrapped yet" case
|
|
542
|
-
idx = 0;
|
|
543
|
-
} else if (history.currentIndex === 0) {
|
|
544
|
-
idx = maxEntries - 1;
|
|
545
|
-
} else {
|
|
546
|
-
idx = history.currentIndex - 1;
|
|
547
|
-
}
|
|
548
|
-
|
|
549
|
-
let sum = new BN(0);
|
|
550
|
-
let valid = 0;
|
|
551
|
-
const zero = new BN(0);
|
|
552
|
-
|
|
553
|
-
for (let i = 0; i < COUNT; i++) {
|
|
554
|
-
const entry: PayRateEntry | undefined = entries[idx];
|
|
555
|
-
if (entry) {
|
|
556
|
-
const rate = new BN(entry.scaledRate.toString());
|
|
557
|
-
if (rate.gt(zero)) {
|
|
558
|
-
sum = sum.add(rate);
|
|
559
|
-
valid += 1;
|
|
560
|
-
}
|
|
561
|
-
}
|
|
562
|
-
|
|
563
|
-
// Same wrap logic as the coworker’s script:
|
|
564
|
-
if (totalEntriesAdded.isZero()) {
|
|
565
|
-
// simple forward iteration if never wrapped
|
|
566
|
-
idx = (idx + 1) % maxEntries;
|
|
567
|
-
} else {
|
|
568
|
-
// walk backwards through the ring buffer
|
|
569
|
-
idx = idx === 0 ? maxEntries - 1 : idx - 1;
|
|
570
|
-
}
|
|
571
|
-
}
|
|
572
|
-
|
|
573
|
-
if (valid > 0) {
|
|
574
|
-
avgPayRate = this.ceilDiv(sum, new BN(valid));
|
|
575
|
-
}
|
|
576
|
-
}
|
|
577
|
-
|
|
578
|
-
// If no valid pay-rate entries, no fee
|
|
579
|
-
if (avgPayRate.isZero()) {
|
|
620
|
+
if (!globalConfig || avgPayRate.isZero()) {
|
|
580
621
|
return BigInt(0);
|
|
581
622
|
}
|
|
582
623
|
|
|
583
624
|
// depositFeeMultiplier may be BN or number depending on your type
|
|
584
|
-
const rawMultiplier
|
|
625
|
+
const rawMultiplier = globalConfig.depositFeeMultiplier;
|
|
585
626
|
const multiplier = new BN(
|
|
586
627
|
rawMultiplier?.toString?.() ?? rawMultiplier ?? 0,
|
|
587
628
|
);
|
|
@@ -592,9 +633,8 @@ export class SolanaStakingClient implements IStakingClient {
|
|
|
592
633
|
const amountBn = new BN(amountLamports.toString());
|
|
593
634
|
|
|
594
635
|
// 10^12 scale (matches scaledRate / index scale)
|
|
595
|
-
const SCALE = new BN('1000000000000');
|
|
596
636
|
|
|
597
|
-
const feeBn =
|
|
637
|
+
const feeBn = ceilDiv(
|
|
598
638
|
avgPayRate.mul(multiplier).mul(amountBn),
|
|
599
639
|
SCALE,
|
|
600
640
|
);
|
|
@@ -642,6 +682,75 @@ export class SolanaStakingClient implements IStakingClient {
|
|
|
642
682
|
});
|
|
643
683
|
}
|
|
644
684
|
|
|
685
|
+
/**
|
|
686
|
+
* Estimate a conservative SOL buffer (lamports) to leave in the wallet
|
|
687
|
+
* so the user can pay fees for the current deposit and at least one
|
|
688
|
+
* more transaction, plus a bit extra for future interactions.
|
|
689
|
+
*
|
|
690
|
+
* Intended usage in UI:
|
|
691
|
+
* const bufferLamports = await client.estimateGasBuffer();
|
|
692
|
+
* const maxSpendable = balanceLamports > bufferLamports
|
|
693
|
+
* ? balanceLamports - bufferLamports
|
|
694
|
+
* : 0n;
|
|
695
|
+
*
|
|
696
|
+
* @param options.txCount How many transactions to cover (default 2)
|
|
697
|
+
* @param options.safetyMultiplier Extra safety multiplier (default 3x)
|
|
698
|
+
* @param options.minBufferLamports Optional override minimum buffer (default ~0.01 SOL)
|
|
699
|
+
*/
|
|
700
|
+
async estimateGasBuffer(options?: {
|
|
701
|
+
txCount?: number;
|
|
702
|
+
safetyMultiplier?: number;
|
|
703
|
+
minBufferLamports?: bigint;
|
|
704
|
+
}): Promise<bigint> {
|
|
705
|
+
this.ensureUser();
|
|
706
|
+
|
|
707
|
+
const payer = this.solPubKey;
|
|
708
|
+
|
|
709
|
+
// 1) Build a small dummy transaction (self-transfer of 0 lamports).
|
|
710
|
+
const dummyIx = SystemProgram.transfer({
|
|
711
|
+
fromPubkey: payer,
|
|
712
|
+
toPubkey: payer,
|
|
713
|
+
lamports: 0,
|
|
714
|
+
});
|
|
715
|
+
|
|
716
|
+
const tx = new Transaction().add(dummyIx);
|
|
717
|
+
const { blockhash } = await this.connection.getLatestBlockhash(commitment);
|
|
718
|
+
tx.recentBlockhash = blockhash;
|
|
719
|
+
tx.feePayer = payer;
|
|
720
|
+
|
|
721
|
+
// 2) Ask the cluster what it would charge in lamports for this tx.
|
|
722
|
+
const message = tx.compileMessage();
|
|
723
|
+
const feeInfo = await this.connection.getFeeForMessage(message, commitment);
|
|
724
|
+
|
|
725
|
+
// Fallback to a conservative default if RPC doesn't give a value.
|
|
726
|
+
const singleTxFeeLamports = BigInt(feeInfo.value ?? 5000);
|
|
727
|
+
|
|
728
|
+
// 3) How many txs do we want to cover?
|
|
729
|
+
// Default: 2 (deposit + one extra operation).
|
|
730
|
+
const txCount = BigInt(options?.txCount ?? 2);
|
|
731
|
+
|
|
732
|
+
// 4) Apply a safety multiplier (default 3x to be very safe).
|
|
733
|
+
const safetyMultiplier = options?.safetyMultiplier ?? 3;
|
|
734
|
+
const safetyScaled = BigInt(Math.round(safetyMultiplier * 100)); // e.g. 300
|
|
735
|
+
|
|
736
|
+
let bufferLamports =
|
|
737
|
+
(singleTxFeeLamports *
|
|
738
|
+
txCount *
|
|
739
|
+
safetyScaled) /
|
|
740
|
+
BigInt(100);
|
|
741
|
+
|
|
742
|
+
// 5) Enforce a minimum buffer (default ~0.01 SOL).
|
|
743
|
+
const defaultMinBufferLamports = BigInt(10_000_000); // 0.01 SOL (1e9 / 100)
|
|
744
|
+
const minBufferLamports =
|
|
745
|
+
options?.minBufferLamports ?? defaultMinBufferLamports;
|
|
746
|
+
|
|
747
|
+
if (bufferLamports < minBufferLamports) {
|
|
748
|
+
bufferLamports = minBufferLamports;
|
|
749
|
+
}
|
|
750
|
+
|
|
751
|
+
return bufferLamports;
|
|
752
|
+
}
|
|
753
|
+
|
|
645
754
|
// ---------------------------------------------------------------------
|
|
646
755
|
// Tx helpers
|
|
647
756
|
// ---------------------------------------------------------------------
|
|
@@ -740,11 +849,4 @@ export class SolanaStakingClient implements IStakingClient {
|
|
|
740
849
|
}
|
|
741
850
|
}
|
|
742
851
|
|
|
743
|
-
private ceilDiv(n: BN, d: BN): BN {
|
|
744
|
-
if (d.isZero()) {
|
|
745
|
-
throw new Error('Division by zero in ceilDiv');
|
|
746
|
-
}
|
|
747
|
-
return n.add(d.subn(1)).div(d);
|
|
748
|
-
}
|
|
749
|
-
|
|
750
852
|
}
|
|
@@ -177,7 +177,7 @@ export interface SharesPreview {
|
|
|
177
177
|
* - NFT withdrawal receipts
|
|
178
178
|
* - Encumbered funds for pending withdrawals
|
|
179
179
|
*/
|
|
180
|
-
export type
|
|
180
|
+
export type GlobalAccount = {
|
|
181
181
|
/** PDA bump */
|
|
182
182
|
bump: number;
|
|
183
183
|
|
|
@@ -210,9 +210,6 @@ export type Global = {
|
|
|
210
210
|
* - `wireState` lifecycle (preLaunch/postLaunch/refund)
|
|
211
211
|
*/
|
|
212
212
|
export type GlobalState = {
|
|
213
|
-
/** Admin authority for pretokens/outpost config */
|
|
214
|
-
admin: PublicKey;
|
|
215
|
-
|
|
216
213
|
/** Deployment timestamp (Unix, seconds) */
|
|
217
214
|
deployedAt: BN;
|
|
218
215
|
|
|
@@ -495,9 +492,6 @@ export type UserPretokenRecord = {
|
|
|
495
492
|
* All price/supply fields use 8-decimal precision (SCALE = 1e8).
|
|
496
493
|
*/
|
|
497
494
|
export type TrancheState = {
|
|
498
|
-
/** Admin authority for tranche parameters */
|
|
499
|
-
admin: PublicKey;
|
|
500
|
-
|
|
501
495
|
/** Current tranche number */
|
|
502
496
|
currentTrancheNumber: BN;
|
|
503
497
|
|
|
@@ -516,8 +510,8 @@ export type TrancheState = {
|
|
|
516
510
|
/** Supply growth in basis points per tranche */
|
|
517
511
|
supplyGrowthBps: number;
|
|
518
512
|
|
|
519
|
-
/** Price growth in
|
|
520
|
-
|
|
513
|
+
/** Price growth in cents per tranche (0.01 USD units) */
|
|
514
|
+
priceGrowthCents: number;
|
|
521
515
|
|
|
522
516
|
/** Minimum valid SOL/USD price for Chainlink validation (8-decimal i128) */
|
|
523
517
|
minPriceUsd: BN;
|
|
@@ -114,7 +114,7 @@ export function buildSolanaTrancheLadder(options: {
|
|
|
114
114
|
currentTrancheSupply: bigint;
|
|
115
115
|
currentPriceUsd: bigint;
|
|
116
116
|
supplyGrowthBps: number;
|
|
117
|
-
|
|
117
|
+
priceGrowthCents: number;
|
|
118
118
|
windowBefore?: number;
|
|
119
119
|
windowAfter?: number;
|
|
120
120
|
}): TrancheLadderItem[] {
|
|
@@ -124,7 +124,7 @@ export function buildSolanaTrancheLadder(options: {
|
|
|
124
124
|
currentTrancheSupply,
|
|
125
125
|
currentPriceUsd,
|
|
126
126
|
supplyGrowthBps,
|
|
127
|
-
|
|
127
|
+
priceGrowthCents,
|
|
128
128
|
windowBefore = 5,
|
|
129
129
|
windowAfter = 5,
|
|
130
130
|
} = options;
|
|
@@ -144,7 +144,7 @@ export function buildSolanaTrancheLadder(options: {
|
|
|
144
144
|
const prevCap = capacity.get(id - 1)!;
|
|
145
145
|
const prevPrice = price.get(id - 1)!;
|
|
146
146
|
capacity.set(id, growOnce(prevCap, supplyGrowthBps));
|
|
147
|
-
price.set(id, growOnce(prevPrice,
|
|
147
|
+
price.set(id, growOnce(prevPrice, priceGrowthCents));
|
|
148
148
|
}
|
|
149
149
|
|
|
150
150
|
// Backward (past tranches)
|
|
@@ -152,7 +152,7 @@ export function buildSolanaTrancheLadder(options: {
|
|
|
152
152
|
const nextCap = capacity.get(id + 1)!;
|
|
153
153
|
const nextPrice = price.get(id + 1)!;
|
|
154
154
|
capacity.set(id, shrinkOnce(nextCap, supplyGrowthBps));
|
|
155
|
-
price.set(id, shrinkOnce(nextPrice,
|
|
155
|
+
price.set(id, shrinkOnce(nextPrice, priceGrowthCents));
|
|
156
156
|
}
|
|
157
157
|
|
|
158
158
|
const ladder: TrancheLadderItem[] = [];
|
|
@@ -210,7 +210,7 @@ export function buildSolanaTrancheSnapshot(options: {
|
|
|
210
210
|
const totalPretokensSold = toBigint(trancheState.totalPretokensSold);
|
|
211
211
|
const currentPriceUsd = toBigint(trancheState.currentTranchePriceUsd);
|
|
212
212
|
const supplyGrowthBps = trancheState.supplyGrowthBps;
|
|
213
|
-
const
|
|
213
|
+
const priceGrowthCents = trancheState.priceGrowthCents;
|
|
214
214
|
|
|
215
215
|
const ladder = buildSolanaTrancheLadder({
|
|
216
216
|
currentTranche,
|
|
@@ -218,7 +218,7 @@ export function buildSolanaTrancheSnapshot(options: {
|
|
|
218
218
|
currentTrancheSupply,
|
|
219
219
|
currentPriceUsd,
|
|
220
220
|
supplyGrowthBps,
|
|
221
|
-
|
|
221
|
+
priceGrowthCents,
|
|
222
222
|
windowBefore: ladderWindowBefore,
|
|
223
223
|
windowAfter: ladderWindowAfter,
|
|
224
224
|
});
|
|
@@ -230,7 +230,7 @@ export function buildSolanaTrancheSnapshot(options: {
|
|
|
230
230
|
currentTranche,
|
|
231
231
|
currentPriceUsd,
|
|
232
232
|
supplyGrowthBps,
|
|
233
|
-
|
|
233
|
+
priceGrowthCents,
|
|
234
234
|
totalPretokensSold,
|
|
235
235
|
currentTrancheSupply,
|
|
236
236
|
initialTrancheSupply,
|
|
@@ -798,4 +798,11 @@ export async function waitUntilSafeToExecuteFunction(
|
|
|
798
798
|
export interface ScheduleConfig {
|
|
799
799
|
early?: number; // fraction of epoch, default 0.10
|
|
800
800
|
late?: number; // fraction of epoch, default 0.90
|
|
801
|
+
}
|
|
802
|
+
|
|
803
|
+
export function ceilDiv(n: BN, d: BN): BN {
|
|
804
|
+
if (d.isZero()) {
|
|
805
|
+
throw new Error('Division by zero in ceilDiv');
|
|
806
|
+
}
|
|
807
|
+
return n.add(d.subn(1)).div(d);
|
|
801
808
|
}
|
package/src/staker.ts
CHANGED
package/src/types.ts
CHANGED
|
@@ -21,7 +21,7 @@ export interface IStakingClient {
|
|
|
21
21
|
buy(amount: bigint): Promise<string>;
|
|
22
22
|
|
|
23
23
|
/** Fetch the complete user portfolio */
|
|
24
|
-
getPortfolio(): Promise<Portfolio>;
|
|
24
|
+
getPortfolio(): Promise<Portfolio | null>;
|
|
25
25
|
|
|
26
26
|
// Estimated total APY for staking yeild
|
|
27
27
|
getSystemAPY(): Promise<number>;
|
|
@@ -45,12 +45,17 @@ export interface IStakingClient {
|
|
|
45
45
|
windowBefore?: number;
|
|
46
46
|
windowAfter?: number;
|
|
47
47
|
}): Promise<TrancheSnapshot | null>;
|
|
48
|
-
|
|
49
|
-
// Estimated total APY for staking yeild
|
|
50
|
-
getSystemAPY(): Promise<number>;
|
|
51
48
|
|
|
52
|
-
|
|
53
|
-
|
|
49
|
+
/**
|
|
50
|
+
* Estimate a conservative ETH(wei) / SOL(lamports) buffer to leave in the wallet
|
|
51
|
+
* so the user can pay fees for the current deposit and at least one
|
|
52
|
+
* more transaction, plus a bit extra for future interactions.
|
|
53
|
+
*/
|
|
54
|
+
estimateGasBuffer(options?: {
|
|
55
|
+
txCount?: number;
|
|
56
|
+
safetyMultiplier?: number;
|
|
57
|
+
minBuffer?: bigint;
|
|
58
|
+
}): Promise<bigint>
|
|
54
59
|
}
|
|
55
60
|
|
|
56
61
|
/**
|
|
@@ -211,10 +216,9 @@ export interface TrancheSnapshot {
|
|
|
211
216
|
/** Current tranche price in USD (1e8 scale) */
|
|
212
217
|
currentPriceUsd: bigint;
|
|
213
218
|
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
priceIncrementUsd: number; // e.g. 200 = +2% per tranche
|
|
219
|
+
supplyGrowthBps: number; // 2.5% growth per tranche
|
|
220
|
+
|
|
221
|
+
priceGrowthCents: number; // $0.02 USD growth per tranche
|
|
218
222
|
|
|
219
223
|
totalPretokensSold: bigint; // total pretokens sold across all tranches (1e8 scale)
|
|
220
224
|
|
package/src/staker/types.ts
DELETED
|
@@ -1,62 +0,0 @@
|
|
|
1
|
-
import { BaseSignerWalletAdapter } from '@solana/wallet-adapter-base';
|
|
2
|
-
import { PublicKey as SolPubKey } from '@solana/web3.js';
|
|
3
|
-
import { ChainID, ExternalNetwork, PublicKey } from '@wireio/core';
|
|
4
|
-
import { ethers } from 'ethers';
|
|
5
|
-
|
|
6
|
-
export interface IStakingClient {
|
|
7
|
-
pubKey: PublicKey;
|
|
8
|
-
network: ExternalNetwork;
|
|
9
|
-
|
|
10
|
-
/** Amount is in the chain's smallest unit (lamports/wei, etc.) */
|
|
11
|
-
deposit(amount: bigint): Promise<string>;
|
|
12
|
-
withdraw(amount: bigint): Promise<string>;
|
|
13
|
-
stake(amount: bigint): Promise<string>;
|
|
14
|
-
unstake(amount: bigint): Promise<string>;
|
|
15
|
-
|
|
16
|
-
buy?(amount: bigint, purchaseAsset: PurchaseAsset): Promise<string>;
|
|
17
|
-
|
|
18
|
-
// REMOVED from shared client, SOLANA ONLY
|
|
19
|
-
/** Register any untracked LIQ staked tokens */
|
|
20
|
-
// register(): Promise<string>;
|
|
21
|
-
|
|
22
|
-
/** Fetch the portfolio for the LIQ stake user */
|
|
23
|
-
getPortfolio(): Promise<Portfolio>;
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
// Enum describing which asset is being used to buy pretoken
|
|
27
|
-
export enum PurchaseAsset {
|
|
28
|
-
SOL = "SOL",
|
|
29
|
-
LIQSOL = "LIQSOL",
|
|
30
|
-
ETH = "ETH",
|
|
31
|
-
LIQETH = "LIQETH",
|
|
32
|
-
YIELD = "YIELD",
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
export type StakerConfig = {
|
|
36
|
-
network: ExternalNetwork;
|
|
37
|
-
provider: BaseSignerWalletAdapter | ethers.providers.Web3Provider;
|
|
38
|
-
pubKey: PublicKey;
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
export interface Portfolio {
|
|
42
|
-
/** Native balance on chain: ETH, SOL */
|
|
43
|
-
native: BalanceView;
|
|
44
|
-
/** Actual Liquid balance of LiqETH, LiqSOL*/
|
|
45
|
-
liq: BalanceView;
|
|
46
|
-
/** Outpost Staked balance */
|
|
47
|
-
staked: BalanceView
|
|
48
|
-
/** SOL ONLY!
|
|
49
|
-
* Tracked liqSOL balance from distribution program */
|
|
50
|
-
tracked?: BalanceView;
|
|
51
|
-
/** Extra PDAs and account addresses */
|
|
52
|
-
extras?: Record<string, any>;
|
|
53
|
-
/** Chain ID of the network for which this portfolio is from */
|
|
54
|
-
chainID: ChainID;
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
export type BalanceView = {
|
|
58
|
-
amount: bigint; // raw on-chain integer value
|
|
59
|
-
decimals: number; // number of decimal places
|
|
60
|
-
symbol?: string; // optional token symbol identifier
|
|
61
|
-
ata?: SolPubKey; // associated token account address
|
|
62
|
-
};
|