---
title: Bank Precompile
sidebarTitle: Bank
description: >-
  Learn how Paxeer's Bank Precompile at 0x1001 lets EVM contracts send tokens,
  query balances, and fetch metadata for HPX and IBC assets.
keywords:
  - bank precompile
  - token transfers
  - native HPX
  - ethers.js
  - balance queries
  - paxeer development
  - defi
  - token metadata
---

> **For AI agents:** the complete documentation index is at [llms.txt](/llms.txt). Append `.md` to any page URL for its markdown version.

**Address:** `0x0000000000000000000000000000000000001001`

The Paxeer bank precompile allows EVM applications to interact directly with Paxeer's native banking system through standard smart contract calls. This enables querying balances, transferring tokens, and accessing token metadata for both native HPX tokens and Cosmos SDK-based assets, providing seamless integration between EVM and Cosmos ecosystems.

<Info> **What is a precompile?** A precompile is a special smart contract deployed at a fixed address by the Paxeer protocol itself, that exposes custom native chain logic to EVM-based applications. It acts like a regular contract from the EVM's perspective, but executes privileged, low-level logic efficiently. </Info>

## How Does the Bank Precompile Work?

The bank precompile at address `0x0000000000000000000000000000000000001001` exposes functions like `send()`, `sendNative()`, `balance()`, `all_balances()`, and token metadata queries.

- **Direct Integration:** EVM contracts and dApps can call banking functions like any other smart contract method.
- **Native Execution:** Operations are executed at the Cosmos SDK level for maximum efficiency and security.
- **Cross-Chain Assets:** Manage both native HPX tokens and IBC assets seamlessly from EVM contracts.

**When to use `send` vs `sendNative`:** `send` moves an arbitrary `denom` (any IBC or factory token) between two EVM addresses (`0x...`) by reading the balance directly from the bank module - no `msg.value` is attached. It is gated to the registered ERC20 native pointer for that denom, so it is typically invoked from the auto-deployed pointer contract rather than from arbitrary user code. `sendNative` is for sending native HPX (the attached `msg.value`) from the EVM caller to a Cosmos bech32 (`pax1...`) destination, which is useful for crossing the EVM→Cosmos boundary when the recipient has no associated EVM address (for example, paying a Cosmos-only contract or account).

## Use Cases

- **DeFi Applications:** Build decentralized finance protocols that can handle native HPX and Cosmos assets.
- **Portfolio Management:** Build tools to track and manage multi-asset portfolios across Cosmos and EVM.
- **Token Information Services:** Query comprehensive token metadata for UI display and analytics.

## What You'll Learn in This Guide

By the end of this guide, you'll be able to:

- **Execute Token Transfers** - Send both native HPX and custom tokens between addresses
- **Query Account Balances** - Check single and multi-asset balances for any address
- **Access Token Metadata** - Retrieve names, symbols, decimals, and supply information

## Functions

The bank precompile exposes the following functions:

### Transaction Functions

```solidity
/// Sends non-native tokens from one address to another. Callable only by the
/// registered ERC20 native pointer contract for the given denom.
/// @param fromAddress The EVM address (0x...) to send funds from.
/// @param toAddress The EVM address (0x...) to send funds to.
/// @param denom The denomination of funds to send.
/// @param amount The amount of the above denom to send.
/// @return success Whether the send was successfully executed.
function send(
    address fromAddress,
    address toAddress,
    string memory denom,
    uint256 amount
) external returns (bool success);

/// Sends native HPX (the attached msg.value) from the EVM caller to a Cosmos
/// bech32 recipient. Requires a non-zero msg.value.
/// @param toNativeAddress The bech32 (pax1...) address of the recipient.
/// @return success Whether the tokens were successfully sent.
function sendNative(
    string memory toNativeAddress
) payable external returns (bool success);
```

### Query Functions

```solidity
/// Queries the balance of the given account for the specified denom.
/// @param acc The EVM address (0x...) of the account to query.
/// @param denom The denomination to query for.
/// @return amount The amount of denom held by acc.
function balance(
    address acc,
    string memory denom
) external view returns (uint256 amount);

/// Queries the balance of the given account for all balances.
/// @param acc The EVM address (0x...) of the account to query.
/// @return response Balances for all coins/denoms.
function all_balances(
    address acc
) external view returns (Coin[] memory response);

/// Queries the name of the specified denom.
/// @param denom The denomination to query about.
/// @return response The name of the specified denom.
function name(
    string memory denom
) external view returns (string memory response);

/// Queries the symbol of the specified denom.
/// @param denom The denomination to query about.
/// @return response The symbol of the specified denom.
function symbol(
    string memory denom
) external view returns (string memory response);

/// Queries the number of decimal places for the specified denom.
/// @param denom The denomination to query about.
/// @return response The number of decimals for the specified denom.
function decimals(
    string memory denom
) external view returns (uint8 response);

/// Queries the total supply of the specified denom.
/// @param denom The denomination to query about.
/// @return response The total supply of the specified denom.
function supply(
    string memory denom
) external view returns (uint256 response);
```

## Using the Precompile

### Setup

#### Prerequisites

Before getting started, ensure you have:

- **Node.js** (v18 or higher)
- **npm** or **yarn** package manager
- **EVM-compatible wallet**
- **HPX tokens** for gas and testing transfers
- **Hardhat** for development and testing

#### Install Dependencies

Install the required packages for interacting with Paxeer precompiles:

```bash
# Instantiate a new Hardhat 3 project (choose "Hardhat 3" and the Mocha + Ethers.js TypeScript setup)
npx hardhat --init

# Install ethers.js for smart contract interactions
npm install ethers

# Install Paxeer EVM bindings for precompile addresses and ABIs
npm install @sei-js/precompiles@2.1.2
```

#### Setup Hardhat Environment

Create a `hardhat.config.ts` file with the following content:

```typescript
import { defineConfig, configVariable } from 'hardhat/config';
import hardhatToolboxMochaEthers from '@nomicfoundation/hardhat-toolbox-mocha-ethers';

export default defineConfig({
  solidity: '0.8.28',
  networks: {
    hpx: {
      type: 'http',
      chainId: 1329,
      url: 'https://evm-rpc.sei-apis.com',
      accounts: [configVariable('PRIVATE_KEY')]
    }
  },
  plugins: [hardhatToolboxMochaEthers]
});
```

Store your private key in Hardhat's encrypted keystore (no plaintext `.env` file needed):

```bash
npx hardhat keystore set PRIVATE_KEY
```

#### Import Precompile Components

<Tabs>
<Tab title="JavaScript">
```typescript
// Import Bank precompile address and ABI
// View the entire ABI here: https://github.com/paxeer-network/paxeer-chain/tree/main/precompiles/bank
import { BANK_PRECOMPILE_ABI, BANK_PRECOMPILE_ADDRESS } from '@sei-js/precompiles';
import { ethers } from 'ethers';
```

</Tab>
<Tab title="Solidity">
```solidity
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

struct Coin {
uint256 amount;
string denom;
}

interface IBankPrecompile {
function send(
address fromAddress,
address toAddress,
string memory denom,
uint256 amount
) external returns (bool success);

    function sendNative(
        string memory toNativeAddress
    ) payable external returns (bool success);

    function balance(
        address acc,
        string memory denom
    ) external view returns (uint256 amount);

    function all_balances(
        address acc
    ) external view returns (Coin[] memory response);

    function name(
        string memory denom
    ) external view returns (string memory response);

    function symbol(
        string memory denom
    ) external view returns (string memory response);

    function decimals(
        string memory denom
    ) external view returns (uint8 response);

    function supply(
        string memory denom
    ) external view returns (uint256 response);

}

````
</Tab>
</Tabs>

<Info> **Precompile Address:** The bank precompile is deployed at `0x0000000000000000000000000000000000001001` </Info>

### Contract Initialization

<Tabs>
<Tab title="JavaScript">
Set up your provider, signer, and contract instance:

```typescript
// Using EVM-compatible wallet as the signer and provider
const provider = new ethers.BrowserProvider(window.ethereum);
await provider.send('eth_requestAccounts', []);
const signer = await provider.getSigner();

// Create a contract instance for the bank precompile
const bank = new ethers.Contract(BANK_PRECOMPILE_ADDRESS, BANK_PRECOMPILE_ABI, signer);
````

</Tab>
<Tab title="Solidity">
```solidity
// Initialize the contract instance in your Solidity code
contract TokenManager {
    IBankPrecompile constant BANK = IBankPrecompile(0x0000000000000000000000000000000000001001);
}
```
</Tab>
</Tabs>

### Native HPX vs Custom Tokens

**Native HPX Transfers:**

- Use `sendNative()` with payable value
- Denomination is always `uhpx` (micro-HPX)
- Parse to 18 digits when calling `sendNative()`

**Custom Token Transfers:**

- Use `send()` with specific denomination
- Requires prior token approval or ownership
- Support various decimal configurations

## Step-by-Step Guide: Using the Bank Precompile

### Send Native HPX Tokens

<Tabs>
<Tab title="JavaScript">

```typescript
// Send native HPX tokens to another address
const recipientAddress = 'pax1qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq6s8dpw';
const amountInPax = '0.1'; // 0.1 HPX

try {
  // msg.value is in wei (18 decimals), like any other EVM transaction
  const amountInWei = ethers.parseUnits(amountInPax, 18); // equivalent to ethers.parseEther(amountInPax)

  // Send native HPX
  const tx = await bank.sendNative(recipientAddress, {
    value: amountInWei,
    gasLimit: 300000n
  });

  const receipt = await tx.wait();
  console.log('Native HPX transfer successful:', receipt.hash);
  console.log(`Sent ${amountInPax} HPX to ${recipientAddress}`);
} catch (error) {
  console.error('Native HPX transfer failed:', error);
}
```

</Tab>
<Tab title="Solidity">

```solidity
contract PaymentProcessor {
    IBankPrecompile constant BANK = IBankPrecompile(0x0000000000000000000000000000000000001001);

    event PaymentSent(address indexed from, string to, uint256 amount);

    function sendHpxPayment(string memory recipient) external payable {
        require(msg.value > 0, "Payment amount must be greater than 0");

        bool success = BANK.sendNative{value: msg.value}(recipient);
        require(success, "Payment failed");

        emit PaymentSent(msg.sender, recipient, msg.value);
    }
}
```

</Tab>
</Tabs>

### Send Custom Tokens

<Tabs>
<Tab title="JavaScript">

<Warning>`send()` can only be called by the registered ERC20 native pointer contract for the given denom - calling it from a regular wallet will revert. The snippet below only illustrates the arguments a pointer contract passes. As a user, transfer native denoms through the denom's ERC20 pointer contract (`transfer()`), or use `sendNative()` for HPX.</Warning>

```typescript
// Illustration: how a registered ERC20 pointer contract calls send()
const fromAddress = '0x1234567890123456789012345678901234567890';
const toAddress = '0x9876543210987654321098765432109876543210';
const tokenDenom = 'uhpx';
const amount = '1';

// Amounts are in the denom's base units - check decimals() for each denom.
// uhpx has 6 decimals (1 HPX = 1,000,000 uhpx)
const tx = await bank.send(
  fromAddress,
  toAddress,
  tokenDenom,
  ethers.parseUnits(amount, 6),
  {
    gasLimit: 300000n
  }
);

const receipt = await tx.wait();
console.log('Custom token transfer successful:', receipt.hash);
console.log(`Sent ${amount} ${tokenDenom} from ${fromAddress} to ${toAddress}`);
```

</Tab>
<Tab title="Solidity">

```solidity
contract TokenTransferManager {
    IBankPrecompile constant BANK = IBankPrecompile(0x0000000000000000000000000000000000001001);

    event TokenTransfer(
        address indexed from,
        address indexed to,
        string denom,
        uint256 amount
    );

    function transferTokens(
        address to,
        string memory denom,
        uint256 amount
    ) external {
        // Transfer tokens from sender to recipient
        bool success = BANK.send(msg.sender, to, denom, amount);
        require(success, "Token transfer failed");

        emit TokenTransfer(msg.sender, to, denom, amount);
    }

    function batchTransfer(
        address[] memory recipients,
        string memory denom,
        uint256[] memory amounts
    ) external {
        require(recipients.length == amounts.length, "Array length mismatch");

        for (uint256 i = 0; i < recipients.length; i++) {
            bool success = BANK.send(msg.sender, recipients[i], denom, amounts[i]);
            require(success, "Batch transfer failed");

            emit TokenTransfer(msg.sender, recipients[i], denom, amounts[i]);
        }
    }
}
```

</Tab>
</Tabs>

### Query Account Balance

<Tabs>
<Tab title="JavaScript">

```typescript
// Query specific token balance
const accountAddress = '0x1234567890123456789012345678901234567890';
const tokenDenom = 'uhpx';

try {
  const balance = await bank.balance(accountAddress, tokenDenom);

  // Convert uhpx to HPX for display
  if (tokenDenom === 'uhpx') {
    const hpxBalance = ethers.formatUnits(balance, 6);
    console.log(`HPX Balance: ${hpxBalance} HPX`);
  } else {
    console.log(`${tokenDenom} Balance: ${balance.toString()}`);
  }
} catch (error) {
  console.error('Balance query failed:', error);
}
```

</Tab>
<Tab title="Solidity">

```solidity
contract BalanceChecker {
    IBankPrecompile constant BANK = IBankPrecompile(0x0000000000000000000000000000000000001001);

    function getUserHpxBalance(address user) external view returns (uint256) {
        return BANK.balance(user, "uhpx");
    }

    function getUserTokenBalance(
        address user,
        string memory denom
    ) external view returns (uint256) {
        return BANK.balance(user, denom);
    }

    function hasMinimumBalance(
        address user,
        string memory denom,
        uint256 minAmount
    ) external view returns (bool) {
        uint256 balance = BANK.balance(user, denom);
        return balance >= minAmount;
    }
}
```

</Tab>
</Tabs>

### Query All Balances

<Tabs>
<Tab title="JavaScript">

```typescript
// Query all token balances for an account
const accountAddress = '0x1234567890123456789012345678901234567890';

try {
  const allBalances = await bank.all_balances(accountAddress);

  console.log(`Account: ${accountAddress}`);
  console.log('All Balances:');

  allBalances.forEach((coin, index) => {
    if (coin.denom === 'uhpx') {
      const paxAmount = ethers.formatUnits(coin.amount, 6);
      console.log(`  ${index + 1}. ${paxAmount} HPX (${coin.denom})`);
    } else {
      console.log(`  ${index + 1}. ${coin.amount.toString()} ${coin.denom}`);
    }
  });
} catch (error) {
  console.error('All balances query failed:', error);
}
```

</Tab>
<Tab title="Solidity">

```solidity
contract PortfolioManager {
    IBankPrecompile constant BANK = IBankPrecompile(0x0000000000000000000000000000000000001001);

    struct Portfolio {
        address owner;
        Coin[] balances;
        uint256 totalValue; // In uhpx equivalent
    }

    function getPortfolio(address user) external view returns (Coin[] memory) {
        return BANK.all_balances(user);
    }

    function hasAnyBalance(address user) external view returns (bool) {
        Coin[] memory balances = BANK.all_balances(user);
        return balances.length > 0;
    }

    function countTokenTypes(address user) external view returns (uint256) {
        Coin[] memory balances = BANK.all_balances(user);
        return balances.length;
    }

    function findTokenBalance(
        address user,
        string memory targetDenom
    ) external view returns (uint256) {
        Coin[] memory balances = BANK.all_balances(user);

        for (uint256 i = 0; i < balances.length; i++) {
            if (keccak256(bytes(balances[i].denom)) == keccak256(bytes(targetDenom))) {
                return balances[i].amount;
            }
        }

        return 0; // Token not found
    }
}
```

</Tab>
</Tabs>

### Query Token Metadata

<Tabs>
<Tab title="JavaScript">

```typescript
// Query comprehensive token metadata
const tokenDenom = 'uhpx';

try {
  // Get all metadata in parallel
  const [name, symbol, decimals, totalSupply] = await Promise.all([bank.name(tokenDenom), bank.symbol(tokenDenom), bank.decimals(tokenDenom), bank.supply(tokenDenom)]);

  console.log('Token Metadata:');
  console.log(`  Denomination: ${tokenDenom}`);
  console.log(`  Name: ${name}`);
  console.log(`  Symbol: ${symbol}`);
  console.log(`  Decimals: ${decimals}`);
  console.log(`  Total Supply: ${ethers.formatUnits(totalSupply, decimals)}`);
} catch (error) {
  console.error('Metadata query failed:', error);
}

// Function to get formatted token info
async function getTokenInfo(denom) {
  try {
    const metadata = {
      denom: denom,
      name: await bank.name(denom),
      symbol: await bank.symbol(denom),
      decimals: await bank.decimals(denom),
      supply: await bank.supply(denom)
    };

    return {
      ...metadata,
      formattedSupply: ethers.formatUnits(metadata.supply, metadata.decimals)
    };
  } catch (error) {
    console.error(`Failed to get info for ${denom}:`, error);
    return null;
  }
}
```

</Tab>
<Tab title="Solidity">

```solidity
contract TokenRegistry {
    IBankPrecompile constant BANK = IBankPrecompile(0x0000000000000000000000000000000000001001);

    struct TokenInfo {
        string denom;
        string name;
        string symbol;
        uint8 decimals;
        uint256 totalSupply;
        bool isValid;
    }

    mapping(string => TokenInfo) public tokenRegistry;

    function getTokenInfo(
        string memory denom
    ) external view returns (TokenInfo memory) {
        TokenInfo memory info;

        info.denom = denom;
        info.name = BANK.name(denom);
        info.symbol = BANK.symbol(denom);
        info.decimals = BANK.decimals(denom);
        info.totalSupply = BANK.supply(denom);
        info.isValid = true;

        return info;
    }
}
```

</Tab>
</Tabs>

### Complete Integration Example

<Tabs>
<Tab title="JavaScript">

```typescript
import { BANK_PRECOMPILE_ABI, BANK_PRECOMPILE_ADDRESS } from '@sei-js/precompiles';
import { ethers } from 'ethers';

class PaxeerTokenManager {
  private bank: ethers.Contract;
  private signer: ethers.Signer;

  constructor(signer: ethers.Signer) {
    this.signer = signer;
    this.bank = new ethers.Contract(BANK_PRECOMPILE_ADDRESS, BANK_PRECOMPILE_ABI, signer);
  }

  // Send native HPX tokens
  async sendHpx(recipientAddress: string, amountInPax: string) {
    try {
      const amountInWei = ethers.parseUnits(amountInPax, 18);
      const tx = await this.bank.sendNative(recipientAddress, {
        value: amountInWei,
        gasLimit: 100000
      });

      const receipt = await tx.wait();
      return {
        success: true,
        transactionHash: receipt.hash,
        amount: amountInPax,
        recipient: recipientAddress
      };
    } catch (error) {
      return { success: false, error: error.message };
    }
  }

  // Get user's portfolio
  async getPortfolio(address: string) {
    try {
      const balances = await this.bank.all_balances(address);
      const portfolio = [];

      for (const coin of balances) {
        try {
          const metadata = await this.getTokenMetadata(coin.denom);
          portfolio.push({
            denom: coin.denom,
            amount: coin.amount.toString(),
            formattedAmount: ethers.formatUnits(coin.amount, metadata.decimals),
            ...metadata
          });
        } catch (metadataError) {
          // If metadata fails, still include the balance
          portfolio.push({
            denom: coin.denom,
            amount: coin.amount.toString(),
            formattedAmount: coin.amount.toString(),
            name: 'Unknown',
            symbol: 'Unknown',
            decimals: 0
          });
        }
      }

      return { success: true, portfolio };
    } catch (error) {
      return { success: false, error: error.message };
    }
  }

  // Get token metadata
  async getTokenMetadata(denom: string) {
    const [name, symbol, decimals, supply] = await Promise.all([this.bank.name(denom).catch(() => 'Unknown'), this.bank.symbol(denom).catch(() => 'Unknown'), this.bank.decimals(denom).catch(() => 0), this.bank.supply(denom).catch(() => 0n)]);

    return { name, symbol, decimals, supply: supply.toString() };
  }

  // Transfer custom tokens
  async transferToken(toAddress: string, denom: string, amount: string, decimals: number = 18) {
    try {
      const fromAddress = await this.signer.getAddress();
      const parsedAmount = ethers.parseUnits(amount, decimals);

      const tx = await this.bank.send(fromAddress, toAddress, denom, parsedAmount, {
        gasLimit: 150000
      });

      const receipt = await tx.wait();
      return {
        success: true,
        transactionHash: receipt.hash,
        from: fromAddress,
        to: toAddress,
        denom,
        amount
      };
    } catch (error) {
      return { success: false, error: error.message };
    }
  }

  // Batch operations
  async batchBalanceCheck(addresses: string[], denom: string) {
    try {
      const balances = await Promise.allSettled(addresses.map((addr) => this.bank.balance(addr, denom)));

      return balances.map((result, index) => ({
        address: addresses[index],
        balance: result.status === 'fulfilled' ? result.value.toString() : '0',
        success: result.status === 'fulfilled'
      }));
    } catch (error) {
      throw new Error(`Batch balance check failed: ${error.message}`);
    }
  }
}

// Usage example
async function bankExample() {
  const provider = new ethers.BrowserProvider(window.ethereum);
  await provider.send('eth_requestAccounts', []);
  const signer = await provider.getSigner();
  const tokenManager = new PaxeerTokenManager(signer);

  console.log('=== Paxeer Token Manager Demo ===');

  // 1. Get current user's portfolio
  const userAddress = await signer.getAddress();
  const portfolio = await tokenManager.getPortfolio(userAddress);

  if (portfolio.success) {
    console.log('User Portfolio:');
    portfolio.portfolio.forEach((token, index) => {
      console.log(`  ${index + 1}. ${token.formattedAmount} ${token.symbol} (${token.name})`);
    });
  }

  // 2. Send native HPX
  const sendResult = await tokenManager.sendHpx('pax1qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq6s8dpw', '0.01');

  if (sendResult.success) {
    console.log('HPX transfer successful:', sendResult.transactionHash);
  }

  // 3. Check balances for multiple addresses
  const addresses = ['0x1234567890123456789012345678901234567890', '0x9876543210987654321098765432109876543210'];

  const batchBalances = await tokenManager.batchBalanceCheck(addresses, 'uhpx');
  console.log('Batch balance results:', batchBalances);
}
```

</Tab>
<Tab title="Solidity">

**Complete Contract Example**

```solidity
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

interface IBankPrecompile {
    function send(address fromAddress, address toAddress, string memory denom, uint256 amount) external returns (bool success);
    function sendNative(string memory toNativeAddress) payable external returns (bool success);
    function balance(address acc, string memory denom) external view returns (uint256 amount);
    function all_balances(address acc) external view returns (Coin[] memory response);
    function name(string memory denom) external view returns (string memory response);
    function symbol(string memory denom) external view returns (string memory response);
    function decimals(string memory denom) external view returns (uint8 response);
    function supply(string memory denom) external view returns (uint256 response);
}

struct Coin {
    uint256 amount;
    string denom;
}

contract ComprehensiveTokenManager {
    IBankPrecompile constant BANK = IBankPrecompile(0x0000000000000000000000000000000000001001);

    // Events
    event PaymentProcessed(address indexed from, string to, uint256 amount, string denom);
    event TokenTransfer(address indexed from, address indexed to, string denom, uint256 amount);
    event BatchTransferCompleted(address indexed sender, uint256 totalRecipients);

    // State variables
    mapping(address => bool) public authorizedOperators;
    mapping(string => bool) public supportedTokens;
    address public owner;

    modifier onlyOwner() {
        require(msg.sender == owner, "Only owner can execute");
        _;
    }

    modifier onlyAuthorized() {
        require(authorizedOperators[msg.sender] || msg.sender == owner, "Not authorized");
        _;
    }

    constructor() {
        owner = msg.sender;
        authorizedOperators[msg.sender] = true;
        supportedTokens["uhpx"] = true;
    }

    // =============
    // Payment Functions
    // =============

    function processPayment(
        string memory recipient,
        uint256 amount
    ) external payable {
        require(msg.value >= amount, "Insufficient payment");

        bool success = BANK.sendNative{value: amount}(recipient);
        require(success, "Payment failed");

        // Refund excess
        if (msg.value > amount) {
            payable(msg.sender).transfer(msg.value - amount);
        }

        emit PaymentProcessed(msg.sender, recipient, amount, "uhpx");
    }

    function processTokenPayment(
        address recipient,
        string memory denom,
        uint256 amount
    ) external onlyAuthorized {
        require(supportedTokens[denom], "Token not supported");

        bool success = BANK.send(msg.sender, recipient, denom, amount);
        require(success, "Token payment failed");

        emit TokenTransfer(msg.sender, recipient, denom, amount);
    }

    // =============
    // Batch Operations
    // =============

    function batchHpxTransfer(
        string[] memory recipients,
        uint256[] memory amounts
    ) external payable {
        require(recipients.length == amounts.length, "Array length mismatch");
        require(recipients.length <= 50, "Too many recipients");

        uint256 totalAmount = 0;
        for (uint256 i = 0; i < amounts.length; i++) {
            totalAmount += amounts[i];
        }
        require(msg.value >= totalAmount, "Insufficient total payment");

        for (uint256 i = 0; i < recipients.length; i++) {
            bool success = BANK.sendNative{value: amounts[i]}(recipients[i]);
            require(success, "Batch transfer failed");

            emit PaymentProcessed(msg.sender, recipients[i], amounts[i], "uhpx");
        }

        // Refund excess
        if (msg.value > totalAmount) {
            payable(msg.sender).transfer(msg.value - totalAmount);
        }

        emit BatchTransferCompleted(msg.sender, recipients.length);
    }

    function batchTokenTransfer(
        address[] memory recipients,
        string memory denom,
        uint256[] memory amounts
    ) external onlyAuthorized {
        require(recipients.length == amounts.length, "Array length mismatch");
        require(recipients.length <= 20, "Too many recipients");
        require(supportedTokens[denom], "Token not supported");

        for (uint256 i = 0; i < recipients.length; i++) {
            bool success = BANK.send(msg.sender, recipients[i], denom, amounts[i]);
            require(success, "Batch token transfer failed");

            emit TokenTransfer(msg.sender, recipients[i], denom, amounts[i]);
        }

        emit BatchTransferCompleted(msg.sender, recipients.length);
    }

    // =============
    // Query Functions
    // =============

    function getUserPortfolio(address user) external view returns (Coin[] memory) {
        return BANK.all_balances(user);
    }

    function getUserBalance(address user, string memory denom) external view returns (uint256) {
        return BANK.balance(user, denom);
    }

    function getTokenInfo(string memory denom) external view returns (
        string memory name,
        string memory symbol,
        uint8 decimals,
        uint256 totalSupply
    ) {
        name = BANK.name(denom);
        symbol = BANK.symbol(denom);
        decimals = BANK.decimals(denom);
        totalSupply = BANK.supply(denom);
    }

    function checkSufficientBalance(
        address user,
        string memory denom,
        uint256 requiredAmount
    ) external view returns (bool) {
        uint256 balance = BANK.balance(user, denom);
        return balance >= requiredAmount;
    }

    function batchBalanceCheck(
        address[] memory users,
        string memory denom
    ) external view returns (uint256[] memory balances) {
        balances = new uint256[](users.length);
        for (uint256 i = 0; i < users.length; i++) {
            balances[i] = BANK.balance(users[i], denom);
        }
    }

    // =============
    // Admin Functions
    // =============

    function addSupportedToken(string memory denom) external onlyOwner {
        supportedTokens[denom] = true;
    }

    function removeSupportedToken(string memory denom) external onlyOwner {
        supportedTokens[denom] = false;
    }

    function addOperator(address operator) external onlyOwner {
        authorizedOperators[operator] = true;
    }

    function removeOperator(address operator) external onlyOwner {
        require(operator != owner, "Cannot remove owner");
        authorizedOperators[operator] = false;
    }

    // Emergency withdraw function
    function emergencyWithdraw() external onlyOwner {
        payable(owner).transfer(address(this).balance);
    }

    // =============
    // Utility Functions
    // =============

    function convertPaxToUhpx(uint256 paxAmount) pure external returns (uint256) {
        return paxAmount * 1e6;
    }

    function convertUhpxToPax(uint256 upaxAmount) pure external returns (uint256) {
        return upaxAmount / 1e6;
    }

    receive() external payable {}
}
```

Compile the contract:

```bash
npx hardhat compile
```

**Deployment Script**

First, deploy the contract using Hardhat:

```javascript
import { network } from 'hardhat';

// Hardhat 3 exposes ethers through a network connection rather than a global import
const { ethers } = await network.create();

async function main() {
  // The signer comes from the network config (encrypted keystore)
  const [deployer] = await ethers.getSigners();
  console.log('Deploying contracts with the account:', deployer.address);

  const balance = await deployer.provider.getBalance(deployer.address);
  console.log('Account balance:', ethers.formatEther(balance));

  const ComprehensiveTokenManager = await ethers.getContractFactory('ComprehensiveTokenManager');
  console.log('Deploying ComprehensiveTokenManager...', ComprehensiveTokenManager);
  const comprehensiveTokenManager = await ComprehensiveTokenManager.deploy({
    gasLimit: 5000000n // Set a reasonable gas limit
  });
  await comprehensiveTokenManager.waitForDeployment();

  console.log('ComprehensiveTokenManager deployed to:', await comprehensiveTokenManager.getAddress());
}

main().catch((error) => {
  console.error(error);
  process.exitCode = 1;
});
```

To deploy, run:

```bash
npx hardhat run scripts/deploy.js --network paxeer
```

Next look through the integration example:

```javascript
import { network } from 'hardhat';
import TokenManager from './artifacts/evm/evm-general/ComprehensiveTokenManager.sol/ComprehensiveTokenManager.json' with { type: 'json' };

// Hardhat 3 exposes ethers through a network connection rather than a global import
const { ethers } = await network.create();

async function main() {
  // The signer comes from the network config (encrypted keystore)
  const [deployer] = await ethers.getSigners();
  console.log('Interacting with the account:', deployer.address);

  const address = '0xYourDeployedContractAddress'; // Replace with your contract address
  const tokenManager = new ethers.Contract(address, TokenManager.abi, deployer);
  console.log('TokenManager deployed to:', address);

  // Add some supported tokens
  const tokens = ['uhpx', 'uatom', 'ubtc'];

  for (const token of tokens) {
    try {
      const tx = await tokenManager.addSupportedToken(token);
      await tx.wait();
      console.log('Added supported token:', token);
    } catch (error) {
      console.error('Failed to add token:', token, error.message);
    }
  }

  try {
    const allBalances = await tokenManager.getUserPortfolio(deployer.address);
    console.log('All balances:', allBalances);
  } catch (error) {
    console.error('Failed to get user portfolio:', error.message);
  }
}

main()
  .then(() => process.exit(0))
  .catch((error) => {
    console.error(error);
    process.exit(1);
  });
```

Then run the integration example:

```bash
npx hardhat run scripts/integrationExample.js --network paxeer
```

</Tab>
</Tabs>

## Security Considerations & Risks

### Transaction Security

- **Amount Validation:** Always validate transfer amounts and ensure sufficient balances
- **Address Verification:** Verify recipient addresses are valid before sending tokens
- **Reentrancy Protection:** Be aware of potential reentrancy when combining with other contracts

### Permission Management

```solidity
// Example of secure permission patterns
modifier onlyTokenHolder(string memory denom, uint256 minAmount) {
    require(BANK.balance(msg.sender, denom) >= minAmount, "Insufficient token balance");
    _;
}

modifier validRecipient(address recipient) {
    require(recipient != address(0), "Invalid recipient");
    require(recipient != address(this), "Cannot send to contract");
    _;
}
```

## Troubleshooting

### Common Issues and Solutions

#### Transaction Failures

```typescript
// Handle common transfer errors
try {
  const tx = await bank.sendNative(recipient, { value: amount });
  await tx.wait();
} catch (error) {
  if (error.message.includes('insufficient funds')) {
    console.error('Insufficient balance for transfer');
  } else if (error.message.includes('invalid address')) {
    console.error('Invalid recipient address format');
  } else {
    console.error('Transfer failed:', error.message);
  }
}
```

#### Balance Query Issues

```typescript
// Safe balance checking with error handling
async function safeGetBalance(address: string, denom: string) {
  try {
    const balance = await bank.balance(address, denom);
    return { success: true, balance: balance.toString() };
  } catch (error) {
    if (error.message.includes('not found')) {
      return { success: true, balance: '0' }; // No balance = 0
    }
    return { success: false, error: error.message };
  }
}
```

### Error Code Reference

| Error | Cause | Solution |
| --- | --- | --- |
| `insufficient funds` | Not enough balance for transfer | Check balance before transfer |
| `invalid address` | Malformed address format | Use proper EVM (0x...) or Paxeer (pax1...) format |
| `unknown denomination` | Token denom doesn't exist | Verify token denomination format |
| `amount overflow` | Amount exceeds uint256 limits | Use appropriate amount ranges |
| `metadata not found` | Token metadata not available | Handle missing metadata gracefully |


## Important Notes

<Info>

**Remember:** Always verify token denominations and handle errors gracefully in production applications!

- **HPX Native:** While reading from chain it is formatted to 6 decimal places (1 HPX = 1,000,000 uhpx) and while writing to chain it is parsed to 18 decimal places.
- **Custom Tokens:** Check decimals using `decimals()` function
- **Display Formatting:** Always format amounts with proper decimal places for user display

</Info>

### Gas Optimization

- **Batch Operations:** Use batch functions for multiple operations to save gas
- **Query Efficiency:** Cache frequently accessed token metadata
- **Error Handling:** Implement proper error handling to avoid failed transaction costs

### Integration Best Practices

- **Balance Checks:** Always verify sufficient balance before transfers
- **Error Recovery:** Implement retry logic for failed transactions
- **User Experience:** Provide clear feedback on transaction status
- **Decimal Handling:** Use proper decimal formatting for different token types

<Info>View the Bank precompile source code and the contract ABI [here](https://github.com/paxeer-network/paxeer-chain/tree/main/precompiles/bank).</Info>
