@upshiftfinance/sdk 0.0.0-stage → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +408 -2
- package/lib/abis/AddressResolver.d.ts +28 -0
- package/lib/abis/AddressResolver.js +23 -0
- package/lib/abis/ChainlinkV3.d.ts +87 -0
- package/lib/abis/ChainlinkV3.js +117 -0
- package/lib/abis/ERC20.d.ts +168 -0
- package/lib/abis/ERC20.js +226 -0
- package/lib/abis/ERC20_Bytes32.d.ts +139 -0
- package/lib/abis/ERC20_Bytes32.js +196 -0
- package/lib/abis/ERC4626.d.ts +364 -0
- package/lib/abis/ERC4626.js +507 -0
- package/lib/abis/ERC721.d.ts +231 -0
- package/lib/abis/ERC721.js +321 -0
- package/lib/abis/FeeOracle.d.ts +120 -0
- package/lib/abis/FeeOracle.js +162 -0
- package/lib/abis/LendingPool.d.ts +1393 -0
- package/lib/abis/LendingPool.js +1807 -0
- package/lib/abis/LendingPoolV2.d.ts +1413 -0
- package/lib/abis/LendingPoolV2.js +1833 -0
- package/lib/abis/LendingPoolV3.d.ts +1677 -0
- package/lib/abis/LendingPoolV3.js +1160 -0
- package/lib/abis/Loan.d.ts +837 -0
- package/lib/abis/Loan.js +1080 -0
- package/lib/abis/MultiAssetNativeDepositWrapper.d.ts +137 -0
- package/lib/abis/MultiAssetNativeDepositWrapper.js +125 -0
- package/lib/abis/Multicall3.d.ts +30 -0
- package/lib/abis/Multicall3.js +97 -0
- package/lib/abis/OFT.d.ts +116 -0
- package/lib/abis/OFT.js +85 -0
- package/lib/abis/PoolAdapter.d.ts +36 -0
- package/lib/abis/PoolAdapter.js +51 -0
- package/lib/abis/RewardDistributor.d.ts +267 -0
- package/lib/abis/RewardDistributor.js +352 -0
- package/lib/abis/RwaRedeemSubaccount.d.ts +747 -0
- package/lib/abis/RwaRedeemSubaccount.js +548 -0
- package/lib/abis/SmartAccount.d.ts +17 -0
- package/lib/abis/SmartAccount.js +19 -0
- package/lib/abis/SwapRouter.d.ts +953 -0
- package/lib/abis/SwapRouter.js +1228 -0
- package/lib/abis/TextResolver.d.ts +16 -0
- package/lib/abis/TextResolver.js +16 -0
- package/lib/abis/TokenizedVaultV2.d.ts +1364 -0
- package/lib/abis/TokenizedVaultV2.js +1041 -0
- package/lib/abis/TokenizedVaultV2DepositWithPermit.d.ts +1456 -0
- package/lib/abis/TokenizedVaultV2DepositWithPermit.js +1878 -0
- package/lib/abis/TokenizedVaultV2Receipt.d.ts +1568 -0
- package/lib/abis/TokenizedVaultV2Receipt.js +1061 -0
- package/lib/abis/TokenizedVaultV2SenderAllocationWhitelist.d.ts +454 -0
- package/lib/abis/TokenizedVaultV2SenderAllocationWhitelist.js +327 -0
- package/lib/abis/TokenizedVaultV2WhitelistedAllocation.d.ts +1466 -0
- package/lib/abis/TokenizedVaultV2WhitelistedAllocation.js +1092 -0
- package/lib/abis/TokenizedVaultV2WhitelistedAssets.d.ts +274 -0
- package/lib/abis/TokenizedVaultV2WhitelistedAssets.js +167 -0
- package/lib/abis/UniversalResolverResolve.d.ts +69 -0
- package/lib/abis/UniversalResolverResolve.js +35 -0
- package/lib/abis/UniversalSignatureValidator.d.ts +17 -0
- package/lib/abis/UniversalSignatureValidator.js +30 -0
- package/lib/abis/WrapperAdapter.d.ts +71 -0
- package/lib/abis/WrapperAdapter.js +77 -0
- package/lib/abis/index.d.ts +34 -0
- package/lib/abis/index.js +51 -0
- package/lib/adapters/evm/getters.d.ts +19 -0
- package/lib/adapters/evm/getters.js +249 -0
- package/lib/adapters/evm/index.d.ts +539 -0
- package/lib/adapters/evm/index.js +656 -0
- package/lib/adapters/evm/utils.d.ts +8 -0
- package/lib/adapters/evm/utils.js +51 -0
- package/lib/adapters/solana/confirm.d.ts +88 -0
- package/lib/adapters/solana/confirm.js +291 -0
- package/lib/adapters/solana/constants.d.ts +42 -0
- package/lib/adapters/solana/constants.js +59 -0
- package/lib/adapters/solana/getters.d.ts +42 -0
- package/lib/adapters/solana/getters.js +275 -0
- package/lib/adapters/solana/idl/vault-idl.d.ts +700 -0
- package/lib/adapters/solana/idl/vault-idl.js +2101 -0
- package/lib/adapters/solana/idl/withdrawal-queue-idl.d.ts +795 -0
- package/lib/adapters/solana/idl/withdrawal-queue-idl.js +1864 -0
- package/lib/adapters/solana/index.d.ts +372 -0
- package/lib/adapters/solana/index.js +459 -0
- package/lib/adapters/solana/pricing.d.ts +184 -0
- package/lib/adapters/solana/pricing.js +239 -0
- package/lib/adapters/solana/queue-hints.d.ts +29 -0
- package/lib/adapters/solana/queue-hints.js +59 -0
- package/lib/adapters/solana/queue.actions.d.ts +97 -0
- package/lib/adapters/solana/queue.actions.js +468 -0
- package/lib/adapters/solana/token-reads.d.ts +108 -0
- package/lib/adapters/solana/token-reads.js +145 -0
- package/lib/adapters/solana/types.d.ts +325 -0
- package/lib/adapters/solana/types.js +3 -0
- package/lib/adapters/solana/utils.d.ts +229 -0
- package/lib/adapters/solana/utils.js +773 -0
- package/lib/adapters/solana/vault.actions.d.ts +140 -0
- package/lib/adapters/solana/vault.actions.js +657 -0
- package/lib/adapters/solana/withdrawal-queue.d.ts +125 -0
- package/lib/adapters/solana/withdrawal-queue.js +301 -0
- package/lib/adapters/stellar/actions.d.ts +41 -0
- package/lib/adapters/stellar/actions.js +126 -0
- package/lib/adapters/stellar/constants.d.ts +43 -0
- package/lib/adapters/stellar/constants.js +53 -0
- package/lib/adapters/stellar/getters.d.ts +68 -0
- package/lib/adapters/stellar/getters.js +290 -0
- package/lib/adapters/stellar/index.d.ts +126 -0
- package/lib/adapters/stellar/index.js +187 -0
- package/lib/adapters/stellar/invocation.d.ts +55 -0
- package/lib/adapters/stellar/invocation.js +92 -0
- package/lib/adapters/stellar/soroban.d.ts +131 -0
- package/lib/adapters/stellar/soroban.js +617 -0
- package/lib/adapters/stellar/submit.d.ts +42 -0
- package/lib/adapters/stellar/submit.js +195 -0
- package/lib/adapters/stellar/types.d.ts +57 -0
- package/lib/adapters/stellar/types.js +6 -0
- package/lib/adapters/stellar/utils.d.ts +24 -0
- package/lib/adapters/stellar/utils.js +34 -0
- package/lib/adapters/sui/constants.d.ts +1 -0
- package/lib/adapters/sui/constants.js +9 -0
- package/lib/adapters/sui/getters.d.ts +24 -0
- package/lib/adapters/sui/getters.js +134 -0
- package/lib/adapters/sui/index.d.ts +53 -0
- package/lib/adapters/sui/index.js +96 -0
- package/lib/adapters/sui/transformer.d.ts +10 -0
- package/lib/adapters/sui/transformer.js +115 -0
- package/lib/adapters/sui/types.d.ts +70 -0
- package/lib/adapters/sui/types.js +3 -0
- package/lib/adapters/sui/utils.d.ts +8 -0
- package/lib/adapters/sui/utils.js +17 -0
- package/lib/core/analytics/app-name.d.ts +27 -0
- package/lib/core/analytics/app-name.js +48 -0
- package/lib/core/analytics/chain-name.d.ts +9 -0
- package/lib/core/analytics/chain-name.js +34 -0
- package/lib/core/analytics/constants.d.ts +15 -0
- package/lib/core/analytics/constants.js +28 -0
- package/lib/core/analytics/env.d.ts +46 -0
- package/lib/core/analytics/env.js +147 -0
- package/lib/core/analytics/index.d.ts +39 -0
- package/lib/core/analytics/index.js +83 -0
- package/lib/core/analytics/instrumentation.d.ts +29 -0
- package/lib/core/analytics/instrumentation.js +292 -0
- package/lib/core/analytics/method-taxonomy.d.ts +19 -0
- package/lib/core/analytics/method-taxonomy.js +156 -0
- package/lib/core/analytics/metrics.d.ts +48 -0
- package/lib/core/analytics/metrics.js +142 -0
- package/lib/core/analytics/sanitize.d.ts +57 -0
- package/lib/core/analytics/sanitize.js +272 -0
- package/lib/core/analytics/sentry-runtime.d.ts +15 -0
- package/lib/core/analytics/sentry-runtime.js +131 -0
- package/lib/core/analytics/sentry.d.ts +100 -0
- package/lib/core/analytics/sentry.js +696 -0
- package/lib/core/analytics/types.d.ts +55 -0
- package/lib/core/analytics/types.js +3 -0
- package/lib/core/analytics/user-identity.d.ts +55 -0
- package/lib/core/analytics/user-identity.js +218 -0
- package/lib/core/analytics/version.d.ts +6 -0
- package/lib/core/analytics/version.js +10 -0
- package/lib/core/attribution.d.ts +159 -0
- package/lib/core/attribution.js +194 -0
- package/lib/core/auth/index.d.ts +1 -0
- package/lib/core/auth/index.js +18 -0
- package/lib/core/auth/verify.d.ts +2 -0
- package/lib/core/auth/verify.js +35 -0
- package/lib/core/base.class.d.ts +193 -0
- package/lib/core/base.class.js +163 -0
- package/lib/core/cache.d.ts +9 -0
- package/lib/core/cache.js +31 -0
- package/lib/core/constants/adapters.d.ts +103 -0
- package/lib/core/constants/adapters.js +180 -0
- package/lib/core/constants/core.d.ts +162 -0
- package/lib/core/constants/core.js +281 -0
- package/lib/core/constants/swap-router.d.ts +180 -0
- package/lib/core/constants/swap-router.js +203 -0
- package/lib/core/constants/vaults.d.ts +93 -0
- package/lib/core/constants/vaults.js +277 -0
- package/lib/core/constants/web3.d.ts +106 -0
- package/lib/core/constants/web3.js +251 -0
- package/lib/core/errors/index.d.ts +177 -0
- package/lib/core/errors/index.js +257 -0
- package/lib/core/fetcher.d.ts +237 -0
- package/lib/core/fetcher.js +1103 -0
- package/lib/core/helpers/adapters.d.ts +13 -0
- package/lib/core/helpers/adapters.js +39 -0
- package/lib/core/helpers/chain-address.d.ts +13 -0
- package/lib/core/helpers/chain-address.js +47 -0
- package/lib/core/helpers/chain-error.d.ts +207 -0
- package/lib/core/helpers/chain-error.js +697 -0
- package/lib/core/helpers/chain-support.d.ts +98 -0
- package/lib/core/helpers/chain-support.js +139 -0
- package/lib/core/helpers/concurrency.d.ts +51 -0
- package/lib/core/helpers/concurrency.js +79 -0
- package/lib/core/helpers/core.d.ts +92 -0
- package/lib/core/helpers/core.js +368 -0
- package/lib/core/helpers/explorer-link.d.ts +16 -0
- package/lib/core/helpers/explorer-link.js +26 -0
- package/lib/core/helpers/multicall.d.ts +68 -0
- package/lib/core/helpers/multicall.js +103 -0
- package/lib/core/helpers/revert-decode.d.ts +248 -0
- package/lib/core/helpers/revert-decode.js +515 -0
- package/lib/core/helpers/signer.d.ts +61 -0
- package/lib/core/helpers/signer.js +184 -0
- package/lib/core/helpers/swap-router.d.ts +269 -0
- package/lib/core/helpers/swap-router.js +542 -0
- package/lib/core/helpers/vault-version.d.ts +24 -0
- package/lib/core/helpers/vault-version.js +83 -0
- package/lib/core/helpers/vaults.d.ts +89 -0
- package/lib/core/helpers/vaults.js +241 -0
- package/lib/core/helpers/web3.d.ts +366 -0
- package/lib/core/helpers/web3.js +1034 -0
- package/lib/core/index.d.ts +23 -0
- package/lib/core/index.js +40 -0
- package/lib/core/logger/curator-alert.d.ts +217 -0
- package/lib/core/logger/curator-alert.js +276 -0
- package/lib/core/logger/index.d.ts +99 -0
- package/lib/core/logger/index.js +163 -0
- package/lib/core/logger/slack.d.ts +49 -0
- package/lib/core/logger/slack.js +111 -0
- package/lib/core/vault-metadata.d.ts +12 -0
- package/lib/core/vault-metadata.js +42 -0
- package/lib/core/version-check.d.ts +58 -0
- package/lib/core/version-check.js +182 -0
- package/lib/evm/index.d.ts +2 -0
- package/lib/evm/index.js +19 -0
- package/lib/evm/methods/crossChainVault.d.ts +132 -0
- package/lib/evm/methods/crossChainVault.js +857 -0
- package/lib/evm/methods/crossChainVaultRegistry.d.ts +92 -0
- package/lib/evm/methods/crossChainVaultRegistry.js +239 -0
- package/lib/evm/methods/index.d.ts +2 -0
- package/lib/evm/methods/index.js +19 -0
- package/lib/evm/types/crossChain.d.ts +363 -0
- package/lib/evm/types/crossChain.js +20 -0
- package/lib/evm/types/index.d.ts +1 -0
- package/lib/evm/types/index.js +18 -0
- package/lib/index.d.ts +30 -0
- package/lib/index.js +52 -0
- package/lib/main.d.ts +596 -0
- package/lib/main.js +681 -0
- package/lib/modules/api/fetcher.d.ts +108 -0
- package/lib/modules/api/fetcher.js +187 -0
- package/lib/modules/api/index.d.ts +1 -0
- package/lib/modules/api/index.js +6 -0
- package/lib/modules/api/main.d.ts +587 -0
- package/lib/modules/api/main.js +911 -0
- package/lib/modules/sub-accounts/fetcher.d.ts +53 -0
- package/lib/modules/sub-accounts/fetcher.js +96 -0
- package/lib/modules/sub-accounts/index.d.ts +2 -0
- package/lib/modules/sub-accounts/index.js +19 -0
- package/lib/modules/sub-accounts/main.d.ts +249 -0
- package/lib/modules/sub-accounts/main.js +211 -0
- package/lib/modules/sub-accounts/utils.d.ts +106 -0
- package/lib/modules/sub-accounts/utils.js +112 -0
- package/lib/modules/vaults/adapter.helpers.d.ts +84 -0
- package/lib/modules/vaults/adapter.helpers.js +202 -0
- package/lib/modules/vaults/fetcher.d.ts +147 -0
- package/lib/modules/vaults/fetcher.js +357 -0
- package/lib/modules/vaults/getters.d.ts +1060 -0
- package/lib/modules/vaults/getters.js +4079 -0
- package/lib/modules/vaults/index.d.ts +20 -0
- package/lib/modules/vaults/index.js +44 -0
- package/lib/modules/vaults/main.d.ts +607 -0
- package/lib/modules/vaults/main.js +1687 -0
- package/lib/modules/vaults/prefetch.d.ts +65 -0
- package/lib/modules/vaults/prefetch.js +120 -0
- package/lib/modules/vaults/read.actions.d.ts +406 -0
- package/lib/modules/vaults/read.actions.js +891 -0
- package/lib/modules/vaults/types.d.ts +149 -0
- package/lib/modules/vaults/types.js +3 -0
- package/lib/modules/vaults/utils/call-data-decoder.d.ts +61 -0
- package/lib/modules/vaults/utils/call-data-decoder.js +194 -0
- package/lib/modules/vaults/utils/date-utils.d.ts +50 -0
- package/lib/modules/vaults/utils/date-utils.js +84 -0
- package/lib/modules/vaults/utils.d.ts +153 -0
- package/lib/modules/vaults/utils.js +818 -0
- package/lib/modules/vaults/write.actions.d.ts +812 -0
- package/lib/modules/vaults/write.actions.js +2377 -0
- package/lib/polyfills.d.ts +1 -0
- package/lib/polyfills.js +12 -0
- package/lib/sdk.d.ts +30154 -0
- package/lib/services/coingecko/fetcher.d.ts +15 -0
- package/lib/services/coingecko/fetcher.js +67 -0
- package/lib/services/coingecko/index.d.ts +2 -0
- package/lib/services/coingecko/index.js +19 -0
- package/lib/services/coingecko/utils.d.ts +1 -0
- package/lib/services/coingecko/utils.js +24 -0
- package/lib/services/debank/fetcher.d.ts +126 -0
- package/lib/services/debank/fetcher.js +47 -0
- package/lib/services/debank/index.d.ts +2 -0
- package/lib/services/debank/index.js +19 -0
- package/lib/services/debank/utils.d.ts +50 -0
- package/lib/services/debank/utils.js +325 -0
- package/lib/services/layerzero/deposits.d.ts +55 -0
- package/lib/services/layerzero/deposits.js +174 -0
- package/lib/services/layerzero/redeems.d.ts +20 -0
- package/lib/services/layerzero/redeems.js +92 -0
- package/lib/services/layerzero/utils.d.ts +9 -0
- package/lib/services/layerzero/utils.js +22 -0
- package/lib/services/octavfi/fetcher.d.ts +33 -0
- package/lib/services/octavfi/fetcher.js +39 -0
- package/lib/services/octavfi/index.d.ts +3 -0
- package/lib/services/octavfi/index.js +20 -0
- package/lib/services/octavfi/types.d.ts +34 -0
- package/lib/services/octavfi/types.js +3 -0
- package/lib/services/octavfi/utils.d.ts +16 -0
- package/lib/services/octavfi/utils.js +244 -0
- package/lib/services/subgraph/fetcher.d.ts +2 -0
- package/lib/services/subgraph/fetcher.js +66 -0
- package/lib/services/subgraph/index.d.ts +2 -0
- package/lib/services/subgraph/index.js +19 -0
- package/lib/services/subgraph/schema.d.ts +48 -0
- package/lib/services/subgraph/schema.js +87 -0
- package/lib/services/subgraph/vaults.d.ts +83 -0
- package/lib/services/subgraph/vaults.js +1282 -0
- package/lib/services/swap-quotes/index.d.ts +105 -0
- package/lib/services/swap-quotes/index.js +77 -0
- package/lib/services/swap-quotes/paraswap.d.ts +51 -0
- package/lib/services/swap-quotes/paraswap.js +138 -0
- package/lib/services/untangled/fetcher.d.ts +40 -0
- package/lib/services/untangled/fetcher.js +110 -0
- package/lib/services/untangled/index.d.ts +3 -0
- package/lib/services/untangled/index.js +20 -0
- package/lib/services/untangled/types.d.ts +83 -0
- package/lib/services/untangled/types.js +10 -0
- package/lib/services/untangled/utils.d.ts +91 -0
- package/lib/services/untangled/utils.js +339 -0
- package/lib/types/api.d.ts +498 -0
- package/lib/types/api.js +3 -0
- package/lib/types/index.d.ts +12 -0
- package/lib/types/index.js +28 -0
- package/lib/types/points.d.ts +28 -0
- package/lib/types/points.js +3 -0
- package/lib/types/pools.d.ts +144 -0
- package/lib/types/pools.js +3 -0
- package/lib/types/staking.d.ts +28 -0
- package/lib/types/staking.js +3 -0
- package/lib/types/sub-accounts.d.ts +150 -0
- package/lib/types/sub-accounts.js +3 -0
- package/lib/types/subgraph.d.ts +84 -0
- package/lib/types/subgraph.js +3 -0
- package/lib/types/typed-contract.d.ts +102 -0
- package/lib/types/typed-contract.js +3 -0
- package/lib/types/user.d.ts +1 -0
- package/lib/types/user.js +3 -0
- package/lib/types/vaults.d.ts +886 -0
- package/lib/types/vaults.js +30 -0
- package/lib/types/web3.d.ts +81 -0
- package/lib/types/web3.js +14 -0
- package/lib/types/webserver.d.ts +730 -0
- package/lib/types/webserver.js +3 -0
- package/package.json +78 -3
|
@@ -0,0 +1,773 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.SolanaUtils = exports.isSolanaAddress = void 0;
|
|
4
|
+
exports.assertSolanaProgramType = assertSolanaProgramType;
|
|
5
|
+
exports.undeployedProgramMessage = undeployedProgramMessage;
|
|
6
|
+
exports.selectRichestTokenAccount = selectRichestTokenAccount;
|
|
7
|
+
exports.uiAmountToRawBn = uiAmountToRawBn;
|
|
8
|
+
const anchor_1 = require("@coral-xyz/anchor");
|
|
9
|
+
const web3_js_1 = require("@solana/web3.js");
|
|
10
|
+
const ethers_1 = require("ethers");
|
|
11
|
+
const constants_1 = require("./constants");
|
|
12
|
+
const confirm_1 = require("./confirm");
|
|
13
|
+
const core_1 = require("../../core");
|
|
14
|
+
const cache_1 = require("../../core/cache");
|
|
15
|
+
// ============================================================================
|
|
16
|
+
// SOLANA UTILITIES
|
|
17
|
+
// ============================================================================
|
|
18
|
+
function getExplorerLink({ signature, type = 'tx', network = constants_1.fallbackNetwork, }) {
|
|
19
|
+
const baseUrl = 'https://solscan.io';
|
|
20
|
+
return `${baseUrl}/${type}/${signature}?cluster=${network}`;
|
|
21
|
+
}
|
|
22
|
+
// Re-exported from the core leaf so consumers in the core layer can import it
|
|
23
|
+
// without taking a dependency on the Solana adapter.
|
|
24
|
+
const chain_address_1 = require("../../core/helpers/chain-address");
|
|
25
|
+
Object.defineProperty(exports, "isSolanaAddress", { enumerable: true, get: function () { return chain_address_1.isSolanaAddress; } });
|
|
26
|
+
function isSolana(signature) {
|
|
27
|
+
if (!signature || typeof signature !== 'string')
|
|
28
|
+
return false;
|
|
29
|
+
// Check if it's an Ethereum address first
|
|
30
|
+
if ((0, ethers_1.isAddress)(signature))
|
|
31
|
+
return false;
|
|
32
|
+
// Check if it's a valid Solana address (including PDAs) — addresses are not signatures
|
|
33
|
+
if ((0, chain_address_1.isSolanaAddress)(signature))
|
|
34
|
+
return false;
|
|
35
|
+
// Check if it's a valid base58 string with correct length for Solana signatures
|
|
36
|
+
// Solana signatures are base58 encoded and typically 88 characters long
|
|
37
|
+
const base58Chars = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz';
|
|
38
|
+
const isValidBase58 = signature
|
|
39
|
+
.split('')
|
|
40
|
+
.every((char) => base58Chars.includes(char));
|
|
41
|
+
if (!isValidBase58)
|
|
42
|
+
return false;
|
|
43
|
+
// Check length - Solana signatures are typically 88 characters
|
|
44
|
+
return (signature.length >= 80 && signature.length <= 100);
|
|
45
|
+
}
|
|
46
|
+
/** On-chain name (IDL `metadata.name`) of each program type. */
|
|
47
|
+
const programNames = {
|
|
48
|
+
vault: 'august_vault',
|
|
49
|
+
withdrawalQueue: 'august_withdrawal_queue',
|
|
50
|
+
};
|
|
51
|
+
/**
|
|
52
|
+
* Throws unless `type` is a program type `programNames` maps. Checked at
|
|
53
|
+
* runtime for untyped callers: a typo would otherwise read as an undeployed
|
|
54
|
+
* program, and a prototype key like `toString` as a program id.
|
|
55
|
+
* @internal
|
|
56
|
+
*/
|
|
57
|
+
function assertSolanaProgramType(type) {
|
|
58
|
+
if (typeof type !== 'string' || !Object.hasOwn(programNames, type)) {
|
|
59
|
+
throw new core_1.UpshiftValidationError('INVALID_INPUT', `Unknown Solana program type '${String(type)}'. Expected one of: ${Object.keys(programNames).join(', ')}.`);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Error text for `program` having no canonical deployment on `network`, naming
|
|
64
|
+
* the networks it is deployed on.
|
|
65
|
+
* @internal
|
|
66
|
+
*/
|
|
67
|
+
function undeployedProgramMessage(program, network) {
|
|
68
|
+
const deployed = Object.entries(constants_1.programIds)
|
|
69
|
+
.filter(([, ids]) => ids[program])
|
|
70
|
+
.map(([name]) => name);
|
|
71
|
+
return (`No canonical ${programNames[program]} deployment for Solana network '${network}'. ` +
|
|
72
|
+
`Deployed networks: ${deployed.join(', ')}.`);
|
|
73
|
+
}
|
|
74
|
+
function getProgram({ provider, idl, programId, network, }) {
|
|
75
|
+
const baseIdl = idl ?? constants_1.vaultIdl;
|
|
76
|
+
// The withdrawal queue IDL resolves through `programIds.withdrawalQueue`;
|
|
77
|
+
// anything else is treated as the vault. Recognised by name, so the queue IDL
|
|
78
|
+
// never has to be imported here (and pulled into every bundle).
|
|
79
|
+
const program = baseIdl?.metadata?.name === programNames.withdrawalQueue
|
|
80
|
+
? 'withdrawalQueue'
|
|
81
|
+
: 'vault';
|
|
82
|
+
// Precedence: explicit `programId` → a custom IDL's `address` → the network
|
|
83
|
+
// default → the bundled IDL's embedded `address`.
|
|
84
|
+
//
|
|
85
|
+
// The custom-address check compares by VALUE, not reference. Keying off
|
|
86
|
+
// `idl === vaultIdl` broke for a structurally-identical-but-not-reference-equal
|
|
87
|
+
// copy (second bundle instance, or JSON round-tripped), silently retargeting
|
|
88
|
+
// devnet callers at mainnet. Every copy carries the same build stamp, so it
|
|
89
|
+
// still defers to the network default — but a genuinely custom IDL is honoured.
|
|
90
|
+
// A build stamp is the program's canonical id, which some network maps.
|
|
91
|
+
const isBuildStamp = Object.values(constants_1.programIds).some((ids) => ids[program] === baseIdl?.address);
|
|
92
|
+
const suppliedAddress = typeof baseIdl?.address === 'string' && !isBuildStamp
|
|
93
|
+
? baseIdl.address
|
|
94
|
+
: undefined;
|
|
95
|
+
const explicitId = programId ?? suppliedAddress;
|
|
96
|
+
if (explicitId) {
|
|
97
|
+
return new anchor_1.Program({ ...baseIdl, address: explicitId }, provider);
|
|
98
|
+
}
|
|
99
|
+
if (network) {
|
|
100
|
+
const networkDefault = (0, constants_1.getDeployedProgramIds)(network)?.[program];
|
|
101
|
+
if (!networkDefault) {
|
|
102
|
+
throw new core_1.UpshiftValidationError('INVALID_INPUT', `${undeployedProgramMessage(program, network)} ` +
|
|
103
|
+
`Pass an explicit \`programId\` (or an IDL carrying the target \`address\`) to target a program on this network.`);
|
|
104
|
+
}
|
|
105
|
+
return new anchor_1.Program({ ...baseIdl, address: networkDefault }, provider);
|
|
106
|
+
}
|
|
107
|
+
return new anchor_1.Program(baseIdl, provider);
|
|
108
|
+
}
|
|
109
|
+
// ============================================================================
|
|
110
|
+
// SOLANA PROVIDERS
|
|
111
|
+
// ============================================================================
|
|
112
|
+
async function testRpcEndpoint({ connection }) {
|
|
113
|
+
try {
|
|
114
|
+
const blockHeight = await connection.getBlockHeight();
|
|
115
|
+
return { success: true, blockHeight };
|
|
116
|
+
}
|
|
117
|
+
catch (error) {
|
|
118
|
+
return { success: false, error };
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
// Function to get the best available RPC endpoint
|
|
122
|
+
async function getBestRpcEndpoint({ endpoint, network = constants_1.fallbackNetwork, connection, }) {
|
|
123
|
+
const primaryEndpoint = endpoint;
|
|
124
|
+
const fallbackEndpoint = constants_1.fallbackRpcEndpoints[network];
|
|
125
|
+
// Test primary endpoint first
|
|
126
|
+
const primaryTest = await testRpcEndpoint({ connection });
|
|
127
|
+
if (primaryTest.success) {
|
|
128
|
+
return primaryEndpoint;
|
|
129
|
+
}
|
|
130
|
+
// Fall back to public endpoint
|
|
131
|
+
core_1.Logger.log.warn('getBestRpcEndpoint', '⚠️ Primary RPC endpoint failed, using fallback:', fallbackEndpoint);
|
|
132
|
+
return fallbackEndpoint;
|
|
133
|
+
}
|
|
134
|
+
function getProvider({ connection, publicKey, signTransaction, }) {
|
|
135
|
+
if (!publicKey || !signTransaction) {
|
|
136
|
+
throw new core_1.UpshiftValidationError('INVALID_INPUT', 'Wallet not connected: publicKey and signTransaction are required for write operations');
|
|
137
|
+
}
|
|
138
|
+
// Anchor's own confirmation misreports landed txs as timed out; see confirm.ts.
|
|
139
|
+
return new confirm_1.BlockhashConfirmingProvider(connection, {
|
|
140
|
+
publicKey,
|
|
141
|
+
signTransaction,
|
|
142
|
+
signAllTransactions: async (txs) => {
|
|
143
|
+
// Use Promise.all instead of await in loop
|
|
144
|
+
const signedTransactions = await Promise.all(txs.map(async (tx) => signTransaction(tx)));
|
|
145
|
+
return signedTransactions;
|
|
146
|
+
},
|
|
147
|
+
},
|
|
148
|
+
// Inherited from the connection, not hard-coded: a caller who configured
|
|
149
|
+
// the adapter for 'finalized' must not have writes issued through
|
|
150
|
+
// `getProgram(...).methods…rpc()` confirm at something weaker.
|
|
151
|
+
{ commitment: connection.commitment ?? 'finalized' });
|
|
152
|
+
}
|
|
153
|
+
// Read-only provider for data fetching without wallet connection
|
|
154
|
+
function getReadOnlyProvider({ connection }) {
|
|
155
|
+
// Create a dummy wallet object for read-only operations
|
|
156
|
+
const dummyWallet = {
|
|
157
|
+
publicKey: web3_js_1.PublicKey.default, // Use default public key
|
|
158
|
+
signTransaction: async (_tx) => {
|
|
159
|
+
throw new core_1.UpshiftValidationError('INVALID_INPUT', 'Cannot sign transactions with read-only provider');
|
|
160
|
+
},
|
|
161
|
+
signAllTransactions: async (_txs) => {
|
|
162
|
+
throw new core_1.UpshiftValidationError('INVALID_INPUT', 'Cannot sign transactions with read-only provider');
|
|
163
|
+
},
|
|
164
|
+
};
|
|
165
|
+
// See `getProvider` — the connection is the single source of truth for
|
|
166
|
+
// commitment, so reads through this provider match reads made directly.
|
|
167
|
+
return new anchor_1.AnchorProvider(connection, dummyWallet, {
|
|
168
|
+
commitment: connection.commitment ?? 'finalized',
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
// ============================================================================
|
|
172
|
+
// SOLANA DERIVATIONS
|
|
173
|
+
// ============================================================================
|
|
174
|
+
function deriveVaultStatePda(vaultProgramId) {
|
|
175
|
+
if (!vaultProgramId)
|
|
176
|
+
throw new core_1.UpshiftValidationError('INVALID_INPUT', 'Vault program ID missing');
|
|
177
|
+
const _vaultProgramId = new web3_js_1.PublicKey(vaultProgramId);
|
|
178
|
+
const [pda] = web3_js_1.PublicKey.findProgramAddressSync([new TextEncoder().encode('VAULT_STATE')], _vaultProgramId);
|
|
179
|
+
return pda;
|
|
180
|
+
}
|
|
181
|
+
function deriveShareMintPda(vaultProgramId) {
|
|
182
|
+
if (!vaultProgramId)
|
|
183
|
+
throw new core_1.UpshiftValidationError('INVALID_INPUT', 'Vault program ID missing');
|
|
184
|
+
const _vaultProgramId = new web3_js_1.PublicKey(vaultProgramId);
|
|
185
|
+
const [pda] = web3_js_1.PublicKey.findProgramAddressSync([new TextEncoder().encode('mint')], _vaultProgramId);
|
|
186
|
+
return pda;
|
|
187
|
+
}
|
|
188
|
+
function deriveVaultTokenAtaPda(vaultProgramId, depositMint, vaultVersion) {
|
|
189
|
+
if (!(vaultProgramId && depositMint))
|
|
190
|
+
throw new core_1.UpshiftValidationError('INVALID_INPUT', 'Vault program ID or deposit mint missing');
|
|
191
|
+
const _vaultProgramId = new web3_js_1.PublicKey(vaultProgramId);
|
|
192
|
+
const seeds = [
|
|
193
|
+
Buffer.from('token_vault'),
|
|
194
|
+
depositMint.toBuffer(),
|
|
195
|
+
];
|
|
196
|
+
if (vaultVersion !== undefined) {
|
|
197
|
+
if (!Number.isInteger(vaultVersion) ||
|
|
198
|
+
vaultVersion < 0 ||
|
|
199
|
+
vaultVersion > 255) {
|
|
200
|
+
throw new core_1.UpshiftValidationError('INVALID_INPUT', `Invalid vault version ${vaultVersion}: must be an integer between 0 and 255`);
|
|
201
|
+
}
|
|
202
|
+
seeds.push(Buffer.from([vaultVersion]));
|
|
203
|
+
}
|
|
204
|
+
const [pda] = web3_js_1.PublicKey.findProgramAddressSync(seeds, _vaultProgramId);
|
|
205
|
+
return pda;
|
|
206
|
+
}
|
|
207
|
+
// ============================================================================
|
|
208
|
+
// SOLANA VAULTS
|
|
209
|
+
// ============================================================================
|
|
210
|
+
/**
|
|
211
|
+
* How long a decoded vault state stays in the process cache, in milliseconds.
|
|
212
|
+
*
|
|
213
|
+
* `vaultState-*` entries carry live balances (`localAum`, `deployedAum`, fee
|
|
214
|
+
* settings), so they must expire quickly: the shared `CACHE` defaults to 24 h,
|
|
215
|
+
* which froze TVL and share price for the life of the process. 60 s bounds
|
|
216
|
+
* the staleness to one block of polling while still collapsing the burst of
|
|
217
|
+
* reads a single page render makes. Mint addresses (`vaultMints-*`) are
|
|
218
|
+
* immutable and keep the default TTL.
|
|
219
|
+
*/
|
|
220
|
+
const VAULT_STATE_CACHE_TTL_MS = 60_000;
|
|
221
|
+
/**
|
|
222
|
+
* Cache a decoded vault state under `cacheKey` with the short
|
|
223
|
+
* {@link VAULT_STATE_CACHE_TTL_MS} TTL. Only called after a successful decode:
|
|
224
|
+
* a failed read is never cached, so the next caller retries the RPC instead
|
|
225
|
+
* of inheriting a `null` state for the TTL.
|
|
226
|
+
*/
|
|
227
|
+
function cacheVaultState(cacheKey, value) {
|
|
228
|
+
cache_1.CACHE.set(cacheKey, value, { ttl: VAULT_STATE_CACHE_TTL_MS });
|
|
229
|
+
}
|
|
230
|
+
async function getVaultState({ provider, connection, vaultProgramId, vaultAddress, idl, }) {
|
|
231
|
+
const returnObj = {
|
|
232
|
+
vaultState: null,
|
|
233
|
+
depositMintDecimals: null,
|
|
234
|
+
};
|
|
235
|
+
try {
|
|
236
|
+
const cacheKey = `vaultState-${vaultAddress || vaultProgramId}`;
|
|
237
|
+
const cachedData = await (0, core_1.getSafeCache)(cacheKey);
|
|
238
|
+
if (cachedData)
|
|
239
|
+
return cachedData;
|
|
240
|
+
if (!vaultProgramId) {
|
|
241
|
+
throw new core_1.UpshiftValidationError('INVALID_INPUT', 'Please enter a vault program ID');
|
|
242
|
+
}
|
|
243
|
+
const vaultStatePda = vaultAddress
|
|
244
|
+
? new web3_js_1.PublicKey(vaultAddress)
|
|
245
|
+
: deriveVaultStatePda(vaultProgramId);
|
|
246
|
+
const _vaultProgramId = new web3_js_1.PublicKey(vaultProgramId);
|
|
247
|
+
const program = getProgram({
|
|
248
|
+
provider,
|
|
249
|
+
idl,
|
|
250
|
+
programId: _vaultProgramId.toBase58(),
|
|
251
|
+
});
|
|
252
|
+
const state = await program.account.vaultState.fetch(vaultStatePda);
|
|
253
|
+
returnObj.vaultState = state;
|
|
254
|
+
if (state.depositMint) {
|
|
255
|
+
try {
|
|
256
|
+
const mintInfo = await connection.getParsedAccountInfo(state.depositMint);
|
|
257
|
+
if (mintInfo.value) {
|
|
258
|
+
const parsedData = mintInfo.value.data;
|
|
259
|
+
returnObj.depositMintDecimals = parsedData.parsed.info.decimals;
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
catch (mintError) {
|
|
263
|
+
core_1.Logger.log.error('getVaultState', mintError, {
|
|
264
|
+
message: 'Error fetching deposit mint decimals',
|
|
265
|
+
});
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
cacheVaultState(cacheKey, returnObj);
|
|
269
|
+
return returnObj;
|
|
270
|
+
}
|
|
271
|
+
catch (e) {
|
|
272
|
+
const error = e;
|
|
273
|
+
core_1.Logger.log.error('getVaultState', error, {
|
|
274
|
+
message: 'Error reading vault state',
|
|
275
|
+
});
|
|
276
|
+
return returnObj;
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
/**
|
|
280
|
+
* Read-only version of `getVaultState` that works without a wallet connection.
|
|
281
|
+
*
|
|
282
|
+
* Makes one `getAccountInfo` for the vault state PDA and decodes the bytes
|
|
283
|
+
* with the program's account coder (no second fetch through
|
|
284
|
+
* `program.account.vaultState.fetch`), plus one `getParsedAccountInfo` for the
|
|
285
|
+
* deposit mint's decimals. Worst case: 2 RPC calls per cache miss; a hit costs
|
|
286
|
+
* none. The decoded state is cached for {@link VAULT_STATE_CACHE_TTL_MS}.
|
|
287
|
+
*
|
|
288
|
+
* Resolves `{ vaultState: null, depositMintDecimals: null }` — and caches
|
|
289
|
+
* nothing — when the account is missing, the RPC fails, or the bytes do not
|
|
290
|
+
* decode. Callers (`getSolanaVault`) treat a `null` state as "on-chain data
|
|
291
|
+
* unavailable" and fall back to backend data.
|
|
292
|
+
*
|
|
293
|
+
* Served from the cache unless `fresh` is set. A fresh read skips the cached
|
|
294
|
+
* value and refreshes it, for callers comparing the vault's AUM with live data
|
|
295
|
+
* such as an operator's current portfolio.
|
|
296
|
+
*/
|
|
297
|
+
async function getVaultStateReadOnly({ vaultProgramId, vaultAddress, connection, idl, fresh = false, }) {
|
|
298
|
+
const returnObj = {
|
|
299
|
+
vaultState: null,
|
|
300
|
+
depositMintDecimals: null,
|
|
301
|
+
};
|
|
302
|
+
try {
|
|
303
|
+
const cacheKey = `vaultState-${vaultAddress || vaultProgramId}`;
|
|
304
|
+
const cachedData = fresh ? undefined : await (0, core_1.getSafeCache)(cacheKey);
|
|
305
|
+
if (cachedData)
|
|
306
|
+
return cachedData;
|
|
307
|
+
if (!vaultProgramId) {
|
|
308
|
+
throw new core_1.UpshiftValidationError('INVALID_INPUT', 'Please enter a vault program ID');
|
|
309
|
+
}
|
|
310
|
+
const _vaultProgramId = new web3_js_1.PublicKey(vaultProgramId);
|
|
311
|
+
const vaultStatePda = vaultAddress
|
|
312
|
+
? new web3_js_1.PublicKey(vaultAddress)
|
|
313
|
+
: deriveVaultStatePda(_vaultProgramId);
|
|
314
|
+
// One account read; the bytes are decoded locally below rather than
|
|
315
|
+
// re-fetched through `program.account.vaultState.fetch`.
|
|
316
|
+
const accountInfo = await connection.getAccountInfo(vaultStatePda);
|
|
317
|
+
if (!accountInfo) {
|
|
318
|
+
core_1.Logger.log.warn('getVaultStateReadOnly', 'Vault state account not found');
|
|
319
|
+
return returnObj;
|
|
320
|
+
}
|
|
321
|
+
// Decode with the program's account coder (same pattern as
|
|
322
|
+
// `readPricingSnapshot`); the read-only provider never signs.
|
|
323
|
+
try {
|
|
324
|
+
const readOnlyProvider = getReadOnlyProvider({ connection });
|
|
325
|
+
const program = getProgram({
|
|
326
|
+
provider: readOnlyProvider,
|
|
327
|
+
idl,
|
|
328
|
+
programId: _vaultProgramId.toBase58(),
|
|
329
|
+
});
|
|
330
|
+
const state = program.coder.accounts.decode('vaultState', accountInfo.data);
|
|
331
|
+
returnObj.vaultState = state;
|
|
332
|
+
if (state.depositMint) {
|
|
333
|
+
try {
|
|
334
|
+
const mintInfo = await connection.getParsedAccountInfo(state.depositMint);
|
|
335
|
+
if (mintInfo.value) {
|
|
336
|
+
const parsedData = mintInfo.value.data;
|
|
337
|
+
returnObj.depositMintDecimals = parsedData.parsed.info.decimals;
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
catch (mintError) {
|
|
341
|
+
core_1.Logger.log.error('getVaultStateReadOnly', mintError, {
|
|
342
|
+
message: 'Error fetching deposit mint decimals',
|
|
343
|
+
});
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
catch (programError) {
|
|
348
|
+
core_1.Logger.log.error('getVaultStateReadOnly', programError, {
|
|
349
|
+
message: 'Error decoding vault state with read-only provider',
|
|
350
|
+
});
|
|
351
|
+
// Decode failed: the state is unavailable and must not be cached, or
|
|
352
|
+
// every caller for the next TTL would inherit the failure.
|
|
353
|
+
returnObj.vaultState = null;
|
|
354
|
+
return returnObj;
|
|
355
|
+
}
|
|
356
|
+
cacheVaultState(cacheKey, returnObj);
|
|
357
|
+
return returnObj;
|
|
358
|
+
}
|
|
359
|
+
catch (e) {
|
|
360
|
+
const error = e;
|
|
361
|
+
core_1.Logger.log.error('getVaultStateReadOnly', error, {
|
|
362
|
+
message: 'Error reading vault state',
|
|
363
|
+
});
|
|
364
|
+
return returnObj;
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
// Fetch deposit and share mint addresses from on-chain vault state (read-only, no wallet required)
|
|
368
|
+
async function getVaultMints({ vaultProgramId, vaultAddress, connection, idl, }) {
|
|
369
|
+
try {
|
|
370
|
+
const cacheKey = `vaultMints-${vaultAddress || vaultProgramId}`;
|
|
371
|
+
const cachedData = await (0, core_1.getSafeCache)(cacheKey);
|
|
372
|
+
if (cachedData)
|
|
373
|
+
return cachedData;
|
|
374
|
+
if (!vaultProgramId) {
|
|
375
|
+
throw new core_1.UpshiftValidationError('INVALID_INPUT', 'Please enter a vault program ID');
|
|
376
|
+
}
|
|
377
|
+
const _vaultProgramId = new web3_js_1.PublicKey(vaultProgramId);
|
|
378
|
+
const vaultStatePda = vaultAddress
|
|
379
|
+
? new web3_js_1.PublicKey(vaultAddress)
|
|
380
|
+
: deriveVaultStatePda(_vaultProgramId);
|
|
381
|
+
const readOnlyProvider = getReadOnlyProvider({ connection });
|
|
382
|
+
const program = getProgram({
|
|
383
|
+
provider: readOnlyProvider,
|
|
384
|
+
idl,
|
|
385
|
+
programId: _vaultProgramId.toBase58(),
|
|
386
|
+
});
|
|
387
|
+
const state = await program.account.vaultState.fetch(vaultStatePda);
|
|
388
|
+
const returnObj = {
|
|
389
|
+
depositMint: state.depositMint.toString(),
|
|
390
|
+
shareMint: state.shareMint.toString(),
|
|
391
|
+
// Empty arrays yield undefined, falling back to legacy PDA seeds (no version byte)
|
|
392
|
+
vaultVersion: Array.isArray(state.vaultVersion)
|
|
393
|
+
? state.vaultVersion[0]
|
|
394
|
+
: undefined,
|
|
395
|
+
};
|
|
396
|
+
await (0, core_1.setSafeCache)(cacheKey, returnObj);
|
|
397
|
+
return returnObj;
|
|
398
|
+
}
|
|
399
|
+
catch (e) {
|
|
400
|
+
const error = e;
|
|
401
|
+
const cause = error?.message || String(error);
|
|
402
|
+
core_1.Logger.log.error('getVaultMints', error, {
|
|
403
|
+
message: 'Error reading vault mints',
|
|
404
|
+
vaultProgramId: String(vaultProgramId),
|
|
405
|
+
vaultAddress: vaultAddress ? String(vaultAddress) : undefined,
|
|
406
|
+
});
|
|
407
|
+
throw new core_1.UpshiftSDKError('UNKNOWN', `Failed to read vault mints from on-chain state: ${cause}`, {
|
|
408
|
+
cause: error,
|
|
409
|
+
context: {
|
|
410
|
+
vaultProgramId: String(vaultProgramId),
|
|
411
|
+
vaultAddress: vaultAddress ? String(vaultAddress) : undefined,
|
|
412
|
+
},
|
|
413
|
+
});
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
async function getToken({ mintAddress, endpoint, connection, }) {
|
|
417
|
+
// `endpoint` is optional on ISolanaConnectionOptions, so a caller with no
|
|
418
|
+
// Solana RPC configured reaches `fetch(undefined, ...)`, which throws
|
|
419
|
+
// "Cannot read properties of undefined (reading 'toString')" from inside the
|
|
420
|
+
// runtime's fetch wrapper — an unactionable error masquerading as a token
|
|
421
|
+
// failure. Bail before the request instead.
|
|
422
|
+
if (!endpoint) {
|
|
423
|
+
core_1.Logger.log.warn('getToken', 'No Solana RPC endpoint configured', {
|
|
424
|
+
mintAddress: String(mintAddress),
|
|
425
|
+
});
|
|
426
|
+
return null;
|
|
427
|
+
}
|
|
428
|
+
try {
|
|
429
|
+
const cacheKey = `solana_token-${mintAddress}`;
|
|
430
|
+
const cachedData = await (0, core_1.getSafeCache)(cacheKey);
|
|
431
|
+
if (cachedData)
|
|
432
|
+
return cachedData;
|
|
433
|
+
// First, try to get token metadata via Metaplex DAS API
|
|
434
|
+
const response = await fetch(endpoint, {
|
|
435
|
+
method: 'POST',
|
|
436
|
+
headers: {
|
|
437
|
+
'Content-Type': 'application/json',
|
|
438
|
+
},
|
|
439
|
+
body: JSON.stringify({
|
|
440
|
+
jsonrpc: '2.0',
|
|
441
|
+
id: 'text',
|
|
442
|
+
method: 'getAsset',
|
|
443
|
+
params: { id: mintAddress.toString() },
|
|
444
|
+
}),
|
|
445
|
+
});
|
|
446
|
+
const data = (await response.json()); // @todo: add interface
|
|
447
|
+
// If we got valid token metadata, return it
|
|
448
|
+
if (data.result?.token_info?.decimals !== undefined) {
|
|
449
|
+
return {
|
|
450
|
+
address: data?.result?.id,
|
|
451
|
+
symbol: data.result?.content?.metadata?.symbol || 'N/A',
|
|
452
|
+
decimals: data.result?.token_info?.decimals,
|
|
453
|
+
name: data.result?.content?.metadata?.name || 'Unknown',
|
|
454
|
+
image: data.result?.content.metadata?.image,
|
|
455
|
+
program: data.result?.token_info?.token_program,
|
|
456
|
+
mintAuthority: data.result?.token_info?.mint_authority,
|
|
457
|
+
freezeAuthority: data.result?.token_info?.freeze_authority,
|
|
458
|
+
supply: data.result?.token_info?.supply,
|
|
459
|
+
};
|
|
460
|
+
}
|
|
461
|
+
// If Metaplex metadata didn't work, check if this is a vault program address
|
|
462
|
+
// In that case, we should query the share mint PDA instead
|
|
463
|
+
if (connection) {
|
|
464
|
+
try {
|
|
465
|
+
const vaultProgramId = new web3_js_1.PublicKey(mintAddress);
|
|
466
|
+
// Try to get vault state to see if this is a vault
|
|
467
|
+
const vaultState = await getVaultStateReadOnly({
|
|
468
|
+
vaultProgramId,
|
|
469
|
+
connection,
|
|
470
|
+
idl: constants_1.vaultIdl,
|
|
471
|
+
});
|
|
472
|
+
// If we successfully got vault state, derive the share mint and query that
|
|
473
|
+
if (vaultState.vaultState) {
|
|
474
|
+
const shareMintPda = deriveShareMintPda(vaultProgramId);
|
|
475
|
+
// Get share mint metadata from blockchain
|
|
476
|
+
const shareMintInfo = await connection.getParsedAccountInfo(shareMintPda);
|
|
477
|
+
if (shareMintInfo.value) {
|
|
478
|
+
const parsedData = shareMintInfo.value.data;
|
|
479
|
+
// Try to get Metaplex metadata for the share mint
|
|
480
|
+
const shareMintMetadata = await fetch(endpoint, {
|
|
481
|
+
method: 'POST',
|
|
482
|
+
headers: {
|
|
483
|
+
'Content-Type': 'application/json',
|
|
484
|
+
},
|
|
485
|
+
body: JSON.stringify({
|
|
486
|
+
jsonrpc: '2.0',
|
|
487
|
+
id: 'text',
|
|
488
|
+
method: 'getAsset',
|
|
489
|
+
params: { id: shareMintPda.toString() },
|
|
490
|
+
}),
|
|
491
|
+
});
|
|
492
|
+
const shareMintData = (await shareMintMetadata.json());
|
|
493
|
+
const returnObj = {
|
|
494
|
+
address: mintAddress.toString(), // Return original vault address
|
|
495
|
+
symbol: shareMintData.result?.content?.metadata?.symbol || 'N/A',
|
|
496
|
+
decimals: parsedData.parsed.info.decimals,
|
|
497
|
+
name: shareMintData.result?.content?.metadata?.name ||
|
|
498
|
+
'Vault Share Token',
|
|
499
|
+
image: shareMintData.result?.content?.metadata?.image,
|
|
500
|
+
program: shareMintData.result?.token_info?.token_program,
|
|
501
|
+
mintAuthority: shareMintData.result?.token_info?.mint_authority,
|
|
502
|
+
freezeAuthority: shareMintData.result?.token_info?.freeze_authority,
|
|
503
|
+
supply: parsedData.parsed.info.supply,
|
|
504
|
+
shareMintAddress: shareMintPda.toString(), // Include share mint address
|
|
505
|
+
};
|
|
506
|
+
await (0, core_1.setSafeCache)(cacheKey, returnObj);
|
|
507
|
+
return returnObj;
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
catch (vaultError) {
|
|
512
|
+
// Not a vault or error checking vault state, continue to return null
|
|
513
|
+
core_1.Logger.log.info('getToken', 'Not a vault program or error querying vault:', vaultError);
|
|
514
|
+
}
|
|
515
|
+
}
|
|
516
|
+
// If all attempts failed, return null
|
|
517
|
+
return null;
|
|
518
|
+
}
|
|
519
|
+
catch (error) {
|
|
520
|
+
// Callers fall back to backend metadata when this returns null, so a failed
|
|
521
|
+
// lookup is degraded-but-handled — warn, don't raise it as an error.
|
|
522
|
+
core_1.Logger.log.warn('getToken', 'Error fetching token metadata', {
|
|
523
|
+
mintAddress: String(mintAddress),
|
|
524
|
+
error,
|
|
525
|
+
});
|
|
526
|
+
return null;
|
|
527
|
+
}
|
|
528
|
+
}
|
|
529
|
+
// Helper function to get just the token symbol
|
|
530
|
+
async function getTokenSymbol({ endpoint, mintAddress, }) {
|
|
531
|
+
try {
|
|
532
|
+
const tokenMetadata = await getToken({ endpoint, mintAddress });
|
|
533
|
+
if (tokenMetadata) {
|
|
534
|
+
return tokenMetadata.symbol;
|
|
535
|
+
}
|
|
536
|
+
return null;
|
|
537
|
+
}
|
|
538
|
+
catch (error) {
|
|
539
|
+
core_1.Logger.log.error('getTokenSymbol', error, {
|
|
540
|
+
message: 'Error fetching token symbol',
|
|
541
|
+
});
|
|
542
|
+
return null;
|
|
543
|
+
}
|
|
544
|
+
}
|
|
545
|
+
/**
|
|
546
|
+
* A wallet's token account of record for one mint: the account holding the
|
|
547
|
+
* largest balance.
|
|
548
|
+
*
|
|
549
|
+
* **This is the single selection rule for the whole Solana adapter.** Reads
|
|
550
|
+
* (balances shown in a UI) and writes (the account a transfer actually names)
|
|
551
|
+
* must agree on which account represents the user, or the two describe
|
|
552
|
+
* different money: a balance read from account A while a deposit mints into
|
|
553
|
+
* account B looks, to the user, like a successful transaction that never
|
|
554
|
+
* arrived. Every reader in this file and both vault handlers call this, so the
|
|
555
|
+
* rule cannot drift.
|
|
556
|
+
*
|
|
557
|
+
* Largest-balance rather than "the first account the RPC returned":
|
|
558
|
+
* `getParsedTokenAccountsByOwner` guarantees no ordering, so taking `[0]` can
|
|
559
|
+
* select an empty leftover while the user's funds sit in a sibling account —
|
|
560
|
+
* which fails on-chain as an insufficient-funds revert *after* they sign.
|
|
561
|
+
*
|
|
562
|
+
* **Spendable accounts win over frozen ones**, regardless of balance: a token
|
|
563
|
+
* issuer can freeze an account, and neither a transfer out of nor a transfer
|
|
564
|
+
* into a frozen account can succeed. Picking the richest account outright would
|
|
565
|
+
* hand a frozen one to the program and revert with SPL `0x11` after the user
|
|
566
|
+
* signs, while a perfectly usable sibling sat next to it. When *every* account
|
|
567
|
+
* is frozen the richest frozen one is still returned, flagged — a reader should
|
|
568
|
+
* show that balance, because the user does own it, and a writer should refuse
|
|
569
|
+
* before asking for a signature.
|
|
570
|
+
*
|
|
571
|
+
* Internal to the adapter: exported for `vault.actions.ts`, deliberately not
|
|
572
|
+
* re-exported on `SolanaUtils`. It takes a raw RPC response shape, which is not
|
|
573
|
+
* a contract worth freezing on the public surface.
|
|
574
|
+
*
|
|
575
|
+
* @param accounts - The `.value` array from `getParsedTokenAccountsByOwner`.
|
|
576
|
+
* @returns The selected account with its raw balance, decimals, the RPC's own
|
|
577
|
+
* `uiAmount`, and whether it is frozen — or `undefined` when the wallet owns
|
|
578
|
+
* no account for the mint.
|
|
579
|
+
* @throws UpshiftSDKError when an account's balance cannot be read. An
|
|
580
|
+
* unreadable balance is *unknown*, not zero — scoring it zero would silently
|
|
581
|
+
* demote a funded account below a dust one and reinstate the `[0]` behaviour
|
|
582
|
+
* this function exists to prevent.
|
|
583
|
+
*/
|
|
584
|
+
function selectRichestTokenAccount(accounts) {
|
|
585
|
+
const parsed = accounts.map((entry) => {
|
|
586
|
+
const info = entry.account.data?.parsed?.info;
|
|
587
|
+
const tokenAmount = info?.tokenAmount;
|
|
588
|
+
const raw = tokenAmount?.amount;
|
|
589
|
+
// A u64 string is the only shape the RPC promises here. Anything else
|
|
590
|
+
// (a base64 `data` the node could not jsonParse, a reshaping proxy) means
|
|
591
|
+
// we do not know this balance.
|
|
592
|
+
if (typeof raw !== 'string' || !/^\d+$/.test(raw)) {
|
|
593
|
+
throw new core_1.UpshiftSDKError('UNKNOWN', `Could not read the balance of token account ${entry.pubkey.toBase58()}. ` +
|
|
594
|
+
'The RPC returned an unrecognized response; please retry or switch RPC.', {
|
|
595
|
+
context: {
|
|
596
|
+
account: entry.pubkey.toBase58(),
|
|
597
|
+
rawAmount: String(raw),
|
|
598
|
+
},
|
|
599
|
+
});
|
|
600
|
+
}
|
|
601
|
+
return {
|
|
602
|
+
pubkey: entry.pubkey,
|
|
603
|
+
amount: BigInt(raw),
|
|
604
|
+
decimals: typeof tokenAmount?.decimals === 'number' ? tokenAmount.decimals : null,
|
|
605
|
+
// Passed through from the RPC rather than recomputed from `amount`, so
|
|
606
|
+
// the lossy float these legacy readers return is not made lossier.
|
|
607
|
+
uiAmount: typeof tokenAmount?.uiAmount === 'number' ? tokenAmount.uiAmount : null,
|
|
608
|
+
frozen: info?.state === 'frozen',
|
|
609
|
+
};
|
|
610
|
+
});
|
|
611
|
+
const richest = (candidates) => candidates.reduce((best, candidate) => {
|
|
612
|
+
if (!best)
|
|
613
|
+
return candidate;
|
|
614
|
+
if (candidate.amount !== best.amount)
|
|
615
|
+
return candidate.amount > best.amount ? candidate : best;
|
|
616
|
+
// Equal balances: break the tie on the address. Falling back to "whichever
|
|
617
|
+
// the RPC listed first" would leave exactly the ordering dependence this
|
|
618
|
+
// function exists to remove — harmless while the balances match, but it
|
|
619
|
+
// means two calls can name different accounts for the same wallet.
|
|
620
|
+
return candidate.pubkey.toBase58() < best.pubkey.toBase58()
|
|
621
|
+
? candidate
|
|
622
|
+
: best;
|
|
623
|
+
}, undefined);
|
|
624
|
+
return richest(parsed.filter((a) => !a.frozen)) ?? richest(parsed);
|
|
625
|
+
}
|
|
626
|
+
async function fetchUserTokenBalance({ connection, publicKey, depositMint, }) {
|
|
627
|
+
if (!publicKey || !depositMint)
|
|
628
|
+
return '0';
|
|
629
|
+
try {
|
|
630
|
+
const _publicKey = new web3_js_1.PublicKey(publicKey);
|
|
631
|
+
const _depositMint = new web3_js_1.PublicKey(depositMint);
|
|
632
|
+
const tokenAccounts = await connection.getParsedTokenAccountsByOwner(_publicKey, {
|
|
633
|
+
mint: _depositMint,
|
|
634
|
+
});
|
|
635
|
+
// Same rule the write path uses — see `selectRichestTokenAccount`.
|
|
636
|
+
const richest = selectRichestTokenAccount(tokenAccounts.value);
|
|
637
|
+
return richest?.uiAmount?.toString() || '0';
|
|
638
|
+
}
|
|
639
|
+
catch (e) {
|
|
640
|
+
const error = e;
|
|
641
|
+
core_1.Logger.log.error('fetchUserTokenBalance', error, {
|
|
642
|
+
message: 'Error fetching user token balance',
|
|
643
|
+
});
|
|
644
|
+
return '0';
|
|
645
|
+
}
|
|
646
|
+
}
|
|
647
|
+
async function fetchUserShareBalance({ connection, publicKey, shareMint, }) {
|
|
648
|
+
if (!publicKey || !shareMint)
|
|
649
|
+
return 0;
|
|
650
|
+
try {
|
|
651
|
+
const _publicKey = new web3_js_1.PublicKey(publicKey);
|
|
652
|
+
const _shareMint = new web3_js_1.PublicKey(shareMint);
|
|
653
|
+
const shareAccounts = await connection.getParsedTokenAccountsByOwner(_publicKey, {
|
|
654
|
+
mint: _shareMint,
|
|
655
|
+
});
|
|
656
|
+
// Same rule the write path uses — see `selectRichestTokenAccount`.
|
|
657
|
+
const richest = selectRichestTokenAccount(shareAccounts.value);
|
|
658
|
+
return richest?.uiAmount ?? 0;
|
|
659
|
+
}
|
|
660
|
+
catch (e) {
|
|
661
|
+
const error = e;
|
|
662
|
+
core_1.Logger.log.error('fetchUserShareBalance', error, {
|
|
663
|
+
message: 'Error fetching user share balance',
|
|
664
|
+
});
|
|
665
|
+
return 0;
|
|
666
|
+
}
|
|
667
|
+
}
|
|
668
|
+
/**
|
|
669
|
+
* Same on-chain lookup as `fetchUserShareBalance`, but returns the raw u64
|
|
670
|
+
* `amount` string and the mint's `decimals` — or `null` when the scale can't
|
|
671
|
+
* be determined (see Return shape) — instead of the floating-point `uiAmount`.
|
|
672
|
+
* Callers that feed the result into BigInt math (positions, redemptions,
|
|
673
|
+
* allowances) must use this — `uiAmount` is lossy and silently encodes with
|
|
674
|
+
* the wrong scale when passed back through `toNormalizedBn` without an
|
|
675
|
+
* explicit decimals argument.
|
|
676
|
+
*
|
|
677
|
+
* Return shape:
|
|
678
|
+
* `{ amount: '<u64>', decimals: number }` — token account exists.
|
|
679
|
+
* `{ amount: '0', decimals: null }` — no token account, missing
|
|
680
|
+
* tokenAmount in the parsed response, missing args, or the RPC threw.
|
|
681
|
+
*
|
|
682
|
+
* `decimals === null` is the "scale unknown" signal — callers that need a
|
|
683
|
+
* normalized `INormalizedNumber` must substitute a known decimals value
|
|
684
|
+
* (typically the deposit mint's decimals from backend metadata) before
|
|
685
|
+
* normalizing. Never throws.
|
|
686
|
+
*
|
|
687
|
+
* RPC cost: 1 × `getParsedTokenAccountsByOwner`. Independent of how many
|
|
688
|
+
* other accounts the wallet owns (filter is mint-scoped).
|
|
689
|
+
*/
|
|
690
|
+
async function fetchUserShareBalanceRaw({ connection, publicKey, shareMint, }) {
|
|
691
|
+
if (!publicKey || !shareMint)
|
|
692
|
+
return { amount: '0', decimals: null };
|
|
693
|
+
try {
|
|
694
|
+
const _publicKey = new web3_js_1.PublicKey(publicKey);
|
|
695
|
+
const _shareMint = new web3_js_1.PublicKey(shareMint);
|
|
696
|
+
const shareAccounts = await connection.getParsedTokenAccountsByOwner(_publicKey, {
|
|
697
|
+
mint: _shareMint,
|
|
698
|
+
});
|
|
699
|
+
// Same rule the write path uses — see `selectRichestTokenAccount`.
|
|
700
|
+
const richest = selectRichestTokenAccount(shareAccounts?.value ?? []);
|
|
701
|
+
if (!richest)
|
|
702
|
+
return { amount: '0', decimals: null };
|
|
703
|
+
return {
|
|
704
|
+
amount: richest.amount.toString(),
|
|
705
|
+
decimals: richest.decimals,
|
|
706
|
+
};
|
|
707
|
+
}
|
|
708
|
+
catch (e) {
|
|
709
|
+
const error = e;
|
|
710
|
+
core_1.Logger.log.error('fetchUserShareBalanceRaw', error, {
|
|
711
|
+
message: 'Error fetching raw user share balance',
|
|
712
|
+
});
|
|
713
|
+
return { amount: '0', decimals: null };
|
|
714
|
+
}
|
|
715
|
+
}
|
|
716
|
+
/**
|
|
717
|
+
* Resolve the on-chain program ID for a Solana vault.
|
|
718
|
+
* Prefers the explicit `program_id` from metadata; falls back to the vault address
|
|
719
|
+
* (for legacy vaults where address == program ID).
|
|
720
|
+
*/
|
|
721
|
+
function resolveProgramId(vaultAddress, solanaMetadata) {
|
|
722
|
+
return solanaMetadata?.program_id || vaultAddress;
|
|
723
|
+
}
|
|
724
|
+
// String-arithmetic UI→raw conversion. Truncates past `decimals` like
|
|
725
|
+
// ethers.parseUnits; safe past Number.MAX_SAFE_INTEGER.
|
|
726
|
+
/** @internal */
|
|
727
|
+
function uiAmountToRawBn(uiAmount, decimals) {
|
|
728
|
+
if (!Number.isFinite(uiAmount)) {
|
|
729
|
+
throw new core_1.UpshiftValidationError('INVALID_INPUT', `uiAmountToRawBn: amount must be finite, got ${uiAmount}`);
|
|
730
|
+
}
|
|
731
|
+
if (uiAmount < 0) {
|
|
732
|
+
throw new core_1.UpshiftValidationError('INVALID_INPUT', `uiAmountToRawBn: amount must be non-negative, got ${uiAmount}`);
|
|
733
|
+
}
|
|
734
|
+
if (!Number.isInteger(decimals) || decimals < 0) {
|
|
735
|
+
throw new core_1.UpshiftValidationError('INVALID_INPUT', `uiAmountToRawBn: decimals must be a non-negative integer, got ${decimals}`);
|
|
736
|
+
}
|
|
737
|
+
// Build the fixed-decimal string manually — `.toFixed(decimals)` rounds
|
|
738
|
+
// the last digit, but parseUnits semantics require truncation.
|
|
739
|
+
const uiStr = uiAmount.toString();
|
|
740
|
+
const expansion = uiStr.includes('e') || uiStr.includes('E')
|
|
741
|
+
? uiAmount.toFixed(Math.max(decimals, 20)).replace(/\.?0+$/, '')
|
|
742
|
+
: uiStr;
|
|
743
|
+
const [whole, fraction = ''] = expansion.split('.');
|
|
744
|
+
const truncated = fraction.slice(0, decimals);
|
|
745
|
+
const paddedFraction = truncated.padEnd(decimals, '0');
|
|
746
|
+
const raw = (whole + paddedFraction).replace(/^0+/, '') || '0';
|
|
747
|
+
return new anchor_1.BN(raw);
|
|
748
|
+
}
|
|
749
|
+
exports.SolanaUtils = {
|
|
750
|
+
getExplorerLink,
|
|
751
|
+
isSolanaAddress: chain_address_1.isSolanaAddress,
|
|
752
|
+
isSolana,
|
|
753
|
+
getProgram,
|
|
754
|
+
getProvider,
|
|
755
|
+
getReadOnlyProvider,
|
|
756
|
+
deriveShareMintPda,
|
|
757
|
+
deriveVaultStatePda,
|
|
758
|
+
deriveVaultTokenAtaPda,
|
|
759
|
+
getVaultState,
|
|
760
|
+
getVaultStateReadOnly,
|
|
761
|
+
getToken,
|
|
762
|
+
getTokenSymbol,
|
|
763
|
+
fetchUserTokenBalance,
|
|
764
|
+
fetchUserShareBalance,
|
|
765
|
+
fetchUserShareBalanceRaw,
|
|
766
|
+
getBestRpcEndpoint,
|
|
767
|
+
fallbackDecimals: constants_1.fallbackDecimals,
|
|
768
|
+
fallbackNetwork: constants_1.fallbackNetwork,
|
|
769
|
+
programIds: constants_1.programIds,
|
|
770
|
+
getVaultMints,
|
|
771
|
+
resolveProgramId,
|
|
772
|
+
};
|
|
773
|
+
//# sourceMappingURL=utils.js.map
|