# How to Map a Substrate Account to an EVM Account on 3DPass

> Learn how to map your Substrate account to an EVM account on 3DPass. Understand the bidirectional conversion system for seamless integration and cross-chain compatibility.

- Repository: [3Dpass/3dp](https://github.com/3dpass/3dp)
- Tags: how-to-guide
- Published: 2026-02-23

---

**3DPass uses a bidirectional conversion system where the runtime's `HashedAddressMapping` converts EVM addresses to Substrate accounts via BlakeTwo256 hashing, while precompiles use a truncation helper to derive EVM addresses from the first 20 bytes of Substrate `AccountId` values.**

To interact with the Ethereum Virtual Machine (EVM) on 3DPass, developers must understand how the Substrate-based chain translates between native 32-byte `AccountId` addresses and 20-byte EVM `H160` addresses. This guide explains the specific **Substrate to EVM account mapping on 3DPass** mechanisms implemented in the `3dpass/3dp` repository, including the runtime configuration and precompile utilities that handle these conversions.

## How EVM to Substrate Conversion Works

The EVM pallet in 3DPass relies on the **`AddressMapping`** trait to translate Ethereum addresses into Substrate account identifiers. The runtime defines this mapping in [`runtime/src/lib.rs`](https://github.com/3dpass/3dp/blob/main/runtime/src/lib.rs) at line 41:

```rust
type AddressMapping = HashedAddressMapping<BlakeTwo256>;

```

This configuration directs the EVM pallet to use **BlakeTwo256** hashing for address conversion. The implementation, located in [`pallets/evm/src/lib.rs`](https://github.com/3dpass/3dp/blob/main/pallets/evm/src/lib.rs) (lines 12-21), operates through the following process:

- The system prepends the bytes `b"evm:"` to the 20-byte EVM `H160` address.
- It hashes the resulting byte string using **BlakeTwo256**.
- The 256-bit hash output becomes the 32-byte Substrate `AccountId32`.

The trait provides the **`into_account_id`** method, which the EVM runtime invokes to resolve Ethereum senders or contract addresses to Substrate accounts. Importantly, this trait defines only the **EVM → Substrate** direction; the reverse conversion requires a separate approach implemented in the precompiles.

## How Substrate to EVM Conversion Works

For the reverse direction—converting a Substrate `AccountId` to an EVM address—3DPass precompiles use a consistent truncation strategy rather than a cryptographic hash.

### The Truncation Helper in Precompiles

Every precompile exposing Substrate addresses to the EVM includes an **`account_id_to_address`** helper function. This utility extracts the first 20 bytes from the 32-byte `AccountId` to form the `H160` address.

In the SerialNumbers precompile ([`precompiles/serial-numbers/src/lib.rs`](https://github.com/3dpass/3dp/blob/main/precompiles/serial-numbers/src/lib.rs), lines 36-43), the implementation appears as:

```rust
fn account_id_to_address<Runtime: frame_system::Config>(account_id: &Runtime::AccountId) -> Address
where
    Runtime::AccountId: AsRef<[u8; 32]>,
{
    let bytes = account_id.as_ref();
    Address(H160::from_slice(&bytes[0..20]))
}

```

This identical pattern appears across other precompiles:

- **Poscan**: [`precompiles/poscan/src/lib.rs`](https://github.com/3dpass/3dp/blob/main/precompiles/poscan/src/lib.rs) (lines 44-51)
- **Identity**: [`precompiles/identity/src/lib.rs`](https://github.com/3dpass/3dp/blob/main/precompiles/identity/src/lib.rs) (lines 116-124)

The truncation method assumes that the first 20 bytes of the hashed `AccountId` provide sufficient uniqueness for EVM interactions, creating a deterministic relationship from the Substrate account space to Ethereum addresses.

## Implementing Substrate to EVM Mapping in Your Pallet

When building custom pallets that need to expose Substrate accounts to the EVM, replicate the truncation logic used by the precompiles:

```rust
use sp_core::H160;

pub fn substrate_to_evm<Runtime>(account_id: &Runtime::AccountId) -> H160
where
    Runtime: frame_system::Config,
    Runtime::AccountId: AsRef<[u8; 32]>,
{
    let bytes = account_id.as_ref();
    H160::from_slice(&bytes[0..20])
}

```

This function works with the default `HashedAddressMapping` configuration because the conversion leverages the raw bytes of the `AccountId` without requiring knowledge of the specific hashing algorithm used during the reverse conversion.

## Custom Address Mapping Configuration

If your application requires a different conversion strategy—such as direct 1-to-1 mapping without hashing—you can implement a custom type that adheres to the `AddressMapping` trait.

Replace the default configuration in [`runtime/src/lib.rs`](https://github.com/3dpass/3dp/blob/main/runtime/src/lib.rs):

```rust
type AddressMapping = MyDirectMapping;

```

Your custom type must implement:

```rust
pub trait AddressMapping<A> {
    fn into_account_id(address: H160) -> A;
}

```

Additionally, provide a complementary `account_id_to_address` function to handle the Substrate → EVM direction consistently across your runtime.

## Summary

- **3DPass** uses `HashedAddressMapping<BlakeTwo256>` (defined in [`runtime/src/lib.rs`](https://github.com/3dpass/3dp/blob/main/runtime/src/lib.rs)) to convert EVM addresses to Substrate accounts by hashing `b"evm:" + H160`.
- The **`into_account_id`** method handles the EVM → Substrate direction exclusively as part of the `AddressMapping` trait.
- Precompiles use the **`account_id_to_address`** helper (found in SerialNumbers, Poscan, and Identity precompiles) to convert Substrate accounts to EVM addresses by truncating to the first 20 bytes.
- Custom mapping implementations require replacing the `AddressMapping` type in the runtime and implementing both conversion directions to maintain consistency.

## Frequently Asked Questions

### How does the default EVM address mapping work in 3DPass?

The default implementation uses **BlakeTwo256** hashing to map EVM addresses to Substrate accounts, as configured in [`runtime/src/lib.rs`](https://github.com/3dpass/3dp/blob/main/runtime/src/lib.rs). The EVM pallet hashes the concatenation of the prefix `b"evm:"` and the 20-byte Ethereum address to generate a 32-byte `AccountId`. This provides a collision-resistant, deterministic mapping from the EVM space into Substrate's native account system.

### Can I convert a Substrate account to an EVM address without using precompiles?

Yes. You can replicate the logic found in the precompiles' `account_id_to_address` helper function, which truncates the first 20 bytes of the 32-byte `AccountId`. Since this conversion is not part of the `AddressMapping` trait, you must implement this truncation manually in your pallet or runtime module to derive the corresponding `H160` address.

### Is it possible to change the address mapping strategy in 3DPass?

Absolutely. You can replace `HashedAddressMapping<BlakeTwo256>` with your own type that implements the `pallet_evm::AddressMapping` trait. This custom type must provide the `into_account_id` method for EVM-to-Substrate conversion, and you should implement a corresponding reverse conversion function to ensure consistent bidirectional mapping throughout your runtime.

### Where can I find examples of address conversion in the 3DPass codebase?

The primary implementation resides in [`pallets/evm/src/lib.rs`](https://github.com/3dpass/3dp/blob/main/pallets/evm/src/lib.rs) (lines 12-21) for EVM-to-Substrate conversion. For Substrate-to-EVM examples, examine the precompile directories: [`precompiles/serial-numbers/src/lib.rs`](https://github.com/3dpass/3dp/blob/main/precompiles/serial-numbers/src/lib.rs) (lines 36-43), [`precompiles/poscan/src/lib.rs`](https://github.com/3dpass/3dp/blob/main/precompiles/poscan/src/lib.rs) (lines 44-51), and [`precompiles/identity/src/lib.rs`](https://github.com/3dpass/3dp/blob/main/precompiles/identity/src/lib.rs) (lines 116-124), all of which contain the `account_id_to_address` helper function.