diff --git a/cashtab/src/wallet/fixtures/vectors.js b/cashtab/src/wallet/fixtures/vectors.js
new file mode 100644
--- /dev/null
+++ b/cashtab/src/wallet/fixtures/vectors.js
@@ -0,0 +1,55 @@
+export default {
+    getBalanceSatsVectors: {
+        expectedReturns: [
+            {
+                description: 'Kind of a normal balance calculation',
+                nonSlpUtxos: [
+                    { value: '546' },
+                    { value: '150000000' },
+                    { value: '62500000' },
+                ],
+                balanceSats: 212500546,
+            },
+            {
+                description: 'Wallet balance of total XEC supply',
+                nonSlpUtxos: [
+                    { value: '700000000000000' },
+                    { value: '700000000000000' },
+                    { value: '700000000000000' },
+                ],
+                balanceSats: 2100000000000000,
+            },
+            {
+                description: 'Empty array returns 0 balance',
+                nonSlpUtxos: [],
+                balanceSats: 0,
+            },
+            {
+                description:
+                    'Array containing valid and invalid chronik utxos returns NaN',
+                nonSlpUtxos: [
+                    { noValueKey: '546' },
+                    { value: { thisKeyIsNotAString: 500 } },
+                    { value: '62500000' },
+                ],
+                balanceSats: NaN,
+            },
+            {
+                description:
+                    'Array containing invalid chronik utxos returns NaN',
+                nonSlpUtxos: [
+                    { noValueKey: '546' },
+                    { value: { thisKeyIsNotAString: 500 } },
+                ],
+                balanceSats: NaN,
+            },
+        ],
+        expectedErrors: [
+            {
+                description: 'Call with non-Array',
+                nonSlpUtxos: { somekey: 'an object instead of an array' },
+                errorMsg: 'nonSlpUtxos.reduce is not a function',
+            },
+        ],
+    },
+};
diff --git a/cashtab/src/wallet/index.js b/cashtab/src/wallet/index.js
new file mode 100644
--- /dev/null
+++ b/cashtab/src/wallet/index.js
@@ -0,0 +1,16 @@
+/**
+ * Get total value of satoshis associated with an array of chronik utxos
+ * @param {array} nonSlpUtxos array of chronik utxos
+ * (each is an object with an integer as a string
+ * stored at 'value' key representing associated satoshis)
+ * e.g. {value: '12345'}
+ * @throws {error} if nonSlpUtxos does not have a .reduce method
+ * @returns {number | NaN} integer, total balance of input utxos in satoshis
+ * or NaN if any utxo is invalid
+ */
+export const getBalanceSats = nonSlpUtxos => {
+    return nonSlpUtxos.reduce(
+        (previousBalance, utxo) => previousBalance + parseInt(utxo.value),
+        0,
+    );
+};
diff --git a/cashtab/src/wallet/index.test.js b/cashtab/src/wallet/index.test.js
new file mode 100644
--- /dev/null
+++ b/cashtab/src/wallet/index.test.js
@@ -0,0 +1,18 @@
+import { getBalanceSats } from 'wallet';
+import vectors from './fixtures/vectors';
+
+describe('Calculates total balance in satoshis from a valid set of chronik utxos', () => {
+    const { expectedReturns, expectedErrors } = vectors.getBalanceSatsVectors;
+    expectedReturns.forEach(expectedReturn => {
+        const { description, nonSlpUtxos, balanceSats } = expectedReturn;
+        it(`getBalanceSats: ${description}`, () => {
+            expect(getBalanceSats(nonSlpUtxos)).toBe(balanceSats);
+        });
+    });
+    expectedErrors.forEach(expectedError => {
+        const { description, nonSlpUtxos, errorMsg } = expectedError;
+        it(`getBalanceSats throws error for: ${description}`, () => {
+            expect(() => getBalanceSats(nonSlpUtxos)).toThrow(errorMsg);
+        });
+    });
+});