Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 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
33 changes: 24 additions & 9 deletions cli/buildModules.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import { readdir, writeFile } from "fs/promises";
import { ADAPTER_TYPES, AdapterType } from "../adapters/types";
import { setModuleDefaults } from "../adapters/utils/runAdapter";
import { listHelperProtocols } from "../factory/registry";
import { listHelperProtocols, deadAdapters } from "../factory/registry";

const extensions = ['ts', 'md', 'js']

Expand All @@ -24,6 +24,7 @@ async function run() {

// Add helper-based adapters for all adapter types
await addFactoryAdapters()
addDeadAdapters()

await writeFile(outputFile, JSON.stringify(dimensionsImports))

Expand Down Expand Up @@ -53,31 +54,31 @@ async function run() {
async function addFactoryAdapters() {
// Get all protocols from factory registry
const factoryProtocols = listHelperProtocols();

for (const { protocolName, factoryName, adapterType, sourcePath } of factoryProtocols) {
if (!dimensionsImports[adapterType]) {
dimensionsImports[adapterType] = {};
}

// Guard: Skip if file-based adapter already exists (file-based takes precedence)
if (dimensionsImports[adapterType][protocolName]) {
// console.log(`Skipping factory adapter ${protocolName} in ${adapterType} - file-based adapter already exists`);
continue;
}

try {
// Import based on source path
const helperModule = sourcePath.startsWith('factory/')
const helperModule = sourcePath.startsWith('factory/')
? await import(`../${sourcePath.replace('.ts', '')}`)
: await import(`../helpers/${factoryName}`);

const adapter = helperModule.getAdapter(protocolName);

if (!adapter) continue;

await setModuleDefaults(adapter);
const mockedAdapter = mockFunctions({ default: adapter });

dimensionsImports[adapterType][protocolName] = {
moduleFilePath: `${adapterType}/${protocolName}`,
codePath: sourcePath,
Expand Down Expand Up @@ -111,6 +112,20 @@ async function run() {
return ''
}
}

function addDeadAdapters() {
for (const [adapterType, adapters] of Object.entries(deadAdapters)) {
if (!dimensionsImports[adapterType]) {
dimensionsImports[adapterType] = {};
}
for (const [protocolName, adapterInfo] of Object.entries(adapters as any)) {
if (dimensionsImports[adapterType][protocolName])
continue;

dimensionsImports[adapterType][protocolName] = adapterInfo;
}
}
}
}

//Replace all fuctions with mock functions in an object all the way down
Expand Down
242 changes: 242 additions & 0 deletions cli/migrateDeadProjects.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,242 @@
import { readdir, writeFile, stat, mkdir, rename } from "fs/promises";
import { existsSync, readFileSync } from "fs";
import { ADAPTER_TYPES, AdapterType } from "../adapters/types";
import { setModuleDefaults } from "../adapters/utils/runAdapter";

const extensions = ['ts', 'md', 'js']
const baseFolderPath = __dirname + "/.."
const deadFolderPath = `${baseFolderPath}/dead`
const outputPath = `${baseFolderPath}/factory/deadAdapters.json`

// Load existing dead adapters if file exists
let deadAdapters: Record<string, Record<string, any>> = {}
if (existsSync(outputPath)) {
try {
deadAdapters = JSON.parse(readFileSync(outputPath, 'utf-8'))
} catch (e) {
deadAdapters = {}
}
}

// Track which adapters have been moved
const movedAdapters = new Set<string>()

// Store dead adapter info for dependency resolution
interface DeadAdapterInfo {
adapterType: string
path: string
fileKey: string
fullPath: string
imports: string[] // list of imported adapter paths (e.g., "dexs/uniswap")
}
const deadAdapterInfos: Map<string, DeadAdapterInfo> = new Map()

function sortObjectByKey(obj: Record<string, any>) {
return Object.keys(obj).sort().reduce((sorted: Record<string, any>, key) => {
sorted[key] = obj[key]
return sorted
}, {})
}

function mockFunctions(obj: any): any {
if (typeof obj === "function") {
return '_f'
} else if (typeof obj === "object" && obj !== null) {
Object.keys(obj).forEach((key) => obj[key] = mockFunctions(obj[key]))
}
return obj
}

function removeDotTs(s: string) {
const splitted = s.split('.')
if (splitted.length > 1 && extensions.includes(splitted[splitted.length - 1]))
splitted.pop()
return splitted.join('.')
}

async function getDirectoriesAsync(source: string): Promise<string[]> {
const dirents = await readdir(source, { withFileTypes: true });
return dirents.map(dirent => dirent.name);
}

// Extract imports from file content
function extractImports(filePath: string): string[] {
const imports: string[] = []
try {
let content: string
const fileStat = existsSync(filePath) ? require('fs').statSync(filePath) : null
if (fileStat?.isDirectory()) {
const indexPath = `${filePath}/index.ts`
if (!existsSync(indexPath)) return imports
content = readFileSync(indexPath, 'utf-8')
} else if (existsSync(filePath)) {
content = readFileSync(filePath, 'utf-8')
} else if (existsSync(filePath + '.ts')) {
content = readFileSync(filePath + '.ts', 'utf-8')
} else {
return imports
}

// Match imports like: import ... from "../dexs/adapter-name"
const importRegex = /from\s+["']\.\.\/([^"']+)["']/g
let match
while ((match = importRegex.exec(content)) !== null) {
const importPath = match[1]
// Only track imports from adapter type folders
for (const adapterType of ADAPTER_TYPES) {
if (importPath.startsWith(adapterType + '/')) {
imports.push(importPath)
break
}
}
}
} catch (e) {
// Ignore errors reading file
}
return imports
}

async function moveAdapter(info: DeadAdapterInfo): Promise<boolean> {
const moduleKey = `${info.adapterType}/${info.fileKey}`

if (movedAdapters.has(moduleKey)) {
return false // Already moved
}

// First, move any dead dependencies
for (const importPath of info.imports) {
if (deadAdapterInfos.has(importPath) && !movedAdapters.has(importPath)) {
const depInfo = deadAdapterInfos.get(importPath)!
await moveAdapter(depInfo)
}
}

try {
const importPath = `../${info.adapterType}/${info.fileKey}`
let module = await import(importPath)
if (!module.default) return false

await setModuleDefaults(module.default)
delete module.default._randomUID

// Initialize adapter type in deadAdapters if not exists
if (!deadAdapters[info.adapterType]) {
deadAdapters[info.adapterType] = {}
}

const mockedModule = mockFunctions({ ...module.default })
deadAdapters[info.adapterType][info.fileKey] = {
modulePath: `-`,
codePath: `dead/${info.adapterType}/${info.path}`,
module: mockedModule
}

console.log(`Found dead adapter: ${moduleKey} (deadFrom: ${module.default.deadFrom})`)

// Move to dead folder
const deadTypeFolder = `${deadFolderPath}/${info.adapterType}`
if (!existsSync(deadTypeFolder)) {
await mkdir(deadTypeFolder, { recursive: true })
}
const destPath = `${deadTypeFolder}/${info.path}`
await rename(info.fullPath, destPath)
console.log(` Moved to: ${destPath}`)

movedAdapters.add(moduleKey)
return true
} catch (error: any) {
// Skip modules that fail to import
}
return false
}

async function scanAdapter(adapterType: string, path: string): Promise<DeadAdapterInfo | null> {
const excludeKeys = new Set(["index", "README", '.gitkeep'])
if (excludeKeys.has(path)) return null

try {
const fileKey = removeDotTs(path)
const moduleKey = `${adapterType}/${fileKey}`
const importPath = `../${adapterType}/${fileKey}`
const fullPath = `${baseFolderPath}/${adapterType}/${path}`

let module = await import(importPath)
if (!module.default) return null

await setModuleDefaults(module.default)

if (module.default.deadFrom !== undefined) {
const imports = extractImports(fullPath)
return {
adapterType,
path,
fileKey,
fullPath,
imports
}
}
} catch (error: any) {
// Skip modules that fail to import
}
return null
}

async function run() {
// Phase 1: Scan all adapters and identify dead ones with their dependencies
console.log('Scanning for dead adapters...\n')

for (const adapterType of ADAPTER_TYPES) {
if (adapterType === AdapterType.DERIVATIVES) {
continue // skip derivatives as they use the same folder as dexs
}

const folderPath = `${baseFolderPath}/${adapterType}`

try {
const entries = await getDirectoriesAsync(folderPath)

for (const entry of entries) {
const info = await scanAdapter(adapterType, entry)
if (info) {
const moduleKey = `${info.adapterType}/${info.fileKey}`
deadAdapterInfos.set(moduleKey, info)
}
}
} catch (error) {
// Folder doesn't exist, skip
}
}

console.log(`Found ${deadAdapterInfos.size} dead adapters\n`)

// Phase 2: Move adapters in dependency order
let totalDead = 0
for (const [_, info] of deadAdapterInfos) {
const moved = await moveAdapter(info)
if (moved) totalDead++
}

// Sort dead adapters by key
for (const adapterType of Object.keys(deadAdapters)) {
deadAdapters[adapterType] = sortObjectByKey(deadAdapters[adapterType])
}
deadAdapters = sortObjectByKey(deadAdapters)

// Ensure the output directory exists
const outputDir = outputPath.substring(0, outputPath.lastIndexOf('/'))
if (!existsSync(outputDir)) {
await mkdir(outputDir, { recursive: true })
}

await writeFile(outputPath, JSON.stringify(deadAdapters, null, 2))

console.log(`\nWrote ${totalDead} dead adapters to ${outputPath}`)
console.log(`Total dead adapters in registry: ${Object.values(deadAdapters).reduce((acc, obj) => acc + Object.keys(obj).length, 0)}`)

process.exit(0)
}

run().catch((e) => {
console.error(e)
process.exit(1)
})
1 change: 1 addition & 0 deletions factory/deadAdapters.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{}
10 changes: 10 additions & 0 deletions factory/registry.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import { SimpleAdapter } from "../adapters/types";
import deadAdapters from './deadAdapters.json'
export { deadAdapters}

/**
* Interface that all factories must implement
Expand Down Expand Up @@ -46,6 +48,14 @@ export function getAdapterFromHelpers(
adapterType: string,
protocolName: string
): { adapter: SimpleAdapter; factoryName: string } | null {

if ((deadAdapters as any)[adapterType]?.[protocolName]) {
return {
factoryName: 'deadAdapter',
adapter: (deadAdapters as any)[adapterType][protocolName]
}
}
Comment on lines +91 to +97
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

rg -n -C3 'getAdapterFromHelpers'

Repository: DefiLlama/dimension-adapters

Length of output: 1282


🏁 Script executed:

cat -n factory/registry.ts | sed -n '40,70p'

Repository: DefiLlama/dimension-adapters

Length of output: 1269


🏁 Script executed:

rg -n -B5 -A10 'deadAdapters' factory/registry.ts | head -40

Repository: DefiLlama/dimension-adapters

Length of output: 1047


🏁 Script executed:

fd migrateDeadProjects.ts

Repository: DefiLlama/dimension-adapters

Length of output: 98


🏁 Script executed:

cat factory/deadAdapters.json | head -100

Repository: DefiLlama/dimension-adapters

Length of output: 74


🏁 Script executed:

cat -n cli/migrateDeadProjects.ts | sed -n '120,145p'

Repository: DefiLlama/dimension-adapters

Length of output: 1132


🏁 Script executed:

rg -n -B5 -A10 'function mockFunctions' cli/migrateDeadProjects.ts

Repository: DefiLlama/dimension-adapters

Length of output: 483


🏁 Script executed:

# Check how the adapter field from getAdapterFromHelpers is used
rg -n -A5 'const result = getAdapterFromHelpers' adapters/utils/importAdapter.ts

Repository: DefiLlama/dimension-adapters

Length of output: 384


🏁 Script executed:

# Check what SimpleAdapter looks like to confirm the incompatibility
rg -n -A10 'interface SimpleAdapter' adapters/types

Repository: DefiLlama/dimension-adapters

Length of output: 126


🏁 Script executed:

# Check if there are any other usages of result.adapter in importAdapter.ts
cat -n adapters/utils/importAdapter.ts

Repository: DefiLlama/dimension-adapters

Length of output: 1881


🏁 Script executed:

fd types.ts | grep -v node_modules

Repository: DefiLlama/dimension-adapters

Length of output: 238


🏁 Script executed:

# Find SimpleAdapter definition
rg -n 'interface SimpleAdapter|type SimpleAdapter' --type ts

Repository: DefiLlama/dimension-adapters

Length of output: 402


🏁 Script executed:

cat -n adapters/types.ts | sed -n '115,160p'

Repository: DefiLlama/dimension-adapters

Length of output: 1847


Fix type mismatch: deadAdapters stores metadata objects, not SimpleAdapter instances.

The function returns deadAdapters[adapterType][protocolName] (a { modulePath, codePath, module } object created in migrateDeadProjects.ts lines 128-132) as the adapter field, but the return type signature declares adapter: SimpleAdapter. Callers like importAdapter.ts (line 41) expect a SimpleAdapter conforming to AdapterBase & { adapter?: BaseAdapter }, not metadata.

Additionally, the module field is corrupted by mockFunctions (lines 41-48 of migrateDeadProjects.ts), which replaces all functions with the string '_f'. When dead adapters are retrieved at runtime, the caller will receive an incompatible data structure instead of a functional adapter.

🤖 Prompt for AI Agents
In `@factory/registry.ts` around lines 51 - 57, The returned object is supplying
metadata (deadAdapters[adapterType][protocolName]) where the signature expects a
functional SimpleAdapter (AdapterBase & { adapter?: BaseAdapter }), and the
stored metadata's module field is corrupted by mockFunctions; fix by changing
the registry return to provide either a real adapter loader or a properly-typed
adapter: locate the deadAdapters map and instead of returning the raw metadata
object, use its modulePath/codePath to import/require and reconstruct a valid
SimpleAdapter instance (or return a wrapper that lazily imports and instantiates
the adapter) so callers like importAdapter.ts receive an AdapterBase-compatible
object; also update the types for deadAdapters and the return type of the
registry function to reflect metadata vs adapter (or convert metadata to a
proper SimpleAdapter before returning), and ensure migrateDeadProjects.ts does
not leave the module field with mockFunctions='_f' or provide a deserialization
step to restore real functions.


const factories = factoriesByAdapterType[adapterType];
if (!factories) return null;

Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
"scripts": {
"test": "ts-node --transpile-only cli/testAdapter.ts",
"build": "ts-node --transpile-only cli/buildModules.ts",
"migrate-dead": "ts-node --transpile-only cli/migrateDeadProjects.ts",
"ts-check": "tsc --project tsconfig.json",
"ts-check-cli": "tsc --project tsconfig.cli.json",
"use-token-labels": "node helpers/useTokenLabels.js",
Expand Down
Loading