diff --git a/test/hub/PathTransferHub.t.sol b/test/hub/PathTransferHub.t.sol
index a101929..ce0c56c 100644
--- a/test/hub/PathTransferHub.t.sol
+++ b/test/hub/PathTransferHub.t.sol
@@ -5,6 +5,7 @@ import {Test} from "forge-std/Test.sol";
 import {StdCheats} from "forge-std/StdCheats.sol";
 import "forge-std/console.sol";
 import "../../src/hub/Hub.sol";
+import "../../src/hub/TypeDefinitions.sol";
 import "../setup/TimeCirclesSetup.sol";
 import "../setup/HumanRegistration.sol";
 import "../utils/Approximation.sol";
@@ -84,12 +85,12 @@ contract HubPathTransferTest is Test, TimeCirclesSetup, HumanRegistration, Appro
         // C-D   .     .    -5C    5C
 
         address[] memory flowVertices = new address[](M);
-        Hub.FlowEdge[] memory flow = new Hub.FlowEdge[](M - 1);
+        TypeDefinitions.FlowEdge[] memory flow = new Hub.FlowEdge[](M - 1);
 
         // allocate three coordinates per flow edge
         uint16[] memory coordinates = new uint16[]((M - 1) * 3);
 
-        // the flow vertices need to be provided in ascending order\
+        // the flow vertices need to be provided in ascending order
         for (uint256 i = 0; i < M; i++) {
             flowVertices[i] = sortedAddresses[i];
         }
@@ -121,7 +122,7 @@ contract HubPathTransferTest is Test, TimeCirclesSetup, HumanRegistration, Appro
         bytes memory packedCoordinates = packCoordinates(coordinates);
 
         // Lastly we need to define the streams (only one from Alice to David)
-        Hub.Stream[] memory streams = new Hub.Stream[](1);
+        TypeDefinitions.Stream[] memory streams = new Hub.Stream[](1);
         // the source coordinate for Alice
         streams[0].sourceCoordinate = lookupMap[0];
         // the flow edges that constitute the termination of this stream
diff --git a/test/operators/SignedPathOperator.t.so b/test/operators/SignedPathOperator.t.so
new file mode 100644
index 0000000..2237f55
--- /dev/null
+++ b/test/operators/SignedPathOperator.t.so
@@ -0,0 +1,206 @@
+// SPDX-License-Identifier: AGPL-3.0-only
+pragma solidity >=0.8.24;
+
+import {Test} from "forge-std/Test.sol";
+import {StdCheats} from "forge-std/StdCheats.sol";
+import "forge-std/console.sol";
+import "../../src/hub/TypeDefinitions.sol";
+import "../../src/operators/SignedPathOperator.sol";
+import "../hub/MockDeployment.sol";
+import "../setup/TimeCirclesSetup.sol";
+import "../setup/HumanRegistration.sol";
+import "./TrustGraph.sol";
+
+contract SignedPathOperatorTest is Test, TimeCirclesSetup, HumanRegistration, TrustGraph {
+    // Constants
+
+    uint96 expiry = type(uint96).max;
+
+    // State variables
+
+    SignedPathOperator public operator;
+    MockHub public hub;
+    MockDeployment public deployment;
+
+    // Constructor
+
+    constructor() HumanRegistration(20) {}
+
+    // Setup
+
+    function setUp() public {
+        // set time to 10 december 2021
+        startTime();
+
+        // deploy all contracts and the signed path operator
+        deployment = new MockDeployment(INFLATION_DAY_ZERO, 365 days);
+        hub = deployment.hub();
+        operator = new SignedPathOperator(IHubV2(address(hub)));
+
+        // register 20 humans
+        for (uint256 i = 0; i < N; i++) {
+            vm.prank(addresses[i]);
+            hub.registerHumanUnrestricted();
+            assertEq(deployment.hub().isTrusted(addresses[i], addresses[i]), true);
+        }
+        // skip time to claim Circles
+        skipTime(14 days + 1 minutes);
+        for (uint256 i = 0; i < N; i++) {
+            vm.prank(addresses[i]);
+            hub.personalMintWithoutV1Check();
+        }
+
+        // diagram of the trust graph (all doubly connected)
+
+        // 1---2---3       8---9
+        // | \ | / |       | / |
+        // 12  4---5---7---6---10
+        // | / |   |       | \ |
+        // 11--13  14      15--16
+        // |       |       |   |
+        // 20------17------18--19
+        _setTrustGraph();
+
+
+    }
+
+    // Tests
+
+    function testSingleSignedPath() public {
+        // Alice is node 11; Bob is node 14
+        address alice = addresses[11];
+
+        // only Alice authorizes the operator to execute a signed path on her behalf.
+        vm.prank(alice);
+        hub.setApprovalForAll(address(operator), true);
+
+        (address[] memory flowVertices,
+        TypeDefinitions.FlowEdge[] memory flowEdges,
+        TypeDefinitions.Stream[] memory streams,
+        bytes memory packedCoordinates) = _setupExplicitFlowMatrix01();
+
+        vm.prank(alice);
+        operator.operateSignedFlowMatrix(flowVertices, flowEdges, streams, packedCoordinates, lookupMap[11]);
+    }
+
+    // Internal functions
+
+    function _setupExplicitFlowMatrix01()
+        internal view
+        returns (address[] memory, TypeDefinitions.FlowEdge[] memory, TypeDefinitions.Stream[] memory, bytes memory)
+    {
+        uint256 M = 7;
+
+        // make it max width even if we dont touch all nodes
+        address[] memory flowVertices = new address[](N);
+        // allocate memory for some flow edges already
+        TypeDefinitions.FlowEdge[] memory flowEdges = new TypeDefinitions.FlowEdge[](M);
+
+        uint16[] memory coordinates = new uint16[](M * 3);
+
+        // the flow vertices need to be sorted
+        for (uint256 i = 0; i < N; i++) {
+            flowVertices[i] = sortedAddresses[i];
+        }
+
+        // todo: figure out how to effectively define advanced flow matrices programatically;
+
+        uint256 index = 0;
+
+        uint256 j = P1.length - 1;
+        // Alice -> Bob along P1
+        for (uint256 i = 0; i < j; i++) {
+            flowEdges[i] = TypeDefinitions.FlowEdge(uint16(i), uint240(40 * CRC));
+            flowEdges[i].streamSinkId = uint16(0);
+            coordinates[index++] = lookupMap[P1[i]]; // CRC
+            coordinates[index++] = lookupMap[P1[i]]; // source
+            coordinates[index++] = lookupMap[P1[i + 1]]; // receiver
+        }
+
+        // Alice -> Bob along P2; continue counting with index
+        for (uint256 i = j; i < j + P2.length - 1; i++) {
+            flowEdges[i] = TypeDefinitions.FlowEdge(uint16(i), uint240(60 * CRC));
+            flowEdges[i].streamSinkId = uint16(0);
+            coordinates[index++] = lookupMap[P2[i]]; // CRC
+            coordinates[index++] = lookupMap[P2[i]]; // source
+            coordinates[index++] = lookupMap[P2[i + 1]]; // receiver
+        }
+        // set the terminal edges to Bob to refer to the first stream (indexed at 1)
+        flowEdges[j - 1].streamSinkId = uint16(1);
+        flowEdges[j + P2.length - 2].streamSinkId = uint16(1);
+
+        // pack the coordinates
+        bytes memory packedCoordinates = packCoordinates(coordinates);
+
+        TypeDefinitions.Stream[] memory streams = new TypeDefinitions.Stream[](1);
+        streams[0].sourceCoordinate = lookupMap[11];
+        streams[0].flowEdgeIds = new uint16[](2);
+        streams[0].flowEdgeIds[0] = uint16(j - 1);
+        streams[0].flowEdgeIds[1] = uint16(j + P2.length - 2);
+        streams[0].data = new bytes(0);
+
+        return (flowVertices, flowEdges, streams, packedCoordinates);
+    }
+
+    function _setTrustGraph() internal {
+        _linearlyDoubleConnect20(L1);
+        _linearlyDoubleConnect7(L2);
+        _fullyConnect3(F1);
+        _fullyConnect3(F2);
+        _fullyConnect3(F3);
+        _fullyConnect3(F4);
+        _fullyConnect3(F5);
+    }
+
+    function _fullyConnect3(uint256[3] memory _list) internal {
+        for (uint256 i = 0; i < _list.length; i++) {
+            for (uint256 j = 0; j < _list.length; j++) {
+                if (i != j) {
+                    vm.prank(addresses[_list[i]]);
+                    hub.trust(addresses[_list[j]], expiry);
+                }
+            }
+        }
+    }
+
+    function _linearlySingleConnect(uint256[] memory _list) internal {
+        for (uint256 i = 0; i < _list.length - 1; i++) {
+            vm.prank(addresses[_list[i]]);
+            hub.trust(addresses[_list[i + 1]], expiry);
+        }
+    }
+
+    function _linearlyDoubleConnect20(uint256[20] memory _list) internal {
+        for (uint256 i = 0; i < _list.length - 1; i++) {
+            vm.prank(addresses[_list[i]]);
+            hub.trust(addresses[_list[i + 1]], expiry);
+            vm.prank(addresses[_list[i + 1]]);
+            hub.trust(addresses[_list[i]], expiry);
+        }
+    }
+
+    function _linearlyDoubleConnect7(uint256[7] memory _list) internal {
+        for (uint256 i = 0; i < _list.length - 1; i++) {
+            vm.prank(addresses[_list[i]]);
+            hub.trust(addresses[_list[i + 1]], expiry);
+            vm.prank(addresses[_list[i + 1]]);
+            hub.trust(addresses[_list[i]], expiry);
+        }
+    }
+
+    // duplicated from test/hub/PathTransferHub.t.sol
+    /**
+     * @dev Packs an array of uint16 coordinates into bytes.
+     * Each coordinate is represented as 16 bits (2 bytes).
+     * @param _coordinates The array of uint16 coordinates.
+     * @return packedData_ The packed coordinates as bytes.
+     */
+    function packCoordinates(uint16[] memory _coordinates) private pure returns (bytes memory packedData_) {
+        packedData_ = new bytes(_coordinates.length * 2);
+
+        for (uint256 i = 0; i < _coordinates.length; i++) {
+            packedData_[2 * i] = bytes1(uint8(_coordinates[i] >> 8)); // High byte
+            packedData_[2 * i + 1] = bytes1(uint8(_coordinates[i] & 0xFF)); // Low byte
+        }
+    }
+}
diff --git a/test/operators/SignedPathOperator.t.sol b/test/operators/SignedPathOperator.t.sol
deleted file mode 100644
index 51798f3..0000000
--- a/test/operators/SignedPathOperator.t.sol
+++ /dev/null
@@ -1,113 +0,0 @@
-// SPDX-License-Identifier: AGPL-3.0-only
-pragma solidity >=0.8.24;
-
-import {Test} from "forge-std/Test.sol";
-import {StdCheats} from "forge-std/StdCheats.sol";
-import "forge-std/console.sol";
-import "../../src/operators/SignedPathOperator.sol";
-import "../hub/MockDeployment.sol";
-import "../setup/TimeCirclesSetup.sol";
-import "../setup/HumanRegistration.sol";
-import "./TrustGraph.sol";
-
-contract SignedPathOperatorTest is Test, TimeCirclesSetup, HumanRegistration, TrustGraph {
-    // Constants
-
-    uint96 expiry = type(uint96).max;
-
-    // State variables
-
-    SignedPathOperator public operator;
-    MockHub public hub;
-    MockDeployment public deployment;
-
-    // Constructor
-
-    constructor() HumanRegistration(20) {}
-
-    // Setup
-
-    function setUp() public {
-        // set time to 10 december 2021
-        startTime();
-
-        // deploy all contracts and the signed path operator
-        deployment = new MockDeployment(INFLATION_DAY_ZERO, 365 days);
-        hub = deployment.hub();
-        operator = new SignedPathOperator(IHubV2(address(hub)));
-
-        // register 20 humans
-        for (uint256 i = 0; i < N; i++) {
-            vm.prank(addresses[i]);
-            deployment.hub().registerHumanUnrestricted();
-            assertEq(deployment.hub().isTrusted(addresses[i], addresses[i]), true);
-        }
-        // skip time to claim Circles
-        skipTime(14 days + 1 minutes);
-        for (uint256 i = 0; i < N; i++) {
-            vm.prank(addresses[i]);
-            hub.personalMintWithoutV1Check();
-        }
-
-        // diagram of the trust graph (all doubly connected)
-
-        // 1---2---3       8---9
-        // | \ | / |       | / |
-        // 12  4---5---7---6---10
-        // | / |   |       | \ |
-        // 11--13  14      15--16
-        // |       |       |   |
-        // 20------17------18--19
-        _setTrustGraph();
-    }
-
-    // Tests
-
-    // Internal functions
-
-    function _setTrustGraph() internal {
-        _linearlyDoubleConnect20(L1);
-        _linearlyDoubleConnect7(L2);
-        _fullyConnect3(F1);
-        _fullyConnect3(F2);
-        _fullyConnect3(F3);
-        _fullyConnect3(F4);
-        _fullyConnect3(F5);
-    }
-
-    function _fullyConnect3(uint256[3] memory _list) internal {
-        for (uint256 i = 0; i < _list.length; i++) {
-            for (uint256 j = 0; j < _list.length; j++) {
-                if (i != j) {
-                    vm.prank(addresses[_list[i]]);
-                    hub.trust(addresses[_list[j]], expiry);
-                }
-            }
-        }
-    }
-
-    function _linearlySingleConnect(uint256[] memory _list) internal {
-        for (uint256 i = 0; i < _list.length - 1; i++) {
-            vm.prank(addresses[_list[i]]);
-            hub.trust(addresses[_list[i + 1]], expiry);
-        }
-    }
-
-    function _linearlyDoubleConnect20(uint256[20] memory _list) internal {
-        for (uint256 i = 0; i < _list.length - 1; i++) {
-            vm.prank(addresses[_list[i]]);
-            hub.trust(addresses[_list[i + 1]], expiry);
-            vm.prank(addresses[_list[i + 1]]);
-            hub.trust(addresses[_list[i]], expiry);
-        }
-    }
-
-    function _linearlyDoubleConnect7(uint256[7] memory _list) internal {
-        for (uint256 i = 0; i < _list.length - 1; i++) {
-            vm.prank(addresses[_list[i]]);
-            hub.trust(addresses[_list[i + 1]], expiry);
-            vm.prank(addresses[_list[i + 1]]);
-            hub.trust(addresses[_list[i]], expiry);
-        }
-    }
-}
diff --git a/test/operators/TrustGraph.sol b/test/operators/TrustGraph.sol
index c4baf34..81f04b2 100644
--- a/test/operators/TrustGraph.sol
+++ b/test/operators/TrustGraph.sol
@@ -16,13 +16,33 @@ contract TrustGraph {
     // |       |       |   |
     // 20------17------18--19
 
+    // should be indexed from zero;
+
+    // // contours
+    // uint256[20] internal L1 = [1, 2, 3, 5, 7, 6, 8, 9, 10, 16, 19, 18, 17, 20, 11, 12];
+    // uint256[7] internal L2 = [5, 7, 6, 15, 18, 17, 14];
+    // // inside clusters
+    // uint256[3] internal F1 = [1, 2, 4];
+    // uint256[3] internal F2 = [4, 3, 5];
+    // uint256[3] internal F3 = [11, 13, 4];
+    // uint256[3] internal F4 = [10, 9, 6];
+    // uint256[3] internal F5 = [6, 15, 16];
+
+    // // define paths from Alice (11) to Bob (14)
+    // uint256[4] internal P1 = [11, 4, 5, 14];
+    // uint256[5] internal P2 = [11, 13, 4, 5, 14];
+
     // contours
-    uint256[20] internal L1 = [1, 2, 3, 5, 7, 6, 8, 9, 10, 16, 19, 18, 17, 20, 11, 12];
-    uint256[7] internal L2 = [5, 7, 6, 15, 18, 17, 14];
+    uint256[20] internal L1 = [0, 1, 2, 4, 6, 5, 7, 8, 9, 15, 18, 17, 16, 19, 10, 11];
+    uint256[7] internal L2 = [4, 6, 5, 14, 17, 16, 13];
     // inside clusters
-    uint256[3] internal F1 = [1, 2, 4];
-    uint256[3] internal F2 = [4, 3, 5];
-    uint256[3] internal F3 = [11, 13, 4];
-    uint256[3] internal F4 = [10, 9, 6];
-    uint256[3] internal F5 = [6, 15, 16];
+    uint256[3] internal F1 = [0, 1, 3];
+    uint256[3] internal F2 = [3, 2, 4];
+    uint256[3] internal F3 = [10, 12, 3];
+    uint256[3] internal F4 = [9, 8, 5];
+    uint256[3] internal F5 = [5, 14, 15];
+
+    // define paths from Alice (11) to Bob (14)
+    uint256[4] internal P1 = [10, 3, 4, 13];
+    uint256[5] internal P2 = [10, 12, 3, 4, 13];
 }