File size: 3,467 Bytes
d2948d0
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
import { execFile } from "node:child_process";
import { promisify } from "node:util";
import { mkdirSync } from "node:fs";
import path from "node:path";
import crypto from "node:crypto";
import type { NextRequest } from "next/server";
import { NextResponse } from "next/server";

const execFileAsync = promisify(execFile);
const SESSION_COOKIE = "cardcli_demo_session";
const DEMO_HOME_ROOT = process.env.CARDCLI_DEMO_HOME_ROOT ?? "/tmp/cardcli-demo";
const SHARED_DEMO_SESSION = (process.env.CARDCLI_DEMO_SHARED_SESSION ?? "true") === "true";

function cardCliBinPath() {
  return (
    process.env.CARDCLI_BIN ??
    path.resolve(process.cwd(), "..", "target", "release", process.platform === "win32" ? "card-cli.exe" : "card-cli")
  );
}

function sessionIdFromRequest(request: NextRequest) {
  if (SHARED_DEMO_SESSION) {
    return "shared-demo";
  }
  return request.cookies.get(SESSION_COOKIE)?.value ?? crypto.randomUUID();
}

function cardCliHome(sessionId: string) {
  const dir = path.join(DEMO_HOME_ROOT, "sessions", sessionId);
  mkdirSync(dir, { recursive: true });
  return dir;
}

function cardCliEnv(sessionId: string): NodeJS.ProcessEnv {
  return {
    ...process.env,
    CARDCLI_HOME: cardCliHome(sessionId),
    CARDCLI_SOLANA_PRIVATE_KEY_BASE58:
      process.env.CARDCLI_DEMO_SOLANA_PRIVATE_KEY ?? process.env.CARDCLI_SOLANA_PRIVATE_KEY_BASE58 ?? "",
    CARDCLI_FEE_PROGRAM_ID:
      process.env.CARDCLI_DEMO_FEE_PROGRAM_ID ?? process.env.CARDCLI_FEE_PROGRAM_ID ?? "EpB6hUZUf1vvvTVAYvEN57pjUWfYswaAuKGGQDHP5iH",
    CARDCLI_ONCHAIN_FEE_COLLECTION_ENABLED:
      process.env.CARDCLI_ONCHAIN_FEE_COLLECTION_ENABLED ?? "true",
    CARDCLI_FEE_FIXED_CENTS: process.env.CARDCLI_FEE_FIXED_CENTS ?? "10",
    CARDCLI_FEE_VARIABLE_BPS: process.env.CARDCLI_FEE_VARIABLE_BPS ?? "20",
    CARDCLI_RPC_URL: process.env.CARDCLI_RPC_URL ?? "https://api.devnet.solana.com",
    CARDCLI_COINBASE_BASE_URL: process.env.CARDCLI_COINBASE_BASE_URL ?? "https://api.coinbase.com",
  };
}

export async function runCardCli(
  request: NextRequest,
  args: string[],
): Promise<{ sessionId: string; payload: unknown; stderr?: string }> {
  const sessionId = sessionIdFromRequest(request);
  const env = cardCliEnv(sessionId);
  const bin = cardCliBinPath();
  try {
    const { stdout, stderr } = await execFileAsync(bin, [...args, "--format", "json"], {
      env,
      maxBuffer: 10 * 1024 * 1024,
    });
    return {
      sessionId,
      payload: JSON.parse(stdout),
      stderr,
    };
  } catch (error) {
    const err = error as { stdout?: string; stderr?: string; message?: string };
    const raw = err.stderr || err.stdout || JSON.stringify({ ok: false, error: { message: err.message } });
    try {
      return {
        sessionId,
        payload: JSON.parse(raw),
        stderr: err.stderr,
      };
    } catch {
      return {
        sessionId,
        payload: {
          ok: false,
          error: {
            code: "WEB_EXEC_ERROR",
            message: err.message ?? "Failed to run card-cli",
            raw,
          },
        },
        stderr: err.stderr,
      };
    }
  }
}

export function jsonWithSession(sessionId: string, payload: unknown, init?: ResponseInit) {
  const response = NextResponse.json(payload, init);
  if (!SHARED_DEMO_SESSION) {
    response.cookies.set(SESSION_COOKIE, sessionId, {
      httpOnly: true,
      sameSite: "lax",
      path: "/",
      maxAge: 60 * 60 * 24,
    });
  }
  return response;
}