Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,9 @@ dist
# Address lists
addresses.json

# Inventory config
inventoryConfig.json

# AI context files
CLAUDE.md

Expand Down
2 changes: 1 addition & 1 deletion CODEOWNERS
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,4 @@

# These owners will be the default owners for everything in
# the repo unless a later match takes precedence.
* @mrice32 @nicholaspai @pxrl @james-a-morris @dohaki @bmzig
* @mrice32 @nicholaspai @pxrl @dohaki @bmzig
2 changes: 1 addition & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
ARG NODEJS_VERSION=20
ARG NODEJS_VERSION=22.18

##### Stage 1

Expand Down
8 changes: 6 additions & 2 deletions index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,24 +10,28 @@ import {
waitForLogger,
stringifyThrownValue,
} from "./src/utils";
import { runRelayer, runRebalancer } from "./src/relayer";
import { runDataworker } from "./src/dataworker";
import { runRelayer, runRebalancer, runInventoryManager } from "./src/relayer";
import { runDataworker, runDisputerWatchdog } from "./src/dataworker";
import { runMonitor } from "./src/monitor";
import { runFinalizer } from "./src/finalizer";
import { version } from "./package.json";
import { runRefiller } from "./src/refiller";
import { runHyperliquidExecutor } from "./src/hyperliquid";

let logger: typeof Logger;
let cmd: string;

const CMDS = {
dataworker: runDataworker,
"disputer-watchdog": runDisputerWatchdog,
finalizer: runFinalizer,
help: help,
monitor: runMonitor,
refiller: runRefiller,
relayer: runRelayer,
rebalancer: runRebalancer,
hlExecutor: runHyperliquidExecutor,
inventoryManager: runInventoryManager,
};

export async function run(args: { [k: string]: boolean | string }): Promise<void> {
Expand Down
19 changes: 12 additions & 7 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,13 @@
"license": "AGPL-3.0-only",
"private": true,
"engines": {
"node": ">=20.19.2"
"node": ">=22.18.0"
},
"dependencies": {
"@across-protocol/constants": "^3.1.79",
"@across-protocol/contracts": "^4.1.9",
"@across-protocol/sdk": "4.3.74",
"@across-protocol/constants": "^3.1.89",
"@across-protocol/contracts": "^4.1.14",
"@across-protocol/contracts-beta": "npm:@across-protocol/contracts@4.1.12-beta.7",
"@across-protocol/sdk": "4.3.97",
"@arbitrum/sdk": "^4.0.2",
"@aws-sdk/client-kms": "^3.592.0",
"@aws-sdk/client-s3": "^3.592.0",
Expand All @@ -28,14 +29,15 @@
"@google-cloud/storage": "^6.10.1",
"@maticnetwork/maticjs": "^3.6.0",
"@maticnetwork/maticjs-ethers": "^1.0.3",
"@nktkas/hyperliquid": "^0.25.9",
"@openzeppelin/hardhat-upgrades": "^1.28.0",
"@solana-program/address-lookup-table": "^0.7.0",
"@solana-program/compute-budget": "^0.8.0",
"@solana-program/system": "^0.7.0",
"@solana-program/token": "^0.5.0",
"@solana/kit": "^2.1.0",
"@uma/logger": "^1.3.3",
"@uma/serverless-orchestration": "^2.17.2",
"@risk-labs/logger": "^1.3.7",
"@risk-labs/serverless-orchestration": "^1.0.5",
"axios": "^1.7.4",
"binance-api-node": "0.12.7",
"dotenv": "^16.3.1",
Expand All @@ -51,7 +53,7 @@
"superstruct": "^1.0.3",
"ts-node": "^10.9.1",
"viem": "^2.33.1",
"winston": "^3.10.0",
"winston": "^3.17.0",
"zksync-ethers": "^5.7.2"
},
"files": [
Expand Down Expand Up @@ -81,11 +83,14 @@
"run-monitor": "node ./dist/index.js --monitor",
"run-refiller": "node ./dist/index.js --refiller",
"run-finalizer": "node ./dist/index.js --finalizer",
"run-hlExecutor": "node ./dist/index.js --hlExecutor",
"run-inventoryManager": "node ./dist/index.js --inventoryManager",
"relay": "node ./dist/index.js --relayer",
"deposit": "yarn ts-node ./scripts/spokepool.ts deposit",
"dispute": "yarn ts-node ./scripts/hubpool.ts dispute",
"update": "git pull && yarn reinstall && yarn version --non-interactive && git show --quiet",
"update-addresses": "yarn ts-node ./scripts/fetch-addresses.ts",
"update-inventory-config": "yarn ts-node ./scripts/fetchInventoryConfig.ts",
"validate-root-bundle": "yarn ts-node src/scripts/validateRootBundle.ts"
},
"devDependencies": {
Expand Down
34 changes: 34 additions & 0 deletions scripts/eventListener.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
// import { Block } from "viem";
import { utils as ethersUtils } from "ethers";
import { Log } from "../src/interfaces";
import { EventListener } from "../src/clients";
import { getDeployedContract, Logger as logger } from "../src/utils";

async function run() {
const chainId = 8453;
const spokePool = getDeployedContract("SpokePool", chainId);
const listener = new EventListener(chainId, logger, 1);
const newBlock = (blockNumber: number, timestamp: number) => {
console.log(`xxx got block ${blockNumber} @ ${timestamp}`);
};
const newFill = (event: Log) => {
console.log(`xxx got event ${event.event}`);
};

const eventDescriptor = spokePool.interface.getEvent("FilledRelay");
const eventFormatted = eventDescriptor.format(ethersUtils.FormatTypes.full);

listener.onBlock(newBlock);
listener.onEvent(spokePool.address, eventFormatted, newFill);
}

if (require.main === module) {
run()
.then(async () => {
process.exitCode = 0;
})
.catch(async (error) => {
console.error("Process exited with", error);
process.exitCode = 128;
});
}
119 changes: 119 additions & 0 deletions scripts/fetchInventoryConfig.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
import { writeFile } from "node:fs/promises";
import { config } from "dotenv";
import axios from "axios";

const OUTPUT_PATH = "inventoryConfig.json";

interface GitHubFileResponse {
content: string;
encoding: string;
sha: string;
size: number;
url: string;
}

async function fetchWithRetry(
url: string,
headers: { Authorization: string; Accept: string },
retries = 3,
delayMs = 1000
): Promise<GitHubFileResponse> {
for (let i = 0; i < retries; i++) {
try {
const response = await axios.get<GitHubFileResponse>(url, { headers });
return response.data;
} catch (error) {
if (i === retries - 1) {
throw error;
}
console.log(`⚠️ Request failed (attempt ${i + 1}/${retries}). Retrying in ${delayMs}ms...`);
await new Promise((resolve) => setTimeout(resolve, delayMs));
}
}
throw new Error("Failed to fetch file from GitHub");
}

async function fetchFileFromGitHub(
owner: string,
repo: string,
path: string,
token: string,
branch = "master"
): Promise<string> {
const url = `https://api.github.com/repos/${owner}/${repo}/contents/${path}?ref=${branch}`;
const headers = {
Authorization: `token ${token}`,
Accept: "application/vnd.github.v3+json",
};

const response = await fetchWithRetry(url, headers);

// GitHub API returns base64 encoded content
const content = Buffer.from(response.content, "base64").toString("utf-8");
return content;
}

async function run(): Promise<number> {
config(); // Load .env file

// Get configuration from environment variables
const githubToken = process.env.GITHUB_TOKEN;
const githubOwner = process.env.GITHUB_REPO_OWNER;
const githubRepo = process.env.GITHUB_REPO_NAME;
const githubFilePath = process.env.GITHUB_FILE_PATH;
const githubBranch = process.env.GITHUB_BRANCH || "master";

// Validate required environment variables
if (!githubToken) {
throw new Error("GITHUB_TOKEN environment variable is required");
}
if (!githubOwner) {
throw new Error("GITHUB_REPO_OWNER environment variable is required");
}
if (!githubRepo) {
throw new Error("GITHUB_REPO_NAME environment variable is required");
}
if (!githubFilePath) {
throw new Error("GITHUB_FILE_PATH environment variable is required");
}

console.log(`Fetching ${githubFilePath} from ${githubOwner}/${githubRepo} (branch: ${githubBranch})...`);

try {
// Fetch the file content from GitHub
const fileContent = await fetchFileFromGitHub(githubOwner, githubRepo, githubFilePath, githubToken, githubBranch);

// Parse JSON to validate it's valid JSON
const jsonData = JSON.parse(fileContent);

// Write to output file
await writeFile(OUTPUT_PATH, JSON.stringify(jsonData, null, 2));

console.log(`✅ Successfully fetched and saved to ${OUTPUT_PATH}`);
return 0;
} catch (error) {
if (axios.isAxiosError(error)) {
if (error.response?.status === 404) {
throw new Error(`File not found: ${githubFilePath} in ${githubOwner}/${githubRepo}`);
} else if (error.response?.status === 401 || error.response?.status === 403) {
throw new Error(
`Authentication failed. Check your GITHUB_TOKEN and ensure it has access to ${githubOwner}/${githubRepo}`
);
} else {
throw new Error(`GitHub API error: ${error.response?.status} - ${error.message}`);
}
}
throw error;
}
}

if (require.main === module) {
run()
.then((result: number) => {
process.exitCode = result;
})
.catch((error) => {
console.error("❌ Process exited with error:", error.message);
process.exitCode = 127;
});
}
51 changes: 51 additions & 0 deletions scripts/hyperliquidExchangeApi.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import * as hl from "@nktkas/hyperliquid";
import { SymbolConverter } from "@nktkas/hyperliquid/script/src/utils/mod";

async function run() {
const subsClient = new hl.SubscriptionClient({
transport: new hl.WebSocketTransport(),
});

const httpTransport = new hl.HttpTransport(); // or `WebSocketTransport`
const converter = await SymbolConverter.create({ transport: httpTransport });
const spotPairId = converter.getSpotPairId("USDT0/USDC");
console.log(`Printing fills for spot pair ${spotPairId}`);
// Log out all historical fills for user and also log
// for each new one.
await subsClient.userFills({ user: "0x9A8f92a830A5cB89a3816e3D267CB7791c16b04D" }, (data) => {
for (const fill of data.fills) {
if (fill.coin === spotPairId) {
console.log(fill);
// e.g. USDT-USDC sell limit order fill (sell USDT for USDC)
// {
// coin: '@166',
// px: '0.99987',
// sz: '12.0',
// side: 'A',
// time: 1762442654786,
// startPosition: '20.75879498',
// dir: 'Sell',
// closedPnl: '-0.00266345',
// hash: '0x12bac8ba533cb3641434042ef5f8990207c6009fee3fd236b683740d12308d4e',
// oid: 225184674691,
// crossed: false,
// fee: '0.00095987',
// tid: 854069469806140,
// feeToken: 'USDC',
// twapId: null
// }
}
}
});
}

if (require.main === module) {
run()
.then(async () => {
process.exitCode = 0;
})
.catch(async (error) => {
console.error("Process exited with", error);
process.exitCode = 128;
});
}
24 changes: 17 additions & 7 deletions src/adapter/BaseChainAdapter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,9 @@ import {
ZERO_BYTES,
isEVMSpokePoolClient,
EvmAddress,
chainIsEvm,
sendAndConfirmSolanaTransaction,
getSvmProvider,
} from "../utils";
import { AugmentedTransaction, TransactionClient } from "../clients/TransactionClient";
import {
Expand Down Expand Up @@ -291,7 +294,7 @@ export class BaseChainAdapter {
if (!this.isSupportedL2Bridge(l1Token)) {
return [];
}
let txnsToSend: AugmentedTransaction[];
let txnsToSend;
try {
txnsToSend = await this.l2Bridges[l1Token.toNative()].constructWithdrawToL1Txns(
address,
Expand All @@ -317,10 +320,17 @@ export class BaseChainAdapter {
);
return [];
}
const multicallerClient = new MultiCallerClient(this.logger);
txnsToSend.forEach((txn) => multicallerClient.enqueueTransaction(txn));
const txnReceipts = await multicallerClient.executeTxnQueues(simMode);
return txnReceipts[this.chainId];
if (chainIsEvm(this.chainId)) {
const multicallerClient = new MultiCallerClient(this.logger);
txnsToSend.forEach((txn) => multicallerClient.enqueueTransaction(txn));
const txnReceipts = await multicallerClient.executeTxnQueues(simMode, [this.chainId]);
return txnReceipts[this.chainId];
}
const txnSignatures = [];
for (const solanaTransaction of txnsToSend) {
txnSignatures.push(await sendAndConfirmSolanaTransaction(solanaTransaction, getSvmProvider()));
}
return txnSignatures;
}

async getL2PendingWithdrawalAmount(
Expand Down Expand Up @@ -453,8 +463,8 @@ export class BaseChainAdapter {
// Permit bypass if simMode is set in order to permit tests to pass.
if (simMode === false) {
const symbol = await contract.symbol();
const { BSC, HYPEREVM, MAINNET, PLASMA } = CHAIN_IDs;
const nativeTokenChains = [BSC, HYPEREVM, MAINNET, PLASMA];
const { BSC, HYPEREVM, MAINNET, PLASMA, MONAD } = CHAIN_IDs;
const nativeTokenChains = [BSC, HYPEREVM, MAINNET, PLASMA, MONAD];
const prependW = nativeTokenChains.some((chainId) => PUBLIC_NETWORKS[chainId].nativeToken === nativeTokenSymbol);
const expectedTokenSymbol = prependW ? `W${nativeTokenSymbol}` : nativeTokenSymbol;
assert(
Expand Down
8 changes: 6 additions & 2 deletions src/adapter/bridges/UsdcCCTPBridge.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,13 @@ import {
bnZero,
} from "../../utils";
import { processEvent, TransferTokenParams } from "../utils";
import { getCctpV2TokenMessenger, getV2DepositForBurnMaxFee } from "../../utils/CCTPUtils";
import {
getCctpV2TokenMessenger,
getV2DepositForBurnMaxFee,
CCTPV2_FINALITY_THRESHOLD_STANDARD,
} from "../../utils/CCTPUtils";
import { CCTP_NO_DOMAIN } from "@across-protocol/constants";
import { CCTP_MAX_SEND_AMOUNT, CCTPV2_FINALITY_THRESHOLD_STANDARD } from "../../common";
import { CCTP_MAX_SEND_AMOUNT } from "../../common";
import { SortableEvent } from "../../interfaces";

export class UsdcCCTPBridge extends BaseBridgeAdapter {
Expand Down
Loading
Loading