Skip to content

Building Transactions (Go)

This guide walks the full life of a transaction: describe it in TxPlan YAML, build it offline, sign it with the right keys, and submit it with your own HTTP client. The YAML shapes for every intent — staking, governance, pools, minting, Plutus — are cataloged in the TxPlan reference; this page shows how to drive them from Go.

Every transaction follows the same four steps:

lib, err := mesmo.New()
if err != nil { log.Fatal(err) }
defer lib.Close()
provider := mesmo.NewYaciProvider("") // or a BlockfrostProvider, or your own
// 1. Describe — TxPlan YAML (see the intent catalog)
yaml := fmt.Sprintf(`
version: 1.0
transaction:
- tx:
from: %s
intents:
- type: payment
address: %s
amounts:
- unit: lovelace
quantity: "5000000"
`, sender, receiver)
// 2. Build — offline; UTXO selection, fee, and change happen in the native lib
result, err := lib.QuickTx.BuildWith(yaml, provider, []string{sender}, 0)
// (or lib.QuickTx.Build(yaml, utxos, protocolParams, additionalSigners) with your own chain data)
// 3. Sign — with the key roles the transaction's certificates require
acct, _ := lib.Accounts.FromMnemonic(mnemonic, mesmo.Testnet, 0, 0)
defer acct.Close()
signed, err := acct.SignTx(result.TxCbor, mesmo.RolePayment)
// 4. Submit — any Blockfrost-compatible endpoint; the library never submits
txBytes, _ := hex.DecodeString(signed)
resp, err := http.Post(submitURL+"/tx/submit", "application/cbor", bytes.NewReader(txBytes))

SignTx witnesses with the payment key only. Certificates need their own witness — combine SigningRole flags with | (witnesses apply in canonical order):

Transaction containsRoles
Payments, metadata, minting, Plutus operationsmesmo.RolePayment
stake_registration / stake_deregistration / stake_delegation / stake_withdrawal / voting_delegationRolePayment|RoleStake
drep_registration / drep_update / drep_deregistration / votingRolePayment|RoleDRep
governance_proposalRolePayment
pool_registration / pool_update / pool_retirementRolePayment|RoleStake when the pool is keyed to the account’s stake key

A missing witness is rejected by the node with MissingVKeyWitnessesUTXOW. The same table gives the fee’s witness budget: pass additional_signers = len(keys) - 1 to the build (the input UTXOs already cover the payment key). For a native-script spend whose only inputs sit at the script address, pass the number of the script’s sig keys instead.

Worked example: register and delegate stake

Section titled “Worked example: register and delegate stake”

Two transactions — the registration must be on-chain before the delegation:

stakeYaml := fmt.Sprintf(`
version: 1.0
transaction:
- tx:
from: %s
intents:
- type: stake_registration
stake_address: %s
`, sender, account.StakeAddress)
reg, err := lib.QuickTx.BuildWith(stakeYaml, provider, []string{sender}, 1)
signedReg, err := acct.SignTx(reg.TxCbor, mesmo.RolePayment|mesmo.RoleStake)
// submit signedReg; wait for inclusion before the next step
delegYaml := fmt.Sprintf(`
version: 1.0
transaction:
- tx:
from: %s
intents:
- type: stake_delegation
stake_address: %s
pool_id: pool1...
`, sender, account.StakeAddress)
deleg, err := lib.QuickTx.BuildWith(delegYaml, provider, []string{sender}, 1)
signedDeleg, err := acct.SignTx(deleg.TxCbor, mesmo.RolePayment|mesmo.RoleStake)

Worked example: DRep registration, then vote

Section titled “Worked example: DRep registration, then vote”

The DRep credential comes from the governance API:

drep, err := lib.Crypto.DeriveKey(mnemonic, 0, 0, "drep")
drepYaml := fmt.Sprintf(`
version: 1.0
transaction:
- tx:
from: %s
intents:
- type: drep_registration
drep_credential_hex: %s
drep_credential_type: key_hash
anchor_url: https://example.com/meta.json
anchor_hash: %s
`, sender, drep.PublicKeyHash, anchorHash)
reg, err := lib.QuickTx.BuildWith(drepYaml, provider, []string{sender}, 1)
signedReg, err := acct.SignTx(reg.TxCbor, mesmo.RolePayment|mesmo.RoleDRep)

To vote on a governance action, the action id is the proposal transaction’s hash plus its index (a proposal you submit yourself returns its hash from Buildresult.TxHash). Sign the voting transaction with RolePayment\|RoleDRep.

Worked example: mint under a native script

Section titled “Worked example: mint under a native script”
mintYaml := fmt.Sprintf(`
version: 1.0
transaction:
- tx:
from: %s
intents:
- type: minting
assets:
- name: TestNFT
value: 1
receiver: %s
script_hex: "820180"
script_type: 0
`, sender, receiver)
mint, err := lib.QuickTx.BuildWith(mintYaml, provider, []string{sender}, 0)
signedMint, err := acct.SignTx(mint.TxCbor, mesmo.RolePayment)

An empty ScriptAll policy (820180) needs no extra signature; a sig-keyed policy needs the corresponding key’s witness.

By default execution units are computed offline (embedded Scalus evaluator) — a Plutus transaction is a normal build:

result, err := lib.QuickTx.BuildWith(plutusMintYaml, provider, []string{sender}, 0)

To cost against a real node instead, pass an evaluator — BuildWith then runs the two-pass flow (draft → remote evaluate → rebuild):

evaluator, _ := mesmo.NewBlockfrostEvaluator(projectID, "preprod")
result, err := lib.QuickTx.BuildWith(plutusMintYaml, provider, []string{sender}, 0, evaluator)

Or supply units yourself with the offline Build:

result, err := lib.QuickTx.Build(plutusMintYaml, utxos, params, 0,
[]map[string]interface{}{{"mem": 2000000, "steps": 500000000}})

For spending a script UTXO (script_collect_from), supply the locked UTXO (with its data_hash) plus a separate UTXO for fee/collateral in utxos — see the catalog entry and the end-to-end lock-then-spend flow in wrappers/go/mesmo/script_spend_test.go.

  • Mesmo error -10 (ErrTxBuild) — the plan didn’t build: malformed YAML, wrong intent field, or a Plutus costing problem. Compare against the catalog.
  • Mesmo error -8 (ErrInsufficientFunds) — the supplied UTXOs can’t cover outputs + fee.
  • Node rejection MissingVKeyWitnessesUTXOW — a certificate wasn’t witnessed; check the roles table above.