@economicagents/graph 0.1.0

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/store.js ADDED
@@ -0,0 +1,288 @@
1
+ /**
2
+ * SQLite store for economic graph. Uses better-sqlite3 (optional dependency).
3
+ * Falls back to sql.js (pure JS) when better-sqlite3 native bindings are unavailable.
4
+ */
5
+ import { createRequire } from "module";
6
+ import { mkdirSync } from "fs";
7
+ import { join } from "path";
8
+ const require = createRequire(import.meta.url);
9
+ let db = null;
10
+ let dbPath = null;
11
+ let sqliteAvailable = null;
12
+ let sqlJsFallback = null;
13
+ /** For tests: set sql.js as fallback when better-sqlite3 native bindings are unavailable. */
14
+ export function setSqlJsFallback(SQL) {
15
+ sqlJsFallback = SQL;
16
+ }
17
+ function wrapSqlJsDb(nativeDb) {
18
+ return {
19
+ exec(sql) {
20
+ nativeDb.exec(sql);
21
+ },
22
+ prepare(sql) {
23
+ return {
24
+ run: (...args) => {
25
+ const stmt = nativeDb.prepare(sql);
26
+ stmt.bind(args);
27
+ while (stmt.step()) {
28
+ /* consume rows */
29
+ }
30
+ stmt.free();
31
+ return { changes: 1 };
32
+ },
33
+ get: (...args) => {
34
+ const stmt = nativeDb.prepare(sql);
35
+ stmt.bind(args);
36
+ const row = stmt.step() ? stmt.getAsObject() : undefined;
37
+ stmt.free();
38
+ return row;
39
+ },
40
+ all: (...args) => {
41
+ const stmt = nativeDb.prepare(sql);
42
+ stmt.bind(args);
43
+ const rows = [];
44
+ while (stmt.step())
45
+ rows.push(stmt.getAsObject());
46
+ stmt.free();
47
+ return rows;
48
+ },
49
+ };
50
+ },
51
+ close: () => nativeDb.close(),
52
+ };
53
+ }
54
+ /** Exported for tests: true when better-sqlite3 (disk persistence) is active; false when using sql.js fallback. */
55
+ export function isUsingBetterSqlite3() {
56
+ if (sqliteAvailable === null)
57
+ isSqliteAvailable();
58
+ return sqliteAvailable === true;
59
+ }
60
+ /** Exported for tests: whether SQLite (better-sqlite3 or sql.js fallback) is available. */
61
+ export function isSqliteAvailable() {
62
+ if (sqliteAvailable === null) {
63
+ try {
64
+ const Database = require("better-sqlite3");
65
+ const testDb = new Database(":memory:");
66
+ testDb.close();
67
+ sqliteAvailable = true;
68
+ }
69
+ catch {
70
+ sqliteAvailable = false;
71
+ }
72
+ }
73
+ if (sqliteAvailable)
74
+ return true;
75
+ return sqlJsFallback !== null;
76
+ }
77
+ function getDb(graphPath) {
78
+ const resolvedPath = join(graphPath, "graph.db");
79
+ if (db && dbPath === resolvedPath)
80
+ return db;
81
+ if (db) {
82
+ db.close();
83
+ db = null;
84
+ dbPath = null;
85
+ }
86
+ if (!isSqliteAvailable()) {
87
+ throw new Error("better-sqlite3 or sql.js required for graph. Install: npm install better-sqlite3");
88
+ }
89
+ try {
90
+ if (sqliteAvailable) {
91
+ const Database = require("better-sqlite3");
92
+ mkdirSync(graphPath, { recursive: true });
93
+ db = new Database(resolvedPath);
94
+ dbPath = resolvedPath;
95
+ }
96
+ else if (sqlJsFallback) {
97
+ // sql.js creates in-memory DB; graphPath is ignored. Use better-sqlite3 for production persistence.
98
+ const nativeDb = new sqlJsFallback.Database();
99
+ db = wrapSqlJsDb(nativeDb);
100
+ dbPath = resolvedPath;
101
+ }
102
+ else {
103
+ throw new Error("No SQLite backend available");
104
+ }
105
+ initSchema(db);
106
+ return db;
107
+ }
108
+ catch (err) {
109
+ const msg = err instanceof Error ? err.message : String(err);
110
+ throw new Error(`Graph database init failed: ${msg}. Install better-sqlite3 for production: npm install better-sqlite3`);
111
+ }
112
+ }
113
+ function initSchema(database) {
114
+ database.exec(`
115
+ CREATE TABLE IF NOT EXISTS accounts (
116
+ address TEXT PRIMARY KEY,
117
+ owner TEXT NOT NULL,
118
+ firstSeenBlock INTEGER NOT NULL
119
+ );
120
+
121
+ CREATE TABLE IF NOT EXISTS payments (
122
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
123
+ fromAddr TEXT NOT NULL,
124
+ toAddr TEXT NOT NULL,
125
+ amount TEXT NOT NULL,
126
+ token TEXT NOT NULL,
127
+ blockNumber INTEGER NOT NULL,
128
+ txHash TEXT NOT NULL,
129
+ logIndex INTEGER,
130
+ source TEXT NOT NULL
131
+ );
132
+ CREATE INDEX IF NOT EXISTS idx_payments_from ON payments(fromAddr);
133
+ CREATE INDEX IF NOT EXISTS idx_payments_to ON payments(toAddr);
134
+ CREATE INDEX IF NOT EXISTS idx_payments_block ON payments(blockNumber);
135
+
136
+ CREATE TABLE IF NOT EXISTS user_ops (
137
+ userOpHash TEXT PRIMARY KEY,
138
+ sender TEXT NOT NULL,
139
+ success INTEGER NOT NULL,
140
+ actualGasCost TEXT,
141
+ blockNumber INTEGER,
142
+ txHash TEXT
143
+ );
144
+
145
+ CREATE TABLE IF NOT EXISTS facilities (
146
+ address TEXT PRIMARY KEY,
147
+ lender TEXT NOT NULL,
148
+ borrower TEXT NOT NULL,
149
+ firstSeenBlock INTEGER NOT NULL
150
+ );
151
+
152
+ CREATE TABLE IF NOT EXISTS escrows (
153
+ address TEXT PRIMARY KEY,
154
+ consumer TEXT NOT NULL,
155
+ provider TEXT NOT NULL,
156
+ firstSeenBlock INTEGER NOT NULL
157
+ );
158
+
159
+ CREATE TABLE IF NOT EXISTS splitters (
160
+ address TEXT PRIMARY KEY,
161
+ token TEXT NOT NULL,
162
+ firstSeenBlock INTEGER NOT NULL
163
+ );
164
+
165
+ CREATE TABLE IF NOT EXISTS slas (
166
+ address TEXT PRIMARY KEY,
167
+ provider TEXT NOT NULL,
168
+ consumer TEXT NOT NULL,
169
+ firstSeenBlock INTEGER NOT NULL
170
+ );
171
+
172
+ CREATE TABLE IF NOT EXISTS sla_events (
173
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
174
+ slaAddress TEXT NOT NULL,
175
+ eventType TEXT NOT NULL,
176
+ provider TEXT NOT NULL,
177
+ consumer TEXT,
178
+ amount TEXT,
179
+ requestHash TEXT,
180
+ blockNumber INTEGER NOT NULL,
181
+ txHash TEXT
182
+ );
183
+ CREATE INDEX IF NOT EXISTS idx_sla_events_sla ON sla_events(slaAddress);
184
+ CREATE INDEX IF NOT EXISTS idx_sla_events_provider ON sla_events(provider);
185
+
186
+ CREATE TABLE IF NOT EXISTS sync_state (
187
+ contract_key TEXT PRIMARY KEY,
188
+ lastBlock INTEGER NOT NULL
189
+ );
190
+ `);
191
+ }
192
+ export function ensureGraphDir(graphPath) {
193
+ mkdirSync(graphPath, { recursive: true });
194
+ }
195
+ export function getDatabase(graphPath) {
196
+ return getDb(graphPath);
197
+ }
198
+ export function closeDatabase() {
199
+ if (db) {
200
+ db.close();
201
+ db = null;
202
+ dbPath = null;
203
+ }
204
+ }
205
+ export function insertAccount(database, address, owner, firstSeenBlock) {
206
+ database
207
+ .prepare("INSERT OR IGNORE INTO accounts (address, owner, firstSeenBlock) VALUES (?, ?, ?)")
208
+ .run(address.toLowerCase(), owner.toLowerCase(), firstSeenBlock);
209
+ }
210
+ export function insertPayment(database, fromAddr, toAddr, amount, token, blockNumber, txHash, logIndex, source) {
211
+ database
212
+ .prepare(`INSERT INTO payments (fromAddr, toAddr, amount, token, blockNumber, txHash, logIndex, source)
213
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?)`)
214
+ .run(fromAddr.toLowerCase(), toAddr.toLowerCase(), amount, token.toLowerCase(), blockNumber, txHash, logIndex, source);
215
+ }
216
+ export function insertUserOp(database, userOpHash, sender, success, actualGasCost, blockNumber, txHash) {
217
+ database
218
+ .prepare(`INSERT OR REPLACE INTO user_ops (userOpHash, sender, success, actualGasCost, blockNumber, txHash)
219
+ VALUES (?, ?, ?, ?, ?, ?)`)
220
+ .run(userOpHash, sender.toLowerCase(), success ? 1 : 0, actualGasCost, blockNumber, txHash);
221
+ }
222
+ export function insertFacility(database, address, lender, borrower, firstSeenBlock) {
223
+ database
224
+ .prepare("INSERT OR IGNORE INTO facilities (address, lender, borrower, firstSeenBlock) VALUES (?, ?, ?, ?)")
225
+ .run(address.toLowerCase(), lender.toLowerCase(), borrower.toLowerCase(), firstSeenBlock);
226
+ }
227
+ export function insertEscrow(database, address, consumer, provider, firstSeenBlock) {
228
+ database
229
+ .prepare("INSERT OR IGNORE INTO escrows (address, consumer, provider, firstSeenBlock) VALUES (?, ?, ?, ?)")
230
+ .run(address.toLowerCase(), consumer.toLowerCase(), provider.toLowerCase(), firstSeenBlock);
231
+ }
232
+ export function insertSplitter(database, address, token, firstSeenBlock) {
233
+ database
234
+ .prepare("INSERT OR IGNORE INTO splitters (address, token, firstSeenBlock) VALUES (?, ?, ?)")
235
+ .run(address.toLowerCase(), token.toLowerCase(), firstSeenBlock);
236
+ }
237
+ export function insertSLA(database, address, provider, consumer, firstSeenBlock) {
238
+ database
239
+ .prepare("INSERT OR IGNORE INTO slas (address, provider, consumer, firstSeenBlock) VALUES (?, ?, ?, ?)")
240
+ .run(address.toLowerCase(), provider.toLowerCase(), consumer.toLowerCase(), firstSeenBlock);
241
+ }
242
+ export function getSyncState(database, key) {
243
+ const row = database
244
+ .prepare("SELECT lastBlock FROM sync_state WHERE contract_key = ?")
245
+ .get(key);
246
+ return row?.lastBlock ?? null;
247
+ }
248
+ export function setSyncState(database, key, lastBlock) {
249
+ database
250
+ .prepare("INSERT OR REPLACE INTO sync_state (contract_key, lastBlock) VALUES (?, ?)")
251
+ .run(key, lastBlock);
252
+ }
253
+ export function getAccountAddresses(database) {
254
+ const rows = database
255
+ .prepare("SELECT address FROM accounts")
256
+ .all();
257
+ return rows.map((r) => r.address);
258
+ }
259
+ export function getFacilityAddresses(database) {
260
+ const rows = database
261
+ .prepare("SELECT address FROM facilities")
262
+ .all();
263
+ return rows.map((r) => r.address);
264
+ }
265
+ export function getEscrowAddresses(database) {
266
+ const rows = database
267
+ .prepare("SELECT address FROM escrows")
268
+ .all();
269
+ return rows.map((r) => r.address);
270
+ }
271
+ export function getSplitterAddresses(database) {
272
+ const rows = database
273
+ .prepare("SELECT address FROM splitters")
274
+ .all();
275
+ return rows.map((r) => r.address);
276
+ }
277
+ export function getSLAAddresses(database) {
278
+ const rows = database
279
+ .prepare("SELECT address FROM slas")
280
+ .all();
281
+ return rows.map((r) => r.address);
282
+ }
283
+ export function insertSLAEvent(database, slaAddress, eventType, provider, consumer, amount, requestHash, blockNumber, txHash) {
284
+ database
285
+ .prepare(`INSERT INTO sla_events (slaAddress, eventType, provider, consumer, amount, requestHash, blockNumber, txHash)
286
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?)`)
287
+ .run(slaAddress.toLowerCase(), eventType, provider.toLowerCase(), consumer?.toLowerCase() ?? null, amount, requestHash, blockNumber, txHash);
288
+ }
@@ -0,0 +1,24 @@
1
+ import type { Address } from "viem";
2
+ export interface GraphConfig {
3
+ rpcUrl: string;
4
+ chainId: number;
5
+ graphPath: string;
6
+ aepAccountFactoryAddress: Address;
7
+ entryPointAddress: Address;
8
+ creditFacilityFactoryAddress?: Address;
9
+ escrowFactoryAddress?: Address;
10
+ revenueSplitterFactoryAddress?: Address;
11
+ slaFactoryAddress?: Address;
12
+ usdcAddress: Address;
13
+ }
14
+ export interface SyncResult {
15
+ accountsAdded: number;
16
+ paymentsAdded: number;
17
+ userOpsAdded: number;
18
+ creditEventsAdded: number;
19
+ escrowEventsAdded: number;
20
+ splitterEventsAdded: number;
21
+ slaEventsAdded: number;
22
+ }
23
+ export type PaymentSource = "transfer" | "credit_draw" | "credit_repay" | "escrow_fund" | "escrow_release" | "splitter_distribute";
24
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,MAAM,CAAC;AAEpC,MAAM,WAAW,WAAW;IAC1B,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,wBAAwB,EAAE,OAAO,CAAC;IAClC,iBAAiB,EAAE,OAAO,CAAC;IAC3B,4BAA4B,CAAC,EAAE,OAAO,CAAC;IACvC,oBAAoB,CAAC,EAAE,OAAO,CAAC;IAC/B,6BAA6B,CAAC,EAAE,OAAO,CAAC;IACxC,iBAAiB,CAAC,EAAE,OAAO,CAAC;IAC5B,WAAW,EAAE,OAAO,CAAC;CACtB;AAED,MAAM,WAAW,UAAU;IACzB,aAAa,EAAE,MAAM,CAAC;IACtB,aAAa,EAAE,MAAM,CAAC;IACtB,YAAY,EAAE,MAAM,CAAC;IACrB,iBAAiB,EAAE,MAAM,CAAC;IAC1B,iBAAiB,EAAE,MAAM,CAAC;IAC1B,mBAAmB,EAAE,MAAM,CAAC;IAC5B,cAAc,EAAE,MAAM,CAAC;CACxB;AAED,MAAM,MAAM,aAAa,GACrB,UAAU,GACV,aAAa,GACb,cAAc,GACd,aAAa,GACb,gBAAgB,GAChB,qBAAqB,CAAC"}
package/dist/types.js ADDED
@@ -0,0 +1 @@
1
+ export {};
package/package.json ADDED
@@ -0,0 +1,56 @@
1
+ {
2
+ "name": "@economicagents/graph",
3
+ "version": "0.1.0",
4
+ "description": "Economic graph for AEP - transaction graph, payments, credit events, credit scoring, recommendations",
5
+ "license": "Apache-2.0",
6
+ "repository": {
7
+ "type": "git",
8
+ "url": "https://github.com/economicagents/AEP.git"
9
+ },
10
+ "bugs": "https://github.com/economicagents/AEP/issues",
11
+ "homepage": "https://github.com/economicagents/AEP#readme",
12
+ "keywords": [
13
+ "aep",
14
+ "agent",
15
+ "graph",
16
+ "analytics",
17
+ "credit-score"
18
+ ],
19
+ "publishConfig": {
20
+ "access": "public"
21
+ },
22
+ "type": "module",
23
+ "main": "dist/index.js",
24
+ "types": "dist/index.d.ts",
25
+ "exports": {
26
+ ".": {
27
+ "types": "./dist/index.d.ts",
28
+ "import": "./dist/index.js"
29
+ }
30
+ },
31
+ "bin": {
32
+ "aep-graph": "./dist/cli.js"
33
+ },
34
+ "dependencies": {
35
+ "viem": "^2.21.0"
36
+ },
37
+ "optionalDependencies": {
38
+ "better-sqlite3": "^12.1.0"
39
+ },
40
+ "devDependencies": {
41
+ "@types/better-sqlite3": "^7.6.11",
42
+ "sql.js": "^1.14.0",
43
+ "typescript": "^5.6.0",
44
+ "vitest": "^2.1.0"
45
+ },
46
+ "files": [
47
+ "dist"
48
+ ],
49
+ "scripts": {
50
+ "build": "tsc",
51
+ "dev": "tsc --watch",
52
+ "sync": "node dist/cli.js sync",
53
+ "test": "vitest run",
54
+ "test:watch": "vitest"
55
+ }
56
+ }