From 61479754d935e36993f7144d70d7c518cdb26a36 Mon Sep 17 00:00:00 2001
From: Aaron Buchwald <aaron.buchwald56@gmail.com>
Date: Wed, 25 Nov 2020 11:20:36 -0500
Subject: Add avax API client and upgrade to avalanchego v1.0.5-client

---
 go.mod                       |   2 +-
 go.sum                       |   2 +
 plugin/evm/client.go         | 174 +++++++++++++++++++++++++++++++++++++++++++
 plugin/evm/export_tx.go      |   2 +-
 plugin/evm/import_tx.go      |   2 +-
 plugin/evm/import_tx_test.go |   4 +-
 plugin/evm/service.go        |  78 +++++--------------
 plugin/evm/static_service.go |   5 +-
 plugin/evm/tx.go             |   6 +-
 plugin/evm/user.go           |   4 +-
 plugin/evm/vm.go             |  57 +++++++-------
 plugin/evm/vm_test.go        |  17 +++--
 12 files changed, 247 insertions(+), 106 deletions(-)
 create mode 100644 plugin/evm/client.go

diff --git a/go.mod b/go.mod
index 7431960..fc27ba4 100644
--- a/go.mod
+++ b/go.mod
@@ -4,7 +4,7 @@ go 1.14
 
 require (
 	github.com/VictoriaMetrics/fastcache v1.5.7
-	github.com/ava-labs/avalanchego v1.0.5
+	github.com/ava-labs/avalanchego v1.0.5-client
 	github.com/davecgh/go-spew v1.1.1
 	github.com/deckarep/golang-set v1.7.1
 	github.com/edsrzf/mmap-go v1.0.0
diff --git a/go.sum b/go.sum
index e8af04f..6232801 100644
--- a/go.sum
+++ b/go.sum
@@ -46,6 +46,8 @@ github.com/aristanetworks/goarista v0.0.0-20170210015632-ea17b1a17847/go.mod h1:
 github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o=
 github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY=
 github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
+github.com/ava-labs/avalanchego v1.0.5-client h1:/tvPyl6T3Ujn8RHeQ83sezt944XZX6BMlKkp1J+I/ss=
+github.com/ava-labs/avalanchego v1.0.5-client/go.mod h1:Q/I7LaMv2EYL8plNVRbcpBJsDk2py2XISfov0KK1MgU=
 github.com/ava-labs/avalanchego v1.0.5 h1:zjBaM/9l2VjnL7j6SEwmqKjPie1W9NSEDLrd2ZotMCA=
 github.com/ava-labs/avalanchego v1.0.5/go.mod h1:4GfedhY9S6k/SOUeMzDh6cuLoPAtnkFsL4R89xg5wf0=
 github.com/aws/aws-sdk-go v1.25.48/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo=
diff --git a/plugin/evm/client.go b/plugin/evm/client.go
new file mode 100644
index 0000000..0817545
--- /dev/null
+++ b/plugin/evm/client.go
@@ -0,0 +1,174 @@
+// (c) 2019-2020, Ava Labs, Inc. All rights reserved.
+// See the file LICENSE for licensing terms.
+
+package evm
+
+import (
+	"fmt"
+	"time"
+
+	"github.com/ava-labs/avalanchego/api"
+	"github.com/ava-labs/avalanchego/ids"
+	"github.com/ava-labs/avalanchego/utils/formatting"
+	cjson "github.com/ava-labs/avalanchego/utils/json"
+	"github.com/ava-labs/avalanchego/utils/rpc"
+)
+
+// Client ...
+type Client struct {
+	requester rpc.EndpointRequester
+}
+
+// NewClient returns a Client for interacting with EVM [chain]
+func NewClient(uri, chain string, requestTimeout time.Duration) *Client {
+	return &Client{
+		requester: rpc.NewEndpointRequester(uri, fmt.Sprintf("/ext/bc/%s/avax", chain), "avax", requestTimeout),
+	}
+}
+
+// NewCChainClient returns a Client for interacting with the C Chain
+func NewCChainClient(uri string, requestTimeout time.Duration) *Client {
+	return NewClient(uri, "C", requestTimeout)
+}
+
+// IssueTx issues a transaction to a node and returns the TxID
+func (c *Client) IssueTx(txBytes []byte) (ids.ID, error) {
+	res := &api.JSONTxID{}
+	txStr, err := formatting.Encode(formatting.Hex, txBytes)
+	if err != nil {
+		return res.TxID, fmt.Errorf("problem hex encoding bytes: %w", err)
+	}
+	err = c.requester.SendRequest("issueTx", &api.FormattedTx{
+		Tx:       txStr,
+		Encoding: formatting.Hex,
+	}, res)
+	return res.TxID, err
+}
+
+// GetTxStatus returns the status of [txID]
+// func (c *Client) GetTxStatus(txID ids.ID) (choices.Status, error) {
+// 	res := &GetTxStatusReply{}
+// 	err := c.requester.SendRequest("getTxStatus", &api.JSONTxID{
+// 		TxID: txID,
+// 	}, res)
+// 	return res.Status, err
+// }
+
+// GetTx returns the byte representation of [txID]
+func (c *Client) GetTx(txID ids.ID) ([]byte, error) {
+	res := &api.FormattedTx{}
+	err := c.requester.SendRequest("getTx", &api.GetTxArgs{
+		TxID:     txID,
+		Encoding: formatting.Hex,
+	}, res)
+	if err != nil {
+		return nil, err
+	}
+
+	return formatting.Decode(formatting.Hex, res.Tx)
+}
+
+// GetUTXOs returns the byte representation of the UTXOs controlled by [addrs]
+func (c *Client) GetUTXOs(addrs []string, limit uint32, startAddress, startUTXOID string) ([][]byte, api.Index, error) {
+	res := &api.GetUTXOsReply{}
+	err := c.requester.SendRequest("getUTXOs", &api.GetUTXOsArgs{
+		Addresses: addrs,
+		Limit:     cjson.Uint32(limit),
+		StartIndex: api.Index{
+			Address: startAddress,
+			UTXO:    startUTXOID,
+		},
+		Encoding: formatting.Hex,
+	}, res)
+	if err != nil {
+		return nil, api.Index{}, err
+	}
+
+	utxos := make([][]byte, len(res.UTXOs))
+	for i, utxo := range res.UTXOs {
+		b, err := formatting.Decode(formatting.Hex, utxo)
+		if err != nil {
+			return nil, api.Index{}, err
+		}
+		utxos[i] = b
+	}
+	return utxos, res.EndIndex, nil
+}
+
+// CreateAddress creates a new address controlled by [user]
+func (c *Client) CreateAddress(user api.UserPass) (string, error) {
+	res := &api.JSONAddress{}
+	err := c.requester.SendRequest("createAddress", &user, res)
+	return res.Address, err
+}
+
+// ListAddresses returns all addresses on this chain controlled by [user]
+func (c *Client) ListAddresses(user api.UserPass) ([]string, error) {
+	res := &api.JSONAddresses{}
+	err := c.requester.SendRequest("listAddresses", &user, res)
+	return res.Addresses, err
+}
+
+// ExportKey returns the private key corresponding to [addr] controlled by [user]
+// in both Avalanche standard format and hex format
+func (c *Client) ExportKey(user api.UserPass, addr string) (string, string, error) {
+	res := &ExportKeyReply{}
+	err := c.requester.SendRequest("exportKey", &ExportKeyArgs{
+		UserPass: user,
+		Address:  addr,
+	}, res)
+	return res.PrivateKey, res.PrivateKeyHex, err
+}
+
+// ImportKey imports [privateKey] to [user]
+func (c *Client) ImportKey(user api.UserPass, privateKey string) (string, error) {
+	res := &api.JSONAddress{}
+	err := c.requester.SendRequest("importKey", &ImportKeyArgs{
+		UserPass:   user,
+		PrivateKey: privateKey,
+	}, res)
+	return res.Address, err
+}
+
+// Import sends an import transaction to import funds from [sourceChain] and
+// returns the ID of the newly created transaction
+func (c *Client) Import(user api.UserPass, to, sourceChain string) (ids.ID, error) {
+	res := &api.JSONTxID{}
+	err := c.requester.SendRequest("import", &ImportArgs{
+		UserPass:    user,
+		To:          to,
+		SourceChain: sourceChain,
+	}, res)
+	return res.TxID, err
+}
+
+// ExportAVAX sends AVAX from this chain to the address specified by [to].
+// Returns the ID of the newly created atomic transaction
+func (c *Client) ExportAVAX(
+	user api.UserPass,
+	amount uint64,
+	to string,
+) (ids.ID, error) {
+	return c.Export(user, amount, to, "AVAX")
+}
+
+// Export sends an asset from this chain to the P/C-Chain.
+// After this tx is accepted, the AVAX must be imported to the P/C-chain with an importTx.
+// Returns the ID of the newly created atomic transaction
+func (c *Client) Export(
+	user api.UserPass,
+	amount uint64,
+	to string,
+	assetID string,
+) (ids.ID, error) {
+	res := &api.JSONTxID{}
+	err := c.requester.SendRequest("export", &ExportArgs{
+		ExportAVAXArgs: ExportAVAXArgs{
+			UserPass: user,
+			Amount:   cjson.Uint64(amount),
+			To:       to,
+		},
+		AssetID: assetID,
+	}, res)
+	return res.TxID, err
+}
diff --git a/plugin/evm/export_tx.go b/plugin/evm/export_tx.go
index ed069d4..2735573 100644
--- a/plugin/evm/export_tx.go
+++ b/plugin/evm/export_tx.go
@@ -149,7 +149,7 @@ func (tx *UnsignedExportTx) Accept(ctx *snow.Context, _ database.Batch) error {
 			Out:   out.Out,
 		}
 
-		utxoBytes, err := Codec.Marshal(utxo)
+		utxoBytes, err := Codec.Marshal(codecVersion, utxo)
 		if err != nil {
 			return err
 		}
diff --git a/plugin/evm/import_tx.go b/plugin/evm/import_tx.go
index 23dbc5f..1ec394c 100644
--- a/plugin/evm/import_tx.go
+++ b/plugin/evm/import_tx.go
@@ -135,7 +135,7 @@ func (tx *UnsignedImportTx) SemanticVerify(
 		utxoBytes := allUTXOBytes[i]
 
 		utxo := &avax.UTXO{}
-		if err := vm.codec.Unmarshal(utxoBytes, utxo); err != nil {
+		if _, err := vm.codec.Unmarshal(utxoBytes, utxo); err != nil {
 			return tempError{err}
 		}
 
diff --git a/plugin/evm/import_tx_test.go b/plugin/evm/import_tx_test.go
index 53b9494..973802a 100644
--- a/plugin/evm/import_tx_test.go
+++ b/plugin/evm/import_tx_test.go
@@ -153,7 +153,7 @@ func TestImportTxSemanticVerify(t *testing.T) {
 			},
 		},
 	}
-	utxoBytes, err := vm.codec.Marshal(utxo)
+	utxoBytes, err := vm.codec.Marshal(codecVersion, utxo)
 	if err != nil {
 		t.Fatal(err)
 	}
@@ -312,7 +312,7 @@ func TestNewImportTx(t *testing.T) {
 			},
 		},
 	}
-	utxoBytes, err := vm.codec.Marshal(utxo)
+	utxoBytes, err := vm.codec.Marshal(codecVersion, utxo)
 	if err != nil {
 		t.Fatal(err)
 	}
diff --git a/plugin/evm/service.go b/plugin/evm/service.go
index 49bb862..2bb06df 100644
--- a/plugin/evm/service.go
+++ b/plugin/evm/service.go
@@ -138,7 +138,11 @@ func (service *AvaxAPI) ExportKey(r *http.Request, args *ExportKeyArgs, reply *E
 	if err != nil {
 		return fmt.Errorf("problem retrieving private key: %w", err)
 	}
-	reply.PrivateKey = constants.SecretKeyPrefix + formatting.CB58{Bytes: sk.Bytes()}.String()
+	encodedKey, err := formatting.Encode(formatting.CB58, sk.Bytes())
+	if err != nil {
+		return fmt.Errorf("problem encoding bytes as cb58: %w", err)
+	}
+	reply.PrivateKey = constants.SecretKeyPrefix + encodedKey
 	reply.PrivateKeyHex = hexutil.Encode(sk.Bytes())
 	return nil
 }
@@ -158,13 +162,13 @@ func (service *AvaxAPI) ImportKey(r *http.Request, args *ImportKeyArgs, reply *a
 	}
 
 	trimmedPrivateKey := strings.TrimPrefix(args.PrivateKey, constants.SecretKeyPrefix)
-	formattedPrivateKey := formatting.CB58{}
-	if err := formattedPrivateKey.FromString(trimmedPrivateKey); err != nil {
+	pkBytes, err := formatting.Decode(formatting.CB58, trimmedPrivateKey)
+	if err != nil {
 		return fmt.Errorf("problem parsing private key: %w", err)
 	}
 
 	factory := crypto.FactorySECP256K1R{}
-	skIntf, err := factory.ToPrivateKey(formattedPrivateKey.Bytes)
+	skIntf, err := factory.ToPrivateKey(pkBytes)
 	if err != nil {
 		return fmt.Errorf("problem parsing private key: %w", err)
 	}
@@ -315,49 +319,8 @@ func (service *AvaxAPI) Export(_ *http.Request, args *ExportArgs, response *api.
 	return service.vm.issueTx(tx)
 }
 
-// Index is an address and an associated UTXO.
-// Marks a starting or stopping point when fetching UTXOs. Used for pagination.
-type Index struct {
-	Address string `json:"address"` // The address as a string
-	UTXO    string `json:"utxo"`    // The UTXO ID as a string
-}
-
-// GetUTXOsArgs are arguments for passing into GetUTXOs.
-// Gets the UTXOs that reference at least one address in [Addresses].
-// Returns at most [limit] addresses.
-// If specified, [SourceChain] is the chain where the atomic UTXOs were exported from. If not specified,
-// then GetUTXOs returns an error since the C Chain only has atomic UTXOs.
-// If [limit] == 0 or > [maxUTXOsToFetch], fetches up to [maxUTXOsToFetch].
-// [StartIndex] defines where to start fetching UTXOs (for pagination.)
-// UTXOs fetched are from addresses equal to or greater than [StartIndex.Address]
-// For address [StartIndex.Address], only UTXOs with IDs greater than [StartIndex.UTXO] will be returned.
-// If [StartIndex] is omitted, gets all UTXOs.
-// If GetUTXOs is called multiple times, with our without [StartIndex], it is not guaranteed
-// that returned UTXOs are unique. That is, the same UTXO may appear in the response of multiple calls.
-type GetUTXOsArgs struct {
-	Addresses   []string    `json:"addresses"`
-	SourceChain string      `json:"sourceChain"`
-	Limit       json.Uint32 `json:"limit"`
-	StartIndex  Index       `json:"startIndex"`
-	Encoding    string      `json:"encoding"`
-}
-
-// GetUTXOsReply defines the GetUTXOs replies returned from the API
-type GetUTXOsReply struct {
-	// Number of UTXOs returned
-	NumFetched json.Uint64 `json:"numFetched"`
-	// The UTXOs
-	UTXOs []string `json:"utxos"`
-	// The last UTXO that was returned, and the address it corresponds to.
-	// Used for pagination. To get the rest of the UTXOs, call GetUTXOs
-	// again and set [StartIndex] to this value.
-	EndIndex Index `json:"endIndex"`
-	// Encoding specifies the encoding format the UTXOs are returned in
-	Encoding string `json:"encoding"`
-}
-
 // GetUTXOs gets all utxos for passed in addresses
-func (service *AvaxAPI) GetUTXOs(r *http.Request, args *GetUTXOsArgs, reply *GetUTXOsReply) error {
+func (service *AvaxAPI) GetUTXOs(r *http.Request, args *api.GetUTXOsArgs, reply *api.GetUTXOsReply) error {
 	service.vm.ctx.Log.Info("EVM: GetUTXOs called for with %s", args.Addresses)
 
 	if len(args.Addresses) == 0 {
@@ -367,11 +330,6 @@ func (service *AvaxAPI) GetUTXOs(r *http.Request, args *GetUTXOsArgs, reply *Get
 		return fmt.Errorf("number of addresses given, %d, exceeds maximum, %d", len(args.Addresses), maxGetUTXOsAddrs)
 	}
 
-	encoding, err := service.vm.encodingManager.GetEncoding(args.Encoding)
-	if err != nil {
-		return fmt.Errorf("problem getting encoding formatter for '%s': %w", args.Encoding, err)
-	}
-
 	sourceChain := ids.ID{}
 	if args.SourceChain == "" {
 		return errNoSourceChain
@@ -418,11 +376,15 @@ func (service *AvaxAPI) GetUTXOs(r *http.Request, args *GetUTXOsArgs, reply *Get
 
 	reply.UTXOs = make([]string, len(utxos))
 	for i, utxo := range utxos {
-		b, err := service.vm.codec.Marshal(utxo)
+		b, err := service.vm.codec.Marshal(codecVersion, utxo)
 		if err != nil {
 			return fmt.Errorf("problem marshalling UTXO: %w", err)
 		}
-		reply.UTXOs[i] = encoding.ConvertBytes(b)
+		str, err := formatting.Encode(args.Encoding, b)
+		if err != nil {
+			return fmt.Errorf("problem encoding utxo: %w", err)
+		}
+		reply.UTXOs[i] = str
 	}
 
 	endAddress, err := service.vm.FormatLocalAddress(endAddr)
@@ -433,7 +395,7 @@ func (service *AvaxAPI) GetUTXOs(r *http.Request, args *GetUTXOsArgs, reply *Get
 	reply.EndIndex.Address = endAddress
 	reply.EndIndex.UTXO = endUTXOID.String()
 	reply.NumFetched = json.Uint64(len(utxos))
-	reply.Encoding = encoding.Encoding()
+	reply.Encoding = args.Encoding
 	return nil
 }
 
@@ -441,17 +403,13 @@ func (service *AvaxAPI) GetUTXOs(r *http.Request, args *GetUTXOsArgs, reply *Get
 func (service *AvaxAPI) IssueTx(r *http.Request, args *api.FormattedTx, response *api.JSONTxID) error {
 	log.Info("EVM: IssueTx called")
 
-	encoding, err := service.vm.encodingManager.GetEncoding(args.Encoding)
-	if err != nil {
-		return fmt.Errorf("problem getting encoding formatter for '%s': %w", args.Encoding, err)
-	}
-	txBytes, err := encoding.ConvertString(args.Tx)
+	txBytes, err := formatting.Decode(args.Encoding, args.Tx)
 	if err != nil {
 		return fmt.Errorf("problem decoding transaction: %w", err)
 	}
 
 	tx := &Tx{}
-	if err := service.vm.codec.Unmarshal(txBytes, tx); err != nil {
+	if _, err := service.vm.codec.Unmarshal(txBytes, tx); err != nil {
 		return fmt.Errorf("problem parsing transaction: %w", err)
 	}
 	if err := tx.Sign(service.vm.codec, nil); err != nil {
diff --git a/plugin/evm/static_service.go b/plugin/evm/static_service.go
index 1e48734..1b45939 100644
--- a/plugin/evm/static_service.go
+++ b/plugin/evm/static_service.go
@@ -8,7 +8,6 @@ import (
 	"encoding/json"
 
 	"github.com/ava-labs/coreth/core"
-	"github.com/ava-labs/avalanchego/utils/formatting"
 )
 
 // StaticService defines the static API services exposed by the evm
@@ -16,7 +15,7 @@ type StaticService struct{}
 
 // BuildGenesis returns the UTXOs such that at least one address in [args.Addresses] is
 // referenced in the UTXO.
-func (*StaticService) BuildGenesis(_ context.Context, args *core.Genesis) (formatting.CB58, error) {
+func (*StaticService) BuildGenesis(_ context.Context, args *core.Genesis) ([]byte, error) {
 	bytes, err := json.Marshal(args)
-	return formatting.CB58{Bytes: bytes}, err
+	return bytes, err
 }
diff --git a/plugin/evm/tx.go b/plugin/evm/tx.go
index 82d4c10..1c0ce48 100644
--- a/plugin/evm/tx.go
+++ b/plugin/evm/tx.go
@@ -89,8 +89,8 @@ type Tx struct {
 // (*secp256k1fx.Credential)
 
 // Sign this transaction with the provided signers
-func (tx *Tx) Sign(c codec.Codec, signers [][]*crypto.PrivateKeySECP256K1R) error {
-	unsignedBytes, err := c.Marshal(&tx.UnsignedTx)
+func (tx *Tx) Sign(c codec.Manager, signers [][]*crypto.PrivateKeySECP256K1R) error {
+	unsignedBytes, err := c.Marshal(codecVersion, &tx.UnsignedTx)
 	if err != nil {
 		return fmt.Errorf("couldn't marshal UnsignedTx: %w", err)
 	}
@@ -111,7 +111,7 @@ func (tx *Tx) Sign(c codec.Codec, signers [][]*crypto.PrivateKeySECP256K1R) erro
 		tx.Creds = append(tx.Creds, cred) // Attach credential
 	}
 
-	signedBytes, err := c.Marshal(tx)
+	signedBytes, err := c.Marshal(codecVersion, tx)
 	if err != nil {
 		return fmt.Errorf("couldn't marshal Tx: %w", err)
 	}
diff --git a/plugin/evm/user.go b/plugin/evm/user.go
index 0ab1863..fc4587e 100644
--- a/plugin/evm/user.go
+++ b/plugin/evm/user.go
@@ -48,7 +48,7 @@ func (u *user) getAddresses() ([]common.Address, error) {
 		return nil, err
 	}
 	addresses := []common.Address{}
-	if err := Codec.Unmarshal(bytes, &addresses); err != nil {
+	if _, err := Codec.Unmarshal(bytes, &addresses); err != nil {
 		return nil, err
 	}
 	return addresses, nil
@@ -94,7 +94,7 @@ func (u *user) putAddress(privKey *crypto.PrivateKeySECP256K1R) error {
 		}
 	}
 	addresses = append(addresses, address)
-	bytes, err := Codec.Marshal(addresses)
+	bytes, err := Codec.Marshal(codecVersion, addresses)
 	if err != nil {
 		return err
 	}
diff --git a/plugin/evm/vm.go b/plugin/evm/vm.go
index 284a772..aaf7f10 100644
--- a/plugin/evm/vm.go
+++ b/plugin/evm/vm.go
@@ -70,6 +70,7 @@ const (
 	batchSize       = 250
 	maxUTXOsToFetch = 1024
 	blockCacheSize  = 1 << 10 // 1024
+	codecVersion    = uint16(0)
 )
 
 const (
@@ -110,26 +111,29 @@ func maxDuration(x, y time.Duration) time.Duration {
 }
 
 // Codec does serialization and deserialization
-var Codec codec.Codec
+var Codec codec.Manager
 
 func init() {
-	Codec = codec.NewDefault()
+	Codec = codec.NewDefaultManager()
+	c := codec.NewDefault()
 
 	errs := wrappers.Errs{}
 	errs.Add(
-		Codec.RegisterType(&UnsignedImportTx{}),
-		Codec.RegisterType(&UnsignedExportTx{}),
+		c.RegisterType(&UnsignedImportTx{}),
+		c.RegisterType(&UnsignedExportTx{}),
 	)
-	Codec.Skip(3)
+	c.Skip(3)
 	errs.Add(
-		Codec.RegisterType(&secp256k1fx.TransferInput{}),
-		Codec.RegisterType(&secp256k1fx.MintOutput{}),
-		Codec.RegisterType(&secp256k1fx.TransferOutput{}),
-		Codec.RegisterType(&secp256k1fx.MintOperation{}),
-		Codec.RegisterType(&secp256k1fx.Credential{}),
-		Codec.RegisterType(&secp256k1fx.Input{}),
-		Codec.RegisterType(&secp256k1fx.OutputOwners{}),
+		c.RegisterType(&secp256k1fx.TransferInput{}),
+		c.RegisterType(&secp256k1fx.MintOutput{}),
+		c.RegisterType(&secp256k1fx.TransferOutput{}),
+		c.RegisterType(&secp256k1fx.MintOperation{}),
+		c.RegisterType(&secp256k1fx.Credential{}),
+		c.RegisterType(&secp256k1fx.Input{}),
+		c.RegisterType(&secp256k1fx.OutputOwners{}),
 	)
+
+	errs.Add(Codec.RegisterCodec(0, c))
 	if errs.Errored() {
 		panic(errs.Err)
 	}
@@ -139,8 +143,7 @@ func init() {
 type VM struct {
 	ctx *snow.Context
 
-	CLIConfig       CommandLineConfig
-	encodingManager formatting.EncodingManager
+	CLIConfig CommandLineConfig
 
 	chainID          *big.Int
 	networkID        uint64
@@ -173,7 +176,8 @@ type VM struct {
 	txSubmitChan          <-chan struct{}
 	atomicTxSubmitChan    chan struct{}
 	shutdownSubmitChan    chan struct{}
-	codec                 codec.Codec
+	baseCodec             codec.Codec
+	codec                 codec.Manager
 	clock                 timer.Clock
 	txFee                 uint64
 	pendingAtomicTxs      chan *Tx
@@ -187,7 +191,7 @@ type VM struct {
 func (vm *VM) getAtomicTx(block *types.Block) *Tx {
 	extdata := block.ExtraData()
 	atx := new(Tx)
-	if err := vm.codec.Unmarshal(extdata, atx); err != nil {
+	if _, err := vm.codec.Unmarshal(extdata, atx); err != nil {
 		return nil
 	}
 	atx.Sign(vm.codec, nil)
@@ -195,7 +199,10 @@ func (vm *VM) getAtomicTx(block *types.Block) *Tx {
 }
 
 // Codec implements the secp256k1fx interface
-func (vm *VM) Codec() codec.Codec { return codec.NewDefault() }
+func (vm *VM) Codec() codec.Manager { return vm.codec }
+
+// CodecRegistry implements the secp256k1fx interface
+func (vm *VM) CodecRegistry() codec.Registry { return vm.baseCodec }
 
 // Clock implements the secp256k1fx interface
 func (vm *VM) Clock() *timer.Clock { return &vm.clock }
@@ -221,12 +228,6 @@ func (vm *VM) Initialize(
 		return vm.CLIConfig.ParsingError
 	}
 
-	encodingManager, err := formatting.NewEncodingManager(formatting.CB58Encoding)
-	if err != nil {
-		return fmt.Errorf("problem creating encoding manager: %w", err)
-	}
-	vm.encodingManager = encodingManager
-
 	if len(fxs) > 0 {
 		return errUnsupportedFXs
 	}
@@ -284,7 +285,7 @@ func (vm *VM) Initialize(
 				vm.newBlockChan <- nil
 				return nil, err
 			}
-			raw, _ := vm.codec.Marshal(atx)
+			raw, _ := vm.codec.Marshal(codecVersion, atx)
 			return raw, nil
 		default:
 			if len(txs) == 0 {
@@ -382,10 +383,14 @@ func (vm *VM) Initialize(
 	vm.genesisHash = chain.GetGenesisBlock().Hash()
 	log.Info(fmt.Sprintf("lastAccepted = %s", vm.lastAccepted.ethBlock.Hash().Hex()))
 
-	// TODO: shutdown this go routine
 	vm.shutdownWg.Add(1)
 	go vm.ctx.Log.RecoverAndPanic(vm.awaitSubmittedTxs)
 	vm.codec = Codec
+	// The Codec explicitly registers the types it requires from the secp256k1fx
+	// so [vm.baseCodec] is a dummy codec use to fulfill the secp256k1fx VM
+	// interface. The fx will register all of its types, which can be safely
+	// ignored by the VM's codec.
+	vm.baseCodec = codec.NewDefault()
 
 	return vm.fx.Initialize(vm)
 }
@@ -857,7 +862,7 @@ func (vm *VM) GetAtomicUTXOs(
 	utxos := make([]*avax.UTXO, len(allUTXOBytes))
 	for i, utxoBytes := range allUTXOBytes {
 		utxo := &avax.UTXO{}
-		if err := vm.codec.Unmarshal(utxoBytes, utxo); err != nil {
+		if _, err := vm.codec.Unmarshal(utxoBytes, utxo); err != nil {
 			return nil, ids.ShortID{}, ids.ID{}, fmt.Errorf("error parsing UTXO: %w", err)
 		}
 		utxos[i] = utxo
diff --git a/plugin/evm/vm_test.go b/plugin/evm/vm_test.go
index 020b663..1b894ce 100644
--- a/plugin/evm/vm_test.go
+++ b/plugin/evm/vm_test.go
@@ -36,7 +36,7 @@ var (
 )
 
 func init() {
-	cb58 := formatting.CB58{}
+	var b []byte
 	factory := crypto.FactorySECP256K1R{}
 
 	for _, key := range []string{
@@ -44,8 +44,8 @@ func init() {
 		"2MMvUMsxx6zsHSNXJdFD8yc5XkancvwyKPwpw4xUK3TCGDuNBY",
 		"cxb7KpGWhDMALTjNNSJ7UQkkomPesyWAPUaWRGdyeBNzR6f35",
 	} {
-		_ = cb58.FromString(key)
-		pk, _ := factory.ToPrivateKey(cb58.Bytes)
+		b, _ = formatting.Decode(formatting.CB58, key)
+		pk, _ := factory.ToPrivateKey(b)
 		secpKey := pk.(*crypto.PrivateKeySECP256K1R)
 		testKeys = append(testKeys, secpKey)
 		testEthAddrs = append(testEthAddrs, GetEthAddress(secpKey))
@@ -63,11 +63,11 @@ func BuildGenesisTest(t *testing.T) []byte {
 	if err := json.Unmarshal([]byte(genesisJSON), genesis); err != nil {
 		t.Fatalf("Problem unmarshaling genesis JSON: %s", err)
 	}
-	genesisReply, err := ss.BuildGenesis(nil, genesis)
+	genesisBytes, err := ss.BuildGenesis(nil, genesis)
 	if err != nil {
 		t.Fatalf("Failed to create test genesis")
 	}
-	return genesisReply.Bytes
+	return genesisBytes
 }
 
 func NewContext() *snow.Context {
@@ -102,7 +102,10 @@ func GenesisVM(t *testing.T, finishBootstrapping bool) (chan engCommon.Message,
 	// The caller of this function is responsible for unlocking.
 	ctx.Lock.Lock()
 
-	userKeystore := keystore.CreateTestKeystore()
+	userKeystore, err := keystore.CreateTestKeystore()
+	if err != nil {
+		t.Fatal(err)
+	}
 	if err := userKeystore.AddUser(username, password); err != nil {
 		t.Fatal(err)
 	}
@@ -112,7 +115,7 @@ func GenesisVM(t *testing.T, finishBootstrapping bool) (chan engCommon.Message,
 	vm := &VM{
 		txFee: testTxFee,
 	}
-	err := vm.Initialize(
+	err = vm.Initialize(
 		ctx,
 		prefixdb.New([]byte{1}, baseDB),
 		genesisBytes,
-- 
cgit v1.2.3-70-g09d2