@panoptic-eng/sdk 1.0.61 → 1.0.63
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{chainDeployments-CmDzAmZ3.js → chainDeployments-BhXMBZ4B.js} +50 -12
- package/dist/{cow-BItQK2Bi.js → cow-BsSaoahK.js} +1 -1
- package/dist/deployments.d.ts +9 -1
- package/dist/deployments.d.ts.map +1 -1
- package/dist/deployments.js +50 -12
- package/dist/deployments.js.map +1 -1
- package/dist/index.d.ts +541 -45
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +551 -353
- package/dist/index.js.map +1 -1
- package/dist/{irm-BEug2-TH.js → irm-BDlcUNgJ.js} +1 -1
- package/dist/panoptic/v2/index.d.ts +122 -1
- package/dist/panoptic/v2/index.d.ts.map +1 -1
- package/dist/panoptic/v2/index.js +406 -25
- package/dist/panoptic/v2/index.js.map +1 -1
- package/dist/panoptic/v2/react-public.d.ts +122 -1
- package/dist/panoptic/v2/react-public.d.ts.map +1 -1
- package/dist/panoptic/v2/react-public.js +406 -25
- package/dist/panoptic/v2/react-public.js.map +1 -1
- package/dist/providers-1WdiaRbY.js +54 -0
- package/dist/{rates-CPHxWqpT.js → rates-BwZnK0tG.js} +1 -1
- package/dist/{router-BZGHjEcU.js → router-Dejb6MWu.js} +137 -10
- package/dist/{router-kTiatP_R.js → router-pm9PDlyH.js} +2 -2
- package/dist/rpc/index.d.ts +112 -0
- package/dist/rpc/index.d.ts.map +1 -0
- package/dist/rpc/index.js +242 -0
- package/dist/rpc/index.js.map +1 -0
- package/dist/test/index.d.ts +1 -1
- package/dist/test/index.d.ts.map +1 -1
- package/dist/test/index.js +1 -14
- package/dist/test/index.js.map +1 -1
- package/dist/{transactionFees-MocbcAyR.js → transactionFees-aXQlx-lq.js} +1 -1
- package/dist/uniswap/index.d.ts +310 -215
- package/dist/uniswap/index.d.ts.map +1 -1
- package/dist/uniswap/index.js +253 -20
- package/dist/uniswap/index.js.map +1 -1
- package/dist/{v2-odOz_0wM.js → v2-ccGPXWvq.js} +226 -9
- package/dist/vault-transaction-fees.js +49 -11
- package/dist/vault-transaction-fees.js.map +1 -1
- package/dist/{writes-gs0k0uoN.js → writes--fM_8ggw.js} +2 -2
- package/dist/zodiac/index.d.ts.map +1 -1
- package/package.json +5 -1
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
//#region src/rpc/providers.ts
|
|
2
|
+
const alchemyChains = {
|
|
3
|
+
1: "eth-mainnet",
|
|
4
|
+
137: "polygon-mainnet",
|
|
5
|
+
42161: "arb-mainnet",
|
|
6
|
+
8453: "base-mainnet",
|
|
7
|
+
10: "opt-mainnet",
|
|
8
|
+
130: "unichain-mainnet",
|
|
9
|
+
4663: "robinhood-mainnet",
|
|
10
|
+
11155111: "eth-sepolia"
|
|
11
|
+
};
|
|
12
|
+
function isAlchemyChainId(chainId) {
|
|
13
|
+
return chainId in alchemyChains;
|
|
14
|
+
}
|
|
15
|
+
const infuraChains = {
|
|
16
|
+
1: "mainnet",
|
|
17
|
+
10: "optimism-mainnet",
|
|
18
|
+
137: "polygon-mainnet",
|
|
19
|
+
8453: "base-mainnet",
|
|
20
|
+
42161: "arbitrum-mainnet",
|
|
21
|
+
43114: "avalanche-mainnet",
|
|
22
|
+
11155111: "sepolia"
|
|
23
|
+
};
|
|
24
|
+
const RPC_PROVIDER_PRESETS = {
|
|
25
|
+
alchemy: {
|
|
26
|
+
apiKeyEnv: () => ["ALCHEMY_API_KEY"],
|
|
27
|
+
endpoint: (chainId, apiKey) => isAlchemyChainId(chainId) ? { url: `https://${alchemyChains[chainId]}.g.alchemy.com/v2/${apiKey}` } : void 0
|
|
28
|
+
},
|
|
29
|
+
infura: {
|
|
30
|
+
apiKeyEnv: () => ["INFURA_API_KEY"],
|
|
31
|
+
endpoint: (chainId, apiKey) => infuraChains[chainId] ? { url: `https://${infuraChains[chainId]}.infura.io/v3/${apiKey}` } : void 0
|
|
32
|
+
},
|
|
33
|
+
uniblock: {
|
|
34
|
+
apiKeyEnv: () => ["UNIBLOCK_API_KEY"],
|
|
35
|
+
endpoint: (chainId, apiKey) => ({
|
|
36
|
+
url: `https://api.uniblock.dev/uni/v1/json-rpc?chainId=${chainId}`,
|
|
37
|
+
headers: { "x-api-key": apiKey }
|
|
38
|
+
})
|
|
39
|
+
}
|
|
40
|
+
};
|
|
41
|
+
function isRpcProviderName(name) {
|
|
42
|
+
return Object.prototype.hasOwnProperty.call(RPC_PROVIDER_PRESETS, name);
|
|
43
|
+
}
|
|
44
|
+
function getAlchemyRpcUrl(chainId, alchemyApiKey) {
|
|
45
|
+
if (alchemyChains[chainId]) return `https://${alchemyChains[chainId]}.g.alchemy.com/v2/${alchemyApiKey}`;
|
|
46
|
+
else throw new Error(`Unsupported chainId: ${chainId}`);
|
|
47
|
+
}
|
|
48
|
+
function getAlchemyWsRpcUrl(chainId, alchemyApiKey) {
|
|
49
|
+
if (alchemyChains[chainId]) return `wss://${alchemyChains[chainId]}.g.alchemy.com/v2/${alchemyApiKey}`;
|
|
50
|
+
else throw new Error(`Unsupported chainId: ${chainId}`);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
//#endregion
|
|
54
|
+
export { RPC_PROVIDER_PRESETS, getAlchemyRpcUrl, getAlchemyWsRpcUrl, isRpcProviderName };
|
|
@@ -11964,4 +11964,4 @@ function formatPerSecondRateWadAsAprPct(ratePerSecondWad, precision) {
|
|
|
11964
11964
|
}
|
|
11965
11965
|
|
|
11966
11966
|
//#endregion
|
|
11967
|
-
export { AccountInsolventError, AlreadyInitializedError, BatchValidationError, BelowMinimumRedemptionError, CastingError, ChunkHasZeroLiquidityError, ChunkLimitError, CrossPoolError, DEFAULT_MAX_SPREAD, DEFAULT_MINT_BUFFER, DEFAULT_VEGOID, DepositTooLargeError, DuplicateTokenIdError, EffectiveLiquidityAboveThresholdError, ExceedsMaximumRedemptionError, InputListFailError, InsufficientCreditLiquidityError, InvalidBuilderCodeError, InvalidHistoryRangeError, InvalidTickBoundError, InvalidTickError, InvalidTickLimitsError, InvalidTokenIdParameterError, InvalidUniswapCallbackError, LEG_BITS, LEG_LIMITS, LEG_MASKS, LengthMismatchError, LiquidityTooHighError, LoanSlotExhaustedError, MINT_BUFFER, MINT_BUFFER_DENOMINATOR, MaxRetriesExceededError, MissingPositionIdsError, MulticallNoDataError, MulticallResultFailedError, MulticallResultMissingError, NetLiquidityZeroError, NetworkMismatchError, NoLegsExercisableError, NoLoanPositionsError, NotALongLegError, NotBuilderError, NotEnoughLiquidityInChunkError, NotEnoughTokensError, NotGuardianError, NotMarginCalledError, NotPanopticPoolError, OracleRateLimitedError, PanopticError, PanopticHelperNotDeployedError, PanopticValidationError, PoolNotInitializedError, PositionCountNotZeroError, PositionNotOwnedError, PositionSnapshotNotFoundError, PositionTooLargeError, PriceBoundFailError, PriceImpactTooLargeError, ProviderLagError, ReentrancyError, RpcError, RpcResponseError, STANDARD_TICK_WIDTHS, STRIKE_CONVERSION_FACTOR, SafeModeError, StaleDataError, StaleOracleError, StorageDataNotFoundError, SwapTokenMismatchError, SyncTimeoutError, TOKEN_ID_BITS, TokenIdHasZeroLegsError, TooManyLegsOpenError, TransferFailedError, UnauthorizedUniswapCallbackError, UnderOverFlowError, UnhealthyPoolError, UnsafePremiumSettlementError, WrongPoolIdError, WrongUniswapPoolError, ZeroAddressError, ZeroCollateralRequirementError, annualizePerSecondRateWad, applyMintBuffer, applyMintBufferPerToken, availableToBorrow, clearTrackedPositions, collateralTrackerV2Abi, createFileStorage, createMemoryStorage, createTxResult, decodeAllDispatchCalldata, decodeDispatchCalldata, decodeLeftRightSigned, decodeLeftRightUnsigned, decodeOraclePack, decodeOracleRiskParameters, decodeOracleTiming, decodePosition, decodePositionBalance$1 as decodePositionBalance, decodeTickSpacing, diagnoseOracleSafeMode, fetchPoolId, formatPerSecondRateWadAsAprPct, formatPerSecondRateWadAsApyPct, formatRateWad, formatTokenAmount, formatTokenAmountSigned, formatTokenDelta, formatTokenFlow, formatWad, formatWadPercent, formatWadSigned, getBlockMeta, getClosedPositionsKey, getOpenPositionIds, getOracleRiskParameters, getOracleState, getPendingPositionsKey, getPool, getPoolCurrentTick, getPoolMetaKey, getPoolMetadata, getPoolPrefix, getPosition, getPositionGreeks, getPositionMetaKey, getPositions, getPositionsKey, getRiskParameters, getSchemaVersionKey, getSyncCheckpointKey, getTrackedChunksKey, getTrackedPositionIds, getUtilization, isPanopticErrorType, isPositionTracked, jsonSerializer, mintableAfterBuffer, oracleEpochAt, panopticErrorsAbi, panopticFactoryV3Abi, panopticFactoryV4Abi, panopticGuardianAbi, panopticLiquidatorAbi, panopticPoolV2Abi, panopticQueryAbi, parsePanopticError, parseTokenAmount, parseWad, recoverSnapshot, recoverSnapshotFromTx, riskEngineAbi, selectDispatchForAccount, semiFungiblePositionManagerV3Abi, semiFungiblePositionManagerV4Abi, stateViewAbi, submitWrite, uniswapV3PoolAbi, validateBuilderCode };
|
|
11967
|
+
export { AccountInsolventError, AlreadyInitializedError, BatchValidationError, BelowMinimumRedemptionError, CastingError, ChunkHasZeroLiquidityError, ChunkLimitError, CrossPoolError, DEFAULT_MAX_SPREAD, DEFAULT_MINT_BUFFER, DEFAULT_VEGOID, DepositTooLargeError, DuplicateTokenIdError, EffectiveLiquidityAboveThresholdError, ExceedsMaximumRedemptionError, InputListFailError, InsufficientCreditLiquidityError, InvalidBuilderCodeError, InvalidHistoryRangeError, InvalidTickBoundError, InvalidTickError, InvalidTickLimitsError, InvalidTokenIdParameterError, InvalidUniswapCallbackError, LEG_BITS, LEG_LIMITS, LEG_MASKS, LengthMismatchError, LiquidityTooHighError, LoanSlotExhaustedError, MINT_BUFFER, MINT_BUFFER_DENOMINATOR, MaxRetriesExceededError, MissingPositionIdsError, MulticallNoDataError, MulticallResultFailedError, MulticallResultMissingError, NetLiquidityZeroError, NetworkMismatchError, NoLegsExercisableError, NoLoanPositionsError, NotALongLegError, NotBuilderError, NotEnoughLiquidityInChunkError, NotEnoughTokensError, NotGuardianError, NotMarginCalledError, NotPanopticPoolError, OracleRateLimitedError, PanopticError, PanopticHelperNotDeployedError, PanopticValidationError, PoolNotInitializedError, PositionCountNotZeroError, PositionNotOwnedError, PositionSnapshotNotFoundError, PositionTooLargeError, PriceBoundFailError, PriceImpactTooLargeError, ProviderLagError, ReentrancyError, RpcError, RpcResponseError, STANDARD_TICK_WIDTHS, STRIKE_CONVERSION_FACTOR, SafeModeError, StaleDataError, StaleOracleError, StorageDataNotFoundError, SwapTokenMismatchError, SyncTimeoutError, TOKEN_ID_BITS, TokenIdHasZeroLegsError, TooManyLegsOpenError, TransferFailedError, UnauthorizedUniswapCallbackError, UnderOverFlowError, UnhealthyPoolError, UnsafePremiumSettlementError, WrongPoolIdError, WrongUniswapPoolError, ZeroAddressError, ZeroCollateralRequirementError, annualizePerSecondRateWad, applyMintBuffer, applyMintBufferPerToken, availableToBorrow, clearTrackedPositions, collateralTrackerV2Abi, createFileStorage, createMemoryStorage, createTxResult, decodeAllDispatchCalldata, decodeDispatchCalldata, decodeLeftRightSigned, decodeLeftRightUnsigned, decodeOraclePack, decodeOracleRiskParameters, decodeOracleTiming, decodePosition, decodePositionBalance$1 as decodePositionBalance, decodeTickSpacing as decodeTickSpacing$1, diagnoseOracleSafeMode, fetchPoolId, formatPerSecondRateWadAsAprPct, formatPerSecondRateWadAsApyPct, formatRateWad, formatTokenAmount, formatTokenAmountSigned, formatTokenDelta, formatTokenFlow, formatWad, formatWadPercent, formatWadSigned, getBlockMeta, getClosedPositionsKey, getOpenPositionIds, getOracleRiskParameters, getOracleState, getPendingPositionsKey, getPool, getPoolCurrentTick, getPoolMetaKey, getPoolMetadata, getPoolPrefix, getPosition, getPositionGreeks, getPositionMetaKey, getPositions, getPositionsKey, getRiskParameters, getSchemaVersionKey, getSyncCheckpointKey, getTrackedChunksKey, getTrackedPositionIds, getUtilization, isPanopticErrorType, isPositionTracked, jsonSerializer, mintableAfterBuffer, oracleEpochAt, panopticErrorsAbi, panopticFactoryV3Abi, panopticFactoryV4Abi, panopticGuardianAbi, panopticLiquidatorAbi, panopticPoolV2Abi, panopticQueryAbi, parsePanopticError, parseTokenAmount, parseWad, recoverSnapshot, recoverSnapshotFromTx, riskEngineAbi, selectDispatchForAccount, semiFungiblePositionManagerV3Abi, semiFungiblePositionManagerV4Abi, stateViewAbi, submitWrite, uniswapV3PoolAbi, validateBuilderCode };
|
|
@@ -1,10 +1,11 @@
|
|
|
1
|
-
import { PanopticError, getBlockMeta, getPoolMetadata, submitWrite } from "./rates-
|
|
1
|
+
import { PanopticError, getBlockMeta, getPoolMetadata, submitWrite } from "./rates-BwZnK0tG.js";
|
|
2
2
|
import { tickToSqrtPriceX96 } from "./greeks-CNi1-cnp.js";
|
|
3
|
-
import { decodeAllLegs } from "./writes
|
|
3
|
+
import { decodeAllLegs, decodeTokenId } from "./writes--fM_8ggw.js";
|
|
4
4
|
import { BaseError, ContractFunctionRevertedError, encodeAbiParameters, encodeFunctionData, encodePacked, isAddressEqual, zeroAddress } from "viem";
|
|
5
|
+
import Decimal from "decimal.js";
|
|
5
6
|
|
|
6
7
|
//#region src/uniswap/lpDeposit.ts
|
|
7
|
-
const Q96 = 1n << 96n;
|
|
8
|
+
const Q96$1 = 1n << 96n;
|
|
8
9
|
const ceilDiv = (n, d) => (n + d - 1n) / d;
|
|
9
10
|
/** Uniswap mint principal in raw token units, rounded up per leg, plus 5% headroom. */
|
|
10
11
|
function getLpDepositAmounts(ranges, sqrtPriceX96) {
|
|
@@ -20,8 +21,8 @@ function getLpDepositBreakdown(ranges, sqrtPriceX96) {
|
|
|
20
21
|
const lower = tickToSqrtPriceX96(BigInt(tickLower));
|
|
21
22
|
const upper = tickToSqrtPriceX96(BigInt(tickUpper));
|
|
22
23
|
const price = sqrtPriceX96 < lower ? lower : sqrtPriceX96 > upper ? upper : sqrtPriceX96;
|
|
23
|
-
amount0 += ceilDiv(liquidity * Q96 * (upper - price), upper * price);
|
|
24
|
-
amount1 += ceilDiv(liquidity * (price - lower), Q96);
|
|
24
|
+
amount0 += ceilDiv(liquidity * Q96$1 * (upper - price), upper * price);
|
|
25
|
+
amount1 += ceilDiv(liquidity * (price - lower), Q96$1);
|
|
25
26
|
}
|
|
26
27
|
const buffer = {
|
|
27
28
|
amount0: ceilDiv(amount0 * 5n, 100n),
|
|
@@ -49,7 +50,7 @@ function getUnhedgedLpRanges({ tokenId, positionSize, tickSpacing }) {
|
|
|
49
50
|
const lower = tickToSqrtPriceX96(tickLower);
|
|
50
51
|
const upper = tickToSqrtPriceX96(tickUpper);
|
|
51
52
|
const amount = positionSize * leg.optionRatio;
|
|
52
|
-
const liquidity = leg.asset === 0n ? amount * (lower * upper / Q96) / (upper - lower) : amount * Q96 / (upper - lower);
|
|
53
|
+
const liquidity = leg.asset === 0n ? amount * (lower * upper / Q96$1) / (upper - lower) : amount * Q96$1 / (upper - lower);
|
|
53
54
|
return liquidity <= 0n ? [] : [{
|
|
54
55
|
tickLower: Number(tickLower),
|
|
55
56
|
tickUpper: Number(tickUpper),
|
|
@@ -70,7 +71,7 @@ function getLpPositionFunding(params) {
|
|
|
70
71
|
});
|
|
71
72
|
const breakdown = getLpDepositBreakdown(ranges, sqrtPriceX96);
|
|
72
73
|
const priceSquared = valuationSqrtPriceX96 * valuationSqrtPriceX96;
|
|
73
|
-
const value = ({ amount0, amount1 }) => quoteTokenIndex === 0 ? amount0 + ceilDiv(amount1 * Q96 * Q96, priceSquared) : amount1 + ceilDiv(amount0 * priceSquared, Q96 * Q96);
|
|
74
|
+
const value = ({ amount0, amount1 }) => quoteTokenIndex === 0 ? amount0 + ceilDiv(amount1 * Q96$1 * Q96$1, priceSquared) : amount1 + ceilDiv(amount0 * priceSquared, Q96$1 * Q96$1);
|
|
74
75
|
const principalInQuote = value(breakdown.principal);
|
|
75
76
|
const totalInQuote = value(breakdown.total);
|
|
76
77
|
return {
|
|
@@ -100,8 +101,8 @@ function getLpFundingDeposit({ funding, availableInQuote, minimumInQuote = 0n, q
|
|
|
100
101
|
requiredInQuote,
|
|
101
102
|
shortfallInQuote,
|
|
102
103
|
defaultQuotePercent,
|
|
103
|
-
amount0: quoteTokenIndex === 0 ? quoteAmount : ceilDiv(assetValue * Q96 * Q96, priceSquared),
|
|
104
|
-
amount1: quoteTokenIndex === 1 ? quoteAmount : ceilDiv(assetValue * priceSquared, Q96 * Q96)
|
|
104
|
+
amount0: quoteTokenIndex === 0 ? quoteAmount : ceilDiv(assetValue * Q96$1 * Q96$1, priceSquared),
|
|
105
|
+
amount1: quoteTokenIndex === 1 ? quoteAmount : ceilDiv(assetValue * priceSquared, Q96$1 * Q96$1)
|
|
105
106
|
};
|
|
106
107
|
}
|
|
107
108
|
/** Largest raw size funded by available collateral, including native-token rounding. */
|
|
@@ -123,6 +124,132 @@ function getMaxLpPositionSize(params) {
|
|
|
123
124
|
return low;
|
|
124
125
|
}
|
|
125
126
|
|
|
127
|
+
//#endregion
|
|
128
|
+
//#region src/panoptic/v2/greeks/positionVolatility.ts
|
|
129
|
+
const Precision = Decimal.clone({ precision: 80 });
|
|
130
|
+
const Q96 = 1n << 96n;
|
|
131
|
+
const YEAR_SECONDS = new Precision(31536e3);
|
|
132
|
+
/** The premium-free position curve, expressed in one quote token's human units. */
|
|
133
|
+
function preparePositionGamma({ tokenId, positionSize, quoteIsToken0, quoteDecimals }) {
|
|
134
|
+
if (positionSize <= 0n || positionSize >= 1n << 128n) throw new RangeError("Invalid position size");
|
|
135
|
+
if (!Number.isInteger(quoteDecimals) || quoteDecimals < 0 || quoteDecimals > 255) throw new RangeError("Invalid quote decimals");
|
|
136
|
+
const decoded = decodeTokenId(tokenId);
|
|
137
|
+
const chunks = decoded.legs.flatMap((leg) => {
|
|
138
|
+
if (leg.width === 0n) return [];
|
|
139
|
+
const width = leg.width * decoded.tickSpacing;
|
|
140
|
+
const lowerTick = leg.strike - width / 2n;
|
|
141
|
+
const upperTick = leg.strike + (width + 1n) / 2n;
|
|
142
|
+
if (lowerTick < -887272n || upperTick > 887272n || lowerTick >= upperTick) throw new RangeError("Invalid position range");
|
|
143
|
+
const lower = tickToSqrtPriceX96(lowerTick);
|
|
144
|
+
const upper = tickToSqrtPriceX96(upperTick);
|
|
145
|
+
const amount = positionSize * leg.optionRatio;
|
|
146
|
+
const liquidity = leg.asset === 0n ? amount * (lower * upper / Q96) / (upper - lower) : amount * Q96 / (upper - lower);
|
|
147
|
+
if (liquidity >= 1n << 128n) throw new RangeError("Liquidity exceeds uint128");
|
|
148
|
+
return [{
|
|
149
|
+
lowerTick: Number(lowerTick),
|
|
150
|
+
upperTick: Number(upperTick),
|
|
151
|
+
liquidity,
|
|
152
|
+
isLong: leg.isLong
|
|
153
|
+
}];
|
|
154
|
+
});
|
|
155
|
+
const scale = new Precision(10).pow(quoteDecimals);
|
|
156
|
+
const atTick = (tick) => {
|
|
157
|
+
const netLiquidity = chunks.reduce((sum, chunk) => tick >= BigInt(chunk.lowerTick) && tick < BigInt(chunk.upperTick) ? sum + (chunk.isLong ? -chunk.liquidity : chunk.liquidity) : sum, 0n);
|
|
158
|
+
const sqrt = new Precision(tickToSqrtPriceX96(tick).toString()).div(Q96.toString());
|
|
159
|
+
const gamma = new Precision(netLiquidity.toString()).neg().div(2).div(scale);
|
|
160
|
+
return quoteIsToken0 ? gamma.div(sqrt) : gamma.mul(sqrt);
|
|
161
|
+
};
|
|
162
|
+
const inRange = (low, high) => {
|
|
163
|
+
if (low > high) throw new RangeError("Invalid candle range");
|
|
164
|
+
const ticks = new Set([low, high]);
|
|
165
|
+
for (const chunk of chunks) for (const boundary of [BigInt(chunk.lowerTick), BigInt(chunk.upperTick)]) {
|
|
166
|
+
if (boundary >= low && boundary <= high) ticks.add(boundary);
|
|
167
|
+
if (boundary - 1n >= low && boundary - 1n <= high) ticks.add(boundary - 1n);
|
|
168
|
+
}
|
|
169
|
+
return [...ticks].map(atTick);
|
|
170
|
+
};
|
|
171
|
+
return {
|
|
172
|
+
chunks,
|
|
173
|
+
atTick,
|
|
174
|
+
inRange
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
/** Historical whole-position diagnostics, with signs retained independently of eligibility. */
|
|
178
|
+
function calculatePositionVolatilityMetrics({ observations, netPremium, baseFees, quoteDecimals }) {
|
|
179
|
+
if (observations.length < 2) throw new RangeError("At least two price observations are required");
|
|
180
|
+
if (!Number.isInteger(quoteDecimals) || quoteDecimals < 0 || quoteDecimals > 255) throw new RangeError("Invalid quote decimals");
|
|
181
|
+
const points = observations.map((point) => ({
|
|
182
|
+
...point,
|
|
183
|
+
price: new Precision(point.price),
|
|
184
|
+
gamma: new Precision(point.gamma),
|
|
185
|
+
rangeGammas: (point.rangeGammas ?? []).map((gamma) => new Precision(gamma))
|
|
186
|
+
}));
|
|
187
|
+
for (const point of points) if (!point.price.isFinite() || point.price.lte(0) || !point.gamma.isFinite() || point.rangeGammas.some((gamma) => !gamma.isFinite())) throw new RangeError("Invalid price or gamma observation");
|
|
188
|
+
const quantum = new Precision(10).pow(-quoteDecimals);
|
|
189
|
+
const gammas = points.flatMap((point) => [point.gamma, ...point.rangeGammas]);
|
|
190
|
+
const peak = gammas.reduce((max, gamma) => Precision.max(max, gamma.abs()), new Precision(0));
|
|
191
|
+
const tolerance = Precision.max(quantum, peak.mul("1e-12"));
|
|
192
|
+
const positive = gammas.some((gamma) => gamma.gt(tolerance));
|
|
193
|
+
const negative = gammas.some((gamma) => gamma.lt(tolerance.neg()));
|
|
194
|
+
const gammaSign = positive && negative ? "changing" : positive ? "positive" : negative ? "negative" : "zero";
|
|
195
|
+
let exposure = new Precision(0);
|
|
196
|
+
let signedConvexity = new Precision(0);
|
|
197
|
+
let absoluteConvexity = new Precision(0);
|
|
198
|
+
let years = new Precision(0);
|
|
199
|
+
for (let i = 1; i < points.length; i++) {
|
|
200
|
+
const previous = points[i - 1];
|
|
201
|
+
const point = points[i];
|
|
202
|
+
const seconds = point.timestamp - previous.timestamp;
|
|
203
|
+
if (seconds <= 0n) throw new RangeError("Observations must have increasing timestamps");
|
|
204
|
+
const elapsed = new Precision(seconds.toString()).div(YEAR_SECONDS);
|
|
205
|
+
const squaredReturn = point.price.div(previous.price).ln().pow(2);
|
|
206
|
+
exposure = exposure.plus(previous.gamma.abs().mul(elapsed));
|
|
207
|
+
signedConvexity = signedConvexity.plus(previous.gamma.mul(squaredReturn).div(2));
|
|
208
|
+
absoluteConvexity = absoluteConvexity.plus(previous.gamma.abs().mul(squaredReturn).div(2));
|
|
209
|
+
years = years.plus(elapsed);
|
|
210
|
+
}
|
|
211
|
+
const sufficientExposure = exposure.gt(tolerance.mul(years).mul(100));
|
|
212
|
+
const premium = netPremium === null ? null : new Precision(netPremium);
|
|
213
|
+
const fees = baseFees == null ? null : new Precision(baseFees);
|
|
214
|
+
if (premium !== null && !premium.isFinite() || fees !== null && !fees.isFinite()) throw new RangeError("Invalid accounting amount");
|
|
215
|
+
const consistent = (amount) => gammaSign === "positive" ? amount.lte(quantum) : gammaSign === "negative" && amount.gte(quantum.neg());
|
|
216
|
+
const reason = !sufficientExposure ? "insufficient-exposure" : gammaSign === "changing" ? "changing-sign" : premium === null ? "premium-unavailable" : !consistent(premium) ? "inconsistent-premium-direction" : null;
|
|
217
|
+
const equivalent = (amount) => amount.abs().mul(2).div(exposure).sqrt().toString();
|
|
218
|
+
const coverage = (amount) => absoluteConvexity.gt(quantum) ? amount.abs().div(absoluteConvexity).toString() : null;
|
|
219
|
+
const eligibleFees = sufficientExposure && fees !== null && consistent(fees);
|
|
220
|
+
return {
|
|
221
|
+
gammaSign,
|
|
222
|
+
exposure: exposure.toString(),
|
|
223
|
+
signedConvexity: signedConvexity.toString(),
|
|
224
|
+
absoluteConvexity: absoluteConvexity.toString(),
|
|
225
|
+
netPremium: premium?.toString() ?? null,
|
|
226
|
+
estimatedHedgedResult: premium?.plus(signedConvexity).toString() ?? null,
|
|
227
|
+
weightedRealizedVolatility: sufficientExposure ? equivalent(absoluteConvexity) : null,
|
|
228
|
+
premiumEquivalentVolatility: reason === null && premium !== null ? equivalent(premium) : null,
|
|
229
|
+
premiumToConvexity: reason === null && premium !== null ? coverage(premium) : null,
|
|
230
|
+
baseFees: fees?.toString() ?? null,
|
|
231
|
+
feeEquivalentVolatility: eligibleFees ? equivalent(fees) : null,
|
|
232
|
+
feeToConvexity: eligibleFees ? coverage(fees) : null,
|
|
233
|
+
comparisonReason: reason
|
|
234
|
+
};
|
|
235
|
+
}
|
|
236
|
+
/** Quote-value signed cumulative token increments, excluding revaluation of previous accrual. */
|
|
237
|
+
function valuePositionAccrual({ snapshots, quoteIsToken0, quoteDecimals }) {
|
|
238
|
+
let previous0 = 0n;
|
|
239
|
+
let previous1 = 0n;
|
|
240
|
+
let total = new Precision(0);
|
|
241
|
+
for (const snapshot of snapshots) {
|
|
242
|
+
const amount0 = new Precision((snapshot.token0 - previous0).toString());
|
|
243
|
+
const amount1 = new Precision((snapshot.token1 - previous1).toString());
|
|
244
|
+
if (snapshot.sqrtPriceX96 !== void 0 && snapshot.sqrtPriceX96 <= 0n) throw new RangeError("Invalid sqrt price");
|
|
245
|
+
const price = snapshot.sqrtPriceX96 === void 0 ? new Precision("1.0001").pow(snapshot.tick.toString()) : new Precision(snapshot.sqrtPriceX96.toString()).div(Q96.toString()).pow(2);
|
|
246
|
+
total = total.plus(quoteIsToken0 ? amount0.plus(amount1.div(price)) : amount1.plus(amount0.mul(price)));
|
|
247
|
+
previous0 = snapshot.token0;
|
|
248
|
+
previous1 = snapshot.token1;
|
|
249
|
+
}
|
|
250
|
+
return total.div(new Precision(10).pow(quoteDecimals));
|
|
251
|
+
}
|
|
252
|
+
|
|
126
253
|
//#endregion
|
|
127
254
|
//#region src/uniswap/v4/router/errors.ts
|
|
128
255
|
/**
|
|
@@ -843,4 +970,4 @@ async function swapExactOutViaV3Router(params) {
|
|
|
843
970
|
}
|
|
844
971
|
|
|
845
972
|
//#endregion
|
|
846
|
-
export { AmountExceedsUint128Error, InvalidSwapTokenError, MSG_SENDER, MissingSweepRecipientError, PERMIT2_ADDRESS, QuoterUnavailableError, UNISWAP_V3_ADDRESSES, UNISWAP_V4_ADDRESSES, UnsupportedChainError, V3_SWAP_EXACT_IN, V3_SWAP_EXACT_OUT, buildV3ExactOutSwapExecuteArgs, buildV3ExactOutSwapExecuteCalldata, buildV3SwapExecuteArgs, buildV3SwapExecuteCalldata, encodeV3Path, getLpDepositAmounts, getLpDepositBreakdown, getLpFundingDeposit, getLpPositionFunding, getMaxLpPositionSize, getUnhedgedLpRanges, getUniswapV3Addresses, getUniswapV4Addresses, quoteSwapExactInViaV3Router, quoteSwapExactOutViaV3Router, quoteV3ExactIn, quoteV3ExactOut, resolveV3SwapRoute, swapExactInViaV3Router, swapExactOutViaV3Router, universalRouterAbi };
|
|
973
|
+
export { AmountExceedsUint128Error, InvalidSwapTokenError, MSG_SENDER, MissingSweepRecipientError, PERMIT2_ADDRESS, QuoterUnavailableError, UNISWAP_V3_ADDRESSES, UNISWAP_V4_ADDRESSES, UnsupportedChainError, V3_SWAP_EXACT_IN, V3_SWAP_EXACT_OUT, buildV3ExactOutSwapExecuteArgs, buildV3ExactOutSwapExecuteCalldata, buildV3SwapExecuteArgs, buildV3SwapExecuteCalldata, calculatePositionVolatilityMetrics, encodeV3Path, getLpDepositAmounts, getLpDepositBreakdown, getLpFundingDeposit, getLpPositionFunding, getMaxLpPositionSize, getUnhedgedLpRanges, getUniswapV3Addresses, getUniswapV4Addresses, preparePositionGamma, quoteSwapExactInViaV3Router, quoteSwapExactOutViaV3Router, quoteV3ExactIn, quoteV3ExactOut, resolveV3SwapRoute, swapExactInViaV3Router, swapExactOutViaV3Router, universalRouterAbi, valuePositionAccrual };
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { PanopticError, getBlockMeta, getPool, submitWrite } from "./rates-
|
|
2
|
-
import { AmountExceedsUint128Error, InvalidSwapTokenError, MissingSweepRecipientError, QuoterUnavailableError, getUniswapV4Addresses, universalRouterAbi } from "./router-
|
|
1
|
+
import { PanopticError, getBlockMeta, getPool, submitWrite } from "./rates-BwZnK0tG.js";
|
|
2
|
+
import { AmountExceedsUint128Error, InvalidSwapTokenError, MissingSweepRecipientError, QuoterUnavailableError, getUniswapV4Addresses, universalRouterAbi } from "./router-Dejb6MWu.js";
|
|
3
3
|
import { BaseError, ContractFunctionRevertedError, encodeAbiParameters, encodeFunctionData, encodePacked, erc20Abi, isAddressEqual, maxUint256, zeroAddress } from "viem";
|
|
4
4
|
|
|
5
5
|
//#region src/uniswap/v4/router/encodeSwap.ts
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import * as viem0 from "viem";
|
|
2
|
+
import * as viem1 from "viem";
|
|
3
|
+
import { HttpTransportConfig, Transport } from "viem";
|
|
4
|
+
|
|
5
|
+
//#region src/rpc/providers.d.ts
|
|
6
|
+
/**
|
|
7
|
+
* The only place that knows RPC vendor URL formats and credentials. Supporting
|
|
8
|
+
* a new vendor means adding one preset here; services select it through env
|
|
9
|
+
* (see `resolveRpcEndpoints`).
|
|
10
|
+
*/
|
|
11
|
+
/**
|
|
12
|
+
* The only place that knows RPC vendor URL formats and credentials. Supporting
|
|
13
|
+
* a new vendor means adding one preset here; services select it through env
|
|
14
|
+
* (see `resolveRpcEndpoints`).
|
|
15
|
+
*/
|
|
16
|
+
type RpcProviderPreset = {
|
|
17
|
+
/** Env vars holding the API key for `chainId`, highest precedence first. */
|
|
18
|
+
apiKeyEnv: (chainId: number) => readonly string[];
|
|
19
|
+
/** Endpoint for `chainId`, or undefined when the vendor does not serve it. */
|
|
20
|
+
endpoint: (chainId: number, apiKey: string) => {
|
|
21
|
+
url: string;
|
|
22
|
+
headers?: Record<string, string>;
|
|
23
|
+
} | undefined;
|
|
24
|
+
};
|
|
25
|
+
declare const alchemyChains: {
|
|
26
|
+
readonly 1: "eth-mainnet";
|
|
27
|
+
readonly 137: "polygon-mainnet";
|
|
28
|
+
readonly 42161: "arb-mainnet";
|
|
29
|
+
readonly 8453: "base-mainnet";
|
|
30
|
+
readonly 10: "opt-mainnet";
|
|
31
|
+
readonly 130: "unichain-mainnet";
|
|
32
|
+
readonly 4663: "robinhood-mainnet";
|
|
33
|
+
readonly 11155111: "eth-sepolia";
|
|
34
|
+
};
|
|
35
|
+
type AlchemyChainId = keyof typeof alchemyChains;
|
|
36
|
+
type RpcProviderName = 'alchemy' | 'infura' | 'uniblock';
|
|
37
|
+
declare const RPC_PROVIDER_PRESETS: Readonly<Record<RpcProviderName, RpcProviderPreset>>;
|
|
38
|
+
declare function getAlchemyRpcUrl(chainId: AlchemyChainId, alchemyApiKey: string): string;
|
|
39
|
+
declare function getAlchemyWsRpcUrl(chainId: AlchemyChainId, alchemyApiKey: string): string; //#endregion
|
|
40
|
+
//#region src/rpc/redact.d.ts
|
|
41
|
+
/**
|
|
42
|
+
* Strip RPC credentials from text before it is logged or shown. Providers
|
|
43
|
+
* embed keys in the URL path or query, so every URL keeps only its origin;
|
|
44
|
+
* credential header values are masked too.
|
|
45
|
+
*/
|
|
46
|
+
declare function redactRpcSecrets(text: string | undefined): string;
|
|
47
|
+
|
|
48
|
+
//#endregion
|
|
49
|
+
//#region src/rpc/resolve.d.ts
|
|
50
|
+
type RpcEnv = Readonly<Record<string, string | undefined>>;
|
|
51
|
+
type RpcEndpoint = {
|
|
52
|
+
/** Preset name, or `'url'` for an explicit URL entry. */
|
|
53
|
+
source: RpcProviderName | 'url';
|
|
54
|
+
url: string;
|
|
55
|
+
/** Credentials that must accompany every request to `url`, and only to `url`. */
|
|
56
|
+
headers: Record<string, string>;
|
|
57
|
+
};
|
|
58
|
+
type ResolveRpcEndpointsOptions = {
|
|
59
|
+
/** Service-specific URL variables honoured when `RPC_URL_<chainId>` is unset, in order. */
|
|
60
|
+
legacyUrlEnv?: readonly string[];
|
|
61
|
+
/** Presets tried, in order, when no URL is configured. Overridden by `RPC_PROVIDERS`. */
|
|
62
|
+
defaultProviders?: readonly RpcProviderName[];
|
|
63
|
+
};
|
|
64
|
+
declare const DEFAULT_RPC_PROVIDERS: readonly RpcProviderName[];
|
|
65
|
+
/**
|
|
66
|
+
* Ordered RPC endpoints for `chainId` (primary first, the rest failover):
|
|
67
|
+
*
|
|
68
|
+
* 1. `RPC_URL_<chainId>` — comma-separated URLs and/or preset names; entry `n`
|
|
69
|
+
* gets headers from `RPC_HEADERS_<chainId>_<n>` ("Name: value;Name: value").
|
|
70
|
+
* 2. Otherwise the service's legacy URL variables (`legacyUrlEnv`).
|
|
71
|
+
* 3. Otherwise the first preset in `RPC_PROVIDERS` (or `defaultProviders`)
|
|
72
|
+
* that has an API key and serves the chain.
|
|
73
|
+
*
|
|
74
|
+
* Blank values count as unset. Throws when nothing resolves.
|
|
75
|
+
*/
|
|
76
|
+
declare function resolveRpcEndpoints(chainId: number, env: RpcEnv, options?: ResolveRpcEndpointsOptions): RpcEndpoint[];
|
|
77
|
+
|
|
78
|
+
//#endregion
|
|
79
|
+
//#region src/rpc/transport.d.ts
|
|
80
|
+
type RpcHttpConfig = Omit<HttpTransportConfig, 'fetchOptions'>;
|
|
81
|
+
/** viem HTTP transport for one endpoint, carrying its credential headers. */
|
|
82
|
+
declare function rpcHttp(endpoint: RpcEndpoint, config?: RpcHttpConfig): viem1.HttpTransport<viem0.RpcSchema | undefined, boolean>;
|
|
83
|
+
type RpcTransportOptions = RpcHttpConfig & {
|
|
84
|
+
/**
|
|
85
|
+
* Fail over to later endpoints. Disable for clients that submit
|
|
86
|
+
* transactions: a tx broadcast through one provider and retried through
|
|
87
|
+
* another surfaces as "already known" / "nonce too low" although it was sent.
|
|
88
|
+
*/
|
|
89
|
+
failover?: boolean;
|
|
90
|
+
};
|
|
91
|
+
/**
|
|
92
|
+
* Transport over resolved endpoints: plain HTTP for one endpoint, otherwise an
|
|
93
|
+
* unranked viem `fallback` in configured order.
|
|
94
|
+
*/
|
|
95
|
+
declare function rpcTransport(endpoints: readonly RpcEndpoint[], {
|
|
96
|
+
failover,
|
|
97
|
+
...config
|
|
98
|
+
}?: RpcTransportOptions): Transport;
|
|
99
|
+
/** anvil fork arguments for the primary endpoint (`--fork-header` only when it needs one). */
|
|
100
|
+
declare function rpcForkArgs(endpoints: readonly RpcEndpoint[]): {
|
|
101
|
+
forkUrl: string;
|
|
102
|
+
forkHeader?: string;
|
|
103
|
+
};
|
|
104
|
+
/**
|
|
105
|
+
* Value for a Ponder chain's `rpc`. Header-free endpoints stay plain URLs so
|
|
106
|
+
* Ponder keeps its own load balancing; credential headers need a transport.
|
|
107
|
+
*/
|
|
108
|
+
declare function ponderRpc(endpoints: readonly RpcEndpoint[]): string | string[] | Transport;
|
|
109
|
+
|
|
110
|
+
//#endregion
|
|
111
|
+
export { AlchemyChainId, DEFAULT_RPC_PROVIDERS, RPC_PROVIDER_PRESETS, ResolveRpcEndpointsOptions, RpcEndpoint, RpcEnv, RpcHttpConfig, RpcProviderName, RpcProviderPreset, RpcTransportOptions, getAlchemyRpcUrl, getAlchemyWsRpcUrl, ponderRpc, redactRpcSecrets, resolveRpcEndpoints, rpcForkArgs, rpcHttp, rpcTransport };
|
|
112
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","names":["RpcProviderPreset","Record","alchemyChains","AlchemyChainId","RpcProviderName","RPC_PROVIDER_PRESETS","Readonly","isRpcProviderName","getAlchemyRpcUrl","getAlchemyWsRpcUrl","redactRpcSecrets","RpcProviderName","RpcEnv","Record","Readonly","RpcEndpoint","ResolveRpcEndpointsOptions","DEFAULT_RPC_PROVIDERS","resolveRpcEndpoints","viem1","HttpTransportConfig","Transport","RpcEndpoint","RpcHttpConfig","Omit","rpcHttp","viem0","RpcSchema","HttpTransport","RpcTransportOptions","rpcTransport","failover","rpcForkArgs","ponderRpc"],"sources":["../../src/rpc/providers.d.ts","../../src/rpc/redact.d.ts","../../src/rpc/resolve.d.ts","../../src/rpc/transport.d.ts"],"sourcesContent":null,"mappings":";;;;;;;;;;;;;;;KAKYA,iBAAAA;;;;;IAAAA,GAAAA,EAAAA,MAAAA;IASEE,OAAAA,CAAAA,EAHIW,MAYjB,CAAA,MAAA,EAAA,MAAA,CAAA;EACWV,CAAAA,GAAAA,SAAAA;AACZ,CAAA;AACA,cAZcD,aAYiF,EAAA;EAAA,SAAA,CAAA,EAAA,aAAA;EAAA,SAApCS,GAAAA,EAAAA,iBAAAA;EAAe,SAAEX,KAAAA,EAAAA,aAAAA;EAAiB,SAAzCa,IAAAA,EAAAA,cAAAA;EAAM,SAAfC,EAAAA,EAAAA,aAAAA;EAAQ,SAAA,GAAA,EAAA,kBAAA;EAE3BN,SAAAA,IAAAA,EAAAA,mBAA0BL;EAC1BM,SAAAA,QAAAA,EAAAA,aAA4BN;;KALxCA,cAAAA,gBAA8BD;KAC9BS,eAAAA;cACSN,sBAAsBS,SAASD,OAAOF,iBAAiBX;iBAEpDQ,gBAAAA,UAA0BL;iBAC1BM,kBAAAA,UAA4BN;;;;;;;iBCxB5BO,gBAAAA;;;;KCJZE,MAAAA,GAASE,SAASD;KAClBS,WAAAA;;UAEAX;;;WAGCE;;KAEDG,0BAAAA;EFJAhB;EASEE,YAAAA,CAAAA,EAAAA,SASb,MAAA,EAAA;EACWC;EACAQ,gBAAAA,CAAAA,EAAe,SEZKA,eFYL,EAAA;AAC3B,CAAA;AAA+F,cEX1EM,qBFW0E,EAAA,SEX1CN,eFW0C,EAAA;;;;;AAA5C;AAEnD;AACA;;;;;iBEFwBO,mBAAAA,uBAA0CN,kBAAkBI,6BAA6BM;;;;KCzBrGC,aAAAA,GAAgBC,KAAKJ;;iBAETK,OAAAA,WAAkBH,sBAAsBC,gBAAqEJ,KAAAA,CAAtCS,cAAlBF,KAAAA,CAA+CC,SAAAA;KAChHE,mBAAAA,GAAsBN;;;;;AHAlC;EAScrB,QAAAA,CAAAA,EAAAA,OASb;AACD,CAAA;AACA;AACA;;;AAA4EF,iBGTpD8B,YAAAA,CHSoD9B,SAAAA,EAAAA,SGTnBsB,WHSmBtB,EAAAA,EAAAA;EAAAA,QAAAA;EAAAA,GAAAA;AAAAA,CAAAA,CAAAA,EGTsB6B,mBHStB7B,CAAAA,EGT4CqB,SHS5CrB;;AAAjCc,iBGPnBkB,WAAAA,CHOmBlB,SAAAA,EAAAA,SGPaQ,WHObR,EAAAA,CAAAA,EAAAA;EAAQ,OAAA,EAAA,MAAA;EAE3BN,UAAAA,CAAAA,EAAAA,MAAgB;AACxC,CAAA;;;;;iBGFwByB,SAAAA,qBAA8BX,oCAAoCD"}
|
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
import { fallback, http } from "viem";
|
|
2
|
+
|
|
3
|
+
//#region src/rpc/providers.ts
|
|
4
|
+
const alchemyChains = {
|
|
5
|
+
1: "eth-mainnet",
|
|
6
|
+
137: "polygon-mainnet",
|
|
7
|
+
42161: "arb-mainnet",
|
|
8
|
+
8453: "base-mainnet",
|
|
9
|
+
10: "opt-mainnet",
|
|
10
|
+
130: "unichain-mainnet",
|
|
11
|
+
4663: "robinhood-mainnet",
|
|
12
|
+
11155111: "eth-sepolia"
|
|
13
|
+
};
|
|
14
|
+
function isAlchemyChainId(chainId) {
|
|
15
|
+
return chainId in alchemyChains;
|
|
16
|
+
}
|
|
17
|
+
const infuraChains = {
|
|
18
|
+
1: "mainnet",
|
|
19
|
+
10: "optimism-mainnet",
|
|
20
|
+
137: "polygon-mainnet",
|
|
21
|
+
8453: "base-mainnet",
|
|
22
|
+
42161: "arbitrum-mainnet",
|
|
23
|
+
43114: "avalanche-mainnet",
|
|
24
|
+
11155111: "sepolia"
|
|
25
|
+
};
|
|
26
|
+
const RPC_PROVIDER_PRESETS = {
|
|
27
|
+
alchemy: {
|
|
28
|
+
apiKeyEnv: () => ["ALCHEMY_API_KEY"],
|
|
29
|
+
endpoint: (chainId, apiKey) => isAlchemyChainId(chainId) ? { url: `https://${alchemyChains[chainId]}.g.alchemy.com/v2/${apiKey}` } : void 0
|
|
30
|
+
},
|
|
31
|
+
infura: {
|
|
32
|
+
apiKeyEnv: () => ["INFURA_API_KEY"],
|
|
33
|
+
endpoint: (chainId, apiKey) => infuraChains[chainId] ? { url: `https://${infuraChains[chainId]}.infura.io/v3/${apiKey}` } : void 0
|
|
34
|
+
},
|
|
35
|
+
uniblock: {
|
|
36
|
+
apiKeyEnv: () => ["UNIBLOCK_API_KEY"],
|
|
37
|
+
endpoint: (chainId, apiKey) => ({
|
|
38
|
+
url: `https://api.uniblock.dev/uni/v1/json-rpc?chainId=${chainId}`,
|
|
39
|
+
headers: { "x-api-key": apiKey }
|
|
40
|
+
})
|
|
41
|
+
}
|
|
42
|
+
};
|
|
43
|
+
function isRpcProviderName(name) {
|
|
44
|
+
return Object.prototype.hasOwnProperty.call(RPC_PROVIDER_PRESETS, name);
|
|
45
|
+
}
|
|
46
|
+
function getAlchemyRpcUrl(chainId, alchemyApiKey) {
|
|
47
|
+
if (alchemyChains[chainId]) return `https://${alchemyChains[chainId]}.g.alchemy.com/v2/${alchemyApiKey}`;
|
|
48
|
+
else throw new Error(`Unsupported chainId: ${chainId}`);
|
|
49
|
+
}
|
|
50
|
+
function getAlchemyWsRpcUrl(chainId, alchemyApiKey) {
|
|
51
|
+
if (alchemyChains[chainId]) return `wss://${alchemyChains[chainId]}.g.alchemy.com/v2/${alchemyApiKey}`;
|
|
52
|
+
else throw new Error(`Unsupported chainId: ${chainId}`);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
//#endregion
|
|
56
|
+
//#region src/rpc/redact.ts
|
|
57
|
+
const URL_PATTERN = /\b(?:https?|wss?):\/\/[^\s"'<>)]+/giu;
|
|
58
|
+
const CREDENTIAL_HEADER_PATTERN = /\b(x-api-key|authorization|proxy-authorization)(["']?\s*[:=]\s*)(?:(["'])(?:\\.|(?!\3)[^\\\r\n])*\3|[^\r\n]+)/giu;
|
|
59
|
+
function redactUrl(raw) {
|
|
60
|
+
try {
|
|
61
|
+
const url = new URL(raw);
|
|
62
|
+
const port = url.port ? `:${url.port}` : "";
|
|
63
|
+
return `${url.protocol}//${url.hostname}${port}/<redacted>`;
|
|
64
|
+
} catch {
|
|
65
|
+
return "<redacted-url>";
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Strip RPC credentials from text before it is logged or shown. Providers
|
|
70
|
+
* embed keys in the URL path or query, so every URL keeps only its origin;
|
|
71
|
+
* credential header values are masked too.
|
|
72
|
+
*/
|
|
73
|
+
function redactRpcSecrets(text) {
|
|
74
|
+
if (!text) return "";
|
|
75
|
+
return text.replace(CREDENTIAL_HEADER_PATTERN, "$1$2$3<redacted>$3").replace(URL_PATTERN, redactUrl);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
//#endregion
|
|
79
|
+
//#region src/rpc/resolve.ts
|
|
80
|
+
const DEFAULT_RPC_PROVIDERS = ["alchemy", "uniblock"];
|
|
81
|
+
function present(value) {
|
|
82
|
+
const trimmed = value?.trim();
|
|
83
|
+
return trimmed ? trimmed : void 0;
|
|
84
|
+
}
|
|
85
|
+
function splitList(value) {
|
|
86
|
+
return (value ?? "").split(",").map((entry) => entry.trim()).filter((entry) => entry !== "");
|
|
87
|
+
}
|
|
88
|
+
function parseHeaders(value, envName) {
|
|
89
|
+
const headers = {};
|
|
90
|
+
for (const pair of splitHeaderPairs(value)) {
|
|
91
|
+
const separator = pair.indexOf(":");
|
|
92
|
+
const name = pair.slice(0, separator).trim();
|
|
93
|
+
if (separator <= 0 || name === "") throw new Error(`${envName} must be "Name: value" pairs separated by ";"`);
|
|
94
|
+
headers[name] = pair.slice(separator + 1).trim();
|
|
95
|
+
}
|
|
96
|
+
return headers;
|
|
97
|
+
}
|
|
98
|
+
function splitHeaderPairs(value) {
|
|
99
|
+
return (value ?? "").split(";").map((pair) => pair.trim()).filter((pair) => pair !== "");
|
|
100
|
+
}
|
|
101
|
+
function isHttpUrl(value) {
|
|
102
|
+
try {
|
|
103
|
+
const { protocol } = new URL(value);
|
|
104
|
+
return protocol === "http:" || protocol === "https:";
|
|
105
|
+
} catch {
|
|
106
|
+
return false;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
function presetApiKey(name, chainId, env) {
|
|
110
|
+
for (const envName of RPC_PROVIDER_PRESETS[name].apiKeyEnv(chainId)) {
|
|
111
|
+
const key = present(env[envName]);
|
|
112
|
+
if (key !== void 0) return key;
|
|
113
|
+
}
|
|
114
|
+
return void 0;
|
|
115
|
+
}
|
|
116
|
+
function presetEndpoint(name, chainId, env) {
|
|
117
|
+
const apiKey = presetApiKey(name, chainId, env);
|
|
118
|
+
if (apiKey === void 0) return void 0;
|
|
119
|
+
const endpoint = RPC_PROVIDER_PRESETS[name].endpoint(chainId, apiKey);
|
|
120
|
+
return endpoint && {
|
|
121
|
+
source: name,
|
|
122
|
+
url: endpoint.url,
|
|
123
|
+
headers: endpoint.headers ?? {}
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
function explicitEndpoints(chainId, env) {
|
|
127
|
+
const listEnv = `RPC_URL_${chainId}`;
|
|
128
|
+
return splitList(env[listEnv]).map((entry, index) => {
|
|
129
|
+
if (isHttpUrl(entry)) {
|
|
130
|
+
const headersEnv = `RPC_HEADERS_${chainId}_${index}`;
|
|
131
|
+
return {
|
|
132
|
+
source: "url",
|
|
133
|
+
url: entry,
|
|
134
|
+
headers: parseHeaders(env[headersEnv], headersEnv)
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
if (!isRpcProviderName(entry)) throw new Error(`${listEnv} entry "${entry}" is neither an http(s) URL nor a known provider`);
|
|
138
|
+
const endpoint = presetEndpoint(entry, chainId, env);
|
|
139
|
+
if (endpoint === void 0) {
|
|
140
|
+
const keyEnv = RPC_PROVIDER_PRESETS[entry].apiKeyEnv(chainId).join(" or ");
|
|
141
|
+
throw new Error(`${listEnv} lists "${entry}", which needs ${keyEnv} and must serve chain ${chainId}`);
|
|
142
|
+
}
|
|
143
|
+
return endpoint;
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
function legacyEndpoints(env, legacyUrlEnv) {
|
|
147
|
+
return legacyUrlEnv.flatMap((envName) => splitList(env[envName]).map((url) => ({
|
|
148
|
+
source: "url",
|
|
149
|
+
url,
|
|
150
|
+
headers: {}
|
|
151
|
+
})));
|
|
152
|
+
}
|
|
153
|
+
function firstPresetEndpoint(chainId, env, providers) {
|
|
154
|
+
for (const name of providers) {
|
|
155
|
+
if (!isRpcProviderName(name)) throw new Error(`RPC_PROVIDERS entry "${name}" is not a known provider`);
|
|
156
|
+
const endpoint = presetEndpoint(name, chainId, env);
|
|
157
|
+
if (endpoint !== void 0) return endpoint;
|
|
158
|
+
}
|
|
159
|
+
return void 0;
|
|
160
|
+
}
|
|
161
|
+
function dedupe(endpoints) {
|
|
162
|
+
const seen = new Set();
|
|
163
|
+
return endpoints.filter((endpoint) => {
|
|
164
|
+
const key = `${endpoint.url}\n${JSON.stringify(endpoint.headers)}`;
|
|
165
|
+
if (seen.has(key)) return false;
|
|
166
|
+
seen.add(key);
|
|
167
|
+
return true;
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
/**
|
|
171
|
+
* Ordered RPC endpoints for `chainId` (primary first, the rest failover):
|
|
172
|
+
*
|
|
173
|
+
* 1. `RPC_URL_<chainId>` — comma-separated URLs and/or preset names; entry `n`
|
|
174
|
+
* gets headers from `RPC_HEADERS_<chainId>_<n>` ("Name: value;Name: value").
|
|
175
|
+
* 2. Otherwise the service's legacy URL variables (`legacyUrlEnv`).
|
|
176
|
+
* 3. Otherwise the first preset in `RPC_PROVIDERS` (or `defaultProviders`)
|
|
177
|
+
* that has an API key and serves the chain.
|
|
178
|
+
*
|
|
179
|
+
* Blank values count as unset. Throws when nothing resolves.
|
|
180
|
+
*/
|
|
181
|
+
function resolveRpcEndpoints(chainId, env, options = {}) {
|
|
182
|
+
const explicit = explicitEndpoints(chainId, env);
|
|
183
|
+
if (explicit.length > 0) return dedupe(explicit);
|
|
184
|
+
const legacyUrlEnv = options.legacyUrlEnv ?? [];
|
|
185
|
+
const legacy = legacyEndpoints(env, legacyUrlEnv);
|
|
186
|
+
if (legacy.length > 0) return dedupe(legacy);
|
|
187
|
+
const providers = present(env.RPC_PROVIDERS) ? splitList(env.RPC_PROVIDERS) : options.defaultProviders ?? DEFAULT_RPC_PROVIDERS;
|
|
188
|
+
const preset = firstPresetEndpoint(chainId, env, providers);
|
|
189
|
+
if (preset !== void 0) return [preset];
|
|
190
|
+
const keyEnv = [...new Set(providers.flatMap((name) => isRpcProviderName(name) ? RPC_PROVIDER_PRESETS[name].apiKeyEnv(chainId) : []))];
|
|
191
|
+
throw new Error(`No RPC configured for chain ${chainId}: set ${[
|
|
192
|
+
`RPC_URL_${chainId}`,
|
|
193
|
+
...legacyUrlEnv,
|
|
194
|
+
...keyEnv
|
|
195
|
+
].join(", ")}`);
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
//#endregion
|
|
199
|
+
//#region src/rpc/transport.ts
|
|
200
|
+
/** viem HTTP transport for one endpoint, carrying its credential headers. */
|
|
201
|
+
function rpcHttp(endpoint, config = {}) {
|
|
202
|
+
return http(endpoint.url, {
|
|
203
|
+
...config,
|
|
204
|
+
fetchOptions: { headers: endpoint.headers }
|
|
205
|
+
});
|
|
206
|
+
}
|
|
207
|
+
/**
|
|
208
|
+
* Transport over resolved endpoints: plain HTTP for one endpoint, otherwise an
|
|
209
|
+
* unranked viem `fallback` in configured order.
|
|
210
|
+
*/
|
|
211
|
+
function rpcTransport(endpoints, { failover = true,...config } = {}) {
|
|
212
|
+
const [primary, ...rest] = endpoints;
|
|
213
|
+
if (primary === void 0) throw new Error("rpcTransport needs at least one endpoint");
|
|
214
|
+
if (!failover || rest.length === 0) return rpcHttp(primary, config);
|
|
215
|
+
return fallback(endpoints.map((endpoint) => rpcHttp(endpoint, config)), { rank: false });
|
|
216
|
+
}
|
|
217
|
+
/** anvil fork arguments for the primary endpoint (`--fork-header` only when it needs one). */
|
|
218
|
+
function rpcForkArgs(endpoints) {
|
|
219
|
+
const [primary] = endpoints;
|
|
220
|
+
if (primary === void 0) throw new Error("rpcForkArgs needs at least one endpoint");
|
|
221
|
+
const headers = Object.entries(primary.headers);
|
|
222
|
+
if (headers.length === 0) return { forkUrl: primary.url };
|
|
223
|
+
if (headers.length > 1) throw new Error("anvil --fork-header supports one header per fork");
|
|
224
|
+
const [[name, value]] = headers;
|
|
225
|
+
return {
|
|
226
|
+
forkUrl: primary.url,
|
|
227
|
+
forkHeader: `${name}: ${value}`
|
|
228
|
+
};
|
|
229
|
+
}
|
|
230
|
+
/**
|
|
231
|
+
* Value for a Ponder chain's `rpc`. Header-free endpoints stay plain URLs so
|
|
232
|
+
* Ponder keeps its own load balancing; credential headers need a transport.
|
|
233
|
+
*/
|
|
234
|
+
function ponderRpc(endpoints) {
|
|
235
|
+
if (endpoints.some((endpoint) => Object.keys(endpoint.headers).length > 0)) return rpcTransport(endpoints);
|
|
236
|
+
const urls = endpoints.map((endpoint) => endpoint.url);
|
|
237
|
+
return urls.length === 1 ? urls[0] : urls;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
//#endregion
|
|
241
|
+
export { DEFAULT_RPC_PROVIDERS, RPC_PROVIDER_PRESETS, getAlchemyRpcUrl, getAlchemyWsRpcUrl, ponderRpc, redactRpcSecrets, resolveRpcEndpoints, rpcForkArgs, rpcHttp, rpcTransport };
|
|
242
|
+
//# sourceMappingURL=index.js.map
|