@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,657 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.describeSolanaError = describeSolanaError;
|
|
4
|
+
exports.slippageHint = slippageHint;
|
|
5
|
+
exports.validateExplicitFloor = validateExplicitFloor;
|
|
6
|
+
exports.handleSolanaDeposit = handleSolanaDeposit;
|
|
7
|
+
exports.handleSolanaRedeem = handleSolanaRedeem;
|
|
8
|
+
const anchor_1 = require("@coral-xyz/anchor");
|
|
9
|
+
const web3_js_1 = require("@solana/web3.js");
|
|
10
|
+
const spl_token_1 = require("@solana/spl-token");
|
|
11
|
+
const pricing_1 = require("./pricing");
|
|
12
|
+
const utils_1 = require("./utils");
|
|
13
|
+
const core_1 = require("../../core");
|
|
14
|
+
const token_reads_1 = require("./token-reads");
|
|
15
|
+
const queue_hints_1 = require("./queue-hints");
|
|
16
|
+
/**
|
|
17
|
+
* User-facing fallback for a throwable that carries no extractable reason.
|
|
18
|
+
* Surfaces as e.g. `"Solana deposit failed: please try again"` — actionable,
|
|
19
|
+
* and without telling the user we couldn't classify the failure.
|
|
20
|
+
*/
|
|
21
|
+
const REASONLESS_SOLANA_ERROR = 'please try again';
|
|
22
|
+
/**
|
|
23
|
+
* Extract a human- and machine-readable reason from a thrown Solana/Anchor
|
|
24
|
+
* error.
|
|
25
|
+
*
|
|
26
|
+
* Anchor's {@link https://github.com/coral-xyz/anchor `ProgramError`} calls
|
|
27
|
+
* `super()` with no argument, so its `.message` is the empty string by
|
|
28
|
+
* construction — the real reason lives on `.msg`/`.code` (and `.toString()`).
|
|
29
|
+
* `AnchorError` puts it on `.error.errorMessage` / `.error.errorCode.number`.
|
|
30
|
+
* Reading `.message` directly (as this file used to) therefore rendered
|
|
31
|
+
* `"Solana deposit failed: "` for every recognized on-chain revert — dropping
|
|
32
|
+
* the numeric code the team needs to tell "Vault is paused" from
|
|
33
|
+
* "Insufficient amount" from an account-constraint violation.
|
|
34
|
+
*
|
|
35
|
+
* When no reason can be extracted, returns an actionable, user-facing fallback
|
|
36
|
+
* rather than an "unknown error" admission — the raw throwable is still
|
|
37
|
+
* preserved on the wrapped error's `cause` and in telemetry for diagnosis.
|
|
38
|
+
*/
|
|
39
|
+
function describeSolanaError(e) {
|
|
40
|
+
if (!(e instanceof Error))
|
|
41
|
+
return REASONLESS_SOLANA_ERROR;
|
|
42
|
+
const anchor = e;
|
|
43
|
+
const reason = anchor.error?.errorMessage ?? anchor.msg ?? '';
|
|
44
|
+
const code = anchor.error?.errorCode?.number ?? anchor.code;
|
|
45
|
+
if (reason)
|
|
46
|
+
return code === undefined ? reason : `${reason} (code ${code})`;
|
|
47
|
+
// SendTransactionError / generic Error carry a non-empty `.message`.
|
|
48
|
+
if (e.message)
|
|
49
|
+
return e.message;
|
|
50
|
+
// Last resort: a subclass may override `toString()` (never the empty string).
|
|
51
|
+
const str = e.toString();
|
|
52
|
+
return str && str !== e.name && str !== `${e.name}: `
|
|
53
|
+
? str
|
|
54
|
+
: REASONLESS_SOLANA_ERROR;
|
|
55
|
+
}
|
|
56
|
+
/** `SlippageExceeded` in `august_vault`'s error enum (ordinal 18 + Anchor's 6000 offset). */
|
|
57
|
+
const SLIPPAGE_EXCEEDED_CODE = 6018;
|
|
58
|
+
/**
|
|
59
|
+
* A sentence to append when the program rejected the instruction for
|
|
60
|
+
* slippage, so the caller learns what to do rather than just what happened.
|
|
61
|
+
* Empty for every other error.
|
|
62
|
+
*
|
|
63
|
+
* Not merely a restatement of the program's reason string. Both checked
|
|
64
|
+
* instructions share error 6018, whose on-chain message is share-worded —
|
|
65
|
+
* "Received fewer shares than the caller's minimum" — so on a *redeem*, where
|
|
66
|
+
* the floor is denominated in assets, the program describes the wrong
|
|
67
|
+
* quantity. This sentence is what makes that case readable. The numeric code
|
|
68
|
+
* `describeSolanaError` renders alongside it is kept deliberately: it is the
|
|
69
|
+
* handle support and telemetry group on, and it survives a reword of the
|
|
70
|
+
* program's message.
|
|
71
|
+
*/
|
|
72
|
+
function slippageHint(e) {
|
|
73
|
+
const anchor = e;
|
|
74
|
+
const code = anchor?.error?.errorCode?.number ?? anchor?.code;
|
|
75
|
+
const name = anchor?.error?.errorCode?.code;
|
|
76
|
+
return code === SLIPPAGE_EXCEEDED_CODE || name === 'SlippageExceeded'
|
|
77
|
+
? ' The share price moved past your slippage tolerance between the quote and execution; retry, or pass a higher `slippageBps`.'
|
|
78
|
+
: '';
|
|
79
|
+
}
|
|
80
|
+
/** Largest value Anchor's `u64` encoder can represent. */
|
|
81
|
+
const U64_MAX = 18446744073709551615n;
|
|
82
|
+
/**
|
|
83
|
+
* Validate a caller-supplied slippage floor before it reaches Anchor's `u64`
|
|
84
|
+
* encoder.
|
|
85
|
+
*
|
|
86
|
+
* Anchor serializes a `BN` to `u64` via `toArrayLike`, which writes the
|
|
87
|
+
* magnitude and drops the sign — a `-1n` floor would reach the program as a
|
|
88
|
+
* floor of `1`, inverting the caller's intent instead of failing. Values above
|
|
89
|
+
* `u64` fare no better: they surface as an untyped "byte array longer than
|
|
90
|
+
* desired length" from inside the encoder, naming nothing the caller passed.
|
|
91
|
+
* Both are caught here, at the public-API boundary.
|
|
92
|
+
*
|
|
93
|
+
* @param value - The caller's `minSharesOut` / `minAssetsOut`, in raw base units.
|
|
94
|
+
* @param label - Option name, quoted in the error.
|
|
95
|
+
* @returns `value`, once known to fit an unsigned 64-bit integer.
|
|
96
|
+
* @throws {@link UpshiftValidationError} when it is not a `bigint` in `0 .. 2^64-1`.
|
|
97
|
+
*/
|
|
98
|
+
function validateExplicitFloor(value, label) {
|
|
99
|
+
if (typeof value !== 'bigint' || value < 0n || value > U64_MAX) {
|
|
100
|
+
throw new core_1.UpshiftValidationError('INVALID_INPUT', `${label} must be a bigint between 0 and ${U64_MAX} raw base units, got ${String(value)}.`);
|
|
101
|
+
}
|
|
102
|
+
return value;
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* Decide whether a handler can send the slippage-checked instruction, or must
|
|
106
|
+
* fall back to the plain one.
|
|
107
|
+
*
|
|
108
|
+
* `idl` is caller-supplied, and an IDL generated before the program shipped
|
|
109
|
+
* `deposit_checked` / `redeem_checked` produces a `Program` with no such
|
|
110
|
+
* method. Until 9.4.0 the handlers sent the plain instructions, so those same
|
|
111
|
+
* inputs used to work; calling the checked method on them throws a TypeError
|
|
112
|
+
* before anything is sent (AUGUST-8023). The program still accepts the plain
|
|
113
|
+
* instructions, with the behaviour a floor of `0` gives the checked ones, so
|
|
114
|
+
* falling back preserves what those callers had.
|
|
115
|
+
*
|
|
116
|
+
* The fallback is refused when the caller asked for protection — a non-zero
|
|
117
|
+
* explicit floor, or any `slippageBps` — since the plain instruction would
|
|
118
|
+
* silently drop it. An explicit floor of `0n` is the documented "no floor" and
|
|
119
|
+
* falls back.
|
|
120
|
+
*
|
|
121
|
+
* Probes `program.methods` rather than `idl.instructions`: that is the object
|
|
122
|
+
* the handler calls, and it already reflects Anchor's camelCase conversion.
|
|
123
|
+
*
|
|
124
|
+
* @param program - Anchor program built from the caller's IDL.
|
|
125
|
+
* @param checkedMethod - `'depositChecked'` or `'redeemChecked'`.
|
|
126
|
+
* @param floor - The caller's explicit `minSharesOut` / `minAssetsOut`, if any.
|
|
127
|
+
* @param slippageBps - The caller's tolerance, if any.
|
|
128
|
+
* @param handler - Handler name, for the log line and the error.
|
|
129
|
+
* @returns `true` to send the checked instruction, `false` for the plain one.
|
|
130
|
+
* @throws {@link UpshiftValidationError} when the IDL lacks the checked
|
|
131
|
+
* instruction and the caller asked for a floor.
|
|
132
|
+
*/
|
|
133
|
+
function sendsCheckedInstruction({ program, checkedMethod, floor, slippageBps, handler, }) {
|
|
134
|
+
const methods = program.methods;
|
|
135
|
+
if (typeof methods[checkedMethod] === 'function')
|
|
136
|
+
return true;
|
|
137
|
+
const instruction = checkedMethod === 'depositChecked' ? 'deposit_checked' : 'redeem_checked';
|
|
138
|
+
const floorOption = checkedMethod === 'depositChecked' ? 'minSharesOut' : 'minAssetsOut';
|
|
139
|
+
if ((floor !== undefined && floor !== 0n) || slippageBps !== undefined) {
|
|
140
|
+
throw new core_1.UpshiftValidationError('INVALID_INPUT', `${handler}: the supplied IDL does not declare \`${instruction}\`, so ` +
|
|
141
|
+
`the requested ${floorOption} / slippageBps cannot be enforced. Pass ` +
|
|
142
|
+
"the SDK's bundled IDL (`Solana.constants.vaultIdl`), or omit both " +
|
|
143
|
+
'options to send the unguarded instruction.');
|
|
144
|
+
}
|
|
145
|
+
core_1.Logger.log.warn(handler, `Supplied IDL does not declare \`${instruction}\`; sending the plain ` +
|
|
146
|
+
"instruction without a slippage floor. Pass the SDK's bundled IDL to " +
|
|
147
|
+
'enable slippage protection.');
|
|
148
|
+
return false;
|
|
149
|
+
}
|
|
150
|
+
/**
|
|
151
|
+
* Resolve the two token accounts a vault instruction needs, concurrently.
|
|
152
|
+
*
|
|
153
|
+
* The lookups are independent, so running them in sequence doubles the
|
|
154
|
+
* round-trip latency on the hot path for no benefit — both results are needed
|
|
155
|
+
* before anything is submitted either way.
|
|
156
|
+
*
|
|
157
|
+
* Rejections are surfaced in argument order rather than in whichever order the
|
|
158
|
+
* RPC happened to fail, so the error a user sees does not depend on network
|
|
159
|
+
* timing. `Promise.all` would reject with whichever call failed *first*, which
|
|
160
|
+
* on a malformed RPC response (both lookups throwing) would name a different
|
|
161
|
+
* account run to run.
|
|
162
|
+
*
|
|
163
|
+
* @param mints - `[first, second]`; the returned tuple matches this order.
|
|
164
|
+
*/
|
|
165
|
+
async function resolveUserTokenAccountPair({ connection, owner, mints, }) {
|
|
166
|
+
const settled = await Promise.allSettled(mints.map((mint) => (0, token_reads_1.resolveUserTokenAccount)({ connection, owner, mint })));
|
|
167
|
+
const resolved = settled.map((outcome) => {
|
|
168
|
+
if (outcome.status === 'rejected')
|
|
169
|
+
throw outcome.reason;
|
|
170
|
+
return outcome.value;
|
|
171
|
+
});
|
|
172
|
+
return [resolved[0], resolved[1]];
|
|
173
|
+
}
|
|
174
|
+
/**
|
|
175
|
+
* Deposit funds into a Solana Upshift vault and mint share tokens.
|
|
176
|
+
*
|
|
177
|
+
* @param depositAmount `bigint` is treated as raw on-chain units (preferred
|
|
178
|
+
* — no JS-float round-trip). `number` is treated as a UI amount and
|
|
179
|
+
* scaled by the deposit mint's decimals via {@link uiAmountToRawBn}.
|
|
180
|
+
* @throws UpshiftValidationError on missing wallet/programId or invalid amount,
|
|
181
|
+
* or when the wallet's deposit-mint balance is below `depositAmount`.
|
|
182
|
+
* @throws UpshiftSDKError when a candidate token account's balance cannot be
|
|
183
|
+
* read, or wrapping any downstream failure (`cause` preserved).
|
|
184
|
+
* @throws UpshiftTransactionError (`TRANSACTION_UNCONFIRMED`) when the
|
|
185
|
+
* transaction was sent but its outcome could not be established; check
|
|
186
|
+
* `signature` before resubmitting.
|
|
187
|
+
* @remarks **Side effects.** Submits exactly one transaction, signed by the
|
|
188
|
+
* provider's wallet. When the wallet has no share account, that transaction
|
|
189
|
+
* also creates one, and `publicKey` pays its rent — roughly 0.00204 SOL,
|
|
190
|
+
* non-refundable while the account stays open — on top of the network fee.
|
|
191
|
+
* Callers should ensure the wallet holds enough SOL for both before
|
|
192
|
+
* prompting the user to sign.
|
|
193
|
+
* @remarks Sends `deposit_checked`, not `deposit`. Unless `options.minSharesOut`
|
|
194
|
+
* is given, the vault state and share mint are read to quote the shares this
|
|
195
|
+
* deposit should mint, and that quote lowered by `options.slippageBps`
|
|
196
|
+
* (default {@link DEFAULT_SLIPPAGE_BPS}) is passed as the floor; the program
|
|
197
|
+
* reverts with `SlippageExceeded` if execution would mint fewer. An `idl`
|
|
198
|
+
* that predates `deposit_checked` gets the plain `deposit` with no floor,
|
|
199
|
+
* unless `options` asks for one — then it throws {@link UpshiftValidationError}.
|
|
200
|
+
*/
|
|
201
|
+
async function handleSolanaDeposit({ provider, connection, vaultProgramId, vaultAddress, depositAmount, publicKey, idl, options = {}, }) {
|
|
202
|
+
try {
|
|
203
|
+
if (!publicKey)
|
|
204
|
+
throw new core_1.UpshiftValidationError('INVALID_INPUT', 'handleSolanaDeposit: wallet not connected');
|
|
205
|
+
if (depositAmount === undefined ||
|
|
206
|
+
depositAmount === null ||
|
|
207
|
+
(typeof depositAmount === 'bigint' && depositAmount <= 0n) ||
|
|
208
|
+
(typeof depositAmount === 'number' && !(depositAmount > 0)))
|
|
209
|
+
throw new core_1.UpshiftValidationError('INVALID_INPUT', 'handleSolanaDeposit: depositAmount is required and must be > 0');
|
|
210
|
+
if (!vaultProgramId)
|
|
211
|
+
throw new core_1.UpshiftValidationError('INVALID_INPUT', 'handleSolanaDeposit: vaultProgramId is required');
|
|
212
|
+
// Before any RPC: an unusable floor should cost the caller nothing.
|
|
213
|
+
if (options.minSharesOut !== undefined)
|
|
214
|
+
validateExplicitFloor(options.minSharesOut, 'minSharesOut');
|
|
215
|
+
const _vaultProgramId = new web3_js_1.PublicKey(vaultProgramId);
|
|
216
|
+
const program = utils_1.SolanaUtils.getProgram({
|
|
217
|
+
provider,
|
|
218
|
+
idl,
|
|
219
|
+
programId: _vaultProgramId.toBase58(),
|
|
220
|
+
});
|
|
221
|
+
// Also before any RPC: a floor the caller's IDL cannot enforce is refused
|
|
222
|
+
// on the spot rather than after the mint and token-account reads.
|
|
223
|
+
const checked = sendsCheckedInstruction({
|
|
224
|
+
program,
|
|
225
|
+
checkedMethod: 'depositChecked',
|
|
226
|
+
floor: options.minSharesOut,
|
|
227
|
+
slippageBps: options.slippageBps,
|
|
228
|
+
handler: 'handleSolanaDeposit',
|
|
229
|
+
});
|
|
230
|
+
// `getVaultMints` throws a typed `UpshiftSDKError` when the state cannot be
|
|
231
|
+
// read, so both mints are present here.
|
|
232
|
+
const { depositMint, shareMint, vaultVersion } = await utils_1.SolanaUtils.getVaultMints({
|
|
233
|
+
connection,
|
|
234
|
+
vaultProgramId,
|
|
235
|
+
vaultAddress,
|
|
236
|
+
idl,
|
|
237
|
+
});
|
|
238
|
+
const _publicKey = new web3_js_1.PublicKey(publicKey);
|
|
239
|
+
const _depositMint = new web3_js_1.PublicKey(depositMint);
|
|
240
|
+
core_1.Logger.log.info('handleSolanaDeposit', 'Program', program);
|
|
241
|
+
const vaultStatePda = vaultAddress
|
|
242
|
+
? new web3_js_1.PublicKey(vaultAddress)
|
|
243
|
+
: utils_1.SolanaUtils.deriveVaultStatePda(_vaultProgramId);
|
|
244
|
+
core_1.Logger.log.info('handleSolanaDeposit', 'Vault state PDA', vaultStatePda.toBase58());
|
|
245
|
+
const vaultTokenAtaPda = utils_1.SolanaUtils.deriveVaultTokenAtaPda(_vaultProgramId, _depositMint, vaultVersion);
|
|
246
|
+
core_1.Logger.log.info('handleSolanaDeposit', 'Vault token ATA PDA', vaultTokenAtaPda.toBase58());
|
|
247
|
+
// Use on-chain share mint from vault state instead of deriving a PDA,
|
|
248
|
+
// since PDA derivation assumes one share mint per program (single-vault only).
|
|
249
|
+
const shareMintAddr = new web3_js_1.PublicKey(shareMint);
|
|
250
|
+
core_1.Logger.log.info('handleSolanaDeposit', 'Share mint', shareMintAddr.toBase58());
|
|
251
|
+
// Get mint info to handle decimals
|
|
252
|
+
const mintInfo = await connection.getParsedAccountInfo(_depositMint);
|
|
253
|
+
if (!mintInfo.value) {
|
|
254
|
+
throw new core_1.UpshiftSDKError('UNKNOWN', `Could not fetch deposit mint info: account ${_depositMint.toBase58()} not found on the connected RPC`, { context: { depositMint: _depositMint.toBase58() } });
|
|
255
|
+
}
|
|
256
|
+
const { decimals } = mintInfo.value.data.parsed.info;
|
|
257
|
+
let depositAmountRaw;
|
|
258
|
+
if (typeof depositAmount === 'bigint') {
|
|
259
|
+
depositAmountRaw = new anchor_1.BN(depositAmount.toString());
|
|
260
|
+
}
|
|
261
|
+
else {
|
|
262
|
+
if (isNaN(depositAmount)) {
|
|
263
|
+
throw new core_1.UpshiftValidationError('INVALID_INPUT', 'Please enter a valid number for the deposit amount');
|
|
264
|
+
}
|
|
265
|
+
if (depositAmount <= 0) {
|
|
266
|
+
throw new core_1.UpshiftValidationError('INVALID_INPUT', 'Deposit amount must be greater than 0');
|
|
267
|
+
}
|
|
268
|
+
depositAmountRaw = (0, utils_1.uiAmountToRawBn)(depositAmount, decimals);
|
|
269
|
+
}
|
|
270
|
+
// Re-check *after* scaling. The guard above tests the caller's number, but
|
|
271
|
+
// a positive UI amount below one raw unit (1e-7 into a 6-decimal mint)
|
|
272
|
+
// truncates to zero here. Zero would then pass the balance gate below
|
|
273
|
+
// (`0n < 0n` is false) and submit a no-op transfer — from an account that
|
|
274
|
+
// may not even exist, since a zero balance is also what an absent account
|
|
275
|
+
// reports.
|
|
276
|
+
if (depositAmountRaw.lten(0)) {
|
|
277
|
+
throw new core_1.UpshiftValidationError('INVALID_INPUT', `Deposit amount is too small: ${depositAmount} is below one raw unit ` +
|
|
278
|
+
`of a ${decimals}-decimal mint.`);
|
|
279
|
+
}
|
|
280
|
+
// Match whatever this connection *reads* at. A `Connection` built without
|
|
281
|
+
// a commitment omits the parameter, so its reads take the RPC's default —
|
|
282
|
+
// `finalized`. Confirming writes at `confirmed` there would put reads
|
|
283
|
+
// behind writes again, which is the mismatch this whole change removes.
|
|
284
|
+
// `SolanaAdapter` always sets one explicitly, so this fallback only
|
|
285
|
+
// applies to callers reaching these handlers directly via `Solana.actions`.
|
|
286
|
+
const txCommitment = connection.commitment ?? 'finalized';
|
|
287
|
+
// `depositAccount` funds the transfer; `shareAccount` receives the minted
|
|
288
|
+
// shares. Independent lookups, so they run concurrently — but the guards
|
|
289
|
+
// below stay in a fixed order so the first problem a user hits is always
|
|
290
|
+
// reported the same way.
|
|
291
|
+
const [depositAccount, shareAccount] = await resolveUserTokenAccountPair({
|
|
292
|
+
connection,
|
|
293
|
+
owner: _publicKey,
|
|
294
|
+
mints: [_depositMint, shareMintAddr],
|
|
295
|
+
});
|
|
296
|
+
// Gate on the balance, not on whether an account exists: an empty leftover
|
|
297
|
+
// account is a real state, and letting it through only trades this error
|
|
298
|
+
// for an insufficient-funds revert the user pays a signature to discover.
|
|
299
|
+
// The balance is already in hand from the lookup, so this costs no extra
|
|
300
|
+
// RPC.
|
|
301
|
+
if (depositAccount.frozen) {
|
|
302
|
+
throw new core_1.UpshiftValidationError('INVALID_INPUT', 'Your token account for the deposit mint is frozen by the token issuer, ' +
|
|
303
|
+
'so the deposit cannot be transferred out of it.');
|
|
304
|
+
}
|
|
305
|
+
if (depositAccount.balance < BigInt(depositAmountRaw.toString())) {
|
|
306
|
+
throw new core_1.UpshiftValidationError('INVALID_INPUT', `Insufficient balance to deposit: this wallet holds ${depositAccount.balance} ` +
|
|
307
|
+
`raw units of the deposit mint, and the deposit requires ${depositAmountRaw.toString()}.`);
|
|
308
|
+
}
|
|
309
|
+
const senderTokenAccount = depositAccount.address;
|
|
310
|
+
// A missing share account is routine — every first-time depositor starts
|
|
311
|
+
// here, as does anyone whose account was closed after redeeming their full
|
|
312
|
+
// balance — so it is created inline rather than rejecting the deposit.
|
|
313
|
+
// Minting into a *frozen* one fails just as transferring out of one does,
|
|
314
|
+
// so this destination needs the same guard as the funding account above.
|
|
315
|
+
if (shareAccount.frozen) {
|
|
316
|
+
throw new core_1.UpshiftValidationError('INVALID_INPUT', 'Your share account is frozen by the token issuer, so it cannot ' +
|
|
317
|
+
'receive the shares this deposit would mint.');
|
|
318
|
+
}
|
|
319
|
+
const senderShareAccount = shareAccount.address;
|
|
320
|
+
const preInstructions = shareAccount.createIx
|
|
321
|
+
? [shareAccount.createIx]
|
|
322
|
+
: [];
|
|
323
|
+
core_1.Logger.log.info('handleSolanaDeposit', 'Sender token account', senderTokenAccount.toBase58());
|
|
324
|
+
core_1.Logger.log.info('handleSolanaDeposit', shareAccount.createIx
|
|
325
|
+
? 'Sender share account (created in this tx)'
|
|
326
|
+
: 'Sender share account (existing)', senderShareAccount.toBase58());
|
|
327
|
+
core_1.Logger.log.info('handleSolanaDeposit', '\n\nDeposit TX Params:\n');
|
|
328
|
+
core_1.Logger.log.info('handleSolanaDeposit', 'Program', depositAmountRaw);
|
|
329
|
+
core_1.Logger.log.info('handleSolanaDeposit', 'Vault state PDA', vaultStatePda.toBase58());
|
|
330
|
+
core_1.Logger.log.info('handleSolanaDeposit', 'Vault token ATA PDA', vaultTokenAtaPda.toBase58());
|
|
331
|
+
core_1.Logger.log.info('handleSolanaDeposit', 'Share mint', shareMintAddr.toBase58());
|
|
332
|
+
core_1.Logger.log.info('handleSolanaDeposit', 'Deposit mint', _depositMint?.toBase58());
|
|
333
|
+
core_1.Logger.log.info('handleSolanaDeposit', 'Signer', _publicKey.toBase58());
|
|
334
|
+
let minSharesOut;
|
|
335
|
+
if (!checked) {
|
|
336
|
+
// The plain instruction takes no floor, so there is nothing to quote.
|
|
337
|
+
}
|
|
338
|
+
else if (options.minSharesOut !== undefined) {
|
|
339
|
+
minSharesOut = options.minSharesOut;
|
|
340
|
+
}
|
|
341
|
+
else {
|
|
342
|
+
// One read the unguarded `deposit` never needed: the vault state (assets
|
|
343
|
+
// and offset) and the share mint (supply) in a single snapshot, since
|
|
344
|
+
// pricing against a mismatched pair is worse than not quoting at all —
|
|
345
|
+
// see `readPricingSnapshot`. A caller already holding a quote can pass
|
|
346
|
+
// `minSharesOut` and skip it.
|
|
347
|
+
const { vaultStateData, supply } = await (0, token_reads_1.readPricingSnapshot)({
|
|
348
|
+
connection,
|
|
349
|
+
program,
|
|
350
|
+
vaultStatePda,
|
|
351
|
+
shareMintAddr,
|
|
352
|
+
});
|
|
353
|
+
minSharesOut = (0, pricing_1.quoteDepositFloor)({
|
|
354
|
+
supply,
|
|
355
|
+
totalAssets: (0, pricing_1.totalAssetsOf)(vaultStateData),
|
|
356
|
+
amount: BigInt(depositAmountRaw.toString()),
|
|
357
|
+
offset: (0, pricing_1.resolveShareOffset)(vaultStateData.shareOffset),
|
|
358
|
+
slippageBps: options.slippageBps ?? pricing_1.DEFAULT_SLIPPAGE_BPS,
|
|
359
|
+
});
|
|
360
|
+
}
|
|
361
|
+
core_1.Logger.log.info('handleSolanaDeposit', 'Min shares out (raw)', minSharesOut?.toString() ?? 'none (plain deposit)');
|
|
362
|
+
const methods = program.methods;
|
|
363
|
+
const tx = await (minSharesOut === undefined
|
|
364
|
+
? methods.deposit(depositAmountRaw)
|
|
365
|
+
: methods.depositChecked(depositAmountRaw, new anchor_1.BN(minSharesOut.toString())))
|
|
366
|
+
.accounts({
|
|
367
|
+
vaultState: vaultStatePda,
|
|
368
|
+
vaultTokenAta: vaultTokenAtaPda,
|
|
369
|
+
senderTokenAccount,
|
|
370
|
+
senderShareAccount,
|
|
371
|
+
shareMint: shareMintAddr,
|
|
372
|
+
depositMint: _depositMint,
|
|
373
|
+
signer: _publicKey,
|
|
374
|
+
tokenProgram: spl_token_1.TOKEN_PROGRAM_ID,
|
|
375
|
+
})
|
|
376
|
+
.preInstructions(preInstructions)
|
|
377
|
+
// `.rpc()` builds, signs, submits and confirms in one step. The extra
|
|
378
|
+
// `getLatestBlockhash` + `confirmTransaction` this module used to run
|
|
379
|
+
// afterwards re-confirmed an already-confirmed signature against a
|
|
380
|
+
// blockhash the transaction never carried.
|
|
381
|
+
//
|
|
382
|
+
// Commitment is taken from the same `Connection` every read in this
|
|
383
|
+
// adapter uses, so a caller who asks for 'finalized' gets it on both
|
|
384
|
+
// sides rather than finalized reads racing confirmed writes.
|
|
385
|
+
.rpc({
|
|
386
|
+
skipPreflight: false,
|
|
387
|
+
preflightCommitment: txCommitment,
|
|
388
|
+
commitment: txCommitment,
|
|
389
|
+
});
|
|
390
|
+
core_1.Logger.log.info('handleSolanaDeposit', 'Deposit successful:', tx);
|
|
391
|
+
return tx;
|
|
392
|
+
}
|
|
393
|
+
catch (e) {
|
|
394
|
+
if (e instanceof core_1.UpshiftSDKError)
|
|
395
|
+
throw e;
|
|
396
|
+
const depositAmountForLog = typeof depositAmount === 'bigint'
|
|
397
|
+
? depositAmount.toString()
|
|
398
|
+
: depositAmount;
|
|
399
|
+
core_1.Logger.log.error('handleSolanaDeposit', e, {
|
|
400
|
+
vaultProgramId: String(vaultProgramId),
|
|
401
|
+
vaultAddress: vaultAddress ? String(vaultAddress) : undefined,
|
|
402
|
+
depositAmount: depositAmountForLog,
|
|
403
|
+
});
|
|
404
|
+
throw new core_1.UpshiftSDKError('UNKNOWN', `Solana deposit failed: ${describeSolanaError(e)}${slippageHint(e)}`, {
|
|
405
|
+
cause: e,
|
|
406
|
+
context: {
|
|
407
|
+
vaultProgramId: String(vaultProgramId),
|
|
408
|
+
vaultAddress: vaultAddress ? String(vaultAddress) : undefined,
|
|
409
|
+
depositAmount: depositAmountForLog,
|
|
410
|
+
},
|
|
411
|
+
});
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
/**
|
|
415
|
+
* Redeem vault shares back into the underlying mint on a Solana Upshift vault.
|
|
416
|
+
*
|
|
417
|
+
* @param redeemShares `bigint` is treated as raw share-token units;
|
|
418
|
+
* `number` is treated as a UI amount.
|
|
419
|
+
* @remarks Sends `redeem_checked`, not `redeem`. Unless `options.minAssetsOut`
|
|
420
|
+
* is given, the payout is quoted from the vault state and share supply the
|
|
421
|
+
* handler already reads, **net of the withdrawal fee**, and that quote
|
|
422
|
+
* lowered by `options.slippageBps` (default {@link DEFAULT_SLIPPAGE_BPS}) is
|
|
423
|
+
* passed as the floor; the program reverts with `SlippageExceeded` below it.
|
|
424
|
+
* An `idl` that predates `redeem_checked` gets the plain `redeem` with no
|
|
425
|
+
* floor, unless `options` asks for one — then it throws
|
|
426
|
+
* {@link UpshiftValidationError}.
|
|
427
|
+
* @throws UpshiftValidationError on missing wallet/programId or invalid amount,
|
|
428
|
+
* or when the wallet holds fewer shares than `redeemShares`.
|
|
429
|
+
* @throws UpshiftSDKError when a candidate token account's balance cannot be
|
|
430
|
+
* read, or wrapping any downstream failure (`cause` preserved).
|
|
431
|
+
* @throws UpshiftTransactionError (`TRANSACTION_UNCONFIRMED`) when the
|
|
432
|
+
* transaction was sent but its outcome could not be established; check
|
|
433
|
+
* `signature` before resubmitting.
|
|
434
|
+
* @remarks **Side effects.** Submits exactly one transaction, signed by the
|
|
435
|
+
* provider's wallet. That transaction also creates the payout account when
|
|
436
|
+
* the wallet has none, and the vault's fee-recipient account when *it* has
|
|
437
|
+
* none — `publicKey` pays rent for both, roughly 0.00204 SOL each and
|
|
438
|
+
* non-refundable, on top of the network fee. Callers should ensure the
|
|
439
|
+
* wallet holds enough SOL for all of it before prompting the user to sign.
|
|
440
|
+
*/
|
|
441
|
+
async function handleSolanaRedeem({ provider, connection, vaultProgramId, vaultAddress, publicKey, redeemShares, idl, options = {}, }) {
|
|
442
|
+
try {
|
|
443
|
+
if (!publicKey)
|
|
444
|
+
throw new core_1.UpshiftValidationError('INVALID_INPUT', 'handleSolanaRedeem: wallet not connected');
|
|
445
|
+
if (redeemShares === undefined ||
|
|
446
|
+
redeemShares === null ||
|
|
447
|
+
(typeof redeemShares === 'bigint' && redeemShares <= 0n) ||
|
|
448
|
+
(typeof redeemShares === 'number' && !(redeemShares > 0)))
|
|
449
|
+
throw new core_1.UpshiftValidationError('INVALID_INPUT', 'handleSolanaRedeem: redeemShares is required and must be > 0');
|
|
450
|
+
if (!vaultProgramId)
|
|
451
|
+
throw new core_1.UpshiftValidationError('INVALID_INPUT', 'handleSolanaRedeem: vaultProgramId is required');
|
|
452
|
+
// Before any RPC: an unusable floor should cost the caller nothing.
|
|
453
|
+
if (options.minAssetsOut !== undefined)
|
|
454
|
+
validateExplicitFloor(options.minAssetsOut, 'minAssetsOut');
|
|
455
|
+
const _vaultProgramId = new web3_js_1.PublicKey(vaultProgramId);
|
|
456
|
+
const program = utils_1.SolanaUtils.getProgram({
|
|
457
|
+
provider,
|
|
458
|
+
idl,
|
|
459
|
+
programId: _vaultProgramId.toBase58(),
|
|
460
|
+
});
|
|
461
|
+
// See `handleSolanaDeposit`: refused before any RPC.
|
|
462
|
+
const checked = sendsCheckedInstruction({
|
|
463
|
+
program,
|
|
464
|
+
checkedMethod: 'redeemChecked',
|
|
465
|
+
floor: options.minAssetsOut,
|
|
466
|
+
slippageBps: options.slippageBps,
|
|
467
|
+
handler: 'handleSolanaRedeem',
|
|
468
|
+
});
|
|
469
|
+
// `getVaultMints` throws a typed `UpshiftSDKError` when the state cannot be
|
|
470
|
+
// read, so both mints are present here.
|
|
471
|
+
const { depositMint, shareMint, vaultVersion } = await utils_1.SolanaUtils.getVaultMints({
|
|
472
|
+
connection,
|
|
473
|
+
vaultProgramId,
|
|
474
|
+
vaultAddress,
|
|
475
|
+
idl,
|
|
476
|
+
});
|
|
477
|
+
const _depositMint = new web3_js_1.PublicKey(depositMint);
|
|
478
|
+
const _publicKey = new web3_js_1.PublicKey(publicKey);
|
|
479
|
+
const vaultStatePda = vaultAddress
|
|
480
|
+
? new web3_js_1.PublicKey(vaultAddress)
|
|
481
|
+
: utils_1.SolanaUtils.deriveVaultStatePda(_vaultProgramId);
|
|
482
|
+
const vaultTokenAtaPda = utils_1.SolanaUtils.deriveVaultTokenAtaPda(_vaultProgramId, _depositMint, vaultVersion);
|
|
483
|
+
// Use on-chain share mint from vault state instead of deriving a PDA,
|
|
484
|
+
// since PDA derivation assumes one share mint per program (single-vault only).
|
|
485
|
+
const shareMintAddr = new web3_js_1.PublicKey(shareMint);
|
|
486
|
+
// Share-mint decimals and supply plus the vault state, from one slot. This
|
|
487
|
+
// replaces a `getParsedAccountInfo` here and a `vaultState.fetch` further
|
|
488
|
+
// down — one round trip rather than two, and supply and assets can no
|
|
489
|
+
// longer be read either side of someone else's deposit.
|
|
490
|
+
const { vaultStateData, supply: shareSupply, shareDecimals, } = await (0, token_reads_1.readPricingSnapshot)({
|
|
491
|
+
connection,
|
|
492
|
+
program,
|
|
493
|
+
vaultStatePda,
|
|
494
|
+
shareMintAddr,
|
|
495
|
+
});
|
|
496
|
+
// A vault with a queue attached accepts a redeem from the queue alone, so
|
|
497
|
+
// a holder's direct redeem can only revert with `WithdrawalQueueRequired`.
|
|
498
|
+
// Say so before the wallet is asked to sign.
|
|
499
|
+
const gate = vaultStateData.withdrawalQueueAuthority;
|
|
500
|
+
if (gate && !new web3_js_1.PublicKey(gate).equals(web3_js_1.PublicKey.default)) {
|
|
501
|
+
throw new core_1.UpshiftValidationError('INVALID_INPUT', 'This vault redeems through its withdrawal queue: request with ' +
|
|
502
|
+
'`vaultRequestRedeem`, then claim the request with `vaultRedeem` and ' +
|
|
503
|
+
'`options.request` once its cooldown has run.');
|
|
504
|
+
}
|
|
505
|
+
let redeemSharesRaw;
|
|
506
|
+
if (typeof redeemShares === 'bigint') {
|
|
507
|
+
redeemSharesRaw = new anchor_1.BN(redeemShares.toString());
|
|
508
|
+
}
|
|
509
|
+
else {
|
|
510
|
+
if (isNaN(redeemShares)) {
|
|
511
|
+
throw new core_1.UpshiftValidationError('INVALID_INPUT', 'Please enter a valid number for the shares to redeem');
|
|
512
|
+
}
|
|
513
|
+
if (redeemShares <= 0) {
|
|
514
|
+
throw new core_1.UpshiftValidationError('INVALID_INPUT', 'Shares to redeem must be greater than 0');
|
|
515
|
+
}
|
|
516
|
+
redeemSharesRaw = (0, utils_1.uiAmountToRawBn)(redeemShares, shareDecimals);
|
|
517
|
+
}
|
|
518
|
+
// See `handleSolanaDeposit` — a positive UI amount can truncate to zero,
|
|
519
|
+
// which would pass the share-balance gate below and burn nothing.
|
|
520
|
+
if (redeemSharesRaw.lten(0)) {
|
|
521
|
+
throw new core_1.UpshiftValidationError('INVALID_INPUT', `Redemption amount is too small: ${redeemShares} is below one raw unit ` +
|
|
522
|
+
`of a ${shareDecimals}-decimal share mint.`);
|
|
523
|
+
}
|
|
524
|
+
// Match whatever this connection *reads* at. A `Connection` built without
|
|
525
|
+
// a commitment omits the parameter, so its reads take the RPC's default —
|
|
526
|
+
// `finalized`. Confirming writes at `confirmed` there would put reads
|
|
527
|
+
// behind writes again, which is the mismatch this whole change removes.
|
|
528
|
+
// `SolanaAdapter` always sets one explicitly, so this fallback only
|
|
529
|
+
// applies to callers reaching these handlers directly via `Solana.actions`.
|
|
530
|
+
const txCommitment = connection.commitment ?? 'finalized';
|
|
531
|
+
// `shareAccount` is burned from; `payoutAccount` receives the underlying.
|
|
532
|
+
// Independent lookups, so they run concurrently — guards stay ordered.
|
|
533
|
+
const [shareAccount, payoutAccount] = await resolveUserTokenAccountPair({
|
|
534
|
+
connection,
|
|
535
|
+
owner: _publicKey,
|
|
536
|
+
mints: [shareMintAddr, _depositMint],
|
|
537
|
+
});
|
|
538
|
+
// Gated on balance for the same reason as the deposit's funding account.
|
|
539
|
+
if (shareAccount.frozen) {
|
|
540
|
+
throw new core_1.UpshiftValidationError('INVALID_INPUT', 'Your share account is frozen by the token issuer, so these shares ' +
|
|
541
|
+
'cannot be redeemed.');
|
|
542
|
+
}
|
|
543
|
+
if (shareAccount.balance < BigInt(redeemSharesRaw.toString())) {
|
|
544
|
+
throw new core_1.UpshiftValidationError('INVALID_INPUT', `Insufficient shares to redeem: this wallet holds ${shareAccount.balance} ` +
|
|
545
|
+
`raw share units, and the redemption requires ${redeemSharesRaw.toString()}.`);
|
|
546
|
+
}
|
|
547
|
+
const senderShareAccount = shareAccount.address;
|
|
548
|
+
// Redeeming is precisely how a holder first receives the deposit mint, so
|
|
549
|
+
// requiring the payout account to pre-exist (as this module used to) left
|
|
550
|
+
// shares unredeemable through this SDK for everyone who acquired them any
|
|
551
|
+
// way other than depositing from this same wallet — a transfer, an
|
|
552
|
+
// airdrop, a market buy — as well as any depositor who later closed their
|
|
553
|
+
// deposit-mint account. A frozen account cannot receive either, so the
|
|
554
|
+
// payout would revert.
|
|
555
|
+
if (payoutAccount.frozen) {
|
|
556
|
+
throw new core_1.UpshiftValidationError('INVALID_INPUT', 'Your token account for the deposit mint is frozen by the token issuer, ' +
|
|
557
|
+
'so it cannot receive the redemption proceeds.');
|
|
558
|
+
}
|
|
559
|
+
const senderTokenAccount = payoutAccount.address;
|
|
560
|
+
// No extra reads here: the share mint (supply) and vault state (assets,
|
|
561
|
+
// offset, fee, fee recipient) came from the snapshot above.
|
|
562
|
+
let minAssetsOut;
|
|
563
|
+
if (!checked) {
|
|
564
|
+
// The plain instruction takes no floor, so there is nothing to quote.
|
|
565
|
+
}
|
|
566
|
+
else if (options.minAssetsOut !== undefined) {
|
|
567
|
+
minAssetsOut = options.minAssetsOut;
|
|
568
|
+
}
|
|
569
|
+
else {
|
|
570
|
+
minAssetsOut = (0, pricing_1.quoteRedeemFloor)({
|
|
571
|
+
supply: shareSupply,
|
|
572
|
+
totalAssets: (0, pricing_1.totalAssetsOf)(vaultStateData),
|
|
573
|
+
shares: BigInt(redeemSharesRaw.toString()),
|
|
574
|
+
offset: (0, pricing_1.resolveShareOffset)(vaultStateData.shareOffset),
|
|
575
|
+
feePpm: (0, pricing_1.toBigInt)(vaultStateData.withdrawalFee, 'withdrawalFee'),
|
|
576
|
+
slippageBps: options.slippageBps ?? pricing_1.DEFAULT_SLIPPAGE_BPS,
|
|
577
|
+
});
|
|
578
|
+
}
|
|
579
|
+
// The fee recipient's token account. Off-curve allowed (as in the queue
|
|
580
|
+
// claim path): a multisig vault or program PDA can be the fee recipient,
|
|
581
|
+
// and the default `allowOwnerOffCurve: false` throws
|
|
582
|
+
// `TokenOwnerOffCurveError` for one, blocking every direct redeem.
|
|
583
|
+
const feeRecipientTokenAccount = (0, spl_token_1.getAssociatedTokenAddressSync)(_depositMint, vaultStateData.feeRecipient, // This is the actual fee recipient from vault state
|
|
584
|
+
true);
|
|
585
|
+
// Every account this handler might have to create is created in the same
|
|
586
|
+
// transaction that uses it. The fee recipient's ATA is created
|
|
587
|
+
// unconditionally: the idempotent instruction is a no-op when the account
|
|
588
|
+
// already exists (costing the user nothing), which buys us one fewer RPC
|
|
589
|
+
// round trip and removes the read-then-create window entirely. The user
|
|
590
|
+
// pays the rent when it is genuinely absent, as before.
|
|
591
|
+
const preInstructions = [
|
|
592
|
+
(0, spl_token_1.createAssociatedTokenAccountIdempotentInstruction)(_publicKey, // payer (user pays for the account creation)
|
|
593
|
+
feeRecipientTokenAccount, // associated token account
|
|
594
|
+
vaultStateData.feeRecipient, // owner (the actual fee recipient)
|
|
595
|
+
_depositMint),
|
|
596
|
+
];
|
|
597
|
+
if (payoutAccount.createIx)
|
|
598
|
+
preInstructions.push(payoutAccount.createIx);
|
|
599
|
+
core_1.Logger.log.info('handleSolanaRedeem', '\n\nRedeem TX Params:\n');
|
|
600
|
+
core_1.Logger.log.info('handleSolanaRedeem', 'Redeem shares (UI):', redeemShares);
|
|
601
|
+
core_1.Logger.log.info('handleSolanaRedeem', 'Redeem shares (raw):', redeemSharesRaw);
|
|
602
|
+
core_1.Logger.log.info('handleSolanaRedeem', 'Share decimals:', shareDecimals);
|
|
603
|
+
core_1.Logger.log.info('handleSolanaRedeem', 'Vault state PDA:', vaultStatePda.toBase58());
|
|
604
|
+
core_1.Logger.log.info('handleSolanaRedeem', 'Vault token ATA PDA:', vaultTokenAtaPda.toBase58());
|
|
605
|
+
core_1.Logger.log.info('handleSolanaRedeem', 'Sender token account:', senderTokenAccount?.toBase58());
|
|
606
|
+
core_1.Logger.log.info('handleSolanaRedeem', 'Sender share account:', senderShareAccount?.toBase58());
|
|
607
|
+
core_1.Logger.log.info('handleSolanaRedeem', 'Fee recipient account:', feeRecipientTokenAccount.toBase58());
|
|
608
|
+
core_1.Logger.log.info('handleSolanaRedeem', 'Share mint:', shareMintAddr.toBase58());
|
|
609
|
+
core_1.Logger.log.info('handleSolanaRedeem', 'Deposit mint:', _depositMint?.toBase58());
|
|
610
|
+
core_1.Logger.log.info('handleSolanaRedeem', 'Signer:', _publicKey.toBase58());
|
|
611
|
+
core_1.Logger.log.info('handleSolanaRedeem', 'Min assets out, net of fee (raw):', minAssetsOut?.toString() ?? 'none (plain redeem)');
|
|
612
|
+
const methods = program.methods;
|
|
613
|
+
const tx = await (minAssetsOut === undefined
|
|
614
|
+
? methods.redeem(redeemSharesRaw)
|
|
615
|
+
: methods.redeemChecked(redeemSharesRaw, new anchor_1.BN(minAssetsOut.toString())))
|
|
616
|
+
.accounts({
|
|
617
|
+
vaultState: vaultStatePda,
|
|
618
|
+
vaultDepositAta: vaultTokenAtaPda,
|
|
619
|
+
senderTokenAccount,
|
|
620
|
+
senderShareAccount,
|
|
621
|
+
feeRecipientAccount: feeRecipientTokenAccount, // The actual fee recipient's token account
|
|
622
|
+
shareMint: shareMintAddr,
|
|
623
|
+
depositMint: _depositMint,
|
|
624
|
+
signer: _publicKey,
|
|
625
|
+
tokenProgram: spl_token_1.TOKEN_PROGRAM_ID,
|
|
626
|
+
})
|
|
627
|
+
.preInstructions(preInstructions)
|
|
628
|
+
// See `handleSolanaDeposit` for why `.rpc()` owns the confirmation and
|
|
629
|
+
// why the commitment comes from the connection.
|
|
630
|
+
.rpc({
|
|
631
|
+
skipPreflight: false,
|
|
632
|
+
preflightCommitment: txCommitment,
|
|
633
|
+
commitment: txCommitment,
|
|
634
|
+
});
|
|
635
|
+
core_1.Logger.log.info('handleSolanaRedeem', 'Redeem successful:', tx);
|
|
636
|
+
return tx;
|
|
637
|
+
}
|
|
638
|
+
catch (e) {
|
|
639
|
+
if (e instanceof core_1.UpshiftSDKError)
|
|
640
|
+
throw e;
|
|
641
|
+
const redeemSharesForLog = typeof redeemShares === 'bigint' ? redeemShares.toString() : redeemShares;
|
|
642
|
+
core_1.Logger.log.error('handleSolanaRedeem', e, {
|
|
643
|
+
vaultProgramId: String(vaultProgramId),
|
|
644
|
+
vaultAddress: vaultAddress ? String(vaultAddress) : undefined,
|
|
645
|
+
redeemShares: redeemSharesForLog,
|
|
646
|
+
});
|
|
647
|
+
throw new core_1.UpshiftSDKError('UNKNOWN', `Solana redeem failed: ${describeSolanaError(e)}${slippageHint(e)}${(0, queue_hints_1.redeemQueueHint)(e)}`, {
|
|
648
|
+
cause: e,
|
|
649
|
+
context: {
|
|
650
|
+
vaultProgramId: String(vaultProgramId),
|
|
651
|
+
vaultAddress: vaultAddress ? String(vaultAddress) : undefined,
|
|
652
|
+
redeemShares: redeemSharesForLog,
|
|
653
|
+
},
|
|
654
|
+
});
|
|
655
|
+
}
|
|
656
|
+
}
|
|
657
|
+
//# sourceMappingURL=vault.actions.js.map
|