Merge pull request 'fix: services/txnBot still has dead recenterAccess read infrastructure (#887)' (#917) from fix/issue-887 into master

This commit is contained in:
johba 2026-03-17 14:56:56 +01:00
commit 5adf70518f
6 changed files with 5 additions and 155 deletions

View file

@ -8,7 +8,7 @@
"build": "tsc -p tsconfig.build.json",
"start": "node dist/service.js",
"dev": "tsx watch src/service.ts",
"test": "node --test --import tsx src/recenterAccess.test.ts",
"test": "node --test --import tsx",
"lint": "eslint src/**/*.ts",
"lint:fix": "eslint --fix src/**/*.ts",
"format": "prettier --write src/**/*.ts",

View file

@ -1,45 +0,0 @@
import assert from 'node:assert/strict';
import test from 'node:test';
import { ethers } from 'ethers';
import { hasRecenterAccess, readRecenterAccess, type RecenterAccessReader } from './recenterAccess.js';
const ZERO_ADDRESS = ethers.ZeroAddress;
class MockRecenterAccessReader implements RecenterAccessReader {
constructor(
private readonly value: string,
private readonly shouldThrow = false
) {}
async recenterAccess(): Promise<string> {
if (this.shouldThrow) {
throw new Error('read failed');
}
return this.value;
}
}
test('readRecenterAccess returns zero address for empty or zero values', async () => {
const reader = new MockRecenterAccessReader('0x0000000000000000000000000000000000000000');
assert.equal(await readRecenterAccess(reader, ZERO_ADDRESS), ZERO_ADDRESS);
const emptyReader = new MockRecenterAccessReader('');
assert.equal(await readRecenterAccess(emptyReader, ZERO_ADDRESS), ZERO_ADDRESS);
});
test('readRecenterAccess normalises checksum addresses', async () => {
const reader = new MockRecenterAccessReader('0x3c44cdddb6a900fa2b585dd299e03d12fa4293bc');
assert.equal(await readRecenterAccess(reader, ZERO_ADDRESS), '0x3C44CdDdB6a900fa2b585dd299e03d12FA4293BC');
});
test('readRecenterAccess throws when reader fails', async () => {
const reader = new MockRecenterAccessReader('0x0', true);
await assert.rejects(() => readRecenterAccess(reader, ZERO_ADDRESS), /read failed/);
});
test('hasRecenterAccess acknowledges zero or wallet matches', () => {
const wallet = '0x3C44CdDdB6a900fa2b585dd299e03d12FA4293BC';
assert.equal(hasRecenterAccess(ZERO_ADDRESS, wallet, ZERO_ADDRESS), true);
assert.equal(hasRecenterAccess(wallet, wallet, ZERO_ADDRESS), true);
assert.equal(hasRecenterAccess('0x5cFB5CDd3E8723ba98312c90a43a4d6Ac6121240', wallet, ZERO_ADDRESS), false);
});

View file

@ -1,40 +0,0 @@
import { ethers } from 'ethers';
export interface RecenterAccessReader {
recenterAccess(): Promise<string>;
}
export async function readRecenterAccess(reader: RecenterAccessReader, zeroAddress: string): Promise<string> {
let raw: string;
try {
raw = await reader.recenterAccess();
} catch (error) {
throw new Error(`Failed to read recenterAccess: ${(error as Error).message}`);
}
if (typeof raw !== 'string' || raw.length === 0) {
return zeroAddress;
}
if (raw === zeroAddress) {
return zeroAddress;
}
try {
return ethers.getAddress(raw);
} catch (error) {
throw new Error(`Invalid recenterAccess address: ${(error as Error).message}`);
}
}
export function hasRecenterAccess(recenterAddress: string, walletAddress: string, zeroAddress: string): boolean {
if (recenterAddress === zeroAddress) {
return true;
}
try {
return ethers.getAddress(recenterAddress) === ethers.getAddress(walletAddress);
} catch {
return false;
}
}

View file

@ -1,5 +1,4 @@
import express, { NextFunction, Request, Response } from 'express';
import { ethers } from 'ethers';
import { decodePositionId } from 'kraiken-lib/ids';
import { isPositionDelinquent } from 'kraiken-lib/staking';
import { pathToFileURL } from 'url';
@ -7,7 +6,6 @@ import { BotConfigService } from './services/BotConfigService.js';
import { BlockchainService } from './services/BlockchainService.js';
import { GraphQLService } from './services/GraphQLService.js';
import { logger } from './logger.js';
import { hasRecenterAccess, readRecenterAccess } from './recenterAccess.js';
import { Position, RecenterAccessStatus, RecenterEligibility, RecenterResult } from './types.js';
const ACTIVE_POSITIONS_QUERY = `
@ -24,8 +22,6 @@ const ACTIVE_POSITIONS_QUERY = `
}
`;
const ZERO_ADDRESS = ethers.ZeroAddress;
export interface TxnBotDependencies {
configService: BotConfigService;
blockchainService: BlockchainService;
@ -91,8 +87,6 @@ function formatDuration(ms: number): string {
export function createTxnBot(dependencies: TxnBotDependencies): TxnBotInstance {
const { configService, blockchainService, graphQLService } = dependencies;
const envConfig = configService.getConfig();
const recenterAccessReader = blockchainService.getRecenterAccessReader();
const walletAddress = blockchainService.getWalletAddress();
const startTime = new Date();
let lastRecenterTime: Date | null = null;
@ -125,28 +119,10 @@ export function createTxnBot(dependencies: TxnBotDependencies): TxnBotInstance {
return lastRecenterAccessStatus;
}
let recenterAddress: string | null = null;
let hasAccess: boolean | null = null;
let slotHex: string | null = null;
let errorMessage: string | null = null;
try {
const address = await readRecenterAccess(recenterAccessReader, ZERO_ADDRESS);
recenterAddress = address;
hasAccess = hasRecenterAccess(address, walletAddress, ZERO_ADDRESS);
slotHex = 'recenterAccess()';
} catch (error) {
const err = error as { shortMessage?: string; message?: string };
errorMessage = err?.shortMessage || err?.message || 'unknown error';
recenterAddress = null;
}
lastRecenterAccessStatus = {
hasAccess,
recenterAccessAddress: recenterAddress,
slot: slotHex,
hasAccess: true,
checkedAtMs: now,
error: errorMessage,
error: null,
};
return lastRecenterAccessStatus;
@ -154,28 +130,6 @@ export function createTxnBot(dependencies: TxnBotDependencies): TxnBotInstance {
async function evaluateRecenterOpportunity(): Promise<RecenterEligibility> {
const now = Date.now();
const accessStatus = await getRecenterAccessStatus(true);
if (accessStatus.error && accessStatus.hasAccess === null) {
lastRecenterEligibility = {
checkedAtMs: now,
canRecenter: false,
reason: 'Failed to determine recenter access.',
error: accessStatus.error,
};
return lastRecenterEligibility;
}
if (accessStatus.hasAccess === false) {
lastRecenterEligibility = {
checkedAtMs: now,
canRecenter: false,
reason: 'txnBot is not the authorized recenter caller.',
error: null,
};
return lastRecenterEligibility;
}
try {
await blockchainService.estimateRecenterGas();
lastRecenterEligibility = {
@ -287,9 +241,7 @@ export function createTxnBot(dependencies: TxnBotDependencies): TxnBotInstance {
lastLiquidationTime: lastLiquidationTime ? lastLiquidationTime.toISOString() : 'Never',
lastRecenterTx,
recenterAccess: {
hasAccess: recenterAccessStatus?.hasAccess ?? null,
grantedTo: recenterAccessStatus?.recenterAccessAddress ?? null,
slot: recenterAccessStatus?.slot ?? null,
hasAccess: recenterAccessStatus?.hasAccess ?? true,
checkedAt: recenterAccessStatus?.checkedAtMs ? new Date(recenterAccessStatus.checkedAtMs).toISOString() : null,
error: recenterAccessStatus?.error ?? null,
},

View file

@ -1,5 +1,4 @@
import { Contract, JsonRpcProvider, TransactionResponse, Wallet, ethers } from 'ethers';
import { RecenterAccessReader } from '../recenterAccess.js';
export interface BlockchainConfig {
providerUrl: string;
@ -11,7 +10,6 @@ export interface BlockchainConfig {
const LM_ABI = [
{ type: 'function', name: 'recenter', inputs: [], outputs: [], stateMutability: 'nonpayable' },
{ type: 'function', name: 'feeDestination', inputs: [], outputs: [{ type: 'address' }], stateMutability: 'view' },
{ type: 'function', name: 'recenterAccess', inputs: [], outputs: [{ type: 'address' }], stateMutability: 'view' },
];
const STAKE_ABI = [
@ -37,19 +35,6 @@ export class BlockchainService {
this.stakeContract = new ethers.Contract(config.stakeContractAddress, STAKE_ABI, this.wallet);
}
getWalletAddress(): string {
return ethers.getAddress(this.wallet.address);
}
getRecenterAccessReader(): RecenterAccessReader {
return {
recenterAccess: async (): Promise<string> => {
const method = this.liquidityManager.getFunction('recenterAccess');
return (await method()) as string;
},
};
}
async checkFunds(): Promise<string> {
const balance = await this.provider.getBalance(this.wallet.address);
return ethers.formatEther(balance);

View file

@ -17,9 +17,7 @@ export interface EnvConfig {
}
export interface RecenterAccessStatus {
hasAccess: boolean | null;
recenterAccessAddress: string | null;
slot: string | null;
hasAccess: boolean;
checkedAtMs: number;
error: string | null;
}