@wopr-network/platform-core 1.43.0 → 1.44.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__/key-server.test.js +16 -1
- package/dist/billing/crypto/btc/watcher.d.ts +2 -0
- package/dist/billing/crypto/btc/watcher.js +1 -1
- package/dist/billing/crypto/charge-store.d.ts +7 -1
- package/dist/billing/crypto/charge-store.js +7 -1
- package/dist/billing/crypto/client.d.ts +0 -26
- package/dist/billing/crypto/client.js +0 -13
- package/dist/billing/crypto/client.test.js +1 -11
- package/dist/billing/crypto/index.d.ts +5 -7
- package/dist/billing/crypto/index.js +3 -5
- package/dist/billing/crypto/key-server-entry.js +43 -2
- package/dist/billing/crypto/key-server-webhook.d.ts +33 -0
- package/dist/billing/crypto/key-server-webhook.js +73 -0
- package/dist/billing/crypto/key-server.d.ts +2 -0
- package/dist/billing/crypto/key-server.js +25 -1
- package/dist/billing/crypto/watcher-service.d.ts +33 -0
- package/dist/billing/crypto/watcher-service.js +295 -0
- package/dist/billing/index.js +1 -1
- package/dist/db/schema/crypto.d.ts +217 -2
- package/dist/db/schema/crypto.js +25 -2
- package/dist/monetization/crypto/__tests__/webhook.test.js +57 -92
- package/dist/monetization/crypto/index.d.ts +4 -4
- package/dist/monetization/crypto/index.js +2 -2
- package/dist/monetization/crypto/webhook.d.ts +13 -14
- package/dist/monetization/crypto/webhook.js +12 -83
- package/dist/monetization/index.d.ts +2 -2
- package/dist/monetization/index.js +1 -1
- package/drizzle/migrations/0015_callback_url.sql +32 -0
- package/drizzle/migrations/meta/_journal.json +7 -0
- package/package.json +1 -1
- package/src/billing/crypto/__tests__/key-server.test.ts +16 -1
- package/src/billing/crypto/btc/watcher.ts +3 -1
- package/src/billing/crypto/charge-store.ts +13 -1
- package/src/billing/crypto/client.test.ts +1 -13
- package/src/billing/crypto/client.ts +0 -21
- package/src/billing/crypto/index.ts +9 -13
- package/src/billing/crypto/key-server-entry.ts +46 -2
- package/src/billing/crypto/key-server-webhook.ts +119 -0
- package/src/billing/crypto/key-server.ts +29 -1
- package/src/billing/crypto/watcher-service.ts +381 -0
- package/src/billing/index.ts +1 -1
- package/src/db/schema/crypto.ts +30 -2
- package/src/monetization/crypto/__tests__/webhook.test.ts +61 -104
- package/src/monetization/crypto/index.ts +9 -11
- package/src/monetization/crypto/webhook.ts +25 -99
- package/src/monetization/index.ts +3 -7
- package/dist/billing/crypto/checkout.d.ts +0 -18
- package/dist/billing/crypto/checkout.js +0 -35
- package/dist/billing/crypto/checkout.test.d.ts +0 -1
- package/dist/billing/crypto/checkout.test.js +0 -71
- package/dist/billing/crypto/webhook.d.ts +0 -34
- package/dist/billing/crypto/webhook.js +0 -107
- package/dist/billing/crypto/webhook.test.d.ts +0 -1
- package/dist/billing/crypto/webhook.test.js +0 -266
- package/src/billing/crypto/checkout.test.ts +0 -93
- package/src/billing/crypto/checkout.ts +0 -48
- package/src/billing/crypto/webhook.test.ts +0 -340
- package/src/billing/crypto/webhook.ts +0 -136
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|
2
|
-
import {
|
|
2
|
+
import { CryptoServiceClient, loadCryptoConfig } from "./client.js";
|
|
3
3
|
|
|
4
4
|
describe("CryptoServiceClient", () => {
|
|
5
5
|
afterEach(() => vi.restoreAllMocks());
|
|
@@ -78,18 +78,6 @@ describe("CryptoServiceClient", () => {
|
|
|
78
78
|
});
|
|
79
79
|
});
|
|
80
80
|
|
|
81
|
-
describe("BTCPayClient (deprecated)", () => {
|
|
82
|
-
it("throws on createInvoice", async () => {
|
|
83
|
-
const client = new BTCPayClient({ apiKey: "k", baseUrl: "https://example.com", storeId: "s" });
|
|
84
|
-
await expect(client.createInvoice({ amountUsd: 10, orderId: "o" })).rejects.toThrow("deprecated");
|
|
85
|
-
});
|
|
86
|
-
|
|
87
|
-
it("throws on getInvoice", async () => {
|
|
88
|
-
const client = new BTCPayClient({ apiKey: "k", baseUrl: "https://example.com", storeId: "s" });
|
|
89
|
-
await expect(client.getInvoice("inv-1")).rejects.toThrow("deprecated");
|
|
90
|
-
});
|
|
91
|
-
});
|
|
92
|
-
|
|
93
81
|
describe("loadCryptoConfig", () => {
|
|
94
82
|
beforeEach(() => {
|
|
95
83
|
delete process.env.CRYPTO_SERVICE_URL;
|
|
@@ -143,24 +143,3 @@ export function loadCryptoConfig(): CryptoServiceConfig | null {
|
|
|
143
143
|
|
|
144
144
|
// Legacy type alias for backwards compat
|
|
145
145
|
export type CryptoConfig = CryptoServiceConfig;
|
|
146
|
-
|
|
147
|
-
/**
|
|
148
|
-
* @deprecated Use CryptoServiceClient instead. BTCPay is replaced by the crypto key server.
|
|
149
|
-
* Kept for backwards compat — products still import BTCPayClient during migration.
|
|
150
|
-
*/
|
|
151
|
-
export class BTCPayClient {
|
|
152
|
-
constructor(_config: { apiKey: string; baseUrl: string; storeId: string }) {}
|
|
153
|
-
|
|
154
|
-
async createInvoice(_opts: {
|
|
155
|
-
amountUsd: number;
|
|
156
|
-
orderId: string;
|
|
157
|
-
buyerEmail?: string;
|
|
158
|
-
redirectURL?: string;
|
|
159
|
-
}): Promise<{ id: string; checkoutLink: string }> {
|
|
160
|
-
throw new Error("BTCPayClient is deprecated — migrate to CryptoServiceClient");
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
async getInvoice(_invoiceId: string): Promise<{ id: string; status: string; amount: string; currency: string }> {
|
|
164
|
-
throw new Error("BTCPayClient is deprecated — migrate to CryptoServiceClient");
|
|
165
|
-
}
|
|
166
|
-
}
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
export * from "./btc/index.js";
|
|
2
2
|
export type { CryptoChargeRecord, CryptoDepositChargeInput, ICryptoChargeRepository } from "./charge-store.js";
|
|
3
3
|
export { CryptoChargeRepository, DrizzleCryptoChargeRepository } from "./charge-store.js";
|
|
4
|
-
export { createCryptoCheckout, MIN_PAYMENT_USD } from "./checkout.js";
|
|
5
4
|
export type {
|
|
6
5
|
ChainInfo,
|
|
7
6
|
ChargeStatus,
|
|
@@ -10,24 +9,21 @@ export type {
|
|
|
10
9
|
CryptoServiceConfig,
|
|
11
10
|
DeriveAddressResult,
|
|
12
11
|
} from "./client.js";
|
|
13
|
-
export {
|
|
12
|
+
export { CryptoServiceClient, loadCryptoConfig } from "./client.js";
|
|
14
13
|
export type { IWatcherCursorStore } from "./cursor-store.js";
|
|
15
14
|
export { DrizzleWatcherCursorStore } from "./cursor-store.js";
|
|
16
15
|
export * from "./evm/index.js";
|
|
17
16
|
export type { KeyServerDeps } from "./key-server.js";
|
|
18
17
|
export { createKeyServerApp } from "./key-server.js";
|
|
18
|
+
export type {
|
|
19
|
+
KeyServerWebhookDeps as CryptoWebhookDeps,
|
|
20
|
+
KeyServerWebhookPayload as CryptoWebhookPayload,
|
|
21
|
+
KeyServerWebhookResult as CryptoWebhookResult,
|
|
22
|
+
} from "./key-server-webhook.js";
|
|
23
|
+
export { handleKeyServerWebhook, handleKeyServerWebhook as handleCryptoWebhook } from "./key-server-webhook.js";
|
|
19
24
|
export * from "./oracle/index.js";
|
|
20
25
|
export type { IPaymentMethodStore, PaymentMethodRecord } from "./payment-method-store.js";
|
|
21
26
|
export { DrizzlePaymentMethodStore } from "./payment-method-store.js";
|
|
22
|
-
export type {
|
|
23
|
-
CryptoBillingConfig,
|
|
24
|
-
CryptoCheckoutOpts,
|
|
25
|
-
CryptoPaymentState,
|
|
26
|
-
CryptoWebhookPayload,
|
|
27
|
-
CryptoWebhookResult,
|
|
28
|
-
} from "./types.js";
|
|
29
|
-
export { mapBtcPayEventToStatus } from "./types.js";
|
|
27
|
+
export type { CryptoPaymentState } from "./types.js";
|
|
30
28
|
export type { UnifiedCheckoutDeps, UnifiedCheckoutResult } from "./unified-checkout.js";
|
|
31
|
-
export { createUnifiedCheckout, MIN_CHECKOUT_USD } from "./unified-checkout.js";
|
|
32
|
-
export type { CryptoWebhookDeps } from "./webhook.js";
|
|
33
|
-
export { handleCryptoWebhook, verifyCryptoWebhookSignature } from "./webhook.js";
|
|
29
|
+
export { createUnifiedCheckout, MIN_CHECKOUT_USD as MIN_PAYMENT_USD, MIN_CHECKOUT_USD } from "./unified-checkout.js";
|
|
@@ -13,13 +13,21 @@ import { migrate } from "drizzle-orm/node-postgres/migrator";
|
|
|
13
13
|
import pg from "pg";
|
|
14
14
|
import * as schema from "../../db/schema/index.js";
|
|
15
15
|
import { DrizzleCryptoChargeRepository } from "./charge-store.js";
|
|
16
|
+
import { DrizzleWatcherCursorStore } from "./cursor-store.js";
|
|
17
|
+
import { createRpcCaller } from "./evm/watcher.js";
|
|
16
18
|
import { createKeyServerApp } from "./key-server.js";
|
|
19
|
+
import { ChainlinkOracle } from "./oracle/chainlink.js";
|
|
20
|
+
import { FixedPriceOracle } from "./oracle/fixed.js";
|
|
17
21
|
import { DrizzlePaymentMethodStore } from "./payment-method-store.js";
|
|
22
|
+
import { startWatchers } from "./watcher-service.js";
|
|
18
23
|
|
|
19
24
|
const PORT = Number(process.env.PORT ?? "3100");
|
|
20
25
|
const DATABASE_URL = process.env.DATABASE_URL;
|
|
21
26
|
const SERVICE_KEY = process.env.SERVICE_KEY;
|
|
22
27
|
const ADMIN_TOKEN = process.env.ADMIN_TOKEN;
|
|
28
|
+
const BITCOIND_USER = process.env.BITCOIND_USER ?? "btcpay";
|
|
29
|
+
const BITCOIND_PASSWORD = process.env.BITCOIND_PASSWORD ?? "";
|
|
30
|
+
const BASE_RPC_URL = process.env.BASE_RPC_URL ?? "https://mainnet.base.org";
|
|
23
31
|
|
|
24
32
|
if (!DATABASE_URL) {
|
|
25
33
|
console.error("DATABASE_URL is required");
|
|
@@ -40,10 +48,46 @@ async function main(): Promise<void> {
|
|
|
40
48
|
const chargeStore = new DrizzleCryptoChargeRepository(db);
|
|
41
49
|
const methodStore = new DrizzlePaymentMethodStore(db);
|
|
42
50
|
|
|
43
|
-
|
|
51
|
+
// Chainlink on-chain oracle for volatile assets (BTC, ETH).
|
|
52
|
+
const oracle = BASE_RPC_URL
|
|
53
|
+
? new ChainlinkOracle({ rpcCall: createRpcCaller(BASE_RPC_URL) })
|
|
54
|
+
: new FixedPriceOracle();
|
|
44
55
|
|
|
56
|
+
const app = createKeyServerApp({
|
|
57
|
+
db,
|
|
58
|
+
chargeStore,
|
|
59
|
+
methodStore,
|
|
60
|
+
oracle,
|
|
61
|
+
serviceKey: SERVICE_KEY,
|
|
62
|
+
adminToken: ADMIN_TOKEN,
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
// Boot watchers (BTC + EVM) — polls for payments, sends webhooks
|
|
66
|
+
const cursorStore = new DrizzleWatcherCursorStore(db);
|
|
67
|
+
const stopWatchers = await startWatchers({
|
|
68
|
+
db,
|
|
69
|
+
chargeStore,
|
|
70
|
+
methodStore,
|
|
71
|
+
cursorStore,
|
|
72
|
+
oracle,
|
|
73
|
+
bitcoindUser: BITCOIND_USER,
|
|
74
|
+
bitcoindPassword: BITCOIND_PASSWORD,
|
|
75
|
+
log: (msg, meta) => console.log(`[watcher] ${msg}`, meta ?? ""),
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
const server = serve({ fetch: app.fetch, port: PORT });
|
|
45
79
|
console.log(`[crypto-key-server] Listening on :${PORT}`);
|
|
46
|
-
|
|
80
|
+
|
|
81
|
+
// Graceful shutdown — stop accepting requests, drain watchers, close pool
|
|
82
|
+
const shutdown = async () => {
|
|
83
|
+
console.log("[crypto-key-server] Shutting down...");
|
|
84
|
+
stopWatchers();
|
|
85
|
+
server.close();
|
|
86
|
+
await pool.end();
|
|
87
|
+
process.exit(0);
|
|
88
|
+
};
|
|
89
|
+
process.on("SIGTERM", shutdown);
|
|
90
|
+
process.on("SIGINT", shutdown);
|
|
47
91
|
}
|
|
48
92
|
|
|
49
93
|
main().catch((err) => {
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Key Server webhook handler — processes payment confirmations from the
|
|
3
|
+
* centralized crypto key server.
|
|
4
|
+
*
|
|
5
|
+
* Payload shape (from watcher-service.ts):
|
|
6
|
+
* {
|
|
7
|
+
* chargeId: "btc:bc1q...",
|
|
8
|
+
* chain: "bitcoin",
|
|
9
|
+
* address: "bc1q...",
|
|
10
|
+
* amountUsdCents: 5000,
|
|
11
|
+
* status: "confirmed",
|
|
12
|
+
* txHash: "abc123...",
|
|
13
|
+
* amountReceived: "50000 sats",
|
|
14
|
+
* confirmations: 6
|
|
15
|
+
* }
|
|
16
|
+
*
|
|
17
|
+
* Replaces handleCryptoWebhook() for products using the key server.
|
|
18
|
+
*/
|
|
19
|
+
import { Credit } from "../../credits/credit.js";
|
|
20
|
+
import type { ILedger } from "../../credits/ledger.js";
|
|
21
|
+
import type { IWebhookSeenRepository } from "../webhook-seen-repository.js";
|
|
22
|
+
import type { ICryptoChargeRepository } from "./charge-store.js";
|
|
23
|
+
|
|
24
|
+
export interface KeyServerWebhookPayload {
|
|
25
|
+
chargeId: string;
|
|
26
|
+
chain: string;
|
|
27
|
+
address: string;
|
|
28
|
+
amountUsdCents: number;
|
|
29
|
+
status: string;
|
|
30
|
+
txHash?: string;
|
|
31
|
+
amountReceived?: string;
|
|
32
|
+
confirmations?: number;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export interface KeyServerWebhookDeps {
|
|
36
|
+
chargeStore: ICryptoChargeRepository;
|
|
37
|
+
creditLedger: ILedger;
|
|
38
|
+
replayGuard: IWebhookSeenRepository;
|
|
39
|
+
onCreditsPurchased?: (tenantId: string, ledger: ILedger) => Promise<string[]>;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export interface KeyServerWebhookResult {
|
|
43
|
+
handled: boolean;
|
|
44
|
+
duplicate?: boolean;
|
|
45
|
+
tenant?: string;
|
|
46
|
+
creditedCents?: number;
|
|
47
|
+
reactivatedBots?: string[];
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Process a payment confirmation from the crypto key server.
|
|
52
|
+
*
|
|
53
|
+
* Credits the ledger when status is "confirmed".
|
|
54
|
+
* Idempotency: ledger referenceId + replay guard (same pattern as Stripe handler).
|
|
55
|
+
*/
|
|
56
|
+
export async function handleKeyServerWebhook(
|
|
57
|
+
deps: KeyServerWebhookDeps,
|
|
58
|
+
payload: KeyServerWebhookPayload,
|
|
59
|
+
): Promise<KeyServerWebhookResult> {
|
|
60
|
+
const { chargeStore, creditLedger } = deps;
|
|
61
|
+
|
|
62
|
+
// Replay guard: deduplicate by chargeId
|
|
63
|
+
const dedupeKey = `ks:${payload.chargeId}`;
|
|
64
|
+
if (await deps.replayGuard.isDuplicate(dedupeKey, "crypto")) {
|
|
65
|
+
return { handled: true, duplicate: true };
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// Look up the charge to find the tenant + amount
|
|
69
|
+
const charge = await chargeStore.getByReferenceId(payload.chargeId);
|
|
70
|
+
if (!charge) {
|
|
71
|
+
return { handled: false };
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
if (payload.status === "confirmed") {
|
|
75
|
+
// Only settle when payment is confirmed
|
|
76
|
+
await chargeStore.updateStatus(payload.chargeId, "Settled", charge.token ?? undefined, payload.amountReceived);
|
|
77
|
+
|
|
78
|
+
// Idempotency: check ledger referenceId (atomic, same as BTCPay handler)
|
|
79
|
+
const creditRef = `crypto:${payload.chargeId}`;
|
|
80
|
+
if (await creditLedger.hasReferenceId(creditRef)) {
|
|
81
|
+
await deps.replayGuard.markSeen(dedupeKey, "crypto");
|
|
82
|
+
return { handled: true, duplicate: true, tenant: charge.tenantId };
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// Credit the original USD amount requested.
|
|
86
|
+
// charge.amountUsdCents is integer cents. Credit.fromCents() → nanodollars.
|
|
87
|
+
await creditLedger.credit(charge.tenantId, Credit.fromCents(charge.amountUsdCents), "purchase", {
|
|
88
|
+
description: `Crypto payment confirmed (${payload.chain}, tx: ${payload.txHash ?? "unknown"})`,
|
|
89
|
+
referenceId: creditRef,
|
|
90
|
+
fundingSource: "crypto",
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
await chargeStore.markCredited(payload.chargeId);
|
|
94
|
+
|
|
95
|
+
let reactivatedBots: string[] | undefined;
|
|
96
|
+
if (deps.onCreditsPurchased) {
|
|
97
|
+
reactivatedBots = await deps.onCreditsPurchased(charge.tenantId, creditLedger);
|
|
98
|
+
if (reactivatedBots.length === 0) reactivatedBots = undefined;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
await deps.replayGuard.markSeen(dedupeKey, "crypto");
|
|
102
|
+
return {
|
|
103
|
+
handled: true,
|
|
104
|
+
tenant: charge.tenantId,
|
|
105
|
+
creditedCents: charge.amountUsdCents,
|
|
106
|
+
reactivatedBots,
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// Non-confirmed status — update status but don't settle or credit
|
|
111
|
+
await chargeStore.updateStatus(
|
|
112
|
+
payload.chargeId,
|
|
113
|
+
payload.status as "Processing",
|
|
114
|
+
charge.token ?? undefined,
|
|
115
|
+
payload.amountReceived,
|
|
116
|
+
);
|
|
117
|
+
await deps.replayGuard.markSeen(dedupeKey, "crypto");
|
|
118
|
+
return { handled: true, tenant: charge.tenantId };
|
|
119
|
+
}
|
|
@@ -14,12 +14,15 @@ import { derivedAddresses, pathAllocations, paymentMethods } from "../../db/sche
|
|
|
14
14
|
import { deriveAddress, deriveP2pkhAddress } from "./btc/address-gen.js";
|
|
15
15
|
import type { ICryptoChargeRepository } from "./charge-store.js";
|
|
16
16
|
import { deriveDepositAddress } from "./evm/address-gen.js";
|
|
17
|
+
import { centsToNative } from "./oracle/convert.js";
|
|
18
|
+
import type { IPriceOracle } from "./oracle/types.js";
|
|
17
19
|
import type { IPaymentMethodStore } from "./payment-method-store.js";
|
|
18
20
|
|
|
19
21
|
export interface KeyServerDeps {
|
|
20
22
|
db: DrizzleDb;
|
|
21
23
|
chargeStore: ICryptoChargeRepository;
|
|
22
24
|
methodStore: IPaymentMethodStore;
|
|
25
|
+
oracle: IPriceOracle;
|
|
23
26
|
/** Bearer token for product API routes. If unset, auth is disabled. */
|
|
24
27
|
serviceKey?: string;
|
|
25
28
|
/** Bearer token for admin routes. If unset, admin routes are disabled. */
|
|
@@ -145,7 +148,24 @@ export function createKeyServerApp(deps: KeyServerDeps): Hono {
|
|
|
145
148
|
const tenantId = c.req.header("X-Tenant-Id") ?? "unknown";
|
|
146
149
|
const { address, index, chain, token } = await deriveNextAddress(deps.db, body.chain, tenantId);
|
|
147
150
|
|
|
151
|
+
// Look up payment method for decimals + oracle config
|
|
152
|
+
const method = await deps.methodStore.getById(body.chain);
|
|
153
|
+
if (!method) return c.json({ error: `Unknown chain: ${body.chain}` }, 400);
|
|
154
|
+
|
|
148
155
|
const amountUsdCents = Math.round(body.amountUsd * 100);
|
|
156
|
+
|
|
157
|
+
// Compute expected crypto amount in native base units.
|
|
158
|
+
// Price is locked NOW — this is what the user must send.
|
|
159
|
+
let expectedAmount: bigint;
|
|
160
|
+
if (method.oracleAddress) {
|
|
161
|
+
// Volatile asset (BTC, ETH, DOGE) — oracle-priced
|
|
162
|
+
const { priceCents } = await deps.oracle.getPrice(token);
|
|
163
|
+
expectedAmount = centsToNative(amountUsdCents, priceCents, method.decimals);
|
|
164
|
+
} else {
|
|
165
|
+
// Stablecoin (1:1 USD) — e.g. $50 USDC = 50_000_000 base units (6 decimals)
|
|
166
|
+
expectedAmount = (BigInt(amountUsdCents) * 10n ** BigInt(method.decimals)) / 100n;
|
|
167
|
+
}
|
|
168
|
+
|
|
149
169
|
const referenceId = `${token.toLowerCase()}:${address.toLowerCase()}`;
|
|
150
170
|
|
|
151
171
|
await deps.chargeStore.createStablecoinCharge({
|
|
@@ -156,15 +176,23 @@ export function createKeyServerApp(deps: KeyServerDeps): Hono {
|
|
|
156
176
|
token,
|
|
157
177
|
depositAddress: address,
|
|
158
178
|
derivationIndex: index,
|
|
179
|
+
callbackUrl: body.callbackUrl,
|
|
180
|
+
expectedAmount: expectedAmount.toString(),
|
|
159
181
|
});
|
|
160
182
|
|
|
183
|
+
// Format display amount for the client
|
|
184
|
+
const divisor = 10 ** method.decimals;
|
|
185
|
+
const displayAmount = `${(Number(expectedAmount) / divisor).toFixed(Math.min(method.decimals, 8))} ${token}`;
|
|
186
|
+
|
|
161
187
|
return c.json(
|
|
162
188
|
{
|
|
163
189
|
chargeId: referenceId,
|
|
164
190
|
address,
|
|
165
|
-
chain
|
|
191
|
+
chain,
|
|
166
192
|
token,
|
|
167
193
|
amountUsd: body.amountUsd,
|
|
194
|
+
expectedAmount: expectedAmount.toString(),
|
|
195
|
+
displayAmount,
|
|
168
196
|
derivationIndex: index,
|
|
169
197
|
expiresAt: new Date(Date.now() + 30 * 60 * 1000).toISOString(), // 30 min
|
|
170
198
|
},
|