@panoptic-eng/sdk 1.0.37 → 1.0.38
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/cow/index.d.ts +4 -5
- package/dist/cow/index.js +3 -3
- package/dist/cow/types.d.ts +3 -2
- package/dist/{cow-aX6of9TS.js → cow-DB8siv6M.js} +2 -2
- package/dist/{cow-aX6of9TS.js.map → cow-DB8siv6M.js.map} +1 -1
- package/dist/deployments.d.ts +3 -2
- package/dist/deployments.d.ts.map +1 -1
- package/dist/deployments.js +4 -0
- package/dist/deployments.js.map +1 -1
- package/dist/{position-C8rkkKhi.js → getTrackedPositionIds-zYPIs5dt.js} +1086 -34
- package/dist/getTrackedPositionIds-zYPIs5dt.js.map +1 -0
- package/dist/hypoVault/vaultDisplayNames.d.ts +2 -0
- package/dist/hypoVault/vaultDisplayNames.js +4 -0
- package/dist/{index-CURXeKNy.d.ts → index-BhEBx83X.d.ts} +405 -351
- package/dist/index-BhEBx83X.d.ts.map +1 -0
- package/dist/index-Bo9tHtD7.d.ts +1901 -0
- package/dist/index-Bo9tHtD7.d.ts.map +1 -0
- package/dist/{index-Cp-nCeV2.d.ts → index-CttjEZ-S.d.ts} +3 -4
- package/dist/index-CttjEZ-S.d.ts.map +1 -0
- package/dist/{index-Zvu-rJpV.d.ts → index-DktZq9Kb.d.ts} +2 -2
- package/dist/{index-Zvu-rJpV.d.ts.map → index-DktZq9Kb.d.ts.map} +1 -1
- package/dist/index.d.ts +159 -39
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +319 -66
- package/dist/index.js.map +1 -1
- package/dist/{irm-DnM96-X4.js → irm-ChkvYphK.js} +3 -318
- package/dist/irm-ChkvYphK.js.map +1 -0
- package/dist/{irm-CmgC1Mqi.d.ts → irm-D2WTHtuo.d.ts} +3 -30
- package/dist/{irm-CmgC1Mqi.d.ts.map → irm-D2WTHtuo.d.ts.map} +1 -1
- package/dist/irm-D5plRhpB.d.ts +32 -0
- package/dist/irm-D5plRhpB.d.ts.map +1 -0
- package/dist/panoptic/v2/greeks/index.d.ts +3 -2
- package/dist/panoptic/v2/index.d.ts +7 -8
- package/dist/panoptic/v2/index.js +7 -6
- package/dist/panoptic/v2/react-public.d.ts +27 -11
- package/dist/panoptic/v2/react-public.d.ts.map +1 -1
- package/dist/panoptic/v2/react-public.js +48 -8
- package/dist/panoptic/v2/react-public.js.map +1 -1
- package/dist/panoptic/v2/types/index.d.ts +3 -0
- package/dist/panoptic/v2/types/index.js +0 -0
- package/dist/quote-DeHNUR0Z.d.ts +52 -0
- package/dist/quote-DeHNUR0Z.d.ts.map +1 -0
- package/dist/{router-CS86ptMu.js → router-BCUJkY8j.js} +3 -3
- package/dist/{router-CS86ptMu.js.map → router-BCUJkY8j.js.map} +1 -1
- package/dist/{router-DcoU2KmV.js → router-tCRa0ekE.js} +2 -2
- package/dist/{router-DcoU2KmV.js.map → router-tCRa0ekE.js.map} +1 -1
- package/dist/src-CRAtPwWk.js +321 -0
- package/dist/src-CRAtPwWk.js.map +1 -0
- package/dist/test/index.d.ts +2 -2
- package/dist/test/index.d.ts.map +1 -1
- package/dist/transactionFees-CVi2TrJh.js +175 -0
- package/dist/transactionFees-CVi2TrJh.js.map +1 -0
- package/dist/transactionFees-DzDyt50k.d.ts +63 -0
- package/dist/transactionFees-DzDyt50k.d.ts.map +1 -0
- package/dist/{types-D_jcYss-.d.ts → types-Cad5m4Td.d.ts} +3 -5
- package/dist/types-Cad5m4Td.d.ts.map +1 -0
- package/dist/{types-BHdnurYr.d.ts → types-DM0nxlZr.d.ts} +2 -2
- package/dist/{types-BHdnurYr.d.ts.map → types-DM0nxlZr.d.ts.map} +1 -1
- package/dist/uniswap/index.d.ts +4 -5
- package/dist/uniswap/index.d.ts.map +1 -1
- package/dist/uniswap/index.js +4 -4
- package/dist/{v2-D0dEatKb.js → v2-BxtzpUnV.js} +3267 -2675
- package/dist/v2-BxtzpUnV.js.map +1 -0
- package/dist/vault-transaction-fees.d.ts +2 -0
- package/dist/vault-transaction-fees.js +3 -0
- package/dist/vaultDisplayNames-CKpz51Hu.js +0 -0
- package/dist/vaultDisplayNames-Q6htUcch.d.ts +2 -0
- package/dist/{writes-AupZCK4M.js → writes-D3bj1wth.js} +326 -1292
- package/dist/writes-D3bj1wth.js.map +1 -0
- package/package.json +5 -1
- package/dist/index-CURXeKNy.d.ts.map +0 -1
- package/dist/index-Cp-nCeV2.d.ts.map +0 -1
- package/dist/irm-DnM96-X4.js.map +0 -1
- package/dist/position-BqIgubs5.d.ts +0 -199
- package/dist/position-BqIgubs5.d.ts.map +0 -1
- package/dist/position-C8rkkKhi.js.map +0 -1
- package/dist/quote-BxQkPBRg.d.ts +0 -718
- package/dist/quote-BxQkPBRg.d.ts.map +0 -1
- package/dist/simulation-zM6-YUaw.d.ts +0 -258
- package/dist/simulation-zM6-YUaw.d.ts.map +0 -1
- package/dist/tx-CbDQ58Io.d.ts +0 -367
- package/dist/tx-CbDQ58Io.d.ts.map +0 -1
- package/dist/types-D_jcYss-.d.ts.map +0 -1
- package/dist/v2-D0dEatKb.js.map +0 -1
- package/dist/writes-AupZCK4M.js.map +0 -1
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { calculatePositionGreeks, tickToSqrtPriceX96 } from "./greeks-GysWXct-.js";
|
|
2
|
-
import { BaseError, decodeAbiParameters, decodeErrorResult, decodeEventLog, encodeFunctionData, keccak256, toFunctionSelector, zeroAddress } from "viem";
|
|
1
|
+
import { ORACLE_EPOCH_SECONDS, SCHEMA_VERSION, STORAGE_PREFIX, calculatePositionGreeks, tickToSqrtPriceX96 } from "./greeks-GysWXct-.js";
|
|
2
|
+
import { BaseError, decodeAbiParameters, decodeErrorResult, decodeEventLog, decodeFunctionData, encodeFunctionData, getAddress, keccak256, parseAbi, toFunctionSelector, zeroAddress } from "viem";
|
|
3
3
|
|
|
4
4
|
//#region src/generated.ts
|
|
5
5
|
const collateralTrackerV2Abi = [
|
|
@@ -8991,6 +8991,179 @@ const uniswapV3PoolAbi = [
|
|
|
8991
8991
|
}
|
|
8992
8992
|
];
|
|
8993
8993
|
|
|
8994
|
+
//#endregion
|
|
8995
|
+
//#region src/panoptic/v2/utils/oraclePack.ts
|
|
8996
|
+
const ORACLE_EPOCH_SHIFT = 232n;
|
|
8997
|
+
const ORACLE_EPOCH_MASK = (1n << 24n) - 1n;
|
|
8998
|
+
const ORACLE_TICK_MASK = (1n << 22n) - 1n;
|
|
8999
|
+
function decodeSigned22(value) {
|
|
9000
|
+
const signBit = 1n << 21n;
|
|
9001
|
+
const truncated = value & ORACLE_TICK_MASK;
|
|
9002
|
+
return (truncated & signBit) === 0n ? truncated : truncated - (1n << 22n);
|
|
9003
|
+
}
|
|
9004
|
+
/**
|
|
9005
|
+
* Decode the 24-bit epoch stored at bits 232..255 of OraclePack.
|
|
9006
|
+
*
|
|
9007
|
+
* The packed timestamp is modulo 2^30 seconds and only identifies an absolute
|
|
9008
|
+
* Unix timestamp within the wrap window containing `blockTimestamp`. Supplying
|
|
9009
|
+
* the relevant block timestamp resolves it to the latest matching epoch at or
|
|
9010
|
+
* before that block, including across the epoch counter wraparound.
|
|
9011
|
+
*/
|
|
9012
|
+
function decodeOracleTiming(oraclePack, blockTimestamp) {
|
|
9013
|
+
const epoch = oraclePack >> ORACLE_EPOCH_SHIFT & ORACLE_EPOCH_MASK;
|
|
9014
|
+
const currentEpoch = oracleEpochAt(blockTimestamp);
|
|
9015
|
+
const elapsedEpochs = currentEpoch - epoch & ORACLE_EPOCH_MASK;
|
|
9016
|
+
const currentEpochTimestamp = blockTimestamp - blockTimestamp % ORACLE_EPOCH_SECONDS;
|
|
9017
|
+
return {
|
|
9018
|
+
epoch,
|
|
9019
|
+
timestamp: currentEpochTimestamp - elapsedEpochs * ORACLE_EPOCH_SECONDS
|
|
9020
|
+
};
|
|
9021
|
+
}
|
|
9022
|
+
/** Decode the fixed-width ticks and guardian state stored in OraclePack. */
|
|
9023
|
+
function decodeOraclePack(oraclePack, blockTimestamp) {
|
|
9024
|
+
const timing = decodeOracleTiming(oraclePack, blockTimestamp);
|
|
9025
|
+
return {
|
|
9026
|
+
referenceTick: decodeSigned22(oraclePack >> 96n),
|
|
9027
|
+
lockMode: oraclePack >> 118n & 3n,
|
|
9028
|
+
spotEMA: decodeSigned22(oraclePack >> 120n),
|
|
9029
|
+
fastEMA: decodeSigned22(oraclePack >> 142n),
|
|
9030
|
+
slowEMA: decodeSigned22(oraclePack >> 164n),
|
|
9031
|
+
eonsEMA: decodeSigned22(oraclePack >> 186n),
|
|
9032
|
+
...timing
|
|
9033
|
+
};
|
|
9034
|
+
}
|
|
9035
|
+
/** Return the contract's modulo-2^24 64-second epoch for a block timestamp. */
|
|
9036
|
+
function oracleEpochAt(timestamp) {
|
|
9037
|
+
return timestamp / ORACLE_EPOCH_SECONDS & ORACLE_EPOCH_MASK;
|
|
9038
|
+
}
|
|
9039
|
+
|
|
9040
|
+
//#endregion
|
|
9041
|
+
//#region src/panoptic/v2/utils/oracleSafeMode.ts
|
|
9042
|
+
const UINT24_MASK = (1n << 24n) - 1n;
|
|
9043
|
+
function absoluteDelta(left, right) {
|
|
9044
|
+
return left >= right ? left - right : right - left;
|
|
9045
|
+
}
|
|
9046
|
+
function epochsForPeriod(period) {
|
|
9047
|
+
return (period + ORACLE_EPOCH_SECONDS - 1n) / ORACLE_EPOCH_SECONDS;
|
|
9048
|
+
}
|
|
9049
|
+
/** Decode the four uint24 periods packed by RiskEngine.EMA_PERIODS(). */
|
|
9050
|
+
function decodeOracleRiskParameters(emaPeriods, maxTicksDelta, maxClampDelta) {
|
|
9051
|
+
const decodedPeriods = {
|
|
9052
|
+
spot: emaPeriods & UINT24_MASK,
|
|
9053
|
+
fast: emaPeriods >> 24n & UINT24_MASK,
|
|
9054
|
+
slow: emaPeriods >> 48n & UINT24_MASK,
|
|
9055
|
+
eons: emaPeriods >> 72n & UINT24_MASK
|
|
9056
|
+
};
|
|
9057
|
+
if (decodedPeriods.spot === 0n || decodedPeriods.fast === 0n || decodedPeriods.slow === 0n || decodedPeriods.eons === 0n || maxTicksDelta <= 0n || maxClampDelta < 0n) throw new PanopticValidationError(`RiskEngine returned invalid oracle parameters: EMA_PERIODS=${emaPeriods}, MAX_TICKS_DELTA=${maxTicksDelta}, MAX_CLAMP_DELTA=${maxClampDelta}`);
|
|
9058
|
+
return {
|
|
9059
|
+
emaPeriods: decodedPeriods,
|
|
9060
|
+
maxTicksDelta,
|
|
9061
|
+
maxClampDelta
|
|
9062
|
+
};
|
|
9063
|
+
}
|
|
9064
|
+
/** Explain the on-chain SafeMode level using this deployment's RiskEngine constants. */
|
|
9065
|
+
function diagnoseOracleSafeMode(state, parameters, onchainLevel) {
|
|
9066
|
+
const externalShockDelta = absoluteDelta(state.currentTick, state.spotEMA);
|
|
9067
|
+
const internalDisagreementDelta = absoluteDelta(state.spotEMA, state.fastEMA);
|
|
9068
|
+
const highDivergenceDelta = absoluteDelta(state.medianTick, state.slowEMA);
|
|
9069
|
+
const internalThreshold = parameters.maxTicksDelta / 2n;
|
|
9070
|
+
const externalShock = externalShockDelta > parameters.maxTicksDelta;
|
|
9071
|
+
const internalDisagreement = internalDisagreementDelta > internalThreshold;
|
|
9072
|
+
const highDivergence = highDivergenceDelta > internalThreshold;
|
|
9073
|
+
const guardianLocked = state.lockMode !== 0n;
|
|
9074
|
+
const algorithmicLevel = (externalShock ? 1n : 0n) + (internalDisagreement ? 1n : 0n) + (highDivergence ? 1n : 0n);
|
|
9075
|
+
const reproducedLevel = algorithmicLevel + state.lockMode;
|
|
9076
|
+
const matchesOnchain = reproducedLevel === onchainLevel;
|
|
9077
|
+
const causes = [];
|
|
9078
|
+
if (matchesOnchain) {
|
|
9079
|
+
if (externalShock) causes.push("externalShock");
|
|
9080
|
+
if (internalDisagreement) causes.push("internalDisagreement");
|
|
9081
|
+
if (highDivergence) causes.push("highDivergence");
|
|
9082
|
+
} else causes.push("unknown");
|
|
9083
|
+
if (guardianLocked) causes.push("guardianLock");
|
|
9084
|
+
const candidateEpochs = [];
|
|
9085
|
+
if (!matchesOnchain) candidateEpochs.push(1n);
|
|
9086
|
+
if (highDivergence) candidateEpochs.push(1n);
|
|
9087
|
+
if (externalShock) candidateEpochs.push(epochsForPeriod(parameters.emaPeriods.spot));
|
|
9088
|
+
if (internalDisagreement) candidateEpochs.push(epochsForPeriod(parameters.emaPeriods.fast));
|
|
9089
|
+
const minimumPokeEpochs = candidateEpochs.reduce((minimum, epochs) => minimum === 0n || epochs < minimum ? epochs : minimum, 0n);
|
|
9090
|
+
return {
|
|
9091
|
+
level: onchainLevel,
|
|
9092
|
+
reproducedLevel,
|
|
9093
|
+
matchesOnchain,
|
|
9094
|
+
causes,
|
|
9095
|
+
externalShockDelta,
|
|
9096
|
+
internalDisagreementDelta,
|
|
9097
|
+
highDivergenceDelta,
|
|
9098
|
+
guardianLocked,
|
|
9099
|
+
minimumPokeEpochs,
|
|
9100
|
+
shouldPoke: onchainLevel > 0n && !guardianLocked
|
|
9101
|
+
};
|
|
9102
|
+
}
|
|
9103
|
+
|
|
9104
|
+
//#endregion
|
|
9105
|
+
//#region src/panoptic/v2/reads/mintBuffer.ts
|
|
9106
|
+
/**
|
|
9107
|
+
* `RiskEngine.BP_DECREASE_BUFFER` — the numerator of the mint-time buffer.
|
|
9108
|
+
*
|
|
9109
|
+
* Exported so callers can display the constant; prefer {@link applyMintBuffer}
|
|
9110
|
+
* over multiplying by it directly, which is how the rounding bug reappears.
|
|
9111
|
+
*/
|
|
9112
|
+
const MINT_BUFFER = 10666667n;
|
|
9113
|
+
/** `RiskEngine.DECIMALS` — the denominator {@link MINT_BUFFER} is taken over. */
|
|
9114
|
+
const MINT_BUFFER_DENOMINATOR = 10000000n;
|
|
9115
|
+
/** The compiled-in default, matching the deployed RiskEngine. */
|
|
9116
|
+
const DEFAULT_MINT_BUFFER = {
|
|
9117
|
+
numerator: MINT_BUFFER,
|
|
9118
|
+
denominator: MINT_BUFFER_DENOMINATOR
|
|
9119
|
+
};
|
|
9120
|
+
/**
|
|
9121
|
+
* Apply the mint buffer to ONE token's NATIVE requirement, rounding up exactly
|
|
9122
|
+
* as `Math.mulDivRoundingUp` does on-chain.
|
|
9123
|
+
*
|
|
9124
|
+
* Call this on the native token0/token1 requirement, never on a quote-converted
|
|
9125
|
+
* total — see the module docstring for why the order matters.
|
|
9126
|
+
*
|
|
9127
|
+
* @param required - Maintenance requirement in that token's own units
|
|
9128
|
+
* @param buffer - Optional live on-chain ratio; defaults to {@link DEFAULT_MINT_BUFFER}
|
|
9129
|
+
* @returns The mint-time requirement the solvency check enforces
|
|
9130
|
+
*/
|
|
9131
|
+
function applyMintBuffer(required, buffer = DEFAULT_MINT_BUFFER) {
|
|
9132
|
+
const { numerator, denominator } = buffer;
|
|
9133
|
+
if (denominator <= 0n) throw new PanopticValidationError("applyMintBuffer: buffer denominator must be positive");
|
|
9134
|
+
if (required <= 0n) return 0n;
|
|
9135
|
+
return (required * numerator + denominator - 1n) / denominator;
|
|
9136
|
+
}
|
|
9137
|
+
/**
|
|
9138
|
+
* Apply the mint buffer to both tokens' NATIVE requirements, independently.
|
|
9139
|
+
*
|
|
9140
|
+
* This is the shape the solvency check itself uses: two separate per-side
|
|
9141
|
+
* comparisons, never a pooled total.
|
|
9142
|
+
*
|
|
9143
|
+
* @param required0 - Maintenance requirement in token0 units
|
|
9144
|
+
* @param required1 - Maintenance requirement in token1 units
|
|
9145
|
+
* @param buffer - Optional live on-chain ratio
|
|
9146
|
+
* @returns `[buffered0, buffered1]`
|
|
9147
|
+
*/
|
|
9148
|
+
function applyMintBufferPerToken(required0, required1, buffer = DEFAULT_MINT_BUFFER) {
|
|
9149
|
+
return [applyMintBuffer(required0, buffer), applyMintBuffer(required1, buffer)];
|
|
9150
|
+
}
|
|
9151
|
+
/**
|
|
9152
|
+
* Collateral still deployable on one side before the mint solvency check fails:
|
|
9153
|
+
* `balance - bufferedRequired`, floored at zero.
|
|
9154
|
+
*
|
|
9155
|
+
* Both arguments must be in the SAME token's units.
|
|
9156
|
+
*
|
|
9157
|
+
* @param balance - That side's collateral balance
|
|
9158
|
+
* @param required - That side's maintenance requirement (unbuffered)
|
|
9159
|
+
* @param buffer - Optional live on-chain ratio
|
|
9160
|
+
* @returns Mintable headroom, never negative
|
|
9161
|
+
*/
|
|
9162
|
+
function mintableAfterBuffer(balance, required, buffer = DEFAULT_MINT_BUFFER) {
|
|
9163
|
+
const requiredAtMint = applyMintBuffer(required, buffer);
|
|
9164
|
+
return balance > requiredAtMint ? balance - requiredAtMint : 0n;
|
|
9165
|
+
}
|
|
9166
|
+
|
|
8994
9167
|
//#endregion
|
|
8995
9168
|
//#region src/panoptic/v2/reads/pool.ts
|
|
8996
9169
|
const erc20Abi$1 = [
|
|
@@ -9132,13 +9305,28 @@ async function getPoolMetadata(params) {
|
|
|
9132
9305
|
const parsedPoolKey = parsePoolKey(poolKeyBytes);
|
|
9133
9306
|
let underlyingPoolId;
|
|
9134
9307
|
let fee;
|
|
9308
|
+
let tickSpacing;
|
|
9135
9309
|
if (isV4) {
|
|
9136
9310
|
underlyingPoolId = keccak256(poolKeyBytes);
|
|
9137
9311
|
fee = parsedPoolKey.fee;
|
|
9312
|
+
tickSpacing = tickSpacingFromPoolId(poolId);
|
|
9138
9313
|
} else {
|
|
9139
9314
|
const v3PoolAddress = decodeAbiParameters([{ type: "address" }], poolKeyBytes)[0];
|
|
9140
9315
|
underlyingPoolId = v3PoolAddress;
|
|
9141
|
-
|
|
9316
|
+
const [v3Fee, v3TickSpacing] = await client.multicall({
|
|
9317
|
+
contracts: [{
|
|
9318
|
+
address: v3PoolAddress,
|
|
9319
|
+
abi: uniswapV3PoolAbi,
|
|
9320
|
+
functionName: "fee"
|
|
9321
|
+
}, {
|
|
9322
|
+
address: v3PoolAddress,
|
|
9323
|
+
abi: uniswapV3PoolAbi,
|
|
9324
|
+
functionName: "tickSpacing"
|
|
9325
|
+
}],
|
|
9326
|
+
allowFailure: false
|
|
9327
|
+
});
|
|
9328
|
+
fee = BigInt(v3Fee);
|
|
9329
|
+
tickSpacing = BigInt(v3TickSpacing);
|
|
9142
9330
|
}
|
|
9143
9331
|
return {
|
|
9144
9332
|
poolKeyBytes,
|
|
@@ -9156,7 +9344,7 @@ async function getPoolMetadata(params) {
|
|
|
9156
9344
|
token0Name,
|
|
9157
9345
|
token1Name,
|
|
9158
9346
|
underlyingPoolId,
|
|
9159
|
-
tickSpacing
|
|
9347
|
+
tickSpacing,
|
|
9160
9348
|
fee,
|
|
9161
9349
|
sfpmAddress
|
|
9162
9350
|
};
|
|
@@ -9277,8 +9465,12 @@ async function getPool(params) {
|
|
|
9277
9465
|
const [currentTick, safeModeRaw, token0PoolData, token0TotalSupply, token0InterestRate, token1PoolData, token1TotalSupply, token1InterestRate, sellerCollateralRatio, maintMarginRate, notionalFee, premiumFee, vegoid$1, maxSpread] = coreResults.map((r) => r.result);
|
|
9278
9466
|
const liquidityResult = dynamicResults[14];
|
|
9279
9467
|
const uniswapPoolLiquidity = liquidityResult?.status === "success" ? BigInt(liquidityResult.result) : 0n;
|
|
9280
|
-
const
|
|
9281
|
-
const
|
|
9468
|
+
const tickSpacing = metadata.tickSpacing;
|
|
9469
|
+
const poolKey = {
|
|
9470
|
+
...parsePoolKey(metadata.poolKeyBytes),
|
|
9471
|
+
fee: metadata.fee,
|
|
9472
|
+
tickSpacing
|
|
9473
|
+
};
|
|
9282
9474
|
const SECONDS_PER_YEAR = 31536000n;
|
|
9283
9475
|
const borrowRate0 = BigInt(token0InterestRate) * SECONDS_PER_YEAR;
|
|
9284
9476
|
const borrowRate1 = BigInt(token1InterestRate) * SECONDS_PER_YEAR;
|
|
@@ -9443,32 +9635,73 @@ function availableToBorrow(poolData, totalSupply) {
|
|
|
9443
9635
|
async function getOracleState(params) {
|
|
9444
9636
|
const { client, poolAddress, blockNumber } = params;
|
|
9445
9637
|
const targetBlockNumber = blockNumber ?? params._meta?.blockNumber ?? await client.getBlockNumber();
|
|
9446
|
-
const [oracleTicks, _meta] = await Promise.all([client.
|
|
9447
|
-
|
|
9448
|
-
|
|
9449
|
-
|
|
9638
|
+
const [[oracleTicks, twapTick], _meta] = await Promise.all([client.multicall({
|
|
9639
|
+
contracts: [{
|
|
9640
|
+
address: poolAddress,
|
|
9641
|
+
abi: panopticPoolV2Abi,
|
|
9642
|
+
functionName: "getOracleTicks"
|
|
9643
|
+
}, {
|
|
9644
|
+
address: poolAddress,
|
|
9645
|
+
abi: panopticPoolV2Abi,
|
|
9646
|
+
functionName: "getTWAP"
|
|
9647
|
+
}],
|
|
9648
|
+
allowFailure: false,
|
|
9450
9649
|
blockNumber: targetBlockNumber
|
|
9451
9650
|
}), params._meta ?? getBlockMeta({
|
|
9452
9651
|
client,
|
|
9453
9652
|
blockNumber: targetBlockNumber
|
|
9454
9653
|
})]);
|
|
9455
9654
|
const [currentTick, spotTick, medianTick, latestTick, oraclePack] = oracleTicks;
|
|
9456
|
-
const
|
|
9457
|
-
const lastUpdateTimestamp = oraclePack >> 176n & (1n << 32n) - 1n;
|
|
9458
|
-
const lockMode = oraclePack >> 118n & 3n;
|
|
9655
|
+
const decoded = decodeOraclePack(oraclePack, _meta.blockTimestamp);
|
|
9459
9656
|
return {
|
|
9460
|
-
epoch,
|
|
9461
|
-
lastUpdateTimestamp,
|
|
9657
|
+
epoch: decoded.epoch,
|
|
9658
|
+
lastUpdateTimestamp: decoded.timestamp,
|
|
9462
9659
|
referenceTick: BigInt(currentTick),
|
|
9660
|
+
currentTick: BigInt(currentTick),
|
|
9661
|
+
oracleReferenceTick: decoded.referenceTick,
|
|
9662
|
+
latestTick: BigInt(latestTick),
|
|
9663
|
+
twapTick: BigInt(twapTick),
|
|
9463
9664
|
spotEMA: BigInt(spotTick),
|
|
9464
|
-
fastEMA:
|
|
9465
|
-
slowEMA:
|
|
9466
|
-
eonsEMA:
|
|
9467
|
-
lockMode,
|
|
9665
|
+
fastEMA: decoded.fastEMA,
|
|
9666
|
+
slowEMA: decoded.slowEMA,
|
|
9667
|
+
eonsEMA: decoded.eonsEMA,
|
|
9668
|
+
lockMode: decoded.lockMode,
|
|
9468
9669
|
medianTick: BigInt(medianTick),
|
|
9469
9670
|
_meta
|
|
9470
9671
|
};
|
|
9471
9672
|
}
|
|
9673
|
+
/** Read the oracle constants from a specific deployed RiskEngine. */
|
|
9674
|
+
async function getOracleRiskParameters(params) {
|
|
9675
|
+
const targetBlockNumber = params.blockNumber ?? await params.client.getBlockNumber();
|
|
9676
|
+
const [[emaPeriods, maxTicksDelta, maxClampDelta], _meta] = await Promise.all([params.client.multicall({
|
|
9677
|
+
contracts: [
|
|
9678
|
+
{
|
|
9679
|
+
address: params.riskEngineAddress,
|
|
9680
|
+
abi: riskEngineAbi,
|
|
9681
|
+
functionName: "EMA_PERIODS"
|
|
9682
|
+
},
|
|
9683
|
+
{
|
|
9684
|
+
address: params.riskEngineAddress,
|
|
9685
|
+
abi: riskEngineAbi,
|
|
9686
|
+
functionName: "MAX_TICKS_DELTA"
|
|
9687
|
+
},
|
|
9688
|
+
{
|
|
9689
|
+
address: params.riskEngineAddress,
|
|
9690
|
+
abi: riskEngineAbi,
|
|
9691
|
+
functionName: "MAX_CLAMP_DELTA"
|
|
9692
|
+
}
|
|
9693
|
+
],
|
|
9694
|
+
allowFailure: false,
|
|
9695
|
+
blockNumber: targetBlockNumber
|
|
9696
|
+
}), getBlockMeta({
|
|
9697
|
+
client: params.client,
|
|
9698
|
+
blockNumber: targetBlockNumber
|
|
9699
|
+
})]);
|
|
9700
|
+
return {
|
|
9701
|
+
...decodeOracleRiskParameters(BigInt(emaPeriods), BigInt(maxTicksDelta), BigInt(maxClampDelta)),
|
|
9702
|
+
_meta
|
|
9703
|
+
};
|
|
9704
|
+
}
|
|
9472
9705
|
/**
|
|
9473
9706
|
* Get risk parameters from the pool.
|
|
9474
9707
|
*
|
|
@@ -9519,6 +9752,16 @@ async function getRiskParameters(params) {
|
|
|
9519
9752
|
abi: riskEngineAbi,
|
|
9520
9753
|
functionName: "SATURATED_POOL_UTIL"
|
|
9521
9754
|
},
|
|
9755
|
+
{
|
|
9756
|
+
address: riskEngineAddress,
|
|
9757
|
+
abi: riskEngineAbi,
|
|
9758
|
+
functionName: "BP_DECREASE_BUFFER"
|
|
9759
|
+
},
|
|
9760
|
+
{
|
|
9761
|
+
address: riskEngineAddress,
|
|
9762
|
+
abi: riskEngineAbi,
|
|
9763
|
+
functionName: "DECIMALS"
|
|
9764
|
+
},
|
|
9522
9765
|
{
|
|
9523
9766
|
address: poolAddress,
|
|
9524
9767
|
abi: panopticPoolV2Abi,
|
|
@@ -9532,7 +9775,7 @@ async function getRiskParameters(params) {
|
|
|
9532
9775
|
client,
|
|
9533
9776
|
blockNumber: targetBlockNumber
|
|
9534
9777
|
})]);
|
|
9535
|
-
const [sellerCollateralRatio, buyerCollateralRatio, maintMarginRate, notionalFee, targetPoolUtil, saturatedPoolUtil, ,] = riskEngineResults;
|
|
9778
|
+
const [sellerCollateralRatio, buyerCollateralRatio, maintMarginRate, notionalFee, targetPoolUtil, saturatedPoolUtil, bpDecreaseBuffer, riskEngineDecimals, ,] = riskEngineResults;
|
|
9536
9779
|
return {
|
|
9537
9780
|
collateralRequirement: sellerCollateralRatio,
|
|
9538
9781
|
maintenanceMargin: maintMarginRate,
|
|
@@ -9540,6 +9783,10 @@ async function getRiskParameters(params) {
|
|
|
9540
9783
|
targetUtilization: targetPoolUtil,
|
|
9541
9784
|
saturatedUtilization: saturatedPoolUtil,
|
|
9542
9785
|
itmSpreadMultiplier: buyerCollateralRatio,
|
|
9786
|
+
mintBuffer: BigInt(riskEngineDecimals) > 0n ? {
|
|
9787
|
+
numerator: BigInt(bpDecreaseBuffer),
|
|
9788
|
+
denominator: BigInt(riskEngineDecimals)
|
|
9789
|
+
} : DEFAULT_MINT_BUFFER,
|
|
9543
9790
|
_meta
|
|
9544
9791
|
};
|
|
9545
9792
|
}
|
|
@@ -9567,17 +9814,6 @@ async function validateBuilderCode(params) {
|
|
|
9567
9814
|
}
|
|
9568
9815
|
}
|
|
9569
9816
|
/**
|
|
9570
|
-
* Read the fee tier from a Uniswap V3 pool contract.
|
|
9571
|
-
*/
|
|
9572
|
-
async function getV3PoolFee(client, poolAddress) {
|
|
9573
|
-
const fee = await client.readContract({
|
|
9574
|
-
address: poolAddress,
|
|
9575
|
-
abi: uniswapV3PoolAbi,
|
|
9576
|
-
functionName: "fee"
|
|
9577
|
-
});
|
|
9578
|
-
return BigInt(fee);
|
|
9579
|
-
}
|
|
9580
|
-
/**
|
|
9581
9817
|
* Extract tickSpacing from the encoded 64-bit poolId.
|
|
9582
9818
|
* Layout: poolAddress (5 bytes) + vegoid (1 byte) + tickSpacing (2 bytes).
|
|
9583
9819
|
* tickSpacing occupies bits 48–63.
|
|
@@ -9594,7 +9830,7 @@ function tickSpacingFromPoolId(poolId) {
|
|
|
9594
9830
|
* V3 pools: poolKey() returns abi.encode(uniswapV3PoolAddress) — a single
|
|
9595
9831
|
* 32-byte slot. The struct fields are not available, so currency0/currency1
|
|
9596
9832
|
* are zeroed and tickSpacing/fee are set to 0 (callers should use
|
|
9597
|
-
*
|
|
9833
|
+
* getV3PoolTickSpacing and getV3PoolFee respectively).
|
|
9598
9834
|
*/
|
|
9599
9835
|
function parsePoolKey(poolKeyBytes) {
|
|
9600
9836
|
const hex = poolKeyBytes.slice(2);
|
|
@@ -10105,5 +10341,821 @@ async function getPositionGreeks(params) {
|
|
|
10105
10341
|
}
|
|
10106
10342
|
|
|
10107
10343
|
//#endregion
|
|
10108
|
-
|
|
10109
|
-
|
|
10344
|
+
//#region src/panoptic/v2/storage/fileStorage.ts
|
|
10345
|
+
/**
|
|
10346
|
+
* Creates a file-based storage adapter for Node.js environments.
|
|
10347
|
+
* Stores data as JSON files in the specified directory.
|
|
10348
|
+
*
|
|
10349
|
+
* The directory will be created if it doesn't exist.
|
|
10350
|
+
*
|
|
10351
|
+
* @param directory - Path to the storage directory
|
|
10352
|
+
*
|
|
10353
|
+
* @example
|
|
10354
|
+
* ```typescript
|
|
10355
|
+
* import { createFileStorage, createConfig } from 'panoptic-v2-sdk'
|
|
10356
|
+
*
|
|
10357
|
+
* // Node.js only
|
|
10358
|
+
* const config = createConfig({
|
|
10359
|
+
* chainId: 1n,
|
|
10360
|
+
* transport: http('...'),
|
|
10361
|
+
* poolAddress: '0x...',
|
|
10362
|
+
* storage: createFileStorage('./panoptic-cache'),
|
|
10363
|
+
* })
|
|
10364
|
+
* ```
|
|
10365
|
+
*/
|
|
10366
|
+
function createFileStorage(directory) {
|
|
10367
|
+
let fsPromises = null;
|
|
10368
|
+
let pathModule = null;
|
|
10369
|
+
let initialized = false;
|
|
10370
|
+
const ensureInitialized = async () => {
|
|
10371
|
+
if (initialized) return;
|
|
10372
|
+
fsPromises = await import("node:fs/promises");
|
|
10373
|
+
pathModule = await import("node:path");
|
|
10374
|
+
await fsPromises.mkdir(directory, { recursive: true });
|
|
10375
|
+
initialized = true;
|
|
10376
|
+
};
|
|
10377
|
+
const getFilePath = (key) => {
|
|
10378
|
+
if (!pathModule) throw new Error("File storage not initialized");
|
|
10379
|
+
const safeKey = Buffer.from(key).toString("base64url");
|
|
10380
|
+
return pathModule.join(directory, `${safeKey}.json`);
|
|
10381
|
+
};
|
|
10382
|
+
return {
|
|
10383
|
+
async get(key) {
|
|
10384
|
+
await ensureInitialized();
|
|
10385
|
+
if (!fsPromises) throw new Error("File storage not initialized");
|
|
10386
|
+
try {
|
|
10387
|
+
const filePath = getFilePath(key);
|
|
10388
|
+
const content = await fsPromises.readFile(filePath, "utf-8");
|
|
10389
|
+
return content;
|
|
10390
|
+
} catch (error) {
|
|
10391
|
+
if (error.code === "ENOENT") return null;
|
|
10392
|
+
throw error;
|
|
10393
|
+
}
|
|
10394
|
+
},
|
|
10395
|
+
async set(key, value) {
|
|
10396
|
+
await ensureInitialized();
|
|
10397
|
+
if (!fsPromises) throw new Error("File storage not initialized");
|
|
10398
|
+
const filePath = getFilePath(key);
|
|
10399
|
+
await fsPromises.writeFile(filePath, value, "utf-8");
|
|
10400
|
+
},
|
|
10401
|
+
async delete(key) {
|
|
10402
|
+
await ensureInitialized();
|
|
10403
|
+
if (!fsPromises) throw new Error("File storage not initialized");
|
|
10404
|
+
try {
|
|
10405
|
+
const filePath = getFilePath(key);
|
|
10406
|
+
await fsPromises.unlink(filePath);
|
|
10407
|
+
} catch (error) {
|
|
10408
|
+
if (error.code !== "ENOENT") throw error;
|
|
10409
|
+
}
|
|
10410
|
+
},
|
|
10411
|
+
async has(key) {
|
|
10412
|
+
await ensureInitialized();
|
|
10413
|
+
if (!fsPromises) throw new Error("File storage not initialized");
|
|
10414
|
+
try {
|
|
10415
|
+
const filePath = getFilePath(key);
|
|
10416
|
+
await fsPromises.access(filePath);
|
|
10417
|
+
return true;
|
|
10418
|
+
} catch {
|
|
10419
|
+
return false;
|
|
10420
|
+
}
|
|
10421
|
+
},
|
|
10422
|
+
async keys(prefix) {
|
|
10423
|
+
await ensureInitialized();
|
|
10424
|
+
if (!fsPromises || !pathModule) throw new Error("File storage not initialized");
|
|
10425
|
+
const result = [];
|
|
10426
|
+
try {
|
|
10427
|
+
const files = await fsPromises.readdir(directory);
|
|
10428
|
+
for (const file of files) if (file.endsWith(".json")) {
|
|
10429
|
+
const safeKey = file.slice(0, -5);
|
|
10430
|
+
try {
|
|
10431
|
+
const key = Buffer.from(safeKey, "base64url").toString();
|
|
10432
|
+
if (key.startsWith(prefix)) result.push(key);
|
|
10433
|
+
} catch {}
|
|
10434
|
+
}
|
|
10435
|
+
} catch (error) {
|
|
10436
|
+
if (error.code !== "ENOENT") throw error;
|
|
10437
|
+
}
|
|
10438
|
+
return result;
|
|
10439
|
+
},
|
|
10440
|
+
async clear(prefix) {
|
|
10441
|
+
await ensureInitialized();
|
|
10442
|
+
if (!fsPromises) throw new Error("File storage not initialized");
|
|
10443
|
+
const keysToDelete = await this.keys(prefix);
|
|
10444
|
+
for (const key of keysToDelete) await this.delete(key);
|
|
10445
|
+
}
|
|
10446
|
+
};
|
|
10447
|
+
}
|
|
10448
|
+
|
|
10449
|
+
//#endregion
|
|
10450
|
+
//#region src/panoptic/v2/storage/keys.ts
|
|
10451
|
+
/**
|
|
10452
|
+
* Get the schema version key.
|
|
10453
|
+
*/
|
|
10454
|
+
function getSchemaVersionKey() {
|
|
10455
|
+
return `${STORAGE_PREFIX}:schemaVersion`;
|
|
10456
|
+
}
|
|
10457
|
+
/**
|
|
10458
|
+
* Get the base prefix for a specific pool.
|
|
10459
|
+
*/
|
|
10460
|
+
function getPoolPrefix(chainId, poolAddress) {
|
|
10461
|
+
return `${STORAGE_PREFIX}:v${SCHEMA_VERSION}:chain${chainId}:pool${poolAddress.toLowerCase()}`;
|
|
10462
|
+
}
|
|
10463
|
+
/**
|
|
10464
|
+
* Get the key for tracked position IDs.
|
|
10465
|
+
*/
|
|
10466
|
+
function getPositionsKey(chainId, poolAddress, account) {
|
|
10467
|
+
return `${getPoolPrefix(chainId, poolAddress)}:positions:${account.toLowerCase()}`;
|
|
10468
|
+
}
|
|
10469
|
+
/**
|
|
10470
|
+
* Get the key for position mint metadata.
|
|
10471
|
+
*/
|
|
10472
|
+
function getPositionMetaKey(chainId, poolAddress, tokenId) {
|
|
10473
|
+
return `${getPoolPrefix(chainId, poolAddress)}:positionMeta:${tokenId.toString()}`;
|
|
10474
|
+
}
|
|
10475
|
+
/**
|
|
10476
|
+
* Get the key for sync checkpoint.
|
|
10477
|
+
*/
|
|
10478
|
+
function getSyncCheckpointKey(chainId, poolAddress, account) {
|
|
10479
|
+
return `${getPoolPrefix(chainId, poolAddress)}:sync:${account.toLowerCase()}`;
|
|
10480
|
+
}
|
|
10481
|
+
/**
|
|
10482
|
+
* Get the key for closed positions.
|
|
10483
|
+
*/
|
|
10484
|
+
function getClosedPositionsKey(chainId, poolAddress, account) {
|
|
10485
|
+
return `${getPoolPrefix(chainId, poolAddress)}:closed:${account.toLowerCase()}`;
|
|
10486
|
+
}
|
|
10487
|
+
/**
|
|
10488
|
+
* Get the key for tracked chunks.
|
|
10489
|
+
*/
|
|
10490
|
+
function getTrackedChunksKey(chainId, poolAddress) {
|
|
10491
|
+
return `${getPoolPrefix(chainId, poolAddress)}:chunks`;
|
|
10492
|
+
}
|
|
10493
|
+
/**
|
|
10494
|
+
* Get the key for pending positions.
|
|
10495
|
+
*/
|
|
10496
|
+
function getPendingPositionsKey(chainId, poolAddress, account) {
|
|
10497
|
+
return `${getPoolPrefix(chainId, poolAddress)}:pending:${account.toLowerCase()}`;
|
|
10498
|
+
}
|
|
10499
|
+
/**
|
|
10500
|
+
* Get the key for pool metadata (tickSpacing, etc.).
|
|
10501
|
+
*/
|
|
10502
|
+
function getPoolMetaKey(chainId, poolAddress) {
|
|
10503
|
+
return `${getPoolPrefix(chainId, poolAddress)}:poolMeta`;
|
|
10504
|
+
}
|
|
10505
|
+
|
|
10506
|
+
//#endregion
|
|
10507
|
+
//#region src/panoptic/v2/storage/memoryStorage.ts
|
|
10508
|
+
/**
|
|
10509
|
+
* Creates an in-memory storage adapter.
|
|
10510
|
+
* Useful for testing and development.
|
|
10511
|
+
*
|
|
10512
|
+
* Data is lost when the process exits.
|
|
10513
|
+
*
|
|
10514
|
+
* @example
|
|
10515
|
+
* ```typescript
|
|
10516
|
+
* import { createMemoryStorage, createConfig } from 'panoptic-v2-sdk'
|
|
10517
|
+
*
|
|
10518
|
+
* const config = createConfig({
|
|
10519
|
+
* chainId: 1n,
|
|
10520
|
+
* transport: http('...'),
|
|
10521
|
+
* poolAddress: '0x...',
|
|
10522
|
+
* storage: createMemoryStorage(),
|
|
10523
|
+
* })
|
|
10524
|
+
* ```
|
|
10525
|
+
*/
|
|
10526
|
+
function createMemoryStorage() {
|
|
10527
|
+
const store = new Map();
|
|
10528
|
+
return {
|
|
10529
|
+
async get(key) {
|
|
10530
|
+
return store.get(key) ?? null;
|
|
10531
|
+
},
|
|
10532
|
+
async set(key, value) {
|
|
10533
|
+
store.set(key, value);
|
|
10534
|
+
},
|
|
10535
|
+
async delete(key) {
|
|
10536
|
+
store.delete(key);
|
|
10537
|
+
},
|
|
10538
|
+
async has(key) {
|
|
10539
|
+
return store.has(key);
|
|
10540
|
+
},
|
|
10541
|
+
async keys(prefix) {
|
|
10542
|
+
const result = [];
|
|
10543
|
+
for (const key of store.keys()) if (key.startsWith(prefix)) result.push(key);
|
|
10544
|
+
return result;
|
|
10545
|
+
},
|
|
10546
|
+
async clear(prefix) {
|
|
10547
|
+
const keysToDelete = [];
|
|
10548
|
+
for (const key of store.keys()) if (key.startsWith(prefix)) keysToDelete.push(key);
|
|
10549
|
+
for (const key of keysToDelete) store.delete(key);
|
|
10550
|
+
}
|
|
10551
|
+
};
|
|
10552
|
+
}
|
|
10553
|
+
|
|
10554
|
+
//#endregion
|
|
10555
|
+
//#region src/panoptic/v2/storage/serializer.ts
|
|
10556
|
+
/**
|
|
10557
|
+
* Type guard to check if a value is a tagged BigInt.
|
|
10558
|
+
*/
|
|
10559
|
+
function isBigIntTagged(value) {
|
|
10560
|
+
return value !== null && typeof value === "object" && "__type" in value && value.__type === "bigint" && "value" in value && typeof value.value === "string";
|
|
10561
|
+
}
|
|
10562
|
+
/**
|
|
10563
|
+
* Replacer function for JSON.stringify that tags BigInt values.
|
|
10564
|
+
*/
|
|
10565
|
+
function replacer(_key, value) {
|
|
10566
|
+
if (typeof value === "bigint") return {
|
|
10567
|
+
__type: "bigint",
|
|
10568
|
+
value: value.toString()
|
|
10569
|
+
};
|
|
10570
|
+
return value;
|
|
10571
|
+
}
|
|
10572
|
+
/**
|
|
10573
|
+
* Reviver function for JSON.parse that restores tagged BigInt values.
|
|
10574
|
+
*/
|
|
10575
|
+
function reviver(_key, value) {
|
|
10576
|
+
if (isBigIntTagged(value)) return BigInt(value.value);
|
|
10577
|
+
return value;
|
|
10578
|
+
}
|
|
10579
|
+
/**
|
|
10580
|
+
* BigInt-safe JSON serializer for the Panoptic v2 SDK.
|
|
10581
|
+
*
|
|
10582
|
+
* This serializer handles BigInt values by tagging them with a special format
|
|
10583
|
+
* that can be restored on parse. The format is compatible with superjson
|
|
10584
|
+
* patterns used in Next.js and Remix for SSR hydration.
|
|
10585
|
+
*
|
|
10586
|
+
* @example
|
|
10587
|
+
* ```typescript
|
|
10588
|
+
* import { jsonSerializer } from 'panoptic-v2-sdk'
|
|
10589
|
+
*
|
|
10590
|
+
* const data = { amount: 1000000000000000000n, nested: { value: 42n } }
|
|
10591
|
+
*
|
|
10592
|
+
* // Serialize to JSON string
|
|
10593
|
+
* const json = jsonSerializer.stringify(data)
|
|
10594
|
+
* // '{"amount":{"__type":"bigint","value":"1000000000000000000"},"nested":{"value":{"__type":"bigint","value":"42"}}}'
|
|
10595
|
+
*
|
|
10596
|
+
* // Parse back to original types
|
|
10597
|
+
* const parsed = jsonSerializer.parse(json)
|
|
10598
|
+
* // { amount: 1000000000000000000n, nested: { value: 42n } }
|
|
10599
|
+
* ```
|
|
10600
|
+
*/
|
|
10601
|
+
const jsonSerializer = {
|
|
10602
|
+
stringify(value, space) {
|
|
10603
|
+
return JSON.stringify(value, replacer, space);
|
|
10604
|
+
},
|
|
10605
|
+
parse(text) {
|
|
10606
|
+
return JSON.parse(text, reviver);
|
|
10607
|
+
}
|
|
10608
|
+
};
|
|
10609
|
+
|
|
10610
|
+
//#endregion
|
|
10611
|
+
//#region src/panoptic/v2/sync/snapshotRecovery.ts
|
|
10612
|
+
const LOG_SCAN_WINDOW = 10000n;
|
|
10613
|
+
const rolesExecutionAbi = parseAbi(["function execTransactionWithRole(address to, uint256 value, bytes data, uint8 operation, bytes32 roleKey, bool shouldRevert)"]);
|
|
10614
|
+
/**
|
|
10615
|
+
* Recover position snapshot from the last dispatch transaction.
|
|
10616
|
+
* This is the primary recovery method - it finds the most recent dispatch()
|
|
10617
|
+
* call and extracts the finalPositionIdList from the calldata.
|
|
10618
|
+
*
|
|
10619
|
+
* @param params - Recovery parameters
|
|
10620
|
+
* @returns Snapshot recovery result
|
|
10621
|
+
*/
|
|
10622
|
+
async function recoverSnapshot(params) {
|
|
10623
|
+
const { client, poolAddress, account, toBlock } = params;
|
|
10624
|
+
const latestBlock = toBlock ?? await client.getBlockNumber();
|
|
10625
|
+
const searchFromBlock = params.fromBlock ?? 0n;
|
|
10626
|
+
if (latestBlock < searchFromBlock) return null;
|
|
10627
|
+
try {
|
|
10628
|
+
const events = await getSnapshotEventsForWindow({
|
|
10629
|
+
client,
|
|
10630
|
+
poolAddress,
|
|
10631
|
+
account,
|
|
10632
|
+
fromBlock: searchFromBlock,
|
|
10633
|
+
toBlock: latestBlock
|
|
10634
|
+
});
|
|
10635
|
+
return await recoverSnapshotFromEvents({
|
|
10636
|
+
client,
|
|
10637
|
+
account,
|
|
10638
|
+
events
|
|
10639
|
+
});
|
|
10640
|
+
} catch (error) {
|
|
10641
|
+
if (!isRangeLimitError(error)) throw error;
|
|
10642
|
+
}
|
|
10643
|
+
let windowToBlock = latestBlock;
|
|
10644
|
+
while (true) {
|
|
10645
|
+
const windowFromBlock = windowToBlock - searchFromBlock + 1n > LOG_SCAN_WINDOW ? windowToBlock - LOG_SCAN_WINDOW + 1n : searchFromBlock;
|
|
10646
|
+
const events = await getSnapshotEventsForWindow({
|
|
10647
|
+
client,
|
|
10648
|
+
poolAddress,
|
|
10649
|
+
account,
|
|
10650
|
+
fromBlock: windowFromBlock,
|
|
10651
|
+
toBlock: windowToBlock
|
|
10652
|
+
});
|
|
10653
|
+
const snapshot = await recoverSnapshotFromEvents({
|
|
10654
|
+
client,
|
|
10655
|
+
account,
|
|
10656
|
+
events
|
|
10657
|
+
});
|
|
10658
|
+
if (snapshot) return snapshot;
|
|
10659
|
+
if (windowFromBlock === searchFromBlock) break;
|
|
10660
|
+
windowToBlock = windowFromBlock - 1n;
|
|
10661
|
+
}
|
|
10662
|
+
return null;
|
|
10663
|
+
}
|
|
10664
|
+
/**
|
|
10665
|
+
* Detect provider errors that indicate the requested getLogs block range was
|
|
10666
|
+
* too wide (so the caller should retry with smaller windows). Covers the common
|
|
10667
|
+
* phrasings used by Alchemy, Infura, and other JSON-RPC providers.
|
|
10668
|
+
*/
|
|
10669
|
+
function isRangeLimitError(error) {
|
|
10670
|
+
const parts = [];
|
|
10671
|
+
let current = error;
|
|
10672
|
+
let depth = 0;
|
|
10673
|
+
while (current && typeof current === "object" && depth < 5) {
|
|
10674
|
+
const obj = current;
|
|
10675
|
+
if (typeof obj.message === "string") parts.push(obj.message);
|
|
10676
|
+
if (typeof obj.details === "string") parts.push(obj.details);
|
|
10677
|
+
current = obj.cause;
|
|
10678
|
+
depth += 1;
|
|
10679
|
+
}
|
|
10680
|
+
if (typeof error === "string") parts.push(error);
|
|
10681
|
+
const message = parts.join(" ").toLowerCase();
|
|
10682
|
+
if (!message) return false;
|
|
10683
|
+
return message.includes("block range") || message.includes("range is too large") || message.includes("range too large") || message.includes("query returned more than") || message.includes("too many results") || message.includes("log response size exceeded") || message.includes("exceeds the limit") || message.includes("range") && message.includes("limit");
|
|
10684
|
+
}
|
|
10685
|
+
async function getSnapshotEventsForWindow(params) {
|
|
10686
|
+
const { client, poolAddress, account, fromBlock, toBlock } = params;
|
|
10687
|
+
const [mintEvents, burnEvents, forceExerciseEvents, liquidationEvents] = await Promise.all([
|
|
10688
|
+
client.getLogs({
|
|
10689
|
+
address: poolAddress,
|
|
10690
|
+
event: {
|
|
10691
|
+
type: "event",
|
|
10692
|
+
name: "OptionMinted",
|
|
10693
|
+
inputs: [
|
|
10694
|
+
{
|
|
10695
|
+
type: "address",
|
|
10696
|
+
name: "recipient",
|
|
10697
|
+
indexed: true
|
|
10698
|
+
},
|
|
10699
|
+
{
|
|
10700
|
+
type: "uint256",
|
|
10701
|
+
name: "tokenId",
|
|
10702
|
+
indexed: true
|
|
10703
|
+
},
|
|
10704
|
+
{
|
|
10705
|
+
type: "uint256",
|
|
10706
|
+
name: "balanceData",
|
|
10707
|
+
indexed: false
|
|
10708
|
+
}
|
|
10709
|
+
]
|
|
10710
|
+
},
|
|
10711
|
+
args: { recipient: account },
|
|
10712
|
+
fromBlock,
|
|
10713
|
+
toBlock
|
|
10714
|
+
}),
|
|
10715
|
+
client.getLogs({
|
|
10716
|
+
address: poolAddress,
|
|
10717
|
+
event: {
|
|
10718
|
+
type: "event",
|
|
10719
|
+
name: "OptionBurnt",
|
|
10720
|
+
inputs: [
|
|
10721
|
+
{
|
|
10722
|
+
type: "address",
|
|
10723
|
+
name: "recipient",
|
|
10724
|
+
indexed: true
|
|
10725
|
+
},
|
|
10726
|
+
{
|
|
10727
|
+
type: "uint128",
|
|
10728
|
+
name: "positionSize",
|
|
10729
|
+
indexed: false
|
|
10730
|
+
},
|
|
10731
|
+
{
|
|
10732
|
+
type: "uint256",
|
|
10733
|
+
name: "tokenId",
|
|
10734
|
+
indexed: true
|
|
10735
|
+
},
|
|
10736
|
+
{
|
|
10737
|
+
type: "int256[4]",
|
|
10738
|
+
name: "premiaByLeg",
|
|
10739
|
+
indexed: false
|
|
10740
|
+
}
|
|
10741
|
+
]
|
|
10742
|
+
},
|
|
10743
|
+
args: { recipient: account },
|
|
10744
|
+
fromBlock,
|
|
10745
|
+
toBlock
|
|
10746
|
+
}),
|
|
10747
|
+
client.getLogs({
|
|
10748
|
+
address: poolAddress,
|
|
10749
|
+
event: {
|
|
10750
|
+
type: "event",
|
|
10751
|
+
name: "ForcedExercised",
|
|
10752
|
+
inputs: [
|
|
10753
|
+
{
|
|
10754
|
+
type: "address",
|
|
10755
|
+
name: "exercisor",
|
|
10756
|
+
indexed: true
|
|
10757
|
+
},
|
|
10758
|
+
{
|
|
10759
|
+
type: "address",
|
|
10760
|
+
name: "user",
|
|
10761
|
+
indexed: true
|
|
10762
|
+
},
|
|
10763
|
+
{
|
|
10764
|
+
type: "uint256",
|
|
10765
|
+
name: "tokenId",
|
|
10766
|
+
indexed: true
|
|
10767
|
+
},
|
|
10768
|
+
{
|
|
10769
|
+
type: "int256",
|
|
10770
|
+
name: "exerciseFee",
|
|
10771
|
+
indexed: false
|
|
10772
|
+
}
|
|
10773
|
+
]
|
|
10774
|
+
},
|
|
10775
|
+
args: { user: account },
|
|
10776
|
+
fromBlock,
|
|
10777
|
+
toBlock
|
|
10778
|
+
}),
|
|
10779
|
+
client.getLogs({
|
|
10780
|
+
address: poolAddress,
|
|
10781
|
+
event: {
|
|
10782
|
+
type: "event",
|
|
10783
|
+
name: "AccountLiquidated",
|
|
10784
|
+
inputs: [
|
|
10785
|
+
{
|
|
10786
|
+
type: "address",
|
|
10787
|
+
name: "liquidator",
|
|
10788
|
+
indexed: true
|
|
10789
|
+
},
|
|
10790
|
+
{
|
|
10791
|
+
type: "address",
|
|
10792
|
+
name: "liquidatee",
|
|
10793
|
+
indexed: true
|
|
10794
|
+
},
|
|
10795
|
+
{
|
|
10796
|
+
type: "int256",
|
|
10797
|
+
name: "bonusAmounts",
|
|
10798
|
+
indexed: false
|
|
10799
|
+
}
|
|
10800
|
+
]
|
|
10801
|
+
},
|
|
10802
|
+
args: { liquidatee: account },
|
|
10803
|
+
fromBlock,
|
|
10804
|
+
toBlock
|
|
10805
|
+
})
|
|
10806
|
+
]);
|
|
10807
|
+
return [
|
|
10808
|
+
...mintEvents,
|
|
10809
|
+
...burnEvents,
|
|
10810
|
+
...forceExerciseEvents,
|
|
10811
|
+
...liquidationEvents
|
|
10812
|
+
];
|
|
10813
|
+
}
|
|
10814
|
+
async function recoverSnapshotFromEvents(params) {
|
|
10815
|
+
const { client, account, events } = params;
|
|
10816
|
+
const allEvents = events.filter(isSnapshotCandidateEvent).sort((a, b) => {
|
|
10817
|
+
const blockDiff = Number(b.blockNumber - a.blockNumber);
|
|
10818
|
+
if (blockDiff !== 0) return blockDiff;
|
|
10819
|
+
return Number(b.logIndex - a.logIndex);
|
|
10820
|
+
});
|
|
10821
|
+
const seen = new Set();
|
|
10822
|
+
const uniqueEvents = allEvents.filter((e) => {
|
|
10823
|
+
if (seen.has(e.transactionHash)) return false;
|
|
10824
|
+
seen.add(e.transactionHash);
|
|
10825
|
+
return true;
|
|
10826
|
+
});
|
|
10827
|
+
for (const event of uniqueEvents) try {
|
|
10828
|
+
const [tx, block] = await Promise.all([client.getTransaction({ hash: event.transactionHash }), client.getBlock({ blockNumber: event.blockNumber })]);
|
|
10829
|
+
const candidates = decodeAllDispatchCalldata(tx.input);
|
|
10830
|
+
const decoded = selectDispatchForAccount(candidates, account, tx.from) ?? selectDispatchProvenByAccountEvent(candidates);
|
|
10831
|
+
if (!decoded) continue;
|
|
10832
|
+
return {
|
|
10833
|
+
success: true,
|
|
10834
|
+
positionIds: decoded.positionIds,
|
|
10835
|
+
blockNumber: event.blockNumber,
|
|
10836
|
+
blockHash: block.hash,
|
|
10837
|
+
transactionHash: event.transactionHash
|
|
10838
|
+
};
|
|
10839
|
+
} catch {}
|
|
10840
|
+
return null;
|
|
10841
|
+
}
|
|
10842
|
+
function isSnapshotCandidateEvent(event) {
|
|
10843
|
+
return event.transactionHash !== null && event.blockNumber !== null && event.logIndex !== null;
|
|
10844
|
+
}
|
|
10845
|
+
function decodeSnapshotTransaction(params) {
|
|
10846
|
+
const { account, pool, rolesContext } = params;
|
|
10847
|
+
let dispatchInput = params.input;
|
|
10848
|
+
let dispatchSender = params.from;
|
|
10849
|
+
if (rolesContext) {
|
|
10850
|
+
if (params.to === null || getAddress(params.to) !== getAddress(rolesContext.modifier) || getAddress(params.from) !== getAddress(rolesContext.member)) return null;
|
|
10851
|
+
try {
|
|
10852
|
+
const decodedRoleCall = decodeFunctionData({
|
|
10853
|
+
abi: rolesExecutionAbi,
|
|
10854
|
+
data: params.input
|
|
10855
|
+
});
|
|
10856
|
+
const [target, value, data, operation, roleKey, shouldRevert] = decodedRoleCall.args;
|
|
10857
|
+
if (!pool || getAddress(target) !== getAddress(pool) || value !== 0n || operation !== 0 || roleKey.toLowerCase() !== rolesContext.roleKey.toLowerCase() || !shouldRevert) return null;
|
|
10858
|
+
dispatchInput = data;
|
|
10859
|
+
if (!account) return null;
|
|
10860
|
+
dispatchSender = account;
|
|
10861
|
+
} catch {
|
|
10862
|
+
return null;
|
|
10863
|
+
}
|
|
10864
|
+
} else if (pool && (params.to === null || getAddress(params.to) !== getAddress(pool))) return null;
|
|
10865
|
+
const candidates = decodeAllDispatchCalldata(dispatchInput);
|
|
10866
|
+
if (candidates.length === 0) return null;
|
|
10867
|
+
return account ? selectDispatchForAccount(candidates, account, dispatchSender) : candidates[candidates.length - 1];
|
|
10868
|
+
}
|
|
10869
|
+
/**
|
|
10870
|
+
* Recover position snapshot from a specific dispatch transaction hash.
|
|
10871
|
+
*
|
|
10872
|
+
* This is an O(1) alternative to {@link recoverSnapshot} when you already
|
|
10873
|
+
* know the tx hash of the most recent dispatch. It fetches the transaction,
|
|
10874
|
+
* decodes the `finalPositionIdList` from the calldata, and returns the result
|
|
10875
|
+
* — no event scanning required.
|
|
10876
|
+
*
|
|
10877
|
+
* @param params - Recovery parameters including the transaction hash
|
|
10878
|
+
* @returns Snapshot recovery result, or null if the tx is not a dispatch call
|
|
10879
|
+
*
|
|
10880
|
+
* @example
|
|
10881
|
+
* ```typescript
|
|
10882
|
+
* const snapshot = await recoverSnapshotFromTx({
|
|
10883
|
+
* client,
|
|
10884
|
+
* transactionHash: '0xabc...',
|
|
10885
|
+
* })
|
|
10886
|
+
* if (snapshot) {
|
|
10887
|
+
* console.log('Open positions:', snapshot.positionIds)
|
|
10888
|
+
* }
|
|
10889
|
+
* ```
|
|
10890
|
+
*/
|
|
10891
|
+
async function recoverSnapshotFromTx(params) {
|
|
10892
|
+
const { client, transactionHash, account, pool, rolesContext } = params;
|
|
10893
|
+
const tx = await client.getTransaction({ hash: transactionHash });
|
|
10894
|
+
if (tx.blockNumber == null) return null;
|
|
10895
|
+
const decoded = decodeSnapshotTransaction({
|
|
10896
|
+
input: tx.input,
|
|
10897
|
+
from: tx.from,
|
|
10898
|
+
to: tx.to,
|
|
10899
|
+
account,
|
|
10900
|
+
pool,
|
|
10901
|
+
rolesContext
|
|
10902
|
+
});
|
|
10903
|
+
if (!decoded) return null;
|
|
10904
|
+
const blockNumber = tx.blockNumber;
|
|
10905
|
+
const block = await client.getBlock({ blockNumber });
|
|
10906
|
+
return {
|
|
10907
|
+
success: true,
|
|
10908
|
+
positionIds: decoded.positionIds,
|
|
10909
|
+
blockNumber,
|
|
10910
|
+
blockHash: block.hash,
|
|
10911
|
+
transactionHash
|
|
10912
|
+
};
|
|
10913
|
+
}
|
|
10914
|
+
/**
|
|
10915
|
+
* Decode position IDs from dispatch calldata.
|
|
10916
|
+
*
|
|
10917
|
+
* When the tx embeds multiple dispatch/dispatchFrom calls (e.g. a force-exercise
|
|
10918
|
+
* multicall), this returns the last one — prefer {@link decodeAllDispatchCalldata}
|
|
10919
|
+
* plus {@link selectDispatchForAccount} when the account context matters.
|
|
10920
|
+
*
|
|
10921
|
+
* @param input - Transaction input data
|
|
10922
|
+
* @returns Decoded dispatch data or null if not a dispatch call
|
|
10923
|
+
*/
|
|
10924
|
+
function decodeDispatchCalldata(input) {
|
|
10925
|
+
const all = decodeAllDispatchCalldata(input);
|
|
10926
|
+
return all.length > 0 ? all[all.length - 1] : null;
|
|
10927
|
+
}
|
|
10928
|
+
/**
|
|
10929
|
+
* Decode every dispatch/dispatchFrom call embedded in transaction calldata.
|
|
10930
|
+
*
|
|
10931
|
+
* A single tx can carry more than one such call — most importantly a force-exercise
|
|
10932
|
+
* `multicall` that bundles the exercisor's own `dispatch` together with the victim's
|
|
10933
|
+
* `dispatchFrom`. Returning all candidates lets the caller select the one attributable
|
|
10934
|
+
* to the relevant account ({@link selectDispatchForAccount}).
|
|
10935
|
+
*
|
|
10936
|
+
* @param input - Transaction input data
|
|
10937
|
+
* @returns All decoded dispatch calls, in calldata order (may be empty)
|
|
10938
|
+
*/
|
|
10939
|
+
function decodeAllDispatchCalldata(input) {
|
|
10940
|
+
const direct = decodeDirectDispatch(input);
|
|
10941
|
+
if (direct) return [direct];
|
|
10942
|
+
return decodeWrappedDispatch(input);
|
|
10943
|
+
}
|
|
10944
|
+
/**
|
|
10945
|
+
* Select the dispatch call attributable to `account` from a set of candidates.
|
|
10946
|
+
*
|
|
10947
|
+
* - `dispatchFrom`: matches when `targetAccount === account` (builder/exercisor acting
|
|
10948
|
+
* on behalf of the account).
|
|
10949
|
+
* - `dispatch` (no `targetAccount`): matches only when the tx sender is the account.
|
|
10950
|
+
*
|
|
10951
|
+
* Returns the LAST matching candidate (in calldata order), or null when none are
|
|
10952
|
+
* attributable to `account`. The last dispatch reflects the final post-transaction
|
|
10953
|
+
* state when a single tx contains multiple dispatches for the same account (e.g. a
|
|
10954
|
+
* multicall). Matching also prevents accepting a third party's `dispatch` (e.g. the
|
|
10955
|
+
* exercisor's own, bundled in a force-exercise multicall) as the account's snapshot.
|
|
10956
|
+
*
|
|
10957
|
+
* @param candidates - Decoded dispatch calls from a single tx, in calldata order
|
|
10958
|
+
* @param account - Account whose position list we are recovering
|
|
10959
|
+
* @param txFrom - The transaction sender (`tx.from`)
|
|
10960
|
+
*/
|
|
10961
|
+
function selectDispatchForAccount(candidates, account, txFrom) {
|
|
10962
|
+
const acct = account.toLowerCase();
|
|
10963
|
+
let match = null;
|
|
10964
|
+
for (const candidate of candidates) if (candidate.targetAccount) {
|
|
10965
|
+
if (candidate.targetAccount.toLowerCase() === acct) match = candidate;
|
|
10966
|
+
} else if (txFrom.toLowerCase() === acct) match = candidate;
|
|
10967
|
+
return match;
|
|
10968
|
+
}
|
|
10969
|
+
function selectDispatchProvenByAccountEvent(candidates) {
|
|
10970
|
+
let match = null;
|
|
10971
|
+
for (const candidate of candidates) if (candidate.targetAccount === void 0) match = candidate;
|
|
10972
|
+
return match;
|
|
10973
|
+
}
|
|
10974
|
+
/**
|
|
10975
|
+
* Try to decode input as a direct dispatch or dispatchFrom call.
|
|
10976
|
+
*/
|
|
10977
|
+
function decodeDirectDispatch(input) {
|
|
10978
|
+
try {
|
|
10979
|
+
const decoded = decodeFunctionData({
|
|
10980
|
+
abi: panopticPoolV2Abi,
|
|
10981
|
+
data: input
|
|
10982
|
+
});
|
|
10983
|
+
if (decoded.functionName === "dispatch") {
|
|
10984
|
+
const args = decoded.args;
|
|
10985
|
+
return { positionIds: [...args[1]] };
|
|
10986
|
+
}
|
|
10987
|
+
if (decoded.functionName === "dispatchFrom") {
|
|
10988
|
+
const args = decoded.args;
|
|
10989
|
+
return {
|
|
10990
|
+
positionIds: [...args[3]],
|
|
10991
|
+
targetAccount: args[1]
|
|
10992
|
+
};
|
|
10993
|
+
}
|
|
10994
|
+
return null;
|
|
10995
|
+
} catch {
|
|
10996
|
+
return null;
|
|
10997
|
+
}
|
|
10998
|
+
}
|
|
10999
|
+
/**
|
|
11000
|
+
* 4-byte function selectors for dispatch/dispatchFrom, derived from the generated ABI.
|
|
11001
|
+
* Used for scanning raw calldata inside smart contract wallet wrappers.
|
|
11002
|
+
*/
|
|
11003
|
+
const DISPATCH_SELECTOR = toFunctionSelector(panopticPoolV2Abi.find((e) => e.type === "function" && e.name === "dispatch")).slice(2);
|
|
11004
|
+
const DISPATCH_FROM_SELECTOR = toFunctionSelector(panopticPoolV2Abi.find((e) => e.type === "function" && e.name === "dispatchFrom")).slice(2);
|
|
11005
|
+
/**
|
|
11006
|
+
* Scan raw transaction input for embedded dispatch calldata.
|
|
11007
|
+
*
|
|
11008
|
+
* Smart contract wallets (Safe, Turnkey, ERC-4337) and vault managers
|
|
11009
|
+
* wrap dispatch calls inside executeBatch → manage → dispatch chains.
|
|
11010
|
+
* Instead of decoding each wrapper layer, we scan the raw hex for the
|
|
11011
|
+
* dispatch function selector and attempt to decode from that offset.
|
|
11012
|
+
*
|
|
11013
|
+
* This handles arbitrary nesting depth without knowing wrapper ABIs.
|
|
11014
|
+
*/
|
|
11015
|
+
function decodeWrappedDispatch(input) {
|
|
11016
|
+
const hex = input.slice(2).toLowerCase();
|
|
11017
|
+
const selectors = [DISPATCH_SELECTOR, DISPATCH_FROM_SELECTOR];
|
|
11018
|
+
const results = [];
|
|
11019
|
+
let offset = 0;
|
|
11020
|
+
while (offset < hex.length) {
|
|
11021
|
+
let earliestIdx = -1;
|
|
11022
|
+
for (const selector of selectors) {
|
|
11023
|
+
const idx = hex.indexOf(selector, offset);
|
|
11024
|
+
if (idx !== -1 && (earliestIdx === -1 || idx < earliestIdx)) earliestIdx = idx;
|
|
11025
|
+
}
|
|
11026
|
+
if (earliestIdx === -1) break;
|
|
11027
|
+
const candidate = `0x${hex.slice(earliestIdx)}`;
|
|
11028
|
+
const result = decodeDirectDispatch(candidate);
|
|
11029
|
+
if (result) results.push(result);
|
|
11030
|
+
offset = earliestIdx + 8;
|
|
11031
|
+
}
|
|
11032
|
+
return results;
|
|
11033
|
+
}
|
|
11034
|
+
|
|
11035
|
+
//#endregion
|
|
11036
|
+
//#region src/panoptic/v2/sync/getTrackedPositionIds.ts
|
|
11037
|
+
/**
|
|
11038
|
+
* Verify that a recovered position-id list still matches the account's current
|
|
11039
|
+
* on-chain state. `getFullPositionsData` reverts when the list contains a
|
|
11040
|
+
* tokenId the account no longer holds, so a successful call is proof the list
|
|
11041
|
+
* is live. Returns `false` on revert so the caller can fall back to an
|
|
11042
|
+
* authoritative event scan instead of trusting a stale checkpoint.
|
|
11043
|
+
*/
|
|
11044
|
+
async function isPositionListLive(params) {
|
|
11045
|
+
const { client, poolAddress, account, positionIds, blockNumber } = params;
|
|
11046
|
+
if (positionIds.length === 0) return true;
|
|
11047
|
+
try {
|
|
11048
|
+
await client.readContract({
|
|
11049
|
+
address: poolAddress,
|
|
11050
|
+
abi: panopticPoolV2Abi,
|
|
11051
|
+
functionName: "getFullPositionsData",
|
|
11052
|
+
args: [
|
|
11053
|
+
account,
|
|
11054
|
+
false,
|
|
11055
|
+
positionIds
|
|
11056
|
+
],
|
|
11057
|
+
blockNumber
|
|
11058
|
+
});
|
|
11059
|
+
return true;
|
|
11060
|
+
} catch {
|
|
11061
|
+
return false;
|
|
11062
|
+
}
|
|
11063
|
+
}
|
|
11064
|
+
/**
|
|
11065
|
+
* Get all tracked position IDs for an account from local cache.
|
|
11066
|
+
*
|
|
11067
|
+
* This function reads from the storage adapter without making RPC calls.
|
|
11068
|
+
* The positions returned may include closed positions if the cache is stale.
|
|
11069
|
+
* Use `getPositions()` for authoritative on-chain data.
|
|
11070
|
+
*
|
|
11071
|
+
* @param params - Query parameters
|
|
11072
|
+
* @returns Array of position token IDs
|
|
11073
|
+
*/
|
|
11074
|
+
async function getTrackedPositionIds(params) {
|
|
11075
|
+
const { chainId, poolAddress, account, storage } = params;
|
|
11076
|
+
const key = getPositionsKey(chainId, poolAddress, account);
|
|
11077
|
+
const data = await storage.get(key);
|
|
11078
|
+
if (!data) return [];
|
|
11079
|
+
try {
|
|
11080
|
+
const positionIds = jsonSerializer.parse(data);
|
|
11081
|
+
return positionIds;
|
|
11082
|
+
} catch {
|
|
11083
|
+
return [];
|
|
11084
|
+
}
|
|
11085
|
+
}
|
|
11086
|
+
/**
|
|
11087
|
+
* Check if a specific position ID is tracked.
|
|
11088
|
+
*
|
|
11089
|
+
* @param params - Query parameters
|
|
11090
|
+
* @param tokenId - Token ID to check
|
|
11091
|
+
* @returns Whether the position is tracked
|
|
11092
|
+
*/
|
|
11093
|
+
async function isPositionTracked(params, tokenId) {
|
|
11094
|
+
const positionIds = await getTrackedPositionIds(params);
|
|
11095
|
+
return positionIds.some((id) => id === tokenId);
|
|
11096
|
+
}
|
|
11097
|
+
/**
|
|
11098
|
+
* Clear all tracked positions for an account.
|
|
11099
|
+
*
|
|
11100
|
+
* @param params - Query parameters
|
|
11101
|
+
*/
|
|
11102
|
+
async function clearTrackedPositions(params) {
|
|
11103
|
+
const { chainId, poolAddress, account, storage } = params;
|
|
11104
|
+
const key = getPositionsKey(chainId, poolAddress, account);
|
|
11105
|
+
await storage.delete(key);
|
|
11106
|
+
}
|
|
11107
|
+
/**
|
|
11108
|
+
* Get the authoritative list of open position IDs from the chain.
|
|
11109
|
+
*
|
|
11110
|
+
* Recovers the `finalPositionIdList` from the account's most recent
|
|
11111
|
+
* `dispatch()` transaction calldata. This is the on-chain–validated set of
|
|
11112
|
+
* open positions (verified against `s_positionsHash` by the contract) and
|
|
11113
|
+
* requires only a few RPC calls regardless of account history length.
|
|
11114
|
+
*
|
|
11115
|
+
* This is the recommended way to get position IDs for `existingPositionIds`
|
|
11116
|
+
* (openPosition) and `positionIdList` (closePosition).
|
|
11117
|
+
*
|
|
11118
|
+
* If `storage` is provided, the local cache is updated with the result.
|
|
11119
|
+
*
|
|
11120
|
+
* @param params - Query parameters
|
|
11121
|
+
* @returns Array of currently open position token IDs, or null if no snapshot was found
|
|
11122
|
+
*/
|
|
11123
|
+
async function getOpenPositionIds(params) {
|
|
11124
|
+
const { client, chainId, poolAddress, account, storage, lastDispatchTxHash, fromBlock, toBlock, rolesContext } = params;
|
|
11125
|
+
let snapshot = null;
|
|
11126
|
+
if (lastDispatchTxHash) try {
|
|
11127
|
+
const candidate = await recoverSnapshotFromTx({
|
|
11128
|
+
client,
|
|
11129
|
+
transactionHash: lastDispatchTxHash,
|
|
11130
|
+
account,
|
|
11131
|
+
pool: poolAddress,
|
|
11132
|
+
rolesContext
|
|
11133
|
+
});
|
|
11134
|
+
if (candidate && await isPositionListLive({
|
|
11135
|
+
client,
|
|
11136
|
+
poolAddress,
|
|
11137
|
+
account,
|
|
11138
|
+
positionIds: candidate.positionIds,
|
|
11139
|
+
blockNumber: toBlock
|
|
11140
|
+
})) snapshot = candidate;
|
|
11141
|
+
} catch {
|
|
11142
|
+
snapshot = null;
|
|
11143
|
+
}
|
|
11144
|
+
if (!snapshot) snapshot = await recoverSnapshot({
|
|
11145
|
+
client,
|
|
11146
|
+
poolAddress,
|
|
11147
|
+
account,
|
|
11148
|
+
fromBlock,
|
|
11149
|
+
toBlock
|
|
11150
|
+
});
|
|
11151
|
+
if (!snapshot) return null;
|
|
11152
|
+
if (storage) {
|
|
11153
|
+
const key = getPositionsKey(chainId, poolAddress, account);
|
|
11154
|
+
await storage.set(key, jsonSerializer.stringify(snapshot.positionIds));
|
|
11155
|
+
}
|
|
11156
|
+
return snapshot.positionIds;
|
|
11157
|
+
}
|
|
11158
|
+
|
|
11159
|
+
//#endregion
|
|
11160
|
+
export { AccountInsolventError as AccountInsolventError$1, AlreadyInitializedError as AlreadyInitializedError$1, BatchValidationError as BatchValidationError$1, BelowMinimumRedemptionError as BelowMinimumRedemptionError$1, CastingError as CastingError$1, ChunkHasZeroLiquidityError as ChunkHasZeroLiquidityError$1, ChunkLimitError as ChunkLimitError$1, CrossPoolError as CrossPoolError$1, DEFAULT_MAX_SPREAD as DEFAULT_MAX_SPREAD$1, DEFAULT_MINT_BUFFER as DEFAULT_MINT_BUFFER$1, DEFAULT_VEGOID as DEFAULT_VEGOID$1, DepositTooLargeError as DepositTooLargeError$1, DuplicateTokenIdError as DuplicateTokenIdError$1, EffectiveLiquidityAboveThresholdError as EffectiveLiquidityAboveThresholdError$1, ExceedsMaximumRedemptionError as ExceedsMaximumRedemptionError$1, InputListFailError as InputListFailError$1, InsufficientCreditLiquidityError as InsufficientCreditLiquidityError$1, InvalidBuilderCodeError as InvalidBuilderCodeError$1, InvalidHistoryRangeError as InvalidHistoryRangeError$1, InvalidTickBoundError as InvalidTickBoundError$1, InvalidTickError as InvalidTickError$1, InvalidTickLimitsError, InvalidTokenIdParameterError as InvalidTokenIdParameterError$1, InvalidUniswapCallbackError as InvalidUniswapCallbackError$1, LEG_BITS as LEG_BITS$1, LEG_LIMITS as LEG_LIMITS$1, LEG_MASKS as LEG_MASKS$1, LengthMismatchError as LengthMismatchError$1, LiquidityTooHighError as LiquidityTooHighError$1, LoanSlotExhaustedError as LoanSlotExhaustedError$1, MINT_BUFFER as MINT_BUFFER$1, MINT_BUFFER_DENOMINATOR as MINT_BUFFER_DENOMINATOR$1, MaxRetriesExceededError as MaxRetriesExceededError$1, MissingPositionIdsError as MissingPositionIdsError$1, MulticallNoDataError, MulticallResultFailedError, MulticallResultMissingError, NetLiquidityZeroError as NetLiquidityZeroError$1, NetworkMismatchError as NetworkMismatchError$1, NoLegsExercisableError as NoLegsExercisableError$1, NoLoanPositionsError, NotALongLegError as NotALongLegError$1, NotBuilderError as NotBuilderError$1, NotEnoughLiquidityInChunkError as NotEnoughLiquidityInChunkError$1, NotEnoughTokensError as NotEnoughTokensError$1, NotGuardianError as NotGuardianError$1, NotMarginCalledError as NotMarginCalledError$1, NotPanopticPoolError as NotPanopticPoolError$1, OracleRateLimitedError as OracleRateLimitedError$1, PanopticError as PanopticError$1, PanopticHelperNotDeployedError as PanopticHelperNotDeployedError$1, PanopticValidationError as PanopticValidationError$1, PoolNotInitializedError as PoolNotInitializedError$1, PositionCountNotZeroError as PositionCountNotZeroError$1, PositionNotOwnedError as PositionNotOwnedError$1, PositionSnapshotNotFoundError as PositionSnapshotNotFoundError$1, PositionTooLargeError as PositionTooLargeError$1, PriceBoundFailError as PriceBoundFailError$1, PriceImpactTooLargeError as PriceImpactTooLargeError$1, ProviderLagError as ProviderLagError$1, ReentrancyError as ReentrancyError$1, RpcError as RpcError$1, RpcResponseError as RpcResponseError$1, STANDARD_TICK_WIDTHS as STANDARD_TICK_WIDTHS$1, STRIKE_CONVERSION_FACTOR, SafeModeError as SafeModeError$1, StaleDataError as StaleDataError$1, StaleOracleError as StaleOracleError$1, StorageDataNotFoundError, SwapTokenMismatchError as SwapTokenMismatchError$1, SyncTimeoutError as SyncTimeoutError$1, TOKEN_ID_BITS as TOKEN_ID_BITS$1, TokenIdHasZeroLegsError as TokenIdHasZeroLegsError$1, TooManyLegsOpenError as TooManyLegsOpenError$1, TransferFailedError as TransferFailedError$1, UnauthorizedUniswapCallbackError as UnauthorizedUniswapCallbackError$1, UnderOverFlowError as UnderOverFlowError$1, UnhealthyPoolError as UnhealthyPoolError$1, WrongPoolIdError as WrongPoolIdError$1, WrongUniswapPoolError as WrongUniswapPoolError$1, ZeroAddressError as ZeroAddressError$1, ZeroCollateralRequirementError as ZeroCollateralRequirementError$1, applyMintBuffer as applyMintBuffer$1, applyMintBufferPerToken as applyMintBufferPerToken$1, clearTrackedPositions as clearTrackedPositions$1, collateralTrackerV2Abi, createFileStorage as createFileStorage$1, createMemoryStorage as createMemoryStorage$1, createTxResult, decodeAllDispatchCalldata as decodeAllDispatchCalldata$1, decodeDispatchCalldata as decodeDispatchCalldata$1, decodeLeftRightSigned as decodeLeftRightSigned$1, decodeLeftRightUnsigned as decodeLeftRightUnsigned$1, decodeOraclePack as decodeOraclePack$1, decodeOracleRiskParameters as decodeOracleRiskParameters$1, decodeOracleTiming as decodeOracleTiming$1, decodePosition, decodePositionBalance$1 as decodePositionBalance, decodeTickSpacing as decodeTickSpacing$1, diagnoseOracleSafeMode as diagnoseOracleSafeMode$1, fetchPoolId as fetchPoolId$1, getBlockMeta as getBlockMeta$1, getClosedPositionsKey as getClosedPositionsKey$1, getOpenPositionIds as getOpenPositionIds$1, getOracleRiskParameters as getOracleRiskParameters$1, getOracleState as getOracleState$1, getPendingPositionsKey as getPendingPositionsKey$1, getPool as getPool$1, getPoolMetaKey as getPoolMetaKey$1, getPoolMetadata as getPoolMetadata$1, getPoolPrefix as getPoolPrefix$1, getPosition as getPosition$1, getPositionGreeks as getPositionGreeks$1, getPositionMetaKey as getPositionMetaKey$1, getPositions as getPositions$1, getPositionsKey as getPositionsKey$1, getRiskParameters as getRiskParameters$1, getSchemaVersionKey as getSchemaVersionKey$1, getSyncCheckpointKey as getSyncCheckpointKey$1, getTrackedChunksKey as getTrackedChunksKey$1, getTrackedPositionIds as getTrackedPositionIds$1, getUtilization as getUtilization$1, isPanopticErrorType as isPanopticErrorType$1, isPositionTracked as isPositionTracked$1, jsonSerializer as jsonSerializer$1, mintableAfterBuffer as mintableAfterBuffer$1, oracleEpochAt as oracleEpochAt$1, panopticErrorsAbi, panopticFactoryV3Abi, panopticFactoryV4Abi, panopticGuardianAbi, panopticPoolV2Abi, panopticQueryAbi, parsePanopticError as parsePanopticError$1, recoverSnapshot as recoverSnapshot$1, recoverSnapshotFromTx as recoverSnapshotFromTx$1, riskEngineAbi, selectDispatchForAccount as selectDispatchForAccount$1, semiFungiblePositionManagerV3Abi, semiFungiblePositionManagerV4Abi, stateViewAbi, submitWrite, uniswapV3PoolAbi, validateBuilderCode as validateBuilderCode$1 };
|
|
11161
|
+
//# sourceMappingURL=getTrackedPositionIds-zYPIs5dt.js.map
|