How to Interact with Native Substrate Functions from Solidity on 3DPass

You interact with native Substrate functions from Solidity on 3DPass by using precompile contracts—fixed-address Solidity interfaces that map directly to Substrate pallet extrinsics, allowing atomic execution within the EVM environment.

The 3DPass blockchain (repository: 3dpass/3dp) bridges the gap between Ethereum-compatible smart contracts and native Substrate runtime logic through a specialized precompile architecture. This enables Solidity developers to leverage powerful native pallets—such as Identity and SerialNumbers—without leaving the EVM environment or managing complex cross-chain messaging.

Understanding Precompile Contracts on 3DPass

Precompiles on 3DPass are system contracts deployed at hard-coded H160 addresses (20-byte Ethereum-style addresses). Each precompile corresponds to a specific Substrate pallet and exposes that pallet's extrinsics as Solidity functions. When you call a precompile function from Solidity, the 3DPass runtime intercepts the call, translates it into the corresponding Substrate extrinsic, executes it atomically with your EVM transaction, and returns the result to your contract.

How Precompile Addresses Work

Each precompile occupies a fixed address derived from the pallet's module ID. For example, the Identity pallet precompile resides at 0x0000000000000000000000000000000000000904, while the SerialNumbers pallet uses its own distinct constant address. These addresses are exported as constants in the Solidity interface files:

  • IDENTITY_ADDRESS in precompiles/identity/Identity.sol
  • SERIAL_NUMBERS_ADDRESS in precompiles/serial-numbers/SerialNumbers.sol

Custom Selectors and Runtime Routing

Every function in a precompile interface is annotated with a @custom:selector tag followed by a hexadecimal value. This selector serves as the function discriminator that the 3DPass runtime uses to route your Solidity call to the correct pallet extrinsic. For instance, when you call setIdentity in Solidity, the runtime recognizes the specific selector and invokes the corresponding set_identity extrinsic in the Substrate Identity pallet.

Step-by-Step: Interact with Native Substrate Functions from Solidity

To interact with native Substrate functions from Solidity on 3DPass, follow this workflow:

  1. Import the precompile interface – Include the appropriate Solidity file from the precompiles/ directory (e.g., Identity.sol or SerialNumbers.sol).
  2. Use the constant contract instance – The interface files define ready-to-use constants like IDENTITY_CONTRACT and SERIAL_NUMBERS_CONTRACT that point to the fixed precompile addresses.
  3. Call Solidity functions – Invoke the functions defined in the interface; these map directly to Substrate extrinsics using the custom selectors.
  4. Handle return values and events – Process the returned structs or emitted events exactly as you would with standard Solidity contracts.

Practical Code Examples

Querying Identity Information

To read identity data from the native Substrate Identity pallet, import Identity.sol and call the identity function using the pre-defined contract constant:

// SPDX-License-Identifier: GPL-3.0-only
pragma solidity >=0.8.3;

import "precompiles/identity/Identity.sol";

contract MyIdentityReader {
    function getMyIdentity() external view returns (Identity.Registration memory reg) {
        // Calls the precompile at IDENTITY_ADDRESS (0x000...0904)
        reg = IDENTITY_CONTRACT.identity(msg.sender);
    }
}

This call retrieves the full Registration struct from the pallet's storage, including all identity fields and judgments, without requiring an off-chain worker or oracle.

Creating Serial Numbers

To write data to the SerialNumbers pallet—creating a new serial number entry—use the SerialNumbers.sol precompile:

// SPDX-License-Identifier: GPL-3.0-only
pragma solidity >=0.8.3;

import "precompiles/serial-numbers/SerialNumbers.sol";

contract SerialNumberMinter {
    function mint(uint32 blockIdx) external returns (bytes16 snHash, uint64 snIdx) {
        // Maps to the create_serial_number extrinsic via custom selector
        (snHash, snIdx, ) = SERIAL_NUMBERS_CONTRACT.createSerialNumber(blockIdx);
    }
}

The createSerialNumber function translates into the native create_serial_number extrinsic. The transaction executes atomically—if the EVM transaction reverts, the Substrate state change rolls back as well.

Setting Identity Data

To update identity information on-chain, construct the IdentityInfo struct and pass it to setIdentity:

// SPDX-License-Identifier: GPL-3.0-only
pragma solidity >=0.8.3;

import "precompiles/identity/Identity.sol";

contract IdentityWriter {
    function setMyIdentity(Identity.IdentityInfo calldata info) external {
        // Invokes the set_identity extrinsic in the Substrate runtime
        IDENTITY_CONTRACT.setIdentity(info);
    }
}

The Solidity struct layout mirrors the Substrate storage struct, ensuring type compatibility between the EVM and native runtime environments.

Listening to Precompile Events

Precompiles emit events that correspond to pallet events. You can listen to these in your Solidity contracts or dApp frontends:

// SPDX-License-Identifier: GPL-3.0-only
pragma solidity >=0.8.3;

import "precompiles/identity/Identity.sol";

contract IdentityWatcher {
    // Mirrors the IdentitySet event from the pallet
    event IdentitySet(address indexed who);
    
    constructor() {
        // Subscribe to events emitted by IDENTITY_CONTRACT
    }
}

The IdentitySet event selector matches the pallet's event definition, allowing standard Ethereum tooling like Ethers.js or Web3.js to capture state changes originating from native Substrate logic.

Key Precompile Interfaces and File Paths

The 3DPass repository organizes precompiles under the precompiles/ directory. Here are the critical interfaces for interacting with native Substrate functions:

File Role Key Constants
precompiles/identity/Identity.sol Interface to the native Identity pallet IDENTITY_ADDRESS, IDENTITY_CONTRACT
precompiles/serial-numbers/SerialNumbers.sol Interface to the SerialNumbers pallet SERIAL_NUMBERS_ADDRESS, SERIAL_NUMBERS_CONTRACT
precompiles/assets-erc20/ERC20.sol Standard ERC‑20 interface for asset pallets Standard ERC-20 methods
precompiles/proxy/Proxy.sol Generic proxy pattern for forwarding calls Proxy delegation methods
precompiles/utils/tests/solidity_test.sol Reference for custom selectors and complex args Selector examples

Gas Accounting and Performance Considerations

When you interact with native Substrate functions from Solidity on 3DPass, gas costs reflect the underlying pallet's computational weight. The runtime converts Substrate weight units to EVM gas at the precompile boundary, ensuring fair pricing for native operations. Because precompile calls execute synchronously within the same block, they offer significant performance advantages over cross-chain messaging or off-chain oracle patterns—state changes commit atomically with your EVM transaction, eliminating latency and bridge risks.

Summary

  • Precompile contracts bridge Solidity and Substrate by exposing pallet APIs at fixed H160 addresses like 0x0000000000000000000000000000000000000904.
  • Import interfaces from precompiles/identity/Identity.sol or precompiles/serial-numbers/SerialNumbers.sol to access native functions.
  • Use constant instances such as IDENTITY_CONTRACT and SERIAL_NUMBERS_CONTRACT to call methods that map to Substrate extrinsics via @custom:selector annotations.
  • Execute atomically—precompile calls run within the EVM transaction context, ensuring state consistency between Solidity and Substrate without cross-chain latency.

Frequently Asked Questions

How do I find the correct precompile address for a specific Substrate pallet?

Each precompile address is hard-coded in the corresponding Solidity interface file as a constant. For example, Identity.sol defines IDENTITY_ADDRESS as 0x0000000000000000000000000000000000000904, while SerialNumbers.sol defines SERIAL_NUMBERS_ADDRESS at a different fixed address. Check the specific interface file in the precompiles/ directory to obtain the correct H160 address for your target pallet.

What is the purpose of the @custom:selector annotation in precompile interfaces?

The @custom:selector annotation specifies the hexadecimal function selector that the 3DPass runtime uses to route your Solidity call to the correct Substrate extrinsic. When you call a function like setIdentity in Solidity, the runtime inspects the selector defined in the annotation (e.g., @custom:selector 0x12345678) and invokes the corresponding set_identity extrinsic in the Identity pallet. This mapping ensures type-safe translation between EVM calls and native runtime functions.

Can I revert a Substrate state change if my Solidity transaction fails?

Yes. Precompile calls execute atomically within the EVM transaction context. If your Solidity contract reverts due to a require statement, out-of-gas error, or explicit revert(), the entire transaction rolls back—including any state changes made by the precompile to the Substrate pallet storage. This atomicity guarantees consistency between your EVM contract state and the native Substrate state, eliminating the need for manual compensation logic or two-phase commit patterns.

How are gas costs calculated for precompile calls?

Gas costs for precompile calls are derived from the underlying Substrate pallet's computational weight. The 3DPass runtime converts the Substrate weight units (representing CPU time and storage I/O) to EVM gas units at the precompile boundary. This ensures that computationally expensive native operations (like complex identity verification or serial number generation) cost more gas than simple storage reads, while maintaining compatibility with standard Ethereum gas estimation tools.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →