[
    {
        "id": "8ee174a8a298654a",
        "type": "tab",
        "label": "Flow 1",
        "disabled": false,
        "info": ""
    },
    {
        "id": "9318cc15f9afd75f",
        "type": "group",
        "z": "8ee174a8a298654a",
        "name": "unitsChanged v1",
        "style": {
            "label": true
        },
        "nodes": [
            "e1f6f9ae2e6910d1",
            "27566d8948e738f9",
            "c3c0627c1de5e349",
            "caada626130afaf6",
            "ac42d7e5c6032791",
            "65d289142a86384f",
            "6ca11a0d57bdc6cc",
            "b8d668ba7a1a03ed",
            "ca32fbaad3ee3347",
            "e9eb48b870d28564",
            "d4403c151c10fd53",
            "98a4076a7346f77b"
        ],
        "x": 2074,
        "y": 119,
        "w": 1032,
        "h": 262
    },
    {
        "id": "35ba395e82c0b1fd",
        "type": "group",
        "z": "8ee174a8a298654a",
        "name": "No protocol (old message format)",
        "style": {
            "label": true
        },
        "nodes": [
            "126dbca226762933",
            "58dda468b6e90876",
            "be6ed27e6faa08cc",
            "56fd2cd0cc0812f8"
        ],
        "x": 74,
        "y": 399,
        "w": 892,
        "h": 82
    },
    {
        "id": "04c09f0c2cd6afcc",
        "type": "group",
        "z": "8ee174a8a298654a",
        "name": "Protocol version 1",
        "style": {
            "label": true
        },
        "nodes": [
            "fb43395da3eb18ac",
            "746c2dd32cd8fa7f",
            "af129b9f346b5e93",
            "0bdc014d7d7e836a",
            "a986536a19a878b8",
            "4d6a02a638fceb91"
        ],
        "x": 74,
        "y": 559,
        "w": 832,
        "h": 122
    },
    {
        "id": "a407a49de3d419a0",
        "type": "sqlite-config",
        "name": "",
        "dbLocation": ""
    },
    {
        "id": "cbe7827e8b1ce3bd",
        "type": "function",
        "z": "8ee174a8a298654a",
        "name": "Restart source",
        "func": "node.warn('Force resetting source')\nreturn {\n    ...msg,\n    topic: 'force-reset'\n}",
        "outputs": 1,
        "timeout": 0,
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "libs": [],
        "x": 280,
        "y": 220,
        "wires": [
            [
                "8308f574539280d2"
            ]
        ]
    },
    {
        "id": "39c8b0b71a53b058",
        "type": "link in",
        "z": "8ee174a8a298654a",
        "name": "Handle error IN",
        "links": [
            "41758cdba3bbaddc",
            "5c9c86e7123f5da9"
        ],
        "x": 125,
        "y": 220,
        "wires": [
            [
                "cbe7827e8b1ce3bd"
            ]
        ]
    },
    {
        "id": "e6aebb3547ebbd29",
        "type": "link in",
        "z": "8ee174a8a298654a",
        "name": "Finished processing IN",
        "links": [
            "27566d8948e738f9",
            "e1f6f9ae2e6910d1",
            "e16ab80308b97075"
        ],
        "x": 125,
        "y": 280,
        "wires": [
            [
                "a8f1dc8b2b511da8"
            ]
        ]
    },
    {
        "id": "a8f1dc8b2b511da8",
        "type": "function",
        "z": "8ee174a8a298654a",
        "name": "Finished processing",
        "func": "node.warn('Finished processing')\nreturn {\n    ...msg,\n    topic: 'finished-processing'\n}",
        "outputs": 1,
        "timeout": 0,
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "libs": [],
        "x": 280,
        "y": 280,
        "wires": [
            [
                "8308f574539280d2"
            ]
        ]
    },
    {
        "id": "e1f6f9ae2e6910d1",
        "type": "link out",
        "z": "8ee174a8a298654a",
        "g": "9318cc15f9afd75f",
        "name": "Finished processing correct OUT",
        "mode": "link",
        "links": [
            "e6aebb3547ebbd29"
        ],
        "x": 3065,
        "y": 260,
        "wires": []
    },
    {
        "id": "27566d8948e738f9",
        "type": "link out",
        "z": "8ee174a8a298654a",
        "g": "9318cc15f9afd75f",
        "name": "Finished processing wrong tx OUT",
        "mode": "link",
        "links": [
            "e6aebb3547ebbd29"
        ],
        "x": 3065,
        "y": 300,
        "wires": []
    },
    {
        "id": "c3c0627c1de5e349",
        "type": "comment",
        "z": "8ee174a8a298654a",
        "g": "9318cc15f9afd75f",
        "name": "Validation failed",
        "info": "",
        "x": 2480,
        "y": 340,
        "wires": []
    },
    {
        "id": "caada626130afaf6",
        "type": "comment",
        "z": "8ee174a8a298654a",
        "g": "9318cc15f9afd75f",
        "name": "Validation succeeded",
        "info": "",
        "x": 2500,
        "y": 220,
        "wires": []
    },
    {
        "id": "ac42d7e5c6032791",
        "type": "voting-marketplace",
        "z": "8ee174a8a298654a",
        "g": "9318cc15f9afd75f",
        "name": "",
        "workerAddress": "",
        "indexerUrl": "",
        "solutionNamespace": "",
        "x": 2500,
        "y": 260,
        "wires": [
            [
                "ca32fbaad3ee3347"
            ]
        ]
    },
    {
        "id": "65d289142a86384f",
        "type": "voting-marketplace",
        "z": "8ee174a8a298654a",
        "g": "9318cc15f9afd75f",
        "name": "",
        "workerAddress": "",
        "indexerUrl": "",
        "solutionNamespace": "",
        "x": 2500,
        "y": 300,
        "wires": [
            [
                "27566d8948e738f9"
            ]
        ]
    },
    {
        "id": "8308f574539280d2",
        "type": "source-http-api",
        "z": "8ee174a8a298654a",
        "name": "",
        "host": "",
        "appId": "ggp",
        "sqliteConfig": "a407a49de3d419a0",
        "x": 520,
        "y": 220,
        "wires": [
            [
                "737cc9c7a540ab89"
            ]
        ]
    },
    {
        "id": "737cc9c7a540ab89",
        "type": "switch",
        "z": "8ee174a8a298654a",
        "name": "Protocol check",
        "property": "payload.protocolVersion",
        "propertyType": "msg",
        "rules": [
            {
                "t": "eq",
                "v": "1",
                "vt": "str"
            },
            {
                "t": "istype",
                "v": "undefined",
                "vt": "undefined"
            },
            {
                "t": "else"
            }
        ],
        "checkall": "true",
        "repair": false,
        "outputs": 3,
        "x": 760,
        "y": 220,
        "wires": [
            [
                "a487a21a1443f6b7"
            ],
            [
                "42fe81814bb0c41b"
            ],
            [
                "90f75c63b182bae9"
            ]
        ]
    },
    {
        "id": "126dbca226762933",
        "type": "json-schema-validator",
        "z": "8ee174a8a298654a",
        "g": "35ba395e82c0b1fd",
        "name": "Validate old message type",
        "jsonSchema": "{\n    \"type\": \"object\",\n    \"required\": [\"type\", \"txLog\"],\n    \"properties\": {\n        \"type\": { \"const\": \"unitsChanged\" },\n        \"txLog\": {\n            \"type\": \"object\",\n            \"required\": [\"rootUnitId\", \"changes\"],\n            \"properties\": {\n                \"rootUnitId\": { \"type\": \"string\" },\n                \"changes\": {\n                    \"type\": \"array\",\n                    \"items\": {\n                        \"type\": \"object\",\n                        \"required\": [\"unitId\", \"volume\", \"owner\", \"prevOwner\"],\n                        \"properties\": {\n                            \"unitId\": { \"type\": \"string\" },\n                            \"volume\": { \"type\": \"number\" },\n                            \"owner\": { \"type\": \"string\" },\n                            \"prevOwner\": { \"type\": [\"string\", \"null\"] }\n                        }\n                    }\n                }\n            }\n        }\n    }\n}",
        "x": 510,
        "y": 440,
        "wires": [
            [
                "56fd2cd0cc0812f8"
            ]
        ]
    },
    {
        "id": "6ca11a0d57bdc6cc",
        "type": "sqlite-inject",
        "z": "8ee174a8a298654a",
        "g": "9318cc15f9afd75f",
        "name": "",
        "sqliteConfig": "a407a49de3d419a0",
        "x": 2170,
        "y": 160,
        "wires": [
            [
                "e9eb48b870d28564"
            ]
        ]
    },
    {
        "id": "b8d668ba7a1a03ed",
        "type": "function",
        "z": "8ee174a8a298654a",
        "g": "9318cc15f9afd75f",
        "name": "Query ledger for accounts",
        "func": "const txLog = msg.payload.payload.txLog;\n\nconst accountIds = txLog.changes.flatMap((change) => {\n    return [change.owner].concat(change.prevOwner ?? []);\n});\n\nconst accountIdsWithRootIds = accountIds.map(accountId => ({\n    accountId,\n    rootUnitId: txLog.rootUnitId,\n}));\n\nmsg.payload.sqlite.then(async db => {\n    if (accountIdsWithRootIds.length === 0) {\n        node.send({\n            ...msg,\n            payload: {\n                ...msg.payload,\n                ledger: [],\n            }\n        });\n\n        return;\n    }\n    \n    const result = await db.selectFrom('ledger')\n        .selectAll()\n        .where(({ eb, refTuple, tuple }) => eb(\n            refTuple('account_id', 'root_unit_id'),\n            'in',\n            accountIdsWithRootIds.flatMap((accountWithUnit) => {\n                return tuple(accountWithUnit.accountId, accountWithUnit.rootUnitId)\n            })\n        ))\n        .execute()\n    \n    node.send({\n        ...msg,\n        payload: {\n            ...msg.payload,\n            ledger: result.map(r => ({\n                accountId: r.account_id,\n                rootUnitId: r.root_unit_id,\n                volume: r.volume\n            }))\n        }\n    })\n}).catch(node.error)\n",
        "outputs": 1,
        "timeout": 0,
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "libs": [],
        "x": 2630,
        "y": 160,
        "wires": [
            [
                "98a4076a7346f77b"
            ]
        ]
    },
    {
        "id": "6b974a17d0bf6e5a",
        "type": "catch",
        "z": "8ee174a8a298654a",
        "name": "",
        "scope": null,
        "uncaught": false,
        "x": 100,
        "y": 160,
        "wires": [
            [
                "75e8cba180af61bb"
            ]
        ]
    },
    {
        "id": "6d927d07bd0f86c8",
        "type": "comment",
        "z": "8ee174a8a298654a",
        "name": "No protocol (old message format)",
        "info": "",
        "x": 1090,
        "y": 220,
        "wires": []
    },
    {
        "id": "42fe81814bb0c41b",
        "type": "link out",
        "z": "8ee174a8a298654a",
        "name": "No protocol flow (OUT)",
        "mode": "link",
        "links": [
            "58dda468b6e90876"
        ],
        "x": 935,
        "y": 220,
        "wires": []
    },
    {
        "id": "58dda468b6e90876",
        "type": "link in",
        "z": "8ee174a8a298654a",
        "g": "35ba395e82c0b1fd",
        "name": "No protocol flow (IN)",
        "links": [
            "42fe81814bb0c41b"
        ],
        "x": 115,
        "y": 440,
        "wires": [
            [
                "be6ed27e6faa08cc"
            ]
        ]
    },
    {
        "id": "ca32fbaad3ee3347",
        "type": "function",
        "z": "8ee174a8a298654a",
        "g": "9318cc15f9afd75f",
        "name": "Update ledger",
        "func": "const txLog = msg.payload.payload.txLog;\nconst ledger = msg.payload.ledger;\n\nconst rootUnitId = txLog.rootUnitId;\nconst volumeMap = ledger.reduce((volumeMap, entry) => {\n    const key = `${entry.accountId}.${entry.rootUnitId}`;\n    const value = entry.volume;\n\n    volumeMap[key] ||= value;\n\n    return volumeMap;\n}, {});\n\nfor (const change of txLog.changes) {\n    if (change.prevOwner) {\n        const prevOwnerVolume = volumeMap[`${change.prevOwner}.${rootUnitId}`];\n\n        if (prevOwnerVolume === undefined) {\n            throw new Error(`Prev owner not found: prevOwnerId=${change.prevOwner}, rootUnitId=${rootUnitId}`);\n        }\n\n        volumeMap[`${change.prevOwner}.${rootUnitId}`] -= change.volume;\n    }\n\n    volumeMap[`${change.owner}.${rootUnitId}`] ||= 0;\n    volumeMap[`${change.owner}.${rootUnitId}`] += change.volume;\n}\n\nconst ledgerUpdate = Object.entries(volumeMap).map(([key, volume]) => {\n    const [accountId, rootUnitId] = key.split('.');\n\n    return {\n        accountId,\n        rootUnitId,\n        volume,\n    };\n});\n\nif (ledgerUpdate.length === 0) {\n    return;\n}\n\nmsg.payload.sqlite.then(async db => {\n    await db\n        .insertInto('ledger')\n        .values(ledgerUpdate.map(e => ({\n            account_id: e.accountId,\n            root_unit_id: e.rootUnitId,\n            volume: e.volume\n        })))\n        .onConflict(c => c.columns(['account_id', 'root_unit_id']).doUpdateSet((eb) => ({\n            volume: eb.ref('excluded.volume'),\n        })))\n        .execute()\n\n    node.send(msg);\n}).catch(node.error);",
        "outputs": 1,
        "timeout": 0,
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "libs": [],
        "x": 2720,
        "y": 260,
        "wires": [
            [
                "d4403c151c10fd53"
            ]
        ]
    },
    {
        "id": "75e8cba180af61bb",
        "type": "function",
        "z": "8ee174a8a298654a",
        "name": "Log error",
        "func": "node.error(`[${msg.error.source.type}:${msg.error.source.name}] ${msg.error.message}`);\n\nreturn msg;",
        "outputs": 1,
        "timeout": 0,
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "libs": [],
        "x": 260,
        "y": 160,
        "wires": [
            [
                "cbe7827e8b1ce3bd"
            ]
        ]
    },
    {
        "id": "be6ed27e6faa08cc",
        "type": "function",
        "z": "8ee174a8a298654a",
        "g": "35ba395e82c0b1fd",
        "name": "No \"type\" compatibility",
        "func": "return {\n    ...msg,\n    payload: {\n        ...msg.payload,\n        type: 'unitsChanged'\n    }\n}",
        "outputs": 1,
        "timeout": 0,
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "libs": [],
        "x": 260,
        "y": 440,
        "wires": [
            [
                "126dbca226762933"
            ]
        ]
    },
    {
        "id": "e9eb48b870d28564",
        "type": "function",
        "z": "8ee174a8a298654a",
        "g": "9318cc15f9afd75f",
        "name": "Filter processed",
        "func": "const txLog = msg.payload.payload.txLog;\n\nmsg.payload.sqlite.then(async db => {\n    const unitIds = txLog.changes.map(c => c.unitId);\n\n    if (unitIds.length === 0) {\n        node.send(msg);\n        return;\n    }\n\n    const existingUnits = await db\n        .selectFrom('processed')\n        .select('unit_id')\n        .where('unit_id', 'in', unitIds)\n        .execute()\n        .then(result => result.map(row => row.unit_id));\n\n    const existingUnitsSet = new Set(existingUnits);\n    const notExistingUnits = unitIds.filter(unitId => !existingUnitsSet.has(unitId));\n\n    if (existingUnits.length !== 0) {\n        if (existingUnits.length === unitIds.length) {\n            node.log(`Filtered ALL changes (unit ids: ${existingUnits.join(', ')})`);\n        } else {\n            node.log(`Filtered some changes (unit ids ${existingUnits.join(', ')})`);\n        }\n    }\n\n    node.send({\n        ...msg,\n        payload: {\n            ...msg.payload,\n            payload: {\n                ...msg.payload.payload,\n                txLog: {\n                    ...txLog,\n                    changes: txLog.changes.filter(change => notExistingUnits.includes(change.unitId))\n                }\n            }\n        }\n    })\n}).catch(node.error);\n",
        "outputs": 1,
        "timeout": 0,
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "libs": [],
        "x": 2360,
        "y": 160,
        "wires": [
            [
                "b8d668ba7a1a03ed"
            ]
        ]
    },
    {
        "id": "d4403c151c10fd53",
        "type": "function",
        "z": "8ee174a8a298654a",
        "g": "9318cc15f9afd75f",
        "name": "Update processed",
        "func": "const txLog = msg.payload.payload.txLog;\n\nmsg.payload.sqlite.then(async db => {\n    const changes = txLog.changes;\n\n    if (changes.length === 0) {\n        node.send(msg);\n        return;\n    }\n\n    await db\n        .insertInto('processed')\n        .values(changes.map(c => ({\n            owner: c.owner,\n            prev_owner: c.prevOwner,\n            root_unit_id: txLog.rootUnitId,\n            unit_id: c.unitId,\n            volume: c.volume,\n            created_at: Date.now(),\n        })))\n        .execute();\n\n    node.send(msg);\n}).catch(node.error);\n",
        "outputs": 1,
        "timeout": 0,
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "libs": [],
        "x": 2930,
        "y": 260,
        "wires": [
            [
                "e1f6f9ae2e6910d1"
            ]
        ]
    },
    {
        "id": "98a4076a7346f77b",
        "type": "function",
        "z": "8ee174a8a298654a",
        "g": "9318cc15f9afd75f",
        "name": "Transaction validator",
        "func": "const VOTE_APPROVE = 'approve';\nconst VOTE_REJECT = 'reject';\n\nconst txLog = msg.payload.payload.txLog;\nconst ledger = msg.payload.ledger;\n\nconst rootUnitId = txLog.rootUnitId;\nconst changes = txLog.changes;\nconst volumeMap = ledger.reduce((volumeMap, entry) => {\n    const key = `${entry.accountId}.${entry.rootUnitId}`;\n    const value = entry.volume;\n\n    volumeMap[key] ||= value;\n\n    return volumeMap;\n}, {});\n\nfor (const change of changes) {\n    // Ensure entries to be modified exist\n    if (change.prevOwner) {\n        volumeMap[`${change.prevOwner}.${rootUnitId}`] ||= 0;\n    }\n    volumeMap[`${change.owner}.${rootUnitId}`] ||= 0;\n\n    if (change.prevOwner) {\n        const ownedVolume = volumeMap[`${change.prevOwner}.${rootUnitId}`] ?? 0;\n\n        if (ownedVolume - change.volume < 0) {\n            return [\n                undefined,\n                {\n                    ...msg,\n                    payload: {\n                        ...msg.payload,\n                        txFailedReason: `${change.prevOwner} has ${ownedVolume} volume in unit ${rootUnitId}, but ${change.volume} is required`,\n                        voting: txLog.changes.map(v => ({\n                            vote: VOTE_REJECT,\n                            votingId: v.votingId,\n                        }))\n                    }\n                }\n            ];\n\n        }\n    }\n\n    if (change.prevOwner) {\n        volumeMap[`${change.prevOwner}.${rootUnitId}`] -= change.volume;\n    }\n\n    volumeMap[`${change.owner}.${rootUnitId}`] += change.volume;\n}\n\n\nreturn [\n    {\n        ...msg,\n        payload: {\n            ...msg.payload,\n            voting: txLog.changes.map(v => ({\n                vote: VOTE_APPROVE,\n                votingId: v.votingId,\n            }))\n        }\n    },\n    undefined\n]\n",
        "outputs": 2,
        "timeout": 0,
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "libs": [],
        "x": 2220,
        "y": 280,
        "wires": [
            [
                "ac42d7e5c6032791"
            ],
            [
                "65d289142a86384f"
            ]
        ]
    },
    {
        "id": "90f75c63b182bae9",
        "type": "function",
        "z": "8ee174a8a298654a",
        "name": "Unknown protocol version",
        "func": "throw new Error(`Protocol version ${msg.payload.protocolVersion} not supported`);\n",
        "outputs": 1,
        "timeout": 0,
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "libs": [],
        "x": 1070,
        "y": 260,
        "wires": [
            []
        ]
    },
    {
        "id": "a487a21a1443f6b7",
        "type": "link out",
        "z": "8ee174a8a298654a",
        "name": "Protocol vresion 1 (OUT)",
        "mode": "link",
        "links": [
            "fb43395da3eb18ac"
        ],
        "x": 935,
        "y": 180,
        "wires": []
    },
    {
        "id": "fb43395da3eb18ac",
        "type": "link in",
        "z": "8ee174a8a298654a",
        "g": "04c09f0c2cd6afcc",
        "name": "Protocol version 1 (IN)",
        "links": [
            "a487a21a1443f6b7"
        ],
        "x": 115,
        "y": 620,
        "wires": [
            [
                "746c2dd32cd8fa7f"
            ]
        ]
    },
    {
        "id": "8cf263f8817fe694",
        "type": "comment",
        "z": "8ee174a8a298654a",
        "name": "Protocol version 1",
        "info": "",
        "x": 1050,
        "y": 180,
        "wires": []
    },
    {
        "id": "746c2dd32cd8fa7f",
        "type": "json-schema-validator",
        "z": "8ee174a8a298654a",
        "g": "04c09f0c2cd6afcc",
        "name": "Protocol validator",
        "jsonSchema": "{\n    \"type\": \"object\",\n    \"required\": [\"protocolVersion\", \"type\", \"version\", \"payload\"],\n    \"properties\": {\n        \"protocolVersion\": { \"const\": 1 },\n        \"type\": { \"type\": \"string\" },\n        \"version\": { \"type\": \"number\" },\n        \"payload\": {}\n    }\n}",
        "x": 290,
        "y": 620,
        "wires": [
            [
                "af129b9f346b5e93"
            ]
        ]
    },
    {
        "id": "af129b9f346b5e93",
        "type": "switch",
        "z": "8ee174a8a298654a",
        "g": "04c09f0c2cd6afcc",
        "name": "Message type check",
        "property": "payload.type",
        "propertyType": "msg",
        "rules": [
            {
                "t": "eq",
                "v": "unitsChanged",
                "vt": "str"
            },
            {
                "t": "else"
            }
        ],
        "checkall": "true",
        "repair": false,
        "outputs": 2,
        "x": 520,
        "y": 620,
        "wires": [
            [
                "a986536a19a878b8"
            ],
            [
                "0bdc014d7d7e836a"
            ]
        ]
    },
    {
        "id": "0bdc014d7d7e836a",
        "type": "function",
        "z": "8ee174a8a298654a",
        "g": "04c09f0c2cd6afcc",
        "name": "Unknown message type",
        "func": "throw new Error(`Message type ${msg.payload.type} not supported`);\n",
        "outputs": 1,
        "timeout": 0,
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "libs": [],
        "x": 770,
        "y": 640,
        "wires": [
            []
        ]
    },
    {
        "id": "04570ad1292f1568",
        "type": "link in",
        "z": "8ee174a8a298654a",
        "name": "unitsChanged (IN)",
        "links": [
            "a986536a19a878b8"
        ],
        "x": 1335,
        "y": 200,
        "wires": [
            [
                "7f0c15b61ca09b2f"
            ]
        ]
    },
    {
        "id": "a986536a19a878b8",
        "type": "link out",
        "z": "8ee174a8a298654a",
        "g": "04c09f0c2cd6afcc",
        "name": "unitsChanged (OUT)",
        "mode": "link",
        "links": [
            "04570ad1292f1568"
        ],
        "x": 675,
        "y": 600,
        "wires": []
    },
    {
        "id": "4d6a02a638fceb91",
        "type": "comment",
        "z": "8ee174a8a298654a",
        "g": "04c09f0c2cd6afcc",
        "name": "unitsChanged",
        "info": "",
        "x": 770,
        "y": 600,
        "wires": []
    },
    {
        "id": "a7187e080554af45",
        "type": "comment",
        "z": "8ee174a8a298654a",
        "name": "unitsChanged",
        "info": "",
        "x": 1390,
        "y": 160,
        "wires": []
    },
    {
        "id": "56fd2cd0cc0812f8",
        "type": "function",
        "z": "8ee174a8a298654a",
        "g": "35ba395e82c0b1fd",
        "name": "Convert old message to new one",
        "func": "const { txLog, type, ...payload } = msg.payload;\n\nreturn {\n    ...msg,\n    payload: {\n        ...payload,\n        protocolVersion: 1,\n        type: 'unitsChanged',\n        version: 1,\n        payload: {\n            txLog: {\n                ...txLog,\n                changes: txLog.changes.map(change => ({\n                    ...change,\n                    votingId: change.unitId\n                }))\n            }\n        }\n    }\n}",
        "outputs": 1,
        "timeout": 0,
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "libs": [],
        "x": 800,
        "y": 440,
        "wires": [
            [
                "746c2dd32cd8fa7f"
            ]
        ]
    },
    {
        "id": "7f0c15b61ca09b2f",
        "type": "switch",
        "z": "8ee174a8a298654a",
        "name": "unitsChanged version check",
        "property": "payload.version",
        "propertyType": "msg",
        "rules": [
            {
                "t": "eq",
                "v": "1",
                "vt": "num"
            },
            {
                "t": "else"
            }
        ],
        "checkall": "true",
        "repair": false,
        "outputs": 2,
        "x": 1500,
        "y": 200,
        "wires": [
            [
                "7a9d1c1446786bd7"
            ],
            [
                "6e4d452be82e6fee"
            ]
        ]
    },
    {
        "id": "6e4d452be82e6fee",
        "type": "function",
        "z": "8ee174a8a298654a",
        "name": "Unknown message version",
        "func": "throw new Error(`Message type \"unitsChanged\" in version ${msg.payload.version} is not supported`);\n",
        "outputs": 1,
        "timeout": 0,
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "libs": [],
        "x": 1780,
        "y": 240,
        "wires": [
            []
        ]
    },
    {
        "id": "7a9d1c1446786bd7",
        "type": "json-schema-validator",
        "z": "8ee174a8a298654a",
        "name": "Validate unitsChanged v1 payload",
        "jsonSchema": "{\n    \"type\": \"object\",\n    \"required\": [\"payload\"],\n    \"properties\": {\n        \"payload\": {\n            \"type\": \"object\",\n            \"required\": [\"txLog\"],\n            \"properties\": {\n                \"txLog\": {\n                    \"type\": \"object\",\n                    \"required\": [\"rootUnitId\", \"changes\"],\n                    \"properties\": {\n                        \"rootUnitId\": { \"type\": \"string\" },\n                        \"changes\": {\n                            \"type\": \"array\",\n                            \"items\": {\n                                \"type\": \"object\",\n                                \"required\": [\"unitId\", \"volume\", \"owner\", \"prevOwner\", \"votingId\"],\n                                \"properties\": {\n                                    \"unitId\": { \"type\": \"string\" },\n                                    \"votingId\": { \"type\": \"string\" },\n                                    \"volume\": { \"type\": \"number\" },\n                                    \"owner\": { \"type\": \"string\" },\n                                    \"prevOwner\": { \"type\": [\"string\", \"null\"] }\n                                }\n                            }\n                        }\n                    }\n                }\n            }\n        }\n    }\n}",
        "x": 1800,
        "y": 160,
        "wires": [
            [
                "6ca11a0d57bdc6cc"
            ]
        ]
    }
]