@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,5 +1,5 @@
1
1
  import { BigNumber, ethers } from "ethers";
2
- import { preLaunchReceipt, StakedEvent, WithdrawnStakeEvent, WithdrawnStakeResult } from "../types";
2
+ import { StakedEvent, WithdrawnStakeEvent, WithdrawnStakeResult } from "../types";
3
3
  import { EthereumContractService } from "../contract";
4
4
  import { formatContractErrors } from "../utils";
5
5
 
@@ -28,62 +28,6 @@ export class StakeClient {
28
28
  }
29
29
 
30
30
 
31
- /**
32
- * Read OPP / Outpost state used by the Depositor to decide whether staking is allowed.
33
- * Returns various data
34
- */
35
- async getOppStatus(): Promise<any> {
36
- const depositor = this.contract.Depositor;
37
- const opp = this.contract.OPP;
38
-
39
-
40
- const oppAddress: string = await depositor.oppAddress();
41
- const oppInboundAddress: string = await depositor.oppInboundAddress();
42
- const prevEpochSent = await opp.prevEpochSent();
43
-
44
-
45
- const inbound = this.contractService.getReadOnly('OPPInbound');
46
-
47
- // Query useful getters
48
- const nextEpochBN: any = await inbound.nextEpochNum();
49
- const pendingEpochRaw: any = await inbound.pendingEpoch();
50
- const pendingMessageCount: any = await inbound.pendingMessageCount();
51
- const previousEpochHash: string = await inbound.previousEpochHash();
52
- const nextEpochNum = (nextEpochBN && typeof nextEpochBN.toNumber === 'function') ? nextEpochBN.toNumber() : Number(nextEpochBN || 0);
53
-
54
- const pendingEpoch = (pendingEpochRaw && pendingEpochRaw.epochNumber !== undefined)
55
- ? {
56
- epochNumber: (pendingEpochRaw.epochNumber && typeof pendingEpochRaw.epochNumber.toNumber === 'function') ? pendingEpochRaw.epochNumber.toNumber() : Number(pendingEpochRaw.epochNumber || 0),
57
- timestamp: (pendingEpochRaw.timestamp && typeof pendingEpochRaw.timestamp.toNumber === 'function') ? pendingEpochRaw.timestamp.toNumber() : Number(pendingEpochRaw.timestamp || 0),
58
- prevEpochHash: pendingEpochRaw.prevEpochHash,
59
- merkleRoot: pendingEpochRaw.merkleRoot,
60
- firstMessageID: pendingEpochRaw.firstMessageID,
61
- lastMessageID: pendingEpochRaw.lastMessageID,
62
- }
63
- : null;
64
-
65
- const pendingMessagesBN = pendingMessageCount;
66
- const pendingMessages = (pendingMessagesBN && typeof pendingMessagesBN.toString === 'function') ? pendingMessagesBN.toString() : String(pendingMessagesBN || '0');
67
-
68
- const hasPendingMessages = (pendingMessagesBN && typeof pendingMessagesBN.gt === 'function') ? pendingMessagesBN.gt(0) : (Number(pendingMessages) > 0);
69
-
70
- return {
71
- oppAddress,
72
- prevEpochSent,
73
- oppInboundAddress,
74
- nextEpochNum,
75
- pendingEpoch,
76
- pendingMessageCount: pendingMessages,
77
- previousEpochHash,
78
- hasPendingMessages,
79
- raw: {
80
- nextEpochBN,
81
- pendingEpochRaw,
82
- pendingMessageCount: pendingMessagesBN,
83
- },
84
- };
85
- }
86
-
87
31
 
88
32
  /**
89
33
  *
@@ -153,97 +97,6 @@ export class StakeClient {
153
97
 
154
98
 
155
99
 
156
- async getOwnedTokenIdsFor(
157
- owner: string,
158
- fromBlock = 1850820,
159
- toBlock: number | string = "latest"
160
- ): Promise<BigNumber[]> {
161
- const receiptContract = this.contract.ReceiptNFT;
162
-
163
- // Logs where address received tokens
164
- const toLogs = await receiptContract.queryFilter(
165
- receiptContract.filters.Transfer(null, owner),
166
- fromBlock,
167
- toBlock
168
- );
169
-
170
- // Logs where address sent tokens (including burns from owner → 0)
171
- const fromLogs = await receiptContract.queryFilter(
172
- receiptContract.filters.Transfer(owner, null),
173
- fromBlock,
174
- toBlock
175
- );
176
-
177
- const owned = new Set<string>();
178
-
179
- // Add all received tokenIds
180
- for (const e of toLogs) {
181
- const tokenId = e.args?.tokenId;
182
- if (!tokenId) continue;
183
- owned.add(tokenId.toString());
184
- }
185
-
186
- // Remove all sent tokenIds
187
- for (const e of fromLogs) {
188
- const tokenId = e.args?.tokenId;
189
- if (!tokenId) continue;
190
- owned.delete(tokenId.toString());
191
- }
192
-
193
- // Convert to BigNumbers
194
- return Array.from(owned).map((id) => BigNumber.from(id));
195
- }
196
-
197
-
198
- /**
199
- *
200
- * @param amountWei an amount of liqETH (in WEI) to stake to the Outpost
201
- * @returns txHash (hash of the transaction), receipt, WithdrawnStake event
202
- */
203
- async fetchPreLaunchReceipts(address: string): Promise<preLaunchReceipt[]> {
204
- const receiptContract = this.contract.ReceiptNFT;
205
-
206
- // first figure out which tokenIds this address owns, from events
207
- const tokenIds = await this.getOwnedTokenIdsFor(address);
208
-
209
- const results: preLaunchReceipt[] = [];
210
-
211
- // next fetch on-chain receipt data just for those ids
212
- for (const idBN of tokenIds) {
213
- try {
214
- const receiptData = await receiptContract.getReceipt(idBN);
215
- const formattedReceipt = {
216
- account: receiptData.account,
217
- currency: receiptData.currency,
218
- kind: receiptData.kind,
219
- indexAtMint: receiptData.indexAtMint.toBigInt(),
220
- principal: {
221
- amount: receiptData.principal.toBigInt(),
222
- decimals: 18,
223
- symbol: "LiqETH"
224
- },
225
- shares: {
226
- amount: receiptData.shares.toBigInt(),
227
- decimals: 18,
228
- symbol: "LiqETH"
229
- },
230
- timestamp: new Date(Number(receiptData.timestamp.toString()) * 1000).toLocaleString(),
231
- }
232
-
233
- // Only fetch staking receipts (kind 1 is a wire pretoken receipt)
234
- if(receiptData.kind == 0) results.push({ tokenId: idBN.toBigInt(), receipt: formattedReceipt } as any);
235
- } catch (err) {
236
- // in case of any mismatch or race, just skip this id
237
- console.warn(`Failed to load receipt for tokenId=${idBN.toString()}`, err);
238
- continue;
239
- }
240
- }
241
-
242
- console.log('results of prelaunch receipts', results)
243
- return results;
244
- }
245
-
246
-
247
100
  /**
248
101
  *
249
102
  * @param amountWei an amount of liqETH (in WEI) to stake to the Outpost
@@ -21,6 +21,7 @@ import OPPInboundArtifact from '../../assets/ethereum/ABI/outpost/OPPInbound.sol
21
21
  import PretokenArtifact from '../../assets/ethereum/ABI/outpost/Pretoken.sol/Pretoken.json';
22
22
  import AggregatorArtifact from '../../assets/ethereum/ABI/outpost/Aggregator.sol/Aggregator.json';
23
23
  import EthUsdPriceConsumerArtifact from '../../assets/ethereum/ABI/outpost/EthUsdPriceConsumer.sol/EthUsdPriceConsumer.json';
24
+ import PoolArtifact from '../../assets/ethereum/ABI/outpost/Pool.sol/Pool.json';
24
25
 
25
26
  import ERC20Artifact from '../../assets/ethereum/ABI/token/ERC20Token.sol/ERC20Token.json';
26
27
  import ERC721Artifact from '../../assets/ethereum/ABI/token/ERC721Token.sol/ERC721Token.json';
@@ -41,6 +42,7 @@ export const ERC1155Abi = ERC1155Artifact.abi;
41
42
 
42
43
  // Make sure ContractName in ./types includes all of these keys.
43
44
  export const ADDRESSES: AddressBook = {
45
+ // LiqETH contracts
44
46
  Accounting: "0xd333A03a44D5d602A98c1B7bcd7ED1f042DD0dEd",
45
47
  DepositManager: "0x601eaA31e8d33D8725786f1733f4cE6cCEf740D4",
46
48
  LiqEth: "0x08252e1Dcbaa86A2887927b02536CD3E67a802c8", // AKA LiqEthToken
@@ -48,10 +50,6 @@ export const ADDRESSES: AddressBook = {
48
50
  WithdrawalQueue: "0x951E413FC81a2CE133078ABE8B88677F5296abE7",
49
51
  WithdrawalVault: "0x0D2bf834DD560839e986d42D06DeE268A17c2d13",
50
52
 
51
- // LiqEthAuthority: "0x7A9cf59EC53F32577Cc8200466Cc7693713129D5",
52
- // BeaconState: "0xD3860E5977C94b343341635a2dEEBD20B651c48f",
53
- // YieldOracle: "0x307b35816674913cf122975B3CF912b5709653F3",
54
-
55
53
  //Outpost contracts
56
54
  Depositor: "0x69Aa53Ef02F124dB421AeDda509d6912341299Cc",
57
55
  ReceiptNFT: "0x13588fF41E2f47D047874162B1eD15AaF6818f5a",
@@ -63,6 +61,7 @@ export const ADDRESSES: AddressBook = {
63
61
  Pretoken: "0xcf6A1209A7A391cc576174204386F4e5462323dC",
64
62
  EthUsdPriceConsumer: "0x1Ef180FF49313fCB8B5c0470268295d0d24CDE69",
65
63
  Aggregator: "0xFCfc3ddd4CBd9Ad3b3af3A374B8bdA1b66eE6FFF",
64
+ Pool: "0x29DEf0fA009e02d108d9505018EAe0168F233e03",
66
65
  };
67
66
 
68
67
  export type Contracts<T extends string = ContractName> = Record<T, ContractConfig>;
@@ -142,6 +141,10 @@ export const CONTRACTS: Contracts<ContractName> = {
142
141
  EthUsdPriceConsumer: {
143
142
  address: ADDRESSES.EthUsdPriceConsumer,
144
143
  abi: EthUsdPriceConsumerArtifact.abi as JsonFragment[],
144
+ },
145
+ Pool: {
146
+ address: ADDRESSES.Pool,
147
+ abi: PoolArtifact.abi as JsonFragment[],
145
148
  }
146
149
  };
147
150
 
@@ -1,13 +1,20 @@
1
1
  import { BigNumber, ethers } from 'ethers';
2
- import { IStakingClient, Portfolio, StakerConfig, TrancheSnapshot } from '../../types';
3
2
  import { ChainID, EvmChainID, PublicKey as WirePubKey } from '@wireio/core';
3
+ import {
4
+ IStakingClient,
5
+ OPPAssertion,
6
+ Portfolio,
7
+ StakerConfig,
8
+ TrancheSnapshot
9
+ } from '../../types';
4
10
  import { EthereumContractService } from './contract';
5
11
  import { preLaunchReceipt } from './types';
6
- import { DepositClient } from './clients/deposit.client';
12
+ import { buildEthereumTrancheSnapshot } from './utils';
13
+ import { ConvertClient } from './clients/convert.client';
7
14
  import { StakeClient } from './clients/stake.client';
8
15
  import { PretokenClient } from './clients/pretoken.client';
9
- import { buildEthereumTrancheSnapshot } from './utils';
10
- import { LiqClient } from './clients/liq.client';
16
+ import { OPPClient } from './clients/opp.client';
17
+ import { ReceiptClient } from './clients/receipt.client';
11
18
 
12
19
 
13
20
 
@@ -18,10 +25,11 @@ export class EthereumStakingClient implements IStakingClient {
18
25
  private readonly signer: ethers.Signer;
19
26
  private readonly contractService: EthereumContractService;
20
27
 
21
- private depositClient: DepositClient;
22
- private liqClient: LiqClient;
28
+ private convertClient: ConvertClient;
23
29
  private pretokenClient: PretokenClient;
24
30
  private stakeClient: StakeClient;
31
+ private oppClient: OPPClient;
32
+ private receiptClient: ReceiptClient;
25
33
 
26
34
 
27
35
  get contract() { return this.contractService.contract; }
@@ -38,10 +46,11 @@ export class EthereumStakingClient implements IStakingClient {
38
46
  signer: this.signer,
39
47
  });
40
48
 
41
- this.depositClient = new DepositClient(this.contractService);
42
- this.liqClient = new LiqClient(this.contractService);
49
+ this.convertClient = new ConvertClient(this.contractService);
43
50
  this.pretokenClient = new PretokenClient(this.contractService);
44
51
  this.stakeClient = new StakeClient(this.contractService);
52
+ this.oppClient = new OPPClient(this.contractService);
53
+ this.receiptClient = new ReceiptClient(this.contractService);
45
54
  }
46
55
  catch (error) {
47
56
  // console.error('Error initializing EthereumStakingClient:', error);
@@ -66,7 +75,7 @@ export class EthereumStakingClient implements IStakingClient {
66
75
  ? amount
67
76
  : BigNumber.from(amount);
68
77
 
69
- const result = await this.depositClient.performDeposit(amountWei);
78
+ const result = await this.convertClient.performDeposit(amountWei);
70
79
  return result.txHash;
71
80
  }
72
81
 
@@ -80,18 +89,15 @@ export class EthereumStakingClient implements IStakingClient {
80
89
  async withdraw(amount: bigint): Promise<string> {
81
90
  const address = await this.signer.getAddress();
82
91
  const amountWei = BigNumber.from(amount);
83
- // const chainId = this.network?.chainId ?? (await this.provider.getNetwork()).chainId;
84
- // const result = await this.depositClient.requestWithdraw(amountWei, this.signer, chainId);
85
92
 
86
- const result = await this.liqClient.safeBurn(address, amountWei)
93
+ const result = await this.convertClient.performWithdraw(address, amountWei)
87
94
  return result.txHash;
88
95
  }
89
96
 
90
97
 
91
98
  /**
92
99
  * Stake liqETH via DepositManager.
93
- * @param amount Amount in wei
94
- * Keep this as a bigint / string in the caller; avoid JS floats.
100
+ * @param amount Amount in wei - Keep this as a bigint / string in the caller; avoid JS floats.
95
101
  * @returns transaction hash
96
102
  */
97
103
  async stake(amount: bigint): Promise<string> {
@@ -125,71 +131,38 @@ export class EthereumStakingClient implements IStakingClient {
125
131
 
126
132
  async buy(amount: bigint): Promise<string> {
127
133
  const buyer = await this.signer.getAddress();
128
- const amountBigNum = BigNumber.from(amount)
129
134
 
130
135
  // ! Hoodi only - check if the mock aggregator price is stale, and if so, update it before submitting the buy request
131
136
  await this.updateMockAggregatorPrice();
132
-
133
-
134
- const bal = await this.contract.LiqEth.balanceOf(buyer);
135
- const paused = await this.contract.Depositor.paused();
136
- if(paused) {
137
- throw new Error("Error - Depositor is in a paused state");
138
- }
139
-
140
- // if current liq balance is less than the requested buy amount, throw an error
141
- if (bal.lt(amount)) {
142
- throw new Error(`Balance insufficient for pre-token purchase`);
143
- }
144
-
145
- //check that the contract has allowance for the token
146
- const depositorAddr = this.contract.Depositor.address;
147
- const allowance = await this.contract.LiqEth.allowance(buyer, depositorAddr);
148
-
149
- // if allowance is less than the requested stake amount, request permission to spend LiqEth
150
- if (allowance.lt(amount)) {
151
- const liqWrite = this.contractService.getWrite('LiqEth');
152
-
153
- // currently requested unlimited amount - potentially change to only request up to the current amount?
154
- const approveAmount = ethers.constants.MaxUint256;
155
-
156
- console.warn(`allowance insufficient (${allowance.toString()} < ${amount.toString()}); sending approve(${depositorAddr}, ${approveAmount.toString()})`);
157
-
158
- const approveTx = await liqWrite.approve(depositorAddr, approveAmount);
159
- await approveTx.wait(1);
160
-
161
- // re-read allowance to ensure approval succeeded
162
- const newAllowance = await this.contract.LiqEth.allowance(buyer, depositorAddr);
163
- if (newAllowance.lt(amount)) {
164
- throw new Error('Approval failed or allowance still insufficient after approve');
165
- }
166
- }
167
-
168
-
169
- let result = await this.pretokenClient.purchasePretokensWithLiqETH(amountBigNum, buyer);
170
137
 
138
+ let result = await this.pretokenClient.purchasePretokensWithLiqETH(amount, buyer);
171
139
  return result && result.txHash ? result.txHash : "Error - no resulting txHash";
172
140
  }
173
141
 
174
142
 
175
-
143
+ async getOPPMessages(address?: string): Promise<OPPAssertion[]> {
144
+ if(!address) address = await this.signer.getAddress();
145
+
146
+ return await this.oppClient.getMessages(address);
147
+ }
148
+
176
149
 
177
150
  async getOPPStatus(): Promise<any> {
178
- return await this.stakeClient.getOppStatus();
151
+ return await this.oppClient.getStatus();
179
152
  }
180
153
 
181
154
 
182
155
 
183
156
  /**
184
- * ETH Prelaunch function to list the ReceiptNFTs owned by a specific user
157
+ * ETH Prelaunch function to list the Stake ReceiptNFTs owned by a specific user
185
158
  * @param address address to query the receipts for
186
159
  * @returns array of receipts
187
160
  */
188
161
  async fetchPrelaunchReceipts(address?: string): Promise<preLaunchReceipt[]> {
189
162
  if(address === undefined) address = await this.signer.getAddress();
190
163
 
191
- let receipts = await this.stakeClient.fetchPreLaunchReceipts(address);
192
- return receipts
164
+ //default to stake receipts
165
+ return await this.receiptClient.stakeReceipts(address);
193
166
  }
194
167
 
195
168
 
@@ -206,6 +179,7 @@ export class EthereumStakingClient implements IStakingClient {
206
179
  }
207
180
  }
208
181
 
182
+
209
183
  /**
210
184
  * Resolve the user's ETH + liqETH balances.
211
185
  *
@@ -215,8 +189,6 @@ export class EthereumStakingClient implements IStakingClient {
215
189
  */
216
190
  async getPortfolio(): Promise<Portfolio> {
217
191
  const walletAddress = await this.signer.getAddress();
218
- // console.log('getPortfolio() wallet address', walletAddress)
219
-
220
192
 
221
193
  // 1) Native ETH balance
222
194
  const nativeBalance = await this.provider.getBalance(walletAddress);
@@ -227,9 +199,16 @@ export class EthereumStakingClient implements IStakingClient {
227
199
  const liqBalance: ethers.BigNumber = await this.contract.LiqEth.balanceOf(walletAddress);
228
200
  const liqSymbol = 'Liq' + (this.network?.nativeCurrency?.symbol ?? 'ETH');
229
201
 
230
- // 3) staked liqEth ERC-20 balance (actual)
231
- const stakedLiqBalance = await this.contract.Depositor.sharesOf(walletAddress);
202
+ // 3) staked liqEth ERC-20 balance (calculate from receipts)
203
+ let stakeReceipts = await this.receiptClient.stakeReceipts(walletAddress);
204
+ let stakeBalanceBN = BigNumber.from(0);
205
+ for (let r of stakeReceipts) {
206
+ stakeBalanceBN = stakeBalanceBN.add(BigNumber.from(r.receipt.principal.amount));
207
+ }
208
+
232
209
 
210
+ // 4) WIRE pretoken balance
211
+ const wireBalance: ethers.BigNumber = await this.contract.Pretoken.balanceOf(walletAddress);
233
212
 
234
213
  const portfolio: Portfolio = {
235
214
  native: {
@@ -242,19 +221,14 @@ export class EthereumStakingClient implements IStakingClient {
242
221
  decimals: nativeDecimals,
243
222
  symbol: liqSymbol,
244
223
  },
245
- tracked: {
246
- amount: liqBalance.toBigInt(),
247
- decimals: nativeDecimals,
248
- symbol: liqSymbol,
249
- },
250
224
  staked: {
251
- amount: stakedLiqBalance.toBigInt(),
225
+ amount: stakeBalanceBN.toBigInt(),
252
226
  decimals: nativeDecimals,
253
227
  symbol: liqSymbol,
254
228
  },
255
229
  wire: {
256
- amount: BigInt(0), // TODO
257
- decimals: 0,
230
+ amount: wireBalance.toBigInt(),
231
+ decimals: 18,
258
232
  symbol: '$WIRE',
259
233
  },
260
234
  chainID: this.network.chainId
@@ -20,6 +20,7 @@ export const CONTRACT_NAMES = [
20
20
  'Pretoken',
21
21
  'Aggregator',
22
22
  'EthUsdPriceConsumer',
23
+ 'Pool',
23
24
 
24
25
  ] as const;
25
26
 
@@ -300,7 +300,7 @@ export async function buildEthereumTrancheSnapshot(options: {
300
300
  priceGrowthBps,
301
301
  currentTrancheSupply,
302
302
  initialTrancheSupply,
303
- totalWarrantsSold: totalTrancheSupply,
303
+ totalPretokensSold: totalTrancheSupply,
304
304
  nativePriceUsd: ethPriceUsd,
305
305
  nativePriceTimestamp,
306
306
  ladder,
@@ -1,6 +1,5 @@
1
1
  import { AnchorProvider, BN, Program } from '@coral-xyz/anchor';
2
2
  import {
3
- PublicKey,
4
3
  SystemProgram,
5
4
  Transaction,
6
5
  TransactionInstruction,
@@ -27,26 +26,54 @@ import {
27
26
  deriveReservePoolPda,
28
27
  deriveVaultPda,
29
28
  deriveStakeControllerStatePda,
29
+ derivePayoutStatePda,
30
30
  deriveBucketAuthorityPda,
31
31
  derivePayRateHistoryPda,
32
32
  deriveDistributionStatePda,
33
33
  deriveUserRecordPda,
34
- derivePayoutStatePda,
35
34
  deriveEphemeralStakeAddress,
35
+ deriveWithdrawGlobalPda,
36
+ deriveStakeAllocationStatePda,
37
+ deriveStakeMetricsPda,
38
+ deriveMaintenanceLedgerPda,
39
+ deriveWithdrawMintAuthorityPda,
40
+ deriveWithdrawMintMetadataPda,
41
+ deriveWithdrawNftMintPda,
42
+ deriveLiqReceiptDataPda,
36
43
  } from '../constants';
37
44
  import { WalletLike } from '../types';
38
45
 
39
46
  export class DepositClient {
40
47
  private program: Program<LiqsolCore>;
41
48
 
42
- get wallet(): WalletLike { return this.provider.wallet; }
49
+ get wallet(): WalletLike {
50
+ return this.provider.wallet;
51
+ }
43
52
 
44
53
  constructor(private provider: AnchorProvider) {
45
54
  const svc = new SolanaProgramService(provider);
46
55
  this.program = svc.getProgram('liqsolCore');
47
56
  }
48
57
 
49
- async buildDepositTx(amount: bigint, user = this.wallet.publicKey): Promise<Transaction> {
58
+ /**
59
+ * Build a deposit transaction:
60
+ * SOL -> liqSOL via liqsol_core::deposit.
61
+ */
62
+ async buildDepositTx(
63
+ amount: bigint,
64
+ user = this.wallet.publicKey,
65
+ ): Promise<Transaction> {
66
+ if (!user) {
67
+ throw new Error(
68
+ 'DepositClient.buildDepositTx: wallet not connected',
69
+ );
70
+ }
71
+ if (!amount || amount <= BigInt(0)) {
72
+ throw new Error(
73
+ 'DepositClient.buildDepositTx: amount must be greater than zero.',
74
+ );
75
+ }
76
+
50
77
  // -------------------------------------------------------------
51
78
  // PDAs
52
79
  // -------------------------------------------------------------
@@ -59,8 +86,6 @@ export class DepositClient {
59
86
  const payoutState = derivePayoutStatePda();
60
87
  const bucketAuthority = deriveBucketAuthorityPda();
61
88
  const payRateHistory = derivePayRateHistoryPda();
62
- const distributionState = deriveDistributionStatePda();
63
- const userRecord = deriveUserRecordPda(user);
64
89
 
65
90
  // -------------------------------------------------------------
66
91
  // Token-2022 ATAs
@@ -69,14 +94,20 @@ export class DepositClient {
69
94
  liqsolMint,
70
95
  user,
71
96
  false,
72
- TOKEN_2022_PROGRAM_ID
97
+ TOKEN_2022_PROGRAM_ID,
73
98
  );
74
99
 
100
+ // -------------------------------------------------------------
101
+ // Distribution state + user_record (KEYED BY TOKEN ACCOUNT)
102
+ // -------------------------------------------------------------
103
+ const distributionState = deriveDistributionStatePda();
104
+ const userRecord = deriveUserRecordPda(userAta);
105
+
75
106
  const bucketTokenAccount = getAssociatedTokenAddressSync(
76
107
  liqsolMint,
77
108
  bucketAuthority,
78
109
  true,
79
- TOKEN_2022_PROGRAM_ID
110
+ TOKEN_2022_PROGRAM_ID,
80
111
  );
81
112
 
82
113
  // -------------------------------------------------------------
@@ -124,4 +155,119 @@ export class DepositClient {
124
155
 
125
156
  return new Transaction().add(ix);
126
157
  }
158
+
159
+ /**
160
+ * Build a withdraw-request transaction:
161
+ * liqSOL -> SOL via liqsol_core::requestWithdraw.
162
+ *
163
+ * This:
164
+ * - burns liqSOL from the user
165
+ * - increments totalEncumberedFunds in global state
166
+ * - mints an NFT receipt (liqReceiptData + NFT ATA for owner)
167
+ */
168
+ async buildWithdrawTx(
169
+ amount: bigint,
170
+ user = this.wallet.publicKey,
171
+ ): Promise<Transaction> {
172
+ if (!user) {
173
+ throw new Error(
174
+ 'DepositClient.buildWithdrawTx: wallet not connected',
175
+ );
176
+ }
177
+ if (!amount || amount <= BigInt(0)) {
178
+ throw new Error(
179
+ 'DepositClient.buildWithdrawTx: amount must be greater than zero.',
180
+ );
181
+ }
182
+
183
+ // -------------------------------------------------------------
184
+ // Core program + liqSOL mint / user ATA
185
+ // -------------------------------------------------------------
186
+ const liqsolMint = deriveLiqsolMintPda();
187
+
188
+ const userAta = getAssociatedTokenAddressSync(
189
+ liqsolMint,
190
+ user,
191
+ false,
192
+ TOKEN_2022_PROGRAM_ID,
193
+ );
194
+
195
+ // Distribution / balance-tracking
196
+ // user_record is keyed by the user’s liqSOL ATA (same convention as deposit/purchase)
197
+ const userRecord = deriveUserRecordPda(userAta);
198
+ const distributionState = deriveDistributionStatePda();
199
+
200
+ // Reserve + stake controller PDAs
201
+ const global = deriveWithdrawGlobalPda();
202
+ const reservePool = deriveReservePoolPda();
203
+ const stakeAllocationState = deriveStakeAllocationStatePda();
204
+ const stakeMetrics = deriveStakeMetricsPda();
205
+ const maintenanceLedger = deriveMaintenanceLedgerPda();
206
+
207
+ // -------------------------------------------------------------
208
+ // Need nextReceiptId from withdraw global state
209
+ // -------------------------------------------------------------
210
+ const globalState = await this.program.account.global.fetch(global);
211
+ const receiptId = (globalState.nextReceiptId as BN).toBigInt();
212
+
213
+ // -------------------------------------------------------------
214
+ // NFT receipt PDAs (mint, metadata, data, ATA)
215
+ // -------------------------------------------------------------
216
+ const mintAuthority = deriveWithdrawMintAuthorityPda();
217
+ const metadata = deriveWithdrawMintMetadataPda();
218
+
219
+ const nftMint = deriveWithdrawNftMintPda(receiptId);
220
+ const receiptData = deriveLiqReceiptDataPda(nftMint);
221
+
222
+ const owner = user;
223
+ const nftAta = getAssociatedTokenAddressSync(
224
+ nftMint,
225
+ owner,
226
+ false,
227
+ TOKEN_2022_PROGRAM_ID,
228
+ );
229
+
230
+ // Bucket token account (same bucket used by deposit/distribution)
231
+ const bucketAuthority = deriveBucketAuthorityPda();
232
+ const bucketTokenAccount = getAssociatedTokenAddressSync(
233
+ liqsolMint,
234
+ bucketAuthority,
235
+ true,
236
+ TOKEN_2022_PROGRAM_ID,
237
+ );
238
+
239
+ // -------------------------------------------------------------
240
+ // BUILD IX (MUST MATCH requestWithdraw IDL)
241
+ // -------------------------------------------------------------
242
+ const ix: TransactionInstruction = await this.program.methods
243
+ .requestWithdraw(new BN(amount.toString()))
244
+ .accounts({
245
+ user,
246
+ owner,
247
+ global,
248
+ liqsolMint,
249
+ userAta,
250
+ userRecord,
251
+ reservePool,
252
+ stakeAllocationState,
253
+ stakeMetrics,
254
+ maintenanceLedger,
255
+ clock: SYSVAR_CLOCK_PUBKEY,
256
+ mintAuthority,
257
+ receiptData,
258
+ metadata,
259
+ nftMint,
260
+ nftAta,
261
+ distributionState,
262
+ bucketTokenAccount,
263
+ tokenProgram: TOKEN_2022_PROGRAM_ID,
264
+ tokenInterface: TOKEN_2022_PROGRAM_ID,
265
+ associatedTokenProgram: ASSOCIATED_TOKEN_PROGRAM_ID,
266
+ systemProgram: SystemProgram.programId,
267
+ rent: SYSVAR_RENT_PUBKEY,
268
+ })
269
+ .instruction();
270
+
271
+ return new Transaction().add(ix);
272
+ }
127
273
  }