Repository: algorand/indexer
Pull request #509 (2021-05-21 14:46:13)

Block Generator Reporting
<!--
Thanks for submitting a pull request! We appreciate the time and effort you spent to get this far.

If you haven't already, please make sure that you've reviewed the CONTRIBUTING guide:
https://github.com/algorand/go-algorand/blob/master/CONTRIBUTING.md#code-guidelines
-->

## Summary

Add a basic reporting endpoint for verifying the expected transaction distribution, and seeing the performance impact from generating transactions.

Technically this should have a mutex but I don't want to add that to the generation path. That can be addressed later if it ever becomes a real problem.

## Test Plan

Run with scenario configs, and check `/report`



Patch:
diff --git a/cmd/block-generator/generator/generate.go b/cmd/block-generator/generator/generate.go
index 881963f..3bc66ab 100644
--- a/cmd/block-generator/generator/generate.go
+++ b/cmd/block-generator/generator/generate.go
@@ -6,6 +6,7 @@ import (
 	"io"
 	"math/rand"
 	"os"
+	"time"
 
 	"github.com/algorand/go-algorand-sdk/encoding/json"
 	"github.com/algorand/go-algorand-sdk/encoding/msgpack"
@@ -18,19 +19,24 @@ import (
 
 var errOutOfRange = fmt.Errorf("selection is out of weighted range")
 
+type txTypeID string
+
 const (
-	// Generator types
-	paymentTx = "pay"
-	assetTx   = "acfg"
-	//keyRegistrationTx = "keyreg"
-	//applicationCallTx = "appl"
-
-	// Asset Tx Types
-	assetCreate  = "create"
-	assetOptin   = "optin"
-	assetXfer    = "xfer"
-	assetClose   = "close"
-	assetDestroy = "destroy"
+	genesis txTypeID = "genesis"
+
+	// Payment Tx IDs
+	paymentTx           txTypeID = "pay"
+	paymentAcctCreateTx txTypeID = "pay_create"
+	assetTx             txTypeID = "asset"
+	//keyRegistrationTx txTypeID = "keyreg"
+	//applicationCallTx txTypeID = "appl"
+
+	// Asset Tx IDs
+	assetCreate  txTypeID = "asset_create"
+	assetOptin   txTypeID = "asset_optin"
+	assetXfer    txTypeID = "asset_xfer"
+	assetClose   txTypeID = "asset_close"
+	assetDestroy txTypeID = "asset_destroy"
 
 	assetTotal = uint64(100000000000000000)
 )
@@ -54,7 +60,7 @@ type GenerationConfig struct {
 
 	// Payment configuration
 	PaymentNewAccountFraction float32 `mapstructure:"pay_acct_create_fraction"`
-	PaymentTransferFraction   float32 `mapstructure:"pay_xfer_fraction"`
+	PaymentFraction           float32 `mapstructure:"pay_xfer_fraction"`
 
 	// Asset configuration
 	AssetCreateFraction  float32 `mapstructure:"asset_create_fraction"`
@@ -78,7 +84,7 @@ func MakeGenerator(config GenerationConfig) (Generator, error) {
 		return nil, fmt.Errorf("transaction distribution ratios should equal 1")
 	}
 
-	if !sumIsCloseToOne(config.PaymentNewAccountFraction, config.PaymentTransferFraction) {
+	if !sumIsCloseToOne(config.PaymentNewAccountFraction, config.PaymentFraction) {
 		return nil, fmt.Errorf("payment configuration ratios should equal 1")
 	}
 
@@ -99,6 +105,7 @@ func MakeGenerator(config GenerationConfig) (Generator, error) {
 		rewardsResidue:            0,
 		rewardsRate:               0,
 		rewardsRecalculationRound: 0,
+		reportData:                make(map[txTypeID]txData),
 	}
 
 	gen.feeSink[31] = 1
@@ -115,6 +122,15 @@ func MakeGenerator(config GenerationConfig) (Generator, error) {
 		}
 	}
 
+	for _, val := range getPaymentTxOptions() {
+		switch val {
+		case paymentTx:
+			gen.payTxWeights = append(gen.payTxWeights, config.PaymentFraction)
+		case paymentAcctCreateTx:
+			gen.payTxWeights = append(gen.payTxWeights, config.PaymentNewAccountFraction)
+		}
+	}
+
 	for _, val := range getAssetTxOptions() {
 		switch val {
 		case assetCreate:
@@ -135,8 +151,9 @@ func MakeGenerator(config GenerationConfig) (Generator, error) {
 
 // Generator is the interface needed to generate blocks.
 type Generator interface {
-	WriteBlock(output io.Writer, round uint64)
+	WriteReport(output io.Writer)
 	WriteGenesis(output io.Writer)
+	WriteBlock(output io.Writer, round uint64)
 }
 
 type generator struct {
@@ -174,7 +191,11 @@ type generator struct {
 	assets []*assetData
 
 	transactionWeights []float32
+	payTxWeights       []float32
 	assetTxWeights     []float32
+
+	// Reporting information from transaction type to data
+	reportData map[txTypeID]txData
 }
 
 type assetData struct {
@@ -191,7 +212,33 @@ type assetHolding struct {
 	balance   uint64
 }
 
+type txData struct {
+	GenerationTimeMilli time.Duration `json:"generation_time_milli"`
+	GenerationCount     uint64        `json:"num_generated"`
+}
+
+func track(id txTypeID) (txTypeID, time.Time) {
+	return id, time.Now()
+}
+func (g *generator) recordData(id txTypeID, start time.Time) {
+	data := g.reportData[id]
+	data.GenerationCount++
+	data.GenerationTimeMilli += time.Since(start)
+	g.reportData[id] = data
+}
+
+func (g *generator) WriteReport(output io.Writer) {
+	data := json.Encode(g.reportData)
+	var err error
+	if err != nil {
+		fmt.Fprintf(output, "Problem indenting data: %v", err)
+	} else {
+		output.Write(data)
+	}
+}
+
 func (g *generator) WriteGenesis(output io.Writer) {
+	defer g.recordData(track(genesis))
 	var allocations []types.GenesisAllocation
 
 	for i := uint64(0); i < g.config.NumGenesisAccounts; i++ {
@@ -353,16 +400,28 @@ func (g *generator) getSuggestedParams(round uint64) sdk_types.SuggestedParams {
 	}
 }
 
+func getPaymentTxOptions() []interface{} {
+	return []interface{}{paymentTx, paymentAcctCreateTx}
+}
+
 // generatePaymentTxn creates a new payment transaction. The sender is always a genesis account, the receiver is random,
 // or a new account.
 func (g *generator) generatePaymentTxn(sp sdk_types.SuggestedParams, _ uint64 /*round*/, _ uint64 /*intra*/) (types.SignedTxnInBlock, error) {
+	selection, err := weightedSelection(g.payTxWeights, getPaymentTxOptions(), paymentTx)
+	if err != nil {
+		return types.SignedTxnInBlock{}, err
+	}
+
+	defer g.recordData(track(selection.(txTypeID)))
+
 	var receiveIndex uint64
-	if g.numPayments%uint64(100*g.config.PaymentNewAccountFraction) == 0 {
+	switch selection {
+	case paymentTx:
+		receiveIndex = rand.Uint64() % g.numAccounts
+	case paymentAcctCreateTx:
 		g.balances = append(g.balances, 0)
 		g.numAccounts++
 		receiveIndex = g.numAccounts - 1
-	} else {
-		receiveIndex = rand.Uint64() % g.numAccounts
 	}
 
 	// Always send from a genesis account.
@@ -371,14 +430,15 @@ func (g *generator) generatePaymentTxn(sp sdk_types.SuggestedParams, _ uint64 /*
 	sender := indexToAccount(sendIndex)
 	receiver := indexToAccount(receiveIndex)
 
-	amount := uint64(1000)
+	amount := uint64(1000000)
 	fee := uint64(1000)
-	if g.balances[sendIndex] < (amount + fee) {
+	total := amount + fee
+	if g.balances[sendIndex] < total {
 		fmt.Printf(fmt.Sprintf("\n\nthe sender account does not have enough algos for the transfer. idx %d, payment number %d\n\n", sendIndex, g.numPayments))
 		os.Exit(1)
 	}
 
-	g.balances[sendIndex] -= amount + fee
+	g.balances[sendIndex] -= total
 	g.balances[receiveIndex] += amount
 
 	g.numPayments++
@@ -394,22 +454,23 @@ func getAssetTxOptions() []interface{} {
 	return []interface{}{assetCreate, assetDestroy, assetOptin, assetXfer, assetClose}
 }
 
-func (g *generator) generateAssetTxnInternal(txType interface{}, sp sdk_types.SuggestedParams, intra uint64) (txn types.Transaction) {
+func (g *generator) generateAssetTxnInternal(txType txTypeID, sp sdk_types.SuggestedParams, intra uint64) (actual txTypeID, txn types.Transaction) {
 	return g.generateAssetTxnInternalHint(txType, sp, intra, 0, nil)
 }
 
-func (g *generator) generateAssetTxnInternalHint(txType interface{}, sp sdk_types.SuggestedParams, intra uint64, hintIndex uint64, hint *assetData) (txn types.Transaction) {
+func (g *generator) generateAssetTxnInternalHint(txType txTypeID, sp sdk_types.SuggestedParams, intra uint64, hintIndex uint64, hint *assetData) (actual txTypeID, txn types.Transaction) {
+	actual = txType
 	// If there are no assets the next operation needs to be a create.
 	if len(g.assets) == 0 {
-		txType = assetCreate
+		actual = assetCreate
 	}
 
 	var err error
 	numAssets := uint64(len(g.assets))
 	var senderIndex uint64
 
-	if txType == assetCreate {
-		senderIndex = numAssets % g.numAccounts
+	if actual == assetCreate {
+		senderIndex = g.numPayments % g.config.NumGenesisAccounts
 		senderAcct := indexToAccount(senderIndex)
 		senderAcctStr := senderAcct.String()
 
@@ -436,7 +497,7 @@ func (g *generator) generateAssetTxnInternalHint(txType interface{}, sp sdk_type
 			asset = hint
 		}
 
-		switch txType {
+		switch actual {
 		case assetDestroy:
 			// delete asset
 
@@ -547,7 +608,7 @@ func (g *generator) generateAssetTxnInternalHint(txType interface{}, sp sdk_type
 
 	txn.Fee = 1000
 	if g.balances[senderIndex] < uint64(txn.Fee) {
-		fmt.Printf(fmt.Sprintf("\n\nthe sender account does not have enough algos for the transfer. idx %d, payment number %d\n\n", senderIndex, g.numPayments))
+		fmt.Printf(fmt.Sprintf("\n\nthe sender account does not have enough algos for the transfer. idx %d, asset transaction type %v, num %d\n\n", senderIndex, actual, g.reportData[actual].GenerationCount))
 		os.Exit(1)
 	}
 	g.balances[senderIndex] -= uint64(txn.Fee)
@@ -555,12 +616,14 @@ func (g *generator) generateAssetTxnInternalHint(txType interface{}, sp sdk_type
 }
 
 func (g *generator) generateAssetTxn(sp sdk_types.SuggestedParams, _ uint64 /*round*/, intra uint64) (types.SignedTxnInBlock, error) {
+	start := time.Now()
 	selection, err := weightedSelection(g.assetTxWeights, getAssetTxOptions(), assetXfer)
 	if err != nil {
 		return types.SignedTxnInBlock{}, err
 	}
 
-	txn := g.generateAssetTxnInternal(selection, sp, intra)
+	actual, txn := g.generateAssetTxnInternal(selection.(txTypeID), sp, intra)
+	defer g.recordData(actual, start)
 
 	if txn.Type == "" {
 		fmt.Println("Empty asset transaction.")
diff --git a/cmd/block-generator/main.go b/cmd/block-generator/main.go
index bcfceea..83939fc 100644
--- a/cmd/block-generator/main.go
+++ b/cmd/block-generator/main.go
@@ -67,6 +67,7 @@ func main() {
 	http.HandleFunc("/", help)
 	http.HandleFunc("/v2/blocks/", handleBlock)
 	http.HandleFunc("/genesis", handleGenesis)
+	http.HandleFunc("/report", handleReport)
 
 	portStr := fmt.Sprintf(":%d", port)
 	fmt.Printf("Starting server at %s\n", portStr)
@@ -77,6 +78,10 @@ func help(w http.ResponseWriter, r *http.Request) {
 	fmt.Fprintf(w, "Use /v2/blocks/:blocknum: to get a block.")
 }
 
+func handleReport(w http.ResponseWriter, r *http.Request) {
+	gen.WriteReport(w)
+}
+
 func handleGenesis(w http.ResponseWriter, r *http.Request) {
 	gen.WriteGenesis(w)
 }
diff --git a/cmd/block-generator/run_generator.sh b/cmd/block-generator/run_generator.sh
index 02c8d6e..eb4fa2a 100755
--- a/cmd/block-generator/run_generator.sh
+++ b/cmd/block-generator/run_generator.sh
@@ -2,6 +2,8 @@
 
 # Demonstrate how to run the generator and connect it to indexer.
 
+set -e
+
 POSTGRES_CONTAINER=generator-test-container
 POSTGRES_PORT=15432
 POSTGRES_DATABASE=generator_db
@@ -34,8 +36,9 @@ function shutdown() {
 trap shutdown EXIT
 
 echo "Building generator."
+pushd $(dirname "$0") > /dev/null
 go build
-pushd $(dirname "$0")/../.. > /dev/null
+cd ../.. > /dev/null
 echo "Building indexer."
 make
 popd
diff --git a/cmd/block-generator/scenarios/config.asset.close.yml b/cmd/block-generator/scenarios/config.asset.close.yml
index 5ff60c8..9a8b86b 100644
--- a/cmd/block-generator/scenarios/config.asset.close.yml
+++ b/cmd/block-generator/scenarios/config.asset.close.yml
@@ -1,6 +1,6 @@
 name: "Asset Close"
 genesis_accounts: 10000
-genesis_account_balance: 100000000
+genesis_account_balance: 1000000000000
 tx_per_block: 5000
 
 # transaction distribution
diff --git a/cmd/block-generator/scenarios/config.asset.destroy.yml b/cmd/block-generator/scenarios/config.asset.destroy.yml
index cea7297..953572e 100644
--- a/cmd/block-generator/scenarios/config.asset.destroy.yml
+++ b/cmd/block-generator/scenarios/config.asset.destroy.yml
@@ -1,6 +1,6 @@
 name: "Asset Destroy"
 genesis_accounts: 10000
-genesis_account_balance: 100000000
+genesis_account_balance: 1000000000000
 tx_per_block: 5000
 
 # transaction distribution
diff --git a/cmd/block-generator/scenarios/config.asset.xfer.yml b/cmd/block-generator/scenarios/config.asset.xfer.yml
index cc417a4..3e6643b 100644
--- a/cmd/block-generator/scenarios/config.asset.xfer.yml
+++ b/cmd/block-generator/scenarios/config.asset.xfer.yml
@@ -1,6 +1,6 @@
 name: "Asset Xfer"
 genesis_accounts: 10000
-genesis_account_balance: 100000000
+genesis_account_balance: 1000000000000
 tx_per_block: 5000
 
 # transaction distribution
diff --git a/cmd/block-generator/scenarios/config.mixed.jumbo.yml b/cmd/block-generator/scenarios/config.mixed.jumbo.yml
index 511d5dd..d02ee40 100644
--- a/cmd/block-generator/scenarios/config.mixed.jumbo.yml
+++ b/cmd/block-generator/scenarios/config.mixed.jumbo.yml
@@ -1,6 +1,6 @@
 name: "Mixed (jumbo)"
 genesis_accounts: 10000
-genesis_account_balance: 100000000
+genesis_account_balance: 1000000000000
 tx_per_block: 19999
 
 # transaction distribution
diff --git a/cmd/block-generator/scenarios/config.mixed.yml b/cmd/block-generator/scenarios/config.mixed.yml
index 2022862..d6e1eea 100644
--- a/cmd/block-generator/scenarios/config.mixed.yml
+++ b/cmd/block-generator/scenarios/config.mixed.yml
@@ -1,6 +1,6 @@
 name: "Mixed"
 genesis_accounts: 10000
-genesis_account_balance: 100000000
+genesis_account_balance: 1000000000000
 tx_per_block: 5000
 
 # transaction distribution
diff --git a/cmd/block-generator/scenarios/config.payment.full.yml b/cmd/block-generator/scenarios/config.payment.full.yml
index c9869ba..5d427c2 100644
--- a/cmd/block-generator/scenarios/config.payment.full.yml
+++ b/cmd/block-generator/scenarios/config.payment.full.yml
@@ -1,6 +1,6 @@
 name: "Pay (full)"
 genesis_accounts: 10
-genesis_account_balance: 1000000000
+genesis_account_balance: 1000000000000
 tx_per_block: 5000
 
 # transaction distribution
diff --git a/cmd/block-generator/scenarios/config.payment.jumbo.yml b/cmd/block-generator/scenarios/config.payment.jumbo.yml
index 60fe40d..8ec13ed 100644
--- a/cmd/block-generator/scenarios/config.payment.jumbo.yml
+++ b/cmd/block-generator/scenarios/config.payment.jumbo.yml
@@ -1,6 +1,6 @@
 name: "Pay (jumbo)"
 genesis_accounts: 10
-genesis_account_balance: 1000000000
+genesis_account_balance: 1000000000000
 tx_per_block: 19999
 
 # transaction distribution
diff --git a/cmd/block-generator/scenarios/config.payment.small.yml b/cmd/block-generator/scenarios/config.payment.small.yml
index fef73da..d941210 100644
--- a/cmd/block-generator/scenarios/config.payment.small.yml
+++ b/cmd/block-generator/scenarios/config.payment.small.yml
@@ -1,6 +1,6 @@
 name: "Pay (small)"
 genesis_accounts: 10
-genesis_account_balance: 1000000000
+genesis_account_balance: 1000000000000
 tx_per_block: 100
 
 # transaction distribution
diff --git a/idb/idb.go b/idb/idb.go
index bef8e74..19b854f 100644
--- a/idb/idb.go
+++ b/idb/idb.go
@@ -16,6 +16,7 @@ import (
 
 	models "github.com/algorand/indexer/api/generated/v2"
 	"github.com/algorand/indexer/types"
+	"github.com/algorand/indexer/util"
 )
 
 // TxnRow is metadata relating to one transaction in a transaction query.
@@ -422,7 +423,7 @@ func (ad AppDelta) String() string {
 	}
 	ds := ""
 	if ad.Delta != nil {
-		ds = string(JSONOneLine(ad.Delta))
+		ds = string(util.JSONOneLine(ad.Delta))
 	}
 	parts = append(parts, fmt.Sprintf("ai=%d oc=%v d=%s", ad.AddrIndex, ad.OnCompletion, ds))
 	if len(ad.ApprovalProgram) > 0 {
diff --git a/idb/postgres/internal/encoding/decode.go b/idb/postgres/internal/encoding/decode.go
new file mode 100644
index 0000000..b5830b5
--- /dev/null
+++ b/idb/postgres/internal/encoding/decode.go
@@ -0,0 +1,8 @@
+package encoding
+
+import (
+	"github.com/algorand/go-algorand-sdk/encoding/json"
+)
+
+// DecodeJSON decodes a json string.
+var DecodeJSON = json.Decode
diff --git a/idb/postgres/internal/encoding/encode.go b/idb/postgres/internal/encoding/encode.go
new file mode 100644
index 0000000..e0c4fd7
--- /dev/null
+++ b/idb/postgres/internal/encoding/encode.go
@@ -0,0 +1,75 @@
+package encoding
+
+import (
+	"encoding/base64"
+
+	"github.com/algorand/go-codec/codec"
+
+	"github.com/algorand/indexer/types"
+)
+
+var jsonCodecHandle *codec.JsonHandle
+
+// EncodeJSON converts an object into JSON
+func EncodeJSON(obj interface{}) []byte {
+	var buf []byte
+	enc := codec.NewEncoderBytes(&buf, jsonCodecHandle)
+	enc.MustEncode(obj)
+	return buf
+}
+
+// Base64 encodes a byte array to a base64 string.
+func Base64(data []byte) string {
+	return base64.StdEncoding.EncodeToString(data)
+}
+
+func convertStateDelta(delta types.StateDelta) types.StateDelta {
+	if delta == nil {
+		return nil
+	}
+
+	res := make(map[string]types.ValueDelta, len(delta))
+	for k, v := range delta {
+		res[Base64([]byte(k))] = v
+	}
+	return res
+}
+
+func convertLocalDeltas(deltas map[uint64]types.StateDelta) map[uint64]types.StateDelta {
+	if deltas == nil {
+		return nil
+	}
+
+	res := make(map[uint64]types.StateDelta, len(deltas))
+	for i, delta := range deltas {
+		res[i] = convertStateDelta(delta)
+	}
+	return res
+}
+
+func convertEvalDelta(evalDelta types.EvalDelta) types.EvalDelta {
+	evalDelta.GlobalDelta = convertStateDelta(evalDelta.GlobalDelta)
+	evalDelta.LocalDeltas = convertLocalDeltas(evalDelta.LocalDeltas)
+	return evalDelta
+}
+
+func convertSignedTxnWithAD(stxn types.SignedTxnWithAD) types.SignedTxnWithAD {
+	stxn.EvalDelta = convertEvalDelta(stxn.EvalDelta)
+	return stxn
+}
+
+// EncodeSignedTxnWithAD returns a json string where all byte arrays are base64 encoded.
+func EncodeSignedTxnWithAD(stxn types.SignedTxnWithAD) []byte {
+	return EncodeJSON(convertSignedTxnWithAD(stxn))
+}
+
+func init() {
+	jsonCodecHandle = new(codec.JsonHandle)
+	jsonCodecHandle.ErrorIfNoField = true
+	jsonCodecHandle.ErrorIfNoArrayExpand = true
+	jsonCodecHandle.Canonical = true
+	jsonCodecHandle.RecursiveEmptyCheck = true
+	jsonCodecHandle.HTMLCharsAsIs = true
+	jsonCodecHandle.Indent = 0
+	jsonCodecHandle.MapKeyAsString = true
+}
diff --git a/idb/postgres/postgres.go b/idb/postgres/postgres.go
index 428e49d..1315518 100644
--- a/idb/postgres/postgres.go
+++ b/idb/postgres/postgres.go
@@ -10,7 +10,6 @@ import (
 	"bytes"
 	"context"
 	"database/sql"
-	"encoding/base64"
 	"errors"
 	"fmt"
 	"math"
@@ -22,7 +21,6 @@ import (
 	"unicode/utf8"
 
 	"github.com/algorand/go-algorand-sdk/crypto"
-	"github.com/algorand/go-algorand-sdk/encoding/json"
 	"github.com/algorand/go-algorand-sdk/encoding/msgpack"
 	sdk_types "github.com/algorand/go-algorand-sdk/types"
 
@@ -33,6 +31,7 @@ import (
 	models "github.com/algorand/indexer/api/generated/v2"
 	"github.com/algorand/indexer/idb"
 	"github.com/algorand/indexer/idb/migration"
+	"github.com/algorand/indexer/idb/postgres/internal/encoding"
 	"github.com/algorand/indexer/importer"
 	"github.com/algorand/indexer/types"
 )
@@ -173,34 +172,10 @@ func (db *IndexerDb) StartBlock() (err error) {
 	return nil
 }
 
-// For App apply data, convert "string" keys which are secretly []byte blobs to their base64 representation so that JSON systems that require strings to be utf8 don't panic.
-func stxnToJSON(txn types.SignedTxnWithAD) string {
-	jt := txn
-	if len(jt.EvalDelta.GlobalDelta) > 0 {
-		gd := make(map[string]types.ValueDelta, len(jt.EvalDelta.GlobalDelta))
-		for k, v := range jt.EvalDelta.GlobalDelta {
-			gd[b64([]byte(k))] = v
-		}
-		jt.EvalDelta.GlobalDelta = gd
-	}
-	if len(jt.EvalDelta.LocalDeltas) > 0 {
-		ldout := make(map[uint64]types.StateDelta, len(jt.EvalDelta.LocalDeltas))
-		for i, ld := range jt.EvalDelta.LocalDeltas {
-			nld := make(map[string]types.ValueDelta, len(ld))
-			for k, v := range ld {
-				nld[b64([]byte(k))] = v
-			}
-			ldout[i] = nld
-		}
-		jt.EvalDelta.LocalDeltas = ldout
-	}
-	return idb.JSONOneLine(jt)
-}
-
 // AddTransaction is part of idb.IndexerDB
 func (db *IndexerDb) AddTransaction(round uint64, intra int, txtypeenum int, assetid uint64, txn types.SignedTxnWithAD, participation [][]byte) error {
 	txnbytes := msgpack.Encode(txn)
-	jsonbytes := stxnToJSON(txn)
+	jsonbytes := encoding.EncodeSignedTxnWithAD(txn)
 	txid := crypto.TransactionIDString(txn.Txn)
 	tx := []interface{}{round, intra, txtypeenum, assetid, txid[:], txnbytes, string(jsonbytes)}
 	db.txrows = append(db.txrows, tx)
@@ -261,7 +236,7 @@ func (db *IndexerDb) commitBlock(tx *sql.Tx, round uint64, timestamp int64, rewa
 		if err != nil {
 			//return err
 			for _, er := range db.txprows[:i+1] {
-				db.log.Printf("%s %d %d", base64.StdEncoding.EncodeToString(er[0].([]byte)), er[1], er[2])
+				db.log.Printf("%s %d %d", encoding.Base64(er[0].([]byte)), er[1], er[2])
 			}
 			return fmt.Errorf("%v, around txp row %#v", err, txpr)
 		}
@@ -281,7 +256,7 @@ func (db *IndexerDb) commitBlock(tx *sql.Tx, round uint64, timestamp int64, rewa
 	if err != nil {
 		return fmt.Errorf("decode header %v", err)
 	}
-	headerjson := idb.JSONOneLine(blockHeader)
+	headerjson := encoding.EncodeJSON(blockHeader)
 	_, err = tx.Exec(`INSERT INTO block_header (round, realtime, rewardslevel, header) VALUES ($1, $2, $3, $4) ON CONFLICT DO NOTHING`, round, time.Unix(timestamp, 0).UTC(), rewardslevel, headerjson)
 	if err != nil {
 		return fmt.Errorf("put block_header %v    %#v", err, err)
@@ -348,14 +323,14 @@ func (db *IndexerDb) LoadGenesis(genesis types.Genesis) (err error) {
 		if len(alloc.State.AssetParams) > 0 || len(alloc.State.Assets) > 0 {
 			return fmt.Errorf("genesis account[%d] has unhandled asset", ai)
 		}
-		_, err = setAccount.Exec(addr[:], alloc.State.MicroAlgos, idb.JSONOneLine(alloc.State), 0)
+		_, err = setAccount.Exec(addr[:], alloc.State.MicroAlgos, encoding.EncodeJSON(alloc.State), 0)
 		total += uint64(alloc.State.MicroAlgos)
 		if err != nil {
 			return fmt.Errorf("error setting genesis account[%d], %v", ai, err)
 		}
 	}
 	var istate importer.ImportState
-	sjs := string(idb.JSONOneLine(istate))
+	sjs := string(encoding.EncodeJSON(istate))
 	_, err = tx.Exec(setMetastateUpsert, stateMetastateKey, sjs)
 	if err != nil {
 		return
@@ -398,7 +373,7 @@ func (db *IndexerDb) GetImportState() (state idb.ImportState, err error) {
 		return
 	}
 
-	err = json.Decode([]byte(importStateJSON), &state)
+	err = encoding.DecodeJSON([]byte(importStateJSON), &state)
 	if err != nil {
 		err = fmt.Errorf("unable to parse import state: %v", err)
 	}
@@ -407,7 +382,7 @@ func (db *IndexerDb) GetImportState() (state idb.ImportState, err error) {
 
 // SetImportState is part of idb.IndexerDB
 func (db *IndexerDb) SetImportState(state idb.ImportState) (err error) {
-	return db.setMetastate(stateMetastateKey, string(json.Encode(state)))
+	return db.setMetastate(stateMetastateKey, string(encoding.EncodeJSON(state)))
 }
 
 // If `tx` is null, make a standalone query.
@@ -547,7 +522,7 @@ func (db *IndexerDb) yieldTxnsThread(ctx context.Context, rows *sql.Rows, result
 			row.TxnBytes = txnbytess[i]
 			row.AssetID = uint64(creatableids[i])
 			if len(extrajsons[i]) > 0 {
-				err := json.Decode(extrajsons[i], &row.Extra)
+				err := encoding.DecodeJSON(extrajsons[i], &row.Extra)
 				if err != nil {
 					row.Error = fmt.Errorf("%d:%d decode txn extra, %v", row.Round, row.Intra, err)
 					results <- row
@@ -592,12 +567,8 @@ func (db *IndexerDb) YieldTxns(ctx context.Context, firstRound uint64) <-chan id
 // debug any asset that isn't working out right:
 var debugAsset uint64 = 0
 
-func b64(addr []byte) string {
-	return base64.StdEncoding.EncodeToString(addr)
-}
-
 func obs(x interface{}) string {
-	return idb.JSONOneLine(x)
+	return string(encoding.EncodeJSON(x))
 }
 
 // StateSchema like go-algorand data/basics/teal.go
@@ -654,7 +625,7 @@ func (tv TealValue) toModel() models.TealValue {
 	case TealUintType:
 		return models.TealValue{Uint: tv.Uint, Type: uint64(tv.Type)}
 	case TealBytesType:
-		return models.TealValue{Bytes: b64(tv.Bytes), Type: uint64(tv.Type)}
+		return models.TealValue{Bytes: encoding.Base64(tv.Bytes), Type: uint64(tv.Type)}
 	}
 	return models.TealValue{}
 }
@@ -679,7 +650,7 @@ func (tkv TealKeyValue) toModel() *models.TealKeyValueStore {
 	var out models.TealKeyValueStore = make([]models.TealKeyValue, len(tkv.They))
 	pos := 0
 	for _, ktv := range tkv.They {
-		out[pos].Key = b64(ktv.Key)
+		out[pos].Key = encoding.Base64(ktv.Key)
 		out[pos].Value = ktv.Tv.toModel()
 		pos++
 	}
@@ -719,14 +690,14 @@ func (tkv *TealKeyValue) delete(key []byte) {
 	}
 }
 
-// MarshalJSON wraps idb.JSONOneLine
+// MarshalJSON wraps encoding.EncodeJSON
 func (tkv TealKeyValue) MarshalJSON() ([]byte, error) {
-	return []byte(idb.JSONOneLine(tkv.They)), nil
+	return encoding.EncodeJSON(tkv.They), nil
 }
 
-// UnmarshalJSON wraps json.Decode
+// UnmarshalJSON wraps encoding.DecodeJSON
 func (tkv *TealKeyValue) UnmarshalJSON(data []byte) error {
-	return json.Decode(data, &tkv.They)
+	return encoding.DecodeJSON(data, &tkv.They)
 }
 
 // AppParams like go-algorand data/basics/userBalance.go AppParams{}
@@ -800,7 +771,7 @@ func (db *IndexerDb) getDirtyAppLocalState(addr []byte, appIndex int64, dirty []
 		err = fmt.Errorf("app local get, %v", err)
 		return
 	} else if len(localstatejson) > 0 {
-		err = json.Decode(localstatejson, &localstate.AppLocalState)
+		err = encoding.DecodeJSON(localstatejson, &localstate.AppLocalState)
 		if err != nil {
 			err = fmt.Errorf("app local get bad json, %v", err)
 		}
@@ -902,7 +873,7 @@ func (db *IndexerDb) commitRoundAccounting(tx *sql.Tx, updates idb.RoundUpdates,
 				}
 			}
 
-			jb := idb.JSONOneLine(set)
+			jb := encoding.EncodeJSON(set)
 			_, err = setad.Exec(jb, addr[:])
 			if err != nil {
 				return fmt.Errorf("update account data, %v", err)
@@ -976,7 +947,7 @@ ON CONFLICT (addr, assetid) DO UPDATE SET amount = account_asset.amount + EXCLUD
 			for addr, aulist := range subround {
 				for _, au := range aulist {
 					if au.AssetID == debugAsset {
-						db.log.Errorf("%d axfer %s %s", round, b64(addr[:]), obs(au))
+						db.log.Errorf("%d axfer %s %s", round, encoding.Base64(addr[:]), obs(au))
 					}
 
 					// Apply deltas
@@ -1037,9 +1008,9 @@ ON CONFLICT (addr, assetid) DO UPDATE SET amount = account_asset.amount + EXCLUD
 
 					// Asset Config
 					if au.Config != nil {
-						var outparams string
+						var outparams []byte
 						if au.Config.IsNew {
-							outparams = string(json.Encode(au.Config.Params))
+							outparams = encoding.EncodeJSON(au.Config.Params)
 						} else {
 							row := getacfg.QueryRow(au.AssetID)
 							var paramjson []byte
@@ -1048,12 +1019,12 @@ ON CONFLICT (addr, assetid) DO UPDATE SET amount = account_asset.amount + EXCLUD
 								return fmt.Errorf("get acfg %d, %v", au.AssetID, err)
 							}
 							var old sdk_types.AssetParams
-							err = json.Decode(paramjson, &old)
+							err = encoding.DecodeJSON(paramjson, &old)
 							if err != nil {
 								return fmt.Errorf("bad acgf json %d, %v", au.AssetID, err)
 							}
 							np := types.MergeAssetConfig(old, au.Config.Params)
-							outparams = string(json.Encode(np))
+							outparams = encoding.EncodeJSON(np)
 						}
 						_, err = setacfg.Exec(au.AssetID, au.Config.Creator[:], outparams, round)
 						if err != nil {
@@ -1064,7 +1035,7 @@ ON CONFLICT (addr, assetid) DO UPDATE SET amount = account_asset.amount + EXCLUD
 					// Asset Freeze
 					if au.Freeze != nil {
 						if au.AssetID == debugAsset {
-							db.log.Errorf("%d %s %s", round, b64(addr[:]), obs(au.Freeze))
+							db.log.Errorf("%d %s %s", round, encoding.Base64(addr[:]), obs(au.Freeze))
 						}
 						_, err = fr.Exec(addr[:], au.AssetID, au.Freeze.Frozen, round)
 						if err != nil {
@@ -1127,7 +1098,7 @@ ON CONFLICT (addr, assetid) DO UPDATE SET amount = account_asset.amount + EXCLUD
 				} else if err != nil {
 					return fmt.Errorf("app[%d] global get, %v", adelta.AppIndex, err)
 				} else {
-					err = json.Decode(paramsjson, &state)
+					err = encoding.DecodeJSON(paramsjson, &state)
 					if err != nil {
 						return fmt.Errorf("app[%d] global get json, %v", adelta.AppIndex, err)
 					}
@@ -1153,7 +1124,7 @@ ON CONFLICT (addr, assetid) DO UPDATE SET amount = account_asset.amount + EXCLUD
 					return fmt.Errorf("app delta apply err r=%d i=%d app=%d, %v", adelta.Round, adelta.Intra, adelta.AppIndex, err)
 				}
 			}
-			reverseDeltas = append(reverseDeltas, []interface{}{idb.JSONOneLine(reverseDelta), adelta.Round, adelta.Intra})
+			reverseDeltas = append(reverseDeltas, []interface{}{encoding.EncodeJSON(reverseDelta), adelta.Round, adelta.Intra})
 			if adelta.OnCompletion == sdk_types.DeleteApplicationOC {
 				// clear content but leave row recording that it existed
 				state = AppParams{}
@@ -1193,7 +1164,7 @@ ON CONFLICT (addr, assetid) DO UPDATE SET amount = account_asset.amount + EXCLUD
 				Valid: destroy[appid],
 			}
 			creator := appCreators[appid]
-			paramjson := idb.JSONOneLine(params)
+			paramjson := encoding.EncodeJSON(params)
 			_, err = putglobal.Exec(appid, creator, paramjson, round, closedAt, destroy[appid])
 			if err != nil {
 				return fmt.Errorf("app global put pj=%v, %v", string(paramjson), err)
@@ -1235,7 +1206,7 @@ ON CONFLICT (addr, assetid) DO UPDATE SET amount = account_asset.amount + EXCLUD
 					return fmt.Errorf("app get (l), %v", err)
 				}
 				var app AppParams
-				err = json.Decode(paramsjson, &app)
+				err = encoding.DecodeJSON(paramsjson, &app)
 				if err != nil {
 					return fmt.Errorf("app[%d] get json (l), %v", ald.AppIndex, err)
 				}
@@ -1251,7 +1222,7 @@ ON CONFLICT (addr, assetid) DO UPDATE SET amount = account_asset.amount + EXCLUD
 				}
 			}
 			dirty = setDirtyAppLocalState(dirty, localstate)
-			reverseDeltas = append(reverseDeltas, []interface{}{idb.JSONOneLine(reverseDelta), ald.Round, ald.Intra})
+			reverseDeltas = append(reverseDeltas, []interface{}{encoding.EncodeJSON(reverseDelta), ald.Round, ald.Intra})
 		}
 
 		// update txns with reverse deltas
@@ -1278,7 +1249,7 @@ ON CONFLICT (addr, assetid) DO UPDATE SET amount = account_asset.amount + EXCLUD
 			}
 			defer putglobal.Close()
 			for _, ld := range dirty {
-				_, err = putglobal.Exec(ld.address, ld.appIndex, idb.JSONOneLine(ld.AppLocalState), round)
+				_, err = putglobal.Exec(ld.address, ld.appIndex, encoding.EncodeJSON(ld.AppLocalState), round)
 				if err != nil {
 					return fmt.Errorf("app local put, %v", err)
 				}
@@ -1311,7 +1282,7 @@ ON CONFLICT (addr, assetid) DO UPDATE SET amount = account_asset.amount + EXCLUD
 	} else if err != nil {
 		return
 	} else {
-		err = json.Decode([]byte(stateJSONStr), &istate)
+		err = encoding.DecodeJSON([]byte(stateJSONStr), &istate)
 		if err != nil {
 			return
 		}
@@ -1322,7 +1293,7 @@ ON CONFLICT (addr, assetid) DO UPDATE SET amount = account_asset.amount + EXCLUD
 		return errors.New(msg)
 	}
 	istate.AccountRound = int64(round)
-	sjs := idb.JSONOneLine(istate)
+	sjs := encoding.EncodeJSON(istate)
 	_, err = tx.Exec(setMetastateUpsert, stateMetastateKey, sjs)
 	if err != nil {
 		return
@@ -1354,7 +1325,7 @@ func (db *IndexerDb) GetBlock(ctx context.Context, round uint64, options idb.Get
 	if err != nil {
 		return
 	}
-	err = json.Decode(blockheaderjson, &blockHeader)
+	err = encoding.DecodeJSON(blockheaderjson, &blockHeader)
 	if err != nil {
 		return
 	}
@@ -1404,7 +1375,7 @@ func buildTransactionQuery(tf idb.TransactionFilter) (query string, whereArgs []
 		whereArgs = append(whereArgs, tf.Address)
 		partNumber++
 		if tf.AddressRole != 0 {
-			addrBase64 := base64.StdEncoding.EncodeToString(tf.Address)
+			addrBase64 := encoding.Base64(tf.Address)
 			roleparts := make([]string, 0, 8)
 			if tf.AddressRole&idb.AddressRoleSender != 0 {
 				roleparts = append(roleparts, fmt.Sprintf("t.txn -> 'txn' ->> 'snd' = $%d", partNumber))
@@ -1755,7 +1726,7 @@ func (db *IndexerDb) yieldTxnsThreadSimple(ctx context.Context, rows *sql.Rows,
 			row.RoundTime = roundtime
 			row.AssetID = asset
 			if len(extraJSON) > 0 {
-				err = json.Decode(extraJSON, &row.Extra)
+				err = encoding.DecodeJSON(extraJSON, &row.Extra)
 				if err != nil {
 					row.Error = fmt.Errorf("%d:%d decode txn extra, %v", row.Round, row.Intra, err)
 				}
@@ -1898,7 +1869,7 @@ func (db *IndexerDb) yieldAccountsThread(req *getAccountsRequest) {
 
 		if accountDataJSONStr != nil {
 			var ad types.AccountData
-			err = json.Decode(accountDataJSONStr, &ad)
+			err = encoding.DecodeJSON(accountDataJSONStr, &ad)
 			if err != nil {
 				err = fmt.Errorf("account decode err (%s) %v", accountDataJSONStr, err)
 				req.out <- idb.AccountRow{Error: err}
@@ -1952,42 +1923,42 @@ func (db *IndexerDb) yieldAccountsThread(req *getAccountsRequest) {
 
 		if len(holdingAssetids) > 0 && string(holdingAssetids) != nullarraystr {
 			var haids []uint64
-			err = json.Decode(holdingAssetids, &haids)
+			err = encoding.DecodeJSON(holdingAssetids, &haids)
 			if err != nil {
 				err = fmt.Errorf("parsing json holding asset ids err %v", err)
 				req.out <- idb.AccountRow{Error: err}
 				break
 			}
 			var hamounts []uint64
-			err = json.Decode(holdingAmount, &hamounts)
+			err = encoding.DecodeJSON(holdingAmount, &hamounts)
 			if err != nil {
 				err = fmt.Errorf("parsing json holding amounts err %v", err)
 				req.out <- idb.AccountRow{Error: err}
 				break
 			}
 			var hfrozen []bool
-			err = json.Decode(holdingFrozen, &hfrozen)
+			err = encoding.DecodeJSON(holdingFrozen, &hfrozen)
 			if err != nil {
 				err = fmt.Errorf("parsing json holding frozen err %v", err)
 				req.out <- idb.AccountRow{Error: err}
 				break
 			}
 			var holdingCreated []*uint64
-			err = json.Decode(holdingCreatedBytes, &holdingCreated)
+			err = encoding.DecodeJSON(holdingCreatedBytes, &holdingCreated)
 			if err != nil {
 				err = fmt.Errorf("parsing json holding created ids, %v", err)
 				req.out <- idb.AccountRow{Error: err}
 				break
 			}
 			var holdingClosed []*uint64
-			err = json.Decode(holdingClosedBytes, &holdingClosed)
+			err = encoding.DecodeJSON(holdingClosedBytes, &holdingClosed)
 			if err != nil {
 				err = fmt.Errorf("parsing json holding closed ids, %v", err)
 				req.out <- idb.AccountRow{Error: err}
 				break
 			}
 			var holdingDeleted []*bool
-			err = json.Decode(holdingDeletedBytes, &holdingDeleted)
+			err = encoding.DecodeJSON(holdingDeletedBytes, &holdingDeleted)
 			if err != nil {
 				err = fmt.Errorf("parsing json holding deleted ids, %v", err)
 				req.out <- idb.AccountRow{Error: err}
@@ -2029,35 +2000,35 @@ func (db *IndexerDb) yieldAccountsThread(req *getAccountsRequest) {
 		}
 		if len(assetParamsIds) > 0 && string(assetParamsIds) != nullarraystr {
 			var assetids []uint64
-			err = json.Decode(assetParamsIds, &assetids)
+			err = encoding.DecodeJSON(assetParamsIds, &assetids)
 			if err != nil {
 				err = fmt.Errorf("parsing json asset param ids, %v", err)
 				req.out <- idb.AccountRow{Error: err}
 				break
 			}
 			var assetParams []types.AssetParams
-			err = json.Decode(assetParamsStr, &assetParams)
+			err = encoding.DecodeJSON(assetParamsStr, &assetParams)
 			if err != nil {
 				err = fmt.Errorf("parsing json asset param string, %v", err)
 				req.out <- idb.AccountRow{Error: err}
 				break
 			}
 			var assetCreated []*uint64
-			err = json.Decode(assetParamsCreatedBytes, &assetCreated)
+			err = encoding.DecodeJSON(assetParamsCreatedBytes, &assetCreated)
 			if err != nil {
 				err = fmt.Errorf("parsing json asset created ids, %v", err)
 				req.out <- idb.AccountRow{Error: err}
 				break
 			}
 			var assetClosed []*uint64
-			err = json.Decode(assetParamsClosedBytes, &assetClosed)
+			err = encoding.DecodeJSON(assetParamsClosedBytes, &assetClosed)
 			if err != nil {
 				err = fmt.Errorf("parsing json asset closed ids, %v", err)
 				req.out <- idb.AccountRow{Error: err}
 				break
 			}
 			var assetDeleted []*bool
-			err = json.Decode(assetParamsDeletedBytes, &assetDeleted)
+			err = encoding.DecodeJSON(assetParamsDeletedBytes, &assetDeleted)
 			if err != nil {
 				err = fmt.Errorf("parsing json asset deleted ids, %v", err)
 				req.out <- idb.AccountRow{Error: err}
@@ -2115,28 +2086,28 @@ func (db *IndexerDb) yieldAccountsThread(req *getAccountsRequest) {
 		if len(appParamIndexes) > 0 {
 			// apps owned by this account
 			var appIds []uint64
-			err = json.Decode(appParamIndexes, &appIds)
+			err = encoding.DecodeJSON(appParamIndexes, &appIds)
 			if err != nil {
 				err = fmt.Errorf("parsing json appids, %v", err)
 				req.out <- idb.AccountRow{Error: err}
 				break
 			}
 			var appCreated []*uint64
-			err = json.Decode(appCreatedBytes, &appCreated)
+			err = encoding.DecodeJSON(appCreatedBytes, &appCreated)
 			if err != nil {
 				err = fmt.Errorf("parsing json app created ids, %v", err)
 				req.out <- idb.AccountRow{Error: err}
 				break
 			}
 			var appClosed []*uint64
-			err = json.Decode(appClosedBytes, &appClosed)
+			err = encoding.DecodeJSON(appClosedBytes, &appClosed)
 			if err != nil {
 				err = fmt.Errorf("parsing json app closed ids, %v", err)
 				req.out <- idb.AccountRow{Error: err}
 				break
 			}
 			var appDeleted []*bool
-			err = json.Decode(appDeletedBytes, &appDeleted)
+			err = encoding.DecodeJSON(appDeletedBytes, &appDeleted)
 			if err != nil {
 				err = fmt.Errorf("parsing json app deleted flags, %v", err)
 				req.out <- idb.AccountRow{Error: err}
@@ -2144,7 +2115,7 @@ func (db *IndexerDb) yieldAccountsThread(req *getAccountsRequest) {
 			}
 
 			var apps []AppParams
-			err = json.Decode(appParams, &apps)
+			err = encoding.DecodeJSON(appParams, &apps)
 			if err != nil {
 				err = fmt.Errorf("parsing json appparams, %v", err)
 				req.out <- idb.AccountRow{Error: err}
@@ -2191,35 +2162,35 @@ func (db *IndexerDb) yieldAccountsThread(req *getAccountsRequest) {
 
 		if len(localStateAppIds) > 0 {
 			var appIds []uint64
-			err = json.Decode(localStateAppIds, &appIds)
+			err = encoding.DecodeJSON(localStateAppIds, &appIds)
 			if err != nil {
 				err = fmt.Errorf("parsing json local appids, %v", err)
 				req.out <- idb.AccountRow{Error: err}
 				break
 			}
 			var appCreated []*uint64
-			err = json.Decode(localStateCreatedBytes, &appCreated)
+			err = encoding.DecodeJSON(localStateCreatedBytes, &appCreated)
 			if err != nil {
 				err = fmt.Errorf("parsing json ls created ids, %v", err)
 				req.out <- idb.AccountRow{Error: err}
 				break
 			}
 			var appClosed []*uint64
-			err = json.Decode(localStateClosedBytes, &appClosed)
+			err = encoding.DecodeJSON(localStateClosedBytes, &appClosed)
 			if err != nil {
 				err = fmt.Errorf("parsing json ls closed ids, %v", err)
 				req.out <- idb.AccountRow{Error: err}
 				break
 			}
 			var appDeleted []*bool
-			err = json.Decode(localStateDeletedBytes, &appDeleted)
+			err = encoding.DecodeJSON(localStateDeletedBytes, &appDeleted)
 			if err != nil {
 				err = fmt.Errorf("parsing json ls closed ids, %v", err)
 				req.out <- idb.AccountRow{Error: err}
 				break
 			}
 			var ls []AppLocalState
-			err = json.Decode(localStates, &ls)
+			err = encoding.DecodeJSON(localStates, &ls)
 			if err != nil {
 				err = fmt.Errorf("parsing json local states, %v", err)
 				req.out <- idb.AccountRow{Error: err}
@@ -2398,7 +2369,7 @@ func (db *IndexerDb) GetAccounts(ctx context.Context, opts idb.AccountQueryOptio
 		return out, round
 	}
 	var blockheader types.BlockHeader
-	err = json.Decode(headerjson, &blockheader)
+	err = encoding.DecodeJSON(headerjson, &blockheader)
 	if err != nil {
 		err = fmt.Errorf("account round header %d err %v", round, err)
 		out <- idb.AccountRow{Error: err}
@@ -2656,7 +2627,7 @@ func (db *IndexerDb) yieldAssetsThread(ctx context.Context, filter idb.AssetsQue
 			break
 		}
 		var params types.AssetParams
-		err = json.Decode(paramsJSONStr, &params)
+		err = encoding.DecodeJSON(paramsJSONStr, &params)
 		if err != nil {
 			out <- idb.AssetRow{Error: err}
 			break
@@ -2873,7 +2844,7 @@ func (db *IndexerDb) yieldApplicationsThread(ctx context.Context, rows *sql.Rows
 		rec.Application.DeletedAtRound = closed
 		rec.Application.Deleted = deleted
 		var ap AppParams
-		err = json.Decode(paramsjson, &ap)
+		err = encoding.DecodeJSON(paramsjson, &ap)
 		if err != nil {
 			rec.Error = fmt.Errorf("app=%d json err, %v", index, err)
 			out <- rec
@@ -2970,8 +2941,8 @@ func (db *IndexerDb) GetSpecialAccounts() (accounts idb.SpecialAccounts, err err
 			RewardsPool: blockHeader.RewardsPool,
 		}
 
-		cache := idb.JSONOneLine(accounts)
-		err = db.setMetastate(specialAccountsMetastateKey, cache)
+		cache := encoding.EncodeJSON(accounts)
+		err = db.setMetastate(specialAccountsMetastateKey, string(cache))
 		if err != nil {
 			return idb.SpecialAccounts{}, fmt.Errorf("problem saving metastate: %v", err)
 		}
@@ -2979,7 +2950,7 @@ func (db *IndexerDb) GetSpecialAccounts() (accounts idb.SpecialAccounts, err err
 		return
 	}
 
-	err = json.Decode([]byte(cache), &accounts)
+	err = encoding.DecodeJSON([]byte(cache), &accounts)
 	if err != nil {
 		err = fmt.Errorf("problem decoding cache '%s': %v", cache, err)
 	}
diff --git a/idb/postgres/postgres_migrations.go b/idb/postgres/postgres_migrations.go
index 5d811a5..0b4519a 100644
--- a/idb/postgres/postgres_migrations.go
+++ b/idb/postgres/postgres_migrations.go
@@ -7,14 +7,12 @@ import (
 	"bytes"
 	"context"
 	"database/sql"
-	"encoding/base64"
 	"fmt"
 	"math"
 	"os"
 	"time"
 
 	"github.com/algorand/go-algorand-sdk/crypto"
-	"github.com/algorand/go-algorand-sdk/encoding/json"
 	"github.com/algorand/go-algorand-sdk/encoding/msgpack"
 	sdk_types "github.com/algorand/go-algorand-sdk/types"
 
@@ -22,6 +20,7 @@ import (
 	"github.com/algorand/indexer/api/generated/v2"
 	"github.com/algorand/indexer/idb"
 	"github.com/algorand/indexer/idb/migration"
+	"github.com/algorand/indexer/idb/postgres/internal/encoding"
 	"github.com/algorand/indexer/types"
 )
 
@@ -120,7 +119,7 @@ func upsertMigrationStateTx(tx *sql.Tx, state *MigrationState, incrementNextMigr
 	if incrementNextMigration {
 		state.NextMigration++
 	}
-	migrationStateJSON := idb.JSONOneLine(state)
+	migrationStateJSON := encoding.EncodeJSON(state)
 	_, err = tx.Exec(setMetastateUpsert, migrationMetastateKey, migrationStateJSON)
 
 	return err
@@ -131,7 +130,7 @@ func upsertMigrationState(db *IndexerDb, state *MigrationState, incrementNextMig
 	if incrementNextMigration {
 		state.NextMigration++
 	}
-	migrationStateJSON := idb.JSONOneLine(state)
+	migrationStateJSON := encoding.EncodeJSON(state)
 	_, err = db.db.Exec(setMetastateUpsert, migrationMetastateKey, migrationStateJSON)
 
 	return err
@@ -140,7 +139,7 @@ func upsertMigrationState(db *IndexerDb, state *MigrationState, incrementNextMig
 func (db *IndexerDb) runAvailableMigrations(migrationStateJSON string) (err error) {
 	var state MigrationState
 	if len(migrationStateJSON) > 0 {
-		err = json.Decode([]byte(migrationStateJSON), &state)
+		err = encoding.DecodeJSON([]byte(migrationStateJSON), &state)
 		if err != nil {
 			return fmt.Errorf("(%s) bad metastate migration json, %v", migrationStateJSON, err)
 		}
@@ -185,8 +184,8 @@ func (db *IndexerDb) markMigrationsAsDone() (err error) {
 	state := MigrationState{
 		NextMigration: len(migrations),
 	}
-	migrationStateJSON := idb.JSONOneLine(state)
-	return db.setMetastate(migrationMetastateKey, migrationStateJSON)
+	migrationStateJSON := encoding.EncodeJSON(state)
+	return db.setMetastate(migrationMetastateKey, string(migrationStateJSON))
 }
 
 func (db *IndexerDb) getMigrationState() (*MigrationState, error) {
@@ -198,7 +197,7 @@ func (db *IndexerDb) getMigrationState() (*MigrationState, error) {
 		return nil, fmt.Errorf("%s, get m state err", txidMigrationErrMsg)
 	}
 	var txstate MigrationState
-	err = json.Decode([]byte(migrationStateJSON), &txstate)
+	err = encoding.DecodeJSON([]byte(migrationStateJSON), &txstate)
 	if err != nil {
 		return nil, fmt.Errorf("%s, migration json err", txidMigrationErrMsg)
 	}
@@ -332,7 +331,7 @@ func m3acfgFixAsyncInner(db *IndexerDb, state *MigrationState, assetIds []int64)
 		if now.Sub(lastlog) > (5 * time.Second) {
 			db.log.Printf("acfg fix next=%d", aid)
 			state.NextAssetID = aid
-			migrationStateJSON := idb.JSONOneLine(state)
+			migrationStateJSON := encoding.EncodeJSON(state)
 			_, err = tx.Exec(setMetastateUpsert, migrationMetastateKey, migrationStateJSON)
 			if err != nil {
 				db.log.WithError(err).Errorf("acfg fix migration %d meta err", state.NextMigration)
@@ -372,7 +371,7 @@ func m3acfgFixAsyncInner(db *IndexerDb, state *MigrationState, assetIds []int64)
 				params = types.MergeAssetConfig(params, stxn.Txn.AssetParams)
 			}
 		}
-		outparams := idb.JSONOneLine(params)
+		outparams := encoding.EncodeJSON(params)
 		_, err = setacfg.Exec(aid, creator[:], outparams)
 		if err != nil {
 			db.log.WithError(err).Errorf("acfg fix asset update")
@@ -381,7 +380,7 @@ func m3acfgFixAsyncInner(db *IndexerDb, state *MigrationState, assetIds []int64)
 	}
 	state.NextAssetID = 0
 	state.NextMigration++
-	migrationStateJSON := idb.JSONOneLine(state)
+	migrationStateJSON := encoding.EncodeJSON(state)
 	_, err = tx.Exec(setMetastateUpsert, migrationMetastateKey, migrationStateJSON)
 	if err != nil {
 		db.log.WithError(err).Errorf("acfg fix migration %d meta err", state.NextMigration)
@@ -824,7 +823,7 @@ func m7RewardsAndDatesPart2UpdateAccounts(db *IndexerDb, accountData []addressAc
 		state.PointerRound = &round
 		state.PointerIntra = &intra
 	}
-	migrationStateJSON := idb.JSONOneLine(state)
+	migrationStateJSON := encoding.EncodeJSON(state)
 	_, err = db.db.Exec(setMetastateUpsert, migrationMetastateKey, migrationStateJSON)
 	if err != nil {
 		return fmt.Errorf("m7: failed to update migration checkpoint: %v", err)
@@ -1151,7 +1150,7 @@ func m7RewardsAndDatesPart2(db *IndexerDb, state *MigrationState) error {
 	state.NextRound = 0
 	state.PointerRound = nil
 	state.PointerIntra = nil
-	migrationStateJSON := idb.JSONOneLine(state)
+	migrationStateJSON := encoding.EncodeJSON(state)
 	_, err := db.db.Exec(setMetastateUpsert, migrationMetastateKey, migrationStateJSON)
 	if err != nil {
 		return fmt.Errorf("m7: failed to write final migration state: %v", err)
@@ -1180,7 +1179,7 @@ func sqlMigration(db *IndexerDb, state *MigrationState, sqlLines []string) error
 		}
 	}
 	state.NextMigration++
-	migrationStateJSON := idb.JSONOneLine(state)
+	migrationStateJSON := encoding.EncodeJSON(state)
 	_, err = tx.Exec(setMetastateUpsert, migrationMetastateKey, migrationStateJSON)
 	if err != nil {
 		db.log.WithError(err).Errorf("migration %d meta err", thisMigration)
@@ -1268,8 +1267,8 @@ func (mtxid *txidFiuxpMigrationContext) asyncTxidFixup() (err error) {
 	// all done, mark migration state
 	state.NextMigration++
 	state.NextRound = 0
-	migrationStateJSON := idb.JSONOneLine(state)
-	err = db.setMetastate(migrationMetastateKey, migrationStateJSON)
+	migrationStateJSON := encoding.EncodeJSON(state)
+	err = db.setMetastate(migrationMetastateKey, string(migrationStateJSON))
 	if err != nil {
 		db.log.WithError(err).Errorf("%s, error setting final migration state", txidMigrationErrMsg)
 		return
@@ -1389,7 +1388,7 @@ func (mtxid *txidFiuxpMigrationContext) putTxidFixupBatch(batch []idb.TxnRow) er
 		return err
 	}
 	txstate.NextRound = int64(maxRound + 1)
-	migrationStateJSON := idb.JSONOneLine(txstate)
+	migrationStateJSON := encoding.EncodeJSON(txstate)
 	_, err = tx.Exec(setMetastateUpsert, migrationMetastateKey, migrationStateJSON)
 	if err != nil {
 		db.log.WithError(err).Errorf("%s, set metastate err", txidMigrationErrMsg)
@@ -1437,7 +1436,7 @@ func readHeaders(db *IndexerDb, minRound, maxRound uint64) (map[uint64]types.Blo
 			return nil, err
 		}
 		var tblock types.Block
-		json.Decode(headerjson, &tblock)
+		encoding.DecodeJSON(headerjson, &tblock)
 		headers[uint64(round)] = tblock
 	}
 	if err := rows.Err(); err != nil {
@@ -1589,7 +1588,7 @@ func m9TxnJSONEncoding(db *IndexerDb, state *MigrationState) (err error) {
 	if err == sql.ErrNoRows {
 		// Indexer may be new after m7, marking it as done without running it, so we don't need to do anything here.
 		state.NextMigration++
-		migrationStateJSON := json.Encode(state)
+		migrationStateJSON := encoding.EncodeJSON(state)
 		_, err = db.db.Exec(setMetastateUpsert, migrationMetastateKey, migrationStateJSON)
 		if err != nil {
 			db.log.WithError(err).Errorf("%s, meta err", m9ErrPrefix)
@@ -1665,7 +1664,7 @@ func m9TxnJSONEncoding(db *IndexerDb, state *MigrationState) (err error) {
 	// all done, mark migration state
 	state.NextMigration++
 	state.NextRound = 0
-	migrationStateJSON := string(json.Encode(state))
+	migrationStateJSON := string(encoding.EncodeJSON(state))
 	err = db.setMetastate(migrationMetastateKey, migrationStateJSON)
 	if err != nil {
 		db.log.WithError(err).Errorf("%s, error setting final migration state", m9ErrPrefix)
@@ -1717,7 +1716,7 @@ func putTxnJSONBatch(db *IndexerDb, state *MigrationState, batch []jsonFixupTxnR
 		if stxn.HasGenesisHash || proto.RequireGenesisHash {
 			stxn.Txn.GenesisHash = block.GenesisHash
 		}
-		js := stxnToJSON(stxn.SignedTxnWithAD)
+		js := string(encoding.EncodeSignedTxnWithAD(stxn.SignedTxnWithAD))
 		if js == string(txr.JSON) {
 			outrows[pos].round = txr.Round
 			outrows[pos].intra = txr.Intra
@@ -1791,7 +1790,7 @@ func putTxnJSONBatch(db *IndexerDb, state *MigrationState, batch []jsonFixupTxnR
 		return err
 	}
 	txstate.NextRound = int64(maxRound + 1)
-	migrationStateJSON := json.Encode(txstate)
+	migrationStateJSON := encoding.EncodeJSON(txstate)
 	_, err = tx.Exec(setMetastateUpsert, migrationMetastateKey, migrationStateJSON)
 	if err != nil {
 		db.log.WithError(err).Errorf("%s, set metastate err", m9ErrPrefix)
@@ -1876,7 +1875,7 @@ func updateFrozenState(db *IndexerDb, assetID uint64, closedAt *uint64, creator,
 		minRound = *closedAt
 	}
 
-	holderb64 := base64.StdEncoding.EncodeToString(holder[:])
+	holderb64 := encoding.Base64(holder[:])
 	row := db.db.QueryRow(freezeTransactionsQuery, freeze[:], holderb64, assetID, minRound)
 	var found uint64
 	err := row.Scan(&found)
@@ -2197,7 +2196,7 @@ func ClearAccountDataMigration(db *IndexerDb, state *MigrationState) error {
 	}
 
 	state.NextMigration++
-	migrationStateJSON := idb.JSONOneLine(state)
+	migrationStateJSON := encoding.EncodeJSON(state)
 	_, err = db.db.Exec(setMetastateUpsert, migrationMetastateKey, migrationStateJSON)
 	if err != nil {
 		return fmt.Errorf("metastate upsert error: %v", err)
diff --git a/idb/transcode.go b/idb/transcode.go
deleted file mode 100644
index 90a0e7e..0000000
--- a/idb/transcode.go
+++ /dev/null
@@ -1,26 +0,0 @@
-package idb
-
-import (
-	"github.com/algorand/go-codec/codec"
-)
-
-// JSONOneLine converts an object into JSON
-func JSONOneLine(obj interface{}) string {
-	var b []byte
-	enc := codec.NewEncoderBytes(&b, oneLineJSONCodecHandle)
-	enc.MustEncode(obj)
-	return string(b)
-}
-
-var oneLineJSONCodecHandle *codec.JsonHandle
-
-func init() {
-	oneLineJSONCodecHandle = new(codec.JsonHandle)
-	oneLineJSONCodecHandle.ErrorIfNoField = true
-	oneLineJSONCodecHandle.ErrorIfNoArrayExpand = true
-	oneLineJSONCodecHandle.Canonical = true
-	oneLineJSONCodecHandle.RecursiveEmptyCheck = true
-	oneLineJSONCodecHandle.HTMLCharsAsIs = true
-	oneLineJSONCodecHandle.Indent = 0
-	oneLineJSONCodecHandle.MapKeyAsString = true
-}
diff --git a/util/util.go b/util/util.go
index 7420adf..984c728 100644
--- a/util/util.go
+++ b/util/util.go
@@ -4,6 +4,8 @@ import (
 	"fmt"
 	"os"
 	"strings"
+
+	"github.com/algorand/go-codec/codec"
 )
 
 // KeysStringInt returns all of the keys in the map joined by a comma.
@@ -33,3 +35,24 @@ func MaybeFail(err error, errfmt string, params ...interface{}) {
 	fmt.Fprintf(os.Stderr, "\nError: %v\n", err)
 	os.Exit(1)
 }
+
+var oneLineJSONCodecHandle *codec.JsonHandle
+
+// JSONOneLine converts an object into JSON
+func JSONOneLine(obj interface{}) string {
+	var b []byte
+	enc := codec.NewEncoderBytes(&b, oneLineJSONCodecHandle)
+	enc.MustEncode(obj)
+	return string(b)
+}
+
+func init() {
+	oneLineJSONCodecHandle = new(codec.JsonHandle)
+	oneLineJSONCodecHandle.ErrorIfNoField = true
+	oneLineJSONCodecHandle.ErrorIfNoArrayExpand = true
+	oneLineJSONCodecHandle.Canonical = true
+	oneLineJSONCodecHandle.RecursiveEmptyCheck = true
+	oneLineJSONCodecHandle.HTMLCharsAsIs = true
+	oneLineJSONCodecHandle.Indent = 0
+	oneLineJSONCodecHandle.MapKeyAsString = true
+}


Test patch:
diff --git a/cmd/block-generator/generator/generate_test.go b/cmd/block-generator/generator/generate_test.go
index d9830fb..cf37d26 100644
--- a/cmd/block-generator/generator/generate_test.go
+++ b/cmd/block-generator/generator/generate_test.go
@@ -25,7 +25,8 @@ func TestAssetXferNoAssetsOverride(t *testing.T) {
 	sp := g.getSuggestedParams(0)
 
 	// First asset transaction must create.
-	txn := g.generateAssetTxnInternal(assetXfer, sp, 0)
+	actual, txn := g.generateAssetTxnInternal(assetXfer, sp, 0)
+	require.Equal(t, assetCreate, actual)
 	require.Equal(t, types.AssetConfigTx, txn.Type)
 	require.Len(t, g.assets, 1)
 	require.Len(t, g.assets[0].holdings, 1)
@@ -38,7 +39,8 @@ func TestAssetXferOneHolderOverride(t *testing.T) {
 	g.generateAssetTxnInternal(assetCreate, sp, 0)
 
 	// Transfer converted to optin if there is only 1 holder.
-	txn := g.generateAssetTxnInternal(assetXfer, sp, 0)
+	actual, txn := g.generateAssetTxnInternal(assetXfer, sp, 0)
+	require.Equal(t, assetOptin, actual)
 	require.Equal(t, types.AssetTransferTx, txn.Type)
 	require.Len(t, g.assets, 1)
 	// A new holding is created, indicating the optin
@@ -52,7 +54,8 @@ func TestAssetCloseCreatorOverride(t *testing.T) {
 	g.generateAssetTxnInternal(assetCreate, sp, 0)
 
 	// Instead of closing the creator, optin a new account
-	txn := g.generateAssetTxnInternal(assetClose, sp, 0)
+	actual, txn := g.generateAssetTxnInternal(assetClose, sp, 0)
+	require.Equal(t, assetOptin, actual)
 	require.Equal(t, types.AssetTransferTx, txn.Type)
 	require.Len(t, g.assets, 1)
 	// A new holding is created, indicating the optin
@@ -67,8 +70,10 @@ func TestAssetOptinEveryAccountOverride(t *testing.T) {
 
 	// Opt all the accounts in, this also verifies that no account is opted in twice
 	var txn types.Transaction
+	var actual txTypeID
 	for i := 2; uint64(i) <= g.numAccounts; i++ {
-		txn = g.generateAssetTxnInternal(assetOptin, sp, 0)
+		actual, txn = g.generateAssetTxnInternal(assetOptin, sp, 0)
+		require.Equal(t, assetOptin, actual)
 		require.Equal(t, types.AssetTransferTx, txn.Type)
 		require.Len(t, g.assets, 1)
 		require.Len(t, g.assets[0].holdings, i)
@@ -79,7 +84,8 @@ func TestAssetOptinEveryAccountOverride(t *testing.T) {
 	require.Equal(t, g.numAccounts, uint64(len(g.assets[0].holdings)))
 
 	// The next optin closes instead
-	txn = g.generateAssetTxnInternal(assetOptin, sp, 0)
+	actual, txn = g.generateAssetTxnInternal(assetOptin, sp, 0)
+	require.Equal(t, assetClose, actual)
 	require.Equal(t, types.AssetTransferTx, txn.Type)
 	require.Len(t, g.assets, 1)
 	require.Len(t, g.assets[0].holdings, int(g.numAccounts-1))
@@ -95,7 +101,8 @@ func TestAssetDestroyWithHoldingsOverride(t *testing.T) {
 	require.Len(t, g.assets[0].holdings, 2)
 	require.Len(t, g.assets[0].holders, 2)
 
-	txn := g.generateAssetTxnInternal(assetDestroy, sp, 0)
+	actual, txn := g.generateAssetTxnInternal(assetDestroy, sp, 0)
+	require.Equal(t, assetClose, actual)
 	require.Equal(t, types.AssetTransferTx, txn.Type)
 	require.Len(t, g.assets, 1)
 	require.Len(t, g.assets[0].holdings, 1)
@@ -118,7 +125,8 @@ func TestAssetDestroy(t *testing.T) {
 	g.generateAssetTxnInternal(assetCreate, sp, 0)
 	require.Len(t, g.assets, 1)
 
-	txn := g.generateAssetTxnInternal(assetDestroy, sp, 0)
+	actual, txn := g.generateAssetTxnInternal(assetDestroy, sp, 0)
+	require.Equal(t, assetDestroy, actual)
 	require.Equal(t, types.AssetConfigTx, txn.Type)
 	require.Len(t, g.assets, 0)
 }
diff --git a/idb/postgres/internal/encoding/encoding_test.go b/idb/postgres/internal/encoding/encoding_test.go
new file mode 100644
index 0000000..60aa246
--- /dev/null
+++ b/idb/postgres/internal/encoding/encoding_test.go
@@ -0,0 +1,86 @@
+package encoding
+
+import (
+	"fmt"
+	"testing"
+
+	"github.com/algorand/go-algorand-sdk/encoding/msgpack"
+	"github.com/stretchr/testify/assert"
+	"github.com/stretchr/testify/require"
+
+	"github.com/algorand/indexer/types"
+)
+
+func TestEncodeSignedTxnWithAD(t *testing.T) {
+	type txnMsgpackJSON struct {
+		msgpack []byte
+		json    string
+	}
+
+	var testTxns = []txnMsgpackJSON{
+		{
+			[]uint8{0x83, 0xa2, 0x64, 0x74, 0x81, 0xa2, 0x67, 0x64, 0x81, 0xa9, 0xfe, 0xfe, 0xff, 0xef, 0x0, 0x0, 0x11, 0x22, 0x33, 0x82, 0xa2, 0x61, 0x74, 0x1, 0xa2, 0x62, 0x73, 0xc4, 0x3, 0x78, 0x78, 0x78, 0xa3, 0x73, 0x69, 0x67, 0xc4, 0x40, 0x51, 0xca, 0x9f, 0x32, 0xca, 0x9d, 0x66, 0x4b, 0xde, 0xa0, 0x98, 0xd9, 0x1b, 0xd, 0xe, 0x4d, 0x39, 0xca, 0x2, 0x4c, 0x4e, 0xc4, 0xba, 0x88, 0x1a, 0xb6, 0xa, 0x63, 0xff, 0xb0, 0x95, 0xc6, 0xb6, 0x7d, 0x0, 0xb4, 0xdc, 0xef, 0x41, 0xe6, 0x3b, 0xc3, 0x43, 0x3e, 0xb5, 0xa2, 0xa0, 0x27, 0xad, 0x9c, 0xc0, 0x57, 0x93, 0x5c, 0x4e, 0xcd, 0x18, 0xea, 0xb0, 0x6b, 0xe3, 0x97, 0x17, 0x3, 0xa3, 0x74, 0x78, 0x6e, 0x8b, 0xa4, 0x61, 0x70, 0x61, 0x61, 0x91, 0xc4, 0x3, 0x78, 0x78, 0x78, 0xa4, 0x61, 0x70, 0x61, 0x70, 0xc4, 0x17, 0x2, 0x20, 0x1, 0x1, 0x26, 0x1, 0x9, 0xfe, 0xfe, 0xff, 0xef, 0x0, 0x0, 0x11, 0x22, 0x33, 0x28, 0x36, 0x1a, 0x0, 0x67, 0x22, 0x43, 0xa4, 0x61, 0x70, 0x67, 0x73, 0x81, 0xa3, 0x6e, 0x62, 0x73, 0x1, 0xa4, 0x61, 0x70, 0x73, 0x75, 0xc4, 0x5, 0x2, 0x20, 0x1, 0x1, 0x22, 0xa3, 0x66, 0x65, 0x65, 0xcd, 0x3, 0xe8, 0xa2, 0x66, 0x76, 0x4, 0xa2, 0x67, 0x68, 0xc4, 0x20, 0x8a, 0xae, 0xf2, 0xee, 0x8f, 0x3, 0x93, 0xb9, 0xa5, 0x47, 0x41, 0x35, 0x3b, 0x97, 0x96, 0xf3, 0xd, 0xcc, 0x52, 0x10, 0x9d, 0x21, 0x15, 0x9a, 0x64, 0xe8, 0x47, 0x52, 0xb2, 0xcc, 0x90, 0x6a, 0xa2, 0x6c, 0x76, 0xcd, 0x3, 0xec, 0xa4, 0x6e, 0x6f, 0x74, 0x65, 0xc4, 0x8, 0x13, 0xfa, 0x3c, 0x55, 0xe8, 0x7b, 0x23, 0xea, 0xa3, 0x73, 0x6e, 0x64, 0xc4, 0x20, 0x4a, 0x82, 0x63, 0xeb, 0xc0, 0xd2, 0xee, 0xed, 0xac, 0x73, 0xdb, 0xb9, 0xd0, 0x27, 0xa1, 0xb2, 0x32, 0x99, 0x7a, 0xed, 0xc5, 0xde, 0xa2, 0x25, 0x7f, 0x7f, 0x2c, 0x8b, 0xcd, 0x42, 0x5f, 0x1a, 0xa4, 0x74, 0x79, 0x70, 0x65, 0xa4, 0x61, 0x70, 0x70, 0x6c},
+			"{\"dt\":{\"gd\":{\"/v7/7wAAESIz\":{\"at\":1,\"bs\":\"eHh4\"}}},\"sig\":\"UcqfMsqdZkveoJjZGw0OTTnKAkxOxLqIGrYKY/+wlca2fQC03O9B5jvDQz61oqAnrZzAV5NcTs0Y6rBr45cXAw==\",\"txn\":{\"apaa\":[\"eHh4\"],\"apap\":\"AiABASYBCf7+/+8AABEiMyg2GgBnIkM=\",\"apgs\":{\"nbs\":1},\"apsu\":\"AiABASI=\",\"fee\":1000,\"fv\":4,\"gh\":\"iq7y7o8Dk7mlR0E1O5eW8w3MUhCdIRWaZOhHUrLMkGo=\",\"lv\":1004,\"note\":\"E/o8Veh7I+o=\",\"snd\":\"SoJj68DS7u2sc9u50CehsjKZeu3F3qIlf38si81CXxo=\",\"type\":\"appl\"}}",
+		},
+		{
+			[]byte{0x83, 0xa2, 0x64, 0x74, 0x82, 0xa2, 0x67, 0x64, 0x82, 0xa3, 0x67, 0x6b, 0x62, 0x82, 0xa2, 0x61, 0x74, 0x1, 0xa2, 0x62, 0x73, 0xc4, 0x4, 0x74, 0x65, 0x73, 0x74, 0xa3, 0x67, 0x6b, 0x69, 0x82, 0xa2, 0x61, 0x74, 0x2, 0xa2, 0x75, 0x69, 0x64, 0xa2, 0x6c, 0x64, 0x81, 0x0, 0x82, 0xa3, 0x6c, 0x6b, 0x62, 0x82, 0xa2, 0x61, 0x74, 0x1, 0xa2, 0x62, 0x73, 0xc4, 0xb, 0x61, 0x6e, 0x6f, 0x74, 0x68, 0x65, 0x72, 0x74, 0x65, 0x73, 0x74, 0xa3, 0x6c, 0x6b, 0x69, 0x82, 0xa2, 0x61, 0x74, 0x2, 0xa2, 0x75, 0x69, 0xcc, 0xc8, 0xa3, 0x73, 0x69, 0x67, 0xc4, 0x40, 0xc9, 0x25, 0xb2, 0xa, 0x42, 0xda, 0x15, 0xbe, 0x74, 0x16, 0x1d, 0x45, 0xc9, 0x3b, 0xf, 0xa4, 0xcc, 0xdd, 0x86, 0xbd, 0xa, 0x53, 0x1e, 0x43, 0xb3, 0x7e, 0xf9, 0xcc, 0xaf, 0x44, 0x38, 0xce, 0x35, 0xa5, 0xaa, 0xb, 0x96, 0x28, 0x79, 0x6, 0xf8, 0xe1, 0xfb, 0x96, 0xe3, 0x79, 0x9b, 0x27, 0xfa, 0xa4, 0x51, 0x10, 0xc7, 0xb1, 0x84, 0x79, 0x46, 0xf8, 0xd8, 0x6a, 0x6c, 0x96, 0x93, 0x6, 0xa3, 0x74, 0x78, 0x6e, 0x8a, 0xa4, 0x61, 0x70, 0x61, 0x61, 0x91, 0xc4, 0x5, 0x66, 0x69, 0x72, 0x73, 0x74, 0xa4, 0x61, 0x70, 0x61, 0x6e, 0x1, 0xa4, 0x61, 0x70, 0x69, 0x64, 0x23, 0xa3, 0x66, 0x65, 0x65, 0xcd, 0x3, 0xe8, 0xa2, 0x66, 0x76, 0x7, 0xa2, 0x67, 0x68, 0xc4, 0x20, 0x8a, 0xae, 0xf2, 0xee, 0x8f, 0x3, 0x93, 0xb9, 0xa5, 0x47, 0x41, 0x35, 0x3b, 0x97, 0x96, 0xf3, 0xd, 0xcc, 0x52, 0x10, 0x9d, 0x21, 0x15, 0x9a, 0x64, 0xe8, 0x47, 0x52, 0xb2, 0xcc, 0x90, 0x6a, 0xa2, 0x6c, 0x76, 0xcd, 0x3, 0xef, 0xa4, 0x6e, 0x6f, 0x74, 0x65, 0xc4, 0x8, 0xc9, 0x83, 0x5, 0x5f, 0x20, 0x45, 0x8f, 0x98, 0xa3, 0x73, 0x6e, 0x64, 0xc4, 0x20, 0x32, 0xf8, 0xa1, 0x14, 0x66, 0x60, 0x7, 0xb7, 0xfe, 0x8, 0xd2, 0x48, 0x83, 0xdf, 0x28, 0x86, 0x16, 0x74, 0xa3, 0xb2, 0x5, 0x48, 0x1d, 0x4a, 0x45, 0x8e, 0x50, 0xb4, 0xba, 0x2f, 0x34, 0xde, 0xa4, 0x74, 0x79, 0x70, 0x65, 0xa4, 0x61, 0x70, 0x70, 0x6c},
+			"{\"dt\":{\"gd\":{\"/v7/7wAAESIz\":{\"at\":1,\"bs\":\"eHh4\"},\"Z2ti\":{\"at\":1,\"bs\":\"dGVzdA==\"},\"Z2tp\":{\"at\":2,\"ui\":100}},\"ld\":{\"0\":{\"bGti\":{\"at\":1,\"bs\":\"YW5vdGhlcnRlc3Q=\"},\"bGtp\":{\"at\":2,\"ui\":200}}}},\"sig\":\"ySWyCkLaFb50Fh1FyTsPpMzdhr0KUx5Ds375zK9EOM41paoLlih5Bvjh+5bjeZsn+qRREMexhHlG+NhqbJaTBg==\",\"txn\":{\"apaa\":[\"Zmlyc3Q=\"],\"apan\":1,\"apap\":\"AiABASYBCf7+/+8AABEiMyg2GgBnIkM=\",\"apgs\":{\"nbs\":1},\"apid\":35,\"apsu\":\"AiABASI=\",\"fee\":1000,\"fv\":7,\"gh\":\"iq7y7o8Dk7mlR0E1O5eW8w3MUhCdIRWaZOhHUrLMkGo=\",\"lv\":1007,\"note\":\"yYMFXyBFj5g=\",\"snd\":\"MvihFGZgB7f+CNJIg98ohhZ0o7IFSB1KRY5QtLovNN4=\",\"type\":\"appl\"}}",
+		},
+	}
+
+	var stxn types.SignedTxnWithAD
+	for i, mt := range testTxns {
+		t.Run(fmt.Sprintf("i=%d", i), func(t *testing.T) {
+			msgpack.Decode(mt.msgpack, &stxn)
+			js := EncodeSignedTxnWithAD(stxn)
+			require.Equal(t, mt.json, string(js))
+		})
+	}
+}
+
+func TestEncodeSignedTxnWithADSynthetic(t *testing.T) {
+	nonutf8b := []byte{254, 254, 255, 239, 0, 0, 17, 34, 51}
+	nonutf8 := string(nonutf8b)
+	var stxn types.SignedTxnWithAD
+	stxn.EvalDelta.GlobalDelta = make(map[string]types.ValueDelta)
+	stxn.EvalDelta.GlobalDelta[nonutf8] = types.ValueDelta{
+		Action: types.SetBytesAction,
+		Bytes:  nonutf8b,
+	}
+	stxn.EvalDelta.LocalDeltas = make(map[uint64]types.StateDelta, 1)
+	ld := make(map[string]types.ValueDelta)
+	ld[nonutf8] = types.ValueDelta{
+		Action: types.SetBytesAction,
+		Bytes:  nonutf8b,
+	}
+	stxn.EvalDelta.LocalDeltas[1] = ld
+	js := EncodeSignedTxnWithAD(stxn)
+	require.Equal(t, "{\"dt\":{\"gd\":{\"/v7/7wAAESIz\":{\"at\":1,\"bs\":\"/v7/7wAAESIz\"}},\"ld\":{\"1\":{\"/v7/7wAAESIz\":{\"at\":1,\"bs\":\"/v7/7wAAESIz\"}}}}}", string(js))
+}
+
+// Test that encoding to JSON and decoding results in the same object.
+func TestJSONEncoding(t *testing.T) {
+	type Y struct {
+		Num int
+	}
+	type T struct {
+		Num    uint64
+		Str    string
+		Bytes  []byte
+		Object Y
+	}
+
+	x := T{
+		Num:    1,
+		Str:    "abc",
+		Bytes:  []byte{4, 5, 6},
+		Object: Y{Num: 7},
+	}
+	buf := EncodeJSON(x)
+
+	var xx T
+	err := DecodeJSON(buf, &xx)
+	require.NoError(t, err)
+
+	assert.Equal(t, x, xx)
+}
diff --git a/idb/postgres/postgres_integration_test.go b/idb/postgres/postgres_integration_test.go
index 153fe23..43c49af 100644
--- a/idb/postgres/postgres_integration_test.go
+++ b/idb/postgres/postgres_integration_test.go
@@ -11,12 +11,12 @@ import (
 	"github.com/stretchr/testify/require"
 
 	"github.com/algorand/go-algorand-sdk/crypto"
-	"github.com/algorand/go-algorand-sdk/encoding/json"
 	"github.com/algorand/go-algorand-sdk/encoding/msgpack"
 	sdk_types "github.com/algorand/go-algorand-sdk/types"
 
 	"github.com/algorand/indexer/accounting"
 	"github.com/algorand/indexer/idb"
+	"github.com/algorand/indexer/idb/postgres/internal/encoding"
 	"github.com/algorand/indexer/importer"
 	"github.com/algorand/indexer/types"
 	"github.com/algorand/indexer/util/test"
@@ -485,7 +485,7 @@ func TestRekeyBasic(t *testing.T) {
 	assert.NoError(t, err, "querying account data")
 
 	var ad types.AccountData
-	err = json.Decode(accountDataStr, &ad)
+	err = encoding.DecodeJSON(accountDataStr, &ad)
 	assert.NoError(t, err, "failed to parse account data json")
 	assert.Equal(t, test.AccountB, ad.SpendingKey)
 }
@@ -534,7 +534,7 @@ func TestRekeyToItself(t *testing.T) {
 	assert.NoError(t, err, "querying account data")
 
 	var ad types.AccountData
-	err = json.Decode(accountDataStr, &ad)
+	err = encoding.DecodeJSON(accountDataStr, &ad)
 	assert.NoError(t, err, "failed to parse account data json")
 	assert.Equal(t, sdk_types.ZeroAddress, ad.SpendingKey)
 }
@@ -581,7 +581,7 @@ func TestRekeyThreeTimesInSameRound(t *testing.T) {
 	assert.NoError(t, err, "querying account data")
 
 	var ad types.AccountData
-	err = json.Decode(accountDataStr, &ad)
+	err = encoding.DecodeJSON(accountDataStr, &ad)
 	assert.NoError(t, err, "failed to parse account data json")
 	assert.Equal(t, test.AccountC, ad.SpendingKey)
 }
diff --git a/idb/postgres/postgres_migrations_integration_test.go b/idb/postgres/postgres_migrations_integration_test.go
index 10ec4b8..65fe3ee 100644
--- a/idb/postgres/postgres_migrations_integration_test.go
+++ b/idb/postgres/postgres_migrations_integration_test.go
@@ -6,10 +6,10 @@ import (
 
 	"github.com/stretchr/testify/assert"
 
-	"github.com/algorand/go-algorand-sdk/encoding/json"
 	sdk_types "github.com/algorand/go-algorand-sdk/types"
 
 	"github.com/algorand/indexer/idb"
+	"github.com/algorand/indexer/idb/postgres/internal/encoding"
 	"github.com/algorand/indexer/types"
 	"github.com/algorand/indexer/util/test"
 )
@@ -21,7 +21,7 @@ func nextMigrationNum(t *testing.T, db *IndexerDb) int {
 	assert.True(t, len(j) > 0)
 
 	var state MigrationState
-	err = json.Decode([]byte(j), &state)
+	err = encoding.DecodeJSON([]byte(j), &state)
 	assert.NoError(t, err)
 
 	return state.NextMigration
diff --git a/idb/postgres/postgres_test.go b/idb/postgres/postgres_test.go
index 9f582b2..e74cd63 100644
--- a/idb/postgres/postgres_test.go
+++ b/idb/postgres/postgres_test.go
@@ -5,10 +5,7 @@ import (
 	"testing"
 	"time"
 
-	"github.com/algorand/go-algorand-sdk/encoding/msgpack"
-
 	"github.com/algorand/indexer/idb"
-	"github.com/algorand/indexer/types"
 	"github.com/stretchr/testify/assert"
 	"github.com/stretchr/testify/require"
 )
@@ -116,50 +113,3 @@ func TestTealKeyValue(t *testing.T) {
 	_, ok = tkv.get(k2)
 	a.False(ok)
 }
-
-type txnMsgpackJSON struct {
-	msgpack []byte
-	json    string
-}
-
-var testTxns = []txnMsgpackJSON{
-	{
-		[]uint8{0x83, 0xa2, 0x64, 0x74, 0x81, 0xa2, 0x67, 0x64, 0x81, 0xa9, 0xfe, 0xfe, 0xff, 0xef, 0x0, 0x0, 0x11, 0x22, 0x33, 0x82, 0xa2, 0x61, 0x74, 0x1, 0xa2, 0x62, 0x73, 0xc4, 0x3, 0x78, 0x78, 0x78, 0xa3, 0x73, 0x69, 0x67, 0xc4, 0x40, 0x51, 0xca, 0x9f, 0x32, 0xca, 0x9d, 0x66, 0x4b, 0xde, 0xa0, 0x98, 0xd9, 0x1b, 0xd, 0xe, 0x4d, 0x39, 0xca, 0x2, 0x4c, 0x4e, 0xc4, 0xba, 0x88, 0x1a, 0xb6, 0xa, 0x63, 0xff, 0xb0, 0x95, 0xc6, 0xb6, 0x7d, 0x0, 0xb4, 0xdc, 0xef, 0x41, 0xe6, 0x3b, 0xc3, 0x43, 0x3e, 0xb5, 0xa2, 0xa0, 0x27, 0xad, 0x9c, 0xc0, 0x57, 0x93, 0x5c, 0x4e, 0xcd, 0x18, 0xea, 0xb0, 0x6b, 0xe3, 0x97, 0x17, 0x3, 0xa3, 0x74, 0x78, 0x6e, 0x8b, 0xa4, 0x61, 0x70, 0x61, 0x61, 0x91, 0xc4, 0x3, 0x78, 0x78, 0x78, 0xa4, 0x61, 0x70, 0x61, 0x70, 0xc4, 0x17, 0x2, 0x20, 0x1, 0x1, 0x26, 0x1, 0x9, 0xfe, 0xfe, 0xff, 0xef, 0x0, 0x0, 0x11, 0x22, 0x33, 0x28, 0x36, 0x1a, 0x0, 0x67, 0x22, 0x43, 0xa4, 0x61, 0x70, 0x67, 0x73, 0x81, 0xa3, 0x6e, 0x62, 0x73, 0x1, 0xa4, 0x61, 0x70, 0x73, 0x75, 0xc4, 0x5, 0x2, 0x20, 0x1, 0x1, 0x22, 0xa3, 0x66, 0x65, 0x65, 0xcd, 0x3, 0xe8, 0xa2, 0x66, 0x76, 0x4, 0xa2, 0x67, 0x68, 0xc4, 0x20, 0x8a, 0xae, 0xf2, 0xee, 0x8f, 0x3, 0x93, 0xb9, 0xa5, 0x47, 0x41, 0x35, 0x3b, 0x97, 0x96, 0xf3, 0xd, 0xcc, 0x52, 0x10, 0x9d, 0x21, 0x15, 0x9a, 0x64, 0xe8, 0x47, 0x52, 0xb2, 0xcc, 0x90, 0x6a, 0xa2, 0x6c, 0x76, 0xcd, 0x3, 0xec, 0xa4, 0x6e, 0x6f, 0x74, 0x65, 0xc4, 0x8, 0x13, 0xfa, 0x3c, 0x55, 0xe8, 0x7b, 0x23, 0xea, 0xa3, 0x73, 0x6e, 0x64, 0xc4, 0x20, 0x4a, 0x82, 0x63, 0xeb, 0xc0, 0xd2, 0xee, 0xed, 0xac, 0x73, 0xdb, 0xb9, 0xd0, 0x27, 0xa1, 0xb2, 0x32, 0x99, 0x7a, 0xed, 0xc5, 0xde, 0xa2, 0x25, 0x7f, 0x7f, 0x2c, 0x8b, 0xcd, 0x42, 0x5f, 0x1a, 0xa4, 0x74, 0x79, 0x70, 0x65, 0xa4, 0x61, 0x70, 0x70, 0x6c},
-		"{\"dt\":{\"gd\":{\"/v7/7wAAESIz\":{\"at\":1,\"bs\":\"eHh4\"}}},\"sig\":\"UcqfMsqdZkveoJjZGw0OTTnKAkxOxLqIGrYKY/+wlca2fQC03O9B5jvDQz61oqAnrZzAV5NcTs0Y6rBr45cXAw==\",\"txn\":{\"apaa\":[\"eHh4\"],\"apap\":\"AiABASYBCf7+/+8AABEiMyg2GgBnIkM=\",\"apgs\":{\"nbs\":1},\"apsu\":\"AiABASI=\",\"fee\":1000,\"fv\":4,\"gh\":\"iq7y7o8Dk7mlR0E1O5eW8w3MUhCdIRWaZOhHUrLMkGo=\",\"lv\":1004,\"note\":\"E/o8Veh7I+o=\",\"snd\":\"SoJj68DS7u2sc9u50CehsjKZeu3F3qIlf38si81CXxo=\",\"type\":\"appl\"}}",
-	},
-	{
-		[]byte{0x83, 0xa2, 0x64, 0x74, 0x82, 0xa2, 0x67, 0x64, 0x82, 0xa3, 0x67, 0x6b, 0x62, 0x82, 0xa2, 0x61, 0x74, 0x1, 0xa2, 0x62, 0x73, 0xc4, 0x4, 0x74, 0x65, 0x73, 0x74, 0xa3, 0x67, 0x6b, 0x69, 0x82, 0xa2, 0x61, 0x74, 0x2, 0xa2, 0x75, 0x69, 0x64, 0xa2, 0x6c, 0x64, 0x81, 0x0, 0x82, 0xa3, 0x6c, 0x6b, 0x62, 0x82, 0xa2, 0x61, 0x74, 0x1, 0xa2, 0x62, 0x73, 0xc4, 0xb, 0x61, 0x6e, 0x6f, 0x74, 0x68, 0x65, 0x72, 0x74, 0x65, 0x73, 0x74, 0xa3, 0x6c, 0x6b, 0x69, 0x82, 0xa2, 0x61, 0x74, 0x2, 0xa2, 0x75, 0x69, 0xcc, 0xc8, 0xa3, 0x73, 0x69, 0x67, 0xc4, 0x40, 0xc9, 0x25, 0xb2, 0xa, 0x42, 0xda, 0x15, 0xbe, 0x74, 0x16, 0x1d, 0x45, 0xc9, 0x3b, 0xf, 0xa4, 0xcc, 0xdd, 0x86, 0xbd, 0xa, 0x53, 0x1e, 0x43, 0xb3, 0x7e, 0xf9, 0xcc, 0xaf, 0x44, 0x38, 0xce, 0x35, 0xa5, 0xaa, 0xb, 0x96, 0x28, 0x79, 0x6, 0xf8, 0xe1, 0xfb, 0x96, 0xe3, 0x79, 0x9b, 0x27, 0xfa, 0xa4, 0x51, 0x10, 0xc7, 0xb1, 0x84, 0x79, 0x46, 0xf8, 0xd8, 0x6a, 0x6c, 0x96, 0x93, 0x6, 0xa3, 0x74, 0x78, 0x6e, 0x8a, 0xa4, 0x61, 0x70, 0x61, 0x61, 0x91, 0xc4, 0x5, 0x66, 0x69, 0x72, 0x73, 0x74, 0xa4, 0x61, 0x70, 0x61, 0x6e, 0x1, 0xa4, 0x61, 0x70, 0x69, 0x64, 0x23, 0xa3, 0x66, 0x65, 0x65, 0xcd, 0x3, 0xe8, 0xa2, 0x66, 0x76, 0x7, 0xa2, 0x67, 0x68, 0xc4, 0x20, 0x8a, 0xae, 0xf2, 0xee, 0x8f, 0x3, 0x93, 0xb9, 0xa5, 0x47, 0x41, 0x35, 0x3b, 0x97, 0x96, 0xf3, 0xd, 0xcc, 0x52, 0x10, 0x9d, 0x21, 0x15, 0x9a, 0x64, 0xe8, 0x47, 0x52, 0xb2, 0xcc, 0x90, 0x6a, 0xa2, 0x6c, 0x76, 0xcd, 0x3, 0xef, 0xa4, 0x6e, 0x6f, 0x74, 0x65, 0xc4, 0x8, 0xc9, 0x83, 0x5, 0x5f, 0x20, 0x45, 0x8f, 0x98, 0xa3, 0x73, 0x6e, 0x64, 0xc4, 0x20, 0x32, 0xf8, 0xa1, 0x14, 0x66, 0x60, 0x7, 0xb7, 0xfe, 0x8, 0xd2, 0x48, 0x83, 0xdf, 0x28, 0x86, 0x16, 0x74, 0xa3, 0xb2, 0x5, 0x48, 0x1d, 0x4a, 0x45, 0x8e, 0x50, 0xb4, 0xba, 0x2f, 0x34, 0xde, 0xa4, 0x74, 0x79, 0x70, 0x65, 0xa4, 0x61, 0x70, 0x70, 0x6c},
-		"{\"dt\":{\"gd\":{\"/v7/7wAAESIz\":{\"at\":1,\"bs\":\"eHh4\"},\"Z2ti\":{\"at\":1,\"bs\":\"dGVzdA==\"},\"Z2tp\":{\"at\":2,\"ui\":100}},\"ld\":{\"0\":{\"bGti\":{\"at\":1,\"bs\":\"YW5vdGhlcnRlc3Q=\"},\"bGtp\":{\"at\":2,\"ui\":200}}}},\"sig\":\"ySWyCkLaFb50Fh1FyTsPpMzdhr0KUx5Ds375zK9EOM41paoLlih5Bvjh+5bjeZsn+qRREMexhHlG+NhqbJaTBg==\",\"txn\":{\"apaa\":[\"Zmlyc3Q=\"],\"apan\":1,\"apap\":\"AiABASYBCf7+/+8AABEiMyg2GgBnIkM=\",\"apgs\":{\"nbs\":1},\"apid\":35,\"apsu\":\"AiABASI=\",\"fee\":1000,\"fv\":7,\"gh\":\"iq7y7o8Dk7mlR0E1O5eW8w3MUhCdIRWaZOhHUrLMkGo=\",\"lv\":1007,\"note\":\"yYMFXyBFj5g=\",\"snd\":\"MvihFGZgB7f+CNJIg98ohhZ0o7IFSB1KRY5QtLovNN4=\",\"type\":\"appl\"}}",
-	},
-}
-
-func TestStxnJSON(t *testing.T) {
-	var stxn types.SignedTxnWithAD
-	for i, mt := range testTxns {
-		t.Run(fmt.Sprintf("i=%d", i), func(t *testing.T) {
-			msgpack.Decode(mt.msgpack, &stxn)
-			js := stxnToJSON(stxn)
-			require.Equal(t, mt.json, string(js))
-		})
-	}
-}
-
-func TestStxnJSONSynthetic(t *testing.T) {
-	nonutf8b := []byte{254, 254, 255, 239, 0, 0, 17, 34, 51}
-	nonutf8 := string(nonutf8b)
-	var stxn types.SignedTxnWithAD
-	stxn.EvalDelta.GlobalDelta = make(map[string]types.ValueDelta)
-	stxn.EvalDelta.GlobalDelta[nonutf8] = types.ValueDelta{
-		Action: types.SetBytesAction,
-		Bytes:  nonutf8b,
-	}
-	stxn.EvalDelta.LocalDeltas = make(map[uint64]types.StateDelta, 1)
-	ld := make(map[string]types.ValueDelta)
-	ld[nonutf8] = types.ValueDelta{
-		Action: types.SetBytesAction,
-		Bytes:  nonutf8b,
-	}
-	stxn.EvalDelta.LocalDeltas[1] = ld
-	js := stxnToJSON(stxn)
-	require.Equal(t, "{\"dt\":{\"gd\":{\"/v7/7wAAESIz\":{\"at\":1,\"bs\":\"/v7/7wAAESIz\"}},\"ld\":{\"1\":{\"/v7/7wAAESIz\":{\"at\":1,\"bs\":\"/v7/7wAAESIz\"}}}}}", string(js))
-}
diff --git a/util/test/testutil.go b/util/test/testutil.go
index 971cbbc..aefda8e 100644
--- a/util/test/testutil.go
+++ b/util/test/testutil.go
@@ -107,7 +107,7 @@ func PrintTxnQuery(db idb.IndexerDb, q idb.TransactionFilter) {
 		//tjs, err := json.Marshal(stxn.Txn) // nope, ugly
 		//MaybeFail(err, "err %v\n", err)
 		//tjs := string(JSONOneLine(stxn.Txn))
-		tjs := string(idb.JSONOneLine(stxn.Txn))
+		tjs := string(util.JSONOneLine(stxn.Txn))
 		info("%d:%d %s sr=%d rr=%d ca=%d cr=%d t=%s\n", txnrow.Round, txnrow.Intra, tjs, stxn.SenderRewards, stxn.ReceiverRewards, stxn.ClosingAmount, stxn.CloseRewards, txnrow.RoundTime.String())
 		count++
 	}