@ape.swap/bonds-sdk 7.0.2 → 7.0.4

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 (39) hide show
  1. package/dist/adapters/solana/index.js +1 -1
  2. package/dist/adapters/solana/index.js.map +1 -1
  3. package/dist/adapters/solana/state/fetchUsersBondsSOL.d.ts +5 -1
  4. package/dist/adapters/solana/state/fetchUsersBondsSOL.js +152 -154
  5. package/dist/adapters/solana/state/fetchUsersBondsSOL.js.map +1 -1
  6. package/dist/adapters/solana/state/fetchUsersBondsSOLMulticall.d.ts +12 -0
  7. package/dist/adapters/solana/state/fetchUsersBondsSOLMulticall.js +162 -0
  8. package/dist/adapters/solana/state/fetchUsersBondsSOLMulticall.js.map +1 -0
  9. package/dist/adapters/solana/utils/calculatePendingRewards.d.ts +2 -0
  10. package/dist/adapters/solana/utils/calculatePendingRewards.js +53 -0
  11. package/dist/adapters/solana/utils/calculatePendingRewards.js.map +1 -0
  12. package/dist/adapters/solana/utils/fetchUserAssetsSOL.d.ts +3 -0
  13. package/dist/adapters/solana/utils/fetchUserAssetsSOL.js +49 -0
  14. package/dist/adapters/solana/utils/fetchUserAssetsSOL.js.map +1 -0
  15. package/dist/adapters/solana/utils/getSolanaAccount.d.ts +5 -0
  16. package/dist/adapters/solana/utils/getSolanaAccount.js +26 -23
  17. package/dist/adapters/solana/utils/getSolanaAccount.js.map +1 -1
  18. package/dist/adapters/solana/utils/solanaMulticall.js +4 -2
  19. package/dist/adapters/solana/utils/solanaMulticall.js.map +1 -1
  20. package/dist/adapters/types.d.ts +1 -1
  21. package/dist/config/constants/networks.d.ts +1 -1
  22. package/dist/config/constants/networks.js +9 -7
  23. package/dist/config/constants/networks.js.map +1 -1
  24. package/dist/state/bonds/useUserBonds.d.ts +10 -1
  25. package/dist/state/bonds/useUserBonds.js +13 -9
  26. package/dist/state/bonds/useUserBonds.js.map +1 -1
  27. package/dist/styles.css +1 -1
  28. package/dist/views/Bonds/components/BondRows/ModalHandler.js +1 -1
  29. package/dist/views/Bonds/components/BondRows/ModalHandler.js.map +1 -1
  30. package/dist/views/Bonds/components/BondsMenu/BondsMenu.js +1 -1
  31. package/dist/views/Bonds/components/BondsMenu/BondsMenu.js.map +1 -1
  32. package/dist/views/FullBondsView/FullBondsView.js +2 -1
  33. package/dist/views/FullBondsView/FullBondsView.js.map +1 -1
  34. package/dist/views/FullBondsView/YourBondsStatus.d.ts +3 -0
  35. package/dist/views/FullBondsView/YourBondsStatus.js +54 -0
  36. package/dist/views/FullBondsView/YourBondsStatus.js.map +1 -0
  37. package/dist/views/YourBonds/components/YourBondsMenu/YourBondsMenu.js +2 -1
  38. package/dist/views/YourBonds/components/YourBondsMenu/YourBondsMenu.js.map +1 -1
  39. package/package.json +1 -1
@@ -24,7 +24,7 @@ const solanaAdapter = {
24
24
  // api-v2 NFT metadata like EVM ones.
25
25
  usesRealtimeApi: true,
26
26
  fetchBondsData: (_chainId, bonds, tokenPrices, apiUrl) => fetchBondsDataSOL(bonds, apiUrl, tokenPrices),
27
- fetchUserBonds: (_chainId, account, bonds, tokenPrices) => fetchUsersBondsSOL(account, bonds, tokenPrices),
27
+ fetchUserBonds: (_chainId, account, bonds, tokenPrices, apiUrl) => fetchUsersBondsSOL(account, bonds, tokenPrices, apiUrl),
28
28
  enrichUserBondsViaApi: true,
29
29
  getTokenBalance: (_chainId, currency, account) => getTokenBalanceSolana(currency, account),
30
30
  getTokensBalances: (_chainId, account, tokens) => getTokensBalanceSolana(account, tokens),
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sources":["../../../src/adapters/solana/index.ts"],"sourcesContent":["// Must stay the first import: sets globalThis.Buffer before any Solana library module is evaluated.\nimport './utils/polyfills'\nimport { ChainId } from '@ape.swap/apeswap-lists'\nimport type { ChainAdapter } from '../types'\nimport SolanaAccountBridge from './state/SolanaAccountBridge'\nimport { fetchBondsDataSOL } from './state/fetchBondsDataSOL'\nimport { fetchUsersBondsSOL } from './state/fetchUsersBondsSOL'\nimport { getTokenBalanceSolana, getTokensBalanceSolana } from './state/useCurrencyBalanceSolana'\nimport BuyComponentSolana from './views/BuyComponentSolana'\nimport UserBondRowSolana from './views/UserBondRowSolana'\nimport ActionsSolana from './views/ActionsSolana'\nimport TransferActionSolana from './views/TransferActionSolana'\nimport ConnectToSolanaBanner from './views/ConnectToSolanaBanner'\n\n/**\n * Solana bonds. Requires `@solana/web3.js`, `@solana/kit`, `@solana/spl-token` and `@solana/wallet-adapter-react`,\n * and the consumer must mount Solana's `ConnectionProvider` + `WalletProvider` (see docs/PLAN_CHAIN_ADAPTERS.md,\n * section 3.2). Sets the `Buffer` global the Solana libraries need (./polyfills).\n */\nexport const solanaAdapter: ChainAdapter = {\n ecosystem: 'sol',\n supportsChain: (chainId) => chainId === ChainId.SOL,\n AccountBridge: SolanaAccountBridge,\n\n // Solana bonds come from the realtime API; fetchBondsData is only the on-chain fallback. Bills are enriched with\n // api-v2 NFT metadata like EVM ones.\n usesRealtimeApi: true,\n fetchBondsData: (_chainId, bonds, tokenPrices, apiUrl) => fetchBondsDataSOL(bonds, apiUrl, tokenPrices),\n fetchUserBonds: (_chainId, account, bonds, tokenPrices) => fetchUsersBondsSOL(account, bonds, tokenPrices),\n enrichUserBondsViaApi: true,\n getTokenBalance: (_chainId, currency, account) => getTokenBalanceSolana(currency, account),\n getTokensBalances: (_chainId, account, tokens) => getTokensBalanceSolana(account, tokens),\n\n BuyComponent: BuyComponentSolana,\n UserBondRow: UserBondRowSolana,\n BondActions: ActionsSolana,\n TransferAction: TransferActionSolana,\n // No ClaimAllAction: Claim All didn't support Solana before the adapter split either.\n ConnectBanner: ConnectToSolanaBanner,\n}\n"],"names":["TransferActionSolana","ConnectToSolanaBanner"],"mappings":";;;;;;;;;;;;AAAA;AAcA;;;;AAIG;AACI,MAAM,aAAa,GAAiB;AACzC,IAAA,SAAS,EAAE,KAAK;IAChB,aAAa,EAAE,CAAC,OAAO,KAAK,OAAO,KAAK,OAAO,CAAC,GAAG;AACnD,IAAA,aAAa,EAAE,mBAAmB;;;AAIlC,IAAA,eAAe,EAAE,IAAI;AACrB,IAAA,cAAc,EAAE,CAAC,QAAQ,EAAE,KAAK,EAAE,WAAW,EAAE,MAAM,KAAK,iBAAiB,CAAC,KAAK,EAAE,MAAM,EAAE,WAAW,CAAC;AACvG,IAAA,cAAc,EAAE,CAAC,QAAQ,EAAE,OAAO,EAAE,KAAK,EAAE,WAAW,KAAK,kBAAkB,CAAC,OAAO,EAAE,KAAK,EAAE,WAAW,CAAC;AAC1G,IAAA,qBAAqB,EAAE,IAAI;AAC3B,IAAA,eAAe,EAAE,CAAC,QAAQ,EAAE,QAAQ,EAAE,OAAO,KAAK,qBAAqB,CAAC,QAAQ,EAAE,OAAO,CAAC;AAC1F,IAAA,iBAAiB,EAAE,CAAC,QAAQ,EAAE,OAAO,EAAE,MAAM,KAAK,sBAAsB,CAAC,OAAO,EAAE,MAAM,CAAC;AAEzF,IAAA,YAAY,EAAE,kBAAkB;AAChC,IAAA,WAAW,EAAE,iBAAiB;AAC9B,IAAA,WAAW,EAAE,aAAa;AAC1B,IAAA,cAAc,EAAEA,cAAoB;;AAEpC,IAAA,aAAa,EAAEC,mBAAqB;;;;;"}
1
+ {"version":3,"file":"index.js","sources":["../../../src/adapters/solana/index.ts"],"sourcesContent":["// Must stay the first import: sets globalThis.Buffer before any Solana library module is evaluated.\nimport './utils/polyfills'\nimport { ChainId } from '@ape.swap/apeswap-lists'\nimport type { ChainAdapter } from '../types'\nimport SolanaAccountBridge from './state/SolanaAccountBridge'\nimport { fetchBondsDataSOL } from './state/fetchBondsDataSOL'\nimport { fetchUsersBondsSOL } from './state/fetchUsersBondsSOL'\nimport { getTokenBalanceSolana, getTokensBalanceSolana } from './state/useCurrencyBalanceSolana'\nimport BuyComponentSolana from './views/BuyComponentSolana'\nimport UserBondRowSolana from './views/UserBondRowSolana'\nimport ActionsSolana from './views/ActionsSolana'\nimport TransferActionSolana from './views/TransferActionSolana'\nimport ConnectToSolanaBanner from './views/ConnectToSolanaBanner'\n\n/**\n * Solana bonds. Requires `@solana/web3.js`, `@solana/kit`, `@solana/spl-token` and `@solana/wallet-adapter-react`,\n * and the consumer must mount Solana's `ConnectionProvider` + `WalletProvider` (see docs/PLAN_CHAIN_ADAPTERS.md,\n * section 3.2). Sets the `Buffer` global the Solana libraries need (./polyfills).\n */\nexport const solanaAdapter: ChainAdapter = {\n ecosystem: 'sol',\n supportsChain: (chainId) => chainId === ChainId.SOL,\n AccountBridge: SolanaAccountBridge,\n\n // Solana bonds come from the realtime API; fetchBondsData is only the on-chain fallback. Bills are enriched with\n // api-v2 NFT metadata like EVM ones.\n usesRealtimeApi: true,\n fetchBondsData: (_chainId, bonds, tokenPrices, apiUrl) => fetchBondsDataSOL(bonds, apiUrl, tokenPrices),\n fetchUserBonds: (_chainId, account, bonds, tokenPrices, apiUrl) =>\n fetchUsersBondsSOL(account, bonds, tokenPrices, apiUrl),\n enrichUserBondsViaApi: true,\n getTokenBalance: (_chainId, currency, account) => getTokenBalanceSolana(currency, account),\n getTokensBalances: (_chainId, account, tokens) => getTokensBalanceSolana(account, tokens),\n\n BuyComponent: BuyComponentSolana,\n UserBondRow: UserBondRowSolana,\n BondActions: ActionsSolana,\n TransferAction: TransferActionSolana,\n // No ClaimAllAction: Claim All didn't support Solana before the adapter split either.\n ConnectBanner: ConnectToSolanaBanner,\n}\n"],"names":["TransferActionSolana","ConnectToSolanaBanner"],"mappings":";;;;;;;;;;;;AAAA;AAcA;;;;AAIG;AACI,MAAM,aAAa,GAAiB;AACzC,IAAA,SAAS,EAAE,KAAK;IAChB,aAAa,EAAE,CAAC,OAAO,KAAK,OAAO,KAAK,OAAO,CAAC,GAAG;AACnD,IAAA,aAAa,EAAE,mBAAmB;;;AAIlC,IAAA,eAAe,EAAE,IAAI;AACrB,IAAA,cAAc,EAAE,CAAC,QAAQ,EAAE,KAAK,EAAE,WAAW,EAAE,MAAM,KAAK,iBAAiB,CAAC,KAAK,EAAE,MAAM,EAAE,WAAW,CAAC;IACvG,cAAc,EAAE,CAAC,QAAQ,EAAE,OAAO,EAAE,KAAK,EAAE,WAAW,EAAE,MAAM,KAC5D,kBAAkB,CAAC,OAAO,EAAE,KAAK,EAAE,WAAW,EAAE,MAAM,CAAC;AACzD,IAAA,qBAAqB,EAAE,IAAI;AAC3B,IAAA,eAAe,EAAE,CAAC,QAAQ,EAAE,QAAQ,EAAE,OAAO,KAAK,qBAAqB,CAAC,QAAQ,EAAE,OAAO,CAAC;AAC1F,IAAA,iBAAiB,EAAE,CAAC,QAAQ,EAAE,OAAO,EAAE,MAAM,KAAK,sBAAsB,CAAC,OAAO,EAAE,MAAM,CAAC;AAEzF,IAAA,YAAY,EAAE,kBAAkB;AAChC,IAAA,WAAW,EAAE,iBAAiB;AAC9B,IAAA,WAAW,EAAE,aAAa;AAC1B,IAAA,cAAc,EAAEA,cAAoB;;AAEpC,IAAA,aAAa,EAAEC,mBAAqB;;;;;"}
@@ -1,4 +1,8 @@
1
1
  import { BillsConfig, ChainId } from '@ape.swap/apeswap-lists';
2
2
  import { UserOwnedBillsData } from '../../../types/yourbonds';
3
3
  import { TokenPrices } from '../../../state/tokenPrices/useTokenPrices';
4
- export declare function fetchUsersBondsSOL(user: string, bondData: BillsConfig[], tokenPrices: Partial<Record<ChainId, TokenPrices[]>>): Promise<UserOwnedBillsData[]>;
4
+ /**
5
+ * Fetches the user's Solana bonds with the multicall strategy (batched account reads and simulations). If it fails
6
+ * the error is reported to the API and no bonds are returned.
7
+ */
8
+ export declare function fetchUsersBondsSOL(user: string, bondData: BillsConfig[], tokenPrices: Partial<Record<ChainId, TokenPrices[]>>, apiUrl: string): Promise<UserOwnedBillsData[]>;
@@ -1,165 +1,163 @@
1
- import { ChainId } from '@ape.swap/apeswap-lists';
2
- import { PublicKey, Connection, TransactionInstruction, Transaction } from '@solana/web3.js';
3
- import { getBondAccountSolana } from '../utils/getSolanaAccount.js';
4
- import { getU64Codec } from '@solana/kit';
5
- import { PROGRAM_ID, CLAIMABLE_PAYOUT_DISCRIMINATOR } from '../constants/solanaConstants.js';
6
- import { Buffer } from 'buffer';
7
- import { getRPC, HELIUS_SOL_RPC } from '../../../config/constants/networks.js';
8
- import { apiGet } from '../../../utils/httpClient.js';
9
- import { PRICE_API_URL } from '../../../config/constants/variables.js';
10
-
11
- async function fetchUsersBondsSOL(user, bondData, tokenPrices) {
12
- if (!user)
13
- return [];
1
+ import { reportError } from '../../../utils/reportError.js';
2
+ import { fetchUsersBondsSOLMulticall } from './fetchUsersBondsSOLMulticall.js';
3
+
4
+ /**
5
+ * Fetches the user's Solana bonds with the multicall strategy (batched account reads and simulations). If it fails
6
+ * the error is reported to the API and no bonds are returned.
7
+ */
8
+ async function fetchUsersBondsSOL(user, bondData, tokenPrices, apiUrl) {
14
9
  try {
15
- console.time('fetchUserBondsSOL');
16
- const results = [];
17
- const userBase58 = new PublicKey(user).toBase58();
18
- const connection = new Connection(getRPC(ChainId.SOL), 'confirmed');
19
- const collections = bondData.map((bond) => bond.billNnftAddress[ChainId.SOL]);
20
- const allOptions = {
21
- method: 'POST',
22
- headers: { 'Content-Type': 'application/json' },
23
- body: JSON.stringify({
24
- jsonrpc: '2.0',
25
- id: '1',
26
- method: 'searchAssets',
27
- params: {
28
- ownerAddress: userBase58,
29
- collections,
30
- },
31
- }),
32
- };
33
- const allResponse = await fetch(HELIUS_SOL_RPC, allOptions);
34
- const allData = await allResponse.json();
35
- const allUserAssets = allData.result.items;
36
- for (const bond of bondData) {
37
- if (bond.chainId !== ChainId.SOL || !bond.contractAddress[ChainId.SOL])
38
- continue;
39
- const bondIssuancePda = new PublicKey(bond.contractAddress[ChainId.SOL]);
40
- const userBills = [];
41
- const collection = bond.billNnftAddress[ChainId.SOL];
42
- let principalTokenPrice = tokenPrices?.[ChainId.SOL]?.find?.((t) => t?.tokenAddress?.toLowerCase() === bond?.lpToken?.address?.[ChainId.SOL]?.toLowerCase())?.price;
43
- let payoutTokenPrice = tokenPrices?.[ChainId.SOL]?.find?.((t) => t?.tokenAddress?.toLowerCase() === bond?.earnToken?.address?.[ChainId.SOL]?.toLowerCase())?.price;
44
- if (principalTokenPrice === undefined) {
45
- const res = await apiGet(`${PRICE_API_URL}/realtime/price?token=${bond?.lpToken?.address?.[bond?.chainId]}&chain=${bond?.chainId}`);
46
- console.log('Individual call for: ', bond.lpToken.symbol, '. Price: ', res.price);
47
- principalTokenPrice = parseFloat(res.price ?? '0');
48
- }
49
- if (payoutTokenPrice === undefined) {
50
- const res = await apiGet(`${PRICE_API_URL}/realtime/price?token=${bond?.earnToken?.address?.[bond?.chainId]}&chain=${bond?.chainId}`);
51
- console.log('Individual call for: ', bond.earnToken.symbol, '. Price: ', res.price);
52
- payoutTokenPrice = parseFloat(res.price ?? '0');
53
- }
54
- const userAssets = allUserAssets.filter((asset) => {
55
- return asset.authorities[0].address.toLowerCase() === bond.contractAddress[ChainId.SOL]?.toLowerCase();
56
- });
57
- for (const asset of userAssets) {
58
- const nftMint = new PublicKey(asset.id);
59
- const [bondPda] = PublicKey.findProgramAddressSync([new TextEncoder().encode('bond'), bondIssuancePda.toBuffer(), nftMint.toBuffer()], PROGRAM_ID);
60
- if (!bondPda) {
61
- console.error(`Could not fetch bond PDA for mint ${nftMint.toBase58()}`);
62
- continue;
63
- }
64
- try {
65
- const [bondTermPda] = PublicKey.findProgramAddressSync([new TextEncoder().encode('bond_term'), bondIssuancePda.toBuffer()], PROGRAM_ID);
66
- const [bondAccounts, pendingRewards] = await Promise.all([
67
- getBondAccountSolana([bondPda.toBase58()]),
68
- calculatePendingRewards(connection, bondPda, bondTermPda, bondIssuancePda, nftMint),
69
- ]);
70
- if (!bondAccounts?.length) {
71
- console.error(`Could not fetch bond account for mint ${nftMint.toBase58()}`);
72
- continue;
73
- }
74
- const bondAccount = bondAccounts[0];
75
- const id = asset.content.metadata.name;
76
- const idTrimmed = id.split('-')[1]?.trim();
77
- userBills.push({
78
- address: bondPda.toBase58(),
79
- id: idTrimmed,
80
- asset: nftMint.toBase58(),
81
- // vesting: bondAccount.vesting.toString(),
82
- vestingTerm: bondAccount.vestingTerm.toString(),
83
- payout: (bondAccount.payout - bondAccount.payoutClaimed).toString(),
84
- totalPayout: bondAccount.payout.toString(),
85
- truePricePaid: bondAccount.truePricePaid.toString(),
86
- lastBlockTimestamp: bondAccount.lastClaimTimestamp.toString(),
87
- vestingStartTimestamp: bondAccount.vestingStartTimestamp.toString(),
88
- pendingRewards,
89
- billNftAddress: collection,
90
- payoutClaimed: bondAccount.payoutClaimed.toString(),
91
- bond: {
92
- ...bond,
93
- payoutTokenPrice: payoutTokenPrice?.toString(),
94
- principalTokenPrice: principalTokenPrice?.toString(),
95
- },
96
- });
97
- // ULTRA HOT FIX :( until we can figure out a multicall strategy
98
- await new Promise((r) => setTimeout(r, 10));
99
- }
100
- catch (e) {
101
- console.log(e);
102
- console.error(`Could not fetch bond PDA for mint ${nftMint.toBase58()}`);
103
- }
104
- }
105
- results.push({
106
- index: bond.index,
107
- userOwnedBills: userBills,
108
- });
109
- }
110
- console.timeEnd('fetchUserBondsSOL');
111
- return results;
10
+ return await fetchUsersBondsSOLMulticall(user, bondData, tokenPrices);
112
11
  }
113
- catch (err) {
114
- console.error('Error fetching assets by collection:', err);
12
+ catch (error) {
13
+ reportError({
14
+ apiUrl,
15
+ error,
16
+ account: user,
17
+ extraInfo: { type: 'user bonds fetching failed solana' },
18
+ });
19
+ // TODO: a failure is returned as "no bonds", so the user can't tell it apart from an empty account (and Query
20
+ // caches it as a success). Surface failed chains to the user instead, and let Query retry.
115
21
  return [];
116
22
  }
117
23
  }
118
- async function calculatePendingRewards(connection, bondPda, bondTermPda, bondIssuancePda, nftMint) {
119
- try {
120
- const u64Codec = getU64Codec();
121
- const currentTimestamp = Math.floor(Date.now() / 1000);
122
- const encodedTimestamp = u64Codec.encode(BigInt(currentTimestamp));
123
- // Instruction data
124
- const instructionData = Buffer.concat([CLAIMABLE_PAYOUT_DISCRIMINATOR, Buffer.from(encodedTimestamp)]);
125
- // Accounts
126
- const keys = [
127
- { pubkey: bondPda, isSigner: false, isWritable: true },
128
- { pubkey: bondTermPda, isSigner: false, isWritable: false },
129
- { pubkey: bondIssuancePda, isSigner: false, isWritable: false },
130
- { pubkey: nftMint, isSigner: false, isWritable: false },
131
- ];
132
- // Instruction
133
- const instruction = new TransactionInstruction({
134
- keys,
135
- programId: PROGRAM_ID,
136
- data: instructionData,
137
- });
138
- // Transaction
139
- const transaction = new Transaction();
140
- const userPublicKey = new PublicKey('11111111111111111111111111111112'); // Dummy public key for fee payer
141
- transaction.feePayer = userPublicKey;
142
- transaction.add(instruction);
143
- // Simulate
144
- const simulation = await connection.simulateTransaction(transaction);
145
- // Extract return value
146
- const returnData = simulation.value.returnData;
147
- if (returnData) {
148
- const [data, encoding] = returnData.data;
149
- if (encoding !== 'base64') {
150
- throw new Error(`Unexpected return data encoding: ${encoding}`);
151
- }
152
- const decodedData = Buffer.from(data, 'base64');
153
- return decodedData.readBigUInt64LE(0).toString();
24
+ // Previous implementation, kept for reference only: one RPC round trip per owned NFT (~10x slower). It must never be
25
+ // used as a fallback. Restoring it needs its imports back:
26
+ // Connection/PublicKey (@solana/web3.js), HeliusAsset, UserBill, getBondAccountSolana, PROGRAM_ID,
27
+ // calculatePendingRewards, fetchUserAssetsSOL, getRPC, apiGet and PRICE_API_URL.
28
+ /*
29
+ import { Connection, PublicKey } from '@solana/web3.js'
30
+ import { UserBill } from '../../../types/yourbonds'
31
+ import { HeliusAsset } from '../types/helius'
32
+ import { getBondAccountSolana } from '../utils/getSolanaAccount'
33
+ import { PROGRAM_ID } from '../constants/solanaConstants'
34
+ import { calculatePendingRewards } from '../utils/calculatePendingRewards'
35
+ import { fetchUserAssetsSOL } from '../utils/fetchUserAssetsSOL'
36
+ import { getRPC } from '../../../config/constants/networks'
37
+ import { apiGet } from '../../../utils/httpClient'
38
+ import { PRICE_API_URL } from '../../../config/constants/variables'
39
+
40
+ export async function fetchUsersBondsSOLSequential(
41
+ user: string,
42
+ bondData: BillsConfig[],
43
+ tokenPrices: Partial<Record<ChainId, TokenPrices[]>>,
44
+ prefetchedAssets?: HeliusAsset[],
45
+ ): Promise<UserOwnedBillsData[]> {
46
+ if (!user) return []
47
+ try {
48
+ console.time('fetchUserBondsSOL')
49
+ const results: UserOwnedBillsData[] = []
50
+
51
+ const connection = new Connection(getRPC(ChainId.SOL), 'confirmed')
52
+
53
+ const collections: string[] = bondData.map((bond) => bond.billNnftAddress[ChainId.SOL]!)
54
+
55
+ const allUserAssets: HeliusAsset[] = prefetchedAssets ?? (await fetchUserAssetsSOL(user, collections))
56
+
57
+ for (const bond of bondData) {
58
+ if (bond.chainId !== ChainId.SOL || !bond.contractAddress[ChainId.SOL]) continue
59
+ const bondIssuancePda = new PublicKey(bond.contractAddress[ChainId.SOL])
60
+ const userBills: UserBill[] = []
61
+ const collection = bond.billNnftAddress[ChainId.SOL]
62
+
63
+ let principalTokenPrice = tokenPrices?.[ChainId.SOL]?.find?.(
64
+ (t) => t?.tokenAddress?.toLowerCase() === bond?.lpToken?.address?.[ChainId.SOL]?.toLowerCase(),
65
+ )?.price
66
+ let payoutTokenPrice = tokenPrices?.[ChainId.SOL]?.find?.(
67
+ (t) => t?.tokenAddress?.toLowerCase() === bond?.earnToken?.address?.[ChainId.SOL]?.toLowerCase(),
68
+ )?.price
69
+
70
+ if (principalTokenPrice === undefined) {
71
+ const res = await apiGet<{ price: string }>(
72
+ `${PRICE_API_URL}/realtime/price?token=${bond?.lpToken?.address?.[bond?.chainId]}&chain=${bond?.chainId}`,
73
+ )
74
+ console.log('Individual call for: ', bond.lpToken.symbol, '. Price: ', res.price)
75
+ principalTokenPrice = parseFloat(res.price ?? '0')
76
+ }
77
+ if (payoutTokenPrice === undefined) {
78
+ const res = await apiGet<{ price: string }>(
79
+ `${PRICE_API_URL}/realtime/price?token=${bond?.earnToken?.address?.[bond?.chainId]}&chain=${bond?.chainId}`,
80
+ )
81
+ console.log('Individual call for: ', bond.earnToken.symbol, '. Price: ', res.price)
82
+ payoutTokenPrice = parseFloat(res.price ?? '0')
83
+ }
84
+
85
+ const userAssets = allUserAssets.filter((asset) => {
86
+ return asset.authorities[0].address.toLowerCase() === bond.contractAddress[ChainId.SOL]?.toLowerCase()
87
+ })
88
+
89
+ for (const asset of userAssets) {
90
+ const nftMint = new PublicKey(asset.id)
91
+
92
+ const [bondPda] = PublicKey.findProgramAddressSync(
93
+ [new TextEncoder().encode('bond'), bondIssuancePda.toBuffer(), nftMint.toBuffer()],
94
+ PROGRAM_ID,
95
+ )
96
+
97
+ if (!bondPda) {
98
+ console.error(`Could not fetch bond PDA for mint ${nftMint.toBase58()}`)
99
+ continue
154
100
  }
155
- // Fallback
156
- return '0';
157
- }
158
- catch (error) {
159
- console.error('Error calculating pending rewards:', error);
160
- return '0';
101
+
102
+ try {
103
+ const [bondTermPda] = PublicKey.findProgramAddressSync(
104
+ [new TextEncoder().encode('bond_term'), bondIssuancePda.toBuffer()],
105
+ PROGRAM_ID,
106
+ )
107
+
108
+ const [bondAccounts, pendingRewards] = await Promise.all([
109
+ getBondAccountSolana([bondPda.toBase58()]),
110
+ calculatePendingRewards(connection, bondPda, bondTermPda, bondIssuancePda, nftMint),
111
+ ])
112
+
113
+ if (!bondAccounts?.length) {
114
+ console.error(`Could not fetch bond account for mint ${nftMint.toBase58()}`)
115
+ continue
116
+ }
117
+
118
+ const bondAccount = bondAccounts[0]
119
+ const id = asset.content.metadata.name
120
+ const idTrimmed: string = id.split('-')[1]?.trim()
121
+ userBills.push({
122
+ address: bondPda.toBase58(),
123
+ id: idTrimmed,
124
+ asset: nftMint.toBase58(),
125
+ // vesting: bondAccount.vesting.toString(),
126
+ vestingTerm: bondAccount.vestingTerm.toString(),
127
+ payout: (bondAccount.payout - bondAccount.payoutClaimed).toString(),
128
+ totalPayout: bondAccount.payout.toString(),
129
+ truePricePaid: bondAccount.truePricePaid.toString(),
130
+ lastBlockTimestamp: bondAccount.lastClaimTimestamp.toString(),
131
+ vestingStartTimestamp: bondAccount.vestingStartTimestamp.toString(),
132
+ pendingRewards,
133
+ billNftAddress: collection as string,
134
+ payoutClaimed: bondAccount.payoutClaimed.toString(),
135
+ bond: {
136
+ ...bond,
137
+ payoutTokenPrice: payoutTokenPrice?.toString(),
138
+ principalTokenPrice: principalTokenPrice?.toString(),
139
+ },
140
+ })
141
+ // ULTRA HOT FIX :( until we can figure out a multicall strategy
142
+ await new Promise((r) => setTimeout(r, 10))
143
+ } catch (e) {
144
+ console.log(e)
145
+ console.error(`Could not fetch bond PDA for mint ${nftMint.toBase58()}`)
146
+ }
147
+ }
148
+ results.push({
149
+ index: bond.index,
150
+ userOwnedBills: userBills,
151
+ })
161
152
  }
153
+ console.timeEnd('fetchUserBondsSOL')
154
+ return results
155
+ } catch (err) {
156
+ console.error('Error fetching assets by collection:', err)
157
+ return []
158
+ }
162
159
  }
160
+ */
163
161
 
164
162
  export { fetchUsersBondsSOL };
165
163
  //# sourceMappingURL=fetchUsersBondsSOL.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"fetchUsersBondsSOL.js","sources":["../../../../src/adapters/solana/state/fetchUsersBondsSOL.ts"],"sourcesContent":["import { BillsConfig, ChainId } from '@ape.swap/apeswap-lists'\nimport { UserBill, UserOwnedBillsData } from '../../../types/yourbonds'\nimport { Connection, PublicKey, Transaction, TransactionInstruction } from '@solana/web3.js'\nimport { TokenPrices } from '../../../state/tokenPrices/useTokenPrices'\nimport { HeliusApiResponse, HeliusAsset } from '../types/helius'\nimport { getBondAccountSolana } from '../utils/getSolanaAccount'\nimport { getU64Codec } from '@solana/kit'\nimport { CLAIMABLE_PAYOUT_DISCRIMINATOR, PROGRAM_ID } from '../constants/solanaConstants'\nimport { Buffer } from 'buffer'\nimport { getRPC, HELIUS_SOL_RPC } from '../../../config/constants/networks'\nimport { apiGet } from '../../../utils/httpClient'\nimport { PRICE_API_URL } from '../../../config/constants/variables'\n\nexport async function fetchUsersBondsSOL(\n user: string,\n bondData: BillsConfig[],\n tokenPrices: Partial<Record<ChainId, TokenPrices[]>>,\n): Promise<UserOwnedBillsData[]> {\n if (!user) return []\n try {\n console.time('fetchUserBondsSOL')\n const results: UserOwnedBillsData[] = []\n\n const userBase58 = new PublicKey(user).toBase58()\n\n const connection = new Connection(getRPC(ChainId.SOL), 'confirmed')\n\n const collections: string[] = bondData.map((bond) => bond.billNnftAddress[ChainId.SOL]!)\n\n const allOptions = {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({\n jsonrpc: '2.0',\n id: '1',\n method: 'searchAssets',\n params: {\n ownerAddress: userBase58,\n collections,\n },\n }),\n }\n const allResponse = await fetch(HELIUS_SOL_RPC, allOptions)\n const allData: HeliusApiResponse = await allResponse.json()\n const allUserAssets: HeliusAsset[] = allData.result.items\n\n for (const bond of bondData) {\n if (bond.chainId !== ChainId.SOL || !bond.contractAddress[ChainId.SOL]) continue\n const bondIssuancePda = new PublicKey(bond.contractAddress[ChainId.SOL])\n const userBills: UserBill[] = []\n const collection = bond.billNnftAddress[ChainId.SOL]\n\n let principalTokenPrice = tokenPrices?.[ChainId.SOL]?.find?.(\n (t) => t?.tokenAddress?.toLowerCase() === bond?.lpToken?.address?.[ChainId.SOL]?.toLowerCase(),\n )?.price\n let payoutTokenPrice = tokenPrices?.[ChainId.SOL]?.find?.(\n (t) => t?.tokenAddress?.toLowerCase() === bond?.earnToken?.address?.[ChainId.SOL]?.toLowerCase(),\n )?.price\n\n if (principalTokenPrice === undefined) {\n const res = await apiGet<{ price: string }>(\n `${PRICE_API_URL}/realtime/price?token=${bond?.lpToken?.address?.[bond?.chainId]}&chain=${bond?.chainId}`,\n )\n console.log('Individual call for: ', bond.lpToken.symbol, '. Price: ', res.price)\n principalTokenPrice = parseFloat(res.price ?? '0')\n }\n if (payoutTokenPrice === undefined) {\n const res = await apiGet<{ price: string }>(\n `${PRICE_API_URL}/realtime/price?token=${bond?.earnToken?.address?.[bond?.chainId]}&chain=${bond?.chainId}`,\n )\n console.log('Individual call for: ', bond.earnToken.symbol, '. Price: ', res.price)\n payoutTokenPrice = parseFloat(res.price ?? '0')\n }\n\n const userAssets = allUserAssets.filter((asset) => {\n return asset.authorities[0].address.toLowerCase() === bond.contractAddress[ChainId.SOL]?.toLowerCase()\n })\n\n for (const asset of userAssets) {\n const nftMint = new PublicKey(asset.id)\n\n const [bondPda] = PublicKey.findProgramAddressSync(\n [new TextEncoder().encode('bond'), bondIssuancePda.toBuffer(), nftMint.toBuffer()],\n PROGRAM_ID,\n )\n\n if (!bondPda) {\n console.error(`Could not fetch bond PDA for mint ${nftMint.toBase58()}`)\n continue\n }\n\n try {\n const [bondTermPda] = PublicKey.findProgramAddressSync(\n [new TextEncoder().encode('bond_term'), bondIssuancePda.toBuffer()],\n PROGRAM_ID,\n )\n\n const [bondAccounts, pendingRewards] = await Promise.all([\n getBondAccountSolana([bondPda.toBase58()]),\n calculatePendingRewards(connection, bondPda, bondTermPda, bondIssuancePda, nftMint),\n ])\n\n if (!bondAccounts?.length) {\n console.error(`Could not fetch bond account for mint ${nftMint.toBase58()}`)\n continue\n }\n\n const bondAccount = bondAccounts[0]\n const id = asset.content.metadata.name\n const idTrimmed: string = id.split('-')[1]?.trim()\n userBills.push({\n address: bondPda.toBase58(),\n id: idTrimmed,\n asset: nftMint.toBase58(),\n // vesting: bondAccount.vesting.toString(),\n vestingTerm: bondAccount.vestingTerm.toString(),\n payout: (bondAccount.payout - bondAccount.payoutClaimed).toString(),\n totalPayout: bondAccount.payout.toString(),\n truePricePaid: bondAccount.truePricePaid.toString(),\n lastBlockTimestamp: bondAccount.lastClaimTimestamp.toString(),\n vestingStartTimestamp: bondAccount.vestingStartTimestamp.toString(),\n pendingRewards,\n billNftAddress: collection as string,\n payoutClaimed: bondAccount.payoutClaimed.toString(),\n bond: {\n ...bond,\n payoutTokenPrice: payoutTokenPrice?.toString(),\n principalTokenPrice: principalTokenPrice?.toString(),\n },\n })\n // ULTRA HOT FIX :( until we can figure out a multicall strategy\n await new Promise((r) => setTimeout(r, 10))\n } catch (e) {\n console.log(e)\n console.error(`Could not fetch bond PDA for mint ${nftMint.toBase58()}`)\n }\n }\n results.push({\n index: bond.index,\n userOwnedBills: userBills,\n })\n }\n console.timeEnd('fetchUserBondsSOL')\n return results\n } catch (err) {\n console.error('Error fetching assets by collection:', err)\n return []\n }\n}\n\nasync function calculatePendingRewards(\n connection: Connection,\n bondPda: PublicKey,\n bondTermPda: PublicKey,\n bondIssuancePda: PublicKey,\n nftMint: PublicKey,\n): Promise<string> {\n try {\n const u64Codec = getU64Codec()\n\n const currentTimestamp = Math.floor(Date.now() / 1000)\n\n const encodedTimestamp = u64Codec.encode(BigInt(currentTimestamp))\n\n // Instruction data\n const instructionData = Buffer.concat([CLAIMABLE_PAYOUT_DISCRIMINATOR, Buffer.from(encodedTimestamp)])\n\n // Accounts\n const keys = [\n { pubkey: bondPda, isSigner: false, isWritable: true },\n { pubkey: bondTermPda, isSigner: false, isWritable: false },\n { pubkey: bondIssuancePda, isSigner: false, isWritable: false },\n { pubkey: nftMint, isSigner: false, isWritable: false },\n ]\n\n // Instruction\n const instruction = new TransactionInstruction({\n keys,\n programId: PROGRAM_ID,\n data: instructionData,\n })\n\n // Transaction\n const transaction = new Transaction()\n const userPublicKey = new PublicKey('11111111111111111111111111111112') // Dummy public key for fee payer\n transaction.feePayer = userPublicKey\n transaction.add(instruction)\n\n // Simulate\n const simulation = await connection.simulateTransaction(transaction)\n\n // Extract return value\n const returnData = simulation.value.returnData\n if (returnData) {\n const [data, encoding] = returnData.data\n if (encoding !== 'base64') {\n throw new Error(`Unexpected return data encoding: ${encoding}`)\n }\n\n const decodedData = Buffer.from(data, 'base64')\n return decodedData.readBigUInt64LE(0).toString()\n }\n\n // Fallback\n return '0'\n } catch (error) {\n console.error('Error calculating pending rewards:', error)\n return '0'\n }\n}\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"fetchUsersBondsSOL.js","sources":["../../../../src/adapters/solana/state/fetchUsersBondsSOL.ts"],"sourcesContent":["import { BillsConfig, ChainId } from '@ape.swap/apeswap-lists'\nimport { UserOwnedBillsData } from '../../../types/yourbonds'\nimport { TokenPrices } from '../../../state/tokenPrices/useTokenPrices'\nimport { reportError } from '../../../utils/reportError'\nimport { fetchUsersBondsSOLMulticall } from './fetchUsersBondsSOLMulticall'\n\n/**\n * Fetches the user's Solana bonds with the multicall strategy (batched account reads and simulations). If it fails\n * the error is reported to the API and no bonds are returned.\n */\nexport async function fetchUsersBondsSOL(\n user: string,\n bondData: BillsConfig[],\n tokenPrices: Partial<Record<ChainId, TokenPrices[]>>,\n apiUrl: string,\n): Promise<UserOwnedBillsData[]> {\n try {\n return await fetchUsersBondsSOLMulticall(user, bondData, tokenPrices)\n } catch (error) {\n reportError({\n apiUrl,\n error,\n account: user,\n extraInfo: { type: 'user bonds fetching failed solana' },\n })\n // TODO: a failure is returned as \"no bonds\", so the user can't tell it apart from an empty account (and Query\n // caches it as a success). Surface failed chains to the user instead, and let Query retry.\n return []\n }\n}\n\n// Previous implementation, kept for reference only: one RPC round trip per owned NFT (~10x slower). It must never be\n// used as a fallback. Restoring it needs its imports back:\n// Connection/PublicKey (@solana/web3.js), HeliusAsset, UserBill, getBondAccountSolana, PROGRAM_ID,\n// calculatePendingRewards, fetchUserAssetsSOL, getRPC, apiGet and PRICE_API_URL.\n/*\nimport { Connection, PublicKey } from '@solana/web3.js'\nimport { UserBill } from '../../../types/yourbonds'\nimport { HeliusAsset } from '../types/helius'\nimport { getBondAccountSolana } from '../utils/getSolanaAccount'\nimport { PROGRAM_ID } from '../constants/solanaConstants'\nimport { calculatePendingRewards } from '../utils/calculatePendingRewards'\nimport { fetchUserAssetsSOL } from '../utils/fetchUserAssetsSOL'\nimport { getRPC } from '../../../config/constants/networks'\nimport { apiGet } from '../../../utils/httpClient'\nimport { PRICE_API_URL } from '../../../config/constants/variables'\n\nexport async function fetchUsersBondsSOLSequential(\n user: string,\n bondData: BillsConfig[],\n tokenPrices: Partial<Record<ChainId, TokenPrices[]>>,\n prefetchedAssets?: HeliusAsset[],\n): Promise<UserOwnedBillsData[]> {\n if (!user) return []\n try {\n console.time('fetchUserBondsSOL')\n const results: UserOwnedBillsData[] = []\n\n const connection = new Connection(getRPC(ChainId.SOL), 'confirmed')\n\n const collections: string[] = bondData.map((bond) => bond.billNnftAddress[ChainId.SOL]!)\n\n const allUserAssets: HeliusAsset[] = prefetchedAssets ?? (await fetchUserAssetsSOL(user, collections))\n\n for (const bond of bondData) {\n if (bond.chainId !== ChainId.SOL || !bond.contractAddress[ChainId.SOL]) continue\n const bondIssuancePda = new PublicKey(bond.contractAddress[ChainId.SOL])\n const userBills: UserBill[] = []\n const collection = bond.billNnftAddress[ChainId.SOL]\n\n let principalTokenPrice = tokenPrices?.[ChainId.SOL]?.find?.(\n (t) => t?.tokenAddress?.toLowerCase() === bond?.lpToken?.address?.[ChainId.SOL]?.toLowerCase(),\n )?.price\n let payoutTokenPrice = tokenPrices?.[ChainId.SOL]?.find?.(\n (t) => t?.tokenAddress?.toLowerCase() === bond?.earnToken?.address?.[ChainId.SOL]?.toLowerCase(),\n )?.price\n\n if (principalTokenPrice === undefined) {\n const res = await apiGet<{ price: string }>(\n `${PRICE_API_URL}/realtime/price?token=${bond?.lpToken?.address?.[bond?.chainId]}&chain=${bond?.chainId}`,\n )\n console.log('Individual call for: ', bond.lpToken.symbol, '. Price: ', res.price)\n principalTokenPrice = parseFloat(res.price ?? '0')\n }\n if (payoutTokenPrice === undefined) {\n const res = await apiGet<{ price: string }>(\n `${PRICE_API_URL}/realtime/price?token=${bond?.earnToken?.address?.[bond?.chainId]}&chain=${bond?.chainId}`,\n )\n console.log('Individual call for: ', bond.earnToken.symbol, '. Price: ', res.price)\n payoutTokenPrice = parseFloat(res.price ?? '0')\n }\n\n const userAssets = allUserAssets.filter((asset) => {\n return asset.authorities[0].address.toLowerCase() === bond.contractAddress[ChainId.SOL]?.toLowerCase()\n })\n\n for (const asset of userAssets) {\n const nftMint = new PublicKey(asset.id)\n\n const [bondPda] = PublicKey.findProgramAddressSync(\n [new TextEncoder().encode('bond'), bondIssuancePda.toBuffer(), nftMint.toBuffer()],\n PROGRAM_ID,\n )\n\n if (!bondPda) {\n console.error(`Could not fetch bond PDA for mint ${nftMint.toBase58()}`)\n continue\n }\n\n try {\n const [bondTermPda] = PublicKey.findProgramAddressSync(\n [new TextEncoder().encode('bond_term'), bondIssuancePda.toBuffer()],\n PROGRAM_ID,\n )\n\n const [bondAccounts, pendingRewards] = await Promise.all([\n getBondAccountSolana([bondPda.toBase58()]),\n calculatePendingRewards(connection, bondPda, bondTermPda, bondIssuancePda, nftMint),\n ])\n\n if (!bondAccounts?.length) {\n console.error(`Could not fetch bond account for mint ${nftMint.toBase58()}`)\n continue\n }\n\n const bondAccount = bondAccounts[0]\n const id = asset.content.metadata.name\n const idTrimmed: string = id.split('-')[1]?.trim()\n userBills.push({\n address: bondPda.toBase58(),\n id: idTrimmed,\n asset: nftMint.toBase58(),\n // vesting: bondAccount.vesting.toString(),\n vestingTerm: bondAccount.vestingTerm.toString(),\n payout: (bondAccount.payout - bondAccount.payoutClaimed).toString(),\n totalPayout: bondAccount.payout.toString(),\n truePricePaid: bondAccount.truePricePaid.toString(),\n lastBlockTimestamp: bondAccount.lastClaimTimestamp.toString(),\n vestingStartTimestamp: bondAccount.vestingStartTimestamp.toString(),\n pendingRewards,\n billNftAddress: collection as string,\n payoutClaimed: bondAccount.payoutClaimed.toString(),\n bond: {\n ...bond,\n payoutTokenPrice: payoutTokenPrice?.toString(),\n principalTokenPrice: principalTokenPrice?.toString(),\n },\n })\n // ULTRA HOT FIX :( until we can figure out a multicall strategy\n await new Promise((r) => setTimeout(r, 10))\n } catch (e) {\n console.log(e)\n console.error(`Could not fetch bond PDA for mint ${nftMint.toBase58()}`)\n }\n }\n results.push({\n index: bond.index,\n userOwnedBills: userBills,\n })\n }\n console.timeEnd('fetchUserBondsSOL')\n return results\n } catch (err) {\n console.error('Error fetching assets by collection:', err)\n return []\n }\n}\n*/\n"],"names":[],"mappings":";;;AAMA;;;AAGG;AACI,eAAe,kBAAkB,CACtC,IAAY,EACZ,QAAuB,EACvB,WAAoD,EACpD,MAAc,EAAA;AAEd,IAAA,IAAI;QACF,OAAO,MAAM,2BAA2B,CAAC,IAAI,EAAE,QAAQ,EAAE,WAAW,CAAC;IACvE;IAAE,OAAO,KAAK,EAAE;AACd,QAAA,WAAW,CAAC;YACV,MAAM;YACN,KAAK;AACL,YAAA,OAAO,EAAE,IAAI;AACb,YAAA,SAAS,EAAE,EAAE,IAAI,EAAE,mCAAmC,EAAE;AACzD,SAAA,CAAC;;;AAGF,QAAA,OAAO,EAAE;IACX;AACF;AAEA;AACA;AACA;AACA;AACA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAoIE;;;;"}
@@ -0,0 +1,12 @@
1
+ import { BillsConfig, ChainId } from '@ape.swap/apeswap-lists';
2
+ import { UserOwnedBillsData } from '../../../types/yourbonds';
3
+ import { TokenPrices } from '../../../state/tokenPrices/useTokenPrices';
4
+ import { HeliusAsset } from '../types/helius';
5
+ /**
6
+ * Multicall version of `fetchUsersBondsSOLSequential`. Throws on failure (the caller `fetchUsersBondsSOL` reports it). Same inputs and output, but:
7
+ * - all Bond accounts are read with one batched `getMultipleAccounts` (`getBondAccountsSolanaAligned`) instead of one per NFT
8
+ * - the pending-rewards simulations are packed several instructions per transaction (the program's return value of
9
+ * each instruction is read from the "Program return:" log lines) instead of one simulation per NFT
10
+ * - token prices are resolved in parallel, only for bonds the user owns
11
+ */
12
+ export declare function fetchUsersBondsSOLMulticall(user: string, bondData: BillsConfig[], tokenPrices: Partial<Record<ChainId, TokenPrices[]>>, prefetchedAssets?: HeliusAsset[]): Promise<UserOwnedBillsData[]>;
@@ -0,0 +1,162 @@
1
+ import { ChainId } from '@ape.swap/apeswap-lists';
2
+ import { Connection, PublicKey, Transaction, TransactionInstruction } from '@solana/web3.js';
3
+ import { getU64Codec } from '@solana/kit';
4
+ import { PROGRAM_ID, CLAIMABLE_PAYOUT_DISCRIMINATOR } from '../constants/solanaConstants.js';
5
+ import { Buffer } from 'buffer';
6
+ import { getRPC } from '../../../config/constants/networks.js';
7
+ import { resolveBondTokenPrices } from '../../../state/tokenPrices/resolveTokenPrice.js';
8
+ import { fetchUserAssetsSOL } from '../utils/fetchUserAssetsSOL.js';
9
+ import { getBondAccountsSolanaAligned } from '../utils/getSolanaAccount.js';
10
+ import { calculatePendingRewards } from '../utils/calculatePendingRewards.js';
11
+
12
+ // A legacy transaction is capped at 1232 bytes; each claimable-payout instruction adds up to two unique accounts
13
+ // (bond PDA + NFT mint), so 6 per simulation leaves a comfortable margin.
14
+ const SIMULATIONS_PER_TX = 6;
15
+ const MAX_PARALLEL_SIMULATIONS = 3;
16
+ /**
17
+ * Multicall version of `fetchUsersBondsSOLSequential`. Throws on failure (the caller `fetchUsersBondsSOL` reports it). Same inputs and output, but:
18
+ * - all Bond accounts are read with one batched `getMultipleAccounts` (`getBondAccountsSolanaAligned`) instead of one per NFT
19
+ * - the pending-rewards simulations are packed several instructions per transaction (the program's return value of
20
+ * each instruction is read from the "Program return:" log lines) instead of one simulation per NFT
21
+ * - token prices are resolved in parallel, only for bonds the user owns
22
+ */
23
+ async function fetchUsersBondsSOLMulticall(user, bondData, tokenPrices, prefetchedAssets) {
24
+ if (!user)
25
+ return [];
26
+ try {
27
+ console.time('fetchUserBondsSOLMulticall');
28
+ const connection = new Connection(getRPC(ChainId.SOL), 'confirmed');
29
+ const collections = bondData.map((bond) => bond.billNnftAddress[ChainId.SOL]);
30
+ const allUserAssets = prefetchedAssets ?? (await fetchUserAssetsSOL(user, collections));
31
+ const solBonds = bondData.filter((bond) => bond.chainId === ChainId.SOL && !!bond.contractAddress[ChainId.SOL]);
32
+ // 1. Flatten every owned NFT of every bond, deriving its PDAs
33
+ const entries = [];
34
+ for (const bond of solBonds) {
35
+ const bondIssuancePda = new PublicKey(bond.contractAddress[ChainId.SOL]);
36
+ const [bondTermPda] = PublicKey.findProgramAddressSync([new TextEncoder().encode('bond_term'), bondIssuancePda.toBuffer()], PROGRAM_ID);
37
+ const userAssets = allUserAssets.filter((asset) => asset.authorities[0].address.toLowerCase() === bond.contractAddress[ChainId.SOL]?.toLowerCase());
38
+ for (const asset of userAssets) {
39
+ const nftMint = new PublicKey(asset.id);
40
+ const [bondPda] = PublicKey.findProgramAddressSync([new TextEncoder().encode('bond'), bondIssuancePda.toBuffer(), nftMint.toBuffer()], PROGRAM_ID);
41
+ entries.push({ bond, bondIssuancePda, bondTermPda, asset, nftMint, bondPda });
42
+ }
43
+ }
44
+ // 2. Bond accounts, pending rewards and token prices. Prices are only resolved for bonds the user owns NFTs of:
45
+ // the bulk price list first, then an individual request for tokens missing from it.
46
+ // The bond accounts are read directly (not through `solanaMulticall`, which swallows errors and would make a
47
+ // failed read look like "no bonds") so any failure reaches `fetchUsersBondsSOL` and gets reported.
48
+ const ownedBonds = solBonds.filter((bond) => entries.some((e) => e.bond === bond));
49
+ const [bondAccounts, pendingRewards, ownedBondPrices] = await Promise.all([
50
+ entries.length
51
+ ? getBondAccountsSolanaAligned(entries.map((e) => e.bondPda.toBase58()))
52
+ : Promise.resolve([]),
53
+ calculatePendingRewardsBatch(connection, entries),
54
+ Promise.all(ownedBonds.map((bond) => resolveBondTokenPrices(bond, tokenPrices?.[ChainId.SOL]))),
55
+ ]);
56
+ const pricesByBond = new Map(ownedBonds.map((bond, k) => [bond, ownedBondPrices[k]]));
57
+ // 3. Assemble, in the same shape and order as the sequential version
58
+ const billsByBond = new Map();
59
+ entries.forEach((entry, i) => {
60
+ const bondAccount = bondAccounts[i];
61
+ if (!bondAccount) {
62
+ console.error(`Could not fetch bond account for mint ${entry.nftMint.toBase58()}`);
63
+ return;
64
+ }
65
+ const { lpPrice: principalTokenPrice, earnTokenPrice: payoutTokenPrice } = pricesByBond.get(entry.bond);
66
+ const idTrimmed = entry.asset.content.metadata.name.split('-')[1]?.trim();
67
+ const bills = billsByBond.get(entry.bond.index) ?? [];
68
+ bills.push({
69
+ address: entry.bondPda.toBase58(),
70
+ id: idTrimmed,
71
+ asset: entry.nftMint.toBase58(),
72
+ vestingTerm: bondAccount.vestingTerm.toString(),
73
+ payout: (bondAccount.payout - bondAccount.payoutClaimed).toString(),
74
+ totalPayout: bondAccount.payout.toString(),
75
+ truePricePaid: bondAccount.truePricePaid.toString(),
76
+ lastBlockTimestamp: bondAccount.lastClaimTimestamp.toString(),
77
+ vestingStartTimestamp: bondAccount.vestingStartTimestamp.toString(),
78
+ pendingRewards: pendingRewards[i],
79
+ billNftAddress: entry.bond.billNnftAddress[ChainId.SOL],
80
+ payoutClaimed: bondAccount.payoutClaimed.toString(),
81
+ bond: {
82
+ ...entry.bond,
83
+ payoutTokenPrice: payoutTokenPrice?.toString(),
84
+ principalTokenPrice: principalTokenPrice?.toString(),
85
+ },
86
+ });
87
+ billsByBond.set(entry.bond.index, bills);
88
+ });
89
+ const results = solBonds.map((bond) => ({
90
+ index: bond.index,
91
+ userOwnedBills: billsByBond.get(bond.index) ?? [],
92
+ }));
93
+ console.timeEnd('fetchUserBondsSOLMulticall');
94
+ return results;
95
+ }
96
+ catch (err) {
97
+ console.error('Error fetching assets by collection (multicall):', err);
98
+ throw err;
99
+ }
100
+ }
101
+ function buildClaimablePayoutInstruction(entry, encodedTimestamp) {
102
+ return new TransactionInstruction({
103
+ keys: [
104
+ { pubkey: entry.bondPda, isSigner: false, isWritable: true },
105
+ { pubkey: entry.bondTermPda, isSigner: false, isWritable: false },
106
+ { pubkey: entry.bondIssuancePda, isSigner: false, isWritable: false },
107
+ { pubkey: entry.nftMint, isSigner: false, isWritable: false },
108
+ ],
109
+ programId: PROGRAM_ID,
110
+ data: Buffer.concat([CLAIMABLE_PAYOUT_DISCRIMINATOR, Buffer.from(encodedTimestamp)]),
111
+ });
112
+ }
113
+ /**
114
+ * Pending rewards for every entry (same order). Simulates SIMULATIONS_PER_TX instructions per transaction and reads
115
+ * each instruction's return value from the `Program return: <programId> <base64>` log lines, which the runtime emits
116
+ * in instruction order. If a chunk fails or doesn't yield exactly one return per instruction (error, truncated logs…)
117
+ * it falls back to one simulation per entry, i.e. the legacy behaviour.
118
+ */
119
+ async function calculatePendingRewardsBatch(connection, entries) {
120
+ const encodedTimestamp = getU64Codec().encode(BigInt(Math.floor(Date.now() / 1000)));
121
+ const programId = PROGRAM_ID.toBase58();
122
+ const out = new Array(entries.length).fill('0');
123
+ const chunks = [];
124
+ for (let i = 0; i < entries.length; i += SIMULATIONS_PER_TX) {
125
+ chunks.push(entries.slice(i, i + SIMULATIONS_PER_TX).map((_, j) => i + j));
126
+ }
127
+ const runChunk = async (indexes) => {
128
+ try {
129
+ const transaction = new Transaction();
130
+ transaction.feePayer = new PublicKey('11111111111111111111111111111112'); // Dummy public key for fee payer
131
+ indexes.forEach((i) => transaction.add(buildClaimablePayoutInstruction(entries[i], encodedTimestamp)));
132
+ const simulation = await connection.simulateTransaction(transaction);
133
+ const returns = (simulation.value.logs ?? [])
134
+ .filter((line) => line.startsWith(`Program return: ${programId} `))
135
+ .map((line) => line.slice(`Program return: ${programId} `.length).trim());
136
+ if (simulation.value.err || returns.length !== indexes.length) {
137
+ throw new Error(`Unusable batched simulation (err=${JSON.stringify(simulation.value.err)}, returns=${returns.length}/${indexes.length})`);
138
+ }
139
+ indexes.forEach((entryIndex, j) => {
140
+ out[entryIndex] = Buffer.from(returns[j], 'base64').readBigUInt64LE(0).toString();
141
+ });
142
+ }
143
+ catch (e) {
144
+ console.warn('Batched pending rewards simulation failed, falling back to individual simulations:', e);
145
+ for (const i of indexes) {
146
+ const { bondPda, bondTermPda, bondIssuancePda, nftMint } = entries[i];
147
+ out[i] = await calculatePendingRewards(connection, bondPda, bondTermPda, bondIssuancePda, nftMint);
148
+ }
149
+ }
150
+ };
151
+ // Bounded parallelism over the chunks
152
+ let next = 0;
153
+ await Promise.all(Array.from({ length: Math.min(MAX_PARALLEL_SIMULATIONS, chunks.length) }, async () => {
154
+ while (next < chunks.length) {
155
+ await runChunk(chunks[next++]);
156
+ }
157
+ }));
158
+ return out;
159
+ }
160
+
161
+ export { fetchUsersBondsSOLMulticall };
162
+ //# sourceMappingURL=fetchUsersBondsSOLMulticall.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fetchUsersBondsSOLMulticall.js","sources":["../../../../src/adapters/solana/state/fetchUsersBondsSOLMulticall.ts"],"sourcesContent":["import { BillsConfig, ChainId } from '@ape.swap/apeswap-lists'\nimport { UserBill, UserOwnedBillsData } from '../../../types/yourbonds'\nimport { Connection, PublicKey, Transaction, TransactionInstruction } from '@solana/web3.js'\nimport { TokenPrices } from '../../../state/tokenPrices/useTokenPrices'\nimport { HeliusAsset } from '../types/helius'\nimport { getU64Codec } from '@solana/kit'\nimport { CLAIMABLE_PAYOUT_DISCRIMINATOR, PROGRAM_ID } from '../constants/solanaConstants'\nimport { Buffer } from 'buffer'\nimport { getRPC } from '../../../config/constants/networks'\nimport { resolveBondTokenPrices } from '../../../state/tokenPrices/resolveTokenPrice'\nimport { fetchUserAssetsSOL } from '../utils/fetchUserAssetsSOL'\nimport { getBondAccountsSolanaAligned } from '../utils/getSolanaAccount'\nimport { BondAccount } from '../utils/codecs'\nimport { calculatePendingRewards } from '../utils/calculatePendingRewards'\n\n// A legacy transaction is capped at 1232 bytes; each claimable-payout instruction adds up to two unique accounts\n// (bond PDA + NFT mint), so 6 per simulation leaves a comfortable margin.\nconst SIMULATIONS_PER_TX = 6\nconst MAX_PARALLEL_SIMULATIONS = 3\n\ninterface UserAssetEntry {\n bond: BillsConfig\n bondIssuancePda: PublicKey\n bondTermPda: PublicKey\n asset: HeliusAsset\n nftMint: PublicKey\n bondPda: PublicKey\n}\n\n/**\n * Multicall version of `fetchUsersBondsSOLSequential`. Throws on failure (the caller `fetchUsersBondsSOL` reports it). Same inputs and output, but:\n * - all Bond accounts are read with one batched `getMultipleAccounts` (`getBondAccountsSolanaAligned`) instead of one per NFT\n * - the pending-rewards simulations are packed several instructions per transaction (the program's return value of\n * each instruction is read from the \"Program return:\" log lines) instead of one simulation per NFT\n * - token prices are resolved in parallel, only for bonds the user owns\n */\nexport async function fetchUsersBondsSOLMulticall(\n user: string,\n bondData: BillsConfig[],\n tokenPrices: Partial<Record<ChainId, TokenPrices[]>>,\n prefetchedAssets?: HeliusAsset[],\n): Promise<UserOwnedBillsData[]> {\n if (!user) return []\n try {\n console.time('fetchUserBondsSOLMulticall')\n const connection = new Connection(getRPC(ChainId.SOL), 'confirmed')\n\n const collections: string[] = bondData.map((bond) => bond.billNnftAddress[ChainId.SOL]!)\n\n const allUserAssets: HeliusAsset[] = prefetchedAssets ?? (await fetchUserAssetsSOL(user, collections))\n\n const solBonds = bondData.filter((bond) => bond.chainId === ChainId.SOL && !!bond.contractAddress[ChainId.SOL])\n\n // 1. Flatten every owned NFT of every bond, deriving its PDAs\n const entries: UserAssetEntry[] = []\n for (const bond of solBonds) {\n const bondIssuancePda = new PublicKey(bond.contractAddress[ChainId.SOL]!)\n const [bondTermPda] = PublicKey.findProgramAddressSync(\n [new TextEncoder().encode('bond_term'), bondIssuancePda.toBuffer()],\n PROGRAM_ID,\n )\n const userAssets = allUserAssets.filter(\n (asset) => asset.authorities[0].address.toLowerCase() === bond.contractAddress[ChainId.SOL]?.toLowerCase(),\n )\n for (const asset of userAssets) {\n const nftMint = new PublicKey(asset.id)\n const [bondPda] = PublicKey.findProgramAddressSync(\n [new TextEncoder().encode('bond'), bondIssuancePda.toBuffer(), nftMint.toBuffer()],\n PROGRAM_ID,\n )\n entries.push({ bond, bondIssuancePda, bondTermPda, asset, nftMint, bondPda })\n }\n }\n\n // 2. Bond accounts, pending rewards and token prices. Prices are only resolved for bonds the user owns NFTs of:\n // the bulk price list first, then an individual request for tokens missing from it.\n // The bond accounts are read directly (not through `solanaMulticall`, which swallows errors and would make a\n // failed read look like \"no bonds\") so any failure reaches `fetchUsersBondsSOL` and gets reported.\n const ownedBonds = solBonds.filter((bond) => entries.some((e) => e.bond === bond))\n const [bondAccounts, pendingRewards, ownedBondPrices] = await Promise.all([\n entries.length\n ? getBondAccountsSolanaAligned(entries.map((e) => e.bondPda.toBase58()))\n : Promise.resolve([] as (BondAccount | null)[]),\n calculatePendingRewardsBatch(connection, entries),\n Promise.all(ownedBonds.map((bond) => resolveBondTokenPrices(bond, tokenPrices?.[ChainId.SOL]))),\n ])\n const pricesByBond = new Map(ownedBonds.map((bond, k) => [bond, ownedBondPrices[k]]))\n\n // 3. Assemble, in the same shape and order as the sequential version\n const billsByBond = new Map<number, UserBill[]>()\n entries.forEach((entry, i) => {\n const bondAccount = bondAccounts[i]\n if (!bondAccount) {\n console.error(`Could not fetch bond account for mint ${entry.nftMint.toBase58()}`)\n return\n }\n const { lpPrice: principalTokenPrice, earnTokenPrice: payoutTokenPrice } = pricesByBond.get(entry.bond)!\n const idTrimmed: string = entry.asset.content.metadata.name.split('-')[1]?.trim()\n const bills = billsByBond.get(entry.bond.index) ?? []\n bills.push({\n address: entry.bondPda.toBase58(),\n id: idTrimmed,\n asset: entry.nftMint.toBase58(),\n vestingTerm: bondAccount.vestingTerm.toString(),\n payout: (bondAccount.payout - bondAccount.payoutClaimed).toString(),\n totalPayout: bondAccount.payout.toString(),\n truePricePaid: bondAccount.truePricePaid.toString(),\n lastBlockTimestamp: bondAccount.lastClaimTimestamp.toString(),\n vestingStartTimestamp: bondAccount.vestingStartTimestamp.toString(),\n pendingRewards: pendingRewards[i],\n billNftAddress: entry.bond.billNnftAddress[ChainId.SOL] as string,\n payoutClaimed: bondAccount.payoutClaimed.toString(),\n bond: {\n ...entry.bond,\n payoutTokenPrice: payoutTokenPrice?.toString(),\n principalTokenPrice: principalTokenPrice?.toString(),\n },\n })\n billsByBond.set(entry.bond.index, bills)\n })\n\n const results: UserOwnedBillsData[] = solBonds.map((bond) => ({\n index: bond.index,\n userOwnedBills: billsByBond.get(bond.index) ?? [],\n }))\n console.timeEnd('fetchUserBondsSOLMulticall')\n return results\n } catch (err) {\n console.error('Error fetching assets by collection (multicall):', err)\n throw err\n }\n}\n\nfunction buildClaimablePayoutInstruction(entry: UserAssetEntry, encodedTimestamp: ArrayLike<number>) {\n return new TransactionInstruction({\n keys: [\n { pubkey: entry.bondPda, isSigner: false, isWritable: true },\n { pubkey: entry.bondTermPda, isSigner: false, isWritable: false },\n { pubkey: entry.bondIssuancePda, isSigner: false, isWritable: false },\n { pubkey: entry.nftMint, isSigner: false, isWritable: false },\n ],\n programId: PROGRAM_ID,\n data: Buffer.concat([CLAIMABLE_PAYOUT_DISCRIMINATOR, Buffer.from(encodedTimestamp)]),\n })\n}\n\n/**\n * Pending rewards for every entry (same order). Simulates SIMULATIONS_PER_TX instructions per transaction and reads\n * each instruction's return value from the `Program return: <programId> <base64>` log lines, which the runtime emits\n * in instruction order. If a chunk fails or doesn't yield exactly one return per instruction (error, truncated logs…)\n * it falls back to one simulation per entry, i.e. the legacy behaviour.\n */\nasync function calculatePendingRewardsBatch(connection: Connection, entries: UserAssetEntry[]): Promise<string[]> {\n const encodedTimestamp = getU64Codec().encode(BigInt(Math.floor(Date.now() / 1000)))\n const programId = PROGRAM_ID.toBase58()\n const out: string[] = new Array(entries.length).fill('0')\n\n const chunks: number[][] = []\n for (let i = 0; i < entries.length; i += SIMULATIONS_PER_TX) {\n chunks.push(entries.slice(i, i + SIMULATIONS_PER_TX).map((_, j) => i + j))\n }\n\n const runChunk = async (indexes: number[]) => {\n try {\n const transaction = new Transaction()\n transaction.feePayer = new PublicKey('11111111111111111111111111111112') // Dummy public key for fee payer\n indexes.forEach((i) => transaction.add(buildClaimablePayoutInstruction(entries[i], encodedTimestamp)))\n const simulation = await connection.simulateTransaction(transaction)\n\n const returns = (simulation.value.logs ?? [])\n .filter((line) => line.startsWith(`Program return: ${programId} `))\n .map((line) => line.slice(`Program return: ${programId} `.length).trim())\n\n if (simulation.value.err || returns.length !== indexes.length) {\n throw new Error(\n `Unusable batched simulation (err=${JSON.stringify(simulation.value.err)}, returns=${returns.length}/${indexes.length})`,\n )\n }\n indexes.forEach((entryIndex, j) => {\n out[entryIndex] = Buffer.from(returns[j], 'base64').readBigUInt64LE(0).toString()\n })\n } catch (e) {\n console.warn('Batched pending rewards simulation failed, falling back to individual simulations:', e)\n for (const i of indexes) {\n const { bondPda, bondTermPda, bondIssuancePda, nftMint } = entries[i]\n out[i] = await calculatePendingRewards(connection, bondPda, bondTermPda, bondIssuancePda, nftMint)\n }\n }\n }\n\n // Bounded parallelism over the chunks\n let next = 0\n await Promise.all(\n Array.from({ length: Math.min(MAX_PARALLEL_SIMULATIONS, chunks.length) }, async () => {\n while (next < chunks.length) {\n await runChunk(chunks[next++])\n }\n }),\n )\n return out\n}\n"],"names":[],"mappings":"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"}