@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.
- package/dist/adapters/solana/index.js +1 -1
- package/dist/adapters/solana/index.js.map +1 -1
- package/dist/adapters/solana/state/fetchUsersBondsSOL.d.ts +5 -1
- package/dist/adapters/solana/state/fetchUsersBondsSOL.js +152 -154
- package/dist/adapters/solana/state/fetchUsersBondsSOL.js.map +1 -1
- package/dist/adapters/solana/state/fetchUsersBondsSOLMulticall.d.ts +12 -0
- package/dist/adapters/solana/state/fetchUsersBondsSOLMulticall.js +162 -0
- package/dist/adapters/solana/state/fetchUsersBondsSOLMulticall.js.map +1 -0
- package/dist/adapters/solana/utils/calculatePendingRewards.d.ts +2 -0
- package/dist/adapters/solana/utils/calculatePendingRewards.js +53 -0
- package/dist/adapters/solana/utils/calculatePendingRewards.js.map +1 -0
- package/dist/adapters/solana/utils/fetchUserAssetsSOL.d.ts +3 -0
- package/dist/adapters/solana/utils/fetchUserAssetsSOL.js +49 -0
- package/dist/adapters/solana/utils/fetchUserAssetsSOL.js.map +1 -0
- package/dist/adapters/solana/utils/getSolanaAccount.d.ts +5 -0
- package/dist/adapters/solana/utils/getSolanaAccount.js +26 -23
- package/dist/adapters/solana/utils/getSolanaAccount.js.map +1 -1
- package/dist/adapters/solana/utils/solanaMulticall.js +4 -2
- package/dist/adapters/solana/utils/solanaMulticall.js.map +1 -1
- package/dist/adapters/types.d.ts +1 -1
- package/dist/config/constants/networks.d.ts +1 -1
- package/dist/config/constants/networks.js +9 -7
- package/dist/config/constants/networks.js.map +1 -1
- package/dist/state/bonds/useUserBonds.d.ts +10 -1
- package/dist/state/bonds/useUserBonds.js +13 -9
- package/dist/state/bonds/useUserBonds.js.map +1 -1
- package/dist/styles.css +1 -1
- package/dist/views/Bonds/components/BondRows/ModalHandler.js +1 -1
- package/dist/views/Bonds/components/BondRows/ModalHandler.js.map +1 -1
- package/dist/views/Bonds/components/BondsMenu/BondsMenu.js +1 -1
- package/dist/views/Bonds/components/BondsMenu/BondsMenu.js.map +1 -1
- package/dist/views/FullBondsView/FullBondsView.js +2 -1
- package/dist/views/FullBondsView/FullBondsView.js.map +1 -1
- package/dist/views/FullBondsView/YourBondsStatus.d.ts +3 -0
- package/dist/views/FullBondsView/YourBondsStatus.js +54 -0
- package/dist/views/FullBondsView/YourBondsStatus.js.map +1 -0
- package/dist/views/YourBonds/components/YourBondsMenu/YourBondsMenu.js +2 -1
- package/dist/views/YourBonds/components/YourBondsMenu/YourBondsMenu.js.map +1 -1
- 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)
|
|
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
|
-
|
|
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 {
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
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
|
-
|
|
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 (
|
|
114
|
-
|
|
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
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
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
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
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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"}
|