Skip to content

The send manager precompile at 0x0000000000000000000000000000000000001003: one send method that moves native coins and alias denoms from a contract via x/bank.

The send manager precompile lets a Solidity contract send native Cosmos coins, including alias denoms such as badgeslp:..., without ERC20 wrapping. Address: 0x0000000000000000000000000000000000001003. Standard coin accounting stays in x/bank; alias denom balances stay in x/tokenization.

Example

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

import "./interfaces/ISendManagerPrecompile.sol";

contract TokenSender {
    ISendManagerPrecompile constant SENDMANAGER =
        ISendManagerPrecompile(0x0000000000000000000000000000000000001003);

    function sendCoins(
        string memory toAddress,
        string memory denom,
        uint256 amount
    ) external returns (bool) {
        string memory msgJson = string(abi.encodePacked(
            '{"to_address":"', toAddress,
            '","amount":[{"denom":"', denom,
            '","amount":"', _uintToString(amount), '"}]}'
        ));

        return SENDMANAGER.send(msgJson);
    }

    function _uintToString(uint256 value) internal pure returns (string memory) {
        if (value == 0) return "0";
        uint256 temp = value;
        uint256 digits;
        while (temp != 0) { digits++; temp /= 10; }
        bytes memory buffer = new bytes(digits);
        while (value != 0) {
            digits -= 1;
            buffer[digits] = bytes1(uint8(48 + uint256(value % 10)));
            value /= 10;
        }
        return string(buffer);
    }
}

From TypeScript with ethers.js:

typescript
import { ethers } from "ethers";

const SENDMANAGER_ADDRESS = "0x0000000000000000000000000000000000001003";

const sendManagerABI = [
  "function send(string memory msgJson) external returns (bool success)"
];

const sendManager = new ethers.Contract(SENDMANAGER_ADDRESS, sendManagerABI, signer);

const msgJson = JSON.stringify({
  to_address: "bb1py4mfpg6uf59qkyzg0nmau322c5873eeysp5ue",
  amount: [{ denom: "ubadge", amount: "1000000000" }]
});

const tx = await sendManager.send(msgJson);
await tx.wait();

Interface

The ABI has exactly one method.

solidity
interface ISendManagerPrecompile {
    /// @notice Send native Cosmos coins from the caller to a recipient
    /// @param msgJson JSON string matching MsgSendWithAliasRouting protobuf format
    /// @return success Whether the send succeeded
    function send(string memory msgJson) external returns (bool success);
}

JSON Format

The JSON is a MsgSendWithAliasRouting from x/sendmanager. The Go side decodes it with encoding/json, so use the snake_case field names.

json
{
  "to_address": "bb1py4mfpg6uf59qkyzg0nmau322c5873eeysp5ue",
  "amount": [
    {"denom": "ubadge", "amount": "1000000000"},
    {"denom": "badgeslp:64:utoken", "amount": "5"}
  ]
}
FieldTypeRequiredDescription
to_addressstringyesRecipient. A bb1 bech32 address, or a 0x address (converted to bech32 on the Go side)
amountCoin[]yesCoins to send. amount values are integer strings in the base unit (ubadge has 9 decimals)
from_addressstringnoIgnored. Always overwritten with the caller (msg.sender)

Behavior

  • from_address is set from msg.sender after unmarshal, so a contract can only spend its own balance.
  • ValidateBasic runs before the send. Empty or invalid coins and addresses fail with code 1.
  • Alias denoms route through the tokenization module; standard denoms go through x/bank. See Send manager module and Alias Denoms.
  • A successful send emits a precompile_send event with from, to_address, and amount attributes.
  • Amounts are in Cosmos precision (9 decimals for BADGE), not the EVM's 18. See Developer Guide.

Gas

ComponentGas
GasSendBase30,000
Fixed buffer added by RequiredGas150,000
Total charged up front180,000
GasPerCoin (per entry in amount, defined in the precompile for dynamic estimates)2,000

The base is deducted before the precompile runs; the bank transfer itself uses the remaining gas of the call.

Error Codes

CodeNameDescription
1InvalidInputInvalid JSON, coins, or address
2SendFailedThe send operation failed
3InsufficientBalanceThe caller does not hold enough of a denom (ErrInsufficientFunds)
4InternalErrorInternal error
5UnauthorizedUnauthorized operation

Errors revert with the text precompile error [code=N]: message: details.

Edit this page on GitHub