holosphere 2.0.0-alpha2 → 2.0.0-alpha5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/2019-D2OG2idw.js +6680 -0
- package/dist/2019-D2OG2idw.js.map +1 -0
- package/dist/2019-EION3wKo.cjs +8 -0
- package/dist/2019-EION3wKo.cjs.map +1 -0
- package/dist/_commonjsHelpers-C37NGDzP.cjs +2 -0
- package/dist/_commonjsHelpers-C37NGDzP.cjs.map +1 -0
- package/dist/_commonjsHelpers-CUmg6egw.js +7 -0
- package/dist/_commonjsHelpers-CUmg6egw.js.map +1 -0
- package/dist/browser-BSniCNqO.js +3058 -0
- package/dist/browser-BSniCNqO.js.map +1 -0
- package/dist/browser-Cq59Ij19.cjs +2 -0
- package/dist/browser-Cq59Ij19.cjs.map +1 -0
- package/dist/cjs/holosphere.cjs +1 -1
- package/dist/esm/holosphere.js +50 -53
- package/dist/index-BG8FStkt.cjs +12 -0
- package/dist/index-BG8FStkt.cjs.map +1 -0
- package/dist/index-Bbey4GkP.js +37869 -0
- package/dist/index-Bbey4GkP.js.map +1 -0
- package/dist/index-Cp3xctq8.js +15104 -0
- package/dist/index-Cp3xctq8.js.map +1 -0
- package/dist/index-hfVGRwSr.cjs +5 -0
- package/dist/index-hfVGRwSr.cjs.map +1 -0
- package/dist/indexeddb-storage-BD70pN7q.cjs +2 -0
- package/dist/indexeddb-storage-BD70pN7q.cjs.map +1 -0
- package/dist/{indexeddb-storage-CMW4qRQS.js → indexeddb-storage-Bjg84U5R.js} +49 -13
- package/dist/indexeddb-storage-Bjg84U5R.js.map +1 -0
- package/dist/{memory-storage-DQzcAZlf.js → memory-storage-CD0XFayE.js} +6 -2
- package/dist/memory-storage-CD0XFayE.js.map +1 -0
- package/dist/{memory-storage-DmePEP2q.cjs → memory-storage-DmMyJtOo.cjs} +2 -2
- package/dist/memory-storage-DmMyJtOo.cjs.map +1 -0
- package/dist/{secp256k1-vOXp40Fx.js → secp256k1-69sS9O-P.js} +2 -393
- package/dist/secp256k1-69sS9O-P.js.map +1 -0
- package/dist/secp256k1-TcN6vWGh.cjs +12 -0
- package/dist/secp256k1-TcN6vWGh.cjs.map +1 -0
- package/docs/CONTRACTS.md +797 -0
- package/examples/demo.html +47 -0
- package/package.json +10 -5
- package/src/contracts/abis/Appreciative.json +1280 -0
- package/src/contracts/abis/AppreciativeFactory.json +101 -0
- package/src/contracts/abis/Bundle.json +1435 -0
- package/src/contracts/abis/BundleFactory.json +106 -0
- package/src/contracts/abis/Holon.json +881 -0
- package/src/contracts/abis/Holons.json +330 -0
- package/src/contracts/abis/Managed.json +1262 -0
- package/src/contracts/abis/ManagedFactory.json +149 -0
- package/src/contracts/abis/Membrane.json +261 -0
- package/src/contracts/abis/Splitter.json +1624 -0
- package/src/contracts/abis/SplitterFactory.json +220 -0
- package/src/contracts/abis/TestToken.json +321 -0
- package/src/contracts/abis/Zoned.json +1461 -0
- package/src/contracts/abis/ZonedFactory.json +154 -0
- package/src/contracts/chain-manager.js +375 -0
- package/src/contracts/deployer.js +443 -0
- package/src/contracts/event-listener.js +507 -0
- package/src/contracts/holon-contracts.js +344 -0
- package/src/contracts/index.js +83 -0
- package/src/contracts/networks.js +224 -0
- package/src/contracts/operations.js +670 -0
- package/src/contracts/queries.js +589 -0
- package/src/core/holosphere.js +453 -1
- package/src/crypto/nostr-utils.js +263 -0
- package/src/federation/handshake.js +455 -0
- package/src/federation/hologram.js +1 -1
- package/src/hierarchical/upcast.js +6 -5
- package/src/index.js +463 -1939
- package/src/lib/ai-methods.js +308 -0
- package/src/lib/contract-methods.js +293 -0
- package/src/lib/errors.js +23 -0
- package/src/lib/federation-methods.js +238 -0
- package/src/lib/index.js +26 -0
- package/src/spatial/h3-operations.js +2 -2
- package/src/storage/backends/gundb-backend.js +377 -46
- package/src/storage/global-tables.js +28 -1
- package/src/storage/gun-auth.js +303 -0
- package/src/storage/gun-federation.js +776 -0
- package/src/storage/gun-references.js +198 -0
- package/src/storage/gun-schema.js +291 -0
- package/src/storage/gun-wrapper.js +347 -31
- package/src/storage/indexeddb-storage.js +49 -11
- package/src/storage/memory-storage.js +5 -0
- package/src/storage/nostr-async.js +194 -37
- package/src/storage/nostr-client.js +580 -51
- package/src/storage/persistent-storage.js +6 -1
- package/src/storage/unified-storage.js +119 -0
- package/src/subscriptions/manager.js +1 -1
- package/types/index.d.ts +133 -0
- package/dist/index-CDfIuXew.js +0 -15974
- package/dist/index-CDfIuXew.js.map +0 -1
- package/dist/index-ifOgtDvd.cjs +0 -3
- package/dist/index-ifOgtDvd.cjs.map +0 -1
- package/dist/indexeddb-storage-CMW4qRQS.js.map +0 -1
- package/dist/indexeddb-storage-DLZOgetM.cjs +0 -2
- package/dist/indexeddb-storage-DLZOgetM.cjs.map +0 -1
- package/dist/memory-storage-DQzcAZlf.js.map +0 -1
- package/dist/memory-storage-DmePEP2q.cjs.map +0 -1
- package/dist/secp256k1-CP0ZkpAx.cjs +0 -13
- package/dist/secp256k1-CP0ZkpAx.cjs.map +0 -1
- package/dist/secp256k1-vOXp40Fx.js.map +0 -1
|
@@ -0,0 +1,507 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Event Listener for Sankey Flow Visualization
|
|
3
|
+
*
|
|
4
|
+
* Listens to contract events and transforms them into a format
|
|
5
|
+
* suitable for Sankey diagram visualization of fund flows.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Standard Sankey event types emitted by our contracts
|
|
10
|
+
*/
|
|
11
|
+
export const SANKEY_EVENTS = {
|
|
12
|
+
// Fund flow events
|
|
13
|
+
FundsReceived: 'FundsReceived',
|
|
14
|
+
FundsCascaded: 'FundsCascaded',
|
|
15
|
+
FundsAllocated: 'FundsAllocated',
|
|
16
|
+
FundsClaimed: 'FundsClaimed',
|
|
17
|
+
FundsTransferred: 'FundsTransferred',
|
|
18
|
+
|
|
19
|
+
// Distribution summary
|
|
20
|
+
DistributionCompleted: 'DistributionCompleted',
|
|
21
|
+
|
|
22
|
+
// Structure events
|
|
23
|
+
ChildContractCreated: 'ChildContractCreated',
|
|
24
|
+
HolonCreated: 'HolonCreated',
|
|
25
|
+
|
|
26
|
+
// Membership events
|
|
27
|
+
MemberAdded: 'MemberAdded',
|
|
28
|
+
MemberRemoved: 'MemberRemoved',
|
|
29
|
+
|
|
30
|
+
// Weight/Zone events
|
|
31
|
+
WeightChanged: 'WeightChanged',
|
|
32
|
+
ZoneAssigned: 'ZoneAssigned',
|
|
33
|
+
AppreciationGiven: 'AppreciationGiven',
|
|
34
|
+
|
|
35
|
+
// Configuration events
|
|
36
|
+
SplitConfigured: 'SplitConfigured',
|
|
37
|
+
ContractSplitConfigured: 'ContractSplitConfigured'
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* EventListener class for tracking contract events
|
|
42
|
+
*/
|
|
43
|
+
export class EventListener {
|
|
44
|
+
constructor(chainManager) {
|
|
45
|
+
this.chainManager = chainManager;
|
|
46
|
+
this.listeners = new Map();
|
|
47
|
+
this.eventHistory = [];
|
|
48
|
+
this.sankeyNodes = new Map(); // address/userId -> node data
|
|
49
|
+
this.sankeyLinks = []; // {source, target, value, ...}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Start listening to all Sankey-relevant events on a contract
|
|
54
|
+
* @param {string} contractAddress - The contract address
|
|
55
|
+
* @param {string} holonId - Human-readable holon identifier
|
|
56
|
+
* @param {Object} contract - ethers.js contract instance
|
|
57
|
+
*/
|
|
58
|
+
async listenToContract(contractAddress, holonId, contract) {
|
|
59
|
+
const eventFilters = [
|
|
60
|
+
'FundsReceived',
|
|
61
|
+
'FundsCascaded',
|
|
62
|
+
'FundsAllocated',
|
|
63
|
+
'FundsClaimed',
|
|
64
|
+
'FundsTransferred',
|
|
65
|
+
'DistributionCompleted',
|
|
66
|
+
'MemberAdded',
|
|
67
|
+
'WeightChanged',
|
|
68
|
+
'ZoneAssigned',
|
|
69
|
+
'AppreciationGiven'
|
|
70
|
+
];
|
|
71
|
+
|
|
72
|
+
for (const eventName of eventFilters) {
|
|
73
|
+
try {
|
|
74
|
+
const filter = contract.filters[eventName]?.();
|
|
75
|
+
if (filter) {
|
|
76
|
+
contract.on(eventName, (...args) => {
|
|
77
|
+
this._handleEvent(eventName, args, contractAddress, holonId);
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
} catch (e) {
|
|
81
|
+
// Event not found on this contract, skip
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
this.listeners.set(contractAddress, { contract, holonId });
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Stop listening to a contract
|
|
90
|
+
*/
|
|
91
|
+
stopListening(contractAddress) {
|
|
92
|
+
const listener = this.listeners.get(contractAddress);
|
|
93
|
+
if (listener) {
|
|
94
|
+
listener.contract.removeAllListeners();
|
|
95
|
+
this.listeners.delete(contractAddress);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Stop all listeners
|
|
101
|
+
*/
|
|
102
|
+
stopAll() {
|
|
103
|
+
for (const [address, listener] of this.listeners) {
|
|
104
|
+
listener.contract.removeAllListeners();
|
|
105
|
+
}
|
|
106
|
+
this.listeners.clear();
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Handle incoming event and transform to Sankey data
|
|
111
|
+
*/
|
|
112
|
+
_handleEvent(eventName, args, contractAddress, holonId) {
|
|
113
|
+
const event = args[args.length - 1]; // Last arg is the event object
|
|
114
|
+
const timestamp = Date.now();
|
|
115
|
+
|
|
116
|
+
const parsed = {
|
|
117
|
+
type: eventName,
|
|
118
|
+
contractAddress,
|
|
119
|
+
holonId,
|
|
120
|
+
timestamp,
|
|
121
|
+
blockNumber: event?.blockNumber,
|
|
122
|
+
transactionHash: event?.transactionHash,
|
|
123
|
+
data: this._parseEventData(eventName, args)
|
|
124
|
+
};
|
|
125
|
+
|
|
126
|
+
this.eventHistory.push(parsed);
|
|
127
|
+
this._updateSankeyData(parsed);
|
|
128
|
+
|
|
129
|
+
// Emit custom event for real-time updates
|
|
130
|
+
if (typeof window !== 'undefined') {
|
|
131
|
+
window.dispatchEvent(new CustomEvent('sankey-event', { detail: parsed }));
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Parse event data based on event type
|
|
137
|
+
*/
|
|
138
|
+
_parseEventData(eventName, args) {
|
|
139
|
+
switch (eventName) {
|
|
140
|
+
case 'FundsReceived':
|
|
141
|
+
return {
|
|
142
|
+
contractAddress: args[0],
|
|
143
|
+
sender: args[1],
|
|
144
|
+
tokenAddress: args[2],
|
|
145
|
+
amount: args[3]?.toString()
|
|
146
|
+
};
|
|
147
|
+
|
|
148
|
+
case 'FundsCascaded':
|
|
149
|
+
return {
|
|
150
|
+
fromContract: args[0],
|
|
151
|
+
toContract: args[1],
|
|
152
|
+
fromHolonId: args[2],
|
|
153
|
+
toHolonId: args[3],
|
|
154
|
+
tokenAddress: args[4],
|
|
155
|
+
amount: args[5]?.toString(),
|
|
156
|
+
splitType: args[6]
|
|
157
|
+
};
|
|
158
|
+
|
|
159
|
+
case 'FundsAllocated':
|
|
160
|
+
return {
|
|
161
|
+
contractAddress: args[0],
|
|
162
|
+
holonId: args[1],
|
|
163
|
+
userId: args[2],
|
|
164
|
+
tokenAddress: args[3],
|
|
165
|
+
amount: args[4]?.toString(),
|
|
166
|
+
distributionType: args[5]
|
|
167
|
+
};
|
|
168
|
+
|
|
169
|
+
case 'FundsClaimed':
|
|
170
|
+
return {
|
|
171
|
+
contractAddress: args[0],
|
|
172
|
+
holonId: args[1],
|
|
173
|
+
userId: args[2],
|
|
174
|
+
beneficiary: args[3],
|
|
175
|
+
tokenAddress: args[4],
|
|
176
|
+
amount: args[5]?.toString()
|
|
177
|
+
};
|
|
178
|
+
|
|
179
|
+
case 'FundsTransferred':
|
|
180
|
+
return {
|
|
181
|
+
contractAddress: args[0],
|
|
182
|
+
holonId: args[1],
|
|
183
|
+
userId: args[2],
|
|
184
|
+
recipient: args[3],
|
|
185
|
+
tokenAddress: args[4],
|
|
186
|
+
amount: args[5]?.toString()
|
|
187
|
+
};
|
|
188
|
+
|
|
189
|
+
case 'DistributionCompleted':
|
|
190
|
+
return {
|
|
191
|
+
contractAddress: args[0],
|
|
192
|
+
holonId: args[1],
|
|
193
|
+
tokenAddress: args[2],
|
|
194
|
+
totalAmount: args[3]?.toString(),
|
|
195
|
+
recipientCount: Number(args[4]),
|
|
196
|
+
cascadeCount: Number(args[5])
|
|
197
|
+
};
|
|
198
|
+
|
|
199
|
+
case 'MemberAdded':
|
|
200
|
+
return {
|
|
201
|
+
contractAddress: args[0],
|
|
202
|
+
holonId: args[1],
|
|
203
|
+
userId: args[2],
|
|
204
|
+
addedBy: args[3]
|
|
205
|
+
};
|
|
206
|
+
|
|
207
|
+
case 'WeightChanged':
|
|
208
|
+
return {
|
|
209
|
+
contractAddress: args[0],
|
|
210
|
+
holonId: args[1],
|
|
211
|
+
userId: args[2],
|
|
212
|
+
oldWeight: args[3]?.toString(),
|
|
213
|
+
newWeight: args[4]?.toString()
|
|
214
|
+
};
|
|
215
|
+
|
|
216
|
+
case 'ZoneAssigned':
|
|
217
|
+
return {
|
|
218
|
+
contractAddress: args[0],
|
|
219
|
+
holonId: args[1],
|
|
220
|
+
userId: args[2],
|
|
221
|
+
oldZone: args[3]?.toString(),
|
|
222
|
+
newZone: args[4]?.toString()
|
|
223
|
+
};
|
|
224
|
+
|
|
225
|
+
case 'AppreciationGiven':
|
|
226
|
+
return {
|
|
227
|
+
contractAddress: args[0],
|
|
228
|
+
holonId: args[1],
|
|
229
|
+
fromUserId: args[2],
|
|
230
|
+
toUserId: args[3],
|
|
231
|
+
amount: args[4]?.toString()
|
|
232
|
+
};
|
|
233
|
+
|
|
234
|
+
default:
|
|
235
|
+
return { raw: args.slice(0, -1) };
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
/**
|
|
240
|
+
* Update Sankey nodes and links based on event
|
|
241
|
+
*/
|
|
242
|
+
_updateSankeyData(event) {
|
|
243
|
+
const { type, data } = event;
|
|
244
|
+
|
|
245
|
+
switch (type) {
|
|
246
|
+
case 'FundsReceived':
|
|
247
|
+
// Add external source node
|
|
248
|
+
this._ensureNode(data.sender, 'external', 'External');
|
|
249
|
+
this._ensureNode(data.contractAddress, 'contract', event.holonId);
|
|
250
|
+
this.sankeyLinks.push({
|
|
251
|
+
source: data.sender,
|
|
252
|
+
target: data.contractAddress,
|
|
253
|
+
value: BigInt(data.amount),
|
|
254
|
+
token: data.tokenAddress,
|
|
255
|
+
type: 'external_inflow'
|
|
256
|
+
});
|
|
257
|
+
break;
|
|
258
|
+
|
|
259
|
+
case 'FundsCascaded':
|
|
260
|
+
this._ensureNode(data.fromContract, 'contract', data.fromHolonId);
|
|
261
|
+
this._ensureNode(data.toContract, 'contract', data.toHolonId);
|
|
262
|
+
this.sankeyLinks.push({
|
|
263
|
+
source: data.fromContract,
|
|
264
|
+
target: data.toContract,
|
|
265
|
+
value: BigInt(data.amount),
|
|
266
|
+
token: data.tokenAddress,
|
|
267
|
+
type: 'cascade',
|
|
268
|
+
splitType: data.splitType
|
|
269
|
+
});
|
|
270
|
+
break;
|
|
271
|
+
|
|
272
|
+
case 'FundsAllocated':
|
|
273
|
+
this._ensureNode(data.contractAddress, 'contract', data.holonId);
|
|
274
|
+
this._ensureNode(data.userId, 'user', data.userId);
|
|
275
|
+
this.sankeyLinks.push({
|
|
276
|
+
source: data.contractAddress,
|
|
277
|
+
target: data.userId,
|
|
278
|
+
value: BigInt(data.amount),
|
|
279
|
+
token: data.tokenAddress,
|
|
280
|
+
type: 'allocation',
|
|
281
|
+
distributionType: data.distributionType
|
|
282
|
+
});
|
|
283
|
+
break;
|
|
284
|
+
|
|
285
|
+
case 'FundsClaimed':
|
|
286
|
+
case 'FundsTransferred':
|
|
287
|
+
this._ensureNode(data.userId, 'user', data.userId);
|
|
288
|
+
this._ensureNode(data.beneficiary || data.recipient, 'wallet',
|
|
289
|
+
(data.beneficiary || data.recipient).slice(0, 8) + '...');
|
|
290
|
+
this.sankeyLinks.push({
|
|
291
|
+
source: data.userId,
|
|
292
|
+
target: data.beneficiary || data.recipient,
|
|
293
|
+
value: BigInt(data.amount),
|
|
294
|
+
token: data.tokenAddress,
|
|
295
|
+
type: type === 'FundsClaimed' ? 'claim' : 'transfer'
|
|
296
|
+
});
|
|
297
|
+
break;
|
|
298
|
+
|
|
299
|
+
case 'MemberAdded':
|
|
300
|
+
this._ensureNode(data.userId, 'user', data.userId);
|
|
301
|
+
break;
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
/**
|
|
306
|
+
* Ensure a node exists in the Sankey graph
|
|
307
|
+
*/
|
|
308
|
+
_ensureNode(id, type, label) {
|
|
309
|
+
if (!this.sankeyNodes.has(id)) {
|
|
310
|
+
this.sankeyNodes.set(id, { id, type, label });
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
/**
|
|
315
|
+
* Get Sankey-compatible data for visualization
|
|
316
|
+
* @param {Object} options - Filter options
|
|
317
|
+
* @returns {Object} { nodes: [], links: [] }
|
|
318
|
+
*/
|
|
319
|
+
getSankeyData(options = {}) {
|
|
320
|
+
const { tokenAddress, fromBlock, toBlock, minValue } = options;
|
|
321
|
+
|
|
322
|
+
let links = [...this.sankeyLinks];
|
|
323
|
+
|
|
324
|
+
// Filter by token
|
|
325
|
+
if (tokenAddress) {
|
|
326
|
+
links = links.filter(l => l.token === tokenAddress);
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
// Filter by minimum value
|
|
330
|
+
if (minValue) {
|
|
331
|
+
const minBigInt = BigInt(minValue);
|
|
332
|
+
links = links.filter(l => l.value >= minBigInt);
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
// Get unique node IDs from filtered links
|
|
336
|
+
const nodeIds = new Set();
|
|
337
|
+
links.forEach(l => {
|
|
338
|
+
nodeIds.add(l.source);
|
|
339
|
+
nodeIds.add(l.target);
|
|
340
|
+
});
|
|
341
|
+
|
|
342
|
+
// Convert nodes to array format
|
|
343
|
+
const nodeArray = [...nodeIds].map(id => {
|
|
344
|
+
const node = this.sankeyNodes.get(id) || { id, type: 'unknown', label: id };
|
|
345
|
+
return {
|
|
346
|
+
id: node.id,
|
|
347
|
+
name: node.label,
|
|
348
|
+
type: node.type
|
|
349
|
+
};
|
|
350
|
+
});
|
|
351
|
+
|
|
352
|
+
// Create node index for links
|
|
353
|
+
const nodeIndex = new Map();
|
|
354
|
+
nodeArray.forEach((n, i) => nodeIndex.set(n.id, i));
|
|
355
|
+
|
|
356
|
+
// Convert links to Sankey format with numeric indices
|
|
357
|
+
const sankeyLinks = links.map(l => ({
|
|
358
|
+
source: nodeIndex.get(l.source),
|
|
359
|
+
target: nodeIndex.get(l.target),
|
|
360
|
+
value: Number(l.value) / 1e18, // Convert from wei to ETH
|
|
361
|
+
type: l.type,
|
|
362
|
+
splitType: l.splitType,
|
|
363
|
+
distributionType: l.distributionType,
|
|
364
|
+
token: l.token
|
|
365
|
+
}));
|
|
366
|
+
|
|
367
|
+
return {
|
|
368
|
+
nodes: nodeArray,
|
|
369
|
+
links: sankeyLinks
|
|
370
|
+
};
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
/**
|
|
374
|
+
* Get aggregated flow summary
|
|
375
|
+
* @returns {Object} Summary statistics
|
|
376
|
+
*/
|
|
377
|
+
getFlowSummary() {
|
|
378
|
+
const summary = {
|
|
379
|
+
totalInflow: 0n,
|
|
380
|
+
totalOutflow: 0n,
|
|
381
|
+
byHolon: {},
|
|
382
|
+
byUser: {},
|
|
383
|
+
byDistributionType: {
|
|
384
|
+
appreciation: 0n,
|
|
385
|
+
equal: 0n,
|
|
386
|
+
zone: 0n,
|
|
387
|
+
percentage: 0n,
|
|
388
|
+
peer_appreciation: 0n
|
|
389
|
+
}
|
|
390
|
+
};
|
|
391
|
+
|
|
392
|
+
for (const link of this.sankeyLinks) {
|
|
393
|
+
if (link.type === 'external_inflow') {
|
|
394
|
+
summary.totalInflow += link.value;
|
|
395
|
+
}
|
|
396
|
+
if (link.type === 'claim' || link.type === 'transfer') {
|
|
397
|
+
summary.totalOutflow += link.value;
|
|
398
|
+
}
|
|
399
|
+
if (link.distributionType && summary.byDistributionType[link.distributionType] !== undefined) {
|
|
400
|
+
summary.byDistributionType[link.distributionType] += link.value;
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
// Convert BigInt to strings for JSON serialization
|
|
405
|
+
return {
|
|
406
|
+
totalInflow: summary.totalInflow.toString(),
|
|
407
|
+
totalOutflow: summary.totalOutflow.toString(),
|
|
408
|
+
totalInflowEth: Number(summary.totalInflow) / 1e18,
|
|
409
|
+
totalOutflowEth: Number(summary.totalOutflow) / 1e18,
|
|
410
|
+
byDistributionType: Object.fromEntries(
|
|
411
|
+
Object.entries(summary.byDistributionType).map(([k, v]) => [k, v.toString()])
|
|
412
|
+
)
|
|
413
|
+
};
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
/**
|
|
417
|
+
* Query historical events from blockchain
|
|
418
|
+
* @param {Object} contract - ethers.js contract instance
|
|
419
|
+
* @param {string} eventName - Event name to query
|
|
420
|
+
* @param {Object} options - { fromBlock, toBlock }
|
|
421
|
+
*/
|
|
422
|
+
async queryPastEvents(contract, eventName, options = {}) {
|
|
423
|
+
const { fromBlock = 0, toBlock = 'latest' } = options;
|
|
424
|
+
|
|
425
|
+
try {
|
|
426
|
+
const filter = contract.filters[eventName]?.();
|
|
427
|
+
if (!filter) return [];
|
|
428
|
+
|
|
429
|
+
const events = await contract.queryFilter(filter, fromBlock, toBlock);
|
|
430
|
+
return events.map(e => ({
|
|
431
|
+
type: eventName,
|
|
432
|
+
contractAddress: e.address,
|
|
433
|
+
blockNumber: e.blockNumber,
|
|
434
|
+
transactionHash: e.transactionHash,
|
|
435
|
+
data: this._parseEventData(eventName, e.args)
|
|
436
|
+
}));
|
|
437
|
+
} catch (e) {
|
|
438
|
+
console.error(`Error querying ${eventName}:`, e);
|
|
439
|
+
return [];
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
/**
|
|
444
|
+
* Build Sankey data from historical events
|
|
445
|
+
* @param {Object} contract - ethers.js contract instance
|
|
446
|
+
* @param {string} holonId - Holon identifier
|
|
447
|
+
* @param {Object} options - Query options
|
|
448
|
+
*/
|
|
449
|
+
async buildFromHistory(contract, holonId, options = {}) {
|
|
450
|
+
const eventTypes = [
|
|
451
|
+
'FundsReceived',
|
|
452
|
+
'FundsCascaded',
|
|
453
|
+
'FundsAllocated',
|
|
454
|
+
'FundsClaimed',
|
|
455
|
+
'FundsTransferred',
|
|
456
|
+
'MemberAdded'
|
|
457
|
+
];
|
|
458
|
+
|
|
459
|
+
for (const eventName of eventTypes) {
|
|
460
|
+
const events = await this.queryPastEvents(contract, eventName, options);
|
|
461
|
+
for (const event of events) {
|
|
462
|
+
event.holonId = holonId;
|
|
463
|
+
this.eventHistory.push(event);
|
|
464
|
+
this._updateSankeyData(event);
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
return this.getSankeyData();
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
/**
|
|
472
|
+
* Clear all stored data
|
|
473
|
+
*/
|
|
474
|
+
clear() {
|
|
475
|
+
this.eventHistory = [];
|
|
476
|
+
this.sankeyNodes.clear();
|
|
477
|
+
this.sankeyLinks = [];
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
/**
|
|
481
|
+
* Export data for persistence
|
|
482
|
+
*/
|
|
483
|
+
export() {
|
|
484
|
+
return {
|
|
485
|
+
events: this.eventHistory,
|
|
486
|
+
nodes: Object.fromEntries(this.sankeyNodes),
|
|
487
|
+
links: this.sankeyLinks.map(l => ({
|
|
488
|
+
...l,
|
|
489
|
+
value: l.value.toString()
|
|
490
|
+
}))
|
|
491
|
+
};
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
/**
|
|
495
|
+
* Import previously exported data
|
|
496
|
+
*/
|
|
497
|
+
import(data) {
|
|
498
|
+
this.eventHistory = data.events || [];
|
|
499
|
+
this.sankeyNodes = new Map(Object.entries(data.nodes || {}));
|
|
500
|
+
this.sankeyLinks = (data.links || []).map(l => ({
|
|
501
|
+
...l,
|
|
502
|
+
value: BigInt(l.value)
|
|
503
|
+
}));
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
export default EventListener;
|