@wopr-network/platform-ui-core 1.7.0 → 1.8.1

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wopr-network/platform-ui-core",
3
- "version": "1.7.0",
3
+ "version": "1.8.1",
4
4
  "description": "Brand-agnostic AI agent platform UI — deploy as any brand via env vars",
5
5
  "repository": {
6
6
  "type": "git",
@@ -44,6 +44,9 @@
44
44
  },
45
45
  "dependencies": {
46
46
  "@hookform/resolvers": "^5.2.2",
47
+ "@noble/hashes": "^2.0.1",
48
+ "@scure/bip32": "^2.0.1",
49
+ "@scure/bip39": "^2.0.1",
47
50
  "@stripe/react-stripe-js": "^5.6.0",
48
51
  "@stripe/stripe-js": "^8.7.0",
49
52
  "@tanstack/react-query": "^5.90.21",
@@ -2,24 +2,16 @@ import { render, screen, waitFor } from "@testing-library/react";
2
2
  import userEvent from "@testing-library/user-event";
3
3
  import { afterEach, describe, expect, it, vi } from "vitest";
4
4
 
5
- const { mockCreateCryptoCheckout, mockIsAllowedRedirectUrl, mockGetSupportedPaymentMethods } =
6
- vi.hoisted(() => ({
7
- mockCreateCryptoCheckout: vi.fn(),
8
- mockIsAllowedRedirectUrl: vi.fn(),
9
- mockGetSupportedPaymentMethods: vi.fn(),
10
- }));
5
+ const { mockCreateCheckout, mockGetSupportedPaymentMethods } = vi.hoisted(() => ({
6
+ mockCreateCheckout: vi.fn(),
7
+ mockGetSupportedPaymentMethods: vi.fn(),
8
+ }));
11
9
 
12
10
  vi.mock("@/lib/api", () => ({
13
- createCryptoCheckout: (...args: unknown[]) => mockCreateCryptoCheckout(...args),
14
- createEthCheckout: vi.fn(),
15
- createStablecoinCheckout: vi.fn(),
11
+ createCheckout: (...args: unknown[]) => mockCreateCheckout(...args),
16
12
  getSupportedPaymentMethods: (...args: unknown[]) => mockGetSupportedPaymentMethods(...args),
17
13
  }));
18
14
 
19
- vi.mock("@/lib/validate-redirect-url", () => ({
20
- isAllowedRedirectUrl: (...args: unknown[]) => mockIsAllowedRedirectUrl(...args),
21
- }));
22
-
23
15
  vi.mock("framer-motion", () => ({
24
16
  motion: {
25
17
  button: ({
@@ -65,33 +57,34 @@ vi.mock("lucide-react", async (importOriginal) => {
65
57
 
66
58
  const MOCK_USDC_METHOD = {
67
59
  id: "usdc:base",
68
- type: "stablecoin",
60
+ type: "erc20",
69
61
  label: "USDC on Base",
70
62
  token: "USDC",
71
63
  chain: "base",
72
64
  };
73
- const MOCK_BTC_METHOD = {
74
- id: "btc:btcpay",
75
- type: "btc",
76
- label: "BTC",
77
- token: "BTC",
78
- chain: "bitcoin",
65
+ const MOCK_ETH_METHOD = {
66
+ id: "eth:base",
67
+ type: "native",
68
+ label: "ETH on Base",
69
+ token: "ETH",
70
+ chain: "base",
79
71
  };
80
72
 
81
73
  afterEach(() => {
82
74
  vi.restoreAllMocks();
83
- mockIsAllowedRedirectUrl.mockReset();
84
- mockCreateCryptoCheckout.mockReset();
75
+ mockCreateCheckout.mockReset();
85
76
  mockGetSupportedPaymentMethods.mockReset();
86
77
  });
87
78
 
88
79
  describe("BuyCryptoCreditPanel", () => {
89
80
  it("renders crypto amount buttons ($10, $25, $50, $100)", async () => {
90
- mockGetSupportedPaymentMethods.mockResolvedValue([MOCK_USDC_METHOD, MOCK_BTC_METHOD]);
81
+ mockGetSupportedPaymentMethods.mockResolvedValue([MOCK_USDC_METHOD, MOCK_ETH_METHOD]);
91
82
  const { BuyCryptoCreditPanel } = await import("../components/billing/buy-crypto-credits-panel");
92
83
  render(<BuyCryptoCreditPanel />);
93
84
 
94
- expect(screen.getByText("Pay with Crypto")).toBeInTheDocument();
85
+ await waitFor(() => {
86
+ expect(screen.getByText("Pay with Crypto")).toBeInTheDocument();
87
+ });
95
88
  expect(screen.getByText("$10")).toBeInTheDocument();
96
89
  expect(screen.getByText("$25")).toBeInTheDocument();
97
90
  expect(screen.getByText("$50")).toBeInTheDocument();
@@ -99,13 +92,12 @@ describe("BuyCryptoCreditPanel", () => {
99
92
  });
100
93
 
101
94
  it("Pay button is disabled when no amount selected", async () => {
102
- mockGetSupportedPaymentMethods.mockResolvedValue([MOCK_USDC_METHOD, MOCK_BTC_METHOD]);
95
+ mockGetSupportedPaymentMethods.mockResolvedValue([MOCK_USDC_METHOD, MOCK_ETH_METHOD]);
103
96
  const { BuyCryptoCreditPanel } = await import("../components/billing/buy-crypto-credits-panel");
104
97
  render(<BuyCryptoCreditPanel />);
105
98
 
106
- // Wait for methods to load
107
99
  await waitFor(() => {
108
- expect(screen.getByText("Stablecoin")).toBeInTheDocument();
100
+ expect(screen.getByText("USDC")).toBeInTheDocument();
109
101
  });
110
102
 
111
103
  const payBtn = screen.getByRole("button", { name: "Pay with USDC" });
@@ -113,120 +105,110 @@ describe("BuyCryptoCreditPanel", () => {
113
105
  });
114
106
 
115
107
  it("Pay button is enabled after selecting an amount", async () => {
116
- mockGetSupportedPaymentMethods.mockResolvedValue([MOCK_USDC_METHOD, MOCK_BTC_METHOD]);
108
+ mockGetSupportedPaymentMethods.mockResolvedValue([MOCK_USDC_METHOD, MOCK_ETH_METHOD]);
117
109
  const user = userEvent.setup();
118
110
  const { BuyCryptoCreditPanel } = await import("../components/billing/buy-crypto-credits-panel");
119
111
  render(<BuyCryptoCreditPanel />);
120
112
 
121
113
  await waitFor(() => {
122
- expect(screen.getByText("Stablecoin")).toBeInTheDocument();
114
+ expect(screen.getByText("USDC")).toBeInTheDocument();
123
115
  });
124
116
 
125
117
  await user.click(screen.getByText("$25"));
126
118
  expect(screen.getByRole("button", { name: "Pay with USDC" })).toBeEnabled();
127
119
  });
128
120
 
129
- it("calls createCryptoCheckout with selected amount and redirects", async () => {
130
- mockGetSupportedPaymentMethods.mockResolvedValue([MOCK_USDC_METHOD, MOCK_BTC_METHOD]);
131
- const hrefSetter = vi.fn();
132
- Object.defineProperty(window, "location", {
133
- value: { ...window.location, href: "" },
134
- writable: true,
135
- configurable: true,
136
- });
137
- Object.defineProperty(window.location, "href", {
138
- set: hrefSetter,
139
- configurable: true,
140
- });
141
-
142
- mockCreateCryptoCheckout.mockResolvedValue({
143
- url: "https://btcpay.example.com/i/abc123",
144
- referenceId: "ref-abc123",
121
+ it("calls createCheckout and shows deposit address", async () => {
122
+ mockGetSupportedPaymentMethods.mockResolvedValue([MOCK_USDC_METHOD, MOCK_ETH_METHOD]);
123
+ mockCreateCheckout.mockResolvedValue({
124
+ depositAddress: "0xabc123def456",
125
+ displayAmount: "50.000000 USDC",
126
+ token: "USDC",
127
+ chain: "base",
145
128
  });
146
- mockIsAllowedRedirectUrl.mockReturnValue(true);
147
129
 
148
130
  const user = userEvent.setup();
149
131
  const { BuyCryptoCreditPanel } = await import("../components/billing/buy-crypto-credits-panel");
150
132
  render(<BuyCryptoCreditPanel />);
151
133
 
152
134
  await waitFor(() => {
153
- expect(screen.getByText("BTC")).toBeInTheDocument();
135
+ expect(screen.getByText("USDC")).toBeInTheDocument();
154
136
  });
155
137
 
156
- // Switch to BTC tab
157
- await user.click(screen.getByText("BTC"));
158
138
  await user.click(screen.getByText("$50"));
159
- await user.click(screen.getByRole("button", { name: "Pay with BTC" }));
139
+ await user.click(screen.getByRole("button", { name: "Pay with USDC" }));
160
140
 
161
- expect(mockCreateCryptoCheckout).toHaveBeenCalledWith(50);
162
- expect(mockIsAllowedRedirectUrl).toHaveBeenCalledWith("https://btcpay.example.com/i/abc123");
163
- expect(hrefSetter).toHaveBeenCalledWith("https://btcpay.example.com/i/abc123");
141
+ expect(mockCreateCheckout).toHaveBeenCalledWith("usdc:base", 50);
142
+ expect(await screen.findByText("0xabc123def456")).toBeInTheDocument();
143
+ expect(screen.getByText("50.000000 USDC")).toBeInTheDocument();
164
144
  });
165
145
 
166
- it("shows error when redirect URL is not allowed", async () => {
167
- mockGetSupportedPaymentMethods.mockResolvedValue([MOCK_USDC_METHOD, MOCK_BTC_METHOD]);
168
- mockCreateCryptoCheckout.mockResolvedValue({
169
- url: "https://evil.com/steal",
170
- referenceId: "ref-evil",
171
- });
172
- mockIsAllowedRedirectUrl.mockReturnValue(false);
146
+ it("shows Creating checkout... while checkout is in progress", async () => {
147
+ mockGetSupportedPaymentMethods.mockResolvedValue([MOCK_USDC_METHOD, MOCK_ETH_METHOD]);
148
+ mockCreateCheckout.mockReturnValue(
149
+ new Promise(() => {
150
+ /* intentionally pending */
151
+ }),
152
+ );
173
153
 
174
154
  const user = userEvent.setup();
175
155
  const { BuyCryptoCreditPanel } = await import("../components/billing/buy-crypto-credits-panel");
176
156
  render(<BuyCryptoCreditPanel />);
177
157
 
178
158
  await waitFor(() => {
179
- expect(screen.getByText("BTC")).toBeInTheDocument();
159
+ expect(screen.getByText("USDC")).toBeInTheDocument();
180
160
  });
181
161
 
182
- await user.click(screen.getByText("BTC"));
183
- await user.click(screen.getByText("$10"));
184
- await user.click(screen.getByRole("button", { name: "Pay with BTC" }));
162
+ await user.click(screen.getByText("$50"));
163
+ await user.click(screen.getByRole("button", { name: "Pay with USDC" }));
185
164
 
186
- expect(
187
- await screen.findByText("Unexpected checkout URL. Please contact support."),
188
- ).toBeInTheDocument();
165
+ expect(await screen.findByText("Creating checkout...")).toBeInTheDocument();
189
166
  });
190
167
 
191
- it("shows Creating checkout... while checkout is in progress", async () => {
192
- mockGetSupportedPaymentMethods.mockResolvedValue([MOCK_USDC_METHOD, MOCK_BTC_METHOD]);
193
- mockCreateCryptoCheckout.mockReturnValue(
194
- new Promise(() => {
195
- /* intentionally pending */
196
- }),
197
- );
168
+ it("shows error when checkout API call fails", async () => {
169
+ mockGetSupportedPaymentMethods.mockResolvedValue([MOCK_USDC_METHOD, MOCK_ETH_METHOD]);
170
+ mockCreateCheckout.mockRejectedValue(new Error("API down"));
198
171
 
199
172
  const user = userEvent.setup();
200
173
  const { BuyCryptoCreditPanel } = await import("../components/billing/buy-crypto-credits-panel");
201
174
  render(<BuyCryptoCreditPanel />);
202
175
 
203
176
  await waitFor(() => {
204
- expect(screen.getByText("BTC")).toBeInTheDocument();
177
+ expect(screen.getByText("USDC")).toBeInTheDocument();
205
178
  });
206
179
 
207
- await user.click(screen.getByText("BTC"));
208
- await user.click(screen.getByText("$100"));
209
- await user.click(screen.getByRole("button", { name: "Pay with BTC" }));
180
+ await user.click(screen.getByText("$25"));
181
+ await user.click(screen.getByRole("button", { name: "Pay with USDC" }));
210
182
 
211
- expect(await screen.findByText("Creating checkout...")).toBeInTheDocument();
183
+ expect(await screen.findByText("Checkout failed. Please try again.")).toBeInTheDocument();
212
184
  });
213
185
 
214
- it("shows error when crypto checkout API call fails", async () => {
215
- mockGetSupportedPaymentMethods.mockResolvedValue([MOCK_USDC_METHOD, MOCK_BTC_METHOD]);
216
- mockCreateCryptoCheckout.mockRejectedValue(new Error("API down"));
186
+ it("hides panel when no payment methods available", async () => {
187
+ mockGetSupportedPaymentMethods.mockResolvedValue([]);
188
+ const { BuyCryptoCreditPanel } = await import("../components/billing/buy-crypto-credits-panel");
189
+ const { container } = render(<BuyCryptoCreditPanel />);
190
+
191
+ // Component returns null when no methods
192
+ await waitFor(() => {
193
+ expect(container.innerHTML).toBe("");
194
+ });
195
+ });
217
196
 
197
+ it("switches between payment methods", async () => {
198
+ mockGetSupportedPaymentMethods.mockResolvedValue([MOCK_USDC_METHOD, MOCK_ETH_METHOD]);
218
199
  const user = userEvent.setup();
219
200
  const { BuyCryptoCreditPanel } = await import("../components/billing/buy-crypto-credits-panel");
220
201
  render(<BuyCryptoCreditPanel />);
221
202
 
222
203
  await waitFor(() => {
223
- expect(screen.getByText("BTC")).toBeInTheDocument();
204
+ expect(screen.getByText("USDC")).toBeInTheDocument();
224
205
  });
225
206
 
226
- await user.click(screen.getByText("BTC"));
227
- await user.click(screen.getByText("$25"));
228
- await user.click(screen.getByRole("button", { name: "Pay with BTC" }));
207
+ // Default is first method (USDC)
208
+ expect(screen.getByRole("button", { name: "Pay with USDC" })).toBeInTheDocument();
229
209
 
230
- expect(await screen.findByText("Checkout failed. Please try again.")).toBeInTheDocument();
210
+ // Switch to ETH
211
+ await user.click(screen.getByText("ETH"));
212
+ expect(screen.getByRole("button", { name: "Pay with ETH" })).toBeInTheDocument();
231
213
  });
232
214
  });
@@ -6,6 +6,9 @@ import { InstanceListClient } from "../app/instances/instance-list-client";
6
6
  vi.mock("@/lib/trpc", () => ({
7
7
  trpc: {
8
8
  fleet: {
9
+ getChangelog: {
10
+ useQuery: vi.fn().mockReturnValue({ data: null, isLoading: false, error: null }),
11
+ },
9
12
  listInstances: {
10
13
  useQuery: vi.fn().mockReturnValue({
11
14
  data: {
@@ -10,6 +10,107 @@ import {
10
10
  type PaymentMethodAdmin,
11
11
  } from "@/lib/api";
12
12
 
13
+ /** BIP-44 coin types by chain name. */
14
+ const COIN_TYPES: Record<string, number> = {
15
+ base: 60,
16
+ ethereum: 60,
17
+ polygon: 60,
18
+ optimism: 60,
19
+ arbitrum: 60,
20
+ bitcoin: 0,
21
+ litecoin: 2,
22
+ dogecoin: 3,
23
+ };
24
+
25
+ /**
26
+ * Derive xpub from mnemonic client-side. Mnemonic NEVER leaves the browser.
27
+ * Returns the extended public key for the given BIP-44 coin type.
28
+ * Path: m/44'/<coinType>'/0'
29
+ */
30
+ async function deriveXpubFromMnemonic(mnemonic: string, coinType: number): Promise<string> {
31
+ const { HDKey } = await import("@scure/bip32");
32
+ const { mnemonicToSeedSync } = await import("@scure/bip39");
33
+ const seed = mnemonicToSeedSync(mnemonic.trim());
34
+ const master = HDKey.fromMasterSeed(seed);
35
+ const account = master.derive(`m/44'/${coinType}'/0'`);
36
+ const xpub = account.publicExtendedKey;
37
+ if (!xpub) throw new Error("Failed to derive xpub");
38
+ return xpub;
39
+ }
40
+
41
+ function MnemonicDeriveSection({
42
+ chain,
43
+ onXpubDerived,
44
+ }: {
45
+ chain: string;
46
+ onXpubDerived: (xpub: string) => void;
47
+ }) {
48
+ const [mnemonic, setMnemonic] = useState("");
49
+ const [deriving, setDeriving] = useState(false);
50
+ const [error, setError] = useState<string | null>(null);
51
+ const [derived, setDerived] = useState(false);
52
+
53
+ const coinType = COIN_TYPES[chain.toLowerCase()] ?? 60;
54
+ const path = `m/44'/${coinType}'/0'`;
55
+
56
+ async function handleDerive() {
57
+ if (!mnemonic.trim()) return;
58
+ setDeriving(true);
59
+ setError(null);
60
+ try {
61
+ const xpub = await deriveXpubFromMnemonic(mnemonic, coinType);
62
+ onXpubDerived(xpub);
63
+ setMnemonic(""); // Clear mnemonic immediately
64
+ setDerived(true);
65
+ } catch (err) {
66
+ setError((err as Error).message);
67
+ } finally {
68
+ setDeriving(false);
69
+ }
70
+ }
71
+
72
+ if (derived) {
73
+ return (
74
+ <div className="col-span-2 rounded-md border border-green-500/30 bg-green-500/5 p-3">
75
+ <p className="text-xs text-green-500">
76
+ xpub derived and set. Mnemonic was cleared from memory.
77
+ </p>
78
+ </div>
79
+ );
80
+ }
81
+
82
+ return (
83
+ <div className="col-span-2 space-y-2 rounded-md border border-dashed border-muted-foreground/30 p-3">
84
+ <div className="flex items-center justify-between">
85
+ <span className="text-xs font-medium text-muted-foreground">Derive xpub from mnemonic</span>
86
+ <span className="text-xs text-muted-foreground/60">Path: {path}</span>
87
+ </div>
88
+ <p className="text-xs text-muted-foreground/60">
89
+ Your mnemonic never leaves this browser. Only the xpub (public key) is sent to the server.
90
+ </p>
91
+ <input
92
+ type="password"
93
+ className="w-full rounded-md border bg-background px-3 py-1.5 text-sm font-mono"
94
+ placeholder="word1 word2 word3 ... (12 or 24 words)"
95
+ value={mnemonic}
96
+ onChange={(e) => setMnemonic(e.target.value)}
97
+ autoComplete="off"
98
+ spellCheck={false}
99
+ />
100
+ {error && <p className="text-xs text-destructive">{error}</p>}
101
+ <Button
102
+ type="button"
103
+ variant="outline"
104
+ size="sm"
105
+ onClick={handleDerive}
106
+ disabled={deriving || !mnemonic.trim()}
107
+ >
108
+ {deriving ? "Deriving..." : "Derive xpub"}
109
+ </Button>
110
+ </div>
111
+ );
112
+ }
113
+
13
114
  function AddMethodForm({ onSaved }: { onSaved: () => void }) {
14
115
  const [open, setOpen] = useState(false);
15
116
  const [saving, setSaving] = useState(false);
@@ -23,6 +124,8 @@ function AddMethodForm({ onSaved }: { onSaved: () => void }) {
23
124
  displayOrder: 0,
24
125
  confirmations: 1,
25
126
  rpcUrl: "",
127
+ oracleAddress: "",
128
+ xpub: "",
26
129
  });
27
130
 
28
131
  async function handleSubmit(e: React.FormEvent) {
@@ -40,6 +143,8 @@ function AddMethodForm({ onSaved }: { onSaved: () => void }) {
40
143
  enabled: true,
41
144
  displayOrder: form.displayOrder,
42
145
  rpcUrl: form.rpcUrl || null,
146
+ oracleAddress: form.oracleAddress || null,
147
+ xpub: form.xpub || null,
43
148
  confirmations: form.confirmations,
44
149
  });
45
150
  setSaving(false);
@@ -54,6 +159,8 @@ function AddMethodForm({ onSaved }: { onSaved: () => void }) {
54
159
  displayOrder: 0,
55
160
  confirmations: 1,
56
161
  rpcUrl: "",
162
+ oracleAddress: "",
163
+ xpub: "",
57
164
  });
58
165
  onSaved();
59
166
  }
@@ -118,6 +225,44 @@ function AddMethodForm({ onSaved }: { onSaved: () => void }) {
118
225
  onChange={(e) => setForm({ ...form, contractAddress: e.target.value })}
119
226
  />
120
227
  </label>
228
+ <label className="col-span-2 space-y-1">
229
+ <span className="text-xs text-muted-foreground">RPC URL (chain node endpoint)</span>
230
+ <input
231
+ className="w-full rounded-md border bg-background px-3 py-1.5 text-sm font-mono"
232
+ placeholder="http://op-geth:8545"
233
+ value={form.rpcUrl}
234
+ onChange={(e) => setForm({ ...form, rpcUrl: e.target.value })}
235
+ />
236
+ </label>
237
+ <label className="col-span-2 space-y-1">
238
+ <span className="text-xs text-muted-foreground">
239
+ Oracle Address (Chainlink feed — empty for stablecoins)
240
+ </span>
241
+ <input
242
+ className="w-full rounded-md border bg-background px-3 py-1.5 text-sm font-mono"
243
+ placeholder="0x..."
244
+ value={form.oracleAddress}
245
+ onChange={(e) => setForm({ ...form, oracleAddress: e.target.value })}
246
+ />
247
+ </label>
248
+
249
+ {/* --- xpub: paste directly or derive from mnemonic --- */}
250
+ <MnemonicDeriveSection
251
+ chain={form.chain}
252
+ onXpubDerived={(xpub) => setForm((f) => ({ ...f, xpub }))}
253
+ />
254
+ <label className="col-span-2 space-y-1">
255
+ <span className="text-xs text-muted-foreground">
256
+ xpub (auto-filled from mnemonic above, or paste directly)
257
+ </span>
258
+ <input
259
+ className="w-full rounded-md border bg-background px-3 py-1.5 text-xs font-mono"
260
+ placeholder="xpub6..."
261
+ value={form.xpub}
262
+ onChange={(e) => setForm({ ...form, xpub: e.target.value })}
263
+ />
264
+ </label>
265
+
121
266
  <label className="space-y-1">
122
267
  <span className="text-xs text-muted-foreground">Display Order</span>
123
268
  <input
@@ -199,8 +344,8 @@ export default function AdminPaymentMethodsPage() {
199
344
  <th className="pb-2 pr-4">Chain</th>
200
345
  <th className="pb-2 pr-4">Type</th>
201
346
  <th className="pb-2 pr-4">Contract</th>
202
- <th className="pb-2 pr-4">Decimals</th>
203
- <th className="pb-2 pr-4">Order</th>
347
+ <th className="pb-2 pr-4">xpub</th>
348
+ <th className="pb-2 pr-4">RPC</th>
204
349
  <th className="pb-2 pr-4">Status</th>
205
350
  <th className="pb-2">Actions</th>
206
351
  </tr>
@@ -214,8 +359,12 @@ export default function AdminPaymentMethodsPage() {
214
359
  <td className="py-2 pr-4 font-mono text-xs">
215
360
  {m.contractAddress ? `${m.contractAddress.slice(0, 10)}...` : "—"}
216
361
  </td>
217
- <td className="py-2 pr-4">{m.decimals}</td>
218
- <td className="py-2 pr-4">{m.displayOrder}</td>
362
+ <td className="py-2 pr-4 font-mono text-xs">
363
+ {m.xpub ? `${m.xpub.slice(0, 12)}...` : "—"}
364
+ </td>
365
+ <td className="py-2 pr-4 font-mono text-xs">
366
+ {m.rpcUrl ? `${m.rpcUrl.slice(0, 20)}...` : "—"}
367
+ </td>
219
368
  <td className="py-2 pr-4">
220
369
  <span
221
370
  className={
package/src/lib/api.ts CHANGED
@@ -1330,45 +1330,6 @@ export async function createCreditCheckout(priceId: string): Promise<CheckoutRes
1330
1330
  return { checkoutUrl: url };
1331
1331
  }
1332
1332
 
1333
- export async function createCryptoCheckout(
1334
- amountUsd: number,
1335
- ): Promise<{ url: string; referenceId: string }> {
1336
- return trpcVanilla.billing.cryptoCheckout.mutate({ amountUsd });
1337
- }
1338
-
1339
- export interface StablecoinCheckoutResult {
1340
- depositAddress: string;
1341
- amountRaw: string;
1342
- amountUsd: number;
1343
- chain: string;
1344
- token: string;
1345
- referenceId: string;
1346
- }
1347
-
1348
- export async function createStablecoinCheckout(
1349
- amountUsd: number,
1350
- token: string,
1351
- chain: string,
1352
- ): Promise<StablecoinCheckoutResult> {
1353
- return trpcVanilla.billing.stablecoinCheckout.mutate({ amountUsd, token, chain });
1354
- }
1355
-
1356
- export interface EthCheckoutResult {
1357
- depositAddress: string;
1358
- expectedWei: string;
1359
- amountUsd: number;
1360
- priceCents: number;
1361
- chain: string;
1362
- referenceId: string;
1363
- }
1364
-
1365
- export async function createEthCheckout(
1366
- amountUsd: number,
1367
- chain: string,
1368
- ): Promise<EthCheckoutResult> {
1369
- return trpcVanilla.billing.ethCheckout.mutate({ amountUsd, chain });
1370
- }
1371
-
1372
1333
  // --- Supported payment methods (runtime-configured) ---
1373
1334
 
1374
1335
  export interface SupportedPaymentMethod {
@@ -1414,6 +1375,8 @@ export interface PaymentMethodAdmin {
1414
1375
  enabled: boolean;
1415
1376
  displayOrder: number;
1416
1377
  rpcUrl: string | null;
1378
+ oracleAddress: string | null;
1379
+ xpub: string | null;
1417
1380
  confirmations: number;
1418
1381
  }
1419
1382
 
@@ -112,10 +112,8 @@ type AppRouterRecord = {
112
112
  setDefaultPaymentMethod: AnyTRPCMutationProcedure;
113
113
  affiliateStats: AnyTRPCQueryProcedure;
114
114
  affiliateReferrals: AnyTRPCQueryProcedure;
115
- cryptoCheckout: AnyTRPCMutationProcedure;
116
- stablecoinCheckout: AnyTRPCMutationProcedure;
117
- ethCheckout: AnyTRPCMutationProcedure;
118
115
  checkout: AnyTRPCMutationProcedure;
116
+ chargeStatus: AnyTRPCQueryProcedure;
119
117
  supportedPaymentMethods: AnyTRPCQueryProcedure;
120
118
  adminListPaymentMethods: AnyTRPCQueryProcedure;
121
119
  adminUpsertPaymentMethod: AnyTRPCMutationProcedure;