Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/workflows/tests.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,7 @@ jobs:
- packages/crons
- packages/geocoder-service
- packages/recent-helium-transactions-service
- packages/blockchain-api
steps:
- uses: actions/checkout@v3
- uses: ./.github/actions/build-anchor/
Expand Down
16 changes: 12 additions & 4 deletions packages/blockchain-api/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,7 @@ ARG SENTRY_ORG
ARG SENTRY_PROJECT
ARG SENTRY_RELEASE

# Skip server env validation at build time — real values injected at runtime
ENV SKIP_ENV_VALIDATION=1 \
NEXT_PUBLIC_PRIVY_APP_ID="__NEXT_PUBLIC_PRIVY_APP_ID__" \
ENV NEXT_PUBLIC_PRIVY_APP_ID="__NEXT_PUBLIC_PRIVY_APP_ID__" \
NEXT_PUBLIC_SOLANA_CLUSTER="__NEXT_PUBLIC_SOLANA_CLUSTER__" \
NEXT_PUBLIC_SOLANA_URL="__NEXT_PUBLIC_SOLANA_URL__" \
NEXT_PUBLIC_WORLD_HELIUM_URL="__NEXT_PUBLIC_WORLD_HELIUM_URL__" \
Expand All @@ -46,7 +44,17 @@ ENV SENTRY_AUTH_TOKEN=${SENTRY_AUTH_TOKEN} \
SENTRY_PROJECT=${SENTRY_PROJECT} \
SENTRY_RELEASE=${SENTRY_RELEASE}

RUN pnpm turbo run build --filter=@helium/blockchain-api-service
RUN printf '%s\n' \
'SKIP_ENV_VALIDATION=1' \
'PG_USER=build' \
'PG_NAME=build' \
'PG_HOST=localhost' \
'PG_PORT=5432' \
'PRIVY_APP_SECRET=build' \
'BRIDGE_API_KEY=build' \
'JUPITER_API_KEY=build' \
> packages/blockchain-api/.env && \
pnpm turbo run build --filter=@helium/blockchain-api-service

# Production image, copy all the files and run next
FROM node:22-alpine
Expand Down
2 changes: 2 additions & 0 deletions packages/blockchain-api/src/app/api/v1/[[...rest]]/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ import { onError } from "@orpc/server";
import { ORPCError } from "@orpc/server";
import * as Sentry from "@sentry/nextjs";

export const dynamic = "force-dynamic";

/**
* ORPC OpenAPI handler for the public v1 API.
*
Expand Down
7 changes: 3 additions & 4 deletions packages/blockchain-api/src/app/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import { Geist, Geist_Mono } from "next/font/google";
import React, { Suspense } from "react";
import { Toaster } from "@/components/ui/sonner";
import "./globals.css";
import "../lib/background-jobs/transaction-resubmission";

const geistSans = Geist({
variable: "--font-geist-sans",
Expand All @@ -31,14 +30,14 @@ export default function RootLayout({
/>
</head>
<body
className={`${geistSans.variable} ${geistMono.variable} antialiased min-h-screen bg-background text-foreground`}
className={`${geistSans.variable} ${geistMono.variable} bg-background text-foreground min-h-screen antialiased`}
>
<Suspense>
<main
className="relative layout-container flex size-full min-h-screen grow flex-col"
className="layout-container size-full relative flex min-h-screen grow flex-col"
style={{ fontFamily: 'Inter, "Noto Sans", sans-serif' }}
>
<div className="flex flex-col flex-1 min-h-screen w-full">
<div className="flex min-h-screen w-full flex-1 flex-col">
<Providers>{children}</Providers>
<Toaster position="top-center" className="md:hidden" />
<Toaster position="bottom-right" className="hidden md:block" />
Expand Down
10 changes: 6 additions & 4 deletions packages/blockchain-api/src/app/rpc/[[...rest]]/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ import { ORPCError } from "@orpc/server";
import { ValidationError } from "@orpc/server";
import * as Sentry from "@sentry/nextjs";

export const dynamic = "force-dynamic";

const rpcHandler = new RPCHandler(appRouter, {
interceptors: [
onError((error) => {
Expand All @@ -26,7 +28,7 @@ const rpcHandler = new RPCHandler(appRouter, {
Object.entries(value).map(([key, val]) => [
key,
serializeValue(val),
]),
])
);
} catch {
// If serialization fails, convert to string
Expand Down Expand Up @@ -54,8 +56,8 @@ const rpcHandler = new RPCHandler(appRouter, {
data: serializeValue(error.cause.data),
}
: error.cause
? serializeValue(error.cause)
: undefined,
? serializeValue(error.cause)
: undefined,
};
console.error("RPC Error:", JSON.stringify(errorDetails, null, 2));

Expand Down Expand Up @@ -147,7 +149,7 @@ async function handleRequest(request: Request) {
if (!response && isBatchPath) {
console.error(
"[RPC] BatchHandlerPlugin failed to handle batch request. " +
"This might indicate a bug in ORPC or incorrect configuration.",
"This might indicate a bug in ORPC or incorrect configuration."
);
}

Expand Down
10 changes: 10 additions & 0 deletions packages/blockchain-api/src/instrumentation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,16 @@ import * as Sentry from "@sentry/nextjs";
export async function register() {
if (process.env.NEXT_RUNTIME === "nodejs") {
await import("../sentry.server.config");
if (process.env.NO_PG !== "true") {
try {
const { transactionResubmissionService } = await import(
"./lib/background-jobs/transaction-resubmission"
);
transactionResubmissionService.start();
} catch (e) {
console.error("Failed to start transaction resubmission service:", e);
}
}
}

if (process.env.NEXT_RUNTIME === "edge") {
Expand Down
19 changes: 11 additions & 8 deletions packages/blockchain-api/src/lib/db.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,24 +12,27 @@ if (!POSTGRES_URL) {
throw new Error("POSTGRES_URL environment variable is not set");
} else {
console.warn(
"POSTGRES_URL environment variable is not set. Using default for development.",
"POSTGRES_URL environment variable is not set. Using default for development."
);
}
}

// For serverless environments, we want to limit the pool size
// For Docker/standalone, we can use a larger pool
export const isServerless = process.env.VERCEL === "1";
const poolConfig = isServerless
const noPg = process.env.NO_PG === "true";
const poolConfig = noPg
? { max: 1, min: 0, acquire: 1000, idle: 1000 }
: isServerless
? {
max: 1, // Serverless should use minimal connections
acquire: 30000, // Time to wait for a connection (30 seconds)
idle: 10000, // Time before connection is released (10 seconds)
max: 1,
acquire: 30000,
idle: 10000,
}
: {
max: 20, // Docker can use more connections
max: 20,
min: 5,
acquire: 60000, // More generous timeouts for Docker
acquire: 60000,
idle: 10000,
};

Expand Down Expand Up @@ -61,7 +64,7 @@ export const sequelize = new Sequelize(POSTGRES_URL, {
return reject(err);
}
resolve(token);
}),
})
);
config.dialectOptions = {
ssl: {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ type InputDeploymentInfo = Extract<

// Convert input deploymentInfo to onboarding format (partial)
function inputToOnboardingDeploymentInfo(
info: InputDeploymentInfo | undefined,
info: InputDeploymentInfo | undefined
): MobileDeploymentInfoV0 | undefined {
if (!info) return undefined;

Expand All @@ -70,7 +70,7 @@ function inputToOnboardingDeploymentInfo(
// null = unset the field, undefined = use the prior value
function mergeDeploymentInfo(
existing: MobileDeploymentInfoV0 | null | undefined,
newInfo: InputDeploymentInfo | undefined,
newInfo: InputDeploymentInfo | undefined
): MobileDeploymentInfoV0 | undefined {
if (!newInfo) return existing ?? undefined;
if (!existing) return inputToOnboardingDeploymentInfo(newInfo);
Expand Down Expand Up @@ -111,7 +111,7 @@ function mergeDeploymentInfo(
? serial === null
? null
: serial
: (existingWifi.serial ?? null),
: existingWifi.serial ?? null,
},
};
}
Expand Down Expand Up @@ -161,7 +161,7 @@ export const updateHotspotInfo =

// Check wallet has sufficient balance for transaction fees
const walletBalance = await connection.getBalance(
new PublicKey(walletAddress),
new PublicKey(walletAddress)
);
const required = calculateRequiredBalance(BASE_TX_FEE_LAMPORTS, 0);
if (walletBalance < required) {
Expand All @@ -174,11 +174,11 @@ export const updateHotspotInfo =
const hemProgram = await initHemLocal(provider);
const [keyToAssetK] = keyToAssetKey(HNT_DAO, entityPubKey);
const keyToAsset = await (hemProgram.account as any).keyToAssetV0.fetch(
keyToAssetK,
keyToAssetK
);
const entityKey = decodeEntityKey(
keyToAsset.entityKey,
keyToAsset.keySerialization,
keyToAsset.keySerialization
);

if (!entityKey) {
Expand Down Expand Up @@ -235,7 +235,7 @@ export const updateHotspotInfo =
// Merge existing with new deploymentInfo
const mergedDeploymentInfo = mergeDeploymentInfo(
existingDeploymentInfo,
input.deploymentInfo,
input.deploymentInfo
);

const response = await onboardingClient.updateMobileMetadata({
Expand Down Expand Up @@ -289,21 +289,32 @@ export const updateHotspotInfo =
hotspotKey: entityPubKey,
deviceType: input.deviceType,
...(location && { location }),
...(h3 && { h3Index: h3.mobile ?? h3.iot }),
...(input.deviceType === "iot" &&
input.gain !== undefined && { gain: input.gain }),
...(input.deviceType === "iot" &&
input.elevation !== undefined && { elevation: input.elevation }),
...(input.deviceType === "mobile" &&
input.deploymentInfo && {
deploymentType: input.deploymentInfo.type,
...(input.deploymentInfo.type === "WIFI" && {
antenna: input.deploymentInfo.antenna,
elevation: input.deploymentInfo.elevation,
azimuth: input.deploymentInfo.azimuth,
mechanicalDownTilt: input.deploymentInfo.mechanicalDownTilt,
electricalDownTilt: input.deploymentInfo.electricalDownTilt,
}),
...(input.deploymentInfo.type === "CBRS" && {
radioInfos: input.deploymentInfo.radioInfos,
}),
}),
},
},
estimatedSolFee: toTokenAmountOutput(
new BN(totalFee),
NATIVE_MINT.toBase58(),
NATIVE_MINT.toBase58()
),
appliedTo,
};
},
}
);
Loading