- recenter.ts: parse isUp from Recentered event logs instead of a follow-up eth_call that would decode wrong post-recenter state - recenter.ts: remove hardcoded private key from comment; add blocks>0 guard in mineBlocks; call provider.destroy() to prevent leaked intervals - market.ts: snapshot KRK balance before buy to compute krkBought as delta instead of cumulative total; call provider.destroy() on exit; remove unused withdraw entry from WETH_ABI Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
86 lines
3.1 KiB
TypeScript
86 lines
3.1 KiB
TypeScript
/**
|
|
* LiquidityManager recenter helper and Anvil block-mining utility.
|
|
*
|
|
* Pure Node.js — no browser/Playwright dependency.
|
|
*/
|
|
import { Interface, JsonRpcProvider, Wallet } from 'ethers';
|
|
import { rpcCall } from './rpc.js';
|
|
import { waitForReceipt } from './swap.js';
|
|
|
|
const RECENTER_ABI = [
|
|
'function recenter() external returns (bool isUp)',
|
|
'event Recentered(int24 indexed currentTick, bool indexed isUp)',
|
|
];
|
|
|
|
export interface RecenterConfig {
|
|
rpcUrl: string;
|
|
/** Address of the LiquidityManager contract */
|
|
lmAddress: string;
|
|
/**
|
|
* Private key of an account with recenter access.
|
|
* In the local Anvil stack, recenterAccess is granted to the deployer
|
|
* (Anvil account index 0) and the txnBot (Anvil account index 2).
|
|
* See scripts/bootstrap-common.sh for how access is granted at deploy time.
|
|
*/
|
|
privateKey: string;
|
|
/** Address corresponding to privateKey */
|
|
accountAddress: string;
|
|
}
|
|
|
|
/**
|
|
* Call LiquidityManager.recenter() from an authorized account.
|
|
*
|
|
* Reads isUp from the Recentered(int24 currentTick, bool isUp) event emitted
|
|
* by the mined transaction — not from a follow-up eth_call, which would
|
|
* simulate a second recenter on already-updated state and return the wrong value.
|
|
*
|
|
* @returns isUp - true if price moved up
|
|
*/
|
|
export async function triggerRecenter(config: RecenterConfig): Promise<boolean> {
|
|
const provider = new JsonRpcProvider(config.rpcUrl);
|
|
const wallet = new Wallet(config.privateKey, provider);
|
|
|
|
const iface = new Interface(RECENTER_ABI);
|
|
const data = iface.encodeFunctionData('recenter', []);
|
|
|
|
console.log('[recenter] Calling LiquidityManager.recenter()...');
|
|
const tx = await wallet.sendTransaction({ to: config.lmAddress, data });
|
|
await waitForReceipt(config.rpcUrl, tx.hash);
|
|
console.log(`[recenter] recenter() mined: ${tx.hash}`);
|
|
|
|
provider.destroy();
|
|
|
|
// Parse isUp from the Recentered event in the receipt logs.
|
|
// A follow-up eth_call would simulate on post-recenter state and give the wrong result.
|
|
const receipt = (await rpcCall(config.rpcUrl, 'eth_getTransactionReceipt', [tx.hash])) as {
|
|
logs: Array<{ address: string; topics: string[]; data: string }>;
|
|
};
|
|
|
|
const recenterEventTopic = iface.getEvent('Recentered')!.topicHash;
|
|
const log = receipt.logs.find(
|
|
l =>
|
|
l.address.toLowerCase() === config.lmAddress.toLowerCase() &&
|
|
l.topics[0] === recenterEventTopic,
|
|
);
|
|
if (!log) {
|
|
throw new Error('[recenter] Recentered event not found in receipt — did the tx revert silently?');
|
|
}
|
|
|
|
const parsed = iface.parseLog({ topics: log.topics, data: log.data });
|
|
const isUp = Boolean(parsed!.args.isUp);
|
|
console.log(`[recenter] isUp = ${isUp}`);
|
|
|
|
return isUp;
|
|
}
|
|
|
|
/**
|
|
* Mine `blocks` empty blocks on Anvil to advance time.
|
|
* Useful to get past MIN_RECENTER_INTERVAL (if set).
|
|
*
|
|
* Uses the anvil_mine RPC method.
|
|
*/
|
|
export async function mineBlocks(rpcUrl: string, blocks: number): Promise<void> {
|
|
if (blocks <= 0) throw new Error(`mineBlocks: blocks must be > 0, got ${blocks}`);
|
|
await rpcCall(rpcUrl, 'anvil_mine', ['0x' + blocks.toString(16)]);
|
|
console.log(`[recenter] Mined ${blocks} blocks`);
|
|
}
|