Flash unwinder contracts and scripts, relay lane tuning, trustless bridge and token-aggregation updates.

Made-with: Cursor
This commit is contained in:
defiQUG
2026-04-12 06:33:54 -07:00
parent 662b35ad69
commit 6817f53591
40 changed files with 682 additions and 88 deletions

View File

@@ -4,7 +4,7 @@
*/
import { ethers } from 'ethers';
import { MessageSentABI, RelayRouterABI, RelayBridgeABI } from './abis.js';
import { MessageSentABI, RelayRouterABI, RelayBridgeABI, ERC20ABI } from './abis.js';
import { MessageQueue } from './MessageQueue.js';
import {
isRelayShedding,
@@ -161,7 +161,7 @@ export class RelayService {
const lastSuccessMs = this.lastRelaySuccess && this.lastRelaySuccess.at ? Date.parse(this.lastRelaySuccess.at) : 0;
if (
this.lastError &&
this.lastError.scope === 'relay_message' &&
(this.lastError.scope === 'relay_message' || this.lastError.scope === 'bridge_inventory') &&
Number.isFinite(lastErrorMs) &&
lastErrorMs > 0 &&
lastErrorMs >= lastSuccessMs
@@ -172,6 +172,35 @@ export class RelayService {
return 'operational';
}
async ensureTargetBridgeInventory(messageId, targetBridge, tokenAmounts) {
if (process.env.RELAY_ENFORCE_BRIDGE_TOKEN_BALANCE !== '1') {
return { ok: true };
}
for (const tokenAmount of tokenAmounts) {
const tokenAddress = ethers.getAddress(tokenAmount.token);
const requiredAmount = typeof tokenAmount.amount === 'bigint'
? tokenAmount.amount
: BigInt(tokenAmount.amount.toString());
const tokenContract = new ethers.Contract(tokenAddress, ERC20ABI, this.destinationProvider);
const availableAmount = await tokenContract.balanceOf(targetBridge);
if (availableAmount < requiredAmount) {
const shortfall = requiredAmount - availableAmount;
return {
ok: false,
token: tokenAddress,
requiredAmount,
availableAmount,
shortfall,
message: `Insufficient bridge inventory for ${messageId}: ${tokenAddress} available=${availableAmount.toString()} required=${requiredAmount.toString()} shortfall=${shortfall.toString()}`
};
}
}
return { ok: true };
}
getHealthSnapshot() {
const queueStats = this.messageQueue.getStats();
const status = this.getHealthStatus();
@@ -359,7 +388,13 @@ export class RelayService {
return (
msg.includes('maximum RPC range') ||
msg.includes('exceeds maximum') ||
(msg.includes('-32000') && msg.includes('range'))
msg.includes('requested too many blocks') ||
msg.includes('maximum is set to') ||
(msg.includes('-32000') && (
msg.includes('range') ||
msg.includes('too many blocks') ||
msg.includes('maximum is set to')
))
);
}
@@ -771,6 +806,27 @@ export class RelayService {
amountType: Number(ta.amountType) // Ensure it's a number (uint8)
};
});
const inventoryCheck = await this.ensureTargetBridgeInventory(
messageId,
targetBridge,
mappedTokenAmounts
);
if (!inventoryCheck.ok) {
const inventoryError = new Error(inventoryCheck.message);
this.logger.warn(inventoryCheck.message);
this.recordError('bridge_inventory', inventoryError, {
message_id: messageId,
target_bridge: targetBridge,
token: inventoryCheck.token,
available_amount: inventoryCheck.availableAmount.toString(),
required_amount: inventoryCheck.requiredAmount.toString(),
shortfall: inventoryCheck.shortfall.toString()
});
await this.messageQueue.retry(messageId);
await new Promise(resolve => setTimeout(resolve, this.config.retry.retryDelay));
return null;
}
// Optional normalization for legacy bridges that decode 4-field payloads:
// (recipient, amount, sender, nonce). TwoWayTokenBridgeL1/L2 decode 2-field payloads
@@ -840,7 +896,7 @@ export class RelayService {
target_bridge: targetBridge,
tx_hash: receipt.hash
};
if (this.lastError && this.lastError.scope === 'relay_message') {
if (this.lastError && (this.lastError.scope === 'relay_message' || this.lastError.scope === 'bridge_inventory')) {
this.lastError = null;
}