@wireio/stake 0.5.0 → 0.5.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.
@@ -3,7 +3,9 @@ import {
3
3
  ComputeBudgetProgram,
4
4
  Connection,
5
5
  ConnectionConfig,
6
+ PerfSample,
6
7
  PublicKey as SolPubKey,
8
+ SystemProgram,
7
9
  Transaction,
8
10
  TransactionSignature,
9
11
  } from '@solana/web3.js';
@@ -39,15 +41,20 @@ import { TokenClient } from './clients/token.client';
39
41
  import {
40
42
  deriveLiqsolMintPda,
41
43
  deriveReservePoolPda,
44
+ deriveStakeMetricsPda,
42
45
  deriveVaultPda,
43
46
  INDEX_SCALE,
47
+ PAY_RATE_SCALE_FACTOR,
44
48
  } from './constants';
45
49
 
46
- import { buildSolanaTrancheSnapshot } from './utils';
47
- import { PayRateEntry, SolanaTransaction } from './types';
50
+ import { buildSolanaTrancheSnapshot, ceilDiv } from './utils';
51
+ import { GlobalConfig, PayRateEntry, SolanaTransaction } from './types';
52
+ import { SolanaProgramService } from './program';
48
53
 
49
54
  const commitment: Commitment = 'confirmed';
50
55
 
56
+ export const SCALE = new BN('1000000000000');
57
+
51
58
  /**
52
59
  * Solana implementation of IStakingClient.
53
60
  *
@@ -69,6 +76,7 @@ export class SolanaStakingClient implements IStakingClient {
69
76
  public leaderboardClient: LeaderboardClient;
70
77
  public outpostClient: OutpostClient;
71
78
  public tokenClient: TokenClient;
79
+ public program: SolanaProgramService
72
80
 
73
81
  get solPubKey(): SolPubKey {
74
82
  if (!this.pubKey) throw new Error('pubKey is undefined');
@@ -182,6 +190,7 @@ export class SolanaStakingClient implements IStakingClient {
182
190
  this.leaderboardClient = new LeaderboardClient(this.anchor);
183
191
  this.outpostClient = new OutpostClient(this.anchor);
184
192
  this.tokenClient = new TokenClient(this.anchor);
193
+ this.program = new SolanaProgramService(this.anchor);
185
194
  }
186
195
 
187
196
  // ---------------------------------------------------------------------
@@ -473,10 +482,112 @@ export class SolanaStakingClient implements IStakingClient {
473
482
  // READ-ONLY Public Methods
474
483
  // ---------------------------------------------------------------------
475
484
 
476
- // Estimated total APY for staking yeild
477
- getSystemAPY(): Promise<number> {
478
- // TODO
479
- return Promise.resolve(0);
485
+ /**
486
+ * Returns the system APY (percent) for Solana,
487
+ * using compound interest per epoch and a
488
+ * cluster-derived epochs-per-year.
489
+ */
490
+ async getSystemAPY(): Promise<number> {
491
+ // 1) Per-epoch rate (decimal) from on-chain stakeMetrics
492
+ const ratePerEpoch = await this.getEpochRateDecimalFromProgram();
493
+ console.log('epochRateDecimal', ratePerEpoch);
494
+
495
+ // 2) Live epochs-per-year estimate from cluster
496
+ const epochsPerYear = await this.getEpochsPerYearFromCluster();
497
+ console.log('epochsPerYear', epochsPerYear);
498
+
499
+ // 3) Compound: (1 + r)^N - 1
500
+ const apyDecimal = Math.pow(1 + ratePerEpoch, epochsPerYear) - 1;
501
+ console.log('apyDecimal', apyDecimal);
502
+
503
+ // 4) Convert to percent
504
+ const apyPercent = apyDecimal * 100;
505
+ console.log('apyPercent', apyPercent);
506
+
507
+ return apyPercent;
508
+ }
509
+
510
+ /**
511
+ * Reads the liqsol_core stakeMetrics account and returns the
512
+ * Solana per-epoch system rate as a **decimal** (not BPS),
513
+ * de-scaled using PAY_RATE_SCALE_FACTOR (1e12).
514
+ */
515
+ private async getEpochRateDecimalFromProgram(): Promise<number> {
516
+ const liqSolCoreProgram = this.program.getProgram('liqsolCore');
517
+ const stakeMetricsPda = deriveStakeMetricsPda();
518
+ const stakeMetrics =
519
+ await liqSolCoreProgram.account.stakeMetrics.fetch(stakeMetricsPda);
520
+
521
+ // solSystemPayRate is stored on-chain with PAY_RATE_SCALE_FACTOR (1e12)
522
+ const raw = BigInt(stakeMetrics.solSystemPayRate.toString());
523
+
524
+ // Convert to JS number in **decimal per epoch** units
525
+ const rateDecimal = Number(raw) / Number(PAY_RATE_SCALE_FACTOR);
526
+
527
+ console.log('solSystemPayRate(raw)', raw.toString());
528
+ console.log('epochRateDecimal(computed)', rateDecimal);
529
+
530
+ return rateDecimal;
531
+ }
532
+
533
+ // Simple cache so we don’t hammer RPC
534
+ private epochsPerYearCache?: { value: number; fetchedAt: number };
535
+ private static readonly EPOCHS_PER_YEAR_TTL_MS = 10 * 60 * 1000; // 10 minutes
536
+
537
+ /**
538
+ * Derive "epochs per year" from the live Solana cluster.
539
+ *
540
+ * Uses:
541
+ * - getRecentPerformanceSamples() to estimate slots/second
542
+ * - getEpochInfo() to read slotsInEpoch
543
+ */
544
+ private async getEpochsPerYearFromCluster(): Promise<number> {
545
+ const now = Date.now();
546
+
547
+ if (
548
+ this.epochsPerYearCache &&
549
+ now - this.epochsPerYearCache.fetchedAt <
550
+ SolanaStakingClient.EPOCHS_PER_YEAR_TTL_MS
551
+ ) {
552
+ return this.epochsPerYearCache.value;
553
+ }
554
+
555
+ const connection = this.anchor.connection;
556
+
557
+ // 1) Estimate slots/second from recent performance samples
558
+ const samples: PerfSample[] = await connection.getRecentPerformanceSamples(
559
+ 60,
560
+ );
561
+ if (!samples.length) {
562
+ throw new Error('No performance samples available from cluster');
563
+ }
564
+
565
+ const totalSlots = samples.reduce((acc, s) => acc + s.numSlots, 0);
566
+ const totalSecs = samples.reduce((acc, s) => acc + s.samplePeriodSecs, 0);
567
+
568
+ if (totalSecs === 0) {
569
+ throw new Error(
570
+ 'Cluster returned zero samplePeriodSecs in performance samples',
571
+ );
572
+ }
573
+
574
+ const slotsPerSecond = totalSlots / totalSecs;
575
+
576
+ // 2) Slots per epoch from cluster
577
+ const epochInfo = await connection.getEpochInfo(); // finalized commitment by default
578
+ const slotsPerEpoch = epochInfo.slotsInEpoch;
579
+
580
+ const secondsPerEpoch = slotsPerEpoch / slotsPerSecond;
581
+ const secondsPerYear = 365 * 24 * 60 * 60;
582
+
583
+ const epochsPerYear = secondsPerYear / secondsPerEpoch;
584
+
585
+ this.epochsPerYearCache = {
586
+ value: epochsPerYear,
587
+ fetchedAt: now,
588
+ };
589
+
590
+ return epochsPerYear;
480
591
  }
481
592
 
482
593
  // ---------------------------------------------
@@ -501,87 +612,17 @@ export class SolanaStakingClient implements IStakingClient {
501
612
  return BigInt(0);
502
613
  }
503
614
 
504
- const [history, globalConfig] = await Promise.all([
505
- this.distributionClient.getPayRateHistory(),
615
+ const [avgPayRate, globalConfig]: [BN, GlobalConfig] = await Promise.all([
616
+ this.distributionClient.getAverageScaledPayRate(windowSize),
506
617
  this.distributionClient.getGlobalConfig(),
507
618
  ]);
508
619
 
509
- if (!history || !globalConfig) {
510
- return BigInt(0);
511
- }
512
-
513
- const entries = history.entries ?? [];
514
- if (!entries.length) {
515
- return BigInt(0);
516
- }
517
-
518
- const maxEntries =
519
- typeof history.maxEntries === 'number'
520
- ? history.maxEntries
521
- : entries.length;
522
-
523
- const totalEntriesAdded = new BN(
524
- history.totalEntriesAdded.toString(),
525
- );
526
-
527
- const COUNT = Math.max(1, Math.min(windowSize, maxEntries, entries.length));
528
-
529
- // ---------------------------------------------
530
- // Walk the circular buffer from "most recent"
531
- // back through COUNT entries, using the same
532
- // logic as your coworker script.
533
- // ---------------------------------------------
534
-
535
- let avgPayRate = new BN(0);
536
-
537
- if (COUNT > 0) {
538
- let idx: number;
539
-
540
- if (totalEntriesAdded.isZero()) {
541
- // "Not wrapped yet" case
542
- idx = 0;
543
- } else if (history.currentIndex === 0) {
544
- idx = maxEntries - 1;
545
- } else {
546
- idx = history.currentIndex - 1;
547
- }
548
-
549
- let sum = new BN(0);
550
- let valid = 0;
551
- const zero = new BN(0);
552
-
553
- for (let i = 0; i < COUNT; i++) {
554
- const entry: PayRateEntry | undefined = entries[idx];
555
- if (entry) {
556
- const rate = new BN(entry.scaledRate.toString());
557
- if (rate.gt(zero)) {
558
- sum = sum.add(rate);
559
- valid += 1;
560
- }
561
- }
562
-
563
- // Same wrap logic as the coworker’s script:
564
- if (totalEntriesAdded.isZero()) {
565
- // simple forward iteration if never wrapped
566
- idx = (idx + 1) % maxEntries;
567
- } else {
568
- // walk backwards through the ring buffer
569
- idx = idx === 0 ? maxEntries - 1 : idx - 1;
570
- }
571
- }
572
-
573
- if (valid > 0) {
574
- avgPayRate = this.ceilDiv(sum, new BN(valid));
575
- }
576
- }
577
-
578
- // If no valid pay-rate entries, no fee
579
- if (avgPayRate.isZero()) {
620
+ if (!globalConfig || avgPayRate.isZero()) {
580
621
  return BigInt(0);
581
622
  }
582
623
 
583
624
  // depositFeeMultiplier may be BN or number depending on your type
584
- const rawMultiplier: any = (globalConfig as any).depositFeeMultiplier;
625
+ const rawMultiplier = globalConfig.depositFeeMultiplier;
585
626
  const multiplier = new BN(
586
627
  rawMultiplier?.toString?.() ?? rawMultiplier ?? 0,
587
628
  );
@@ -592,9 +633,8 @@ export class SolanaStakingClient implements IStakingClient {
592
633
  const amountBn = new BN(amountLamports.toString());
593
634
 
594
635
  // 10^12 scale (matches scaledRate / index scale)
595
- const SCALE = new BN('1000000000000');
596
636
 
597
- const feeBn = this.ceilDiv(
637
+ const feeBn = ceilDiv(
598
638
  avgPayRate.mul(multiplier).mul(amountBn),
599
639
  SCALE,
600
640
  );
@@ -642,6 +682,75 @@ export class SolanaStakingClient implements IStakingClient {
642
682
  });
643
683
  }
644
684
 
685
+ /**
686
+ * Estimate a conservative SOL buffer (lamports) to leave in the wallet
687
+ * so the user can pay fees for the current deposit and at least one
688
+ * more transaction, plus a bit extra for future interactions.
689
+ *
690
+ * Intended usage in UI:
691
+ * const bufferLamports = await client.estimateGasBuffer();
692
+ * const maxSpendable = balanceLamports > bufferLamports
693
+ * ? balanceLamports - bufferLamports
694
+ * : 0n;
695
+ *
696
+ * @param options.txCount How many transactions to cover (default 2)
697
+ * @param options.safetyMultiplier Extra safety multiplier (default 3x)
698
+ * @param options.minBufferLamports Optional override minimum buffer (default ~0.01 SOL)
699
+ */
700
+ async estimateGasBuffer(options?: {
701
+ txCount?: number;
702
+ safetyMultiplier?: number;
703
+ minBufferLamports?: bigint;
704
+ }): Promise<bigint> {
705
+ this.ensureUser();
706
+
707
+ const payer = this.solPubKey;
708
+
709
+ // 1) Build a small dummy transaction (self-transfer of 0 lamports).
710
+ const dummyIx = SystemProgram.transfer({
711
+ fromPubkey: payer,
712
+ toPubkey: payer,
713
+ lamports: 0,
714
+ });
715
+
716
+ const tx = new Transaction().add(dummyIx);
717
+ const { blockhash } = await this.connection.getLatestBlockhash(commitment);
718
+ tx.recentBlockhash = blockhash;
719
+ tx.feePayer = payer;
720
+
721
+ // 2) Ask the cluster what it would charge in lamports for this tx.
722
+ const message = tx.compileMessage();
723
+ const feeInfo = await this.connection.getFeeForMessage(message, commitment);
724
+
725
+ // Fallback to a conservative default if RPC doesn't give a value.
726
+ const singleTxFeeLamports = BigInt(feeInfo.value ?? 5000);
727
+
728
+ // 3) How many txs do we want to cover?
729
+ // Default: 2 (deposit + one extra operation).
730
+ const txCount = BigInt(options?.txCount ?? 2);
731
+
732
+ // 4) Apply a safety multiplier (default 3x to be very safe).
733
+ const safetyMultiplier = options?.safetyMultiplier ?? 3;
734
+ const safetyScaled = BigInt(Math.round(safetyMultiplier * 100)); // e.g. 300
735
+
736
+ let bufferLamports =
737
+ (singleTxFeeLamports *
738
+ txCount *
739
+ safetyScaled) /
740
+ BigInt(100);
741
+
742
+ // 5) Enforce a minimum buffer (default ~0.01 SOL).
743
+ const defaultMinBufferLamports = BigInt(10_000_000); // 0.01 SOL (1e9 / 100)
744
+ const minBufferLamports =
745
+ options?.minBufferLamports ?? defaultMinBufferLamports;
746
+
747
+ if (bufferLamports < minBufferLamports) {
748
+ bufferLamports = minBufferLamports;
749
+ }
750
+
751
+ return bufferLamports;
752
+ }
753
+
645
754
  // ---------------------------------------------------------------------
646
755
  // Tx helpers
647
756
  // ---------------------------------------------------------------------
@@ -740,11 +849,4 @@ export class SolanaStakingClient implements IStakingClient {
740
849
  }
741
850
  }
742
851
 
743
- private ceilDiv(n: BN, d: BN): BN {
744
- if (d.isZero()) {
745
- throw new Error('Division by zero in ceilDiv');
746
- }
747
- return n.add(d.subn(1)).div(d);
748
- }
749
-
750
852
  }
@@ -177,7 +177,7 @@ export interface SharesPreview {
177
177
  * - NFT withdrawal receipts
178
178
  * - Encumbered funds for pending withdrawals
179
179
  */
180
- export type Global = {
180
+ export type GlobalAccount = {
181
181
  /** PDA bump */
182
182
  bump: number;
183
183
 
@@ -210,9 +210,6 @@ export type Global = {
210
210
  * - `wireState` lifecycle (preLaunch/postLaunch/refund)
211
211
  */
212
212
  export type GlobalState = {
213
- /** Admin authority for pretokens/outpost config */
214
- admin: PublicKey;
215
-
216
213
  /** Deployment timestamp (Unix, seconds) */
217
214
  deployedAt: BN;
218
215
 
@@ -495,9 +492,6 @@ export type UserPretokenRecord = {
495
492
  * All price/supply fields use 8-decimal precision (SCALE = 1e8).
496
493
  */
497
494
  export type TrancheState = {
498
- /** Admin authority for tranche parameters */
499
- admin: PublicKey;
500
-
501
495
  /** Current tranche number */
502
496
  currentTrancheNumber: BN;
503
497
 
@@ -516,8 +510,8 @@ export type TrancheState = {
516
510
  /** Supply growth in basis points per tranche */
517
511
  supplyGrowthBps: number;
518
512
 
519
- /** Price growth in basis points per tranche */
520
- priceGrowthBps: number;
513
+ /** Price growth in cents per tranche (0.01 USD units) */
514
+ priceGrowthCents: number;
521
515
 
522
516
  /** Minimum valid SOL/USD price for Chainlink validation (8-decimal i128) */
523
517
  minPriceUsd: BN;
@@ -114,7 +114,7 @@ export function buildSolanaTrancheLadder(options: {
114
114
  currentTrancheSupply: bigint;
115
115
  currentPriceUsd: bigint;
116
116
  supplyGrowthBps: number;
117
- priceGrowthBps: number;
117
+ priceGrowthCents: number;
118
118
  windowBefore?: number;
119
119
  windowAfter?: number;
120
120
  }): TrancheLadderItem[] {
@@ -124,7 +124,7 @@ export function buildSolanaTrancheLadder(options: {
124
124
  currentTrancheSupply,
125
125
  currentPriceUsd,
126
126
  supplyGrowthBps,
127
- priceGrowthBps,
127
+ priceGrowthCents,
128
128
  windowBefore = 5,
129
129
  windowAfter = 5,
130
130
  } = options;
@@ -144,7 +144,7 @@ export function buildSolanaTrancheLadder(options: {
144
144
  const prevCap = capacity.get(id - 1)!;
145
145
  const prevPrice = price.get(id - 1)!;
146
146
  capacity.set(id, growOnce(prevCap, supplyGrowthBps));
147
- price.set(id, growOnce(prevPrice, priceGrowthBps));
147
+ price.set(id, growOnce(prevPrice, priceGrowthCents));
148
148
  }
149
149
 
150
150
  // Backward (past tranches)
@@ -152,7 +152,7 @@ export function buildSolanaTrancheLadder(options: {
152
152
  const nextCap = capacity.get(id + 1)!;
153
153
  const nextPrice = price.get(id + 1)!;
154
154
  capacity.set(id, shrinkOnce(nextCap, supplyGrowthBps));
155
- price.set(id, shrinkOnce(nextPrice, priceGrowthBps));
155
+ price.set(id, shrinkOnce(nextPrice, priceGrowthCents));
156
156
  }
157
157
 
158
158
  const ladder: TrancheLadderItem[] = [];
@@ -210,7 +210,7 @@ export function buildSolanaTrancheSnapshot(options: {
210
210
  const totalPretokensSold = toBigint(trancheState.totalPretokensSold);
211
211
  const currentPriceUsd = toBigint(trancheState.currentTranchePriceUsd);
212
212
  const supplyGrowthBps = trancheState.supplyGrowthBps;
213
- const priceGrowthBps = trancheState.priceGrowthBps;
213
+ const priceGrowthCents = trancheState.priceGrowthCents;
214
214
 
215
215
  const ladder = buildSolanaTrancheLadder({
216
216
  currentTranche,
@@ -218,7 +218,7 @@ export function buildSolanaTrancheSnapshot(options: {
218
218
  currentTrancheSupply,
219
219
  currentPriceUsd,
220
220
  supplyGrowthBps,
221
- priceGrowthBps,
221
+ priceGrowthCents,
222
222
  windowBefore: ladderWindowBefore,
223
223
  windowAfter: ladderWindowAfter,
224
224
  });
@@ -230,7 +230,7 @@ export function buildSolanaTrancheSnapshot(options: {
230
230
  currentTranche,
231
231
  currentPriceUsd,
232
232
  supplyGrowthBps,
233
- priceGrowthBps,
233
+ priceGrowthCents,
234
234
  totalPretokensSold,
235
235
  currentTrancheSupply,
236
236
  initialTrancheSupply,
@@ -798,4 +798,11 @@ export async function waitUntilSafeToExecuteFunction(
798
798
  export interface ScheduleConfig {
799
799
  early?: number; // fraction of epoch, default 0.10
800
800
  late?: number; // fraction of epoch, default 0.90
801
+ }
802
+
803
+ export function ceilDiv(n: BN, d: BN): BN {
804
+ if (d.isZero()) {
805
+ throw new Error('Division by zero in ceilDiv');
806
+ }
807
+ return n.add(d.subn(1)).div(d);
801
808
  }
package/src/staker.ts CHANGED
@@ -36,7 +36,7 @@ export class Staker {
36
36
 
37
37
  config.forEach((cfg) => {
38
38
  switch (cfg.network.chainId) {
39
- case SolChainID.WireTestnet:
39
+ case SolChainID.Devnet:
40
40
  this.clients.set(cfg.network.chainId, new SolanaStakingClient(cfg));
41
41
  break;
42
42
 
package/src/types.ts CHANGED
@@ -21,7 +21,7 @@ export interface IStakingClient {
21
21
  buy(amount: bigint): Promise<string>;
22
22
 
23
23
  /** Fetch the complete user portfolio */
24
- getPortfolio(): Promise<Portfolio>;
24
+ getPortfolio(): Promise<Portfolio | null>;
25
25
 
26
26
  // Estimated total APY for staking yeild
27
27
  getSystemAPY(): Promise<number>;
@@ -45,12 +45,17 @@ export interface IStakingClient {
45
45
  windowBefore?: number;
46
46
  windowAfter?: number;
47
47
  }): Promise<TrancheSnapshot | null>;
48
-
49
- // Estimated total APY for staking yeild
50
- getSystemAPY(): Promise<number>;
51
48
 
52
- // Protocol fee charged for deposit from Native to LIQ
53
- getDepositFee(amount: bigint): Promise<bigint>;
49
+ /**
50
+ * Estimate a conservative ETH(wei) / SOL(lamports) buffer to leave in the wallet
51
+ * so the user can pay fees for the current deposit and at least one
52
+ * more transaction, plus a bit extra for future interactions.
53
+ */
54
+ estimateGasBuffer(options?: {
55
+ txCount?: number;
56
+ safetyMultiplier?: number;
57
+ minBuffer?: bigint;
58
+ }): Promise<bigint>
54
59
  }
55
60
 
56
61
  /**
@@ -211,10 +216,9 @@ export interface TrancheSnapshot {
211
216
  /** Current tranche price in USD (1e8 scale) */
212
217
  currentPriceUsd: bigint;
213
218
 
214
- /** Tranche curve config (per-chain) */
215
- // TODO make a constant?
216
- supplyGrowthBps: number; // e.g. 100 = +1% per tranche
217
- priceIncrementUsd: number; // e.g. 200 = +2% per tranche
219
+ supplyGrowthBps: number; // 2.5% growth per tranche
220
+
221
+ priceGrowthCents: number; // $0.02 USD growth per tranche
218
222
 
219
223
  totalPretokensSold: bigint; // total pretokens sold across all tranches (1e8 scale)
220
224
 
@@ -1,62 +0,0 @@
1
- import { BaseSignerWalletAdapter } from '@solana/wallet-adapter-base';
2
- import { PublicKey as SolPubKey } from '@solana/web3.js';
3
- import { ChainID, ExternalNetwork, PublicKey } from '@wireio/core';
4
- import { ethers } from 'ethers';
5
-
6
- export interface IStakingClient {
7
- pubKey: PublicKey;
8
- network: ExternalNetwork;
9
-
10
- /** Amount is in the chain's smallest unit (lamports/wei, etc.) */
11
- deposit(amount: bigint): Promise<string>;
12
- withdraw(amount: bigint): Promise<string>;
13
- stake(amount: bigint): Promise<string>;
14
- unstake(amount: bigint): Promise<string>;
15
-
16
- buy?(amount: bigint, purchaseAsset: PurchaseAsset): Promise<string>;
17
-
18
- // REMOVED from shared client, SOLANA ONLY
19
- /** Register any untracked LIQ staked tokens */
20
- // register(): Promise<string>;
21
-
22
- /** Fetch the portfolio for the LIQ stake user */
23
- getPortfolio(): Promise<Portfolio>;
24
- }
25
-
26
- // Enum describing which asset is being used to buy pretoken
27
- export enum PurchaseAsset {
28
- SOL = "SOL",
29
- LIQSOL = "LIQSOL",
30
- ETH = "ETH",
31
- LIQETH = "LIQETH",
32
- YIELD = "YIELD",
33
- }
34
-
35
- export type StakerConfig = {
36
- network: ExternalNetwork;
37
- provider: BaseSignerWalletAdapter | ethers.providers.Web3Provider;
38
- pubKey: PublicKey;
39
- }
40
-
41
- export interface Portfolio {
42
- /** Native balance on chain: ETH, SOL */
43
- native: BalanceView;
44
- /** Actual Liquid balance of LiqETH, LiqSOL*/
45
- liq: BalanceView;
46
- /** Outpost Staked balance */
47
- staked: BalanceView
48
- /** SOL ONLY!
49
- * Tracked liqSOL balance from distribution program */
50
- tracked?: BalanceView;
51
- /** Extra PDAs and account addresses */
52
- extras?: Record<string, any>;
53
- /** Chain ID of the network for which this portfolio is from */
54
- chainID: ChainID;
55
- }
56
-
57
- export type BalanceView = {
58
- amount: bigint; // raw on-chain integer value
59
- decimals: number; // number of decimal places
60
- symbol?: string; // optional token symbol identifier
61
- ata?: SolPubKey; // associated token account address
62
- };