@wopr-network/platform-core 1.63.1 → 1.64.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/__tests__/address-gen.test.js +191 -90
- package/dist/billing/crypto/__tests__/key-server.test.js +3 -0
- package/dist/billing/crypto/address-gen.js +32 -0
- package/dist/billing/crypto/evm/eth-watcher.js +52 -41
- package/dist/billing/crypto/evm/watcher.js +5 -11
- package/dist/billing/crypto/key-server-entry.js +8 -1
- package/dist/billing/crypto/key-server.js +19 -14
- package/dist/billing/crypto/oracle/coingecko.js +3 -0
- package/dist/billing/crypto/payment-method-store.d.ts +2 -0
- package/dist/billing/crypto/payment-method-store.js +5 -0
- package/dist/billing/crypto/tron/address-convert.js +15 -5
- package/dist/billing/crypto/watcher-service.js +9 -9
- package/dist/db/schema/crypto.d.ts +34 -0
- package/dist/db/schema/crypto.js +3 -1
- package/docs/superpowers/plans/2026-03-24-crypto-plugin-phase1.md +697 -0
- package/docs/superpowers/specs/2026-03-24-crypto-plugin-architecture-design.md +309 -0
- package/drizzle/migrations/0022_oracle_asset_id_column.sql +23 -0
- package/drizzle/migrations/0022_rpc_headers_column.sql +1 -0
- package/drizzle/migrations/meta/_journal.json +14 -0
- package/package.json +1 -1
- package/src/billing/crypto/__tests__/address-gen.test.ts +207 -88
- package/src/billing/crypto/__tests__/key-server.test.ts +3 -0
- package/src/billing/crypto/address-gen.ts +31 -0
- package/src/billing/crypto/evm/eth-watcher.ts +64 -47
- package/src/billing/crypto/evm/watcher.ts +8 -9
- package/src/billing/crypto/key-server-entry.ts +7 -1
- package/src/billing/crypto/key-server.ts +26 -19
- package/src/billing/crypto/oracle/coingecko.ts +3 -0
- package/src/billing/crypto/payment-method-store.ts +7 -0
- package/src/billing/crypto/tron/address-convert.ts +13 -4
- package/src/billing/crypto/watcher-service.ts +12 -11
- package/src/db/schema/crypto.ts +3 -1
|
@@ -44,11 +44,13 @@ export class DrizzlePaymentMethodStore {
|
|
|
44
44
|
displayOrder: method.displayOrder,
|
|
45
45
|
iconUrl: method.iconUrl,
|
|
46
46
|
rpcUrl: method.rpcUrl,
|
|
47
|
+
rpcHeaders: method.rpcHeaders ?? "{}",
|
|
47
48
|
oracleAddress: method.oracleAddress,
|
|
48
49
|
xpub: method.xpub,
|
|
49
50
|
addressType: method.addressType,
|
|
50
51
|
encodingParams: method.encodingParams,
|
|
51
52
|
watcherType: method.watcherType,
|
|
53
|
+
oracleAssetId: method.oracleAssetId,
|
|
52
54
|
confirmations: method.confirmations,
|
|
53
55
|
})
|
|
54
56
|
.onConflictDoUpdate({
|
|
@@ -69,6 +71,7 @@ export class DrizzlePaymentMethodStore {
|
|
|
69
71
|
addressType: method.addressType,
|
|
70
72
|
encodingParams: method.encodingParams,
|
|
71
73
|
watcherType: method.watcherType,
|
|
74
|
+
oracleAssetId: method.oracleAssetId,
|
|
72
75
|
confirmations: method.confirmations,
|
|
73
76
|
},
|
|
74
77
|
});
|
|
@@ -103,11 +106,13 @@ function toRecord(row) {
|
|
|
103
106
|
displayOrder: row.displayOrder,
|
|
104
107
|
iconUrl: row.iconUrl,
|
|
105
108
|
rpcUrl: row.rpcUrl,
|
|
109
|
+
rpcHeaders: row.rpcHeaders ?? "{}",
|
|
106
110
|
oracleAddress: row.oracleAddress,
|
|
107
111
|
xpub: row.xpub,
|
|
108
112
|
addressType: row.addressType,
|
|
109
113
|
encodingParams: row.encodingParams,
|
|
110
114
|
watcherType: row.watcherType,
|
|
115
|
+
oracleAssetId: row.oracleAssetId,
|
|
111
116
|
confirmations: row.confirmations,
|
|
112
117
|
};
|
|
113
118
|
}
|
|
@@ -8,6 +8,13 @@
|
|
|
8
8
|
import { sha256 } from "@noble/hashes/sha2.js";
|
|
9
9
|
const BASE58_ALPHABET = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz";
|
|
10
10
|
function base58decode(s) {
|
|
11
|
+
// Count leading '1' characters (each represents a 0x00 byte)
|
|
12
|
+
let leadingZeros = 0;
|
|
13
|
+
for (const ch of s) {
|
|
14
|
+
if (ch !== "1")
|
|
15
|
+
break;
|
|
16
|
+
leadingZeros++;
|
|
17
|
+
}
|
|
11
18
|
let num = 0n;
|
|
12
19
|
for (const ch of s) {
|
|
13
20
|
const idx = BASE58_ALPHABET.indexOf(ch);
|
|
@@ -15,11 +22,14 @@ function base58decode(s) {
|
|
|
15
22
|
throw new Error(`Invalid base58 character: ${ch}`);
|
|
16
23
|
num = num * 58n + BigInt(idx);
|
|
17
24
|
}
|
|
18
|
-
const hex = num.toString(16).padStart(
|
|
19
|
-
const
|
|
20
|
-
for (let i = 0; i <
|
|
21
|
-
|
|
22
|
-
|
|
25
|
+
const hex = num.toString(16).padStart(2, "0");
|
|
26
|
+
const dataBytes = new Uint8Array(hex.length / 2);
|
|
27
|
+
for (let i = 0; i < dataBytes.length; i++)
|
|
28
|
+
dataBytes[i] = Number.parseInt(hex.slice(i * 2, i * 2 + 2), 16);
|
|
29
|
+
// Prepend leading zero bytes
|
|
30
|
+
const result = new Uint8Array(leadingZeros + dataBytes.length);
|
|
31
|
+
result.set(dataBytes, leadingZeros);
|
|
32
|
+
return result;
|
|
23
33
|
}
|
|
24
34
|
/**
|
|
25
35
|
* Convert a Tron T... address to 0x hex (20 bytes, no 0x41 prefix).
|
|
@@ -274,16 +274,16 @@ export async function startWatchers(opts) {
|
|
|
274
274
|
const nativeEvmMethods = evmMethods.filter((m) => m.type === "native");
|
|
275
275
|
const erc20Methods = evmMethods.filter((m) => m.type === "erc20" && m.contractAddress);
|
|
276
276
|
const BACKFILL_BLOCKS = 1000; // Scan ~30min of blocks on first deploy to catch missed deposits
|
|
277
|
-
// Address conversion
|
|
278
|
-
//
|
|
279
|
-
//
|
|
280
|
-
const
|
|
281
|
-
const toWatcherAddr = (addr, method) =>
|
|
282
|
-
const fromWatcherAddr = (addr, method) =>
|
|
277
|
+
// Address conversion for EVM-watched chains with non-0x address formats (Tron T...).
|
|
278
|
+
// Only applies to chains routed through the EVM watcher but storing non-hex addresses.
|
|
279
|
+
// UTXO chains (DOGE p2pkh) never enter this path — they use the UTXO watcher.
|
|
280
|
+
const isTronMethod = (method) => (method.addressType === "p2pkh" || method.addressType === "keccak-b58check") && method.chain === "tron";
|
|
281
|
+
const toWatcherAddr = (addr, method) => isTronMethod(method) && isTronAddress(addr) ? tronToHex(addr) : addr;
|
|
282
|
+
const fromWatcherAddr = (addr, method) => isTronMethod(method) ? hexToTron(addr) : addr;
|
|
283
283
|
for (const method of nativeEvmMethods) {
|
|
284
284
|
if (!method.rpcUrl)
|
|
285
285
|
continue;
|
|
286
|
-
const rpcCall = createRpcCaller(method.rpcUrl);
|
|
286
|
+
const rpcCall = createRpcCaller(method.rpcUrl, JSON.parse(method.rpcHeaders ?? "{}"));
|
|
287
287
|
let latestBlock;
|
|
288
288
|
try {
|
|
289
289
|
const latestHex = (await rpcCall("eth_blockNumber", []));
|
|
@@ -352,7 +352,7 @@ export async function startWatchers(opts) {
|
|
|
352
352
|
for (const method of erc20Methods) {
|
|
353
353
|
if (!method.rpcUrl || !method.contractAddress)
|
|
354
354
|
continue;
|
|
355
|
-
const rpcCall = createRpcCaller(method.rpcUrl);
|
|
355
|
+
const rpcCall = createRpcCaller(method.rpcUrl, JSON.parse(method.rpcHeaders ?? "{}"));
|
|
356
356
|
let latestBlock;
|
|
357
357
|
try {
|
|
358
358
|
const latestHex = (await rpcCall("eth_blockNumber", []));
|
|
@@ -370,7 +370,7 @@ export async function startWatchers(opts) {
|
|
|
370
370
|
rpcCall,
|
|
371
371
|
fromBlock: latestBlock,
|
|
372
372
|
watchedAddresses: chainAddresses.map((a) => toWatcherAddr(a, method)),
|
|
373
|
-
contractAddress: method.contractAddress,
|
|
373
|
+
contractAddress: toWatcherAddr(method.contractAddress, method),
|
|
374
374
|
decimals: method.decimals,
|
|
375
375
|
confirmations: method.confirmations,
|
|
376
376
|
cursorStore,
|
|
@@ -631,6 +631,23 @@ export declare const paymentMethods: import("drizzle-orm/pg-core").PgTableWithCo
|
|
|
631
631
|
identity: undefined;
|
|
632
632
|
generated: undefined;
|
|
633
633
|
}, {}, {}>;
|
|
634
|
+
rpcHeaders: import("drizzle-orm/pg-core").PgColumn<{
|
|
635
|
+
name: "rpc_headers";
|
|
636
|
+
tableName: "payment_methods";
|
|
637
|
+
dataType: "string";
|
|
638
|
+
columnType: "PgText";
|
|
639
|
+
data: string;
|
|
640
|
+
driverParam: string;
|
|
641
|
+
notNull: true;
|
|
642
|
+
hasDefault: true;
|
|
643
|
+
isPrimaryKey: false;
|
|
644
|
+
isAutoincrement: false;
|
|
645
|
+
hasRuntimeDefault: false;
|
|
646
|
+
enumValues: [string, ...string[]];
|
|
647
|
+
baseColumn: never;
|
|
648
|
+
identity: undefined;
|
|
649
|
+
generated: undefined;
|
|
650
|
+
}, {}, {}>;
|
|
634
651
|
oracleAddress: import("drizzle-orm/pg-core").PgColumn<{
|
|
635
652
|
name: "oracle_address";
|
|
636
653
|
tableName: "payment_methods";
|
|
@@ -716,6 +733,23 @@ export declare const paymentMethods: import("drizzle-orm/pg-core").PgTableWithCo
|
|
|
716
733
|
identity: undefined;
|
|
717
734
|
generated: undefined;
|
|
718
735
|
}, {}, {}>;
|
|
736
|
+
oracleAssetId: import("drizzle-orm/pg-core").PgColumn<{
|
|
737
|
+
name: "oracle_asset_id";
|
|
738
|
+
tableName: "payment_methods";
|
|
739
|
+
dataType: "string";
|
|
740
|
+
columnType: "PgText";
|
|
741
|
+
data: string;
|
|
742
|
+
driverParam: string;
|
|
743
|
+
notNull: false;
|
|
744
|
+
hasDefault: false;
|
|
745
|
+
isPrimaryKey: false;
|
|
746
|
+
isAutoincrement: false;
|
|
747
|
+
hasRuntimeDefault: false;
|
|
748
|
+
enumValues: [string, ...string[]];
|
|
749
|
+
baseColumn: never;
|
|
750
|
+
identity: undefined;
|
|
751
|
+
generated: undefined;
|
|
752
|
+
}, {}, {}>;
|
|
719
753
|
confirmations: import("drizzle-orm/pg-core").PgColumn<{
|
|
720
754
|
name: "confirmations";
|
|
721
755
|
tableName: "payment_methods";
|
package/dist/db/schema/crypto.js
CHANGED
|
@@ -73,11 +73,13 @@ export const paymentMethods = pgTable("payment_methods", {
|
|
|
73
73
|
displayOrder: integer("display_order").notNull().default(0),
|
|
74
74
|
iconUrl: text("icon_url"),
|
|
75
75
|
rpcUrl: text("rpc_url"), // chain node RPC endpoint
|
|
76
|
+
rpcHeaders: text("rpc_headers").notNull().default("{}"), // JSON: extra headers for RPC calls (e.g. {"TRON-PRO-API-KEY":"xxx"})
|
|
76
77
|
oracleAddress: text("oracle_address"), // Chainlink feed address for price (null = 1:1 stablecoin)
|
|
77
78
|
xpub: text("xpub"), // HD wallet extended public key for deposit address derivation
|
|
78
|
-
addressType: text("address_type").notNull().default("evm"), // "bech32" (BTC/LTC), "p2pkh" (DOGE), "evm" (ETH/ERC20)
|
|
79
|
+
addressType: text("address_type").notNull().default("evm"), // "bech32" (BTC/LTC), "p2pkh" (DOGE/TRX), "evm" (ETH/ERC20)
|
|
79
80
|
encodingParams: text("encoding_params").notNull().default("{}"), // JSON: {"hrp":"bc"}, {"version":"0x1e"}, etc.
|
|
80
81
|
watcherType: text("watcher_type").notNull().default("evm"), // "utxo" (BTC/LTC/DOGE) or "evm" (ETH/ERC20/TRX)
|
|
82
|
+
oracleAssetId: text("oracle_asset_id"), // CoinGecko slug (e.g. "bitcoin", "tron"). Null = stablecoin (1:1 USD) or use token symbol fallback.
|
|
81
83
|
confirmations: integer("confirmations").notNull().default(1),
|
|
82
84
|
nextIndex: integer("next_index").notNull().default(0), // atomic derivation counter, never reuses
|
|
83
85
|
createdAt: text("created_at").notNull().default(sql `(now())`),
|