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
44 changes: 44 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,9 @@ src/
├── utils/
│ ├── bridge-detector.js # Auto-detect bridge type
│ ├── bridge-contracts.js # Bridge interaction helpers
│ ├── bridge-filter.js # Extract bridges for specific network
│ ├── data-normalizer.js # Normalize BigNumber/amounts to strings
│ ├── event-parser.js # Parse event args into named fields
│ ├── threedpass.js # 3DPass precompile utilities
│ ├── token-detector.js # Auto-detect token type
│ ├── assistant-detector.js # Assistant type detection
Expand All @@ -114,6 +117,12 @@ src/

**Decimal Handling**: P3D has special decimal handling - native P3D uses 12 decimals on-chain but 18 decimals in EVM representation. Use `decimalsDisplayMultiplier: 1000000` in token configs to compensate.

**Bridge Filtering**: `bridge-filter.js` provides centralized logic for extracting bridges relevant to a specific network. It combines default bridges from config, import bridges defined at network level, and custom bridges from user settings, with automatic deduplication.

**Data Normalization**: `data-normalizer.js` handles conversion of various amount formats (BigNumber objects, hex strings, numbers) into consistent string representations. Essential for working with event args from smart contracts.

**Event Parsing**: `event-parser.js` converts raw event args arrays into named field objects, eliminating magic array indices and improving type safety. Automatically handles amount normalization.

## Critical Implementation Details

### Bridge Instance Creation Flow
Expand Down Expand Up @@ -156,10 +165,18 @@ When MetaMask changes networks, the context automatically updates provider, sign
## Testing Notes

Tests exist in `src/utils/__tests__/`:
- `bridge-filter.test.js` - Network-specific bridge extraction
- `data-normalizer.test.js` - Amount/BigNumber normalization
- `event-parser.test.js` - Event args parsing
- `fetch-last-transfers.test.js` - Module import validation
- `retry-with-fallback.test.js` - Provider fallback logic
- `settings-consistency.test.js` - Settings validation
- `network-switcher.test.js` - Network switching logic
- `error-parser.test.js` - Error message parsing
- `decimal-converter.test.js` - Decimal conversion utilities

Run tests with `pnpm test` for watch mode.
Run full test suite: `pnpm test -- --no-watch --passWithNoTests --watchAll=false`

## Test-Driven Development (TDD)

Expand Down Expand Up @@ -265,3 +282,30 @@ import { getProvider } from './utils/provider-manager';
const provider = await getProvider('ETHEREUM', settings);
// Automatically handles fallback if primary RPC fails
```

**Filtering bridges for a network**:
```javascript
import { getBridgesForNetwork } from './utils/bridge-filter';
const bridges = getBridgesForNetwork(networkConfig, customBridges);
// Returns: Array of bridge instances for the network
// Combines default, import, and custom bridges with deduplication
```

**Normalizing event amounts**:
```javascript
import { normalizeAmount } from './utils/data-normalizer';
const amount = normalizeAmount(event.args[1]);
// Handles BigNumber, string, number, objects with hex properties
// Returns: string representation or '0' if invalid
```

**Parsing event args**:
```javascript
import { parseExpatriationEvent, parseRepatriationEvent } from './utils/event-parser';
const eventData = parseExpatriationEvent(event);
// Returns: { senderAddress, amount, reward, foreignAddress, data }

const eventData = parseRepatriationEvent(event);
// Returns: { senderAddress, amount, reward, homeAddress, data }
// Eliminates magic array indices, uses normalizeAmount internally
```
168 changes: 168 additions & 0 deletions src/utils/__tests__/bridge-filter.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
import { getBridgesForNetwork } from '../bridge-filter';

describe('bridge-filter', () => {
describe('getBridgesForNetwork', () => {
it('should return empty array when networkConfig has no bridges', () => {
const networkConfig = {
name: 'ETHEREUM',
contracts: {}
};
const customBridges = {};

const result = getBridgesForNetwork(networkConfig, customBridges);

expect(result).toEqual([]);
});

it('should return bridges from networkConfig.bridges', () => {
const networkConfig = {
name: 'ETHEREUM',
contracts: {},
bridges: {
bridge1: { address: '0x123', type: 'export' },
bridge2: { address: '0x456', type: 'import' }
}
};
const customBridges = {};

const result = getBridgesForNetwork(networkConfig, customBridges);

expect(result).toHaveLength(2);
expect(result).toContainEqual({ address: '0x123', type: 'export' });
expect(result).toContainEqual({ address: '0x456', type: 'import' });
});

it('should extract import bridges from network-level properties', () => {
const networkConfig = {
name: 'ETHEREUM',
contracts: {},
bridges: {},
P3D_IMPORT: { address: '0xabc', type: 'import' },
USDT_WRAPPER: { address: '0xdef', type: 'import_wrapper' }
};
const customBridges = {};

const result = getBridgesForNetwork(networkConfig, customBridges);

expect(result).toHaveLength(2);
expect(result).toContainEqual({ address: '0xabc', type: 'import' });
expect(result).toContainEqual({ address: '0xdef', type: 'import_wrapper' });
});

it('should not include non-bridge network properties', () => {
const networkConfig = {
name: 'ETHEREUM',
contracts: {},
bridges: {},
assistants: { assist1: { address: '0x111' } },
tokens: { token1: { address: '0x222' } },
P3D_IMPORT: { address: '0xabc', type: 'import' }
};
const customBridges = {};

const result = getBridgesForNetwork(networkConfig, customBridges);

expect(result).toHaveLength(1);
expect(result[0].address).toBe('0xabc');
});

it('should include custom export bridges when network is home network', () => {
const networkConfig = {
name: 'ETHEREUM',
contracts: {},
bridges: {}
};
const customBridges = {
custom1: {
address: '0xcustom1',
type: 'export',
homeNetwork: 'ETHEREUM',
foreignNetwork: 'BSC'
},
custom2: {
address: '0xcustom2',
type: 'export',
homeNetwork: 'BSC',
foreignNetwork: 'ETHEREUM'
}
};

const result = getBridgesForNetwork(networkConfig, customBridges);

expect(result).toHaveLength(1);
expect(result[0].address).toBe('0xcustom1');
});

it('should include custom import bridges when network is foreign network', () => {
const networkConfig = {
name: 'ETHEREUM',
contracts: {},
bridges: {}
};
const customBridges = {
custom1: {
address: '0xcustom1',
type: 'import',
homeNetwork: 'BSC',
foreignNetwork: 'ETHEREUM'
},
custom2: {
address: '0xcustom2',
type: 'import',
homeNetwork: 'ETHEREUM',
foreignNetwork: 'BSC'
}
};

const result = getBridgesForNetwork(networkConfig, customBridges);

expect(result).toHaveLength(1);
expect(result[0].address).toBe('0xcustom1');
});

it('should not include duplicate bridges', () => {
const networkConfig = {
name: 'ETHEREUM',
contracts: {},
bridges: {
bridge1: { address: '0x123', type: 'export' }
}
};
const customBridges = {
custom1: {
address: '0x123',
type: 'export',
homeNetwork: 'ETHEREUM'
}
};

const result = getBridgesForNetwork(networkConfig, customBridges);

expect(result).toHaveLength(1);
expect(result[0].address).toBe('0x123');
});

it('should combine default bridges, import bridges, and custom bridges', () => {
const networkConfig = {
name: 'ETHEREUM',
contracts: {},
bridges: {
bridge1: { address: '0x111', type: 'export' }
},
P3D_IMPORT: { address: '0x222', type: 'import' }
};
const customBridges = {
custom1: {
address: '0x333',
type: 'import_wrapper',
foreignNetwork: 'ETHEREUM'
}
};

const result = getBridgesForNetwork(networkConfig, customBridges);

expect(result).toHaveLength(3);
expect(result.map(b => b.address).sort()).toEqual(['0x111', '0x222', '0x333']);
});
});
});
129 changes: 129 additions & 0 deletions src/utils/__tests__/data-normalizer.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
import { normalizeAmount } from '../data-normalizer';

describe('data-normalizer', () => {
describe('normalizeAmount', () => {
it('should handle BigNumber objects with toNumber function', () => {
const bigNumber = {
toNumber: () => 12345,
toString: () => '12345'
};

const result = normalizeAmount(bigNumber);

expect(result).toBe('12345');
});

it('should handle string values', () => {
const result = normalizeAmount('98765');

expect(result).toBe('98765');
});

it('should handle number values', () => {
const result = normalizeAmount(42);

expect(result).toBe('42');
});

it('should handle objects with _hex property', () => {
const obj = {
_hex: '0x1234'
};

const result = normalizeAmount(obj);

expect(result).toBe('0x1234');
});

it('should handle objects with hex property', () => {
const obj = {
hex: '0xabcd'
};

const result = normalizeAmount(obj);

expect(result).toBe('0xabcd');
});

it('should handle objects with toString method', () => {
const obj = {
toString: () => '999'
};

const result = normalizeAmount(obj);

expect(result).toBe('999');
});

it('should return "0" for null', () => {
const result = normalizeAmount(null);

expect(result).toBe('0');
});

it('should return "0" for undefined', () => {
const result = normalizeAmount(undefined);

expect(result).toBe('0');
});

it('should return "0" for empty object', () => {
const result = normalizeAmount({});

expect(result).toBe('0');
});

it('should prioritize _hex over hex if both exist', () => {
const obj = {
_hex: '0x1111',
hex: '0x2222'
};

const result = normalizeAmount(obj);

expect(result).toBe('0x1111');
});

it('should prioritize toNumber over string type', () => {
const bigNumber = {
toNumber: () => 100,
toString: () => '100',
valueOf: () => '100'
};

const result = normalizeAmount(bigNumber);

expect(result).toBe('100');
});

it('should handle zero as number', () => {
const result = normalizeAmount(0);

expect(result).toBe('0');
});

it('should handle zero as string', () => {
const result = normalizeAmount('0');

expect(result).toBe('0');
});

it('should handle large numbers', () => {
const result = normalizeAmount(999999999999999);

expect(result).toBe('999999999999999');
});

it('should handle ethers BigNumber with hex', () => {
const bigNumber = {
_hex: '0x5f5e100',
_isBigNumber: true,
toString: () => '100000000'
};

const result = normalizeAmount(bigNumber);

expect(result).toBe('0x5f5e100');
});
});
});
Loading