@wireio/stake 0.0.6 → 0.1.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 (48) hide show
  1. package/README.md +260 -13
  2. package/lib/stake.browser.js +4861 -4218
  3. package/lib/stake.browser.js.map +1 -1
  4. package/lib/stake.d.ts +434 -6484
  5. package/lib/stake.js +5059 -4371
  6. package/lib/stake.js.map +1 -1
  7. package/lib/stake.m.js +4861 -4218
  8. package/lib/stake.m.js.map +1 -1
  9. package/package.json +2 -2
  10. package/src/assets/solana/idl/liqsol_core.json +4239 -0
  11. package/src/assets/solana/idl/liqsol_token.json +183 -0
  12. package/src/assets/solana/idl/validator_leaderboard.json +296 -250
  13. package/src/assets/solana/types/liqsol_core.ts +4245 -0
  14. package/src/assets/solana/types/liqsol_token.ts +189 -0
  15. package/src/assets/solana/types/validator_leaderboard.ts +296 -250
  16. package/src/index.ts +2 -5
  17. package/src/networks/ethereum/contract.ts +138 -36
  18. package/src/networks/ethereum/ethereum.ts +167 -38
  19. package/src/networks/ethereum/types.ts +32 -1
  20. package/src/networks/solana/clients/deposit.client.ts +92 -139
  21. package/src/networks/solana/clients/distribution.client.ts +302 -178
  22. package/src/networks/solana/clients/leaderboard.client.ts +40 -160
  23. package/src/networks/solana/constants.ts +238 -69
  24. package/src/networks/solana/program.ts +27 -93
  25. package/src/networks/solana/solana.ts +181 -36
  26. package/src/networks/solana/types.ts +47 -0
  27. package/src/networks/solana/utils.ts +522 -93
  28. package/src/scripts/fetch-artifacts.sh +24 -0
  29. package/src/scripts/tsconfig.json +17 -0
  30. package/src/staker/staker.ts +35 -30
  31. package/src/staker/types.ts +25 -22
  32. package/src/assets/solana/idl/deposit.json +0 -260
  33. package/src/assets/solana/idl/distribution.json +0 -736
  34. package/src/assets/solana/idl/liq_sol_token.json +0 -275
  35. package/src/assets/solana/idl/stake_controller.json +0 -1788
  36. package/src/assets/solana/idl/stake_registry.json +0 -435
  37. package/src/assets/solana/idl/treasury.json +0 -336
  38. package/src/assets/solana/idl/validator_registry.json +0 -418
  39. package/src/assets/solana/idl/yield_oracle.json +0 -32
  40. package/src/assets/solana/types/deposit.ts +0 -266
  41. package/src/assets/solana/types/distribution.ts +0 -742
  42. package/src/assets/solana/types/liq_sol_token.ts +0 -281
  43. package/src/assets/solana/types/stake_controller.ts +0 -1794
  44. package/src/assets/solana/types/stake_registry.ts +0 -441
  45. package/src/assets/solana/types/treasury.ts +0 -342
  46. package/src/assets/solana/types/validator_registry.ts +0 -424
  47. package/src/assets/solana/types/yield_oracle.ts +0 -38
  48. package/src/utils.ts +0 -9
@@ -1,230 +1,354 @@
1
- // src/solana/clients/DistributionClient.ts
2
- import { AnchorProvider, Program, BN, web3 } from '@coral-xyz/anchor';
1
+ import { AnchorProvider, Program } from '@coral-xyz/anchor';
3
2
  import {
3
+ ParsedAccountData,
4
4
  PublicKey,
5
+ SYSVAR_INSTRUCTIONS_PUBKEY,
5
6
  Transaction,
6
- TransactionInstruction,
7
7
  SystemProgram,
8
- VersionedTransaction,
9
8
  } from '@solana/web3.js';
10
9
  import {
11
10
  TOKEN_2022_PROGRAM_ID,
12
- ASSOCIATED_TOKEN_PROGRAM_ID,
13
- getAssociatedTokenAddressSync,
11
+ getAssociatedTokenAddress,
14
12
  } from '@solana/spl-token';
13
+
14
+ import { SolanaProgramService } from '../program';
15
+ import type { LiqsolCore } from '../../../assets/solana/types/liqsol_core';
15
16
  import {
16
- DISTRIBUTION_PROGRAM_ID,
17
- STAKE_CONTROLLER_PROGRAM_ID,
18
- DistributionIDL,
19
- Distribution,
20
- YIELD_ORACLE_PROGRAM_ID,
21
- LIQSOL_TOKEN_PROGRAM_ID,
17
+ deriveBucketAuthorityPda,
18
+ deriveDistributionStatePda,
19
+ deriveLiqsolMintPda,
20
+ derivePayRateHistoryPda,
21
+ deriveUserRecordPda,
22
22
  } from '../constants';
23
- import {
24
- deriveDistributionStatePDA,
25
- deriveUserRecordPDA,
26
- deriveLiqsolMintAuthorityPDA,
27
- } from '../utils';
23
+ import type { CorrectRegisterBuildResult, DistributionState, MismatchCandidate, ParsedAccountInfo, UserRecord } from '../types';
28
24
 
25
+ /**
26
+ * Distribution client – wraps the distribution portion of the liqsol_core
27
+ * program. Responsible for:
28
+ * - Reading DistributionState + UserRecord
29
+ * - Computing mismatch candidates (tracked > actual)
30
+ * - Building a "correct then register" transaction for the caller
31
+ *
32
+ * Aligned with the on-chain `update_user` script:
33
+ * - Single `updateUser()` entrypoint that:
34
+ * * Can create userRecord if missing
35
+ * * Reconciles tracked vs actual using user, userAta, bucket, pay-rate
36
+ */
29
37
  export class DistributionClient {
30
- constructor(private provider: AnchorProvider) { }
31
-
32
- /** Wrapped Anchor Program for Distribution */
33
- private get program(): Program<Distribution> {
34
- const idlWithAddress = {
35
- ...JSON.parse(JSON.stringify(DistributionIDL)),
36
- address: DISTRIBUTION_PROGRAM_ID.toString(),
37
- };
38
- return new Program(idlWithAddress as any, this.provider) as Program<Distribution>;
39
- }
38
+ private program: Program<LiqsolCore>;
40
39
 
41
- /** Derive the PDA for global distribution state */
42
- deriveStatePDA(): PublicKey {
43
- return deriveDistributionStatePDA()[0];
40
+ constructor(private provider: AnchorProvider) {
41
+ const svc = new SolanaProgramService(provider);
42
+ this.program = svc.getProgram('liqsolCore');
44
43
  }
45
44
 
46
- /** Derive the PDA for a user’s record */
47
- deriveUserRecordPDA(user: PublicKey): PublicKey {
48
- return deriveUserRecordPDA(user)[0];
45
+ get connection() {
46
+ return this.provider.connection;
49
47
  }
50
48
 
51
- /** Fetch on-chain distribution state */
52
- async getState(): Promise<any> {
53
- return this.program.account.distributionState.fetch(this.deriveStatePDA());
49
+ /**
50
+ * Fetch the global distribution state account.
51
+ */
52
+ async getDistributionState(): Promise<DistributionState | null> {
53
+ const pda = deriveDistributionStatePda();
54
+ try {
55
+ // IDL account name: "distributionState"
56
+ return await this.program.account.distributionState.fetch(pda);
57
+ } catch {
58
+ return null;
59
+ }
54
60
  }
55
61
 
56
- /** Fetch a user’s record or return null if it doesn’t exist */
57
- async getUserRecord(user: PublicKey): Promise<any | null> {
62
+ /**
63
+ * Fetch a user's distribution userRecord (or null if missing).
64
+ */
65
+ async getUserRecord(user: PublicKey): Promise<UserRecord | null> {
66
+ const pda = deriveUserRecordPda(user);
58
67
  try {
59
- return await this.program.account.userRecord.fetch(
60
- this.deriveUserRecordPDA(user)
61
- );
68
+ return await this.program.account.userRecord.fetchNullable(pda);
62
69
  } catch {
63
70
  return null;
64
71
  }
65
72
  }
66
73
 
67
- /** Build an `initialize` transaction for the distribution program */
68
- async buildInitializeTransaction(user: PublicKey): Promise<Transaction> {
69
- const [statePda] = deriveDistributionStatePDA();
70
- const ix = await this.program.methods
71
- .initialize()
72
- .accounts({
73
- authority: user,
74
- distributionState: statePda,
75
- systemProgram: SystemProgram.programId,
76
- rent: web3.SYSVAR_RENT_PUBKEY,
77
- } as any)
78
- .instruction();
79
-
80
- return new Transaction().add(ix);
81
- }
74
+ /**
75
+ * Helper: get actual liqSOL balances for all token holders.
76
+ *
77
+ * Returns a map: owner (base58) -> actual balance (BigInt)
78
+ */
79
+ private async getActualBalancesByOwner(): Promise<Map<string, bigint>> {
80
+ const liqsolMint = deriveLiqsolMintPda();
81
+ const mintStr = liqsolMint.toBase58();
82
82
 
83
- /** Build an `updateUser` transaction (register or refresh a user record) */
84
- async buildUpdateUserTransaction(user: PublicKey): Promise<Transaction> {
85
- const statePda = this.deriveStatePDA();
86
- const userPda = this.deriveUserRecordPDA(user);
87
-
88
- // Fetch state to get the liqSOL mint address
89
- const state: any = await this.getState();
90
- const liqsolMint: PublicKey = state.liqsolMint;
91
- const userAta = getAssociatedTokenAddressSync(
92
- liqsolMint,
93
- user,
94
- false,
83
+ const accounts = await this.connection.getParsedProgramAccounts(
95
84
  TOKEN_2022_PROGRAM_ID,
96
- ASSOCIATED_TOKEN_PROGRAM_ID
85
+ {
86
+ filters: [
87
+ // SPL token layout: mint at offset 0
88
+ { memcmp: { offset: 0, bytes: mintStr } },
89
+ ],
90
+ commitment: 'confirmed',
91
+ },
97
92
  );
98
93
 
99
- const [stakeControllerStatePda] = PublicKey.findProgramAddressSync(
100
- [Buffer.from('stake_controller')],
101
- STAKE_CONTROLLER_PROGRAM_ID
102
- );
94
+ const byOwner = new Map<string, bigint>();
103
95
 
104
- const ix = await this.program.methods
105
- .updateUser()
106
- .accounts({
107
- user,
108
- userAta,
109
- userRecord: userPda,
110
- authority: user,
111
- payer: user,
112
- distributionState: statePda,
113
- stakeControllerState: stakeControllerStatePda,
114
- tokenProgram: TOKEN_2022_PROGRAM_ID,
115
- yieldOracleProgram: YIELD_ORACLE_PROGRAM_ID,
116
- systemProgram: SystemProgram.programId,
117
- } as any)
118
- .instruction();
96
+ for (const acct of accounts) {
97
+ const data = acct.account.data as ParsedAccountData;
98
+ const parsed = data.parsed;
99
+ if (!parsed || parsed.type !== 'account') continue;
100
+
101
+ const info: ParsedAccountInfo = parsed.info;
102
+ const ownerStr = info.owner;
103
+ const amountStr = info.tokenAmount.amount;
104
+ const amount = BigInt(amountStr);
105
+
106
+ const prev = byOwner.get(ownerStr) ?? BigInt(0);
107
+ byOwner.set(ownerStr, prev + amount);
108
+ }
119
109
 
120
- return new Transaction().add(ix);
110
+ return byOwner;
121
111
  }
122
112
 
123
- /** Build a `withdraw` transaction */
124
- async buildWithdrawTransaction(user: PublicKey, amount: number): Promise<Transaction> {
125
- const statePda = this.deriveStatePDA();
126
- const userPda = this.deriveUserRecordPDA(user);
127
-
128
- // Fetch state for the mint
129
- const state: any = await this.getState();
130
- const liqsolMint: PublicKey = state.liqsolMint;
131
- const userAta = getAssociatedTokenAddressSync(
132
- liqsolMint,
133
- user,
134
- false,
135
- TOKEN_2022_PROGRAM_ID,
136
- ASSOCIATED_TOKEN_PROGRAM_ID
137
- );
113
+ /**
114
+ * Helper: get tracked balances from all userRecord accounts,
115
+ * keyed by *actual wallet owner*, not the userRecord PDA.
116
+ *
117
+ * userRecord struct:
118
+ * - userAta: pubkey
119
+ * - trackedBalance: u64
120
+ * - claimBalance: u64
121
+ * - lastClaimTimestamp: i64
122
+ * - bump: u8
123
+ */
124
+ private async getTrackedBalances(): Promise<
125
+ Map<string, { owner: PublicKey; tracked: bigint }>
126
+ > {
127
+ const records = await this.program.account.userRecord.all();
128
+ const map = new Map<string, { owner: PublicKey; tracked: bigint }>();
138
129
 
139
- const [stakeControllerStatePda] = PublicKey.findProgramAddressSync(
140
- [Buffer.from('stake_controller')],
141
- STAKE_CONTROLLER_PROGRAM_ID
142
- );
143
- const [controllerAuthPda] = PublicKey.findProgramAddressSync(
144
- [Buffer.from('stake_authority')],
145
- STAKE_CONTROLLER_PROGRAM_ID
146
- );
130
+ for (const r of records) {
131
+ const ur = r.account as UserRecord;
132
+ const userAta = ur.userAta as PublicKey;
147
133
 
148
- const ix = await this.program.methods
149
- .withdraw(new BN(amount))
150
- .accounts({
151
- user,
152
- userAta,
153
- userRecord: userPda,
154
- distributionState: statePda,
155
- liqsolMint,
156
- tokenProgram: TOKEN_2022_PROGRAM_ID,
157
- stakeControllerProgram: STAKE_CONTROLLER_PROGRAM_ID,
158
- stakeControllerState: stakeControllerStatePda,
159
- controllerAuthority: controllerAuthPda,
160
- yieldOracleProgram: YIELD_ORACLE_PROGRAM_ID,
161
- systemProgram: SystemProgram.programId,
162
- } as any)
163
- .instruction();
134
+ // Resolve the *wallet* that owns this ATA
135
+ const ataInfo = await this.connection.getParsedAccountInfo(userAta);
136
+ const parsedData = ataInfo.value?.data as ParsedAccountData | undefined;
137
+ if (!parsedData) continue;
138
+
139
+ const parsed = parsedData.parsed;
140
+ if (!parsed || (parsed as any).type !== 'account') continue;
141
+
142
+ const info: ParsedAccountInfo = parsed.info;
143
+ const ownerStr = info.owner;
144
+ const owner = new PublicKey(ownerStr);
145
+
146
+ const tracked = BigInt(ur.trackedBalance.toString());
147
+ map.set(ownerStr, { owner, tracked });
148
+ }
164
149
 
165
- return new Transaction().add(ix);
150
+ return map;
166
151
  }
167
152
 
168
- /** Build a `claimRewards` transaction */
169
- async buildClaimRewardsTransaction(user: PublicKey): Promise<Transaction> {
170
- const statePda = this.deriveStatePDA();
171
- const userPda = this.deriveUserRecordPDA(user);
172
-
173
- // Fetch state for the mint
174
- const state: any = await this.getState();
175
- const liqsolMint: PublicKey = state.liqsolMint;
176
- const userAta = getAssociatedTokenAddressSync(
177
- liqsolMint,
178
- user,
153
+ /**
154
+ * Discover all mismatch candidates where tracked > actual.
155
+ *
156
+ * - actual balances are derived from token accounts for the liqSOL mint
157
+ * - tracked balances come from Distribution.userRecord
158
+ */
159
+ private async deriveMismatchCandidates(): Promise<MismatchCandidate[]> {
160
+ const [actualByOwner, trackedByOwner] = await Promise.all([
161
+ this.getActualBalancesByOwner(),
162
+ this.getTrackedBalances(),
163
+ ]);
164
+
165
+ const out: MismatchCandidate[] = [];
166
+
167
+ for (const [ownerStr, { owner, tracked }] of trackedByOwner.entries()) {
168
+ const actual = actualByOwner.get(ownerStr) ?? BigInt(0);
169
+ const delta = tracked - actual;
170
+ if (delta > BigInt(0)) {
171
+ out.push({ owner, actual, tracked, delta });
172
+ }
173
+ }
174
+
175
+ // Largest discrepancy first
176
+ out.sort((a, b) => (b.delta > a.delta ? 1 : b.delta < a.delta ? -1 : 0));
177
+ return out;
178
+ }
179
+
180
+ /**
181
+ * Canonical helper to build an `updateUser` instruction for a given user,
182
+ * matching the standalone update-user.ts script.
183
+ */
184
+ private async buildUpdateUserIx(targetUser: PublicKey) {
185
+ const walletPk = this.provider.wallet.publicKey;
186
+
187
+ const distributionStatePDA = deriveDistributionStatePda();
188
+ const userRecordPDA = deriveUserRecordPda(targetUser);
189
+ const liqsolMintPDA = deriveLiqsolMintPda();
190
+ const payRateHistoryPDA = derivePayRateHistoryPda();
191
+ const bucketAuthorityPDA = deriveBucketAuthorityPda();
192
+
193
+ const userAta = await getAssociatedTokenAddress(
194
+ liqsolMintPDA,
195
+ targetUser,
179
196
  false,
180
197
  TOKEN_2022_PROGRAM_ID,
181
- ASSOCIATED_TOKEN_PROGRAM_ID
182
198
  );
183
- const [mintAuthPda] = deriveLiqsolMintAuthorityPDA();
184
199
 
185
- const ix = await this.program.methods
186
- .claimRewards()
200
+ const bucketTokenAccount = await getAssociatedTokenAddress(
201
+ liqsolMintPDA,
202
+ bucketAuthorityPDA,
203
+ true, // allowOwnerOffCurve
204
+ TOKEN_2022_PROGRAM_ID,
205
+ );
206
+
207
+ return (this.program.methods as any)
208
+ .updateUser()
187
209
  .accounts({
188
- user,
210
+ user: targetUser,
189
211
  userAta,
190
- userRecord: userPda,
191
- distributionState: statePda,
192
- liqsolMint,
193
- liqsolProgram: LIQSOL_TOKEN_PROGRAM_ID,
194
- liqsolMintAuthority: mintAuthPda,
195
- instructionsSysvar: web3.SYSVAR_INSTRUCTIONS_PUBKEY,
196
- yieldOracleProgram: YIELD_ORACLE_PROGRAM_ID,
212
+ userRecord: userRecordPDA,
213
+ authority: walletPk,
214
+ payer: walletPk,
215
+ distributionState: distributionStatePDA,
216
+ liqsolMint: liqsolMintPDA,
217
+ instructionsSysvar: SYSVAR_INSTRUCTIONS_PUBKEY,
197
218
  tokenProgram: TOKEN_2022_PROGRAM_ID,
198
- associatedTokenProgram: ASSOCIATED_TOKEN_PROGRAM_ID,
219
+ bucketAuthority: bucketAuthorityPDA,
220
+ bucketTokenAccount,
221
+ payRateHistory: payRateHistoryPDA,
199
222
  systemProgram: SystemProgram.programId,
200
- } as any)
223
+ })
201
224
  .instruction();
202
-
203
- return new Transaction().add(ix);
204
225
  }
205
226
 
206
- /** Send & confirm a single-IX transaction; returns the tx signature */
207
- async sendTransaction(
208
- tx: Transaction,
209
- signers: import('@solana/web3.js').Signer[] = []
210
- ): Promise<string> {
211
- tx.feePayer = this.provider.wallet.publicKey;
212
- const { blockhash } = await this.provider.connection.getLatestBlockhash();
213
- tx.recentBlockhash = blockhash;
214
- return this.provider.sendAndConfirm(tx, signers);
215
- }
227
+ /**
228
+ * Build the "correct & register" transaction.
229
+ *
230
+ * - Fetches DistributionState + all userRecords + token holders
231
+ * - Computes caller's untracked amount (actual - tracked)
232
+ * - If DistributionState.availableBalance already covers that, we only
233
+ * send updateUser(caller).
234
+ * - Otherwise we select top mismatch candidates until their freed deltas
235
+ * cover the shortfall, then build updateUser(target) for each,
236
+ * followed by updateUser(caller).
237
+ *
238
+ * NOTE:
239
+ * - This no longer uses a separate updateSpecificUser; the single
240
+ * updateUser entrypoint accepts any `user` as long as authority/payer
241
+ * are valid, per your script.
242
+ */
243
+ async buildCorrectRegisterTx(opts: {
244
+ amount?: bigint;
245
+ maxCandidates?: number;
246
+ } = {}): Promise<CorrectRegisterBuildResult> {
247
+ const walletPk = this.provider.wallet.publicKey;
248
+ const callerStr = walletPk.toBase58();
249
+
250
+ const [distState, actualByOwner, trackedByOwner] = await Promise.all([
251
+ this.getDistributionState(),
252
+ this.getActualBalancesByOwner(),
253
+ this.getTrackedBalances(),
254
+ ]);
255
+
256
+ if (!distState) {
257
+ return {
258
+ needToRegister: false,
259
+ canSucceed: false,
260
+ reason: 'DistributionState not initialized',
261
+ transaction: undefined,
262
+ plan: { deficit: BigInt(0), willFree: BigInt(0), selected: [] },
263
+ };
264
+ }
265
+
266
+ const availableBalance = BigInt(distState.availableBalance.toString());
267
+
268
+ const trackedEntry =
269
+ trackedByOwner.get(callerStr) ?? {
270
+ owner: walletPk,
271
+ tracked: BigInt(0),
272
+ };
273
+
274
+ const actual = actualByOwner.get(callerStr) ?? BigInt(0);
275
+ const tracked = trackedEntry.tracked;
276
+ const untracked = actual - tracked;
216
277
 
217
- /** Simulate a single-IX transaction; returns error (if any) + compute units used */
218
- async simulateTransaction(
219
- tx: Transaction
220
- ): Promise<{ err: any; unitsConsumed: number }> {
221
- tx.feePayer = this.provider.wallet.publicKey;
222
- const { blockhash } = await this.provider.connection.getLatestBlockhash();
223
- tx.recentBlockhash = blockhash;
224
- const versioned = new VersionedTransaction(tx.compileMessage());
225
- const sim = await this.provider.connection.simulateTransaction(versioned, {
226
- sigVerify: false,
227
- });
228
- return { err: sim.value.err, unitsConsumed: sim.value.unitsConsumed! };
278
+ // Nothing to register
279
+ if (untracked <= BigInt(0)) {
280
+ return {
281
+ needToRegister: false,
282
+ canSucceed: true,
283
+ reason: 'No untracked liqSOL to register',
284
+ transaction: undefined,
285
+ plan: { deficit: BigInt(0), willFree: BigInt(0), selected: [] },
286
+ };
287
+ }
288
+
289
+ // Optional user-specified cap on how much to register.
290
+ const targetRegister =
291
+ opts.amount && opts.amount > BigInt(0) ? opts.amount : untracked;
292
+
293
+ // Simple case: availableBalance already covers what we want to register
294
+ if (availableBalance >= targetRegister) {
295
+ const tx = new Transaction();
296
+ const ix = await this.buildUpdateUserIx(walletPk); // caller only
297
+ tx.add(ix);
298
+
299
+ return {
300
+ needToRegister: true,
301
+ canSucceed: true,
302
+ transaction: tx,
303
+ plan: { deficit: BigInt(0), willFree: BigInt(0), selected: [] },
304
+ };
305
+ }
306
+
307
+ // Need to free up more availableBalance by correcting others.
308
+ const deficit = targetRegister - availableBalance;
309
+
310
+ const allCandidates = await this.deriveMismatchCandidates();
311
+ const maxCandidates = opts.maxCandidates ?? 10;
312
+
313
+ const selected: MismatchCandidate[] = [];
314
+ let willFree = BigInt(0);
315
+
316
+ for (const c of allCandidates) {
317
+ if (c.owner.equals(walletPk)) continue; // don't self-correct here
318
+ selected.push(c);
319
+ willFree += c.delta;
320
+ if (willFree >= deficit || selected.length >= maxCandidates) break;
321
+ }
322
+
323
+ if (willFree < deficit) {
324
+ return {
325
+ needToRegister: true,
326
+ canSucceed: false,
327
+ reason: 'Not enough mismatched candidates to cover deficit',
328
+ transaction: undefined,
329
+ plan: { deficit, willFree, selected },
330
+ };
331
+ }
332
+
333
+ const tx = new Transaction();
334
+
335
+ // 1) Correct selected mismatched users.
336
+ for (const c of selected) {
337
+ const ix = await this.buildUpdateUserIx(c.owner);
338
+ tx.add(ix);
339
+ }
340
+
341
+ // 2) Register caller (updateUser(caller)).
342
+ {
343
+ const ix = await this.buildUpdateUserIx(walletPk);
344
+ tx.add(ix);
345
+ }
346
+
347
+ return {
348
+ needToRegister: true,
349
+ canSucceed: true,
350
+ transaction: tx,
351
+ plan: { deficit, willFree, selected },
352
+ };
229
353
  }
230
354
  }