@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.
@@ -7,17 +7,23 @@ import {
7
7
  PublicKey as SolPubKey,
8
8
  SystemProgram,
9
9
  Transaction,
10
+ TransactionInstruction,
11
+ TransactionMessage,
10
12
  TransactionSignature,
11
13
  } from '@solana/web3.js';
12
14
  import { AnchorProvider, BN } from '@coral-xyz/anchor';
13
15
  import { BaseSignerWalletAdapter } from '@solana/wallet-adapter-base';
14
16
  import {
15
17
  ASSOCIATED_TOKEN_PROGRAM_ID,
18
+ createAssociatedTokenAccountInstruction,
16
19
  getAssociatedTokenAddressSync,
17
20
  TOKEN_2022_PROGRAM_ID,
18
21
  } from '@solana/spl-token';
19
22
 
23
+ import * as multisig from "@sqds/multisig";
24
+
20
25
  import {
26
+ Base58,
21
27
  ChainID,
22
28
  ExternalNetwork,
23
29
  KeyType,
@@ -28,6 +34,7 @@ import {
28
34
  import {
29
35
  IStakingClient,
30
36
  Portfolio,
37
+ SquadsXConfig,
31
38
  StakerConfig,
32
39
  TrancheSnapshot,
33
40
  } from '../../types';
@@ -50,6 +57,7 @@ import {
50
57
  import { buildSolanaTrancheSnapshot, ceilDiv } from './utils';
51
58
  import { GlobalConfig, PayRateEntry, SolanaTransaction } from './types';
52
59
  import { SolanaProgramService } from './program';
60
+ import bs58 from 'bs58';
53
61
 
54
62
  const commitment: Commitment = 'confirmed';
55
63
 
@@ -78,6 +86,9 @@ export class SolanaStakingClient implements IStakingClient {
78
86
  public tokenClient: TokenClient;
79
87
  public program: SolanaProgramService
80
88
 
89
+ private smartAccount?: SolPubKey; // PDA (off-curve)
90
+ private signer?: SolPubKey; // on-curve signer
91
+
81
92
  get solPubKey(): SolPubKey {
82
93
  if (!this.pubKey) throw new Error('pubKey is undefined');
83
94
  return new SolPubKey(this.pubKey.data.array);
@@ -87,6 +98,18 @@ export class SolanaStakingClient implements IStakingClient {
87
98
  return this.config.network;
88
99
  }
89
100
 
101
+ get feePayer(): SolPubKey {
102
+ if (this.signer) return this.signer;
103
+ // fallback for normal wallets
104
+ if (this.anchor.wallet.publicKey) return this.anchor.wallet.publicKey;
105
+ throw new Error('No signing authority available');
106
+ }
107
+
108
+ get squadsX(): SquadsXConfig | null {
109
+ const config = this.config.extras?.squadsX;
110
+ return config ?? null;
111
+ }
112
+
90
113
  constructor(private config: StakerConfig) {
91
114
  const adapter = config.provider as BaseSignerWalletAdapter | undefined;
92
115
 
@@ -197,6 +220,83 @@ export class SolanaStakingClient implements IStakingClient {
197
220
  // IStakingClient core methods
198
221
  // ---------------------------------------------------------------------
199
222
 
223
+
224
+ async createVaultLiqsolAtaOneShot(params: {
225
+ connection: Connection;
226
+ payer: SolPubKey; // user's wallet pubkey (signer)
227
+ vaultPda: SolPubKey; // squads vault PDA (off-curve owner)
228
+ }): Promise<{ tx: Transaction; vaultAta: SolPubKey } | null> {
229
+ const { connection, payer, vaultPda } = params;
230
+
231
+ const liqsolMint = deriveLiqsolMintPda();
232
+
233
+ const vaultAta = getAssociatedTokenAddressSync(
234
+ liqsolMint,
235
+ vaultPda,
236
+ true, // allowOwnerOffCurve
237
+ TOKEN_2022_PROGRAM_ID,
238
+ ASSOCIATED_TOKEN_PROGRAM_ID,
239
+ );
240
+
241
+ // If it already exists, just no-op
242
+ const info = await connection.getAccountInfo(vaultAta, "confirmed");
243
+ console.log('info?', info);
244
+
245
+ if (info) return null;
246
+
247
+ const ix = createAssociatedTokenAccountInstruction(
248
+ payer, // payer = user
249
+ vaultAta, // ata address
250
+ vaultPda, // owner = vault
251
+ liqsolMint,
252
+ TOKEN_2022_PROGRAM_ID,
253
+ ASSOCIATED_TOKEN_PROGRAM_ID,
254
+ );
255
+
256
+ const tx = new Transaction().add(ix);
257
+ return { tx, vaultAta };
258
+ }
259
+
260
+ private async prepSquadsIxs(ix: TransactionInstruction): Promise<TransactionInstruction[]> {
261
+ if (!this.squadsX) throw new Error('Attempting to wrap Squads instruction without SquadsX config');
262
+
263
+ const multisigPda = this.squadsMultisigPDA!;
264
+ const vaultPda = this.squadsVaultPDA!;
265
+ const vaultIndex = this.squadsX?.vaultIndex ?? 0;
266
+ const creator = this.solPubKey;
267
+
268
+ // compute next transactionIndex
269
+ const ms = await multisig.accounts.Multisig.fromAccountAddress(this.connection, multisigPda);
270
+ const current = BigInt(ms.transactionIndex?.toString() ?? 0);
271
+ const transactionIndex = current + BigInt(1);
272
+
273
+ // inner message uses vault as payer
274
+ // const cuIx = ComputeBudgetProgram.setComputeUnitLimit({ units: 300_000 });
275
+ const { blockhash } = await this.connection.getLatestBlockhash("confirmed");
276
+ const transactionMessage = new TransactionMessage({
277
+ payerKey: vaultPda,
278
+ recentBlockhash: blockhash,
279
+ instructions: [ix],
280
+ });
281
+
282
+ const createVaultTxIx = await multisig.instructions.vaultTransactionCreate({
283
+ multisigPda,
284
+ transactionIndex,
285
+ creator,
286
+ vaultIndex,
287
+ transactionMessage,
288
+ ephemeralSigners: 0,
289
+ });
290
+
291
+ const createProposalIx = await multisig.instructions.proposalCreate({
292
+ multisigPda,
293
+ transactionIndex,
294
+ creator,
295
+ });
296
+
297
+ return [createVaultTxIx, createProposalIx];
298
+ }
299
+
200
300
  /**
201
301
  * Deposit native SOL into liqSOL (liqsol_core::deposit).
202
302
  * Handles tx build, sign, send, and confirmation.
@@ -208,18 +308,51 @@ export class SolanaStakingClient implements IStakingClient {
208
308
  }
209
309
 
210
310
  try {
211
- // Build compute budget increase instruction
212
311
  const cuIx = ComputeBudgetProgram.setComputeUnitLimit({ units: 400_000 });
312
+ // console.log('amountLamports', amountLamports);
313
+
314
+ if (!!this.squadsX) {
315
+
316
+ const createVaultTx = await this.createVaultLiqsolAtaOneShot({
317
+ connection: this.connection,
318
+ payer: this.solPubKey,
319
+ vaultPda: this.squadsVaultPDA!,
320
+ })
321
+
322
+ if (createVaultTx !== null) {
323
+ console.log('need to create vault ata first...');
324
+ const tx0 = new Transaction().add(createVaultTx.tx);
325
+ const prepared0 = await this.prepareTx(tx0);
326
+ const signed0 = await this.signTransaction(prepared0.tx);
327
+ const sent0 = await this.sendAndConfirmHttp(signed0, prepared0);
328
+ console.log('create Vault ATA', sent0);
329
+ }
213
330
 
214
- // Build the Outpost synd instruction
215
- const ix = await this.depositClient.buildDepositTx(amountLamports);
331
+ const ix = await this.depositClient.buildDepositTx(amountLamports, this.squadsVaultPDA!)
332
+ const squadIxs = await this.prepSquadsIxs(ix)
216
333
 
217
- // Wrap in a transaction and send
218
- const tx = new Transaction().add(cuIx, ix);
219
- const prepared = await this.prepareTx(tx);
220
- const signed = await this.signTransaction(prepared.tx);
334
+ const tx1 = new Transaction().add(cuIx, squadIxs[0]);
335
+ const prepared1 = await this.prepareTx(tx1);
336
+ const signed1 = await this.signTransaction(prepared1.tx);
337
+ const sent1 = await this.sendAndConfirmHttp(signed1, prepared1);
338
+ console.log('SENT 1', sent1);
221
339
 
222
- return this.sendAndConfirmHttp(signed, prepared);
340
+ const tx2 = new Transaction().add(cuIx, squadIxs[1]);
341
+ const prepared2 = await this.prepareTx(tx2);
342
+ const signed2 = await this.signTransaction(prepared2.tx);
343
+ const sent2 = await this.sendAndConfirmHttp(signed2, prepared2);
344
+ console.log('SENT 2', sent2);
345
+
346
+ return sent2;
347
+ }
348
+ else {
349
+ const ix = await this.depositClient.buildDepositTx(amountLamports)
350
+ const tx = new Transaction().add(ix);
351
+ const prepared = await this.prepareTx(tx);
352
+ const signed = await this.signTransaction(prepared.tx);
353
+
354
+ return this.sendAndConfirmHttp(signed, prepared);
355
+ }
223
356
  } catch (err) {
224
357
  throw new Error(`Failed to deposit Solana: ${err}`);
225
358
  }
@@ -361,7 +494,9 @@ export class SolanaStakingClient implements IStakingClient {
361
494
  if (!this.pubKey) throw new Error('User pubKey is undefined');
362
495
 
363
496
  try {
364
- const user = this.solPubKey;
497
+ const user = !!this.squadsX ? this.squadsVaultPDA! : this.solPubKey;
498
+
499
+ console.log('get portfolio for user', user.toBase58());
365
500
 
366
501
  const reservePoolPDA = deriveReservePoolPda();
367
502
  const vaultPDA = deriveVaultPda();
@@ -370,7 +505,7 @@ export class SolanaStakingClient implements IStakingClient {
370
505
  const userLiqsolAta = getAssociatedTokenAddressSync(
371
506
  liqsolMint,
372
507
  user,
373
- false,
508
+ true, //set to true to allow off curve (e.g. PDA for squadsx wallet)
374
509
  TOKEN_2022_PROGRAM_ID,
375
510
  ASSOCIATED_TOKEN_PROGRAM_ID,
376
511
  );
@@ -510,6 +645,21 @@ export class SolanaStakingClient implements IStakingClient {
510
645
  return this.distributionClient.getUserRecord(this.solPubKey);
511
646
  }
512
647
 
648
+ // ---------------------------------------------------------------------
649
+ // SquadsX Helpers
650
+ // ---------------------------------------------------------------------
651
+
652
+ get squadsMultisigPDA(): SolPubKey | null {
653
+ if (!this.squadsX) return null;
654
+ return new SolPubKey(this.squadsX.multisigPDA);
655
+ }
656
+ get squadsVaultPDA(): SolPubKey | null {
657
+ if (!this.squadsX || !this.squadsMultisigPDA) return null;
658
+ const multisigPda = this.squadsMultisigPDA;
659
+ const index = this.squadsX.vaultIndex ?? 0;
660
+ const pda = multisig.getVaultPda({ multisigPda, index });
661
+ return pda[0];
662
+ }
513
663
 
514
664
  // ---------------------------------------------------------------------
515
665
  // READ-ONLY Public Methods
@@ -690,7 +840,7 @@ export class SolanaStakingClient implements IStakingClient {
690
840
  return BigInt(0);
691
841
  }
692
842
 
693
- const [avgPayRate, globalConfig]: [BN, GlobalConfig] = await Promise.all([
843
+ const [avgPayRate, globalConfig]: [BN, GlobalConfig | null] = await Promise.all([
694
844
  this.distributionClient.getAverageScaledPayRate(windowSize),
695
845
  this.distributionClient.getGlobalConfig(),
696
846
  ]);
@@ -741,14 +891,12 @@ export class SolanaStakingClient implements IStakingClient {
741
891
  }): Promise<bigint> {
742
892
  this.ensureUser();
743
893
 
744
- const payer = this.solPubKey;
745
-
746
894
  // -------------------------------------------------------------
747
895
  // 1) Current wallet balance (prefer caller override)
748
896
  // -------------------------------------------------------------
749
897
  const balanceLamports: bigint =
750
898
  options?.balanceOverrideLamports ??
751
- BigInt(await this.connection.getBalance(payer, commitment));
899
+ BigInt(await this.connection.getBalance(this.feePayer, commitment));
752
900
 
753
901
  if (balanceLamports <= BigInt(0)) {
754
902
  return BigInt(0);
@@ -859,7 +1007,7 @@ export class SolanaStakingClient implements IStakingClient {
859
1007
  return this.cachedTxFee.value;
860
1008
  }
861
1009
 
862
- const payer = this.solPubKey;
1010
+ const payer = this.feePayer;
863
1011
 
864
1012
  const dummyIx = SystemProgram.transfer({
865
1013
  fromPubkey: payer,
@@ -961,7 +1109,7 @@ export class SolanaStakingClient implements IStakingClient {
961
1109
  const { blockhash, lastValidBlockHeight } =
962
1110
  await this.connection.getLatestBlockhash('confirmed');
963
1111
  tx.recentBlockhash = blockhash;
964
- tx.feePayer = this.solPubKey;
1112
+ tx.feePayer = this.feePayer;
965
1113
  return { tx, blockhash, lastValidBlockHeight };
966
1114
  }
967
1115
 
@@ -970,17 +1118,27 @@ export class SolanaStakingClient implements IStakingClient {
970
1118
  * Ensures we have a Wire pubKey and an Anchor wallet pubKey, and that they match.
971
1119
  */
972
1120
  ensureUser() {
973
- if (!this.pubKey || !this.anchor.wallet.publicKey) {
974
- throw new Error('User Authorization required: pubKey is undefined');
975
- }
976
- if (
977
- this.solPubKey.toBase58() !==
978
- this.anchor.wallet.publicKey.toBase58()
979
- ) {
980
- throw new Error(
981
- 'Write access requires connected wallet to match pubKey',
982
- );
1121
+ if (!this.pubKey) throw new Error('User pubKey is undefined');
1122
+
1123
+ const wallet = this.anchor?.wallet as any;
1124
+ const pk = wallet?.publicKey as SolPubKey | undefined;
1125
+
1126
+ if (!pk) throw new Error('Wallet not connected');
1127
+ if (typeof wallet.signTransaction !== 'function') {
1128
+ throw new Error('Wallet does not support signTransaction');
983
1129
  }
1130
+
1131
+ // if (!this.pubKey || !this.anchor.wallet.publicKey) {
1132
+ // throw new Error('User Authorization required: pubKey is undefined');
1133
+ // }
1134
+ // if (
1135
+ // this.solPubKey.toBase58() !==
1136
+ // this.anchor.wallet.publicKey.toBase58()
1137
+ // ) {
1138
+ // throw new Error(
1139
+ // 'Write access requires connected wallet to match pubKey',
1140
+ // );
1141
+ // }
984
1142
  }
985
1143
 
986
1144
  }
@@ -262,7 +262,7 @@ export type GlobalState = {
262
262
  roleWarmupDuration: BN;
263
263
 
264
264
  /** Wire lifecycle state (preLaunch / postLaunch / refund) */
265
- wireState: WireState;
265
+ wireState: WireState | any;
266
266
 
267
267
  /** PDA bump */
268
268
  bump: number;
@@ -615,7 +615,7 @@ export type LeaderboardState = {
615
615
  * Per-validator scores (i64 on-chain).
616
616
  * Index corresponds to `registryIndex` in ValidatorRecord.
617
617
  */
618
- scores: BN[];
618
+ scores: number[];
619
619
 
620
620
  /**
621
621
  * Sorted indices into `scores[]` / `voteAccounts[]`,
@@ -628,7 +628,7 @@ export type LeaderboardState = {
628
628
  * `voteAccounts[i]` is the vote account for validator with
629
629
  * `registryIndex == i`.
630
630
  */
631
- voteAccounts: PublicKey[];
631
+ voteAccounts: { bytes: number[]; }[];
632
632
 
633
633
  /**
634
634
  * Number of active validators currently tracked in the leaderboard.
@@ -643,7 +643,7 @@ export type LeaderboardState = {
643
643
  * Reserved padding / future-proofing on-chain (u64[8]).
644
644
  * Not used by client logic, but surfaced for completeness.
645
645
  */
646
- padding: BN[];
646
+ padding: number[];
647
647
  };
648
648
 
649
649
  /**
package/src/staker.ts CHANGED
@@ -36,12 +36,12 @@ export class Staker {
36
36
 
37
37
  config.forEach((cfg) => {
38
38
  switch (cfg.network.chainId) {
39
- case SolChainID.Mainnet:
39
+ // case SolChainID.Mainnet:
40
40
  case SolChainID.Devnet:
41
41
  this.clients.set(cfg.network.chainId, new SolanaStakingClient(cfg));
42
42
  break;
43
43
 
44
- case EvmChainID.Ethereum:
44
+ // case EvmChainID.Ethereum:
45
45
  case EvmChainID.Hoodi:
46
46
  this.clients.set(cfg.network.chainId, new EthereumStakingClient(cfg));
47
47
  break;
package/src/types.ts CHANGED
@@ -7,8 +7,16 @@ export type StakerConfig = {
7
7
  network: ExternalNetwork;
8
8
  provider?: BaseSignerWalletAdapter | ethers.providers.Web3Provider;
9
9
  pubKey?: PublicKey;
10
+ extras?: {
11
+ squadsX?: SquadsXConfig;
12
+ }
10
13
  }
11
14
 
15
+ export type SquadsXConfig = {
16
+ multisigPDA: string; // REQUIRED, base58 multisig address string
17
+ vaultIndex?: number; // default 0
18
+ };
19
+
12
20
  export interface IStakingClient {
13
21
  pubKey?: PublicKey;
14
22
  network: ExternalNetwork;