Skip to main content
The Go SDK provides Go bindings for building payment applications, backend services, and integrations with Tempo. It offers full support for TIP-20 tokens, batch payments, and Tempo’s account abstraction features.
The Go SDK is currently under development. Check the Tempo repository for the latest updates.

Installation

Install the Go SDK:
go get github.com/tempoxyz/tempo/sdk/go

Quick Start

Configure Client

Connect to Tempo testnet:
package main

import (
    "context"
    "log"
    
    "github.com/tempoxyz/tempo/sdk/go"
)

func main() {
    client, err := tempo.Dial("https://rpc.moderato.tempo.xyz")
    if err != nil {
        log.Fatal(err)
    }
    defer client.Close()

    blockNumber, err := client.BlockNumber(context.Background())
    if err != nil {
        log.Fatal(err)
    }
    
    log.Printf("Current block: %d", blockNumber)
}

Get Token Balance

Query TIP-20 token balances:
import (
    "context"
    "math/big"
    
    "github.com/ethereum/go-ethereum/common"
    "github.com/tempoxyz/tempo/sdk/go/contracts"
)

tokenAddress := common.HexToAddress("0x20c0000000000000000000000000000000000001")
token, err := contracts.NewTIP20(tokenAddress, client)
if err != nil {
    log.Fatal(err)
}

balance, err := token.BalanceOf(
    nil, // call options
    common.HexToAddress("0x742d35Cc6634C0532925a3b844Bc9e7595f0bEbb"),
)
if err != nil {
    log.Fatal(err)
}

log.Printf("Balance: %s", balance.String())

Send Transfer

Send a TIP-20 token transfer:
import (
    "crypto/ecdsa"
    
    "github.com/ethereum/go-ethereum/accounts/abi/bind"
    "github.com/ethereum/go-ethereum/crypto"
)

// Load private key
privateKey, err := crypto.HexToECDSA("your-private-key")
if err != nil {
    log.Fatal(err)
}

// Create transactor
auth, err := bind.NewKeyedTransactorWithChainID(privateKey, big.NewInt(42431))
if err != nil {
    log.Fatal(err)
}

// Send transfer
tx, err := token.Transfer(
    auth,
    common.HexToAddress("0x742d35Cc6634C0532925a3b844Bc9e7595f0bEbb"),
    big.NewInt(100000000), // 100 tokens (6 decimals)
)
if err != nil {
    log.Fatal(err)
}

log.Printf("Transaction sent: %s", tx.Hash().Hex())

// Wait for receipt
receipt, err := bind.WaitMined(context.Background(), client, tx)
if err != nil {
    log.Fatal(err)
}

log.Printf("Transaction mined in block %d", receipt.BlockNumber)

TIP-20 Tokens

Transfer with Memo

Include reconciliation data:
memo := [32]byte{}
copy(memo[:], []byte("INV-12345"))

tx, err := token.TransferWithMemo(
    auth,
    recipient,
    amount,
    memo,
)

Watch Transfer Events

Listen for incoming transfers:
import (
    "github.com/ethereum/go-ethereum/accounts/abi/bind"
    "github.com/ethereum/go-ethereum/core/types"
)

// Create watch options
watchOpts := &bind.WatchOpts{
    Context: context.Background(),
}

// Watch for transfers
events := make(chan *contracts.TIP20Transfer)
sub, err := token.WatchTransfer(watchOpts, events, nil, nil)
if err != nil {
    log.Fatal(err)
}
defer sub.Unsubscribe()

for {
    select {
    case err := <-sub.Err():
        log.Fatal(err)
    case transfer := <-events:
        log.Printf("Transfer: %s -> %s: %s",
            transfer.From.Hex(),
            transfer.To.Hex(),
            transfer.Value.String(),
        )
    }
}

Filter Historical Transfers

Query past transfer events:
// Create filter options
filterOpts := &bind.FilterOpts{
    Start: 0,
    End:   nil, // latest block
}

// Filter transfers to a specific address
recipient := common.HexToAddress("0x742d35Cc6634C0532925a3b844Bc9e7595f0bEbb")
iter, err := token.FilterTransfer(
    filterOpts,
    nil,         // any sender
    []common.Address{recipient}, // specific recipient
)
if err != nil {
    log.Fatal(err)
}
defer iter.Close()

for iter.Next() {
    log.Printf("Block %d: %s tokens",
        iter.Event.Raw.BlockNumber,
        iter.Event.Value.String(),
    )
}

Batch Payments

Send multiple transfers atomically using Tempo Transactions:
import (
    "github.com/tempoxyz/tempo/sdk/go/types"
)

// Create batch of calls
calls := []types.Call{
    {
        To:    tokenAddress,
        Data:  encodeTransfer(recipient1, amount1),
        Value: big.NewInt(0),
    },
    {
        To:    tokenAddress,
        Data:  encodeTransfer(recipient2, amount2),
        Value: big.NewInt(0),
    },
}

// Create Tempo Transaction
tx := types.NewTempoTransaction(
    auth.From,
    calls,
    auth.Nonce,
    auth.GasLimit,
    auth.GasFeeCap,
    auth.GasTipCap,
)

// Sign and send
signedTx, err := auth.Signer(auth.From, tx)
if err != nil {
    log.Fatal(err)
}

err = client.SendTransaction(context.Background(), signedTx)
if err != nil {
    log.Fatal(err)
}

log.Printf("Batch transaction sent: %s", signedTx.Hash().Hex())

Account Abstraction

Fee Sponsorship

Pay gas fees for users:
tx := types.NewTempoTransaction(
    userAddress,
    calls,
    nonce,
    gasLimit,
    maxFeePerGas,
    maxPriorityFeePerGas,
)

// Set fee token for sponsor
tx.SetFeeToken(tokenAddress)

// Sponsor signs the transaction
signedTx, err := sponsorAuth.Signer(sponsorAuth.From, tx)
if err != nil {
    log.Fatal(err)
}

err = client.SendTransaction(context.Background(), signedTx)

Scheduled Payments

Set validity windows:
import "time"

now := uint64(time.Now().Unix())
oneHourLater := now + 3600

tx := types.NewTempoTransaction(
    auth.From,
    calls,
    nonce,
    gasLimit,
    maxFeePerGas,
    maxPriorityFeePerGas,
)

tx.SetValidAfter(now)
tx.SetValidBefore(oneHourLater)

Network Configuration

Configure for Tempo testnet:
import (
    "github.com/ethereum/go-ethereum/ethclient"
    "github.com/ethereum/go-ethereum/rpc"
)

const (
    TempoTestnetChainID = 42431
    TempoTestnetRPC     = "https://rpc.moderato.tempo.xyz"
    TempoTestnetWSS     = "wss://rpc.moderato.tempo.xyz"
)

// HTTP connection
client, err := ethclient.Dial(TempoTestnetRPC)

// WebSocket connection for subscriptions
clientWS, err := ethclient.Dial(TempoTestnetWSS)

Error Handling

Handle common errors:
import (
    "errors"
    
    "github.com/ethereum/go-ethereum/accounts/abi"
    "github.com/ethereum/go-ethereum/core/types"
)

tx, err := token.Transfer(auth, recipient, amount)
if err != nil {
    // Check for specific errors
    if errors.Is(err, abi.ErrInvalidData) {
        log.Printf("Invalid transaction data: %v", err)
    } else {
        log.Printf("Transfer failed: %v", err)
    }
    return
}

receipt, err := bind.WaitMined(ctx, client, tx)
if err != nil {
    log.Fatal(err)
}

if receipt.Status == types.ReceiptStatusFailed {
    log.Printf("Transaction failed on-chain: %s", tx.Hash().Hex())
}

Testing with Localnet

Connect to a local Tempo node:
// Connect to localnet
client, err := tempo.Dial("http://localhost:8545")
if err != nil {
    log.Fatal(err)
}

// Use test accounts
testKey, _ := crypto.HexToECDSA("ac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80")
auth, _ := bind.NewKeyedTransactorWithChainID(testKey, big.NewInt(42431))

Contract Bindings

Generate Go bindings for custom contracts:
abigen --abi MyContract.abi --pkg contracts --type MyContract --out my_contract.go
For Tempo precompiles, use the built-in bindings:
import "github.com/tempoxyz/tempo/sdk/go/contracts"

// TIP-20 token
token, err := contracts.NewTIP20(address, client)

// TIP-403 policy registry
policy, err := contracts.NewTIP403(address, client)

// Fee AMM
feeAmm, err := contracts.NewFeeAMM(address, client)

Production Considerations

Connection Pooling

Reuse clients across requests:
var globalClient *ethclient.Client

func init() {
    var err error
    globalClient, err = ethclient.Dial(TempoTestnetRPC)
    if err != nil {
        panic(err)
    }
}

Transaction Management

Manage nonces for concurrent transactions:
import "sync"

type NonceManager struct {
    mu    sync.Mutex
    nonce uint64
}

func (nm *NonceManager) NextNonce() uint64 {
    nm.mu.Lock()
    defer nm.mu.Unlock()
    nonce := nm.nonce
    nm.nonce++
    return nonce
}

Monitoring

Track transaction status:
import "time"

func waitForConfirmations(ctx context.Context, client *ethclient.Client, txHash common.Hash, confirmations uint64) error {
    ticker := time.NewTicker(time.Second)
    defer ticker.Stop()

    for {
        select {
        case <-ctx.Done():
            return ctx.Err()
        case <-ticker.C:
            receipt, err := client.TransactionReceipt(ctx, txHash)
            if err != nil {
                continue
            }
            
            currentBlock, err := client.BlockNumber(ctx)
            if err != nil {
                continue
            }
            
            if currentBlock-receipt.BlockNumber.Uint64() >= confirmations {
                return nil
            }
        }
    }
}

Next Steps

Go Ethereum Docs

Learn go-ethereum basics

TIP-20 Reference

Complete TIP-20 specification

Tempo Transactions

Account abstraction features

Repository

View source code on GitHub