@wireio/stake 0.2.4 → 0.3.0
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 +5991 -4340
- package/lib/stake.browser.js.map +1 -1
- package/lib/stake.d.ts +74 -60
- package/lib/stake.js +9782 -8054
- package/lib/stake.js.map +1 -1
- package/lib/stake.m.js +5991 -4340
- package/lib/stake.m.js.map +1 -1
- package/package.json +1 -1
- package/src/assets/ethereum/ABI/liqEth/BeaconState.sol/BeaconState.dbg.json +4 -0
- package/src/assets/ethereum/ABI/liqEth/BeaconState.sol/BeaconState.json +807 -0
- package/src/assets/ethereum/ABI/liqEth/DepositManager.sol/DepositManager.dbg.json +1 -1
- package/src/assets/ethereum/ABI/liqEth/DepositManager.sol/DepositManager.json +184 -346
- package/src/assets/ethereum/ABI/liqEth/LiqEthAuthority.sol/LiqEthAuthority.dbg.json +4 -0
- package/src/assets/ethereum/ABI/liqEth/LiqEthAuthority.sol/LiqEthAuthority.json +1289 -0
- package/src/assets/ethereum/ABI/liqEth/LiqEthCommon.sol/IAccounting.dbg.json +1 -1
- package/src/assets/ethereum/ABI/liqEth/LiqEthCommon.sol/IDepositContract.dbg.json +1 -1
- package/src/assets/ethereum/ABI/liqEth/LiqEthCommon.sol/IDepositManager.dbg.json +1 -1
- package/src/assets/ethereum/ABI/liqEth/LiqEthCommon.sol/ILiqEthUpgradeable.dbg.json +4 -0
- package/src/assets/ethereum/ABI/liqEth/LiqEthCommon.sol/ILiqEthUpgradeable.json +29 -0
- package/src/assets/ethereum/ABI/liqEth/LiqEthCommon.sol/IRewardsERC20.dbg.json +1 -1
- package/src/assets/ethereum/ABI/liqEth/LiqEthCommon.sol/IRewardsERC20.json +36 -0
- package/src/assets/ethereum/ABI/liqEth/LiqEthCommon.sol/IRewardsERC20Pausable.dbg.json +4 -0
- package/src/assets/ethereum/ABI/liqEth/LiqEthCommon.sol/IRewardsERC20Pausable.json +263 -0
- package/src/assets/ethereum/ABI/liqEth/LiqEthCommon.sol/IStakingModule.dbg.json +1 -1
- package/src/assets/ethereum/ABI/liqEth/LiqEthCommon.sol/IStakingModule.json +226 -0
- package/src/assets/ethereum/ABI/liqEth/LiqEthCommon.sol/IValidatorBalanceVerifier.dbg.json +1 -1
- package/src/assets/ethereum/ABI/liqEth/LiqEthCommon.sol/IValidatorBalanceVerifier.json +59 -0
- package/src/assets/ethereum/ABI/liqEth/LiqEthCommon.sol/IWithdrawalRecord.dbg.json +1 -1
- package/src/assets/ethereum/ABI/liqEth/LiqEthCommon.sol/IWithdrawalRecord.json +14 -12
- package/src/assets/ethereum/ABI/liqEth/LiqEthCommon.sol/LiqEthCommon.dbg.json +1 -1
- package/src/assets/ethereum/ABI/liqEth/LiqEthCommon.sol/LiqEthCommon.json +2 -2
- package/src/assets/ethereum/ABI/liqEth/LiqEthManaged.sol/LiqEthManaged.dbg.json +4 -0
- package/src/assets/ethereum/ABI/liqEth/LiqEthManaged.sol/LiqEthManaged.json +229 -0
- package/src/assets/ethereum/ABI/liqEth/RewardsERC20.sol/RewardsERC20Upgradeable.dbg.json +4 -0
- package/src/assets/ethereum/ABI/liqEth/{RewardsERC20Pausable.sol/RewardsERC20Pausable.json → RewardsERC20.sol/RewardsERC20Upgradeable.json} +140 -78
- package/src/assets/ethereum/ABI/liqEth/RewardsERC20Pausable.sol/RewardsERC20PausableUpgradeable.dbg.json +4 -0
- package/src/assets/ethereum/ABI/liqEth/{RewardsERC20.sol/RewardsERC20.json → RewardsERC20Pausable.sol/RewardsERC20PausableUpgradeable.json} +218 -30
- package/src/assets/ethereum/ABI/liqEth/ValidatorBalanceVerifier.sol/BeaconRoots.dbg.json +1 -1
- package/src/assets/ethereum/ABI/liqEth/ValidatorBalanceVerifier.sol/BeaconRoots.json +2 -2
- package/src/assets/ethereum/ABI/liqEth/ValidatorBalanceVerifier.sol/SSZ.dbg.json +1 -1
- package/src/assets/ethereum/ABI/liqEth/ValidatorBalanceVerifier.sol/SSZ.json +2 -2
- package/src/assets/ethereum/ABI/liqEth/ValidatorBalanceVerifier.sol/SSZExtras.dbg.json +4 -0
- package/src/assets/ethereum/ABI/liqEth/ValidatorBalanceVerifier.sol/SSZExtras.json +10 -0
- package/src/assets/ethereum/ABI/liqEth/ValidatorBalanceVerifier.sol/SSZVec48.dbg.json +4 -0
- package/src/assets/ethereum/ABI/liqEth/ValidatorBalanceVerifier.sol/SSZVec48.json +10 -0
- package/src/assets/ethereum/ABI/liqEth/ValidatorBalanceVerifier.sol/ValidatorBalanceVerifier.dbg.json +1 -1
- package/src/assets/ethereum/ABI/liqEth/ValidatorBalanceVerifier.sol/ValidatorBalanceVerifier.json +121 -55
- package/src/assets/ethereum/ABI/liqEth/Yield.sol/YieldOracle.dbg.json +1 -1
- package/src/assets/ethereum/ABI/liqEth/Yield.sol/YieldOracle.json +836 -273
- package/src/assets/ethereum/ABI/liqEth/accounting.sol/Accounting.dbg.json +1 -1
- package/src/assets/ethereum/ABI/liqEth/accounting.sol/Accounting.json +150 -168
- package/src/assets/ethereum/ABI/liqEth/liqEth.sol/LiqEthToken.dbg.json +1 -1
- package/src/assets/ethereum/ABI/liqEth/liqEth.sol/LiqEthToken.json +301 -186
- package/src/assets/ethereum/ABI/liqEth/stakingModule.sol/StakingModule.dbg.json +1 -1
- package/src/assets/ethereum/ABI/liqEth/stakingModule.sol/StakingModule.json +814 -206
- package/src/assets/ethereum/ABI/liqEth/withdrawalQueue.sol/WithdrawalQueue.dbg.json +1 -1
- package/src/assets/ethereum/ABI/liqEth/withdrawalQueue.sol/WithdrawalQueue.json +244 -198
- package/src/assets/ethereum/ABI/liqEth/withdrawalVault.sol/Uint64BE.dbg.json +1 -1
- package/src/assets/ethereum/ABI/liqEth/withdrawalVault.sol/Uint64BE.json +2 -2
- package/src/assets/ethereum/ABI/liqEth/withdrawalVault.sol/WithdrawalVault.dbg.json +1 -1
- package/src/assets/ethereum/ABI/liqEth/withdrawalVault.sol/WithdrawalVault.json +165 -152
- package/src/assets/ethereum/ABI/outpost/Aggregator.sol/Aggregator.json +82 -0
- package/src/assets/ethereum/ABI/outpost/Depositor.sol/Depositor.dbg.json +1 -1
- package/src/assets/ethereum/ABI/outpost/Depositor.sol/Depositor.json +167 -282
- package/src/assets/ethereum/ABI/outpost/EthUsdPriceConsumer.sol/AggregatorV3Interface.dbg.json +1 -1
- package/src/assets/ethereum/ABI/outpost/EthUsdPriceConsumer.sol/EthUsdPriceConsumer.dbg.json +1 -1
- package/src/assets/ethereum/ABI/outpost/EthUsdPriceConsumer.sol/EthUsdPriceConsumer.json +2 -54
- package/src/assets/ethereum/ABI/outpost/OPP.sol/OPP.dbg.json +1 -1
- package/src/assets/ethereum/ABI/outpost/OPP.sol/OPP.json +26 -8
- package/src/assets/ethereum/ABI/outpost/OPPInbound.sol/OPPInbound.dbg.json +1 -1
- package/src/assets/ethereum/ABI/outpost/OPPInbound.sol/OPPInbound.json +2 -2
- package/src/assets/ethereum/ABI/outpost/Pretoken.sol/Pretoken.dbg.json +4 -0
- package/src/assets/ethereum/ABI/outpost/Pretoken.sol/Pretoken.json +1650 -0
- package/src/assets/ethereum/ABI/outpost/ReceiptNFT.sol/ReceiptNFT.dbg.json +1 -1
- package/src/assets/ethereum/ABI/outpost/ReceiptNFT.sol/ReceiptNFT.json +2 -22
- package/src/assets/ethereum/ABI/outpost/interfaces/IPretoken.sol/IPretoken.dbg.json +4 -0
- package/src/assets/ethereum/ABI/outpost/interfaces/IPretoken.sol/IPretoken.json +29 -0
- package/src/assets/ethereum/ABI/outpost/interfaces/IWarrant.sol/IWarrant.dbg.json +1 -1
- package/src/networks/ethereum/clients/deposit.client.ts +86 -8
- package/src/networks/ethereum/clients/liq.client.ts +47 -0
- package/src/networks/ethereum/clients/pretoken.client.ts +127 -0
- package/src/networks/ethereum/clients/stake.client.ts +87 -24
- package/src/networks/ethereum/contract.ts +34 -48
- package/src/networks/ethereum/ethereum.ts +219 -96
- package/src/networks/ethereum/types.ts +9 -6
- package/src/networks/ethereum/utils.ts +308 -0
- package/src/networks/solana/clients/token.client.ts +0 -1
- package/src/networks/solana/solana.ts +44 -146
- package/src/networks/solana/types.ts +6 -2
- package/src/networks/solana/utils.ts +4 -6
- package/src/staker/types.ts +62 -0
- package/src/types.ts +18 -33
- package/src/assets/ethereum/ABI/liqEth/RewardsERC20.sol/RewardsERC20.dbg.json +0 -4
- package/src/assets/ethereum/ABI/liqEth/RewardsERC20Pausable.sol/RewardsERC20Pausable.dbg.json +0 -4
- package/src/assets/ethereum/ABI/liqEth/Yield.sol/BeaconRoots.dbg.json +0 -4
- package/src/assets/ethereum/ABI/liqEth/Yield.sol/BeaconRoots.json +0 -10
- package/src/assets/ethereum/ABI/liqEth/Yield.sol/SSZ.dbg.json +0 -4
- package/src/assets/ethereum/ABI/liqEth/Yield.sol/SSZ.json +0 -10
- package/src/assets/ethereum/ABI/outpost/Warrant.sol/Warrant.dbg.json +0 -4
- package/src/assets/ethereum/ABI/outpost/Warrant.sol/Warrant.json +0 -1650
|
@@ -0,0 +1,308 @@
|
|
|
1
|
+
|
|
2
|
+
import { ethers } from "ethers";
|
|
3
|
+
import { TrancheLadderItem, TrancheSnapshot } from "../../types";
|
|
4
|
+
import { ChainID } from "@wireio/core";
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
const BPS = BigInt(10_000);
|
|
8
|
+
|
|
9
|
+
export interface CustomContractError {
|
|
10
|
+
name?: string;
|
|
11
|
+
signature?: string;
|
|
12
|
+
args?: any[];
|
|
13
|
+
data?: any;
|
|
14
|
+
method?: string;
|
|
15
|
+
code?: any;
|
|
16
|
+
raw: string;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
export function formatContractErrors(err: any): CustomContractError {
|
|
22
|
+
// Extract custom error details if present
|
|
23
|
+
if (err.errorName && err.errorArgs) {
|
|
24
|
+
const errorObj: CustomContractError = {
|
|
25
|
+
name: err.errorName,
|
|
26
|
+
signature: err.errorSignature,
|
|
27
|
+
args: err.errorArgs.map((arg: any) =>
|
|
28
|
+
arg && arg._isBigNumber ? ethers.BigNumber.from(arg).toString() : arg
|
|
29
|
+
),
|
|
30
|
+
data: err.data,
|
|
31
|
+
method: err.method,
|
|
32
|
+
code: err.code,
|
|
33
|
+
raw: err,
|
|
34
|
+
};
|
|
35
|
+
console.error("Custom contract error:", errorObj);
|
|
36
|
+
return errorObj;
|
|
37
|
+
} else {
|
|
38
|
+
console.error("Contract Error:", err);
|
|
39
|
+
return {
|
|
40
|
+
raw: typeof err === 'string' ? err : (err?.message || String(err))
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Finalize an OPP epoch. Optionally accept a gasLimit override (ethers BigNumberish).
|
|
49
|
+
* If gasLimit is not provided, the method will attempt estimateGas.finalizeEpoch()
|
|
50
|
+
* and pad it (1.2x). If estimateGas fails, a conservative fallback is used.
|
|
51
|
+
*/
|
|
52
|
+
export async function sendOPPFinalize(opp: ethers.Contract, gasLimit?: ethers.BigNumberish) {
|
|
53
|
+
const overrides: any = {};
|
|
54
|
+
try {
|
|
55
|
+
if (gasLimit === undefined) {
|
|
56
|
+
try {
|
|
57
|
+
const estimated = await opp.estimateGas.finalizeEpoch();
|
|
58
|
+
const padded = ethers.BigNumber.from(estimated).mul(12).div(10); // 1.2x
|
|
59
|
+
overrides.gasLimit = padded;
|
|
60
|
+
console.log('sendFinalize: estimated gas', estimated.toString(), 'padded to', overrides.gasLimit.toString());
|
|
61
|
+
} catch (estErr) {
|
|
62
|
+
// estimateGas can throw UNPREDICTABLE_GAS_LIMIT; fall back to a safe default
|
|
63
|
+
console.warn('sendFinalize: estimateGas.finalizeEpoch() failed, falling back to hardcoded gasLimit', estErr);
|
|
64
|
+
overrides.gasLimit = ethers.BigNumber.from(8000000);
|
|
65
|
+
}
|
|
66
|
+
} else {
|
|
67
|
+
overrides.gasLimit = ethers.BigNumber.from(gasLimit);
|
|
68
|
+
console.log('sendFinalize: using provided gasLimit override', overrides.gasLimit.toString());
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const tx = await opp.finalizeEpoch(overrides); // submit tx with overrides
|
|
72
|
+
console.log('sendFinalize tx hash:', tx.hash, 'overrides:', overrides);
|
|
73
|
+
|
|
74
|
+
const receipt = await tx.wait(); // wait for mined
|
|
75
|
+
console.log('sendFinalize tx mined, block:', receipt.blockNumber);
|
|
76
|
+
return receipt;
|
|
77
|
+
} catch (err: any) {
|
|
78
|
+
// Verbose error logging to help debugging reverts / provider issues
|
|
79
|
+
console.error('sendFinalize() failed:', err?.message || err);
|
|
80
|
+
|
|
81
|
+
// Try to extract common raw payload locations used by ethers errors
|
|
82
|
+
const raw = err?.error?.data || err?.data || err?.body || err?.receipt || err?.transaction || err;
|
|
83
|
+
console.error('sendFinalize raw payload:', raw);
|
|
84
|
+
|
|
85
|
+
// Try parsing with contractService if available
|
|
86
|
+
try {
|
|
87
|
+
const parsed = formatContractErrors(raw);
|
|
88
|
+
console.error('sendFinalize parsed error:', parsed.name, parsed.args);
|
|
89
|
+
} catch (parseErr) {
|
|
90
|
+
// Fallback: decode Error(string) ABI encoded revert (0x08c379a0)
|
|
91
|
+
try {
|
|
92
|
+
const hex = (typeof raw === 'string') ? raw : (raw && raw.data) ? raw.data : null;
|
|
93
|
+
if (hex && typeof hex === 'string' && hex.startsWith('0x08c379a0')) {
|
|
94
|
+
const reason = ethers.utils.defaultAbiCoder.decode(['string'], '0x' + hex.slice(10))[0];
|
|
95
|
+
console.error('sendFinalize revert reason:', reason);
|
|
96
|
+
} else {
|
|
97
|
+
console.error('sendFinalize: unable to decode revert payload (not standard Error(string))');
|
|
98
|
+
}
|
|
99
|
+
} catch (fallbackErr) {
|
|
100
|
+
console.error('sendFinalize: fallback decode failed:', fallbackErr);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// If there is a receipt, print it for extra context
|
|
105
|
+
if (err?.receipt) console.error('sendFinalize receipt:', err.receipt);
|
|
106
|
+
if (err?.transaction) console.error('sendFinalize transaction object:', err.transaction);
|
|
107
|
+
|
|
108
|
+
// Re-throw so callers can handle it as well
|
|
109
|
+
throw err;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Apply one forward growth step: value * (BPS + growthBps) / BPS.
|
|
117
|
+
* Simple integer round-half-up.
|
|
118
|
+
*/
|
|
119
|
+
function growOnce(value: bigint, growthBps: number): bigint {
|
|
120
|
+
const g = BigInt(growthBps);
|
|
121
|
+
return (value * (BPS + g) + BPS / BigInt(2)) / BPS;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Apply one backward step: value * BPS / (BPS + growthBps).
|
|
127
|
+
* Also integer round-half-up.
|
|
128
|
+
*/
|
|
129
|
+
function shrinkOnce(value: bigint, growthBps: number): bigint {
|
|
130
|
+
const g = BigInt(growthBps);
|
|
131
|
+
return (value * BPS + (BPS + g) / BigInt(2)) / (BPS + g);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Calculate the full supply for a given tranche using BigInt math.
|
|
137
|
+
* trancheNumber is 1-based (tranche 1 = startSupply)
|
|
138
|
+
*/
|
|
139
|
+
function getTrancheSize(startSupply: bigint, supplyGrowthBps: number, trancheNumber: number): bigint {
|
|
140
|
+
let supply = startSupply;
|
|
141
|
+
for (let i = 0; i < trancheNumber; i++) {
|
|
142
|
+
supply = (supply * (BPS + BigInt(supplyGrowthBps)) + BPS / BigInt(2)) / BPS;
|
|
143
|
+
}
|
|
144
|
+
return supply;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Build a local tranche ladder around the current tranche
|
|
150
|
+
* using only on-chain config + current state.
|
|
151
|
+
*
|
|
152
|
+
*/
|
|
153
|
+
export function buildEthereumTrancheLadder(options: {
|
|
154
|
+
currentTranche: number;
|
|
155
|
+
totalTrancheSupply: bigint,
|
|
156
|
+
initialTrancheSupply: bigint;
|
|
157
|
+
currentTrancheSupply: bigint;
|
|
158
|
+
currentPriceUsd: bigint;
|
|
159
|
+
supplyGrowthBps: number;
|
|
160
|
+
priceGrowthBps: number;
|
|
161
|
+
windowBefore?: number;
|
|
162
|
+
windowAfter?: number;
|
|
163
|
+
}): TrancheLadderItem[] {
|
|
164
|
+
const {
|
|
165
|
+
currentTranche,
|
|
166
|
+
initialTrancheSupply,
|
|
167
|
+
currentTrancheSupply,
|
|
168
|
+
currentPriceUsd,
|
|
169
|
+
supplyGrowthBps,
|
|
170
|
+
priceGrowthBps,
|
|
171
|
+
windowBefore = 5,
|
|
172
|
+
windowAfter = 5,
|
|
173
|
+
} = options;
|
|
174
|
+
|
|
175
|
+
const startId = Math.max(0, currentTranche - windowBefore);
|
|
176
|
+
const endId = currentTranche + windowAfter;
|
|
177
|
+
|
|
178
|
+
//calculate total tranche size (e.g. 60,600 on tranche 2)
|
|
179
|
+
const currentTrancheSize = getTrancheSize(initialTrancheSupply, supplyGrowthBps, currentTranche);
|
|
180
|
+
|
|
181
|
+
const capacity = new Map<number, bigint>();
|
|
182
|
+
const price = new Map<number, bigint>();
|
|
183
|
+
|
|
184
|
+
// Seed current
|
|
185
|
+
capacity.set(currentTranche, currentTrancheSize);
|
|
186
|
+
price.set(currentTranche, currentPriceUsd);
|
|
187
|
+
|
|
188
|
+
// Forward (future tranches)
|
|
189
|
+
for (let id = currentTranche + 1; id <= endId; id++) {
|
|
190
|
+
const prevCap = capacity.get(id - 1)!;
|
|
191
|
+
const prevPrice = price.get(id - 1)!;
|
|
192
|
+
capacity.set(id, growOnce(prevCap, supplyGrowthBps));
|
|
193
|
+
price.set(id, growOnce(prevPrice, priceGrowthBps));
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
// Backward (past tranches)
|
|
197
|
+
for (let id = currentTranche - 1; id >= startId; id--) {
|
|
198
|
+
const nextCap = capacity.get(id + 1)!;
|
|
199
|
+
const nextPrice = price.get(id + 1)!;
|
|
200
|
+
capacity.set(id, shrinkOnce(nextCap, supplyGrowthBps));
|
|
201
|
+
price.set(id, shrinkOnce(nextPrice, priceGrowthBps));
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
const ladder: TrancheLadderItem[] = [];
|
|
205
|
+
for (let id = startId; id <= endId; id++) {
|
|
206
|
+
const cap = capacity.get(id)!;
|
|
207
|
+
let sold: bigint;
|
|
208
|
+
if (id < currentTranche) {
|
|
209
|
+
sold = cap;
|
|
210
|
+
} else if (id === currentTranche) {
|
|
211
|
+
sold = cap - currentTrancheSupply;
|
|
212
|
+
} else {
|
|
213
|
+
sold = BigInt(0);
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
ladder.push({
|
|
217
|
+
id,
|
|
218
|
+
capacity: cap,
|
|
219
|
+
sold,
|
|
220
|
+
remaining: cap - sold,
|
|
221
|
+
priceUsd: price.get(id)!,
|
|
222
|
+
});
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
return ladder;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
|
|
229
|
+
/**
|
|
230
|
+
* Turn raw liqsol_core accounts into a chain-agnostic TrancheSnapshot for SOL.
|
|
231
|
+
* All math stays here; TokenClient just wires accounts + connection.
|
|
232
|
+
*/
|
|
233
|
+
export async function buildEthereumTrancheSnapshot(options: {
|
|
234
|
+
chainID: ChainID;
|
|
235
|
+
totalSharesBn;
|
|
236
|
+
indexBn;
|
|
237
|
+
trancheNumberBn;
|
|
238
|
+
currentTrancheSupply;
|
|
239
|
+
tranchePriceWadBn;
|
|
240
|
+
totalTrancheSupply;
|
|
241
|
+
initialTrancheSupply;
|
|
242
|
+
supplyGrowthBps;
|
|
243
|
+
priceGrowthBps;
|
|
244
|
+
minPriceUsd;
|
|
245
|
+
maxPriceUsd;
|
|
246
|
+
|
|
247
|
+
ethPriceUsd?: bigint;
|
|
248
|
+
nativePriceTimestamp?: number;
|
|
249
|
+
ladderWindowBefore?: number;
|
|
250
|
+
ladderWindowAfter?: number;
|
|
251
|
+
}): Promise<TrancheSnapshot> {
|
|
252
|
+
const {
|
|
253
|
+
chainID,
|
|
254
|
+
ethPriceUsd,
|
|
255
|
+
nativePriceTimestamp,
|
|
256
|
+
ladderWindowBefore,
|
|
257
|
+
ladderWindowAfter,
|
|
258
|
+
|
|
259
|
+
totalSharesBn,
|
|
260
|
+
indexBn,
|
|
261
|
+
trancheNumberBn,
|
|
262
|
+
currentTrancheSupply,
|
|
263
|
+
tranchePriceWadBn,
|
|
264
|
+
totalTrancheSupply,
|
|
265
|
+
initialTrancheSupply,
|
|
266
|
+
supplyGrowthBps,
|
|
267
|
+
priceGrowthBps,
|
|
268
|
+
minPriceUsd,
|
|
269
|
+
maxPriceUsd,
|
|
270
|
+
} = options;
|
|
271
|
+
|
|
272
|
+
|
|
273
|
+
// convert default BigNumber to bigint for hub to handle, and partially convert from 1e18 to 1e8 for the hub
|
|
274
|
+
const totalShares = BigInt(totalSharesBn.toString()) / BigInt(1e10);
|
|
275
|
+
const currentIndex = BigInt(indexBn.toString()); // RAY (1e27)
|
|
276
|
+
const currentTranche = Number(trancheNumberBn.toString());
|
|
277
|
+
const currentPriceUsd = BigInt(tranchePriceWadBn.toString()) / BigInt(1e10); // 1e18 WAD
|
|
278
|
+
|
|
279
|
+
|
|
280
|
+
const ladder = buildEthereumTrancheLadder({
|
|
281
|
+
currentTranche,
|
|
282
|
+
totalTrancheSupply,
|
|
283
|
+
initialTrancheSupply,
|
|
284
|
+
currentTrancheSupply,
|
|
285
|
+
currentPriceUsd,
|
|
286
|
+
supplyGrowthBps,
|
|
287
|
+
priceGrowthBps,
|
|
288
|
+
windowBefore: ladderWindowBefore,
|
|
289
|
+
windowAfter: ladderWindowAfter,
|
|
290
|
+
});
|
|
291
|
+
|
|
292
|
+
|
|
293
|
+
return {
|
|
294
|
+
chainID,
|
|
295
|
+
currentIndex,
|
|
296
|
+
totalShares,
|
|
297
|
+
currentTranche,
|
|
298
|
+
currentPriceUsd,
|
|
299
|
+
supplyGrowthBps,
|
|
300
|
+
priceGrowthBps,
|
|
301
|
+
currentTrancheSupply,
|
|
302
|
+
initialTrancheSupply,
|
|
303
|
+
totalPretokensSold: totalTrancheSupply,
|
|
304
|
+
nativePriceUsd: ethPriceUsd,
|
|
305
|
+
nativePriceTimestamp,
|
|
306
|
+
ladder,
|
|
307
|
+
};
|
|
308
|
+
}
|
|
@@ -20,7 +20,6 @@ import {
|
|
|
20
20
|
import { derivePriceHistoryPda } from '../constants';
|
|
21
21
|
import type { } from '../types';
|
|
22
22
|
import { ChainID, SolChainID } from '@wireio/core';
|
|
23
|
-
import { PurchaseAsset, PurchaseQuote, TrancheSnapshot } from '../../../types';
|
|
24
23
|
|
|
25
24
|
export class TokenClient {
|
|
26
25
|
private readonly program: Program<LiqsolCore>;
|
|
@@ -27,8 +27,6 @@ import {
|
|
|
27
27
|
import {
|
|
28
28
|
IStakingClient,
|
|
29
29
|
Portfolio,
|
|
30
|
-
PurchaseAsset,
|
|
31
|
-
PurchaseQuote,
|
|
32
30
|
StakerConfig,
|
|
33
31
|
TrancheSnapshot,
|
|
34
32
|
} from '../../types';
|
|
@@ -62,7 +60,7 @@ const commitment: Commitment = 'confirmed';
|
|
|
62
60
|
* This class composes lower-level clients; it does not know about UI.
|
|
63
61
|
*/
|
|
64
62
|
export class SolanaStakingClient implements IStakingClient {
|
|
65
|
-
public pubKey
|
|
63
|
+
public pubKey?: PublicKey;
|
|
66
64
|
public connection: Connection;
|
|
67
65
|
public anchor: AnchorProvider;
|
|
68
66
|
|
|
@@ -73,6 +71,7 @@ export class SolanaStakingClient implements IStakingClient {
|
|
|
73
71
|
private tokenClient: TokenClient;
|
|
74
72
|
|
|
75
73
|
get solPubKey(): SolPubKey {
|
|
74
|
+
if (!this.pubKey) throw new Error('pubKey is undefined');
|
|
76
75
|
return new SolPubKey(this.pubKey.data.array);
|
|
77
76
|
}
|
|
78
77
|
|
|
@@ -82,13 +81,17 @@ export class SolanaStakingClient implements IStakingClient {
|
|
|
82
81
|
|
|
83
82
|
constructor(private config: StakerConfig) {
|
|
84
83
|
const adapter = config.provider as BaseSignerWalletAdapter;
|
|
85
|
-
if (!adapter?.publicKey) throw new Error('Solana wallet adapter not connected');
|
|
84
|
+
// if (!adapter?.publicKey) throw new Error('Solana wallet adapter not connected');
|
|
86
85
|
if (!config.network.rpcUrls.length) throw new Error('No RPC URLs provided');
|
|
87
|
-
|
|
88
|
-
const publicKey = adapter.publicKey
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
86
|
+
|
|
87
|
+
const publicKey = adapter.publicKey || new SolPubKey(new Uint8Array(32))
|
|
88
|
+
if (config.pubKey){
|
|
89
|
+
// If pubKey provided, ensure it matches the adapter's pubkey
|
|
90
|
+
const wirePub = new PublicKey(KeyType.ED, publicKey.toBytes());
|
|
91
|
+
if (!wirePub.equals(config.pubKey))
|
|
92
|
+
throw new Error("Passed-in pubKey doesn't match adapter.publicKey");
|
|
93
|
+
// All good; assign it
|
|
94
|
+
this.pubKey = wirePub;
|
|
92
95
|
}
|
|
93
96
|
|
|
94
97
|
const opts: ConnectionConfig = { commitment };
|
|
@@ -111,7 +114,6 @@ export class SolanaStakingClient implements IStakingClient {
|
|
|
111
114
|
},
|
|
112
115
|
};
|
|
113
116
|
|
|
114
|
-
this.pubKey = wirePub;
|
|
115
117
|
this.connection = new Connection(config.network.rpcUrls[0], opts);
|
|
116
118
|
this.anchor = new AnchorProvider(this.connection, anchorWallet, { commitment });
|
|
117
119
|
|
|
@@ -120,7 +122,6 @@ export class SolanaStakingClient implements IStakingClient {
|
|
|
120
122
|
this.leaderboardClient = new LeaderboardClient(this.anchor);
|
|
121
123
|
this.outpostClient = new OutpostClient(this.anchor);
|
|
122
124
|
this.tokenClient = new TokenClient(this.anchor);
|
|
123
|
-
this.tokenClient = new TokenClient(this.anchor);
|
|
124
125
|
}
|
|
125
126
|
|
|
126
127
|
// ---------------------------------------------------------------------
|
|
@@ -132,6 +133,7 @@ export class SolanaStakingClient implements IStakingClient {
|
|
|
132
133
|
* Handles tx build, sign, send, and confirmation.
|
|
133
134
|
*/
|
|
134
135
|
async deposit(amountLamports: bigint): Promise<string> {
|
|
136
|
+
this.ensureWriteAccess();
|
|
135
137
|
if (amountLamports <= BigInt(0)) {
|
|
136
138
|
throw new Error('Deposit amount must be greater than zero.');
|
|
137
139
|
}
|
|
@@ -152,6 +154,7 @@ export class SolanaStakingClient implements IStakingClient {
|
|
|
152
154
|
* NOTE: placeholder until a withdraw flow is implemented in DepositClient.
|
|
153
155
|
*/
|
|
154
156
|
async withdraw(_amountLamports: bigint): Promise<string> {
|
|
157
|
+
this.ensureWriteAccess();
|
|
155
158
|
throw new Error('Withdraw method not yet implemented.');
|
|
156
159
|
}
|
|
157
160
|
|
|
@@ -160,6 +163,7 @@ export class SolanaStakingClient implements IStakingClient {
|
|
|
160
163
|
* Ensures user ATA exists, then stakes via outpostClient.
|
|
161
164
|
*/
|
|
162
165
|
async stake(amountLamports: bigint): Promise<string> {
|
|
166
|
+
this.ensureWriteAccess();
|
|
163
167
|
if (amountLamports <= BigInt(0)) {
|
|
164
168
|
throw new Error('Stake amount must be greater than zero.');
|
|
165
169
|
}
|
|
@@ -180,6 +184,7 @@ export class SolanaStakingClient implements IStakingClient {
|
|
|
180
184
|
* Mirrors stake() but calls withdrawStake.
|
|
181
185
|
*/
|
|
182
186
|
async unstake(amountLamports: bigint): Promise<string> {
|
|
187
|
+
this.ensureWriteAccess();
|
|
183
188
|
if (amountLamports <= BigInt(0)) {
|
|
184
189
|
throw new Error('Unstake amount must be greater than zero.');
|
|
185
190
|
}
|
|
@@ -204,56 +209,17 @@ export class SolanaStakingClient implements IStakingClient {
|
|
|
204
209
|
*
|
|
205
210
|
* ETH / LIQETH are not valid on Solana.
|
|
206
211
|
*/
|
|
207
|
-
async buy(amountLamports: bigint
|
|
212
|
+
async buy(amountLamports: bigint): Promise<string> {
|
|
213
|
+
this.ensureWriteAccess();
|
|
214
|
+
if (!amountLamports || amountLamports <= BigInt(0))
|
|
215
|
+
throw new Error('liqSOL pretoken purchase requires a positive amount.');
|
|
216
|
+
|
|
208
217
|
const user = this.solPubKey;
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
if (!amountLamports || amountLamports <= BigInt(0)) {
|
|
215
|
-
throw new Error('SOL pretoken purchase requires a positive amount.');
|
|
216
|
-
}
|
|
217
|
-
ix = await this.tokenClient.buildPurchaseWithSolIx(
|
|
218
|
-
amountLamports,
|
|
219
|
-
user,
|
|
220
|
-
);
|
|
221
|
-
break;
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
case PurchaseAsset.LIQSOL: {
|
|
225
|
-
if (!amountLamports || amountLamports <= BigInt(0)) {
|
|
226
|
-
throw new Error(
|
|
227
|
-
'liqSOL pretoken purchase requires a positive amount.',
|
|
228
|
-
);
|
|
229
|
-
}
|
|
230
|
-
preIxs = await this.outpostClient.maybeBuildCreateUserAtaIx(user);
|
|
231
|
-
ix = await this.tokenClient.buildPurchaseWithLiqsolIx(
|
|
232
|
-
amountLamports,
|
|
233
|
-
user,
|
|
234
|
-
);
|
|
235
|
-
break;
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
case PurchaseAsset.YIELD: {
|
|
239
|
-
// Amount is ignored by the on-chain program; it consumes tracked yield.
|
|
240
|
-
ix = await this.tokenClient.buildPurchaseFromYieldIx(user);
|
|
241
|
-
break;
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
case PurchaseAsset.ETH:
|
|
245
|
-
case PurchaseAsset.LIQETH: {
|
|
246
|
-
throw new Error(
|
|
247
|
-
'ETH / LIQETH pretoken purchases are not supported on Solana.',
|
|
248
|
-
);
|
|
249
|
-
}
|
|
250
|
-
|
|
251
|
-
default:
|
|
252
|
-
throw new Error(`Unsupported pretoken purchase asset: ${String(
|
|
253
|
-
purchaseAsset,
|
|
254
|
-
)}`);
|
|
255
|
-
}
|
|
256
|
-
|
|
218
|
+
const preIxs = await this.outpostClient.maybeBuildCreateUserAtaIx(user);
|
|
219
|
+
const ix = await this.tokenClient.buildPurchaseWithLiqsolIx(
|
|
220
|
+
amountLamports,
|
|
221
|
+
user,
|
|
222
|
+
);
|
|
257
223
|
const tx = new Transaction().add(...preIxs, ix);
|
|
258
224
|
const prepared = await this.prepareTx(tx);
|
|
259
225
|
const signed = await this.signTransaction(prepared.tx);
|
|
@@ -270,6 +236,8 @@ export class SolanaStakingClient implements IStakingClient {
|
|
|
270
236
|
* - extras: useful internal addresses and raw state for debugging/UX
|
|
271
237
|
*/
|
|
272
238
|
async getPortfolio(): Promise<Portfolio> {
|
|
239
|
+
if (!this.pubKey) throw new Error('User pubKey is undefined');
|
|
240
|
+
|
|
273
241
|
const user = this.solPubKey;
|
|
274
242
|
|
|
275
243
|
const reservePoolPDA = deriveReservePoolPda();
|
|
@@ -313,7 +281,7 @@ export class SolanaStakingClient implements IStakingClient {
|
|
|
313
281
|
wireReceipt?.stakedLiqsol?.toString() ?? '0';
|
|
314
282
|
|
|
315
283
|
const wireSharesStr =
|
|
316
|
-
userWarrantRecord?.
|
|
284
|
+
userWarrantRecord?.totalPretokensPurchased?.toString() ?? '0';
|
|
317
285
|
|
|
318
286
|
return {
|
|
319
287
|
native: {
|
|
@@ -369,6 +337,8 @@ export class SolanaStakingClient implements IStakingClient {
|
|
|
369
337
|
* windowBefore/windowAfter control how many ladder rows we precompute
|
|
370
338
|
* around the current tranche for UI, but you can pass nothing if you
|
|
371
339
|
* only need current tranche info.
|
|
340
|
+
*
|
|
341
|
+
* READ-ONLY allowed
|
|
372
342
|
*/
|
|
373
343
|
async getTrancheSnapshot(options?: {
|
|
374
344
|
chainID?: ChainID;
|
|
@@ -402,95 +372,12 @@ export class SolanaStakingClient implements IStakingClient {
|
|
|
402
372
|
});
|
|
403
373
|
}
|
|
404
374
|
|
|
405
|
-
/**
|
|
406
|
-
* Approximate prelaunch WIRE quote for a given amount & asset.
|
|
407
|
-
*
|
|
408
|
-
* Uses TrancheSnapshot + SOL/USD price for:
|
|
409
|
-
* - SOL: amount is lamports
|
|
410
|
-
* - LIQSOL: amount is liqSOL base units (decimals = 9)
|
|
411
|
-
* - YIELD: amount is treated as SOL lamports-equivalent of yield
|
|
412
|
-
*
|
|
413
|
-
* NOTE: On-chain rounding may differ slightly (this is UI-only).
|
|
414
|
-
*/
|
|
415
|
-
async getBuyQuote(
|
|
416
|
-
amount: bigint,
|
|
417
|
-
asset: PurchaseAsset,
|
|
418
|
-
opts?: { chainID?: ChainID },
|
|
419
|
-
): Promise<PurchaseQuote> {
|
|
420
|
-
// For non-YIELD purchases we require a positive amount.
|
|
421
|
-
if (asset !== PurchaseAsset.YIELD && amount <= BigInt(0)) {
|
|
422
|
-
throw new Error('amount must be > 0 for non-YIELD purchases');
|
|
423
|
-
}
|
|
424
|
-
|
|
425
|
-
const snapshot = await this.getTrancheSnapshot({
|
|
426
|
-
chainID: opts?.chainID,
|
|
427
|
-
});
|
|
428
|
-
|
|
429
|
-
const wirePriceUsd = snapshot.currentPriceUsd; // 1e8
|
|
430
|
-
const solPriceUsd = snapshot.nativePriceUsd; // 1e8
|
|
431
|
-
|
|
432
|
-
if (!wirePriceUsd || wirePriceUsd <= BigInt(0)) {
|
|
433
|
-
throw new Error('Invalid WIRE price in tranche snapshot');
|
|
434
|
-
}
|
|
435
|
-
if (!solPriceUsd || solPriceUsd <= BigInt(0)) {
|
|
436
|
-
throw new Error('No SOL/USD price available');
|
|
437
|
-
}
|
|
438
|
-
|
|
439
|
-
const ONE_E9 = BigInt(LAMPORTS_PER_SOL); // 1e9
|
|
440
|
-
const ONE_E8 = BigInt(100_000_000); // 1e8
|
|
441
|
-
|
|
442
|
-
let notionalUsd: bigint;
|
|
443
|
-
|
|
444
|
-
switch (asset) {
|
|
445
|
-
case PurchaseAsset.SOL: {
|
|
446
|
-
// lamports * solPriceUsd / 1e9 → 1e8 USD
|
|
447
|
-
notionalUsd = (amount * solPriceUsd) / ONE_E9;
|
|
448
|
-
break;
|
|
449
|
-
}
|
|
450
|
-
|
|
451
|
-
case PurchaseAsset.LIQSOL: {
|
|
452
|
-
// liqSOL also uses 9 decimals; use same conversion.
|
|
453
|
-
notionalUsd = (amount * solPriceUsd) / ONE_E9;
|
|
454
|
-
break;
|
|
455
|
-
}
|
|
456
|
-
|
|
457
|
-
case PurchaseAsset.YIELD: {
|
|
458
|
-
// Treat amount as lamports-equivalent of SOL yield (UI convention).
|
|
459
|
-
notionalUsd = (amount * solPriceUsd) / ONE_E9;
|
|
460
|
-
break;
|
|
461
|
-
}
|
|
462
|
-
|
|
463
|
-
case PurchaseAsset.ETH:
|
|
464
|
-
case PurchaseAsset.LIQETH:
|
|
465
|
-
throw new Error('getBuyQuote for ETH/LIQETH is not supported on Solana');
|
|
466
|
-
|
|
467
|
-
default:
|
|
468
|
-
throw new Error(`Unsupported purchase asset: ${String(asset)}`);
|
|
469
|
-
}
|
|
470
|
-
|
|
471
|
-
// WIRE shares (1e8) = (notionalUsd * 1e8) / wirePriceUsd
|
|
472
|
-
// Add a small bias to avoid truncating to 0 on tiny buys.
|
|
473
|
-
const numerator = notionalUsd * ONE_E8;
|
|
474
|
-
const wireShares =
|
|
475
|
-
numerator === BigInt(0)
|
|
476
|
-
? BigInt(0)
|
|
477
|
-
: (numerator + wirePriceUsd - BigInt(1)) / wirePriceUsd;
|
|
478
|
-
|
|
479
|
-
return {
|
|
480
|
-
purchaseAsset: asset,
|
|
481
|
-
amountIn: amount,
|
|
482
|
-
wireShares,
|
|
483
|
-
wireDecimals: 8,
|
|
484
|
-
wirePriceUsd,
|
|
485
|
-
notionalUsd,
|
|
486
|
-
};
|
|
487
|
-
}
|
|
488
|
-
|
|
489
375
|
/**
|
|
490
376
|
* Convenience helper to fetch the distribution userRecord for the current user.
|
|
491
377
|
* Used by balance-correction flows and debugging.
|
|
492
378
|
*/
|
|
493
379
|
async getUserRecord() {
|
|
380
|
+
if (!this.pubKey) throw new Error('User pubKey is undefined');
|
|
494
381
|
return this.distributionClient.getUserRecord(this.solPubKey);
|
|
495
382
|
}
|
|
496
383
|
|
|
@@ -500,6 +387,7 @@ export class SolanaStakingClient implements IStakingClient {
|
|
|
500
387
|
* - signs and sends the transaction if it can succeed
|
|
501
388
|
*/
|
|
502
389
|
async correctBalance(amount?: bigint): Promise<string> {
|
|
390
|
+
this.ensureWriteAccess();
|
|
503
391
|
const build = await this.distributionClient.buildCorrectRegisterTx({ amount });
|
|
504
392
|
if (!build.canSucceed || !build.transaction) {
|
|
505
393
|
throw new Error(build.reason ?? 'Unable to build Correct&Register transaction');
|
|
@@ -528,7 +416,8 @@ export class SolanaStakingClient implements IStakingClient {
|
|
|
528
416
|
signed: SolanaTransaction,
|
|
529
417
|
ctx: { blockhash: string; lastValidBlockHeight: number },
|
|
530
418
|
): Promise<string> {
|
|
531
|
-
|
|
419
|
+
this.ensureWriteAccess();
|
|
420
|
+
const signature = await this.connection.sendRawTransaction(
|
|
532
421
|
signed.serialize(),
|
|
533
422
|
{
|
|
534
423
|
skipPreflight: false,
|
|
@@ -557,6 +446,7 @@ export class SolanaStakingClient implements IStakingClient {
|
|
|
557
446
|
* Sign a single Solana transaction using the connected wallet adapter.
|
|
558
447
|
*/
|
|
559
448
|
async signTransaction(tx: SolanaTransaction): Promise<SolanaTransaction> {
|
|
449
|
+
this.ensureWriteAccess();
|
|
560
450
|
return this.anchor.wallet.signTransaction(tx);
|
|
561
451
|
}
|
|
562
452
|
|
|
@@ -565,6 +455,7 @@ export class SolanaStakingClient implements IStakingClient {
|
|
|
565
455
|
* prepared and signed the transaction.
|
|
566
456
|
*/
|
|
567
457
|
async sendTransaction(signed: SolanaTransaction): Promise<TransactionSignature> {
|
|
458
|
+
this.ensureWriteAccess();
|
|
568
459
|
return this.anchor.sendAndConfirm(signed);
|
|
569
460
|
}
|
|
570
461
|
|
|
@@ -581,4 +472,11 @@ export class SolanaStakingClient implements IStakingClient {
|
|
|
581
472
|
tx.feePayer = this.solPubKey;
|
|
582
473
|
return { tx, blockhash, lastValidBlockHeight };
|
|
583
474
|
}
|
|
475
|
+
|
|
476
|
+
ensureWriteAccess() {
|
|
477
|
+
if (!this.pubKey || !this.anchor.wallet.publicKey)
|
|
478
|
+
throw new Error('User Authorization required: pubKey is undefined');
|
|
479
|
+
if (this.solPubKey.toBase58() !== this.anchor.wallet.publicKey.toBase58())
|
|
480
|
+
throw new Error('Write access requires connected wallet to match pubKey');
|
|
481
|
+
}
|
|
584
482
|
}
|
|
@@ -99,7 +99,7 @@ export type GlobalState = {
|
|
|
99
99
|
export type UserWarrantRecord = {
|
|
100
100
|
user: PublicKey;
|
|
101
101
|
totalSolDeposited: BN;
|
|
102
|
-
|
|
102
|
+
totalPretokensPurchased: BN;
|
|
103
103
|
lastTrancheNumber: BN;
|
|
104
104
|
lastTranchePriceUsd: BN;
|
|
105
105
|
bump: number;
|
|
@@ -110,7 +110,11 @@ export type TrancheState = {
|
|
|
110
110
|
currentTrancheNumber: BN;
|
|
111
111
|
currentTrancheSupply: BN;
|
|
112
112
|
currentTranchePriceUsd: BN;
|
|
113
|
-
|
|
113
|
+
//Currently working on renaming Warrants to Pretokens,
|
|
114
|
+
// current version of sol contracts appears to still
|
|
115
|
+
// be using warrants phrasing for this variable
|
|
116
|
+
totalPretokensSold?: BN;
|
|
117
|
+
totalWarrantsSold?: BN;
|
|
114
118
|
initialTrancheSupply: BN;
|
|
115
119
|
supplyGrowthBps: number;
|
|
116
120
|
priceGrowthBps: number;
|