In this lesson, you will build, sign, and submit a simple ADA transaction on Cardano using Apollo. This is your first hands-on experience turning transaction intent into a real ledger event.
Apollo is a Go library that makes Cardano transaction building accessible. Rather than manually constructing CBOR-encoded transactions, Apollo provides a fluent builder pattern that handles UTxO selection, fee calculation, and change output generation automatically.
By the end of this lesson, you will have submitted a transaction to Cardano preprod and verified it on a block explorer.
Prerequisites
Before you begin, ensure you have:
- Go 1.21+ installed
- A Blockfrost API key (free tier works for this lesson)
- A Cardano preprod testnet wallet funded with test ADA - both mnemonics and address (created in previous lesson)
- A basic understanding of the Cardano UTxO model
- Completed SLT 100.4 – Apollo: Background and Purpose
Note on infrastructure: You do not need to run a local Cardano node for this lesson. Apollo can query the chain and submit transactions via hosted services like Blockfrost. Running a local node is an option explored later in the course.
What You Are Building
You will build a program that:
- Connects to the Cardano Preprod network through Blockfrost apis
- Loads a wallet from a mnemonic
- Queries UTxOs for that wallet
- Constructs a transaction that sends ADA
- Automatically balances inputs and change
- Signs the transaction
- Submits it to the network
This lesson focuses on single-signer, ADA-only transactions.
Minimal Go Setup To Run a Simple Apollo Transaction
Treat the setup and the example as one sequence. You will copy the example into tx.go, add your .env values, initialize the module, install dependencies, and run.
1) Create a project folder and files
mkdir apollo_test && cd apollo_test
touch tx.go .env
2) Add the example code to tx.go
Copy the full example below into tx.go.
3) Set required environment variables in .env
The example uses godotenv.Load(), so put values in .env:
BLOCKFROST_API_KEY="your_blockfrost_api_key"
WALLET_MNEMONIC="your mnemonic words here"
RECIPIENT_ADDRESS="addr_test1..."
4) Initialize the Go module
go mod init apollo_test
go mod init creates a go.mod file, which tells Go this folder is a module and records its module name.
5) Install dependencies
go mod tidy
go mod tidy reads imports from your code, downloads required packages, and updates go.mod and go.sum.
6) Run
go run ./tx.go
Example: Simple ADA Transfer (tx.go)
Below is a complete example. You do not need to understand every line yet—focus on the overall structure and flow.
package main
import (
"encoding/hex"
"fmt"
"log"
"os"
"github.com/Salvionied/apollo"
"github.com/Salvionied/apollo/constants"
"github.com/Salvionied/apollo/txBuilding/Backend/BlockFrostChainContext"
"github.com/fxamacker/cbor/v2"
"github.com/joho/godotenv" // Needed
)
func main() {
// ---- 1) Connect (ChainContext) ----
if err := godotenv.Load(); err != nil {
log.Printf("warning: could not load .env file: %v", err)
}
blockfrostAPIKey := requiredEnv("BLOCKFROST_API_KEY")
seed := requiredEnv("WALLET_MNEMONIC")
recipient := requiredEnv("RECIPIENT_ADDRESS")
bfc, err := BlockFrostChainContext.NewBlockfrostChainContext(
constants.BLOCKFROST_BASE_URL_PREPROD,
int(constants.PREPROD),
blockfrostAPIKey,
)
if err != nil {
log.Fatal(err)
}
// ---- 2) Load wallet ----
cc := apollo.NewEmptyBackend()
apollob := apollo.New(&cc)
apollob, err = apollob.SetWalletFromMnemonic(seed, constants.PREPROD)
if err != nil {
log.Fatal(err)
}
apollob, err = apollob.SetWalletAsChangeAddress()
if err != nil {
log.Fatal(err)
}
// fmt.Println(apollob.GetWallet().GetAddress()) // Print wallet address
// ---- 3) Fetch UTxOs ----
utxos, err := bfc.Utxos(*apollob.GetWallet().GetAddress())
if err != nil {
log.Fatal(err)
}
// fmt.Println(bfc.Utxos(*apollob.GetWallet().GetAddress()))
// ---- 4) Declare intent + 5) Complete (balance) ----
apollob, _, err = apollob.AddLoadedUTxOs(utxos...).PayToAddressBech32(recipient, 1_000_000).Complete()
if err != nil {
log.Fatal(err)
}
// ---- 6) Sign ----
apollob = apollob.Sign()
tx := apollob.GetTx()
cborred, err := cbor.Marshal(tx)
if err != nil {
log.Fatal(err)
}
fmt.Println("Hex encoded CBOR: ", hex.EncodeToString(cborred))
// ---- 7) Submit ----
txID, err := bfc.SubmitTx(*tx)
if err != nil {
log.Fatal(err)
}
fmt.Println("Transaction ID: ", hex.EncodeToString(txID.Payload))
}
func requiredEnv(key string) string {
value := os.Getenv(key)
if value == "" {
log.Fatalf("missing required env var: %s", key)
}
return value
}
The transaction succeeded if you see something like this:
Transaction Lifecycle
Cardano transactions follow typical patterns. This pattern is:
- Connect (ChainContext)
- Load wallet
- Fetch UTxOs
- Declare intent
- Complete (balance)
- Sign
- Submit
Lifecycle Step
Where It Happens
Connect
NewBlockfrostChainContext(...)
Load wallet
SetWalletFromMnemonic(...)
Fetch UTxOs
bfc.Utxos(...)
Declare intent
AddLoadedUTxOs(), PayToAddressBech32()
Finalize
Complete()
Sign
apollob.Sign()
Submit
bfc.SubmitTx()
Verifying Your Transaction
After submission, copy the transaction hash and view it on a block explorer:
You should see:
- Inputs consumed
- Outputs created
- Fees paid
What This Example Does Not Cover
To keep the lesson focused, this example does not include:
- Multi-signature transactions
- Native assets or tokens
- Smart contract interactions
- Custom fee logic
These will be introduced in later lessons.
Common Errors
- Insufficient funds: Ensure your wallet has enough test ADA
- Wrong network: Your Blockfrost key must match the network
- Pending UTxOs: Wait for previous transactions to confirm
Summary
- You built a real Cardano transaction in Go
- Apollo handled UTxO selection and fee calculation
- You signed and submitted the transaction
- You verified the result on-chain
This pattern is the foundation for every transaction you will build in this course.