Guides
Tempo
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 =
106base units (micro-units) - 1 base unit =
10-6TIP-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 as0,1, or123. Tempo rejects-1,1.5,01, andabc. - Reuse the single base address across all users; the
memoIduniquely identifies each. This removes per-user address generation and eliminates manual consolidation. - The
memoIdnever changes ownership — every?memoId=<N>variant belongs to the same wallet. ?memoId=0is equivalent to the bare base address. A deposit with no memo creditsmemoId=0, so treat0x2476...?memoId=0and0x2476...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
feeTokento pay fees in pathUSD. - Applies to both
sendcoins(single) andsendmanyrequests.
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.