@wopr-network/platform-core 1.67.0 → 1.68.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.
Files changed (140) hide show
  1. package/dist/auth/better-auth.js +7 -0
  2. package/dist/billing/crypto/btc/checkout.d.ts +4 -0
  3. package/dist/billing/crypto/btc/checkout.js +1 -2
  4. package/dist/billing/crypto/btc/index.d.ts +0 -4
  5. package/dist/billing/crypto/btc/index.js +0 -2
  6. package/dist/billing/crypto/evm/__tests__/checkout.test.js +8 -11
  7. package/dist/billing/crypto/evm/__tests__/eth-checkout.test.js +15 -1
  8. package/dist/billing/crypto/evm/checkout.d.ts +2 -0
  9. package/dist/billing/crypto/evm/checkout.js +1 -2
  10. package/dist/billing/crypto/evm/eth-checkout.d.ts +13 -2
  11. package/dist/billing/crypto/evm/eth-checkout.js +2 -4
  12. package/dist/billing/crypto/evm/eth-settler.d.ts +1 -1
  13. package/dist/billing/crypto/evm/index.d.ts +2 -8
  14. package/dist/billing/crypto/evm/index.js +0 -3
  15. package/dist/billing/crypto/evm/types.d.ts +16 -0
  16. package/dist/billing/crypto/index.d.ts +1 -6
  17. package/dist/billing/crypto/index.js +2 -3
  18. package/dist/billing/crypto/types.d.ts +0 -43
  19. package/dist/billing/crypto/types.js +1 -24
  20. package/dist/email/client.js +16 -0
  21. package/package.json +4 -7
  22. package/src/auth/better-auth.ts +8 -0
  23. package/src/billing/crypto/btc/checkout.ts +3 -2
  24. package/src/billing/crypto/btc/index.ts +0 -4
  25. package/src/billing/crypto/evm/__tests__/checkout.test.ts +10 -12
  26. package/src/billing/crypto/evm/__tests__/eth-checkout.test.ts +17 -1
  27. package/src/billing/crypto/evm/__tests__/eth-settler.test.ts +1 -1
  28. package/src/billing/crypto/evm/checkout.ts +3 -2
  29. package/src/billing/crypto/evm/eth-checkout.ts +15 -6
  30. package/src/billing/crypto/evm/eth-settler.ts +1 -1
  31. package/src/billing/crypto/evm/index.ts +8 -7
  32. package/src/billing/crypto/evm/types.ts +17 -0
  33. package/src/billing/crypto/index.ts +14 -12
  34. package/src/billing/crypto/types.ts +0 -63
  35. package/src/email/client.ts +18 -0
  36. package/dist/billing/crypto/__tests__/address-gen.test.d.ts +0 -1
  37. package/dist/billing/crypto/__tests__/address-gen.test.js +0 -219
  38. package/dist/billing/crypto/__tests__/key-server.test.d.ts +0 -1
  39. package/dist/billing/crypto/__tests__/key-server.test.js +0 -742
  40. package/dist/billing/crypto/__tests__/watcher-service.test.d.ts +0 -1
  41. package/dist/billing/crypto/__tests__/watcher-service.test.js +0 -174
  42. package/dist/billing/crypto/address-gen.d.ts +0 -24
  43. package/dist/billing/crypto/address-gen.js +0 -176
  44. package/dist/billing/crypto/btc/__tests__/watcher.test.d.ts +0 -1
  45. package/dist/billing/crypto/btc/__tests__/watcher.test.js +0 -170
  46. package/dist/billing/crypto/btc/watcher.d.ts +0 -44
  47. package/dist/billing/crypto/btc/watcher.js +0 -118
  48. package/dist/billing/crypto/evm/__tests__/eth-watcher.test.d.ts +0 -1
  49. package/dist/billing/crypto/evm/__tests__/eth-watcher.test.js +0 -167
  50. package/dist/billing/crypto/evm/__tests__/watcher-confirmations.test.d.ts +0 -1
  51. package/dist/billing/crypto/evm/__tests__/watcher-confirmations.test.js +0 -159
  52. package/dist/billing/crypto/evm/__tests__/watcher.test.d.ts +0 -1
  53. package/dist/billing/crypto/evm/__tests__/watcher.test.js +0 -145
  54. package/dist/billing/crypto/evm/eth-watcher.d.ts +0 -66
  55. package/dist/billing/crypto/evm/eth-watcher.js +0 -121
  56. package/dist/billing/crypto/evm/watcher.d.ts +0 -51
  57. package/dist/billing/crypto/evm/watcher.js +0 -156
  58. package/dist/billing/crypto/key-server-entry.d.ts +0 -1
  59. package/dist/billing/crypto/key-server-entry.js +0 -122
  60. package/dist/billing/crypto/key-server.d.ts +0 -32
  61. package/dist/billing/crypto/key-server.js +0 -472
  62. package/dist/billing/crypto/oracle/__tests__/chainlink.test.d.ts +0 -1
  63. package/dist/billing/crypto/oracle/__tests__/chainlink.test.js +0 -83
  64. package/dist/billing/crypto/oracle/__tests__/coingecko.test.d.ts +0 -1
  65. package/dist/billing/crypto/oracle/__tests__/coingecko.test.js +0 -65
  66. package/dist/billing/crypto/oracle/__tests__/composite.test.d.ts +0 -1
  67. package/dist/billing/crypto/oracle/__tests__/composite.test.js +0 -48
  68. package/dist/billing/crypto/oracle/__tests__/convert.test.d.ts +0 -1
  69. package/dist/billing/crypto/oracle/__tests__/convert.test.js +0 -61
  70. package/dist/billing/crypto/oracle/__tests__/fixed.test.d.ts +0 -1
  71. package/dist/billing/crypto/oracle/__tests__/fixed.test.js +0 -20
  72. package/dist/billing/crypto/oracle/chainlink.d.ts +0 -26
  73. package/dist/billing/crypto/oracle/chainlink.js +0 -62
  74. package/dist/billing/crypto/oracle/coingecko.d.ts +0 -22
  75. package/dist/billing/crypto/oracle/coingecko.js +0 -71
  76. package/dist/billing/crypto/oracle/composite.d.ts +0 -14
  77. package/dist/billing/crypto/oracle/composite.js +0 -34
  78. package/dist/billing/crypto/oracle/convert.d.ts +0 -30
  79. package/dist/billing/crypto/oracle/convert.js +0 -51
  80. package/dist/billing/crypto/oracle/fixed.d.ts +0 -10
  81. package/dist/billing/crypto/oracle/fixed.js +0 -22
  82. package/dist/billing/crypto/oracle/index.d.ts +0 -9
  83. package/dist/billing/crypto/oracle/index.js +0 -6
  84. package/dist/billing/crypto/oracle/types.d.ts +0 -22
  85. package/dist/billing/crypto/oracle/types.js +0 -7
  86. package/dist/billing/crypto/plugin/__tests__/integration.test.d.ts +0 -1
  87. package/dist/billing/crypto/plugin/__tests__/integration.test.js +0 -58
  88. package/dist/billing/crypto/plugin/__tests__/interfaces.test.d.ts +0 -1
  89. package/dist/billing/crypto/plugin/__tests__/interfaces.test.js +0 -46
  90. package/dist/billing/crypto/plugin/__tests__/registry.test.d.ts +0 -1
  91. package/dist/billing/crypto/plugin/__tests__/registry.test.js +0 -49
  92. package/dist/billing/crypto/plugin/index.d.ts +0 -2
  93. package/dist/billing/crypto/plugin/index.js +0 -1
  94. package/dist/billing/crypto/plugin/interfaces.d.ts +0 -97
  95. package/dist/billing/crypto/plugin/interfaces.js +0 -2
  96. package/dist/billing/crypto/plugin/registry.d.ts +0 -8
  97. package/dist/billing/crypto/plugin/registry.js +0 -21
  98. package/dist/billing/crypto/plugin-watcher-service.d.ts +0 -32
  99. package/dist/billing/crypto/plugin-watcher-service.js +0 -113
  100. package/dist/billing/crypto/tron/__tests__/address-convert.test.d.ts +0 -1
  101. package/dist/billing/crypto/tron/__tests__/address-convert.test.js +0 -55
  102. package/dist/billing/crypto/tron/address-convert.d.ts +0 -14
  103. package/dist/billing/crypto/tron/address-convert.js +0 -93
  104. package/dist/billing/crypto/watcher-service.d.ts +0 -55
  105. package/dist/billing/crypto/watcher-service.js +0 -438
  106. package/src/billing/crypto/__tests__/address-gen.test.ts +0 -264
  107. package/src/billing/crypto/__tests__/key-server.test.ts +0 -823
  108. package/src/billing/crypto/__tests__/watcher-service.test.ts +0 -242
  109. package/src/billing/crypto/address-gen.ts +0 -185
  110. package/src/billing/crypto/btc/__tests__/watcher.test.ts +0 -201
  111. package/src/billing/crypto/btc/watcher.ts +0 -161
  112. package/src/billing/crypto/evm/__tests__/eth-watcher.test.ts +0 -190
  113. package/src/billing/crypto/evm/__tests__/watcher-confirmations.test.ts +0 -191
  114. package/src/billing/crypto/evm/__tests__/watcher.test.ts +0 -167
  115. package/src/billing/crypto/evm/eth-watcher.ts +0 -182
  116. package/src/billing/crypto/evm/watcher.ts +0 -204
  117. package/src/billing/crypto/key-server-entry.ts +0 -144
  118. package/src/billing/crypto/key-server.ts +0 -617
  119. package/src/billing/crypto/oracle/__tests__/chainlink.test.ts +0 -107
  120. package/src/billing/crypto/oracle/__tests__/coingecko.test.ts +0 -75
  121. package/src/billing/crypto/oracle/__tests__/composite.test.ts +0 -61
  122. package/src/billing/crypto/oracle/__tests__/convert.test.ts +0 -74
  123. package/src/billing/crypto/oracle/__tests__/fixed.test.ts +0 -23
  124. package/src/billing/crypto/oracle/chainlink.ts +0 -86
  125. package/src/billing/crypto/oracle/coingecko.ts +0 -96
  126. package/src/billing/crypto/oracle/composite.ts +0 -35
  127. package/src/billing/crypto/oracle/convert.ts +0 -53
  128. package/src/billing/crypto/oracle/fixed.ts +0 -25
  129. package/src/billing/crypto/oracle/index.ts +0 -9
  130. package/src/billing/crypto/oracle/types.ts +0 -28
  131. package/src/billing/crypto/plugin/__tests__/integration.test.ts +0 -64
  132. package/src/billing/crypto/plugin/__tests__/interfaces.test.ts +0 -51
  133. package/src/billing/crypto/plugin/__tests__/registry.test.ts +0 -58
  134. package/src/billing/crypto/plugin/index.ts +0 -17
  135. package/src/billing/crypto/plugin/interfaces.ts +0 -106
  136. package/src/billing/crypto/plugin/registry.ts +0 -26
  137. package/src/billing/crypto/plugin-watcher-service.ts +0 -148
  138. package/src/billing/crypto/tron/__tests__/address-convert.test.ts +0 -67
  139. package/src/billing/crypto/tron/address-convert.ts +0 -89
  140. package/src/billing/crypto/watcher-service.ts +0 -549
@@ -1,5 +1,4 @@
1
1
  import { Credit } from "../../../credits/credit.js";
2
- import { deriveAddress } from "../address-gen.js";
3
2
  import type { ICryptoChargeRepository } from "../charge-store.js";
4
3
  import { getTokenConfig, tokenAmountFromCents } from "./config.js";
5
4
  import type { StablecoinCheckoutOpts } from "./types.js";
@@ -8,6 +7,8 @@ export const MIN_STABLECOIN_USD = 10;
8
7
 
9
8
  export interface StablecoinCheckoutDeps {
10
9
  chargeStore: Pick<ICryptoChargeRepository, "getNextDerivationIndex" | "createStablecoinCharge">;
10
+ /** HD key derivation function — injected from @wopr-network/platform-crypto-server. */
11
+ deriveAddress: (xpub: string, index: number, encoding: string) => string;
11
12
  xpub: string;
12
13
  }
13
14
 
@@ -47,7 +48,7 @@ export async function createStablecoinCheckout(
47
48
  const maxRetries = 3;
48
49
  for (let attempt = 0; attempt <= maxRetries; attempt++) {
49
50
  const derivationIndex = await deps.chargeStore.getNextDerivationIndex();
50
- const depositAddress = deriveAddress(deps.xpub, derivationIndex, "evm");
51
+ const depositAddress = deps.deriveAddress(deps.xpub, derivationIndex, "evm");
51
52
  const referenceId = `sc:${opts.chain}:${opts.token.toLowerCase()}:${depositAddress.toLowerCase()}`;
52
53
 
53
54
  try {
@@ -1,15 +1,24 @@
1
1
  import { Credit } from "../../../credits/credit.js";
2
- import { deriveAddress } from "../address-gen.js";
3
2
  import type { ICryptoChargeRepository } from "../charge-store.js";
4
- import { centsToNative } from "../oracle/convert.js";
5
- import type { IPriceOracle } from "../oracle/types.js";
6
3
  import type { EvmChain } from "./types.js";
7
4
 
8
5
  export const MIN_ETH_USD = 10;
9
6
 
7
+ /** Price oracle interface — injected from @wopr-network/platform-crypto-server. */
8
+ export interface EthPriceOracle {
9
+ getPrice(asset: string): Promise<{ priceMicros: number }>;
10
+ }
11
+
12
+ /** Convert USD cents to native token base units (e.g. wei for ETH). */
13
+ export type CentsToNativeFn = (cents: number, priceMicros: number, decimals: number) => bigint;
14
+
10
15
  export interface EthCheckoutDeps {
11
16
  chargeStore: Pick<ICryptoChargeRepository, "getNextDerivationIndex" | "createStablecoinCharge">;
12
- oracle: IPriceOracle;
17
+ oracle: EthPriceOracle;
18
+ /** HD key derivation function — injected from @wopr-network/platform-crypto-server. */
19
+ deriveAddress: (xpub: string, index: number, encoding: string) => string;
20
+ /** Convert cents to native base units — injected from @wopr-network/platform-crypto-server. */
21
+ centsToNative: CentsToNativeFn;
13
22
  xpub: string;
14
23
  }
15
24
 
@@ -46,12 +55,12 @@ export async function createEthCheckout(deps: EthCheckoutDeps, opts: EthCheckout
46
55
 
47
56
  const amountUsdCents = Credit.fromDollars(opts.amountUsd).toCentsRounded();
48
57
  const { priceMicros } = await deps.oracle.getPrice("ETH");
49
- const expectedWei = centsToNative(amountUsdCents, priceMicros, 18);
58
+ const expectedWei = deps.centsToNative(amountUsdCents, priceMicros, 18);
50
59
  const maxRetries = 3;
51
60
 
52
61
  for (let attempt = 0; attempt <= maxRetries; attempt++) {
53
62
  const derivationIndex = await deps.chargeStore.getNextDerivationIndex();
54
- const depositAddress = deriveAddress(deps.xpub, derivationIndex, "evm") as `0x${string}`;
63
+ const depositAddress = deps.deriveAddress(deps.xpub, derivationIndex, "evm") as `0x${string}`;
55
64
  const referenceId = `eth:${opts.chain}:${depositAddress}`;
56
65
 
57
66
  try {
@@ -2,7 +2,7 @@ import { Credit } from "../../../credits/credit.js";
2
2
  import type { ILedger } from "../../../credits/ledger.js";
3
3
  import type { ICryptoChargeRepository } from "../charge-store.js";
4
4
  import type { CryptoWebhookResult } from "../types.js";
5
- import type { EthPaymentEvent } from "./eth-watcher.js";
5
+ import type { EthPaymentEvent } from "./types.js";
6
6
 
7
7
  export interface EthSettlerDeps {
8
8
  chargeStore: Pick<ICryptoChargeRepository, "getByDepositAddress" | "updateStatus" | "markCredited">;
@@ -1,23 +1,24 @@
1
- export type { EncodingParams } from "../address-gen.js";
2
- export { deriveAddress, isValidXpub } from "../address-gen.js";
3
1
  export type { StablecoinCheckoutDeps, StablecoinCheckoutResult } from "./checkout.js";
4
2
  export { createStablecoinCheckout, MIN_STABLECOIN_USD } from "./checkout.js";
5
3
  export { centsFromTokenAmount, getChainConfig, getTokenConfig, tokenAmountFromCents } from "./config.js";
6
- export type { EthCheckoutDeps, EthCheckoutOpts, EthCheckoutResult } from "./eth-checkout.js";
4
+ export type {
5
+ CentsToNativeFn,
6
+ EthCheckoutDeps,
7
+ EthCheckoutOpts,
8
+ EthCheckoutResult,
9
+ EthPriceOracle,
10
+ } from "./eth-checkout.js";
7
11
  export { createEthCheckout, MIN_ETH_USD } from "./eth-checkout.js";
8
12
  export type { EthSettlerDeps } from "./eth-settler.js";
9
13
  export { settleEthPayment } from "./eth-settler.js";
10
- export type { EthPaymentEvent, EthWatcherOpts } from "./eth-watcher.js";
11
- export { EthWatcher } from "./eth-watcher.js";
12
14
  export type { EvmSettlerDeps } from "./settler.js";
13
15
  export { settleEvmPayment } from "./settler.js";
14
16
  export type {
15
17
  ChainConfig,
18
+ EthPaymentEvent,
16
19
  EvmChain,
17
20
  EvmPaymentEvent,
18
21
  StablecoinCheckoutOpts,
19
22
  StablecoinToken,
20
23
  TokenConfig,
21
24
  } from "./types.js";
22
- export type { EvmWatcherOpts } from "./watcher.js";
23
- export { createRpcCaller, EvmWatcher } from "./watcher.js";
@@ -40,6 +40,23 @@ export interface EvmPaymentEvent {
40
40
  readonly confirmationsRequired: number;
41
41
  }
42
42
 
43
+ /** Event emitted on each confirmation increment for a native ETH deposit. */
44
+ export interface EthPaymentEvent {
45
+ readonly chain: EvmChain;
46
+ readonly from: string;
47
+ readonly to: string;
48
+ /** Raw value in wei (BigInt as string for serialization). */
49
+ readonly valueWei: string;
50
+ /** USD cents equivalent at detection time (integer). */
51
+ readonly amountUsdCents: number;
52
+ readonly txHash: string;
53
+ readonly blockNumber: number;
54
+ /** Current confirmation count (latest block - tx block). */
55
+ readonly confirmations: number;
56
+ /** Required confirmations for this chain. */
57
+ readonly confirmationsRequired: number;
58
+ }
59
+
43
60
  /** Options for creating a stablecoin checkout. */
44
61
  export interface StablecoinCheckoutOpts {
45
62
  tenant: string;
@@ -1,4 +1,8 @@
1
+ // Client
2
+
3
+ // Chain-specific settlers + checkouts
1
4
  export * from "./btc/index.js";
5
+ // Stores (products use these for local charge tracking)
2
6
  export type {
3
7
  CryptoChargeProgressUpdate,
4
8
  CryptoChargeRecord,
@@ -18,8 +22,8 @@ export { CryptoServiceClient, loadCryptoConfig } from "./client.js";
18
22
  export type { IWatcherCursorStore } from "./cursor-store.js";
19
23
  export { DrizzleWatcherCursorStore } from "./cursor-store.js";
20
24
  export * from "./evm/index.js";
21
- export type { KeyServerDeps } from "./key-server.js";
22
- export { createKeyServerApp } from "./key-server.js";
25
+
26
+ // Ledger glue (webhook handler)
23
27
  export type {
24
28
  KeyServerWebhookDeps as CryptoWebhookDeps,
25
29
  KeyServerWebhookPayload as CryptoWebhookPayload,
@@ -30,18 +34,16 @@ export {
30
34
  handleKeyServerWebhook as handleCryptoWebhook,
31
35
  normalizeStatus,
32
36
  } from "./key-server-webhook.js";
33
- export * from "./oracle/index.js";
34
37
  export type { IPaymentMethodStore, PaymentMethodRecord } from "./payment-method-store.js";
35
38
  export { DrizzlePaymentMethodStore } from "./payment-method-store.js";
39
+
40
+ // Types
36
41
  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";
45
- export type { CryptoCharge, CryptoChargeStatus, CryptoPaymentState } from "./types.js";
42
+ CryptoCharge,
43
+ CryptoChargeStatus,
44
+ CryptoPaymentState,
45
+ CryptoWebhookResult as SettlerWebhookResult,
46
+ } from "./types.js";
47
+ // Checkout orchestration
46
48
  export type { UnifiedCheckoutDeps, UnifiedCheckoutResult } from "./unified-checkout.js";
47
49
  export { createUnifiedCheckout, MIN_CHECKOUT_USD as MIN_PAYMENT_USD, MIN_CHECKOUT_USD } from "./unified-checkout.js";
@@ -27,44 +27,6 @@ export interface CryptoCheckoutOpts {
27
27
  amountUsd: number;
28
28
  }
29
29
 
30
- /** Webhook payload received from BTCPay Server (InvoiceSettled event). */
31
- export interface CryptoWebhookPayload {
32
- /** BTCPay delivery ID (for deduplication). */
33
- deliveryId: string;
34
- /** Webhook ID. */
35
- webhookId: string;
36
- /** Original delivery ID (same as deliveryId on first delivery). */
37
- originalDeliveryId: string;
38
- /** Whether this is a redelivery. */
39
- isRedelivery: boolean;
40
- /** Event type (e.g. "InvoiceSettled", "InvoiceProcessing", "InvoiceExpired"). */
41
- type: string;
42
- /** Unix timestamp. */
43
- timestamp: number;
44
- /** BTCPay store ID. */
45
- storeId: string;
46
- /** BTCPay invoice ID. */
47
- invoiceId: string;
48
- /** Invoice metadata (echoed from creation). */
49
- metadata: Record<string, unknown>;
50
- /** Whether admin manually marked as settled (InvoiceSettled only). */
51
- manuallyMarked?: boolean;
52
- /** Whether customer overpaid (InvoiceSettled only). */
53
- overPaid?: boolean;
54
- /** Whether invoice was partially paid (InvoiceExpired only). */
55
- partiallyPaid?: boolean;
56
- }
57
-
58
- /** Configuration for BTCPay Server integration. */
59
- export interface CryptoBillingConfig {
60
- /** BTCPay API key (from Account > API keys). */
61
- apiKey: string;
62
- /** BTCPay Server base URL. */
63
- baseUrl: string;
64
- /** BTCPay store ID. */
65
- storeId: string;
66
- }
67
-
68
30
  /** Result of processing a crypto webhook event. */
69
31
  export interface CryptoWebhookResult {
70
32
  handled: boolean;
@@ -74,28 +36,3 @@ export interface CryptoWebhookResult {
74
36
  reactivatedBots?: string[];
75
37
  duplicate?: boolean;
76
38
  }
77
-
78
- /**
79
- * Map BTCPay webhook event type string to a CryptoPaymentState.
80
- *
81
- * Shared between the core (billing) and consumer (monetization) webhook handlers.
82
- * Throws on unrecognized event types to surface integration errors early.
83
- */
84
- export function mapBtcPayEventToStatus(eventType: string): CryptoPaymentState {
85
- switch (eventType) {
86
- case "InvoiceCreated":
87
- return "New";
88
- case "InvoiceReceivedPayment":
89
- case "InvoiceProcessing":
90
- return "Processing";
91
- case "InvoiceSettled":
92
- case "InvoicePaymentSettled":
93
- return "Settled";
94
- case "InvoiceExpired":
95
- return "Expired";
96
- case "InvoiceInvalid":
97
- return "Invalid";
98
- default:
99
- throw new Error(`Unknown BTCPay event type: ${eventType}`);
100
- }
101
- }
@@ -129,6 +129,18 @@ class ResendTransport implements EmailTransport {
129
129
  }
130
130
  }
131
131
 
132
+ /** No-op transport that logs but does not send. Used when EMAIL_DISABLED=true. */
133
+ class NoopTransport implements EmailTransport {
134
+ async send(opts: SendTemplateEmailOpts): Promise<EmailSendResult> {
135
+ logger.info("Email suppressed (EMAIL_DISABLED)", {
136
+ to: opts.to,
137
+ template: opts.templateName,
138
+ userId: opts.userId,
139
+ });
140
+ return { id: "noop", success: true };
141
+ }
142
+ }
143
+
132
144
  /**
133
145
  * Create a lazily-initialized singleton EmailClient from environment variables.
134
146
  *
@@ -174,6 +186,12 @@ export interface EmailClientOverrides {
174
186
  */
175
187
  export function getEmailClient(overrides?: EmailClientOverrides): EmailClient {
176
188
  if (!_client) {
189
+ if (process.env.EMAIL_DISABLED === "true") {
190
+ _client = new EmailClient(new NoopTransport());
191
+ logger.info("Email client disabled (EMAIL_DISABLED=true)");
192
+ return _client;
193
+ }
194
+
177
195
  const from = overrides?.from || process.env.EMAIL_FROM || process.env.RESEND_FROM || "noreply@wopr.bot";
178
196
  const replyTo =
179
197
  overrides?.replyTo || process.env.EMAIL_REPLY_TO || process.env.RESEND_REPLY_TO || "support@wopr.bot";
@@ -1 +0,0 @@
1
- export {};
@@ -1,219 +0,0 @@
1
- import { HDKey } from "@scure/bip32";
2
- import * as bip39 from "@scure/bip39";
3
- import { privateKeyToAccount } from "viem/accounts";
4
- import { describe, expect, it } from "vitest";
5
- import { deriveAddress, deriveTreasury, isValidXpub } from "../address-gen.js";
6
- /**
7
- * Well-known BIP-39 test mnemonic.
8
- * DO NOT use in production — this is public and widely known.
9
- */
10
- const TEST_MNEMONIC = "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about";
11
- const TEST_SEED = bip39.mnemonicToSeedSync(TEST_MNEMONIC);
12
- /** Derive xpub at a BIP-44 path from the test mnemonic. */
13
- function xpubAt(path) {
14
- return HDKey.fromMasterSeed(TEST_SEED).derive(path).publicExtendedKey;
15
- }
16
- /** Derive private key at xpub / chainIndex / addressIndex from the test mnemonic. */
17
- function privKeyAt(path, chainIndex, addressIndex) {
18
- const child = HDKey.fromMasterSeed(TEST_SEED).derive(path).deriveChild(chainIndex).deriveChild(addressIndex);
19
- if (!child.privateKey)
20
- throw new Error("No private key");
21
- return child.privateKey;
22
- }
23
- function privKeyHex(key) {
24
- return `0x${Array.from(key, (b) => b.toString(16).padStart(2, "0")).join("")}`;
25
- }
26
- // =============================================================
27
- // DB chain configs — must match production payment_methods rows
28
- // =============================================================
29
- const CHAIN_CONFIGS = [
30
- { name: "bitcoin", coinType: 0, addressType: "bech32", params: { hrp: "bc" }, addrRegex: /^bc1q[a-z0-9]+$/ },
31
- { name: "litecoin", coinType: 2, addressType: "bech32", params: { hrp: "ltc" }, addrRegex: /^ltc1q[a-z0-9]+$/ },
32
- {
33
- name: "dogecoin",
34
- coinType: 3,
35
- addressType: "p2pkh",
36
- params: { version: "0x1e" },
37
- addrRegex: /^D[a-km-zA-HJ-NP-Z1-9]+$/,
38
- },
39
- {
40
- name: "tron",
41
- coinType: 195,
42
- addressType: "keccak-b58check",
43
- params: { version: "0x41" },
44
- addrRegex: /^T[a-km-zA-HJ-NP-Z1-9]+$/,
45
- },
46
- { name: "ethereum", coinType: 60, addressType: "evm", params: {}, addrRegex: /^0x[0-9a-fA-F]{40}$/ },
47
- ];
48
- // =============================================================
49
- // Core property: xpub-derived address == mnemonic-derived address
50
- // This guarantees sweep scripts can sign for pay server addresses.
51
- // =============================================================
52
- describe("address derivation — sweep key parity", () => {
53
- for (const cfg of CHAIN_CONFIGS) {
54
- const path = `m/44'/${cfg.coinType}'/0'`;
55
- const xpub = xpubAt(path);
56
- describe(cfg.name, () => {
57
- it("xpub address matches format", () => {
58
- const addr = deriveAddress(xpub, 0, cfg.addressType, cfg.params);
59
- expect(addr).toMatch(cfg.addrRegex);
60
- });
61
- it("derives different addresses at different indices", () => {
62
- const a = deriveAddress(xpub, 0, cfg.addressType, cfg.params);
63
- const b = deriveAddress(xpub, 1, cfg.addressType, cfg.params);
64
- expect(a).not.toBe(b);
65
- });
66
- it("is deterministic", () => {
67
- const a = deriveAddress(xpub, 7, cfg.addressType, cfg.params);
68
- const b = deriveAddress(xpub, 7, cfg.addressType, cfg.params);
69
- expect(a).toBe(b);
70
- });
71
- it("treasury differs from deposit index 0", () => {
72
- const deposit = deriveAddress(xpub, 0, cfg.addressType, cfg.params);
73
- const treasury = deriveTreasury(xpub, cfg.addressType, cfg.params);
74
- expect(deposit).not.toBe(treasury);
75
- });
76
- });
77
- }
78
- });
79
- // =============================================================
80
- // Sweep key parity for EVERY chain config.
81
- // Proves: mnemonic → private key → public key → address == xpub → address
82
- // This guarantees sweep scripts can sign for pay server addresses.
83
- // =============================================================
84
- describe("sweep key parity — privkey derives same address as xpub", () => {
85
- for (const cfg of CHAIN_CONFIGS) {
86
- const path = `m/44'/${cfg.coinType}'/0'`;
87
- const xpub = xpubAt(path);
88
- describe(cfg.name, () => {
89
- it("deposit addresses match at indices 0-4", () => {
90
- for (let i = 0; i < 5; i++) {
91
- const fromXpub = deriveAddress(xpub, i, cfg.addressType, cfg.params);
92
- // Derive from privkey: get the child's public key and re-derive the address
93
- const child = HDKey.fromMasterSeed(TEST_SEED).derive(path).deriveChild(0).deriveChild(i);
94
- const fromPriv = deriveAddress(
95
- // Use the child's public extended key — but HDKey.deriveChild on a full key
96
- // produces a full key. Extract just the public key and re-derive via xpub at same index.
97
- // Simpler: verify the public keys match, then the address must match.
98
- xpub, i, cfg.addressType, cfg.params);
99
- // This is tautological — we need to verify from the private key side.
100
- // For EVM we can use viem. For others, verify pubkey identity.
101
- expect(child.publicKey).toBeDefined();
102
- // The xpub's child pubkey at index i must equal the full-key's child pubkey at index i
103
- const xpubChild = HDKey.fromExtendedKey(xpub).deriveChild(0).deriveChild(i);
104
- const childPk = child.publicKey;
105
- const xpubPk = xpubChild.publicKey;
106
- expect(Buffer.from(childPk).toString("hex")).toBe(Buffer.from(xpubPk).toString("hex"));
107
- // And the address from xpub must match
108
- expect(fromXpub).toBe(fromPriv);
109
- }
110
- });
111
- it("treasury pubkey matches", () => {
112
- const fullKey = HDKey.fromMasterSeed(TEST_SEED).derive(path).deriveChild(1).deriveChild(0);
113
- const xpubKey = HDKey.fromExtendedKey(xpub).deriveChild(1).deriveChild(0);
114
- const fullPk = fullKey.publicKey;
115
- const xpubPk = xpubKey.publicKey;
116
- expect(Buffer.from(fullPk).toString("hex")).toBe(Buffer.from(xpubPk).toString("hex"));
117
- });
118
- });
119
- }
120
- });
121
- // =============================================================
122
- // EVM: private key → viem account → address must match.
123
- // Strongest proof: viem can sign transactions from this key
124
- // and the address matches what the pay server derived.
125
- // =============================================================
126
- describe("EVM sweep key parity — viem account matches derived address", () => {
127
- const EVM_PATH = "m/44'/60'/0'";
128
- const xpub = xpubAt(EVM_PATH);
129
- // All EVM chains share coin type 60
130
- it("deposit privkey account matches at indices 0-9", () => {
131
- for (let i = 0; i < 10; i++) {
132
- const derivedAddr = deriveAddress(xpub, i, "evm");
133
- const privKey = privKeyAt(EVM_PATH, 0, i);
134
- const account = privateKeyToAccount(privKeyHex(privKey));
135
- expect(account.address.toLowerCase()).toBe(derivedAddr.toLowerCase());
136
- }
137
- });
138
- it("treasury privkey account matches", () => {
139
- const treasuryAddr = deriveTreasury(xpub, "evm");
140
- const privKey = privKeyAt(EVM_PATH, 1, 0);
141
- const account = privateKeyToAccount(privKeyHex(privKey));
142
- expect(account.address.toLowerCase()).toBe(treasuryAddr.toLowerCase());
143
- });
144
- });
145
- // =============================================================
146
- // Tron-specific: keccak-b58check produces known test vectors
147
- // =============================================================
148
- describe("Tron keccak-b58check — known test vectors", () => {
149
- const TRON_XPUB = xpubAt("m/44'/195'/0'");
150
- it("index 0 produces known address", () => {
151
- const addr = deriveAddress(TRON_XPUB, 0, "keccak-b58check", { version: "0x41" });
152
- // Verified against TronWeb / TronLink derivation from test mnemonic
153
- expect(addr).toBe("TUEZSdKsoDHQMeZwihtdoBiN46zxhGWYdH");
154
- });
155
- it("index 1 produces known address", () => {
156
- const addr = deriveAddress(TRON_XPUB, 1, "keccak-b58check", { version: "0x41" });
157
- expect(addr).toBe("TSeJkUh4Qv67VNFwY8LaAxERygNdy6NQZK");
158
- });
159
- it("treasury produces known address", () => {
160
- const addr = deriveTreasury(TRON_XPUB, "keccak-b58check", { version: "0x41" });
161
- expect(addr).toMatch(/^T[a-km-zA-HJ-NP-Z1-9]{33}$/);
162
- });
163
- it("address has correct length (34 chars)", () => {
164
- for (let i = 0; i < 10; i++) {
165
- const addr = deriveAddress(TRON_XPUB, i, "keccak-b58check", { version: "0x41" });
166
- expect(addr.length).toBe(34);
167
- }
168
- });
169
- });
170
- // =============================================================
171
- // Bitcoin — known test vectors from BIP-84 test mnemonic
172
- // =============================================================
173
- describe("Bitcoin bech32 — known test vectors", () => {
174
- const BTC_XPUB = xpubAt("m/44'/0'/0'");
175
- it("index 0 produces valid bc1q address", () => {
176
- const addr = deriveAddress(BTC_XPUB, 0, "bech32", { hrp: "bc" });
177
- expect(addr).toMatch(/^bc1q[a-z0-9]{38,42}$/);
178
- });
179
- it("testnet uses tb prefix", () => {
180
- const addr = deriveAddress(BTC_XPUB, 0, "bech32", { hrp: "tb" });
181
- expect(addr).toMatch(/^tb1q[a-z0-9]+$/);
182
- });
183
- });
184
- // =============================================================
185
- // Error handling
186
- // =============================================================
187
- describe("error handling", () => {
188
- const ETH_XPUB = xpubAt("m/44'/60'/0'");
189
- const BTC_XPUB = xpubAt("m/44'/0'/0'");
190
- it("rejects negative index", () => {
191
- expect(() => deriveAddress(ETH_XPUB, -1, "evm")).toThrow("Invalid");
192
- });
193
- it("rejects unknown address type", () => {
194
- expect(() => deriveAddress(ETH_XPUB, 0, "foo")).toThrow("Unknown address type");
195
- });
196
- it("bech32 throws without hrp", () => {
197
- expect(() => deriveAddress(BTC_XPUB, 0, "bech32", {})).toThrow("hrp");
198
- });
199
- it("p2pkh throws without version", () => {
200
- expect(() => deriveAddress(BTC_XPUB, 0, "p2pkh", {})).toThrow("version");
201
- });
202
- it("keccak-b58check throws without version", () => {
203
- expect(() => deriveAddress(BTC_XPUB, 0, "keccak-b58check", {})).toThrow("version");
204
- });
205
- });
206
- // =============================================================
207
- // isValidXpub
208
- // =============================================================
209
- describe("isValidXpub", () => {
210
- it("accepts valid xpub", () => {
211
- expect(isValidXpub(xpubAt("m/44'/0'/0'"))).toBe(true);
212
- });
213
- it("rejects garbage", () => {
214
- expect(isValidXpub("not-an-xpub")).toBe(false);
215
- });
216
- it("rejects empty string", () => {
217
- expect(isValidXpub("")).toBe(false);
218
- });
219
- });
@@ -1 +0,0 @@
1
- export {};