Eco Routes Protocol supports cross-chain intents between different virtual machine architectures, including EVM-to-EVM, EVM-to-Solana, and other combinations. This requires careful handling of address formats across different chain types.
The protocol uses bytes32 as a universal address format:
// From BaseDepositAddress.sol:20-21/// @notice User's destination address on target chain/// @dev bytes32 for universal chain compatibility (supports both EVM and non-EVM chains)bytes32 public destinationAddress;
// From AddressConverter.sol:13-21/** * @notice Convert an Ethereum address to bytes32 * @dev Pads the 20-byte address to 32 bytes by converting to uint160, then uint256, then bytes32 * @param addr The address to convert * @return The bytes32 representation of the address */function toBytes32(address addr) internal pure returns (bytes32) { return bytes32(uint256(uint160(addr)));}
How it works:
Original address: 0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb (20 bytes) ↓Step 1: uint160: 0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb ↓Step 2: uint256: 0x000000000000000000000000742d35Cc6634C0532925a3b844Bc9e7595f0bEb ↓Step 3: bytes32: 0x000000000000000000000000742d35cc6634c0532925a3b844bc9e7595f0beb (padded with 12 zero bytes on the left)
// From AddressConverter.sol:23-35/** * @notice Convert bytes32 to an Ethereum address * @dev Truncates the 32-byte value to 20 bytes by converting to uint256, then uint160, then address * @param b The bytes32 value to convert * @return The address representation of the bytes32 value */function toAddress(bytes32 b) internal pure returns (address) { if (!isValidAddress(b)) { revert InvalidAddress(b); } return address(uint160(uint256(b)));}
How it works:
Original bytes32: 0x000000000000000000000000742d35cc6634c0532925a3b844bc9e7595f0beb ↓Step 1: uint256: 0x000000000000000000000000742d35cc6634c0532925a3b844bc9e7595f0beb ↓Step 2: uint160: 0x742d35cc6634c0532925a3b844bc9e7595f0beb (truncate top 12 bytes) ↓Step 3: address: 0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb
Critical: Not all bytes32 values represent valid Ethereum addresses. The top 12 bytes must be zero.
// From AddressConverter.sol:37-46/** * @notice Check if a bytes32 value represents a valid Ethereum address * @dev An Ethereum address must have the top 12 bytes as zero * @param b The bytes32 value to check * @return True if the bytes32 value can be safely converted to an Ethereum address */function isValidAddress(bytes32 b) internal pure returns (bool) { // The top 12 bytes must be zero for a valid Ethereum address return uint256(b) >> 160 == 0;}
Example validation:
// Valid EVM address (top 12 bytes are zero)bytes32 valid = 0x000000000000000000000000742d35cc6634c0532925a3b844bc9e7595f0beb;isValidAddress(valid); // ✅ true// Invalid - top bytes not zero (Solana pubkey)bytes32 invalid = 0x742d35cc6634c0532925a3b844bc9e7595f0beb9f7e2d3a1c5b8e4f6a2d8c1e3;isValidAddress(invalid); // ❌ false// Attempting conversion will reverttoAddress(invalid); // ❌ Reverts with InvalidAddress
The IntentSource contract uses AddressConverter throughout:
// From IntentSource.sol:29using AddressConverter for address;// Example: Converting reward creator to bytes32function publishIntent( Intent calldata intent) external { // When emitting events or encoding data cross-chain, // addresses are converted to bytes32 bytes32 creatorBytes = intent.reward.creator.toBytes32(); // ...}
When destination is Solana (or another non-EVM chain):
// User provides Solana pubkey (as bytes32)bytes32 destinationSolana = 0x742d35cc6634c0532925a3b844bc9e7595f0beb9f7e2d3a1c5b8e4f6a2d8c1e3;// ❌ This is NOT a valid EVM addressisValidAddress(destinationSolana); // false// ✅ But it's a valid bytes32, so it can be used directlyIntent memory intent = Intent({ destination: SOLANA_CHAIN_ID, route: Route({ recipient: destinationSolana, // Used as-is // ... }), // ...});
// User wants to bridge from Ethereum to Arbitrum (both EVM)address userAddressArbitrum = 0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb;// Factory converts to bytes32 for storagebytes32 destinationBytes = userAddressArbitrum.toBytes32();// Get deposit addressaddress depositAddr = factory.getDepositAddress( userAddressArbitrum, msg.sender // depositor);// User sends USDC to depositAddr// ...// On Arbitrum, solver fulfills intentbytes32 recipientBytes = route.recipient; // bytes32 from intentaddress recipient = recipientBytes.toAddress(); // ✅ converts back to addressUSDC.transfer(recipient, amount);
// User wants to bridge from Ethereum to Solana// User provides their Solana Associated Token Account (ATA) as bytes32bytes32 solanaATA = 0x9c2e1f3b8a7d6e5c4f2a8b9e1d3c7a6f5e4b2d8c1a9f7e6d5c4b3a2f1e8d7c6b;// ❌ Cannot convert to address (not a valid EVM address)// AddressConverter.toAddress(solanaATA); // Would revert!// ✅ But can use as bytes32 directlyIntent memory intent = Intent({ destination: SOLANA_CHAIN_ID, route: Route({ recipient: solanaATA, // Used directly as bytes32 // ... }), // ...});
contract CrossChainBridge { using AddressConverter for bytes32; function bridgeToEVM( bytes32 recipient, uint256 amount ) external { // Validate recipient is a valid EVM address if (!AddressConverter.isValidAddress(recipient)) { revert InvalidEVMAddress(recipient); } address recipientAddr = recipient.toAddress(); IERC20(token).transfer(recipientAddr, amount); } function bridgeToSolana( bytes32 solanaAddress, uint256 amount ) external { // No validation needed - any bytes32 is valid for Solana // (Solana program will validate on destination) // Create intent with solanaAddress as recipient // ... }}
ENS names must be resolved to addresses before being used in intents:
// Resolve ENS name firstconst address = await provider.resolveName('vitalik.eth');const bytes32Addr = ethers.utils.hexZeroPad(address, 32);// Now use bytes32Addr in intent