@wireio/stake 0.9.0 → 0.9.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 +341 -32
- package/lib/stake.browser.js.map +1 -1
- package/lib/stake.d.ts +93 -10
- package/lib/stake.js +386 -30
- package/lib/stake.js.map +1 -1
- package/lib/stake.m.js +341 -32
- package/lib/stake.m.js.map +1 -1
- package/package.json +4 -3
- package/src/networks/ethereum/clients/convert.client.ts +29 -1
- package/src/networks/ethereum/clients/receipt.client.ts +81 -3
- package/src/networks/ethereum/clients/stake.client.ts +1 -0
- package/src/networks/ethereum/ethereum.ts +33 -11
- package/src/networks/ethereum/types.ts +15 -0
- package/src/networks/solana/clients/deposit.client.ts +260 -9
- package/src/networks/solana/clients/distribution.client.ts +1 -1
- package/src/networks/solana/clients/outpost.client.ts +1 -1
- package/src/networks/solana/solana.ts +184 -26
- package/src/networks/solana/types.ts +4 -4
- package/src/staker.ts +2 -2
- package/src/types.ts +8 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@wireio/stake",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.2",
|
|
4
4
|
"description": "LIQ Staking Module for Wire Network",
|
|
5
5
|
"homepage": "https://gitea.gitgo.app/Wire/sdk-stake",
|
|
6
6
|
"license": "FSL-1.1-Apache-2.0",
|
|
@@ -33,7 +33,7 @@
|
|
|
33
33
|
"lint:fix": "eslint 'src/**/*.{js,ts}' --fix"
|
|
34
34
|
},
|
|
35
35
|
"peerDependencies": {
|
|
36
|
-
"@wireio/core": ">=0.1.0 <0.
|
|
36
|
+
"@wireio/core": ">=0.1.0 <0.4.0"
|
|
37
37
|
},
|
|
38
38
|
"dependencies": {
|
|
39
39
|
"@coral-xyz/anchor": "^0.31.1",
|
|
@@ -41,6 +41,7 @@
|
|
|
41
41
|
"@solana/spl-token": "^0.4.13",
|
|
42
42
|
"@solana/wallet-adapter-base": "^0.9.27",
|
|
43
43
|
"@solana/web3.js": "^1.98.2",
|
|
44
|
+
"@sqds/multisig": "^2.1.4",
|
|
44
45
|
"bs58": "^6.0.0",
|
|
45
46
|
"buffer": "^6.0.3",
|
|
46
47
|
"crypto-browserify": "^3.12.1",
|
|
@@ -63,7 +64,7 @@
|
|
|
63
64
|
"@types/node": "^18.19.0",
|
|
64
65
|
"@typescript-eslint/eslint-plugin": "^5.60.0",
|
|
65
66
|
"@typescript-eslint/parser": "^5.60.0",
|
|
66
|
-
"@wireio/core": "^0.
|
|
67
|
+
"@wireio/core": "^0.3.0",
|
|
67
68
|
"assert": "^2.0.0",
|
|
68
69
|
"chai": "^4.3.6",
|
|
69
70
|
"esbuild": "^0.25.8",
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { BigNumber, ethers } from "ethers";
|
|
2
|
-
import { DepositEvent, DepositResult, SharesBurnedEvent } from "../types";
|
|
2
|
+
import { ClaimedEvent, DepositEvent, DepositResult, SharesBurnedEvent } from "../types";
|
|
3
3
|
import { EthereumContractService } from "../contract";
|
|
4
4
|
import { formatContractErrors } from "../utils";
|
|
5
5
|
|
|
@@ -114,6 +114,34 @@ export class ConvertClient {
|
|
|
114
114
|
}
|
|
115
115
|
|
|
116
116
|
|
|
117
|
+
public async claimWithdraw(tokenId: BigNumber): Promise<any> {
|
|
118
|
+
let tx, receipt;
|
|
119
|
+
try {
|
|
120
|
+
tx = await this.contract.DepositManager.claim(tokenId);
|
|
121
|
+
receipt = await tx.wait(1);
|
|
122
|
+
} catch (err: any) {
|
|
123
|
+
let errorObj = formatContractErrors(err);
|
|
124
|
+
throw new Error(errorObj.name ?? errorObj.raw)
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// Parse Claimed event if present
|
|
128
|
+
let event: ClaimedEvent | undefined;
|
|
129
|
+
const ev = receipt.events?.find((e) => e.event === 'Claimed');
|
|
130
|
+
|
|
131
|
+
if (ev && ev.args) {
|
|
132
|
+
const { sender, ethAmount } = ev.args;
|
|
133
|
+
event = {
|
|
134
|
+
sender,
|
|
135
|
+
ethAmount: BigNumber.from(ethAmount),
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
return {
|
|
140
|
+
txHash: tx.hash,
|
|
141
|
+
receipt,
|
|
142
|
+
event,
|
|
143
|
+
};
|
|
144
|
+
}
|
|
117
145
|
|
|
118
146
|
/*
|
|
119
147
|
// OLD - this was replaced with LiqEth.safeBurn() on 1/13/26
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { BigNumber } from "ethers";
|
|
2
|
-
import { preLaunchReceipt } from "../types";
|
|
2
|
+
import { preLaunchReceipt, WithdrawReceipt } from "../types";
|
|
3
3
|
import { EthereumContractService } from "../contract";
|
|
4
4
|
import { ReceiptNFTKind } from "../../../types";
|
|
5
5
|
|
|
@@ -45,7 +45,7 @@ export class ReceiptClient {
|
|
|
45
45
|
const receiptContract = this.contract.ReceiptNFT;
|
|
46
46
|
|
|
47
47
|
// first figure out which tokenIds this address owns, from events
|
|
48
|
-
const tokenIds = await this.
|
|
48
|
+
const tokenIds = await this.getOwnedReceiptNFTsFor(address);
|
|
49
49
|
|
|
50
50
|
const results: preLaunchReceipt[] = [];
|
|
51
51
|
|
|
@@ -90,7 +90,7 @@ export class ReceiptClient {
|
|
|
90
90
|
|
|
91
91
|
|
|
92
92
|
|
|
93
|
-
private async
|
|
93
|
+
private async getOwnedReceiptNFTsFor(
|
|
94
94
|
owner: string,
|
|
95
95
|
fromBlock = 0,
|
|
96
96
|
toBlock: number | string = "latest"
|
|
@@ -132,5 +132,83 @@ export class ReceiptClient {
|
|
|
132
132
|
}
|
|
133
133
|
|
|
134
134
|
|
|
135
|
+
/**
|
|
136
|
+
*
|
|
137
|
+
* @param address (string) to fetch receipts for
|
|
138
|
+
* @returns preLaunchReceipt[]
|
|
139
|
+
*/
|
|
140
|
+
async fetchWithdrawReceipts(address: string): Promise<WithdrawReceipt[]> {
|
|
141
|
+
// first figure out which tokenIds this address owns, from events
|
|
142
|
+
const tokenIds = await this.getOwnedWithdrawReceiptsFor(address);
|
|
143
|
+
const results: WithdrawReceipt[] = [];
|
|
144
|
+
|
|
145
|
+
// next fetch on-chain token data just for those ids
|
|
146
|
+
for (const idBN of tokenIds) {
|
|
147
|
+
try {
|
|
148
|
+
const receiptData = await this.contract.WithdrawalQueue.info(idBN);
|
|
149
|
+
|
|
150
|
+
results.push({
|
|
151
|
+
tokenId: idBN.toBigInt(),
|
|
152
|
+
receipt: {
|
|
153
|
+
ethAmount: receiptData.ethAmount,
|
|
154
|
+
ethBalance: {
|
|
155
|
+
amount: receiptData.ethAmount.toBigInt(),
|
|
156
|
+
decimals: 18,
|
|
157
|
+
symbol: "ETH"
|
|
158
|
+
},
|
|
159
|
+
readyAt: new Date(Number(receiptData.readyAt.toString()) * 1000).valueOf(),
|
|
160
|
+
}
|
|
161
|
+
});
|
|
162
|
+
} catch (err) {
|
|
163
|
+
// in case of any mismatch or race, just skip this id
|
|
164
|
+
console.warn(`Failed to load receipt for tokenId=${idBN.toString()}`, err);
|
|
165
|
+
continue;
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
return results;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
private async getOwnedWithdrawReceiptsFor(
|
|
175
|
+
owner: string,
|
|
176
|
+
fromBlock = 0,
|
|
177
|
+
toBlock: number | string = "latest"
|
|
178
|
+
): Promise<BigNumber[]> {
|
|
179
|
+
const contract = this.contract.WithdrawalQueue;
|
|
180
|
+
|
|
181
|
+
// Logs where address received tokens
|
|
182
|
+
const toLogs = await contract.queryFilter(
|
|
183
|
+
contract.filters.Transfer(null, owner),
|
|
184
|
+
fromBlock,
|
|
185
|
+
toBlock
|
|
186
|
+
);
|
|
187
|
+
|
|
188
|
+
// Logs where address sent tokens (including burns from owner → 0)
|
|
189
|
+
const fromLogs = await contract.queryFilter(
|
|
190
|
+
contract.filters.Transfer(owner, null),
|
|
191
|
+
fromBlock,
|
|
192
|
+
toBlock
|
|
193
|
+
);
|
|
135
194
|
|
|
195
|
+
const owned = new Set<string>();
|
|
196
|
+
|
|
197
|
+
// Add all received tokenIds
|
|
198
|
+
for (const e of toLogs) {
|
|
199
|
+
const tokenId = e.args?.tokenId;
|
|
200
|
+
if (!tokenId) continue;
|
|
201
|
+
owned.add(tokenId.toString());
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
// Remove all sent tokenIds
|
|
205
|
+
for (const e of fromLogs) {
|
|
206
|
+
const tokenId = e.args?.tokenId;
|
|
207
|
+
if (!tokenId) continue;
|
|
208
|
+
owned.delete(tokenId.toString());
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
// Convert to BigNumbers
|
|
212
|
+
return Array.from(owned).map((id) => BigNumber.from(id));
|
|
213
|
+
}
|
|
136
214
|
}
|
|
@@ -45,6 +45,7 @@ export class StakeClient {
|
|
|
45
45
|
throw new Error("Error - Depositor is in a paused state");
|
|
46
46
|
}
|
|
47
47
|
|
|
48
|
+
if (bal.lt(amountWei)) throw new Error("Insufficient LiqETH balance");
|
|
48
49
|
|
|
49
50
|
// if allowance is less than the requested stake amount, request permission to spend LiqEth
|
|
50
51
|
if (allowance.lt(amountWei)) {
|
|
@@ -8,7 +8,7 @@ import {
|
|
|
8
8
|
TrancheSnapshot
|
|
9
9
|
} from '../../types';
|
|
10
10
|
import { EthereumContractService } from './contract';
|
|
11
|
-
import { preLaunchReceipt } from './types';
|
|
11
|
+
import { preLaunchReceipt, WithdrawReceipt } from './types';
|
|
12
12
|
import { buildEthereumTrancheSnapshot } from './utils';
|
|
13
13
|
import { ConvertClient } from './clients/convert.client';
|
|
14
14
|
import { StakeClient } from './clients/stake.client';
|
|
@@ -98,6 +98,31 @@ export class EthereumStakingClient implements IStakingClient {
|
|
|
98
98
|
return result.txHash;
|
|
99
99
|
}
|
|
100
100
|
|
|
101
|
+
/**
|
|
102
|
+
* Withdraw native ETH from the liqETH protocol via the liqeth safeBurn function, which burns the LiqETH and adds the user to the withdrawal queue.
|
|
103
|
+
* @param amount Amount in wei (or something convertible to BigNumber).
|
|
104
|
+
* @returns transaction hash
|
|
105
|
+
*/
|
|
106
|
+
async loadPendingWithdraws(): Promise<WithdrawReceipt[]> {
|
|
107
|
+
this.ensureUser();
|
|
108
|
+
const address = await this.signer!.getAddress();
|
|
109
|
+
|
|
110
|
+
return await this.receiptClient.fetchWithdrawReceipts(address);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Withdraw native ETH from the liqETH protocol via the liqeth safeBurn function, which burns the LiqETH and adds the user to the withdrawal queue.
|
|
115
|
+
* @param amount Amount in wei (or something convertible to BigNumber).
|
|
116
|
+
* @returns transaction hash
|
|
117
|
+
*/
|
|
118
|
+
async claimWithdraw(tokenId: bigint): Promise<string> {
|
|
119
|
+
this.ensureUser();
|
|
120
|
+
|
|
121
|
+
const tokenIdBigNum = BigNumber.from(tokenId)
|
|
122
|
+
const result = await this.convertClient.claimWithdraw(tokenIdBigNum)
|
|
123
|
+
return result.txHash;
|
|
124
|
+
}
|
|
125
|
+
|
|
101
126
|
|
|
102
127
|
/**
|
|
103
128
|
* Stake liqETH via DepositManager.
|
|
@@ -213,17 +238,14 @@ export class EthereumStakingClient implements IStakingClient {
|
|
|
213
238
|
// sharesToTokens(userShares, currentIndex) = userShares * currentIndex / indexScale
|
|
214
239
|
let estimatedClaim = BigInt(0);
|
|
215
240
|
let estimatedYield = BigInt(0);
|
|
241
|
+
|
|
242
|
+
if (userShares > BigInt(0) && currentIndex > BigInt(0)) {
|
|
243
|
+
estimatedClaim = (userShares * currentIndex) / indexScale;
|
|
244
|
+
if (estimatedClaim > stakeBalanceBN.toBigInt()) {
|
|
245
|
+
estimatedYield = estimatedClaim - stakeBalanceBN.toBigInt();
|
|
246
|
+
}
|
|
247
|
+
}
|
|
216
248
|
|
|
217
|
-
// started work on estimating the user's personal APY - not necessary at the moment
|
|
218
|
-
// let estimatedAPY: number | null = null;
|
|
219
|
-
// if (userShares > BigInt(0) && currentIndex > BigInt(0)) {
|
|
220
|
-
// estimatedClaim = (userShares * currentIndex) / indexScale;
|
|
221
|
-
// if (estimatedClaim > stakeBalanceBN.toBigInt()) {
|
|
222
|
-
// estimatedYield = estimatedClaim - stakeBalanceBN.toBigInt();
|
|
223
|
-
// }
|
|
224
|
-
|
|
225
|
-
// estimatedAPY = null;
|
|
226
|
-
// }
|
|
227
249
|
|
|
228
250
|
const portfolio: Portfolio = {
|
|
229
251
|
native: {
|
|
@@ -110,4 +110,19 @@ export interface preLaunchReceipt {
|
|
|
110
110
|
shares: BalanceView,
|
|
111
111
|
timestamp: string,
|
|
112
112
|
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
export interface ClaimedEvent {
|
|
116
|
+
sender: string;
|
|
117
|
+
ethAmount: BigNumber;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
export interface WithdrawReceipt {
|
|
122
|
+
tokenId: bigint;
|
|
123
|
+
receipt: {
|
|
124
|
+
ethAmount: BigNumber;
|
|
125
|
+
ethBalance: BalanceView;
|
|
126
|
+
readyAt: number;
|
|
127
|
+
}
|
|
113
128
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { AnchorProvider, BN, Program } from '@coral-xyz/anchor';
|
|
1
|
+
import { AnchorProvider, BN, Program, Wallet } from '@coral-xyz/anchor';
|
|
2
2
|
import {
|
|
3
3
|
SystemProgram,
|
|
4
4
|
Transaction,
|
|
@@ -8,12 +8,19 @@ import {
|
|
|
8
8
|
SYSVAR_CLOCK_PUBKEY,
|
|
9
9
|
SYSVAR_RENT_PUBKEY,
|
|
10
10
|
SYSVAR_STAKE_HISTORY_PUBKEY,
|
|
11
|
+
Connection,
|
|
12
|
+
PublicKey,
|
|
13
|
+
TransactionMessage,
|
|
14
|
+
LAMPORTS_PER_SOL,
|
|
15
|
+
Signer,
|
|
11
16
|
} from '@solana/web3.js';
|
|
17
|
+
|
|
12
18
|
import {
|
|
13
19
|
TOKEN_2022_PROGRAM_ID,
|
|
14
20
|
ASSOCIATED_TOKEN_PROGRAM_ID,
|
|
15
21
|
getAssociatedTokenAddressSync,
|
|
16
22
|
} from '@solana/spl-token';
|
|
23
|
+
import * as multisig from "@sqds/multisig";
|
|
17
24
|
|
|
18
25
|
import { SolanaProgramService } from '../program';
|
|
19
26
|
import type { LiqsolCore } from '../../../assets/solana/types/liqsol_core';
|
|
@@ -47,6 +54,10 @@ import { GlobalAccount, WalletLike } from '../types';
|
|
|
47
54
|
export class DepositClient {
|
|
48
55
|
private program: Program<LiqsolCore>;
|
|
49
56
|
|
|
57
|
+
get connection() {
|
|
58
|
+
return this.provider.connection;
|
|
59
|
+
}
|
|
60
|
+
|
|
50
61
|
get wallet(): WalletLike {
|
|
51
62
|
return this.provider.wallet;
|
|
52
63
|
}
|
|
@@ -63,7 +74,7 @@ export class DepositClient {
|
|
|
63
74
|
async buildDepositTx(
|
|
64
75
|
amount: bigint,
|
|
65
76
|
user = this.wallet.publicKey,
|
|
66
|
-
): Promise<
|
|
77
|
+
): Promise<TransactionInstruction> {
|
|
67
78
|
if (!user) {
|
|
68
79
|
throw new Error(
|
|
69
80
|
'DepositClient.buildDepositTx: wallet not connected',
|
|
@@ -95,7 +106,7 @@ export class DepositClient {
|
|
|
95
106
|
const userAta = getAssociatedTokenAddressSync(
|
|
96
107
|
liqsolMint,
|
|
97
108
|
user,
|
|
98
|
-
|
|
109
|
+
true,
|
|
99
110
|
TOKEN_2022_PROGRAM_ID,
|
|
100
111
|
);
|
|
101
112
|
|
|
@@ -121,7 +132,7 @@ export class DepositClient {
|
|
|
121
132
|
// -------------------------------------------------------------
|
|
122
133
|
// BUILD IX (MUST MATCH IDL)
|
|
123
134
|
// -------------------------------------------------------------
|
|
124
|
-
|
|
135
|
+
return await this.program.methods
|
|
125
136
|
.deposit(new BN(amount.toString()), seed)
|
|
126
137
|
.accounts({
|
|
127
138
|
user,
|
|
@@ -151,10 +162,130 @@ export class DepositClient {
|
|
|
151
162
|
globalConfig
|
|
152
163
|
})
|
|
153
164
|
.instruction();
|
|
165
|
+
}
|
|
154
166
|
|
|
155
|
-
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
async buildSquadsDepositProposalTx(params: {
|
|
170
|
+
connection: Connection;
|
|
171
|
+
multisigPda: PublicKey;
|
|
172
|
+
amountLamports: bigint;
|
|
173
|
+
wallet: WalletLikeSigner; // your WalletLikeSigner type
|
|
174
|
+
vaultIndex?: number;
|
|
175
|
+
}): Promise<{
|
|
176
|
+
tx: Transaction;
|
|
177
|
+
transactionIndex: bigint;
|
|
178
|
+
vaultPda: PublicKey;
|
|
179
|
+
}> {
|
|
180
|
+
const { connection, multisigPda, amountLamports, wallet } = params;
|
|
181
|
+
const vaultIndex = params.vaultIndex ?? 0;
|
|
182
|
+
|
|
183
|
+
if (!wallet?.publicKey) throw new Error("wallet.publicKey missing");
|
|
184
|
+
if (!amountLamports || amountLamports <= BigInt(0)) throw new Error("amountLamports must be > 0");
|
|
185
|
+
|
|
186
|
+
// 1) vault PDA
|
|
187
|
+
const [vaultPda] = multisig.getVaultPda({ multisigPda, index: vaultIndex });
|
|
188
|
+
|
|
189
|
+
// 2) build deposit ix where `user` = vaultPda (off-curve OK)
|
|
190
|
+
const depositBuilt = await this.buildDepositIxForUser(amountLamports, vaultPda);
|
|
191
|
+
|
|
192
|
+
// 3) compute next transactionIndex (best-effort; see note below)
|
|
193
|
+
const ms = await multisig.accounts.Multisig.fromAccountAddress(connection, multisigPda);
|
|
194
|
+
const current = BigInt((ms as any).transactionIndex?.toString?.() ?? 0);
|
|
195
|
+
const transactionIndex = current + BigInt(1);
|
|
196
|
+
|
|
197
|
+
// 4) inner message uses vault as payer
|
|
198
|
+
const { blockhash } = await connection.getLatestBlockhash("confirmed");
|
|
199
|
+
const message = new TransactionMessage({
|
|
200
|
+
payerKey: vaultPda,
|
|
201
|
+
recentBlockhash: blockhash,
|
|
202
|
+
instructions: [depositBuilt.ix],
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
// 5) squads instructions (no rpc.*)
|
|
206
|
+
const createVaultTxIx = await multisig.instructions.vaultTransactionCreate({
|
|
207
|
+
multisigPda,
|
|
208
|
+
transactionIndex,
|
|
209
|
+
creator: wallet.publicKey,
|
|
210
|
+
vaultIndex,
|
|
211
|
+
ephemeralSigners: 0,
|
|
212
|
+
transactionMessage: message,
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
// const createProposalIx = await multisig.instructions.proposalCreate({
|
|
216
|
+
// multisigPda,
|
|
217
|
+
// transactionIndex,
|
|
218
|
+
// creator: wallet.publicKey,
|
|
219
|
+
// });
|
|
220
|
+
console.log('createVaultTxIx', createVaultTxIx);
|
|
221
|
+
|
|
222
|
+
|
|
223
|
+
// 6) return a normal Transaction for your pipeline
|
|
224
|
+
const tx = new Transaction().add(createVaultTxIx);
|
|
225
|
+
|
|
226
|
+
return { tx, transactionIndex, vaultPda };
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
async buildSquadsDepositProposalTx2(params: {
|
|
230
|
+
connection: Connection;
|
|
231
|
+
multisigPda: PublicKey;
|
|
232
|
+
amountLamports: bigint;
|
|
233
|
+
wallet: WalletLikeSigner; // your WalletLikeSigner type
|
|
234
|
+
vaultIndex?: number;
|
|
235
|
+
}): Promise<{
|
|
236
|
+
tx: Transaction;
|
|
237
|
+
transactionIndex: bigint;
|
|
238
|
+
vaultPda: PublicKey;
|
|
239
|
+
}> {
|
|
240
|
+
const { connection, multisigPda, amountLamports, wallet } = params;
|
|
241
|
+
const vaultIndex = params.vaultIndex ?? 0;
|
|
242
|
+
|
|
243
|
+
if (!wallet?.publicKey) throw new Error("wallet.publicKey missing");
|
|
244
|
+
if (!amountLamports || amountLamports <= BigInt(0)) throw new Error("amountLamports must be > 0");
|
|
245
|
+
|
|
246
|
+
// 1) vault PDA
|
|
247
|
+
const [vaultPda] = multisig.getVaultPda({ multisigPda, index: vaultIndex });
|
|
248
|
+
|
|
249
|
+
// 2) build deposit ix where `user` = vaultPda (off-curve OK)
|
|
250
|
+
const depositBuilt = await this.buildDepositIxForUser(amountLamports, vaultPda);
|
|
251
|
+
|
|
252
|
+
// 3) compute next transactionIndex (best-effort; see note below)
|
|
253
|
+
const ms = await multisig.accounts.Multisig.fromAccountAddress(connection, multisigPda);
|
|
254
|
+
const current = BigInt(ms.transactionIndex?.toString?.() ?? 0);
|
|
255
|
+
const transactionIndex = current + BigInt(1);
|
|
256
|
+
|
|
257
|
+
// 4) inner message uses vault as payer
|
|
258
|
+
const { blockhash } = await connection.getLatestBlockhash("confirmed");
|
|
259
|
+
const message = new TransactionMessage({
|
|
260
|
+
payerKey: vaultPda,
|
|
261
|
+
recentBlockhash: blockhash,
|
|
262
|
+
instructions: [depositBuilt.ix],
|
|
263
|
+
});
|
|
264
|
+
|
|
265
|
+
// 5) squads instructions (no rpc.*)
|
|
266
|
+
// const createVaultTxIx = await multisig.instructions.vaultTransactionCreate({
|
|
267
|
+
// multisigPda,
|
|
268
|
+
// transactionIndex,
|
|
269
|
+
// creator: wallet.publicKey,
|
|
270
|
+
// vaultIndex,
|
|
271
|
+
// ephemeralSigners: 0,
|
|
272
|
+
// transactionMessage: message,
|
|
273
|
+
// });
|
|
274
|
+
|
|
275
|
+
const createProposalIx = await multisig.instructions.proposalCreate({
|
|
276
|
+
multisigPda,
|
|
277
|
+
transactionIndex,
|
|
278
|
+
creator: wallet.publicKey,
|
|
279
|
+
});
|
|
280
|
+
|
|
281
|
+
// 6) return a normal Transaction for your pipeline
|
|
282
|
+
const tx = new Transaction().add(createProposalIx);
|
|
283
|
+
|
|
284
|
+
return { tx, transactionIndex, vaultPda };
|
|
156
285
|
}
|
|
157
286
|
|
|
287
|
+
|
|
288
|
+
|
|
158
289
|
/**
|
|
159
290
|
* Build a withdraw-request transaction:
|
|
160
291
|
* liqSOL -> SOL via liqsol_core::requestWithdraw.
|
|
@@ -187,7 +318,7 @@ export class DepositClient {
|
|
|
187
318
|
const userAta = getAssociatedTokenAddressSync(
|
|
188
319
|
liqsolMint,
|
|
189
320
|
user,
|
|
190
|
-
|
|
321
|
+
true,
|
|
191
322
|
TOKEN_2022_PROGRAM_ID,
|
|
192
323
|
);
|
|
193
324
|
|
|
@@ -208,7 +339,7 @@ export class DepositClient {
|
|
|
208
339
|
// -------------------------------------------------------------
|
|
209
340
|
// Need nextReceiptId from withdraw global state
|
|
210
341
|
// -------------------------------------------------------------
|
|
211
|
-
const globalAcct
|
|
342
|
+
const globalAcct: GlobalAccount = await this.program.account.global.fetch(global);
|
|
212
343
|
|
|
213
344
|
const rawId = globalAcct.nextReceiptId;
|
|
214
345
|
let receiptId: bigint;
|
|
@@ -241,7 +372,7 @@ export class DepositClient {
|
|
|
241
372
|
const nftAta = getAssociatedTokenAddressSync(
|
|
242
373
|
nftMint,
|
|
243
374
|
owner,
|
|
244
|
-
|
|
375
|
+
true,
|
|
245
376
|
TOKEN_2022_PROGRAM_ID,
|
|
246
377
|
);
|
|
247
378
|
|
|
@@ -289,4 +420,124 @@ export class DepositClient {
|
|
|
289
420
|
|
|
290
421
|
return new Transaction().add(ix);
|
|
291
422
|
}
|
|
292
|
-
|
|
423
|
+
|
|
424
|
+
|
|
425
|
+
|
|
426
|
+
|
|
427
|
+
|
|
428
|
+
/**
|
|
429
|
+
* Internal helper: build the liqsol_core::deposit instruction for a specific `user`.
|
|
430
|
+
* This is the exact same account wiring as buildDepositTx(), but returns the IX
|
|
431
|
+
* plus useful derived addresses/seed for Squads flows.
|
|
432
|
+
*/
|
|
433
|
+
private async buildDepositIxForUser(
|
|
434
|
+
amount: bigint,
|
|
435
|
+
user: PublicKey,
|
|
436
|
+
): Promise<{
|
|
437
|
+
ix: TransactionInstruction;
|
|
438
|
+
seed: number;
|
|
439
|
+
userAta: PublicKey;
|
|
440
|
+
ephemeralStake: PublicKey;
|
|
441
|
+
}> {
|
|
442
|
+
if (!user) {
|
|
443
|
+
throw new Error("buildDepositIxForUser: user is required");
|
|
444
|
+
}
|
|
445
|
+
if (!amount || amount <= BigInt(0)) {
|
|
446
|
+
throw new Error("buildDepositIxForUser: amount must be > 0");
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
// -------------------------------------------------------------
|
|
450
|
+
// PDAs
|
|
451
|
+
// -------------------------------------------------------------
|
|
452
|
+
const depositAuthority = deriveDepositAuthorityPda();
|
|
453
|
+
const liqsolMint = deriveLiqsolMintPda();
|
|
454
|
+
const liqsolMintAuthority = deriveLiqsolMintAuthorityPda();
|
|
455
|
+
const reservePool = deriveReservePoolPda();
|
|
456
|
+
const vault = deriveVaultPda();
|
|
457
|
+
const controllerState = deriveStakeControllerStatePda();
|
|
458
|
+
const payoutState = derivePayoutStatePda();
|
|
459
|
+
const bucketAuthority = deriveBucketAuthorityPda();
|
|
460
|
+
const payRateHistory = derivePayRateHistoryPda();
|
|
461
|
+
const globalConfig = deriveGlobalConfigPda();
|
|
462
|
+
|
|
463
|
+
// -------------------------------------------------------------
|
|
464
|
+
// Token-2022 ATAs
|
|
465
|
+
// -------------------------------------------------------------
|
|
466
|
+
const userAta = getAssociatedTokenAddressSync(
|
|
467
|
+
liqsolMint,
|
|
468
|
+
user,
|
|
469
|
+
true,
|
|
470
|
+
TOKEN_2022_PROGRAM_ID,
|
|
471
|
+
);
|
|
472
|
+
|
|
473
|
+
// -------------------------------------------------------------
|
|
474
|
+
// Distribution state + user_record (KEYED BY TOKEN ACCOUNT)
|
|
475
|
+
// -------------------------------------------------------------
|
|
476
|
+
const distributionState = deriveDistributionStatePda();
|
|
477
|
+
const userRecord = deriveUserRecordPda(userAta);
|
|
478
|
+
|
|
479
|
+
const bucketTokenAccount = getAssociatedTokenAddressSync(
|
|
480
|
+
liqsolMint,
|
|
481
|
+
bucketAuthority,
|
|
482
|
+
true,
|
|
483
|
+
TOKEN_2022_PROGRAM_ID,
|
|
484
|
+
);
|
|
485
|
+
|
|
486
|
+
// -------------------------------------------------------------
|
|
487
|
+
// Ephemeral stake
|
|
488
|
+
// -------------------------------------------------------------
|
|
489
|
+
const seed = Math.floor(Math.random() * 2 ** 32);
|
|
490
|
+
const ephemeralStake = await deriveEphemeralStakeAddress(user, seed);
|
|
491
|
+
|
|
492
|
+
// -------------------------------------------------------------
|
|
493
|
+
// BUILD IX (MUST MATCH IDL)
|
|
494
|
+
// -------------------------------------------------------------
|
|
495
|
+
const ix: TransactionInstruction = await this.program.methods
|
|
496
|
+
.deposit(new BN(amount.toString()), seed)
|
|
497
|
+
.accounts({
|
|
498
|
+
user,
|
|
499
|
+
depositAuthority,
|
|
500
|
+
systemProgram: SystemProgram.programId,
|
|
501
|
+
tokenProgram: TOKEN_2022_PROGRAM_ID,
|
|
502
|
+
associatedTokenProgram: ASSOCIATED_TOKEN_PROGRAM_ID,
|
|
503
|
+
liqsolProgram: PROGRAM_IDS.LIQSOL_TOKEN,
|
|
504
|
+
stakeProgram: StakeProgram.programId,
|
|
505
|
+
liqsolMint,
|
|
506
|
+
userAta,
|
|
507
|
+
liqsolMintAuthority,
|
|
508
|
+
reservePool,
|
|
509
|
+
vault,
|
|
510
|
+
ephemeralStake,
|
|
511
|
+
controllerState,
|
|
512
|
+
payoutState,
|
|
513
|
+
bucketAuthority,
|
|
514
|
+
bucketTokenAccount,
|
|
515
|
+
userRecord,
|
|
516
|
+
distributionState,
|
|
517
|
+
payRateHistory,
|
|
518
|
+
instructionsSysvar: SYSVAR_INSTRUCTIONS_PUBKEY,
|
|
519
|
+
clock: SYSVAR_CLOCK_PUBKEY,
|
|
520
|
+
stakeHistory: SYSVAR_STAKE_HISTORY_PUBKEY,
|
|
521
|
+
rent: SYSVAR_RENT_PUBKEY,
|
|
522
|
+
globalConfig,
|
|
523
|
+
})
|
|
524
|
+
.instruction();
|
|
525
|
+
|
|
526
|
+
return { ix, seed, userAta, ephemeralStake };
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
// A “wallet-adapter-like” shape (AnchorProvider.wallet matches this)
|
|
532
|
+
type WalletLikeSigner = {
|
|
533
|
+
publicKey: PublicKey;
|
|
534
|
+
signTransaction: (tx: Transaction) => Promise<Transaction>;
|
|
535
|
+
};
|
|
536
|
+
|
|
537
|
+
function isKeypairSigner(x: any): x is { publicKey: PublicKey; secretKey: Uint8Array } {
|
|
538
|
+
return !!x && x.publicKey instanceof PublicKey && x.secretKey instanceof Uint8Array;
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
function isWalletLikeSigner(x: any): x is WalletLikeSigner {
|
|
542
|
+
return !!x && x.publicKey instanceof PublicKey && typeof x.signTransaction === "function";
|
|
543
|
+
}
|
|
@@ -277,7 +277,7 @@ export class OutpostClient {
|
|
|
277
277
|
* if remainder > 0 → shares + 1
|
|
278
278
|
*/
|
|
279
279
|
static tokensToShares(amount: BN, currentIndex: BN): BN {
|
|
280
|
-
const numerator = amount.mul(INDEX_SCALE);
|
|
280
|
+
const numerator = amount.mul(new BN(INDEX_SCALE));
|
|
281
281
|
const shares = numerator.div(currentIndex);
|
|
282
282
|
const remainder = numerator.mod(currentIndex);
|
|
283
283
|
return remainder.eqn(0) ? shares : shares.addn(1);
|