@wireio/stake 0.0.6 → 0.1.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.
Files changed (44) hide show
  1. package/README.md +203 -13
  2. package/lib/stake.browser.js +2800 -3329
  3. package/lib/stake.browser.js.map +1 -1
  4. package/lib/stake.d.ts +376 -6261
  5. package/lib/stake.js +2937 -3476
  6. package/lib/stake.js.map +1 -1
  7. package/lib/stake.m.js +2800 -3329
  8. package/lib/stake.m.js.map +1 -1
  9. package/package.json +2 -2
  10. package/src/assets/solana/idl/deposit.json +46 -10
  11. package/src/assets/solana/idl/distribution.json +40 -8
  12. package/src/assets/solana/idl/liq_sol_token.json +25 -2
  13. package/src/assets/solana/idl/mint_helper.json +110 -0
  14. package/src/assets/solana/idl/read_tracked_balance.json +140 -0
  15. package/src/assets/solana/idl/stake_controller.json +1141 -780
  16. package/src/assets/solana/idl/treasury.json +1 -227
  17. package/src/assets/solana/idl/validator_leaderboard.json +88 -47
  18. package/src/assets/solana/idl/validator_registry.json +115 -46
  19. package/src/assets/solana/idl/yield_oracle.json +1 -1
  20. package/src/assets/solana/types/deposit.ts +46 -10
  21. package/src/assets/solana/types/distribution.ts +40 -8
  22. package/src/assets/solana/types/liq_sol_token.ts +25 -2
  23. package/src/assets/solana/types/mint_helper.ts +116 -0
  24. package/src/assets/solana/types/read_tracked_balance.ts +146 -0
  25. package/src/assets/solana/types/stake_controller.ts +1141 -780
  26. package/src/assets/solana/types/treasury.ts +1 -227
  27. package/src/assets/solana/types/validator_leaderboard.ts +88 -47
  28. package/src/assets/solana/types/validator_registry.ts +115 -46
  29. package/src/assets/solana/types/yield_oracle.ts +1 -1
  30. package/src/index.ts +3 -4
  31. package/src/networks/ethereum/ethereum.ts +2 -2
  32. package/src/networks/solana/clients/deposit.client.ts +71 -80
  33. package/src/networks/solana/clients/distribution.client.ts +392 -141
  34. package/src/networks/solana/clients/leaderboard.client.ts +82 -107
  35. package/src/networks/solana/constants.ts +141 -56
  36. package/src/networks/solana/program.ts +36 -89
  37. package/src/networks/solana/solana.ts +168 -34
  38. package/src/networks/solana/types.ts +57 -0
  39. package/src/scripts/fetch-artifacts.sh +24 -0
  40. package/src/staker/staker.ts +32 -28
  41. package/src/staker/types.ts +24 -21
  42. package/src/assets/solana/idl/stake_registry.json +0 -435
  43. package/src/networks/solana/utils.ts +0 -122
  44. /package/src/{utils.ts → common/utils.ts} +0 -0
@@ -1,45 +1,43 @@
1
1
  import {
2
2
  Connection,
3
3
  PublicKey as SolPubKey,
4
- Transaction,
5
- VersionedTransaction,
6
4
  TransactionSignature,
7
5
  Commitment,
6
+ ConnectionConfig,
7
+ Transaction,
8
8
  } from '@solana/web3.js';
9
9
  import { AnchorProvider } from '@coral-xyz/anchor';
10
10
  import { BaseSignerWalletAdapter } from '@solana/wallet-adapter-base';
11
- import { IStakingClient, SolanaTransaction, StakerConfig } from '../../staker/types';
11
+ import { IStakingClient, Portfolio, StakerConfig } from '../../staker/types';
12
12
  import { ChainID, ExternalNetwork, KeyType, PublicKey } from '@wireio/core';
13
13
  import { DepositClient } from './clients/deposit.client';
14
+ import { deriveStakeControllerReservePoolPDA, deriveStakeControllerVaultPDA, getUserLiqsolATA } from './constants';
14
15
  import { DistributionClient } from './clients/distribution.client';
15
- import { ValidatorLeaderboardClient } from './clients/leaderboard.client';
16
+ import { SolanaTransaction } from './types';
17
+ // import { ValidatorLeaderboardClient } from './clients/leaderboard.client';
16
18
 
17
19
  const commitment: Commitment = 'confirmed';
18
20
 
19
21
  export class SolanaStakingClient implements IStakingClient {
20
22
  public pubKey: PublicKey;
21
- public network: ExternalNetwork;
22
-
23
23
  public connection: Connection;
24
24
  public anchor: AnchorProvider;
25
25
 
26
26
  private depositClient: DepositClient;
27
27
  private distributionClient: DistributionClient;
28
- private leaderboardClient: ValidatorLeaderboardClient;
29
-
30
- balanceNative: number = 0;
31
- balanceLiquid: number = 0;
28
+ // private leaderboardClient: ValidatorLeaderboardClient;
32
29
 
33
30
  get solPubKey(): SolPubKey {
34
31
  return new SolPubKey(this.pubKey.data.array);
35
32
  }
36
33
 
34
+ get network() { return this.config.network; }
35
+
37
36
  constructor(private config: StakerConfig) {
38
37
  // 1) unwrap & validate wallet adapter
39
38
  const adapter = config.provider as BaseSignerWalletAdapter;
40
- if (!adapter.publicKey) {
41
- throw new Error('Solana wallet adapter not connected');
42
- }
39
+ if (!adapter.publicKey) throw new Error('Solana wallet adapter not connected');
40
+ if (!config.network.rpcUrls.length) throw new Error('No RPC URLs provided');
43
41
 
44
42
  // 2) sanity‐check wire ↔ solana pubkey
45
43
  const publicKey = adapter.publicKey;
@@ -48,10 +46,14 @@ export class SolanaStakingClient implements IStakingClient {
48
46
  throw new Error('Passed-in pubKey doesn\'t match adapter.publicKey');
49
47
  }
50
48
 
51
- this.network = config.network;
49
+ // build connection config
50
+ let opts: ConnectionConfig = { commitment }
51
+ if (config.network.rpcUrls.length > 1 && config.network.rpcUrls[1].startsWith('ws')) {
52
+ opts.wsEndpoint = config.network.rpcUrls[1];
53
+ }
54
+
52
55
  this.pubKey = wirePub;
53
- this.connection = new Connection(config.network.rpcUrls[0], commitment);
54
- this.setBalances();
56
+ this.connection = new Connection(config.network.rpcUrls[0], opts);
55
57
 
56
58
  const anchorWallet = {
57
59
  publicKey,
@@ -63,27 +65,122 @@ export class SolanaStakingClient implements IStakingClient {
63
65
  },
64
66
  };
65
67
 
66
- this.anchor = new AnchorProvider(
67
- this.connection,
68
- anchorWallet,
69
- { commitment }
70
- );
68
+ this.anchor = new AnchorProvider(this.connection, anchorWallet, { commitment });
71
69
 
72
70
  // 4) staking clients
73
71
  this.depositClient = new DepositClient(this.anchor);
74
72
  this.distributionClient = new DistributionClient(this.anchor);
75
- this.leaderboardClient = new ValidatorLeaderboardClient(this.anchor);
73
+ // this.leaderboardClient = new ValidatorLeaderboardClient(this.anchor);
76
74
  }
77
75
 
78
76
  /**
79
- * Update the native and liquid balances.
80
- * @returns The updated balances.
77
+ * Resolve the user's liqSOL ATA and balances + key protocol balances.
78
+ * native = SOL in wallet
79
+ * actual = liqSOL token balance (from ATA)
80
+ * tracked = liqSOL tracked balance (from Distribution.userRecord)
81
81
  */
82
- async setBalances() {
83
- this.balanceNative = await this.connection.getBalance(this.solPubKey);
84
- this.balanceLiquid = 0; // TODO fetch liq balance
82
+ async getPortfolio(): Promise<Portfolio> {
83
+ const user = this.solPubKey;
84
+
85
+ // Handy PDAs & ATA
86
+ const [reservePoolPDA] = deriveStakeControllerReservePoolPDA();
87
+ const [vaultPDA] = deriveStakeControllerVaultPDA();
88
+ const userLiqsolAta = getUserLiqsolATA(user);
89
+
90
+ // Pull balances in parallel; ATA may not exist yet
91
+ const [nativeLamports, actualBalResp, userRecord] = await Promise.all([
92
+ this.connection.getBalance(user),
93
+ this.connection.getTokenAccountBalance(userLiqsolAta).catch(() => null),
94
+ this.distributionClient.getUserRecord(user).catch(() => null),
95
+ ]);
96
+
97
+ // Actual (LiqSOL) balance + decimals (fallback to 9 if ATA missing)
98
+ const actualAmountStr = actualBalResp?.value?.amount ?? '0';
99
+ const actualDecimals = actualBalResp?.value?.decimals ?? 9;
100
+
101
+ // Tracked (from userRecord in Distribution program)
102
+ const trackedAmountStr = userRecord?.trackedBalance
103
+ ? (userRecord.trackedBalance as any).toString()
104
+ : '0';
105
+ const trackedDecimals = actualDecimals; // same mint as liqSOL
106
+
107
+ // Assemble the portfolio
108
+ const portfolio: Portfolio = {
109
+ native: {
110
+ symbol: 'SOL',
111
+ decimals: 9,
112
+ amount: BigInt(nativeLamports),
113
+ },
114
+ actual: {
115
+ symbol: 'LiqSOL',
116
+ decimals: actualDecimals,
117
+ amount: BigInt(actualAmountStr),
118
+ },
119
+ tracked: {
120
+ symbol: 'LiqSOL',
121
+ decimals: trackedDecimals,
122
+ amount: BigInt(trackedAmountStr),
123
+ },
124
+ extras: {
125
+ userLiqsolAta: userLiqsolAta.toBase58(),
126
+ reservePoolPDA: reservePoolPDA.toBase58(),
127
+ vaultPDA: vaultPDA.toBase58(),
128
+ },
129
+ };
85
130
 
86
- return { native: this.balanceNative, liquid: this.balanceLiquid };
131
+ // console.log('>> PORTFOLIO SET', this.network.name, this.portfolio);
132
+ return portfolio;
133
+ }
134
+ /**
135
+ * Optional: fetch your Distribution program user record
136
+ * (often contains per-user deposit/claim state).
137
+ */
138
+ async getUserRecord(): Promise<any | null> {
139
+ return this.distributionClient.getUserRecord(this.solPubKey);
140
+ }
141
+
142
+ getProtocolFee() {
143
+
144
+ }
145
+
146
+ /**
147
+ * Build, sign, and submit a single transaction that:
148
+ * - Corrects other users (if needed) to free available balance, then
149
+ * - Registers the caller’s untracked liqSOL.
150
+ *
151
+ * @param amount Optional: register a smaller amount than your full untracked balance.
152
+ * @returns signature string
153
+ */
154
+ async register(amount?: bigint): Promise<string> {
155
+ try {
156
+ console.log('Building CorrectAndRegister transaction with amount:', amount);
157
+
158
+ // Build the transaction using the Distribution client (self = this.solPubKey)
159
+ const build = await this.distributionClient.buildCorrectRegisterTx({ amount });
160
+ if (!build.canSucceed || !build.transaction)
161
+ throw new Error(build.reason ?? 'Unable to build Correct&Register transaction');
162
+
163
+ console.log('buildCorrectRegisterTx:', build);
164
+
165
+ const { tx, blockhash, lastValidBlockHeight } = await this.prepareTx(build.transaction);
166
+ const signed = await this.signTransaction(tx);
167
+ const result = await this.sendAndConfirmHttp(signed, { blockhash, lastValidBlockHeight });
168
+
169
+ // Optionally refresh portfolio after success (non-blocking)
170
+ console.log('Registered:', {
171
+ needToRegister: build.needToRegister,
172
+ freed: build.plan.willFree,
173
+ corrected: build.plan.selected.map(c => ({
174
+ owner: c.owner.toBase58(),
175
+ delta: c.delta.toString(),
176
+ }))
177
+ });
178
+
179
+ return result.signature;
180
+ } catch (error) {
181
+ console.error('Error in register:', error);
182
+ throw error;
183
+ }
87
184
  }
88
185
 
89
186
  /**
@@ -92,12 +189,35 @@ export class SolanaStakingClient implements IStakingClient {
92
189
  * @returns The transaction signature.
93
190
  */
94
191
  async deposit(lamports: number): Promise<string> {
95
- const tx = await this.depositClient.buildDepositTx(
96
- this.solPubKey,
97
- lamports,
192
+ const { transaction } = await this.depositClient.buildDepositTx(this.solPubKey, lamports);
193
+ const { tx, blockhash, lastValidBlockHeight } = await this.prepareTx(transaction);
194
+ const signed = await this.signTransaction(tx);
195
+ const result = await this.sendAndConfirmHttp(signed, { blockhash, lastValidBlockHeight });
196
+ return result.signature;
197
+ }
198
+
199
+ private async sendAndConfirmHttp(
200
+ signed: SolanaTransaction,
201
+ ctx: { blockhash: string; lastValidBlockHeight: number }
202
+ ): Promise<TxResult> {
203
+ // sendRawTransaction is HTTP
204
+ const signature = await this.connection.sendRawTransaction(signed.serialize(), {
205
+ skipPreflight: false,
206
+ preflightCommitment: commitment,
207
+ maxRetries: 3,
208
+ });
209
+
210
+ // Poll confirmation using blockhash/lastValidBlockHeight
211
+ const conf = await this.connection.confirmTransaction(
212
+ { signature, blockhash: ctx.blockhash, lastValidBlockHeight: ctx.lastValidBlockHeight },
213
+ commitment
98
214
  );
99
- const signed = await this.signTransaction(tx.transaction);
100
- return this.sendTransaction(signed);
215
+
216
+ const ok = !conf.value.err;
217
+ if (!ok) {
218
+ throw new Error(`Transaction failed: ${JSON.stringify(conf.value.err)}`);
219
+ }
220
+ return { signature, slot: conf.context.slot, confirmed: true };
101
221
  }
102
222
 
103
223
  async signTransaction(tx: SolanaTransaction): Promise<SolanaTransaction> {
@@ -107,4 +227,18 @@ export class SolanaStakingClient implements IStakingClient {
107
227
  async sendTransaction(signed: SolanaTransaction): Promise<TransactionSignature> {
108
228
  return await this.anchor.sendAndConfirm(signed);
109
229
  }
110
- }
230
+
231
+ async prepareTx(tx: Transaction): Promise<{ tx: Transaction; blockhash: string; lastValidBlockHeight: number }> {
232
+ const { blockhash, lastValidBlockHeight } = await this.connection.getLatestBlockhash('confirmed');
233
+ tx.recentBlockhash = blockhash;
234
+ tx.feePayer = this.solPubKey;
235
+
236
+ return { tx, blockhash, lastValidBlockHeight };
237
+ }
238
+ }
239
+
240
+ export interface TxResult {
241
+ signature: string;
242
+ slot: number;
243
+ confirmed: boolean;
244
+ };
@@ -0,0 +1,57 @@
1
+ import { PublicKey, Transaction, VersionedTransaction } from "@solana/web3.js";
2
+
3
+ export type SolanaTransaction = Transaction | VersionedTransaction
4
+
5
+ /** Raw mismatch row (per user with a userRecord) */
6
+ export type MismatchCandidate = {
7
+ /** Wallet that owns the ATA (decoded from token account) */
8
+ owner: PublicKey;
9
+ /** user_record PDA */
10
+ userRecordPda: PublicKey;
11
+ /** user’s ATA for liqSOL */
12
+ userAta: PublicKey;
13
+ /** protocol tracked balance (u64) */
14
+ tracked: bigint;
15
+ /** actual on-chain token balance (u64) */
16
+ actual: bigint;
17
+ /** tracked - actual (positive means “freeable”) */
18
+ delta: bigint;
19
+ };
20
+
21
+ /** Output when choosing candidates to free liquidity */
22
+ export type CorrectionPlan = {
23
+ /** selected candidates sorted by delta desc */
24
+ selected: MismatchCandidate[];
25
+ /** total delta we’ll free by correcting selected */
26
+ willFree: bigint;
27
+ /** how much still missing after selection (0 if we can meet the target) */
28
+ deficit: bigint;
29
+ };
30
+
31
+ /** What the builder returns to your caller/UI */
32
+ export type CorrectAndRegisterBuild = {
33
+ /** The ready-to-send transaction (if buildable) */
34
+ transaction?: Transaction;
35
+ /** True if the tx can succeed with current state */
36
+ canSucceed: boolean;
37
+ /** Explanation if not buildable */
38
+ reason?: string;
39
+
40
+ /** Current liqSOL mint; useful for UI */
41
+ liqsolMint: PublicKey;
42
+ /** Amount you need to register (actual - tracked if positive) */
43
+ needToRegister: bigint;
44
+ /** Distribution “availableBalance” before this action */
45
+ availableBefore: bigint;
46
+
47
+ /** Candidates we scanned (already sorted by delta desc) */
48
+ candidates: MismatchCandidate[];
49
+ /** Subset we’d correct (maybe empty) */
50
+ plan: CorrectionPlan;
51
+
52
+ /** Convenience for caller */
53
+ accounts: {
54
+ selfUserRecordPda: PublicKey;
55
+ selfUserAta: PublicKey;
56
+ };
57
+ };
@@ -0,0 +1,24 @@
1
+ #!/bin/bash
2
+ set -e
3
+
4
+ # Define variables
5
+ IDL_ZIP_URL="https://bucket.gitgo.app/solana/idl.zip"
6
+ TYPES_ZIP_URL="https://bucket.gitgo.app/solana/types.zip"
7
+ DEST_DIR="$(dirname "$0")/../assets/solana"
8
+ TMP_DIR="$(mktemp -d)"
9
+
10
+ # Download the zip files
11
+ curl -L "$IDL_ZIP_URL" -o "$TMP_DIR/idl.zip"
12
+ curl -L "$TYPES_ZIP_URL" -o "$TMP_DIR/types.zip"
13
+
14
+ # Create destination directory if it doesn't exist
15
+ mkdir -p "$DEST_DIR"
16
+
17
+ # Unzip into the destination directory
18
+ unzip -o "$TMP_DIR/idl.zip" -d "$DEST_DIR"
19
+ unzip -o "$TMP_DIR/types.zip" -d "$DEST_DIR"
20
+
21
+ # Clean up
22
+ rm -rf "$TMP_DIR"
23
+
24
+ echo "IDL and types files fetched and extracted to $DEST_DIR"
@@ -1,14 +1,34 @@
1
1
  // src/staker/staker.ts
2
2
 
3
- import { ChainID, Curve, EvmChainID, SolChainID } from '@wireio/core';
4
- import { IStakingClient, StakeBalance, StakerConfig } from './types';
3
+ import { ChainID, SolChainID } from '@wireio/core';
4
+ import { IStakingClient, StakerConfig } from './types';
5
5
  import { SolanaStakingClient } from '../networks/solana/solana';
6
- import { EthereumStakingClient } from '../networks/ethereum/ethereum';
7
6
 
8
7
  export class Staker {
8
+ public selectedChainID?: ChainID;
9
9
  private clients: Map<ChainID, IStakingClient> = new Map();
10
10
 
11
- constructor(config: StakerConfig | StakerConfig[]) {
11
+ /**
12
+ * Get the staking client for the currently selected chain.
13
+ * @returns The staking client instance.
14
+ */
15
+ get client(): IStakingClient | undefined {
16
+ return this.selectedChainID && this.isConfigured(this.selectedChainID)
17
+ ? this.clients.get(this.selectedChainID) : undefined;
18
+ }
19
+
20
+ /** Is there a client configured for this chain? */
21
+ isConfigured(chainId: ChainID): boolean {
22
+ return this.clients.has(chainId);
23
+ }
24
+
25
+ /** List of configured chains */
26
+ listConfigured(): ChainID[] {
27
+ return [...this.clients.keys()];
28
+ }
29
+
30
+ constructor(config: StakerConfig | StakerConfig[], selectedChainID?: ChainID) {
31
+ if (!config) throw new Error('StakerConfig is required');
12
32
  if (!Array.isArray(config)) config = [config];
13
33
 
14
34
  config.forEach((cfg) => {
@@ -21,36 +41,20 @@ export class Staker {
21
41
  // this.clients.set(EvmChainID.Sepolia, new EthereumStakingClient(cfg));
22
42
  // break;
23
43
  default:
24
- throw new Error(`Unsupported network curve: ${cfg.network.name}`);
44
+ // console.log(`No staking client available for chain ${cfg.network.chainId}`);
45
+ // throw new Error(`Unsupported network curve: ${cfg.network.name}`);
25
46
  }
26
47
  });
27
- }
28
-
29
- /** Return the raw client (if any) for a chain */
30
- getClient(chainId: ChainID): IStakingClient | undefined {
31
- return this.clients.get(chainId);
32
- }
33
48
 
34
- /** Convenience: ensure a client exists or throw a friendly error */
35
- private ensureClient(chainId: ChainID): IStakingClient {
36
- const c = this.clients.get(chainId);
37
- if (!c) throw new Error(`Staker not initialized for chain ${chainId}`);
38
- return c;
49
+ this.selectedChainID = selectedChainID;
39
50
  }
40
51
 
41
52
  /**
42
- * Deposit funds into the staking pool.
43
- * @param chainId The chain ID to deposit into.
44
- * @param amount The amount to deposit (in smallest unit i.e. Lamport or Wei).
45
- * @returns The transaction signature.
53
+ * Select a chain. Returns true if a client exists for it, false otherwise.
54
+ * (We still record the selectedChainID so the UI can reflect the chosen chain.)
46
55
  */
47
- deposit(chainId: ChainID, amount: number): Promise<string> {
48
- return this.ensureClient(chainId).deposit(amount);
56
+ setChain(chainID: ChainID): boolean {
57
+ this.selectedChainID = chainID;
58
+ return this.clients.has(chainID);
49
59
  }
50
-
51
- getBalance(chainId: ChainID): Promise<StakeBalance> {
52
- return this.ensureClient(chainId).setBalances();
53
- }
54
-
55
- // …withdraw, claimRewards, etc…
56
60
  }
@@ -1,41 +1,44 @@
1
1
  // types.ts
2
2
 
3
3
  import { BaseSignerWalletAdapter } from '@solana/wallet-adapter-base';
4
- import { Transaction, VersionedTransaction } from '@solana/web3.js';
5
- import { ChainID, Curve, ExternalNetwork, ProviderType, PublicKey } from '@wireio/core';
4
+ import { PublicKey as SolPubKey } from '@solana/web3.js';
5
+ import { ExternalNetwork, PublicKey } from '@wireio/core';
6
6
  import { ethers } from 'ethers';
7
7
 
8
- export type SolanaTransaction = Transaction | VersionedTransaction
9
- export interface IStakingClient{
8
+ export interface IStakingClient {
10
9
  pubKey: PublicKey;
11
10
  network: ExternalNetwork;
12
11
 
13
- balanceNative: number;
14
- balanceLiquid: number;
15
-
16
12
  /** Amount is in the chain's smallest unit (lamports/wei, etc.) */
17
13
  deposit(amount: number): Promise<string>;
18
14
 
19
- /** Update the native and liquid balances. */
20
- setBalances(): Promise<StakeBalance>;
21
-
22
- // /** Return native balance in the chain’s smallest unit (lamports for Sol) */
23
- // getBalanceNative(): Promise<number>;
15
+ /** Register any untracked LIQ staked tokens */
16
+ register(): Promise<string>;
24
17
 
25
- // /** Return liquid balance in the chain’s smallest unit (lamports for Sol) */
26
- // getBalanceLiquid(): Promise<number>;
18
+ /** Fetch the portfolio for the LIQ stake user */
19
+ getPortfolio(): Promise<Portfolio>;
27
20
  }
28
21
 
29
- /**
30
- * Union config for our unified Staker
31
- */
32
22
  export type StakerConfig = {
33
23
  network: ExternalNetwork;
34
24
  provider: BaseSignerWalletAdapter | ethers.providers.Web3Provider;
35
25
  pubKey: PublicKey;
36
26
  }
37
27
 
38
- export interface StakeBalance {
39
- native: number;
40
- liquid: number;
41
- }
28
+ export interface Portfolio {
29
+ /** Native SOL balance on chain */
30
+ native: BalanceView;
31
+ /** Actual liquid SOL balance from ATA */
32
+ actual: BalanceView;
33
+ /** Tracked liquid SOL balance from distribution program */
34
+ tracked: BalanceView;
35
+ /** Extra PDAs and account addresses */
36
+ extras?: Record<string, any>;
37
+ }
38
+
39
+ export type BalanceView = {
40
+ amount: bigint; // raw on-chain integer value
41
+ decimals: number; // number of decimal places
42
+ symbol?: string; // optional token symbol identifier
43
+ ata?: SolPubKey; // associated token account address
44
+ };