diff --git a/pkg/solana/marshal_signed_int.go b/pkg/solana/marshal_signed_int.go
new file mode 100644
index 000000000..76a978776
--- /dev/null
+++ b/pkg/solana/marshal_signed_int.go
@@ -0,0 +1,67 @@
+// code from: https://github.com/smartcontractkit/chainlink-terra/blob/develop/pkg/terra/marshal_signed_int.go
+// will eventually be removed and replaced with a generalized version from libocr
+
+package solana
+
+import (
+	"bytes"
+	"fmt"
+	"math/big"
+)
+
+var i = big.NewInt
+
+func bounds(numBytes uint) (*big.Int, *big.Int) {
+	max := i(0).Sub(i(0).Lsh(i(1), numBytes*8-1), i(1)) // 2**(numBytes*8-1)- 1
+	min := i(0).Sub(i(0).Neg(max), i(1))                // -2**(numBytes*8-1)
+	return min, max
+}
+
+// ToBigInt interprets bytes s as a big-endian signed integer
+// of size numBytes.
+func ToBigInt(s []byte, numBytes uint) (*big.Int, error) {
+	if uint(len(s)) != numBytes {
+		return nil, fmt.Errorf("invalid int length: expected %d got %d", numBytes, len(s))
+	}
+	val := (&big.Int{}).SetBytes(s)
+	numBits := numBytes * 8
+	_, max := bounds(numBytes)
+	negative := val.Cmp(max) > 0
+	if negative {
+		// Get the complement wrt to 2^numBits
+		maxUint := big.NewInt(1)
+		maxUint.Lsh(maxUint, numBits)
+		val.Sub(maxUint, val)
+		val.Neg(val)
+	}
+	return val, nil
+}
+
+// ToBytes converts *big.Int o into bytes as a big-endian signed
+// integer of size numBytes
+func ToBytes(o *big.Int, numBytes uint) ([]byte, error) {
+	min, max := bounds(numBytes)
+	if o.Cmp(max) > 0 || o.Cmp(min) < 0 {
+		return nil, fmt.Errorf("value won't fit in int%v: 0x%x", numBytes*8, o)
+	}
+	negative := o.Sign() < 0
+	val := (&big.Int{})
+	numBits := numBytes * 8
+	if negative {
+		// compute two's complement as 2**numBits - abs(o) = 2**numBits + o
+		val.SetInt64(1)
+		val.Lsh(val, numBits)
+		val.Add(val, o)
+	} else {
+		val.Set(o)
+	}
+	b := val.Bytes() // big-endian representation of abs(val)
+	if uint(len(b)) > numBytes {
+		return nil, fmt.Errorf("b must fit in %v bytes", numBytes)
+	}
+	b = bytes.Join([][]byte{bytes.Repeat([]byte{0}, int(numBytes)-len(b)), b}, []byte{})
+	if uint(len(b)) != numBytes {
+		return nil, fmt.Errorf("wrong length; there must be an error in the padding of b: %v", b)
+	}
+	return b, nil
+}
diff --git a/pkg/solana/marshal_signed_int_test.go b/pkg/solana/marshal_signed_int_test.go
new file mode 100644
index 000000000..66e8dce10
--- /dev/null
+++ b/pkg/solana/marshal_signed_int_test.go
@@ -0,0 +1,186 @@
+package solana
+
+import (
+	"encoding/hex"
+	"math/big"
+	"testing"
+
+	"github.com/stretchr/testify/assert"
+	"github.com/stretchr/testify/require"
+)
+
+func TestMarshalSignedInt(t *testing.T) {
+	var tt = []struct {
+		bytesVal  string
+		size      uint
+		expected  *big.Int
+		expectErr bool
+	}{
+		{
+			"ffffffffffffffff",
+			8,
+			big.NewInt(-1),
+			false,
+		},
+		{
+			"fffffffffffffffe",
+			8,
+			big.NewInt(-2),
+			false,
+		},
+		{
+			"0000000000000000",
+			8,
+			big.NewInt(0),
+			false,
+		},
+		{
+			"0000000000000001",
+			8,
+			big.NewInt(1),
+			false,
+		},
+		{
+			"0000000000000002",
+			8,
+			big.NewInt(2),
+			false,
+		},
+		{
+			"7fffffffffffffff",
+			8,
+			big.NewInt(9223372036854775807), // 2^63 - 1
+			false,
+		},
+		{
+			"00000000000000000000000000000000",
+			16,
+			big.NewInt(0),
+			false,
+		},
+		{
+			"00000000000000000000000000000001",
+			16,
+			big.NewInt(1),
+			false,
+		},
+		{
+			"00000000000000000000000000000002",
+			16,
+			big.NewInt(2),
+			false,
+		},
+		{
+			"7fffffffffffffffffffffffffffffff", // 2^127 - 1
+			16,
+			big.NewInt(0).Sub(big.NewInt(0).Lsh(big.NewInt(1), 127), big.NewInt(1)),
+			false,
+		},
+		{
+			"ffffffffffffffffffffffffffffffff",
+			16,
+			big.NewInt(-1),
+			false,
+		},
+		{
+			"fffffffffffffffffffffffffffffffe",
+			16,
+			big.NewInt(-2),
+			false,
+		},
+		{
+			"000000000000000000000000000000000000000000000000",
+			24,
+			big.NewInt(0),
+			false,
+		},
+		{
+			"000000000000000000000000000000000000000000000001",
+			24,
+			big.NewInt(1),
+			false,
+		},
+		{
+			"000000000000000000000000000000000000000000000002",
+			24,
+			big.NewInt(2),
+			false,
+		},
+		{
+			"ffffffffffffffffffffffffffffffffffffffffffffffff",
+			24,
+			big.NewInt(-1),
+			false,
+		},
+		{
+			"fffffffffffffffffffffffffffffffffffffffffffffffe",
+			24,
+			big.NewInt(-2),
+			false,
+		},
+	}
+	for _, tc := range tt {
+		tc := tc
+		b, err := hex.DecodeString(tc.bytesVal)
+		require.NoError(t, err)
+		i, err := ToBigInt(b, tc.size)
+		require.NoError(t, err)
+		assert.Equal(t, i.String(), tc.expected.String())
+
+		// Marshalling back should give us the same bytes
+		bAfter, err := ToBytes(i, tc.size)
+		require.NoError(t, err)
+		assert.Equal(t, tc.bytesVal, hex.EncodeToString(bAfter))
+	}
+
+	var tt2 = []struct {
+		o         *big.Int
+		numBytes  uint
+		expectErr bool
+	}{
+		{
+			big.NewInt(128),
+			1,
+			true,
+		},
+		{
+			big.NewInt(-129),
+			1,
+			true,
+		},
+		{
+			big.NewInt(-128),
+			1,
+			false,
+		},
+		{
+			big.NewInt(2147483648),
+			4,
+			true,
+		},
+		{
+			big.NewInt(2147483647),
+			4,
+			false,
+		},
+		{
+			big.NewInt(-2147483649),
+			4,
+			true,
+		},
+		{
+			big.NewInt(-2147483648),
+			4,
+			false,
+		},
+	}
+	for _, tc := range tt2 {
+		tc := tc
+		_, err := ToBytes(tc.o, tc.numBytes)
+		if tc.expectErr {
+			require.Error(t, err)
+		} else {
+			require.NoError(t, err)
+		}
+	}
+}
diff --git a/pkg/solana/report.go b/pkg/solana/report.go
index f28e7ac89..6b67bea61 100644
--- a/pkg/solana/report.go
+++ b/pkg/solana/report.go
@@ -7,6 +7,7 @@ import (
 	"math/big"
 	"sort"
 
+	"github.com/pkg/errors"
 	"github.com/smartcontractkit/libocr/offchainreporting2/chains/evmutil"
 	"github.com/smartcontractkit/libocr/offchainreporting2/reportingplugin/median"
 	"github.com/smartcontractkit/libocr/offchainreporting2/types"
@@ -62,11 +63,19 @@ func (c ReportCodec) BuildReport(oo []median.ParsedAttributedObservation) (types
 
 	report = append(report, observers[:]...)
 
-	mBytes := make([]byte, MedianLen)
-	report = append(report, median.FillBytes(mBytes)[:]...)
+	// TODO: replace with generalized function from libocr
+	medianBytes, err := ToBytes(median, uint(MedianLen))
+	if err != nil {
+		return nil, errors.Wrap(err, "error in ToBytes(median)")
+	}
+	report = append(report, medianBytes[:]...)
 
-	jBytes := make([]byte, JuelsLen)
-	report = append(report, juelsPerFeeCoin.FillBytes(jBytes)[:]...)
+	// TODO: replace with generalized function from libocr
+	juelsPerFeeCoinBytes, err := ToBytes(juelsPerFeeCoin, uint(JuelsLen))
+	if err != nil {
+		return nil, errors.Wrap(err, "error in ToBytes(juelsPerFeeCoin)")
+	}
+	report = append(report, juelsPerFeeCoinBytes[:]...)
 
 	return types.Report(report), nil
 }
@@ -81,7 +90,7 @@ func (c ReportCodec) MedianFromReport(report types.Report) (*big.Int, error) {
 	start := 4 + 1 + 32
 	end := start + int(MedianLen)
 	median := report[start:end]
-	return big.NewInt(0).SetBytes(median), nil
+	return ToBigInt(median, uint(MedianLen))
 }
 
 // Create report digest using SHA256 hash fn
diff --git a/pkg/solana/report_test.go b/pkg/solana/report_test.go
index be71b9202..3cdc4f900 100644
--- a/pkg/solana/report_test.go
+++ b/pkg/solana/report_test.go
@@ -2,10 +2,12 @@ package solana
 
 import (
 	"encoding/binary"
+	"math"
 	"math/big"
 	"testing"
 	"time"
 
+	bin "github.com/gagliardetto/binary"
 	"github.com/smartcontractkit/libocr/commontypes"
 	"github.com/smartcontractkit/libocr/offchainreporting2/reportingplugin/median"
 	"github.com/smartcontractkit/libocr/offchainreporting2/types"
@@ -108,3 +110,63 @@ func TestHashReport(t *testing.T) {
 	assert.NoError(t, err)
 	assert.Equal(t, mockHash, h)
 }
+
+func TestNegativeMedianValue(t *testing.T) {
+	c := ReportCodec{}
+	oo := []median.ParsedAttributedObservation{
+		median.ParsedAttributedObservation{
+			Timestamp:       uint32(time.Now().Unix()),
+			Value:           big.NewInt(-2),
+			JuelsPerFeeCoin: big.NewInt(1),
+			Observer:        commontypes.OracleID(0),
+		},
+	}
+
+	// create report
+	report, err := c.BuildReport(oo)
+	assert.NoError(t, err)
+
+	// check report properly encoded negative number
+	index := 4 + 1 + 32
+	var medianFromRaw bin.Int128
+	medianBytes := make([]byte, MedianLen)
+	copy(medianBytes, report[index:index+int(MedianLen)])
+	// flip order: bin decoder parses from little endian
+	for i, j := 0, len(medianBytes)-1; i < j; i, j = i+1, j-1 {
+		medianBytes[i], medianBytes[j] = medianBytes[j], medianBytes[i]
+	}
+	bin.NewBinDecoder(medianBytes).Decode(&medianFromRaw)
+	assert.True(t, oo[0].Value.Cmp(medianFromRaw.BigInt()) == 0, "median observation in raw report does not match")
+
+	// check report can be parsed properly with a negative number
+	res, err := c.MedianFromReport(report)
+	assert.NoError(t, err)
+	assert.True(t, oo[0].Value.Cmp(res) == 0)
+}
+
+func TestReportHandleOverflow(t *testing.T) {
+	// too large observation should not cause panic
+	c := ReportCodec{}
+	oo := []median.ParsedAttributedObservation{
+		median.ParsedAttributedObservation{
+			Timestamp:       uint32(time.Now().Unix()),
+			Value:           big.NewInt(0).Lsh(big.NewInt(1), 127), // 1<<127
+			JuelsPerFeeCoin: big.NewInt(0),
+			Observer:        commontypes.OracleID(0),
+		},
+	}
+	_, err := c.BuildReport(oo)
+	assert.Error(t, err)
+
+	// too large juelsPerFeeCoin should not cause panic
+	oo = []median.ParsedAttributedObservation{
+		median.ParsedAttributedObservation{
+			Timestamp:       uint32(time.Now().Unix()),
+			Value:           big.NewInt(0),
+			JuelsPerFeeCoin: big.NewInt(0).Add(big.NewInt(math.MaxInt64), big.NewInt(1)),
+			Observer:        commontypes.OracleID(0),
+		},
+	}
+	_, err = c.BuildReport(oo)
+	assert.Error(t, err)
+}