diff --git a/api/results.go b/api/results.go
new file mode 100644
index 0000000000000000000000000000000000000000..e339a483767b2eed9342b7846b81e8aa38f0dd7a
--- /dev/null
+++ b/api/results.go
@@ -0,0 +1,209 @@
+///////////////////////////////////////////////////////////////////////////////
+// Copyright © 2020 xx network SEZC                                          //
+//                                                                           //
+// Use of this source code is governed by a license that can be found in the //
+// LICENSE file                                                              //
+///////////////////////////////////////////////////////////////////////////////
+package api
+
+import (
+	"fmt"
+	jww "github.com/spf13/jwalterweatherman"
+	"gitlab.com/elixxir/client/globals"
+	"gitlab.com/elixxir/client/network/gateway"
+	pb "gitlab.com/elixxir/comms/mixmessages"
+	"gitlab.com/elixxir/comms/network"
+	ds "gitlab.com/elixxir/comms/network/dataStructures"
+	"gitlab.com/elixxir/primitives/states"
+	"gitlab.com/xx_network/comms/connect"
+	"gitlab.com/xx_network/primitives/id"
+	"time"
+)
+
+// Enum of possible round results to pass back
+type RoundResult uint
+
+const (
+	TimeOut RoundResult = iota
+	Failed
+	Succeeded
+)
+
+func (rr RoundResult) String() string  {
+	switch rr {
+	case TimeOut:
+		return "TimeOut"
+	case Failed:
+		return "Failed"
+	case Succeeded:
+		return "Succeeded"
+	default:
+		return fmt.Sprintf("UNKNOWN RESULT: %d", rr)
+	}
+}
+
+// Callback interface which reports the requested rounds.
+// Designed such that the caller may decide how much detail they need.
+// allRoundsSucceeded:
+//   Returns false if any rounds in the round map were unsuccessful.
+//   Returns true if ALL rounds were successful
+// timedOut:
+//    Returns true if any of the rounds timed out while being monitored
+//	  Returns false if all rounds statuses were returned
+// rounds contains a mapping of all previously requested rounds to
+//   their respective round results
+type RoundEventCallback func(allRoundsSucceeded, timedOut bool, rounds map[id.Round]RoundResult)
+
+// Comm interface for RequestHistoricalRounds.
+// Constructed for testability with getRoundResults
+type historicalRoundsComm interface {
+	RequestHistoricalRounds(host *connect.Host,
+		message *pb.HistoricalRounds) (*pb.HistoricalRoundsResponse, error)
+	GetHost(hostId *id.ID) (*connect.Host, bool)
+}
+
+// Adjudicates on the rounds requested. Checks if they are
+// older rounds or in progress rounds.
+func (c *Client) GetRoundResults(roundList []id.Round, timeout time.Duration,
+	roundCallback RoundEventCallback) error {
+
+	sendResults := make(chan ds.EventReturn, len(roundList))
+
+	return c.getRoundResults(roundList, timeout, roundCallback,
+		sendResults, c.comms)
+}
+
+// Helper function which does all the logic for GetRoundResults
+func (c *Client) getRoundResults(roundList []id.Round, timeout time.Duration,
+	roundCallback RoundEventCallback, sendResults chan ds.EventReturn,
+	commsInterface historicalRoundsComm) error {
+
+	networkInstance := c.network.GetInstance()
+
+	// Generate a message to track all older rounds
+	historicalRequest := &pb.HistoricalRounds{
+		Rounds: []uint64{},
+	}
+
+	// Generate all tracking structures for rounds
+	roundEvents := c.GetRoundEvents()
+	roundsResults := make(map[id.Round]RoundResult)
+	allRoundsSucceeded := true
+	numResults := 0
+
+	// Parse and adjudicate every round
+	for _, rnd := range roundList {
+		// Every round is timed out by default, until proven to have finished
+		roundsResults[rnd] = TimeOut
+		roundInfo, err := networkInstance.GetRound(rnd)
+		// If we have the round in the buffer
+		if err == nil {
+			// Check if the round is done (completed or failed) or in progress
+			if states.Round(roundInfo.State) == states.COMPLETED {
+				roundsResults[rnd] = Succeeded
+			} else if states.Round(roundInfo.State) == states.FAILED {
+				roundsResults[rnd] = Failed
+				allRoundsSucceeded = false
+			} else {
+				// If in progress, add a channel monitoring its status
+				roundEvents.AddRoundEventChan(rnd, sendResults,
+					timeout-time.Millisecond, states.COMPLETED, states.FAILED)
+				numResults++
+			}
+		} else {
+			jww.DEBUG.Printf("Failed to ger round [%d] in buffer: %v", rnd, err)
+			// Update oldest round (buffer may have updated externally)
+			oldestRound := networkInstance.GetOldestRoundID()
+			if rnd < oldestRound {
+				// If round is older that oldest round in our buffer
+				// Add it to the historical round request (performed later)
+				historicalRequest.Rounds = append(historicalRequest.Rounds, uint64(rnd))
+				numResults++
+			} else {
+				// Otherwise, monitor it's progress
+				roundEvents.AddRoundEventChan(rnd, sendResults,
+					timeout-time.Millisecond, states.COMPLETED, states.FAILED)
+				numResults++
+			}
+		}
+	}
+
+	// Find out what happened to old (historical) rounds if any are needed
+	if len(historicalRequest.Rounds) > 0 {
+		go c.getHistoricalRounds(historicalRequest, networkInstance, sendResults, commsInterface)
+	}
+
+	// Determine the results of all rounds requested
+	go func() {
+		// Create the results timer
+		timer := time.NewTimer(timeout)
+		for {
+
+			// If we know about all rounds, return
+			if numResults == 0 {
+				roundCallback(allRoundsSucceeded, false, roundsResults)
+				return
+			}
+
+			// Wait for info about rounds or the timeout to occur
+			select {
+			case <-timer.C:
+				roundCallback(false, true, roundsResults)
+				return
+			case roundReport := <-sendResults:
+				numResults--
+				// Skip if the round is nil (unknown from historical rounds)
+				// they default to timed out, so correct behavior is preserved
+				if roundReport.RoundInfo == nil || roundReport.TimedOut {
+					allRoundsSucceeded = false
+				} else {
+					// If available, denote the result
+					roundId := id.Round(roundReport.RoundInfo.ID)
+					if states.Round(roundReport.RoundInfo.State) == states.COMPLETED {
+						roundsResults[roundId] = Succeeded
+					} else {
+						roundsResults[roundId] = Failed
+						allRoundsSucceeded = false
+
+					}
+				}
+			}
+		}
+	}()
+
+	return nil
+}
+
+// Helper function which asynchronously pings a random gateway until
+// it gets information on it's requested historical rounds
+func (c *Client) getHistoricalRounds(msg *pb.HistoricalRounds,
+	instance *network.Instance, sendResults chan ds.EventReturn,
+	comms historicalRoundsComm) {
+
+	var resp *pb.HistoricalRoundsResponse
+
+	for {
+		// Find a gateway to request about the roundRequests
+		gwHost, err := gateway.Get(instance.GetPartialNdf().Get(), comms, c.rng.GetStream())
+		if err != nil {
+			globals.Log.FATAL.Panicf("Failed to track network, NDF has corrupt "+
+				"data: %s", err)
+		}
+
+		// If an error, retry with (potentially) a different gw host.
+		// If no error from received gateway request, exit loop
+		// and process rounds
+		resp, err = comms.RequestHistoricalRounds(gwHost, msg)
+		if err == nil {
+			break
+		}
+	}
+
+	// Process historical rounds, sending back to the caller thread
+	for _, ri := range resp.Rounds {
+		sendResults <- ds.EventReturn{
+			ri,
+			false,
+		}
+	}
+}
diff --git a/api/results_test.go b/api/results_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..54433f2e9b8bcfd91e3d83e5745bdc1d3ceecc75
--- /dev/null
+++ b/api/results_test.go
@@ -0,0 +1,249 @@
+///////////////////////////////////////////////////////////////////////////////
+// Copyright © 2020 xx network SEZC                                          //
+//                                                                           //
+// Use of this source code is governed by a license that can be found in the //
+// LICENSE file                                                              //
+///////////////////////////////////////////////////////////////////////////////
+package api
+
+import (
+	pb "gitlab.com/elixxir/comms/mixmessages"
+	ds "gitlab.com/elixxir/comms/network/dataStructures"
+	"gitlab.com/elixxir/primitives/states"
+	"gitlab.com/xx_network/primitives/id"
+	"testing"
+	"time"
+)
+
+const numRounds = 10
+
+// Happy path
+func TestClient_GetRoundResults(t *testing.T) {
+	// Populate a round list to request
+	var roundList []id.Round
+	for i := 0; i < numRounds; i++ {
+		roundList = append(roundList, id.Round(i))
+	}
+
+	// Pre-populate the results channel with successful rounds
+	sendResults := make(chan ds.EventReturn, len(roundList))
+	for i := 0; i < numRounds; i++ {
+		sendResults <- ds.EventReturn{
+			RoundInfo: &pb.RoundInfo{
+				ID:    uint64(i),
+				State: uint32(states.COMPLETED),
+			},
+			TimedOut: false,
+		}
+	}
+
+	// Create a new copy of the test client for this test
+	client, err := newTestingClient(t)
+	if err != nil {
+		t.Errorf("Failed in setup: %v", err)
+	}
+
+	// Construct the round call back function signature
+	var successfulRounds, timeout bool
+	receivedRCB := func(allRoundsSucceeded, timedOut bool, rounds map[id.Round]RoundResult) {
+		successfulRounds = allRoundsSucceeded
+		timeout = timedOut
+	}
+
+	// Call the round results
+	err = client.getRoundResults(roundList, time.Duration(10)*time.Millisecond,
+		receivedRCB, sendResults, NewNoHistoricalRoundsComm())
+	if err != nil {
+		t.Errorf("Error in happy path: %v", err)
+	}
+
+	// Sleep to allow the report to come through the pipeline
+	time.Sleep(1 * time.Second)
+
+	// If any rounds timed out or any round failed, the happy path has failed
+	if timeout || !successfulRounds {
+		t.Errorf("Unexpected round failures in happy path. "+
+			"Expected all rounds to succeed with no timeouts."+
+			"\n\tTimedOut: %v"+
+			"\n\tallRoundsSucceeded: %v", timeout, successfulRounds)
+	}
+
+}
+
+// Checks that an two failed rounds (one timed out, one failure)
+// affects the values in the report.
+// Kept separately to ensure uncoupled failed rounds
+// affect both report booleans
+func TestClient_GetRoundResults_FailedRounds(t *testing.T) {
+	// Populate a round list to request
+	var roundList []id.Round
+	for i := 0; i < numRounds; i++ {
+		roundList = append(roundList, id.Round(i))
+	}
+
+	// Pre-populate the results channel with mostly successful rounds
+	sendResults := make(chan ds.EventReturn, len(roundList))
+	for i := 0; i < numRounds; i++ {
+		// Last two rounds will have a failure and a timeout respectively
+		result := ds.EventReturn{
+			RoundInfo: &pb.RoundInfo{
+				ID:    uint64(i),
+				State: uint32(states.COMPLETED),
+			},
+			TimedOut: false,
+		}
+		if i == numRounds-2 {
+			result.RoundInfo.State = uint32(states.FAILED)
+		}
+
+		sendResults <- result
+
+	}
+
+	// Create a new copy of the test client for this test
+	client, err := newTestingClient(t)
+	if err != nil {
+		t.Errorf("Failed in setup: %v", err)
+	}
+
+	// Construct the round call back function signature
+	var successfulRounds, timeout bool
+	receivedRCB := func(allRoundsSucceeded, timedOut bool, rounds map[id.Round]RoundResult) {
+		successfulRounds = allRoundsSucceeded
+		timeout = timedOut
+	}
+
+	// Call the round results
+	err = client.getRoundResults(roundList, time.Duration(10)*time.Millisecond,
+		receivedRCB, sendResults, NewNoHistoricalRoundsComm())
+	if err != nil {
+		t.Errorf("Error in happy path: %v", err)
+	}
+
+	// Sleep to allow the report to come through the pipeline
+	time.Sleep(2 * time.Second)
+
+	// If no rounds have failed, this test has failed
+	if successfulRounds {
+		t.Errorf("Expected some rounds to fail. "+
+			"\n\tTimedOut: %v"+
+			"\n\tallRoundsSucceeded: %v", timeout, successfulRounds)
+	}
+
+}
+
+// Use the historical rounds interface which actually sends back rounds
+func TestClient_GetRoundResults_HistoricalRounds(t *testing.T) {
+	// Populate a round list to request
+	var roundList []id.Round
+	for i := 0; i < numRounds; i++ {
+		roundList = append(roundList, id.Round(i))
+	}
+
+	// Pre-populate the results channel with successful rounds
+	sendResults := make(chan ds.EventReturn, len(roundList)-2)
+	for i := 0; i < numRounds; i++ {
+		// Skip sending rounds intended for historical rounds comm
+		if i == failedHistoricalRoundID ||
+			i == completedHistoricalRoundID {
+			continue
+		}
+
+		sendResults <- ds.EventReturn{
+			RoundInfo: &pb.RoundInfo{
+				ID:    uint64(i),
+				State: uint32(states.COMPLETED),
+			},
+			TimedOut: false,
+		}
+	}
+
+	// Create a new copy of the test client for this test
+	client, err := newTestingClient(t)
+	if err != nil {
+		t.Errorf("Failed in setup: %v", err)
+	}
+
+	// Overpopulate the round buffer, ensuring a circle back of the ring buffer
+	for i := 1; i <= ds.RoundInfoBufLen+completedHistoricalRoundID+1; i++ {
+		ri := &pb.RoundInfo{ID: uint64(i)}
+		if err = signRoundInfo(ri); err != nil {
+			t.Errorf("Failed to sign round in set up: %v", err)
+		}
+
+		err = client.network.GetInstance().RoundUpdate(ri)
+		if err != nil {
+			t.Errorf("Failed to upsert round in set up: %v", err)
+		}
+
+	}
+
+	// Construct the round call back function signature
+	var successfulRounds, timeout bool
+	receivedRCB := func(allRoundsSucceeded, timedOut bool, rounds map[id.Round]RoundResult) {
+		successfulRounds = allRoundsSucceeded
+		timeout = timedOut
+	}
+
+	// Call the round results
+	err = client.getRoundResults(roundList, time.Duration(10)*time.Millisecond,
+		receivedRCB, sendResults, NewHistoricalRoundsComm())
+	if err != nil {
+		t.Errorf("Error in happy path: %v", err)
+	}
+
+	// Sleep to allow the report to come through the pipeline
+	time.Sleep(2 * time.Second)
+
+	// If no round failed, this test has failed
+	if successfulRounds {
+		t.Errorf("Expected historical rounds to have round failures"+
+			"\n\tTimedOut: %v"+
+			"\n\tallRoundsSucceeded: %v", timeout, successfulRounds)
+	}
+}
+
+// Force some timeouts by not populating the entire results channel
+func TestClient_GetRoundResults_Timeout(t *testing.T) {
+	// Populate a round list to request
+	var roundList []id.Round
+	for i := 0; i < numRounds; i++ {
+		roundList = append(roundList, id.Round(i))
+	}
+
+	// Create a broken channel which will never send,
+	// forcing a timeout
+	var sendResults chan ds.EventReturn
+	sendResults = nil
+
+	// Create a new copy of the test client for this test
+	client, err := newTestingClient(t)
+	if err != nil {
+		t.Errorf("Failed in setup: %v", err)
+	}
+
+	// Construct the round call back function signature
+	var successfulRounds, timeout bool
+	receivedRCB := func(allRoundsSucceeded, timedOut bool, rounds map[id.Round]RoundResult) {
+		successfulRounds = allRoundsSucceeded
+		timeout = timedOut
+	}
+
+	// Call the round results
+	err = client.getRoundResults(roundList, time.Duration(10)*time.Millisecond,
+		receivedRCB, sendResults, NewNoHistoricalRoundsComm())
+	if err != nil {
+		t.Errorf("Error in happy path: %v", err)
+	}
+
+	// Sleep to allow the report to come through the pipeline
+	time.Sleep(2 * time.Second)
+
+	// If no rounds have timed out , this test has failed
+	if !timeout {
+		t.Errorf("Expected all rounds to timeout with no valid round reporter."+
+			"\n\tTimedOut: %v"+
+			"\n\tallRoundsSucceeded: %v", timeout, successfulRounds)
+	}
+
+}
diff --git a/api/utilsInterfaces_test.go b/api/utilsInterfaces_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..6eb4c466a51845e756f275483271a785b643feaa
--- /dev/null
+++ b/api/utilsInterfaces_test.go
@@ -0,0 +1,117 @@
+///////////////////////////////////////////////////////////////////////////////
+// Copyright © 2020 xx network SEZC                                          //
+//                                                                           //
+// Use of this source code is governed by a license that can be found in the //
+// LICENSE file                                                              //
+///////////////////////////////////////////////////////////////////////////////
+package api
+
+import (
+	"gitlab.com/elixxir/client/interfaces"
+	"gitlab.com/elixxir/client/interfaces/message"
+	"gitlab.com/elixxir/client/interfaces/params"
+	"gitlab.com/elixxir/client/stoppable"
+	pb "gitlab.com/elixxir/comms/mixmessages"
+	"gitlab.com/elixxir/comms/network"
+	cE2e "gitlab.com/elixxir/crypto/e2e"
+	"gitlab.com/elixxir/primitives/format"
+	"gitlab.com/elixxir/primitives/states"
+	"gitlab.com/xx_network/comms/connect"
+	"gitlab.com/xx_network/primitives/id"
+	"gitlab.com/xx_network/primitives/id/ephemeral"
+)
+
+// Mock comm struct which returns no historical round data
+type noHistoricalRounds struct{}
+
+// Constructor for noHistoricalRounds
+func NewNoHistoricalRoundsComm() *noHistoricalRounds {
+	return &noHistoricalRounds{}
+}
+
+// Returns no rounds back
+func (ht *noHistoricalRounds) RequestHistoricalRounds(host *connect.Host,
+	message *pb.HistoricalRounds) (*pb.HistoricalRoundsResponse, error) {
+	return nil, nil
+}
+
+// Built for interface adherence
+func (ht *noHistoricalRounds) GetHost(hostId *id.ID) (*connect.Host, bool) {
+	return nil, false
+}
+
+// Generate a mock comm which returns some historical round data
+type historicalRounds struct{}
+
+// Constructor for historicalRounds comm interface
+func NewHistoricalRoundsComm() *historicalRounds {
+	return &historicalRounds{}
+}
+
+// Round IDs to return on mock historicalRounds comm
+const failedHistoricalRoundID = 7
+const completedHistoricalRoundID = 8
+
+//  Mock comms endpoint which returns historical rounds
+func (ht *historicalRounds) RequestHistoricalRounds(host *connect.Host,
+	message *pb.HistoricalRounds) (*pb.HistoricalRoundsResponse, error) {
+	// Return one successful and one failed mock round
+	failedRound := &pb.RoundInfo{
+		ID:    failedHistoricalRoundID,
+		State: uint32(states.FAILED),
+	}
+
+	completedRound := &pb.RoundInfo{
+		ID:    completedHistoricalRoundID,
+		State: uint32(states.COMPLETED),
+	}
+
+	return &pb.HistoricalRoundsResponse{
+		Rounds: []*pb.RoundInfo{failedRound, completedRound},
+	}, nil
+}
+
+// Build for interface adherence
+func (ht *historicalRounds) GetHost(hostId *id.ID) (*connect.Host, bool) {
+	return nil, true
+}
+
+// Contains a test implementation of the networkManager interface.
+type testNetworkManagerGeneric struct {
+	instance *network.Instance
+}
+
+/* Below methods built for interface adherence */
+func (t *testNetworkManagerGeneric) GetHealthTracker() interfaces.HealthTracker {
+	return nil
+}
+func (t *testNetworkManagerGeneric) Follow() (stoppable.Stoppable, error) {
+	return nil, nil
+}
+func (t *testNetworkManagerGeneric) CheckGarbledMessages() {
+	return
+}
+func (t *testNetworkManagerGeneric) SendE2E(m message.Send, p params.E2E) (
+	[]id.Round, cE2e.MessageID, error) {
+	rounds := []id.Round{id.Round(0), id.Round(1), id.Round(2)}
+	return rounds, cE2e.MessageID{}, nil
+
+}
+func (t *testNetworkManagerGeneric) SendUnsafe(m message.Send, p params.Unsafe) ([]id.Round, error) {
+	return nil, nil
+}
+func (t *testNetworkManagerGeneric) SendCMIX(message format.Message, rid *id.ID, p params.CMIX) (id.Round, ephemeral.Id, error) {
+	return id.Round(0), ephemeral.Id{}, nil
+}
+func (t *testNetworkManagerGeneric) GetInstance() *network.Instance {
+	return t.instance
+}
+func (t *testNetworkManagerGeneric) RegisterWithPermissioning(string) ([]byte, error) {
+	return nil, nil
+}
+func (t *testNetworkManagerGeneric) GetRemoteVersion() (string, error) {
+	return "test", nil
+}
+func (t *testNetworkManagerGeneric) GetStoppable() stoppable.Stoppable {
+	return &stoppable.Multi{}
+}
diff --git a/api/utils_test.go b/api/utils_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..74360610803c2107181f87efa0f29363d6da3f80
--- /dev/null
+++ b/api/utils_test.go
@@ -0,0 +1,151 @@
+///////////////////////////////////////////////////////////////////////////////
+// Copyright © 2020 xx network SEZC                                          //
+//                                                                           //
+// Use of this source code is governed by a license that can be found in the //
+// LICENSE file                                                              //
+///////////////////////////////////////////////////////////////////////////////
+
+package api
+
+import (
+	"github.com/pkg/errors"
+	"gitlab.com/elixxir/client/globals"
+	"gitlab.com/elixxir/client/interfaces/params"
+	pb "gitlab.com/elixxir/comms/mixmessages"
+	"gitlab.com/elixxir/comms/network"
+	"gitlab.com/elixxir/comms/testkeys"
+	"gitlab.com/xx_network/comms/connect"
+	"gitlab.com/xx_network/comms/signature"
+	"gitlab.com/xx_network/crypto/signature/rsa"
+	"gitlab.com/xx_network/crypto/tls"
+	"gitlab.com/xx_network/primitives/id"
+	"gitlab.com/xx_network/primitives/ndf"
+	"gitlab.com/xx_network/primitives/utils"
+	"testing"
+)
+
+func newTestingClient(face interface{}) (*Client, error) {
+	switch face.(type) {
+	case *testing.T, *testing.M, *testing.B, *testing.PB:
+		break
+	default:
+		globals.Log.FATAL.Panicf("InitTestingSession is restricted to testing only. Got %T", face)
+	}
+
+	def := getNDF(face)
+	marshalledDef, _ := def.Marshal()
+	storageDir := "ignore.1"
+	password := []byte("hunter2")
+	err := NewClient(string(marshalledDef), storageDir, password, "AAAA")
+	if err != nil {
+		return nil, errors.Errorf("Could not construct a mock client: %v", err)
+	}
+
+	c, err := OpenClient(storageDir, password, params.GetDefaultNetwork())
+	if err != nil {
+		return nil, errors.Errorf("Could not open a mock client: %v", err)
+	}
+
+	commsManager := connect.NewManagerTesting(face)
+
+	cert, err := utils.ReadFile(testkeys.GetNodeCertPath())
+	if err != nil {
+		globals.Log.FATAL.Panicf("Failed to create new test Instance: %v", err)
+	}
+
+	commsManager.AddHost(&id.Permissioning, "", cert, connect.GetDefaultHostParams())
+	instanceComms := &connect.ProtoComms{
+		Manager: commsManager,
+	}
+
+	thisInstance, err := network.NewInstanceTesting(instanceComms, def, def, nil, nil, face)
+	if err != nil {
+		return nil, nil
+	}
+
+	c.network = &testNetworkManagerGeneric{instance: thisInstance}
+
+	return c, nil
+}
+
+// Helper function which generates an ndf for testing
+func getNDF(face interface{}) *ndf.NetworkDefinition {
+	switch face.(type) {
+	case *testing.T, *testing.M, *testing.B, *testing.PB:
+		break
+	default:
+		globals.Log.FATAL.Panicf("InitTestingSession is restricted to testing only. Got %T", face)
+	}
+
+	cert, _ := utils.ReadFile(testkeys.GetNodeCertPath())
+	nodeID := id.NewIdFromBytes([]byte("gateway"), face)
+	return &ndf.NetworkDefinition{
+		Registration: ndf.Registration{
+			TlsCertificate: string(cert),
+		},
+		Nodes: []ndf.Node{
+			{
+				ID:             nodeID.Bytes(),
+				Address:        "",
+				TlsCertificate: string(cert),
+			},
+		},
+		Gateways: []ndf.Gateway{
+			{
+				ID:             nodeID.Bytes(),
+				Address:        "",
+				TlsCertificate: string(cert),
+			},
+		},
+		E2E: ndf.Group{
+			Prime: "E2EE983D031DC1DB6F1A7A67DF0E9A8E5561DB8E8D49413394C049B" +
+				"7A8ACCEDC298708F121951D9CF920EC5D146727AA4AE535B0922C688B55B3DD2AE" +
+				"DF6C01C94764DAB937935AA83BE36E67760713AB44A6337C20E7861575E745D31F" +
+				"8B9E9AD8412118C62A3E2E29DF46B0864D0C951C394A5CBBDC6ADC718DD2A3E041" +
+				"023DBB5AB23EBB4742DE9C1687B5B34FA48C3521632C4A530E8FFB1BC51DADDF45" +
+				"3B0B2717C2BC6669ED76B4BDD5C9FF558E88F26E5785302BEDBCA23EAC5ACE9209" +
+				"6EE8A60642FB61E8F3D24990B8CB12EE448EEF78E184C7242DD161C7738F32BF29" +
+				"A841698978825B4111B4BC3E1E198455095958333D776D8B2BEEED3A1A1A221A6E" +
+				"37E664A64B83981C46FFDDC1A45E3D5211AAF8BFBC072768C4F50D7D7803D2D4F2" +
+				"78DE8014A47323631D7E064DE81C0C6BFA43EF0E6998860F1390B5D3FEACAF1696" +
+				"015CB79C3F9C2D93D961120CD0E5F12CBB687EAB045241F96789C38E89D796138E" +
+				"6319BE62E35D87B1048CA28BE389B575E994DCA755471584A09EC723742DC35873" +
+				"847AEF49F66E43873",
+			Generator: "2",
+		},
+		CMIX: ndf.Group{
+			Prime: "9DB6FB5951B66BB6FE1E140F1D2CE5502374161FD6538DF1648218642F0B5C48" +
+				"C8F7A41AADFA187324B87674FA1822B00F1ECF8136943D7C55757264E5A1A44F" +
+				"FE012E9936E00C1D3E9310B01C7D179805D3058B2A9F4BB6F9716BFE6117C6B5" +
+				"B3CC4D9BE341104AD4A80AD6C94E005F4B993E14F091EB51743BF33050C38DE2" +
+				"35567E1B34C3D6A5C0CEAA1A0F368213C3D19843D0B4B09DCB9FC72D39C8DE41" +
+				"F1BF14D4BB4563CA28371621CAD3324B6A2D392145BEBFAC748805236F5CA2FE" +
+				"92B871CD8F9C36D3292B5509CA8CAA77A2ADFC7BFD77DDA6F71125A7456FEA15" +
+				"3E433256A2261C6A06ED3693797E7995FAD5AABBCFBE3EDA2741E375404AE25B",
+			Generator: "5C7FF6B06F8F143FE8288433493E4769C4D988ACE5BE25A0E24809670716C613" +
+				"D7B0CEE6932F8FAA7C44D2CB24523DA53FBE4F6EC3595892D1AA58C4328A06C4" +
+				"6A15662E7EAA703A1DECF8BBB2D05DBE2EB956C142A338661D10461C0D135472" +
+				"085057F3494309FFA73C611F78B32ADBB5740C361C9F35BE90997DB2014E2EF5" +
+				"AA61782F52ABEB8BD6432C4DD097BC5423B285DAFB60DC364E8161F4A2A35ACA" +
+				"3A10B1C4D203CC76A470A33AFDCBDD92959859ABD8B56E1725252D78EAC66E71" +
+				"BA9AE3F1DD2487199874393CD4D832186800654760E1E34C09E4D155179F9EC0" +
+				"DC4473F996BDCE6EED1CABED8B6F116F7AD9CF505DF0F998E34AB27514B0FFE7",
+		},
+	}
+}
+
+// Signs a passed round info with the key tied to the test node cert
+// used throughout utils and other tests
+func signRoundInfo(ri *pb.RoundInfo) error {
+	privKeyFromFile := testkeys.LoadFromPath(testkeys.GetNodeKeyPath())
+
+	pk, err := tls.LoadRSAPrivateKey(string(privKeyFromFile))
+	if err != nil {
+		return errors.Errorf("Couldn't load private key: %+v", err)
+	}
+
+	ourPrivateKey := &rsa.PrivateKey{PrivateKey: *pk}
+
+	return signature.Sign(ri, ourPrivateKey)
+
+}
diff --git a/bindings/callback.go b/bindings/callback.go
index 68ca5646e0122f8a08b1d5d27ca580e2783e603c..4fc0431a41718961405f040fed2a0691aa86b44d 100644
--- a/bindings/callback.go
+++ b/bindings/callback.go
@@ -38,7 +38,7 @@ type RoundEventCallback interface {
 
 // RoundEventHandler handles round events happening on the cMix network.
 type MessageDeliveryCallback interface {
-	EventCallback(msgID []byte, delivered, timedOut bool)
+	EventCallback(msgID []byte, delivered, timedOut bool, roundResults []byte)
 }
 
 // AuthRequestCallback notifies the register whenever they receive an auth
diff --git a/bindings/client.go b/bindings/client.go
index 7b026bd913960409189070363fb87d7bad92ed34..866721af2ed32316a3191c0b60d7bfbdfe82691a 100644
--- a/bindings/client.go
+++ b/bindings/client.go
@@ -16,9 +16,7 @@ import (
 	"gitlab.com/elixxir/client/interfaces/contact"
 	"gitlab.com/elixxir/client/interfaces/message"
 	"gitlab.com/elixxir/client/interfaces/params"
-	"gitlab.com/elixxir/client/interfaces/utility"
 	"gitlab.com/elixxir/comms/mixmessages"
-	ds "gitlab.com/elixxir/comms/network/dataStructures"
 	"gitlab.com/elixxir/primitives/states"
 	"gitlab.com/xx_network/primitives/id"
 	"time"
@@ -298,7 +296,7 @@ func (c *Client) RegisterRoundEventsHandler(rid int, cb RoundEventCallback,
 
 // RegisterMessageDeliveryCB allows the caller to get notified if the rounds a
 // message was sent in successfully completed. Under the hood, this uses the same
-// interface as RegisterRoundEventsHandler, but provides a convent way to use
+// interface as RegisterRoundEventsHandler, but provides a convenient way to use
 // the interface in its most common form, looking up the result of message
 // retrieval
 //
@@ -307,37 +305,30 @@ func (c *Client) RegisterRoundEventsHandler(rid int, cb RoundEventCallback,
 // This function takes the marshaled send report to ensure a memory leak does
 // not occur as a result of both sides of the bindings holding a reference to
 // the same pointer.
-func (c *Client) RegisterMessageDeliveryCB(marshaledSendReport []byte,
-	mdc MessageDeliveryCallback, timeoutMS int) (*Unregister, error) {
+func (c *Client) WaitForRoundCompletion(marshaledSendReport []byte,
+	mdc MessageDeliveryCallback, timeoutMS int) error {
 
 	sr, err := UnmarshalSendReport(marshaledSendReport)
 	if err != nil {
-		return nil, errors.New(fmt.Sprintf("Failed to "+
-			"RegisterMessageDeliveryCB: %+v", err))
+		 return errors.New(fmt.Sprintf("Failed to "+
+			"WaitForRoundCompletion callback due to bad Send Report: %+v", err))
 	}
 
-	/*check message delivery*/
-	sendResults := make(chan ds.EventReturn, len(sr.rl.list))
-	roundEvents := c.api.GetRoundEvents()
+	f := func(allRoundsSucceeded, timedOut bool, rounds map[id.Round]api.RoundResult){
+		results := make([]byte, len(sr.rl.list))
 
-	reventObjs := make([]*ds.EventCallback, len(sr.rl.list))
+		for i, r := range sr.rl.list{
+			if result, exists := rounds[r]; exists{
+				results[i] = byte(result)
+			}
+		}
 
-	for i, r := range sr.rl.list {
-		reventObjs[i] = roundEvents.AddRoundEventChan(r, sendResults,
-			time.Duration(timeoutMS)*time.Millisecond, states.COMPLETED,
-			states.FAILED)
+		mdc.EventCallback(sr.mid.Marshal(), allRoundsSucceeded, timedOut, results)
 	}
 
-	go func() {
-		success, _, numTmeout := utility.TrackResults(sendResults, len(sr.rl.list))
-		if !success {
-			mdc.EventCallback(sr.mid[:], false, numTmeout > 0)
-		} else {
-			mdc.EventCallback(sr.mid[:], true, false)
-		}
-	}()
+	timeout := time.Duration(timeoutMS)*time.Millisecond
 
-	return newRoundListUnregister(sr.rl.list, reventObjs, roundEvents), nil
+	return c.api.GetRoundResults(sr.rl.list, timeout, f)
 }
 
 // Returns a user object from which all information about the current user
diff --git a/cmd/root.go b/cmd/root.go
index 6a20d202be592f7d2d50b826af9ec3ca14b69cb5..8dcdb2b3ab4a2cc169d5924b234f949f9a6664c0 100644
--- a/cmd/root.go
+++ b/cmd/root.go
@@ -134,16 +134,32 @@ var rootCmd = &cobra.Command{
 		for i := 0; i < sendCnt; i++ {
 			fmt.Printf("Sending to %s: %s\n", recipientID, msgBody)
 			var roundIDs []id.Round
+			var roundTimeout time.Duration
 			if unsafe {
 				roundIDs, err = client.SendUnsafe(msg,
 					paramsUnsafe)
+				roundTimeout = paramsUnsafe.Timeout
 			} else {
 				roundIDs, _, err = client.SendE2E(msg,
 					paramsE2E)
+				roundTimeout = paramsE2E.Timeout
 			}
 			if err != nil {
 				jww.FATAL.Panicf("%+v", err)
 			}
+
+			// Construct the callback function which prints out the rounds' results
+			f := func(allRoundsSucceeded, timedOut bool,
+				rounds map[id.Round]api.RoundResult) {
+				printRoundResults(allRoundsSucceeded, timedOut, rounds, roundIDs, msg)
+			}
+
+			// Have the client report back the round results
+			err = client.GetRoundResults(roundIDs, roundTimeout, f)
+			if err != nil {
+				jww.FATAL.Panicf("%+v", err)
+			}
+
 			jww.INFO.Printf("RoundIDs: %+v\n", roundIDs)
 			time.Sleep(sendDelay * time.Millisecond)
 		}
@@ -181,6 +197,46 @@ var rootCmd = &cobra.Command{
 	},
 }
 
+// Helper function which prints the round resuls
+func printRoundResults(allRoundsSucceeded, timedOut bool,
+	rounds map[id.Round]api.RoundResult, roundIDs []id.Round, msg message.Send) {
+
+	// Done as string slices for easy and human readable printing
+	successfulRounds := make([]string, 0)
+	failedRounds := make([]string, 0)
+	timedOutRounds := make([]string, 0)
+
+	for _, r := range roundIDs {
+		// Group all round reports into a category based on their
+		// result (successful, failed, or timed out)
+		if result, exists := rounds[r]; exists {
+			if result == api.Succeeded {
+				successfulRounds = append(successfulRounds, strconv.Itoa(int(r)))
+			} else if result == api.Failed {
+				failedRounds = append(failedRounds, strconv.Itoa(int(r)))
+			} else {
+				timedOutRounds = append(timedOutRounds, strconv.Itoa(int(r)))
+			}
+		}
+	}
+
+	jww.INFO.Printf("Result of sending message \"%s\" to \"%v\":",
+		msg.Payload, msg.Recipient)
+
+	// Print out all rounds results, if they are populated
+	if len(successfulRounds) > 0 {
+		jww.INFO.Printf("\tRound(s) %v successful", strings.Join(successfulRounds, ","))
+	}
+	if len(failedRounds) > 0 {
+		jww.ERROR.Printf("\tRound(s) %v failed", strings.Join(failedRounds, ","))
+	}
+	if len(timedOutRounds) > 0 {
+		jww.ERROR.Printf("\tRound(s) %v timed " +
+			"\n\tout (no network resolution could be found)", strings.Join(timedOutRounds, ","))
+	}
+
+}
+
 func createClient() *api.Client {
 	initLog(viper.GetBool("verbose"), viper.GetString("log"))
 	jww.INFO.Printf(Version())
@@ -582,8 +638,9 @@ func init() {
 		"Identity code (optional)")
 	viper.BindPFlag("regcode", rootCmd.Flags().Lookup("regcode"))
 
-	rootCmd.Flags().StringP("message", "m", "", "Message to send")
-	viper.BindPFlag("message", rootCmd.Flags().Lookup("message"))
+	rootCmd.PersistentFlags().StringP("message", "m", "",
+		"Message to send")
+	viper.BindPFlag("message", rootCmd.PersistentFlags().Lookup("message"))
 
 	rootCmd.Flags().UintP("sendid", "", 0,
 		"Use precanned user id (must be between 1 and 40, inclusive)")
diff --git a/cmd/single.go b/cmd/single.go
index e3c2ad639a5b7b0cd160d29023f06a407f0c2b61..a7c8fafcc17af98cd355bdbbc2de807fd2f48432 100644
--- a/cmd/single.go
+++ b/cmd/single.go
@@ -116,10 +116,6 @@ func init() {
 		"Path to contact file to send message to.")
 	_ = viper.BindPFlag("contact", singleCmd.Flags().Lookup("contact"))
 
-	singleCmd.Flags().StringP("message", "m", "",
-		"Message to send.")
-	_ = viper.BindPFlag("message", singleCmd.Flags().Lookup("message"))
-
 	singleCmd.Flags().Uint8("maxMessages", 1,
 		"The max number of single-use response messages.")
 	_ = viper.BindPFlag("maxMessages", singleCmd.Flags().Lookup("maxMessages"))
diff --git a/go.mod b/go.mod
index c83caf94f41f20efd27b7b383eb92a04fb947ce5..0792504a91217fe7d064204d8894e114382df4a5 100644
--- a/go.mod
+++ b/go.mod
@@ -17,7 +17,7 @@ require (
 	github.com/spf13/jwalterweatherman v1.1.0
 	github.com/spf13/viper v1.7.1
 	gitlab.com/elixxir/bloomfilter v0.0.0-20200930191214-10e9ac31b228
-	gitlab.com/elixxir/comms v0.0.4-0.20210217173658-d751b9c00e5a
+	gitlab.com/elixxir/comms v0.0.4-0.20210218234550-f2e03b19bdb2
 	gitlab.com/elixxir/crypto v0.0.7-0.20210216174551-f806f79610eb
 	gitlab.com/elixxir/ekv v0.1.4
 	gitlab.com/elixxir/primitives v0.0.3-0.20210216174458-2a23825c1eb1
diff --git a/go.sum b/go.sum
index 1d690892a15314e6af21a02b0f7d6f50122548a1..0d535ad56dce99718be355c34655e8d41088e32e 100644
--- a/go.sum
+++ b/go.sum
@@ -251,12 +251,12 @@ github.com/zeebo/pcg v1.0.0 h1:dt+dx+HvX8g7Un32rY9XWoYnd0NmKmrIzpHF7qiTDj0=
 github.com/zeebo/pcg v1.0.0/go.mod h1:09F0S9iiKrwn9rlI5yjLkmrug154/YRW6KnnXVDM/l4=
 gitlab.com/elixxir/bloomfilter v0.0.0-20200930191214-10e9ac31b228 h1:Gi6rj4mAlK0BJIk1HIzBVMjWNjIUfstrsXC2VqLYPcA=
 gitlab.com/elixxir/bloomfilter v0.0.0-20200930191214-10e9ac31b228/go.mod h1:H6jztdm0k+wEV2QGK/KYA+MY9nj9Zzatux/qIvDDv3k=
-gitlab.com/elixxir/comms v0.0.4-0.20210215180544-d672a6bfd53a h1:PrNMCPpAQQbSDlkHNBAIgjBWDVjTg7LDyPUlajGttUo=
-gitlab.com/elixxir/comms v0.0.4-0.20210215180544-d672a6bfd53a/go.mod h1:ZXv+fpI/kRCzxxX6p4JXlonJVDl49t4+v71kEkBipgM=
-gitlab.com/elixxir/comms v0.0.4-0.20210216174714-dc3e5c456e20 h1:XMKvdEDh/ThSowYroZ4PGY/eMNm1xV9ov4rf1UkgLCY=
-gitlab.com/elixxir/comms v0.0.4-0.20210216174714-dc3e5c456e20/go.mod h1:GCbfPWB7VF5ZeDsLBCwfy0JiquG4OK6gsRjaIS66+yg=
-gitlab.com/elixxir/comms v0.0.4-0.20210217173658-d751b9c00e5a h1:DYzWv9ffCssipLjwoCfe1zln7Mt+V155PY4rzAjflC0=
-gitlab.com/elixxir/comms v0.0.4-0.20210217173658-d751b9c00e5a/go.mod h1:GCbfPWB7VF5ZeDsLBCwfy0JiquG4OK6gsRjaIS66+yg=
+gitlab.com/elixxir/comms v0.0.4-0.20210210215855-f8a4b9751c59 h1:/MSWVvLFV8Z2H37l+9fC3z5aO/fXktwd3RKFT21dvcM=
+gitlab.com/elixxir/comms v0.0.4-0.20210210215855-f8a4b9751c59/go.mod h1:ZXv+fpI/kRCzxxX6p4JXlonJVDl49t4+v71kEkBipgM=
+gitlab.com/elixxir/comms v0.0.4-0.20210212194414-4c36bb47fa96 h1:66NNOMK9zQqW3xmGYll8wB/BKD5jMHOyYjpgwQD7b+Q=
+gitlab.com/elixxir/comms v0.0.4-0.20210212194414-4c36bb47fa96/go.mod h1:ZXv+fpI/kRCzxxX6p4JXlonJVDl49t4+v71kEkBipgM=
+gitlab.com/elixxir/comms v0.0.4-0.20210218234550-f2e03b19bdb2 h1:p5GunVi5sP9atTw3DKBkgV6k3eR9iTyI6m9GbUr8hhA=
+gitlab.com/elixxir/comms v0.0.4-0.20210218234550-f2e03b19bdb2/go.mod h1:GCbfPWB7VF5ZeDsLBCwfy0JiquG4OK6gsRjaIS66+yg=
 gitlab.com/elixxir/crypto v0.0.0-20200804182833-984246dea2c4 h1:28ftZDeYEko7xptCZzeFWS1Iam95dj46TWFVVlKmw6A=
 gitlab.com/elixxir/crypto v0.0.0-20200804182833-984246dea2c4/go.mod h1:ucm9SFKJo+K0N2GwRRpaNr+tKXMIOVWzmyUD0SbOu2c=
 gitlab.com/elixxir/crypto v0.0.3 h1:znCt/x2bL4y8czTPaaFkwzdgSgW3BJc/1+dxyf1jqVw=
@@ -295,8 +295,8 @@ gitlab.com/xx_network/primitives v0.0.2 h1:r45yKenJ9e7PylI1ZXJ1Es09oYNaYXjxVy9+u
 gitlab.com/xx_network/primitives v0.0.2/go.mod h1:cs0QlFpdMDI6lAo61lDRH2JZz+3aVkHy+QogOB6F/qc=
 gitlab.com/xx_network/primitives v0.0.4-0.20210210215152-e4534abf3ae5 h1:skLonqleopw1osf3xBQTNX9NjTV/OQP6AM4HKpLNUdc=
 gitlab.com/xx_network/primitives v0.0.4-0.20210210215152-e4534abf3ae5/go.mod h1:9imZHvYwNFobxueSvVtHneZLk9wTK7HQTzxPm+zhFhE=
-gitlab.com/xx_network/primitives v0.0.4-0.20210215191517-2f56b21d6ed7 h1:Q4zU46rz3zf+9q6syhA8rk4SS9TQwsk4ggUJ68IiKv8=
-gitlab.com/xx_network/primitives v0.0.4-0.20210215191517-2f56b21d6ed7/go.mod h1:9imZHvYwNFobxueSvVtHneZLk9wTK7HQTzxPm+zhFhE=
+gitlab.com/xx_network/primitives v0.0.4-0.20210212180522-50ec526a6c12 h1:dOQS9tzT4fllDhU37pbJhAAW8qlB0HDjuf74rvzOZQQ=
+gitlab.com/xx_network/primitives v0.0.4-0.20210212180522-50ec526a6c12/go.mod h1:9imZHvYwNFobxueSvVtHneZLk9wTK7HQTzxPm+zhFhE=
 gitlab.com/xx_network/primitives v0.0.4-0.20210215192713-e32335847d4f h1:0wFEYIHuPkWJuDkbDXNrwC5yGwkd7Mugt2BwcTqQbFY=
 gitlab.com/xx_network/primitives v0.0.4-0.20210215192713-e32335847d4f/go.mod h1:9imZHvYwNFobxueSvVtHneZLk9wTK7HQTzxPm+zhFhE=
 gitlab.com/xx_network/ring v0.0.2 h1:TlPjlbFdhtJrwvRgIg4ScdngMTaynx/ByHBRZiXCoL0=