Overview

Tempo is an EVM-compatible blockchain purpose-built for global payments. It features native Account Abstraction (EIP-7702) at the protocol level, enabling advanced transaction capabilities such as batch transactions and custom fee tokens — all natively supported at the protocol layer. Tempo eliminates volatile gas tokens by using USD-denominated TIP-20 stablecoins for all transaction fees, with ultra-low costs under $0.001 per transfer. The network achieves sub-second finality (~1 second) and ~0.4 second block times using Simplex BFT consensus.

Explorer

https://explore.mainnet.tempo.xyz

Wallets Types

BitGo enables holding tempo in the following wallet types:

Multisig Cold Multisig Hot MPC Cold MPC Hot
Custody
Self-Custody

Ticker Symbols

Mainnet Testnet
TEMPO TTEMPO

Faucet

You can use a faucet to obtain free testnet TIP-20 tokens for development and testing purposes.

Faucet: https://docs.tempo.xyz/quickstart/faucet

Units

Tempo has no native coin. All balances and fees use TIP-20 USD stablecoins, which adhere to 6 decimal places:

  • 1 TIP-20 token = 106 base units (micro-units)
  • 1 base unit = 10-6 TIP-20 token

For that reason, only string balance properties are available, which are balanceString, confirmedBalanceString, and spendableBalanceString.

Memos / memoId addressing

Tempo uses a single-account model: each wallet has one base address (a standard 0x-prefixed EVM address). Append a ?memoId=<N> query parameter to that base address to route incoming payments to a specific user or purpose — you never generate a new on-chain address:

0x2476602c78e9a5e0563320c78878faa3952b256f?memoId=123
  • Set <N> to a non-negative integer with no leading zeros, such as 0, 1, or 123. Tempo rejects -1, 1.5, 01, and abc.
  • Reuse the single base address across all users; the memoId uniquely identifies each. This removes per-user address generation and eliminates manual consolidation.
  • The memoId never changes ownership — every ?memoId=<N> variant belongs to the same wallet.
  • ?memoId=0 is equivalent to the bare base address. A deposit with no memo credits memoId=0, so treat 0x2476...?memoId=0 and 0x2476... as the same destination.

DX note: wallet.receiveAddress() appends a default ?memoId=0 suffix, producing 0x2476...?memoId=0. External EVM tools (wallets, explorers, exchanges) do not recognize this suffix, so strip ?memoId=0 before displaying the address or sharing it for deposits. Only append a suffix when you need a specific non-zero memoId.

memoId works only through TIP-20 transferWithMemo

BitGo attributes deposits by reading the on-chain memo emitted by the TIP-20 transferWithMemo method. The memoId model therefore applies only to TIP-20 transfers. Any operation that must carry a memoId — including mint, burn, or other token actions — must use the corresponding *WithMemo variant; plain calls without a memo credit memoId=0 by default.

Reading the memoId from transfers

The List Transfers response does not return the Tempo memo ID in entries[].memo. Read it from the ?memoId=<N> suffix on entries[].address instead.

Fees

Tempo uses a fixed base fee model rather than EIP-1559 dynamic pricing, making transaction costs predictable and ultra-low — basic transfers cost less than $0.001 USD. Tempo pays all fees in USD-denominated TIP-20 stablecoins. If a user's preferred fee token differs from the validator's, an on-chain Fee AMM automatically converts it at settlement. The network calculates the total fee as:

fee = ceil(base_fee * gas_used / 10^12)
Transaction Type Gas Cost Estimated Fee (USD)
AA Transaction (P256) 26,000 ~$0.001
Batch Transaction (3 calls) ~50,000 < $0.002

Create Wallet

export BITGO_EXPRESS_HOST="<YOUR_LOCALHOST>"
export COIN="ttempo"
export ACCESS_TOKEN="<YOUR_ACCESS_TOKEN>"
export LABEL="<DESIRED_WALLET_NAME>"
export PASSPHRASE="<YOUR_BITGO_LOGIN_PASSPHRASE>"
export ENTERPRISE_ID="<YOUR_ENTERPRISE_ID>"

curl -X POST \
 http://$BITGO_EXPRESS_HOST/api/v2/$COIN/wallet/generate \
 -H 'Content-Type: application/json' \
 -H "Authorization: Bearer $ACCESS_TOKEN" \
  -d '{
    "label": "'"$LABEL"'",
    "passphrase": "'"$PASSPHRASE"'",
    "enterprise": "'"$ENTERPRISE_ID"'",
    "walletVersion": 4
}'
bitgo
  .coin('ttempo')
  .wallets()
  .generateWallet({
    label: 'My Test Wallet',
    passphrase: 'secretpassphrase1a5df8380e0e30',
    enterprise: '5612c2beeecf83610b621b90964448cd',
    walletVersion: 4,
  })
  .then(function (wallet) {
    // print the new wallet
    console.dir(wallet);
  });

Create Address

Tempo follows a single-account model (see Memos / memoId addressing). The returned address may include a ?memoId=<N> suffix; the base 0x address is reusable across many memoId values rather than requiring a new address per deposit.

export WALLET="585c51a5df8380e0e3082e46"
export ACCESS_TOKEN="<YOUR_ACCESS_TOKEN>"

curl -X POST \
-H "Authorization: Bearer $ACCESS_TOKEN" \
https://app.bitgo-test.com/api/v2/ttempo/wallet/$WALLET/address
bitgo
  .coin('ttempo')
  .wallets()
  .getWallet({ id: '585c51a5df8380e0e3082e46' })
  .then(function (wallet) {
    return wallet.createAddress();
  })
  .then(function (newAddress) {
    // print new address details
    console.dir(newAddress);
  });

Estimate Fee

export COIN="ttempo"
export ACCESS_TOKEN="<YOUR_ACCESS_TOKEN>"

curl -X GET \
 https://app.bitgo-test.com/api/v2/$COIN/tx/fee \
 -H 'Content-Type: application/json' \
 -H "Authorization: Bearer $ACCESS_TOKEN"
const BitGoJS = require('../../../src/index.js');
const bitgo = new BitGoJS.BitGo({ env: 'test' });
const accessToken = '<YOUR_ACCESS_TOKEN>';
const coin = 'ttempo';

async function getFeeEstimate() {
  try {
    await bitgo.authenticateWithAccessToken({ accessToken });
    const res = await bitgo.coin(coin).feeEstimate({ numBlocks: 2 });
    console.dir(res);
  } catch (err) {
    console.error('Error fetching fee estimate:', err);
  }
}

getFeeEstimate();

Transact

Tempo has no native coin — every transfer moves a TIP-20 token, so a network-prefixed tokenName is required on each transfer: use tempo:<token> on mainnet or ttempo:<token> on testnet, such as tempo:pathusd or ttempo:pathusd. All amounts use 6-decimal base units (1 TIP-20 token = 10^6 base units, so 1000000 = 1 token).

export BITGO_EXPRESS_HOST="<YOUR_LOCALHOST>"
export COIN="ttempo"
export WALLET_ID="<YOUR_WALLET_ID>"
export ACCESS_TOKEN="<YOUR_ACCESS_TOKEN>"
export ADDRESS="<DESTINATION_ADDRESS>"
export TOKEN_NAME="ttempo:pathusd"
export AMOUNT="1000000"  # 1 token = 10^6 base units
export WALLET_PASSPHRASE="<YOUR_WALLET_PASSPHRASE>"

curl -X POST \
 http://$BITGO_EXPRESS_HOST/api/v2/$COIN/wallet/$WALLET_ID/sendcoins \
  -H 'Content-Type: application/json' \
  -H "Authorization: Bearer $ACCESS_TOKEN" \
  -d '{
    "address": "'"$ADDRESS"'",
    "amount": "'"$AMOUNT"'",
    "tokenName": "'"$TOKEN_NAME"'",
    "walletPassphrase": "'"$WALLET_PASSPHRASE"'"
}'
const tx = await fundedWallet.send({
    address: '<DESTINATION_ADDRESS>',
    amount: '1000000', // 1 token = 10^6 base units
    tokenName: 'ttempo:pathusd',
    walletPassphrase: process.env.PASSWORD,
  });

Send a TIP-20 transfer with a memoId

To direct a transfer to a specific memoId (see Memos / memoId addressing), append the ?memoId=<N> suffix to the recipient address. The same tokenName and base-unit amount rules apply.

export BITGO_EXPRESS_HOST="<YOUR_LOCALHOST>"
export COIN="ttempo"
export WALLET_ID="<YOUR_WALLET_ID>"
export ACCESS_TOKEN="<YOUR_ACCESS_TOKEN>"
export ADDRESS="0x2476602c78e9a5e0563320c78878faa3952b256f?memoId=123"
export TOKEN_NAME="ttempo:pathusd"
export AMOUNT="1000000"  # 1 token = 10^6 base units
export WALLET_PASSPHRASE="<YOUR_WALLET_PASSPHRASE>"

curl -X POST \
 http://$BITGO_EXPRESS_HOST/api/v2/$COIN/wallet/$WALLET_ID/sendcoins \
  -H 'Content-Type: application/json' \
  -H "Authorization: Bearer $ACCESS_TOKEN" \
  -d '{
    "address": "'"$ADDRESS"'",
    "amount": "'"$AMOUNT"'",
    "tokenName": "'"$TOKEN_NAME"'",
    "walletPassphrase": "'"$WALLET_PASSPHRASE"'"
}'
const tx = await fundedWallet.send({
    address: '0x2476602c78e9a5e0563320c78878faa3952b256f?memoId=123',
    amount: '1000000', // 1 token = 10^6 base units
    tokenName: 'ttempo:pathusd',
    walletPassphrase: process.env.PASSWORD,
  });

The call returns the built transfer. The recipient memoId stays on transfer.entries[].address:

{
  "txid": "0x9f2c1d4e...c7a8",
  "status": "signed",
  "transfer": {
    "id": "665f0a1b2c3d4e5f60718293",
    "coin": "ttempo:pathusd",
    "wallet": "585c51a5df8380e0e3082e46",
    "txid": "0x9f2c1d4e...c7a8",
    "type": "send",
    "state": "signed",
    "valueString": "-1000000",
    "feeString": "1000",
    "entries": [
      {
        "address": "0x2476602c78e9a5e0563320c78878faa3952b256f?memoId=123",
        "valueString": "1000000"
      }
    ]
  },
  "txRequest": {
    "txRequestId": "b1c2d3e4-5678-90ab-cdef-1234567890ab",
    "state": "delivered"
  }
}

Send to Many

export BITGO_EXPRESS_HOST="<YOUR_LOCALHOST>"
export COIN="ttempo"
export WALLET_ID="<YOUR_WALLET_ID>"
export ACCESS_TOKEN="<YOUR_ACCESS_TOKEN>"
export ADDRESS_1="<DESTINATION_ADDRESS_1>"
export AMOUNT_1="1000000"  # 1 token = 10^6 base units
export TOKEN_NAME="ttempo:pathusd"
export WALLET_PASSPHRASE="<YOUR_WALLET_PASSPHRASE>"

curl -X POST \
  http://$BITGO_EXPRESS_HOST/api/v2/$COIN/wallet/$WALLET_ID/sendmany \
  -H 'Content-Type: application/json' \
  -H "Authorization: Bearer $ACCESS_TOKEN" \
  -d '{
    "recipients": [
    {
      "address": "'"$ADDRESS_1"'",
      "amount": "'"$AMOUNT_1"'",
      "tokenName": "'"$TOKEN_NAME"'"
    }
  ],
    "walletPassphrase": "'"$WALLET_PASSPHRASE"'"
}'
let params = {
  recipients: [
    {
      amount: "1000000", // 1 token = 10^6 base units
      address: "<DESTINATION_ADDRESS_1>", // optionally append ?memoId=<N>
      tokenName: "ttempo:pathusd", // required on every recipient
    }
  ],
  walletPassphrase: "<YOUR_WALLET_PASSPHRASE>",
};
wallet.sendMany(params).then(function (transaction) {
  // Print transaction details
  console.dir(transaction);
});

Paying fees with a custom token

Tempo has no native gas coin — transaction fees are paid in a TIP-20 stablecoin. By default, BitGo pays fees in pathUSD. To pay fees in a different TIP-20 token, pass the token's contract address as the top-level feeToken parameter on your send request; it applies to the whole transaction (one fee token per transaction).

The fee token is independent of the token being transferred — you can send ttempo:pathusd while paying the fee in ttempo:usd1. If the fee token differs from the validator's preferred token, Tempo's on-chain Fee AMM converts it automatically at settlement.

  • Value: the fee token's on-chain contract address (not the tokenName).
  • Default: omit feeToken to pay fees in pathUSD.
  • Applies to both sendcoins (single) and sendmany requests.

Common testnet (ttempo) fee-token contract addresses:

Token tokenName Contract address
pathUSD (default) ttempo:pathusd 0x20c0000000000000000000000000000000000000
alphaUSD ttempo:alphausd 0x20c0000000000000000000000000000000000001

On mainnet (tempo), pathUSD uses the same 0x20c0…0000 address; look up other tokens in Statics.

export BITGO_EXPRESS_HOST="<YOUR_LOCALHOST>"
export COIN="ttempo"
export WALLET_ID="<YOUR_WALLET_ID>"
export ACCESS_TOKEN="<YOUR_ACCESS_TOKEN>"
export ADDRESS="<DESTINATION_ADDRESS>"
export TOKEN_NAME="ttempo:pathusd"
export AMOUNT="1000000"  # 1 token = 10^6 base units
export FEE_TOKEN="<FEE_TOKEN_CONTRACT_ADDRESS>"
export WALLET_PASSPHRASE="<YOUR_WALLET_PASSPHRASE>"

curl -X POST \
 http://$BITGO_EXPRESS_HOST/api/v2/$COIN/wallet/$WALLET_ID/sendcoins \
  -H 'Content-Type: application/json' \
  -H "Authorization: Bearer $ACCESS_TOKEN" \
  -d '{
    "address": "'"$ADDRESS"'",
    "amount": "'"$AMOUNT"'",
    "tokenName": "'"$TOKEN_NAME"'",
    "feeToken": "'"$FEE_TOKEN"'",
    "walletPassphrase": "'"$WALLET_PASSPHRASE"'"
}'
const tx = await fundedWallet.send({
    address: '<DESTINATION_ADDRESS>',
    amount: '1000000', // 1 token = 10^6 base units
    tokenName: 'ttempo:pathusd',
    feeToken: '<FEE_TOKEN_CONTRACT_ADDRESS>',
    walletPassphrase: process.env.PASSWORD,
  });

The chosen fee token is echoed back on the response under coinSpecific:

{
  "coinSpecific": {
    "feeToken": "<FEE_TOKEN_CONTRACT_ADDRESS>",
    "feeTokenName": "<FEE_TOKEN_NAME>"
  }
}

Stake

Tempo staking is out of scope for this integration.

See Also