@wopr-network/platform-core 1.64.0 → 1.65.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/billing/crypto/index.d.ts +2 -0
- package/dist/billing/crypto/index.js +1 -0
- package/dist/billing/crypto/key-server.js +3 -0
- package/dist/billing/crypto/payment-method-store.d.ts +3 -0
- package/dist/billing/crypto/payment-method-store.js +9 -0
- package/dist/billing/crypto/plugin/__tests__/integration.test.d.ts +1 -0
- package/dist/billing/crypto/plugin/__tests__/integration.test.js +58 -0
- package/dist/billing/crypto/plugin/__tests__/interfaces.test.d.ts +1 -0
- package/dist/billing/crypto/plugin/__tests__/interfaces.test.js +46 -0
- package/dist/billing/crypto/plugin/__tests__/registry.test.d.ts +1 -0
- package/dist/billing/crypto/plugin/__tests__/registry.test.js +49 -0
- package/dist/billing/crypto/plugin/index.d.ts +2 -0
- package/dist/billing/crypto/plugin/index.js +1 -0
- package/dist/billing/crypto/plugin/interfaces.d.ts +97 -0
- package/dist/billing/crypto/plugin/interfaces.js +2 -0
- package/dist/billing/crypto/plugin/registry.d.ts +8 -0
- package/dist/billing/crypto/plugin/registry.js +21 -0
- package/dist/db/schema/crypto.d.ts +328 -0
- package/dist/db/schema/crypto.js +33 -1
- package/dist/db/schema/snapshots.d.ts +1 -1
- package/drizzle/migrations/0023_key_rings_table.sql +35 -0
- package/drizzle/migrations/0024_backfill_key_rings.sql +75 -0
- package/package.json +5 -1
- package/src/billing/crypto/index.ts +9 -0
- package/src/billing/crypto/key-server.ts +3 -0
- package/src/billing/crypto/payment-method-store.ts +12 -0
- package/src/billing/crypto/plugin/__tests__/integration.test.ts +64 -0
- package/src/billing/crypto/plugin/__tests__/interfaces.test.ts +51 -0
- package/src/billing/crypto/plugin/__tests__/registry.test.ts +58 -0
- package/src/billing/crypto/plugin/index.ts +17 -0
- package/src/billing/crypto/plugin/interfaces.ts +106 -0
- package/src/billing/crypto/plugin/registry.ts +26 -0
- package/src/db/schema/crypto.ts +43 -1
|
@@ -784,6 +784,57 @@ export declare const paymentMethods: import("drizzle-orm/pg-core").PgTableWithCo
|
|
|
784
784
|
identity: undefined;
|
|
785
785
|
generated: undefined;
|
|
786
786
|
}, {}, {}>;
|
|
787
|
+
keyRingId: import("drizzle-orm/pg-core").PgColumn<{
|
|
788
|
+
name: "key_ring_id";
|
|
789
|
+
tableName: "payment_methods";
|
|
790
|
+
dataType: "string";
|
|
791
|
+
columnType: "PgText";
|
|
792
|
+
data: string;
|
|
793
|
+
driverParam: string;
|
|
794
|
+
notNull: false;
|
|
795
|
+
hasDefault: false;
|
|
796
|
+
isPrimaryKey: false;
|
|
797
|
+
isAutoincrement: false;
|
|
798
|
+
hasRuntimeDefault: false;
|
|
799
|
+
enumValues: [string, ...string[]];
|
|
800
|
+
baseColumn: never;
|
|
801
|
+
identity: undefined;
|
|
802
|
+
generated: undefined;
|
|
803
|
+
}, {}, {}>;
|
|
804
|
+
encoding: import("drizzle-orm/pg-core").PgColumn<{
|
|
805
|
+
name: "encoding";
|
|
806
|
+
tableName: "payment_methods";
|
|
807
|
+
dataType: "string";
|
|
808
|
+
columnType: "PgText";
|
|
809
|
+
data: string;
|
|
810
|
+
driverParam: string;
|
|
811
|
+
notNull: false;
|
|
812
|
+
hasDefault: false;
|
|
813
|
+
isPrimaryKey: false;
|
|
814
|
+
isAutoincrement: false;
|
|
815
|
+
hasRuntimeDefault: false;
|
|
816
|
+
enumValues: [string, ...string[]];
|
|
817
|
+
baseColumn: never;
|
|
818
|
+
identity: undefined;
|
|
819
|
+
generated: undefined;
|
|
820
|
+
}, {}, {}>;
|
|
821
|
+
pluginId: import("drizzle-orm/pg-core").PgColumn<{
|
|
822
|
+
name: "plugin_id";
|
|
823
|
+
tableName: "payment_methods";
|
|
824
|
+
dataType: "string";
|
|
825
|
+
columnType: "PgText";
|
|
826
|
+
data: string;
|
|
827
|
+
driverParam: string;
|
|
828
|
+
notNull: false;
|
|
829
|
+
hasDefault: false;
|
|
830
|
+
isPrimaryKey: false;
|
|
831
|
+
isAutoincrement: false;
|
|
832
|
+
hasRuntimeDefault: false;
|
|
833
|
+
enumValues: [string, ...string[]];
|
|
834
|
+
baseColumn: never;
|
|
835
|
+
identity: undefined;
|
|
836
|
+
generated: undefined;
|
|
837
|
+
}, {}, {}>;
|
|
787
838
|
createdAt: import("drizzle-orm/pg-core").PgColumn<{
|
|
788
839
|
name: "created_at";
|
|
789
840
|
tableName: "payment_methods";
|
|
@@ -1237,3 +1288,280 @@ export declare const watcherProcessed: import("drizzle-orm/pg-core").PgTableWith
|
|
|
1237
1288
|
};
|
|
1238
1289
|
dialect: "pg";
|
|
1239
1290
|
}>;
|
|
1291
|
+
/**
|
|
1292
|
+
* Key rings — decouples key material (xpub/seed) from payment methods.
|
|
1293
|
+
* Each key ring maps to a BIP-44 coin type + account index.
|
|
1294
|
+
*/
|
|
1295
|
+
export declare const keyRings: import("drizzle-orm/pg-core").PgTableWithColumns<{
|
|
1296
|
+
name: "key_rings";
|
|
1297
|
+
schema: undefined;
|
|
1298
|
+
columns: {
|
|
1299
|
+
id: import("drizzle-orm/pg-core").PgColumn<{
|
|
1300
|
+
name: "id";
|
|
1301
|
+
tableName: "key_rings";
|
|
1302
|
+
dataType: "string";
|
|
1303
|
+
columnType: "PgText";
|
|
1304
|
+
data: string;
|
|
1305
|
+
driverParam: string;
|
|
1306
|
+
notNull: true;
|
|
1307
|
+
hasDefault: false;
|
|
1308
|
+
isPrimaryKey: true;
|
|
1309
|
+
isAutoincrement: false;
|
|
1310
|
+
hasRuntimeDefault: false;
|
|
1311
|
+
enumValues: [string, ...string[]];
|
|
1312
|
+
baseColumn: never;
|
|
1313
|
+
identity: undefined;
|
|
1314
|
+
generated: undefined;
|
|
1315
|
+
}, {}, {}>;
|
|
1316
|
+
curve: import("drizzle-orm/pg-core").PgColumn<{
|
|
1317
|
+
name: "curve";
|
|
1318
|
+
tableName: "key_rings";
|
|
1319
|
+
dataType: "string";
|
|
1320
|
+
columnType: "PgText";
|
|
1321
|
+
data: string;
|
|
1322
|
+
driverParam: string;
|
|
1323
|
+
notNull: true;
|
|
1324
|
+
hasDefault: false;
|
|
1325
|
+
isPrimaryKey: false;
|
|
1326
|
+
isAutoincrement: false;
|
|
1327
|
+
hasRuntimeDefault: false;
|
|
1328
|
+
enumValues: [string, ...string[]];
|
|
1329
|
+
baseColumn: never;
|
|
1330
|
+
identity: undefined;
|
|
1331
|
+
generated: undefined;
|
|
1332
|
+
}, {}, {}>;
|
|
1333
|
+
derivationScheme: import("drizzle-orm/pg-core").PgColumn<{
|
|
1334
|
+
name: "derivation_scheme";
|
|
1335
|
+
tableName: "key_rings";
|
|
1336
|
+
dataType: "string";
|
|
1337
|
+
columnType: "PgText";
|
|
1338
|
+
data: string;
|
|
1339
|
+
driverParam: string;
|
|
1340
|
+
notNull: true;
|
|
1341
|
+
hasDefault: false;
|
|
1342
|
+
isPrimaryKey: false;
|
|
1343
|
+
isAutoincrement: false;
|
|
1344
|
+
hasRuntimeDefault: false;
|
|
1345
|
+
enumValues: [string, ...string[]];
|
|
1346
|
+
baseColumn: never;
|
|
1347
|
+
identity: undefined;
|
|
1348
|
+
generated: undefined;
|
|
1349
|
+
}, {}, {}>;
|
|
1350
|
+
derivationMode: import("drizzle-orm/pg-core").PgColumn<{
|
|
1351
|
+
name: "derivation_mode";
|
|
1352
|
+
tableName: "key_rings";
|
|
1353
|
+
dataType: "string";
|
|
1354
|
+
columnType: "PgText";
|
|
1355
|
+
data: string;
|
|
1356
|
+
driverParam: string;
|
|
1357
|
+
notNull: true;
|
|
1358
|
+
hasDefault: true;
|
|
1359
|
+
isPrimaryKey: false;
|
|
1360
|
+
isAutoincrement: false;
|
|
1361
|
+
hasRuntimeDefault: false;
|
|
1362
|
+
enumValues: [string, ...string[]];
|
|
1363
|
+
baseColumn: never;
|
|
1364
|
+
identity: undefined;
|
|
1365
|
+
generated: undefined;
|
|
1366
|
+
}, {}, {}>;
|
|
1367
|
+
keyMaterial: import("drizzle-orm/pg-core").PgColumn<{
|
|
1368
|
+
name: "key_material";
|
|
1369
|
+
tableName: "key_rings";
|
|
1370
|
+
dataType: "string";
|
|
1371
|
+
columnType: "PgText";
|
|
1372
|
+
data: string;
|
|
1373
|
+
driverParam: string;
|
|
1374
|
+
notNull: true;
|
|
1375
|
+
hasDefault: true;
|
|
1376
|
+
isPrimaryKey: false;
|
|
1377
|
+
isAutoincrement: false;
|
|
1378
|
+
hasRuntimeDefault: false;
|
|
1379
|
+
enumValues: [string, ...string[]];
|
|
1380
|
+
baseColumn: never;
|
|
1381
|
+
identity: undefined;
|
|
1382
|
+
generated: undefined;
|
|
1383
|
+
}, {}, {}>;
|
|
1384
|
+
coinType: import("drizzle-orm/pg-core").PgColumn<{
|
|
1385
|
+
name: "coin_type";
|
|
1386
|
+
tableName: "key_rings";
|
|
1387
|
+
dataType: "number";
|
|
1388
|
+
columnType: "PgInteger";
|
|
1389
|
+
data: number;
|
|
1390
|
+
driverParam: string | number;
|
|
1391
|
+
notNull: true;
|
|
1392
|
+
hasDefault: false;
|
|
1393
|
+
isPrimaryKey: false;
|
|
1394
|
+
isAutoincrement: false;
|
|
1395
|
+
hasRuntimeDefault: false;
|
|
1396
|
+
enumValues: undefined;
|
|
1397
|
+
baseColumn: never;
|
|
1398
|
+
identity: undefined;
|
|
1399
|
+
generated: undefined;
|
|
1400
|
+
}, {}, {}>;
|
|
1401
|
+
accountIndex: import("drizzle-orm/pg-core").PgColumn<{
|
|
1402
|
+
name: "account_index";
|
|
1403
|
+
tableName: "key_rings";
|
|
1404
|
+
dataType: "number";
|
|
1405
|
+
columnType: "PgInteger";
|
|
1406
|
+
data: number;
|
|
1407
|
+
driverParam: string | number;
|
|
1408
|
+
notNull: true;
|
|
1409
|
+
hasDefault: true;
|
|
1410
|
+
isPrimaryKey: false;
|
|
1411
|
+
isAutoincrement: false;
|
|
1412
|
+
hasRuntimeDefault: false;
|
|
1413
|
+
enumValues: undefined;
|
|
1414
|
+
baseColumn: never;
|
|
1415
|
+
identity: undefined;
|
|
1416
|
+
generated: undefined;
|
|
1417
|
+
}, {}, {}>;
|
|
1418
|
+
createdAt: import("drizzle-orm/pg-core").PgColumn<{
|
|
1419
|
+
name: "created_at";
|
|
1420
|
+
tableName: "key_rings";
|
|
1421
|
+
dataType: "string";
|
|
1422
|
+
columnType: "PgText";
|
|
1423
|
+
data: string;
|
|
1424
|
+
driverParam: string;
|
|
1425
|
+
notNull: true;
|
|
1426
|
+
hasDefault: true;
|
|
1427
|
+
isPrimaryKey: false;
|
|
1428
|
+
isAutoincrement: false;
|
|
1429
|
+
hasRuntimeDefault: false;
|
|
1430
|
+
enumValues: [string, ...string[]];
|
|
1431
|
+
baseColumn: never;
|
|
1432
|
+
identity: undefined;
|
|
1433
|
+
generated: undefined;
|
|
1434
|
+
}, {}, {}>;
|
|
1435
|
+
};
|
|
1436
|
+
dialect: "pg";
|
|
1437
|
+
}>;
|
|
1438
|
+
/**
|
|
1439
|
+
* Pre-derived address pool — for Ed25519 chains that need offline derivation.
|
|
1440
|
+
* Addresses are derived in batches and assigned on demand.
|
|
1441
|
+
*/
|
|
1442
|
+
export declare const addressPool: import("drizzle-orm/pg-core").PgTableWithColumns<{
|
|
1443
|
+
name: "address_pool";
|
|
1444
|
+
schema: undefined;
|
|
1445
|
+
columns: {
|
|
1446
|
+
id: import("drizzle-orm/pg-core").PgColumn<{
|
|
1447
|
+
name: "id";
|
|
1448
|
+
tableName: "address_pool";
|
|
1449
|
+
dataType: "number";
|
|
1450
|
+
columnType: "PgSerial";
|
|
1451
|
+
data: number;
|
|
1452
|
+
driverParam: number;
|
|
1453
|
+
notNull: true;
|
|
1454
|
+
hasDefault: true;
|
|
1455
|
+
isPrimaryKey: true;
|
|
1456
|
+
isAutoincrement: false;
|
|
1457
|
+
hasRuntimeDefault: false;
|
|
1458
|
+
enumValues: undefined;
|
|
1459
|
+
baseColumn: never;
|
|
1460
|
+
identity: undefined;
|
|
1461
|
+
generated: undefined;
|
|
1462
|
+
}, {}, {}>;
|
|
1463
|
+
keyRingId: import("drizzle-orm/pg-core").PgColumn<{
|
|
1464
|
+
name: "key_ring_id";
|
|
1465
|
+
tableName: "address_pool";
|
|
1466
|
+
dataType: "string";
|
|
1467
|
+
columnType: "PgText";
|
|
1468
|
+
data: string;
|
|
1469
|
+
driverParam: string;
|
|
1470
|
+
notNull: true;
|
|
1471
|
+
hasDefault: false;
|
|
1472
|
+
isPrimaryKey: false;
|
|
1473
|
+
isAutoincrement: false;
|
|
1474
|
+
hasRuntimeDefault: false;
|
|
1475
|
+
enumValues: [string, ...string[]];
|
|
1476
|
+
baseColumn: never;
|
|
1477
|
+
identity: undefined;
|
|
1478
|
+
generated: undefined;
|
|
1479
|
+
}, {}, {}>;
|
|
1480
|
+
derivationIndex: import("drizzle-orm/pg-core").PgColumn<{
|
|
1481
|
+
name: "derivation_index";
|
|
1482
|
+
tableName: "address_pool";
|
|
1483
|
+
dataType: "number";
|
|
1484
|
+
columnType: "PgInteger";
|
|
1485
|
+
data: number;
|
|
1486
|
+
driverParam: string | number;
|
|
1487
|
+
notNull: true;
|
|
1488
|
+
hasDefault: false;
|
|
1489
|
+
isPrimaryKey: false;
|
|
1490
|
+
isAutoincrement: false;
|
|
1491
|
+
hasRuntimeDefault: false;
|
|
1492
|
+
enumValues: undefined;
|
|
1493
|
+
baseColumn: never;
|
|
1494
|
+
identity: undefined;
|
|
1495
|
+
generated: undefined;
|
|
1496
|
+
}, {}, {}>;
|
|
1497
|
+
publicKey: import("drizzle-orm/pg-core").PgColumn<{
|
|
1498
|
+
name: "public_key";
|
|
1499
|
+
tableName: "address_pool";
|
|
1500
|
+
dataType: "string";
|
|
1501
|
+
columnType: "PgText";
|
|
1502
|
+
data: string;
|
|
1503
|
+
driverParam: string;
|
|
1504
|
+
notNull: true;
|
|
1505
|
+
hasDefault: false;
|
|
1506
|
+
isPrimaryKey: false;
|
|
1507
|
+
isAutoincrement: false;
|
|
1508
|
+
hasRuntimeDefault: false;
|
|
1509
|
+
enumValues: [string, ...string[]];
|
|
1510
|
+
baseColumn: never;
|
|
1511
|
+
identity: undefined;
|
|
1512
|
+
generated: undefined;
|
|
1513
|
+
}, {}, {}>;
|
|
1514
|
+
address: import("drizzle-orm/pg-core").PgColumn<{
|
|
1515
|
+
name: "address";
|
|
1516
|
+
tableName: "address_pool";
|
|
1517
|
+
dataType: "string";
|
|
1518
|
+
columnType: "PgText";
|
|
1519
|
+
data: string;
|
|
1520
|
+
driverParam: string;
|
|
1521
|
+
notNull: true;
|
|
1522
|
+
hasDefault: false;
|
|
1523
|
+
isPrimaryKey: false;
|
|
1524
|
+
isAutoincrement: false;
|
|
1525
|
+
hasRuntimeDefault: false;
|
|
1526
|
+
enumValues: [string, ...string[]];
|
|
1527
|
+
baseColumn: never;
|
|
1528
|
+
identity: undefined;
|
|
1529
|
+
generated: undefined;
|
|
1530
|
+
}, {}, {}>;
|
|
1531
|
+
assignedTo: import("drizzle-orm/pg-core").PgColumn<{
|
|
1532
|
+
name: "assigned_to";
|
|
1533
|
+
tableName: "address_pool";
|
|
1534
|
+
dataType: "string";
|
|
1535
|
+
columnType: "PgText";
|
|
1536
|
+
data: string;
|
|
1537
|
+
driverParam: string;
|
|
1538
|
+
notNull: false;
|
|
1539
|
+
hasDefault: false;
|
|
1540
|
+
isPrimaryKey: false;
|
|
1541
|
+
isAutoincrement: false;
|
|
1542
|
+
hasRuntimeDefault: false;
|
|
1543
|
+
enumValues: [string, ...string[]];
|
|
1544
|
+
baseColumn: never;
|
|
1545
|
+
identity: undefined;
|
|
1546
|
+
generated: undefined;
|
|
1547
|
+
}, {}, {}>;
|
|
1548
|
+
createdAt: import("drizzle-orm/pg-core").PgColumn<{
|
|
1549
|
+
name: "created_at";
|
|
1550
|
+
tableName: "address_pool";
|
|
1551
|
+
dataType: "string";
|
|
1552
|
+
columnType: "PgText";
|
|
1553
|
+
data: string;
|
|
1554
|
+
driverParam: string;
|
|
1555
|
+
notNull: true;
|
|
1556
|
+
hasDefault: true;
|
|
1557
|
+
isPrimaryKey: false;
|
|
1558
|
+
isAutoincrement: false;
|
|
1559
|
+
hasRuntimeDefault: false;
|
|
1560
|
+
enumValues: [string, ...string[]];
|
|
1561
|
+
baseColumn: never;
|
|
1562
|
+
identity: undefined;
|
|
1563
|
+
generated: undefined;
|
|
1564
|
+
}, {}, {}>;
|
|
1565
|
+
};
|
|
1566
|
+
dialect: "pg";
|
|
1567
|
+
}>;
|
package/dist/db/schema/crypto.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { sql } from "drizzle-orm";
|
|
2
|
-
import { boolean, index, integer, pgTable, primaryKey, text } from "drizzle-orm/pg-core";
|
|
2
|
+
import { boolean, index, integer, pgTable, primaryKey, serial, text, uniqueIndex } from "drizzle-orm/pg-core";
|
|
3
3
|
/**
|
|
4
4
|
* Crypto payment charges — tracks the lifecycle of each payment.
|
|
5
5
|
* reference_id is the charge ID (e.g. "btc:bc1q...").
|
|
@@ -82,6 +82,9 @@ export const paymentMethods = pgTable("payment_methods", {
|
|
|
82
82
|
oracleAssetId: text("oracle_asset_id"), // CoinGecko slug (e.g. "bitcoin", "tron"). Null = stablecoin (1:1 USD) or use token symbol fallback.
|
|
83
83
|
confirmations: integer("confirmations").notNull().default(1),
|
|
84
84
|
nextIndex: integer("next_index").notNull().default(0), // atomic derivation counter, never reuses
|
|
85
|
+
keyRingId: text("key_ring_id"), // FK to key_rings.id (nullable during migration)
|
|
86
|
+
encoding: text("encoding"), // address encoding override (e.g. "bech32", "p2pkh", "evm")
|
|
87
|
+
pluginId: text("plugin_id"), // plugin identifier (e.g. "evm", "utxo", "solana")
|
|
85
88
|
createdAt: text("created_at").notNull().default(sql `(now())`),
|
|
86
89
|
});
|
|
87
90
|
/**
|
|
@@ -134,3 +137,32 @@ export const watcherProcessed = pgTable("watcher_processed", {
|
|
|
134
137
|
txId: text("tx_id").notNull(),
|
|
135
138
|
processedAt: text("processed_at").notNull().default(sql `(now())`),
|
|
136
139
|
}, (table) => [primaryKey({ columns: [table.watcherId, table.txId] })]);
|
|
140
|
+
/**
|
|
141
|
+
* Key rings — decouples key material (xpub/seed) from payment methods.
|
|
142
|
+
* Each key ring maps to a BIP-44 coin type + account index.
|
|
143
|
+
*/
|
|
144
|
+
export const keyRings = pgTable("key_rings", {
|
|
145
|
+
id: text("id").primaryKey(),
|
|
146
|
+
curve: text("curve").notNull(), // "secp256k1" | "ed25519"
|
|
147
|
+
derivationScheme: text("derivation_scheme").notNull(), // "bip32" | "slip10" | "ed25519-hd"
|
|
148
|
+
derivationMode: text("derivation_mode").notNull().default("on-demand"), // "on-demand" | "pre-derived"
|
|
149
|
+
keyMaterial: text("key_material").notNull().default("{}"), // JSON: { xpub: "..." }
|
|
150
|
+
coinType: integer("coin_type").notNull(), // BIP-44 coin type
|
|
151
|
+
accountIndex: integer("account_index").notNull().default(0),
|
|
152
|
+
createdAt: text("created_at").notNull().default(sql `(now())`),
|
|
153
|
+
}, (table) => [uniqueIndex("key_rings_path_unique").on(table.coinType, table.accountIndex)]);
|
|
154
|
+
/**
|
|
155
|
+
* Pre-derived address pool — for Ed25519 chains that need offline derivation.
|
|
156
|
+
* Addresses are derived in batches and assigned on demand.
|
|
157
|
+
*/
|
|
158
|
+
export const addressPool = pgTable("address_pool", {
|
|
159
|
+
id: serial("id").primaryKey(),
|
|
160
|
+
keyRingId: text("key_ring_id")
|
|
161
|
+
.notNull()
|
|
162
|
+
.references(() => keyRings.id),
|
|
163
|
+
derivationIndex: integer("derivation_index").notNull(),
|
|
164
|
+
publicKey: text("public_key").notNull(),
|
|
165
|
+
address: text("address").notNull(),
|
|
166
|
+
assignedTo: text("assigned_to"), // charge reference or tenant ID
|
|
167
|
+
createdAt: text("created_at").notNull().default(sql `(now())`),
|
|
168
|
+
}, (table) => [uniqueIndex("address_pool_ring_index").on(table.keyRingId, table.derivationIndex)]);
|
|
@@ -92,7 +92,7 @@ export declare const snapshots: import("drizzle-orm/pg-core").PgTableWithColumns
|
|
|
92
92
|
tableName: "snapshots";
|
|
93
93
|
dataType: "string";
|
|
94
94
|
columnType: "PgText";
|
|
95
|
-
data: "
|
|
95
|
+
data: "on-demand" | "nightly" | "pre-restore";
|
|
96
96
|
driverParam: string;
|
|
97
97
|
notNull: true;
|
|
98
98
|
hasDefault: true;
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
-- Key rings: decouples key material from payment methods
|
|
2
|
+
CREATE TABLE IF NOT EXISTS "key_rings" (
|
|
3
|
+
"id" text PRIMARY KEY,
|
|
4
|
+
"curve" text NOT NULL,
|
|
5
|
+
"derivation_scheme" text NOT NULL,
|
|
6
|
+
"derivation_mode" text NOT NULL DEFAULT 'on-demand',
|
|
7
|
+
"key_material" text NOT NULL DEFAULT '{}',
|
|
8
|
+
"coin_type" integer NOT NULL,
|
|
9
|
+
"account_index" integer NOT NULL DEFAULT 0,
|
|
10
|
+
"created_at" text NOT NULL DEFAULT (now())
|
|
11
|
+
);
|
|
12
|
+
--> statement-breakpoint
|
|
13
|
+
CREATE UNIQUE INDEX IF NOT EXISTS "key_rings_path_unique" ON "key_rings" ("coin_type", "account_index");
|
|
14
|
+
--> statement-breakpoint
|
|
15
|
+
|
|
16
|
+
-- Pre-derived address pool (for Ed25519 chains)
|
|
17
|
+
CREATE TABLE IF NOT EXISTS "address_pool" (
|
|
18
|
+
"id" serial PRIMARY KEY,
|
|
19
|
+
"key_ring_id" text NOT NULL REFERENCES "key_rings"("id"),
|
|
20
|
+
"derivation_index" integer NOT NULL,
|
|
21
|
+
"public_key" text NOT NULL,
|
|
22
|
+
"address" text NOT NULL,
|
|
23
|
+
"assigned_to" text,
|
|
24
|
+
"created_at" text NOT NULL DEFAULT (now())
|
|
25
|
+
);
|
|
26
|
+
--> statement-breakpoint
|
|
27
|
+
CREATE UNIQUE INDEX IF NOT EXISTS "address_pool_ring_index" ON "address_pool" ("key_ring_id", "derivation_index");
|
|
28
|
+
--> statement-breakpoint
|
|
29
|
+
|
|
30
|
+
-- Add new columns to payment_methods
|
|
31
|
+
ALTER TABLE "payment_methods" ADD COLUMN IF NOT EXISTS "key_ring_id" text REFERENCES "key_rings"("id");
|
|
32
|
+
--> statement-breakpoint
|
|
33
|
+
ALTER TABLE "payment_methods" ADD COLUMN IF NOT EXISTS "encoding" text;
|
|
34
|
+
--> statement-breakpoint
|
|
35
|
+
ALTER TABLE "payment_methods" ADD COLUMN IF NOT EXISTS "plugin_id" text;
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
-- Create key rings from existing payment method xpubs
|
|
2
|
+
-- Each unique (coin_type via path_allocations) gets a key ring
|
|
3
|
+
|
|
4
|
+
-- EVM chains (coin type 60)
|
|
5
|
+
INSERT INTO "key_rings" ("id", "curve", "derivation_scheme", "derivation_mode", "key_material", "coin_type", "account_index")
|
|
6
|
+
SELECT DISTINCT 'evm-main', 'secp256k1', 'bip32', 'on-demand',
|
|
7
|
+
json_build_object('xpub', pm.xpub)::text,
|
|
8
|
+
pa.coin_type, pa.account_index
|
|
9
|
+
FROM path_allocations pa
|
|
10
|
+
JOIN payment_methods pm ON pm.id = pa.chain_id
|
|
11
|
+
WHERE pa.coin_type = 60
|
|
12
|
+
LIMIT 1
|
|
13
|
+
ON CONFLICT DO NOTHING;
|
|
14
|
+
--> statement-breakpoint
|
|
15
|
+
|
|
16
|
+
-- BTC (coin type 0)
|
|
17
|
+
INSERT INTO "key_rings" ("id", "curve", "derivation_scheme", "derivation_mode", "key_material", "coin_type", "account_index")
|
|
18
|
+
SELECT DISTINCT 'btc-main', 'secp256k1', 'bip32', 'on-demand',
|
|
19
|
+
json_build_object('xpub', pm.xpub)::text,
|
|
20
|
+
pa.coin_type, pa.account_index
|
|
21
|
+
FROM path_allocations pa
|
|
22
|
+
JOIN payment_methods pm ON pm.id = pa.chain_id
|
|
23
|
+
WHERE pa.coin_type = 0
|
|
24
|
+
LIMIT 1
|
|
25
|
+
ON CONFLICT DO NOTHING;
|
|
26
|
+
--> statement-breakpoint
|
|
27
|
+
|
|
28
|
+
-- LTC (coin type 2)
|
|
29
|
+
INSERT INTO "key_rings" ("id", "curve", "derivation_scheme", "derivation_mode", "key_material", "coin_type", "account_index")
|
|
30
|
+
SELECT DISTINCT 'ltc-main', 'secp256k1', 'bip32', 'on-demand',
|
|
31
|
+
json_build_object('xpub', pm.xpub)::text,
|
|
32
|
+
pa.coin_type, pa.account_index
|
|
33
|
+
FROM path_allocations pa
|
|
34
|
+
JOIN payment_methods pm ON pm.id = pa.chain_id
|
|
35
|
+
WHERE pa.coin_type = 2
|
|
36
|
+
LIMIT 1
|
|
37
|
+
ON CONFLICT DO NOTHING;
|
|
38
|
+
--> statement-breakpoint
|
|
39
|
+
|
|
40
|
+
-- DOGE (coin type 3)
|
|
41
|
+
INSERT INTO "key_rings" ("id", "curve", "derivation_scheme", "derivation_mode", "key_material", "coin_type", "account_index")
|
|
42
|
+
SELECT DISTINCT 'doge-main', 'secp256k1', 'bip32', 'on-demand',
|
|
43
|
+
json_build_object('xpub', pm.xpub)::text,
|
|
44
|
+
pa.coin_type, pa.account_index
|
|
45
|
+
FROM path_allocations pa
|
|
46
|
+
JOIN payment_methods pm ON pm.id = pa.chain_id
|
|
47
|
+
WHERE pa.coin_type = 3
|
|
48
|
+
LIMIT 1
|
|
49
|
+
ON CONFLICT DO NOTHING;
|
|
50
|
+
--> statement-breakpoint
|
|
51
|
+
|
|
52
|
+
-- TRON (coin type 195)
|
|
53
|
+
INSERT INTO "key_rings" ("id", "curve", "derivation_scheme", "derivation_mode", "key_material", "coin_type", "account_index")
|
|
54
|
+
SELECT DISTINCT 'tron-main', 'secp256k1', 'bip32', 'on-demand',
|
|
55
|
+
json_build_object('xpub', pm.xpub)::text,
|
|
56
|
+
pa.coin_type, pa.account_index
|
|
57
|
+
FROM path_allocations pa
|
|
58
|
+
JOIN payment_methods pm ON pm.id = pa.chain_id
|
|
59
|
+
WHERE pa.coin_type = 195
|
|
60
|
+
LIMIT 1
|
|
61
|
+
ON CONFLICT DO NOTHING;
|
|
62
|
+
--> statement-breakpoint
|
|
63
|
+
|
|
64
|
+
-- Backfill payment_methods with key_ring_id, encoding, plugin_id
|
|
65
|
+
UPDATE payment_methods SET
|
|
66
|
+
key_ring_id = CASE
|
|
67
|
+
WHEN chain IN ('arbitrum','avalanche','base','base-sepolia','bsc','optimism','polygon','sepolia') THEN 'evm-main'
|
|
68
|
+
WHEN chain = 'bitcoin' THEN 'btc-main'
|
|
69
|
+
WHEN chain = 'litecoin' THEN 'ltc-main'
|
|
70
|
+
WHEN chain = 'dogecoin' THEN 'doge-main'
|
|
71
|
+
WHEN chain = 'tron' THEN 'tron-main'
|
|
72
|
+
END,
|
|
73
|
+
encoding = address_type,
|
|
74
|
+
plugin_id = watcher_type
|
|
75
|
+
WHERE key_ring_id IS NULL;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@wopr-network/platform-core",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.65.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -72,6 +72,10 @@
|
|
|
72
72
|
"./api/routes/verify-email": "./dist/api/routes/verify-email.js",
|
|
73
73
|
"./api/routes/ws-auth": "./dist/api/routes/ws-auth.js",
|
|
74
74
|
"./trpc": "./dist/trpc/index.js",
|
|
75
|
+
"./crypto-plugin": {
|
|
76
|
+
"import": "./dist/billing/crypto/plugin/index.js",
|
|
77
|
+
"types": "./dist/billing/crypto/plugin/index.d.ts"
|
|
78
|
+
},
|
|
75
79
|
"./*": "./dist/*.js"
|
|
76
80
|
},
|
|
77
81
|
"scripts": {
|
|
@@ -33,6 +33,15 @@ export {
|
|
|
33
33
|
export * from "./oracle/index.js";
|
|
34
34
|
export type { IPaymentMethodStore, PaymentMethodRecord } from "./payment-method-store.js";
|
|
35
35
|
export { DrizzlePaymentMethodStore } from "./payment-method-store.js";
|
|
36
|
+
export type {
|
|
37
|
+
IAddressEncoder,
|
|
38
|
+
IChainPlugin,
|
|
39
|
+
IChainWatcher,
|
|
40
|
+
ICurveDeriver,
|
|
41
|
+
ISweepStrategy,
|
|
42
|
+
PaymentEvent,
|
|
43
|
+
} from "./plugin/index.js";
|
|
44
|
+
export { PluginRegistry } from "./plugin/index.js";
|
|
36
45
|
export type { CryptoCharge, CryptoChargeStatus, CryptoPaymentState } from "./types.js";
|
|
37
46
|
export type { UnifiedCheckoutDeps, UnifiedCheckoutResult } from "./unified-checkout.js";
|
|
38
47
|
export { createUnifiedCheckout, MIN_CHECKOUT_USD as MIN_PAYMENT_USD, MIN_CHECKOUT_USD } from "./unified-checkout.js";
|
|
@@ -364,6 +364,9 @@ export function createKeyServerApp(deps: KeyServerDeps): Hono {
|
|
|
364
364
|
watcherType: body.watcher_type ?? "evm",
|
|
365
365
|
oracleAssetId: body.oracle_asset_id ?? null,
|
|
366
366
|
confirmations: body.confirmations ?? 6,
|
|
367
|
+
keyRingId: null,
|
|
368
|
+
encoding: null,
|
|
369
|
+
pluginId: null,
|
|
367
370
|
});
|
|
368
371
|
|
|
369
372
|
// Record the path allocation (idempotent — ignore if already exists)
|
|
@@ -22,6 +22,9 @@ export interface PaymentMethodRecord {
|
|
|
22
22
|
watcherType: string;
|
|
23
23
|
oracleAssetId: string | null;
|
|
24
24
|
confirmations: number;
|
|
25
|
+
keyRingId: string | null;
|
|
26
|
+
encoding: string | null;
|
|
27
|
+
pluginId: string | null;
|
|
25
28
|
}
|
|
26
29
|
|
|
27
30
|
export interface IPaymentMethodStore {
|
|
@@ -98,6 +101,9 @@ export class DrizzlePaymentMethodStore implements IPaymentMethodStore {
|
|
|
98
101
|
watcherType: method.watcherType,
|
|
99
102
|
oracleAssetId: method.oracleAssetId,
|
|
100
103
|
confirmations: method.confirmations,
|
|
104
|
+
keyRingId: method.keyRingId,
|
|
105
|
+
encoding: method.encoding,
|
|
106
|
+
pluginId: method.pluginId,
|
|
101
107
|
})
|
|
102
108
|
.onConflictDoUpdate({
|
|
103
109
|
target: paymentMethods.id,
|
|
@@ -119,6 +125,9 @@ export class DrizzlePaymentMethodStore implements IPaymentMethodStore {
|
|
|
119
125
|
watcherType: method.watcherType,
|
|
120
126
|
oracleAssetId: method.oracleAssetId,
|
|
121
127
|
confirmations: method.confirmations,
|
|
128
|
+
keyRingId: method.keyRingId,
|
|
129
|
+
encoding: method.encoding,
|
|
130
|
+
pluginId: method.pluginId,
|
|
122
131
|
},
|
|
123
132
|
});
|
|
124
133
|
}
|
|
@@ -164,5 +173,8 @@ function toRecord(row: typeof paymentMethods.$inferSelect): PaymentMethodRecord
|
|
|
164
173
|
watcherType: row.watcherType,
|
|
165
174
|
oracleAssetId: row.oracleAssetId,
|
|
166
175
|
confirmations: row.confirmations,
|
|
176
|
+
keyRingId: row.keyRingId,
|
|
177
|
+
encoding: row.encoding,
|
|
178
|
+
pluginId: row.pluginId,
|
|
167
179
|
};
|
|
168
180
|
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import type { IChainPlugin, PaymentEvent, WatcherOpts } from "../interfaces.js";
|
|
3
|
+
import { PluginRegistry } from "../registry.js";
|
|
4
|
+
|
|
5
|
+
describe("plugin integration — registry → watcher → events", () => {
|
|
6
|
+
it("full lifecycle: register → create watcher → poll → events", async () => {
|
|
7
|
+
const mockEvent: PaymentEvent = {
|
|
8
|
+
chain: "test",
|
|
9
|
+
token: "TEST",
|
|
10
|
+
from: "0xsender",
|
|
11
|
+
to: "0xreceiver",
|
|
12
|
+
rawAmount: "1000",
|
|
13
|
+
amountUsdCents: 100,
|
|
14
|
+
txHash: "0xhash",
|
|
15
|
+
blockNumber: 42,
|
|
16
|
+
confirmations: 6,
|
|
17
|
+
confirmationsRequired: 6,
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
const plugin: IChainPlugin = {
|
|
21
|
+
pluginId: "test",
|
|
22
|
+
supportedCurve: "secp256k1",
|
|
23
|
+
encoders: {},
|
|
24
|
+
createWatcher: (_opts: WatcherOpts) => ({
|
|
25
|
+
init: async () => {},
|
|
26
|
+
poll: async () => [mockEvent],
|
|
27
|
+
setWatchedAddresses: () => {},
|
|
28
|
+
getCursor: () => 42,
|
|
29
|
+
stop: () => {},
|
|
30
|
+
}),
|
|
31
|
+
createSweeper: () => ({ scan: async () => [], sweep: async () => [] }),
|
|
32
|
+
version: 1,
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
const registry = new PluginRegistry();
|
|
36
|
+
registry.register(plugin);
|
|
37
|
+
|
|
38
|
+
const resolved = registry.getOrThrow("test");
|
|
39
|
+
const watcher = resolved.createWatcher({
|
|
40
|
+
rpcUrl: "http://localhost:8545",
|
|
41
|
+
rpcHeaders: {},
|
|
42
|
+
oracle: {
|
|
43
|
+
getPrice: async () => ({ priceMicros: 3500_000000 }),
|
|
44
|
+
},
|
|
45
|
+
cursorStore: {
|
|
46
|
+
get: async () => null,
|
|
47
|
+
save: async () => {},
|
|
48
|
+
getConfirmationCount: async () => null,
|
|
49
|
+
saveConfirmationCount: async () => {},
|
|
50
|
+
},
|
|
51
|
+
token: "TEST",
|
|
52
|
+
chain: "test",
|
|
53
|
+
decimals: 18,
|
|
54
|
+
confirmations: 6,
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
await watcher.init();
|
|
58
|
+
const events = await watcher.poll();
|
|
59
|
+
expect(events).toHaveLength(1);
|
|
60
|
+
expect(events[0].txHash).toBe("0xhash");
|
|
61
|
+
expect(watcher.getCursor()).toBe(42);
|
|
62
|
+
watcher.stop();
|
|
63
|
+
});
|
|
64
|
+
});
|