diff --git a/api/src/lib/brokerCreation.ts b/api/src/lib/brokerCreation.ts index 61903a6f..e148b6ee 100644 --- a/api/src/lib/brokerCreation.ts +++ b/api/src/lib/brokerCreation.ts @@ -94,7 +94,12 @@ export async function initializeBrokerCreation(): Promise { evmPrivateKey = walletKeys.evmPrivateKey; solanaPrivateKey = walletKeys.solanaPrivateKey; - const evmWallet = new ethers.Wallet(evmPrivateKey); + let evmWallet: ethers.Wallet; + try { + evmWallet = new ethers.Wallet(evmPrivateKey); + } catch { + throw new Error("Invalid broker creation EVM private key"); + } console.log("✅ EVM private key loaded"); console.log(`📍 EVM wallet address: ${evmWallet.address}`); diff --git a/api/src/models/dex.ts b/api/src/models/dex.ts index c5c7d742..52e1e2ae 100644 --- a/api/src/models/dex.ts +++ b/api/src/models/dex.ts @@ -2,8 +2,10 @@ import { z } from "zod"; import { getPrisma } from "../lib/prisma"; import type { Prisma, Dex, PrismaClient } from "@prisma/client"; import type { DexResult, Result } from "../lib/types"; -import { DexErrorType } from "../lib/types"; +import { DexErrorType, GitHubErrorType } from "../lib/types"; import { + forkTemplateRepository, + setupRepositoryWithSingleCommit, deleteRepository, setCustomDomain, removeCustomDomain, @@ -11,6 +13,7 @@ import { } from "../lib/github"; import { CAMPAIGNS_INTRO_COMMIT_PREFIXES } from "../../../config"; import type { DexConfig } from "../lib/types"; +import { generateRepositoryName } from "../lib/nameGenerator"; import { validateTradingViewColorConfig } from "./tradingViewConfig.js"; import { validateCSS } from "../lib/cssValidator.js"; @@ -59,6 +62,54 @@ export function convertDexToDexConfig(dex: Dex): DexConfig { }; } +function convertValidatedDataToDexConfig( + validatedData: z.infer, + brokerId: string, + brokerName: string +): DexConfig { + const decodedAnalyticsScript = validatedData.analyticsScript + ? decodeBase64(validatedData.analyticsScript) + : null; + + return { + brokerId, + brokerName, + chainIds: validatedData.chainIds ?? null, + defaultChain: validatedData.defaultChain ?? null, + themeCSS: validatedData.themeCSS ?? null, + telegramLink: validatedData.telegramLink ?? null, + discordLink: validatedData.discordLink ?? null, + xLink: validatedData.xLink ?? null, + walletConnectProjectId: validatedData.walletConnectProjectId ?? null, + privyAppId: validatedData.privyAppId ?? null, + privyTermsOfUse: validatedData.privyTermsOfUse ?? null, + privyLoginMethods: validatedData.privyLoginMethods ?? null, + enabledMenus: validatedData.enabledMenus ?? null, + customMenus: validatedData.customMenus ?? null, + enableAbstractWallet: validatedData.enableAbstractWallet ?? null, + disableMainnet: validatedData.disableMainnet ?? null, + disableTestnet: validatedData.disableTestnet ?? null, + disableEvmWallets: validatedData.disableEvmWallets ?? null, + disableSolanaWallets: validatedData.disableSolanaWallets ?? null, + enableServiceDisclaimerDialog: + validatedData.enableServiceDisclaimerDialog ?? null, + enableCampaigns: validatedData.enableCampaigns ?? null, + tradingViewColorConfig: validatedData.tradingViewColorConfig ?? null, + availableLanguages: validatedData.availableLanguages ?? null, + seoSiteName: validatedData.seoSiteName ?? null, + seoSiteDescription: validatedData.seoSiteDescription ?? null, + seoSiteLanguage: validatedData.seoSiteLanguage ?? null, + seoSiteLocale: validatedData.seoSiteLocale ?? null, + seoTwitterHandle: validatedData.seoTwitterHandle ?? null, + seoThemeColor: validatedData.seoThemeColor ?? null, + seoKeywords: validatedData.seoKeywords ?? null, + analyticsScript: decodedAnalyticsScript, + symbolList: validatedData.symbolList ?? null, + restrictedRegions: validatedData.restrictedRegions ?? null, + whitelistedIps: validatedData.whitelistedIps ?? null, + }; +} + export type Environment = "mainnet" | "staging" | "qa" | "dev"; export function getCurrentEnvironment(): Environment { @@ -435,9 +486,109 @@ export async function createDex( }; } + const brokerName = validatedData.brokerName || "Orderly DEX"; const integrationType = validatedData.integrationType || "low_code"; - const repoUrl: string | null = null; + let repoUrl: string | null = null; + + if (integrationType === "low_code") { + const repoName = generateRepositoryName(brokerName); + + try { + console.log( + "Creating repository in OrderlyNetworkDexCreator organization..." + ); + const forkResult = await forkTemplateRepository(repoName); + if (!forkResult.success) { + switch (forkResult.error.type) { + case GitHubErrorType.REPOSITORY_NAME_EMPTY: + case GitHubErrorType.REPOSITORY_NAME_INVALID: + case GitHubErrorType.REPOSITORY_NAME_TOO_LONG: + return { + success: false, + error: { + type: DexErrorType.VALIDATION_ERROR, + message: forkResult.error.message, + }, + }; + case GitHubErrorType.FORK_PERMISSION_DENIED: + return { + success: false, + error: { + type: DexErrorType.REPOSITORY_PERMISSION_DENIED, + message: forkResult.error.message, + }, + }; + case GitHubErrorType.FORK_REPOSITORY_NOT_FOUND: + return { + success: false, + error: { + type: DexErrorType.REPOSITORY_NOT_FOUND, + message: forkResult.error.message, + }, + }; + case GitHubErrorType.FORK_REPOSITORY_ALREADY_EXISTS: + return { + success: false, + error: { + type: DexErrorType.REPOSITORY_ALREADY_EXISTS, + message: forkResult.error.message, + }, + }; + default: + return { + success: false, + error: { + type: DexErrorType.REPOSITORY_CREATION_FAILED, + message: forkResult.error.message, + }, + }; + } + } + + repoUrl = forkResult.data; + console.log(`Successfully forked repository: ${repoUrl}`); + + const repoInfo = extractRepoInfoFromUrl(repoUrl); + if (!repoInfo) { + return { + success: false, + error: { + type: DexErrorType.REPOSITORY_INFO_EXTRACTION_FAILED, + message: `Failed to extract repository information from URL: ${repoUrl}`, + }, + }; + } + + const brokerId = "demo"; + + await setupRepositoryWithSingleCommit( + repoInfo.owner, + repoInfo.repo, + convertValidatedDataToDexConfig(validatedData, brokerId, brokerName), + { + primaryLogo: validatedData.primaryLogo ?? null, + secondaryLogo: validatedData.secondaryLogo ?? null, + favicon: validatedData.favicon ?? null, + pnlPosters: validatedData.pnlPosters ?? null, + }, + null, + user.address + ); + console.log(`Successfully set up repository for ${brokerName}`); + } catch (error) { + console.error("Error setting up repository:", error); + return { + success: false, + error: { + type: DexErrorType.REPOSITORY_CREATION_FAILED, + message: `Repository setup failed: ${ + error instanceof Error ? error.message : String(error) + }`, + }, + }; + } + } try { const brokerId = "demo"; diff --git a/app/scripts/check-i18n-untranslated.mjs b/app/scripts/check-i18n-untranslated.mjs index b4286cdd..679ef6a3 100644 --- a/app/scripts/check-i18n-untranslated.mjs +++ b/app/scripts/check-i18n-untranslated.mjs @@ -38,6 +38,7 @@ const INTENTIONALLY_IDENTICAL = new Set([ "seoConfig.siteLocalePlaceholder", "seoConfig.themeColorPlaceholder", "customDomain.ttl", + "landing.tvl", "settings.dexId", ]); diff --git a/app/src/components/orderly/ApiKeyRequiredOverlay.tsx b/app/src/components/orderly/ApiKeyRequiredOverlay.tsx index 0e6da528..71d39c89 100644 --- a/app/src/components/orderly/ApiKeyRequiredOverlay.tsx +++ b/app/src/components/orderly/ApiKeyRequiredOverlay.tsx @@ -12,6 +12,7 @@ import { useAccount, useChainId, useWalletClient } from "wagmi"; import { useLocation } from "@remix-run/react"; import { useLocalizedNavigate } from "@/utils/localizedRoute"; import { Modal } from "@/components/orderly/Modal"; +import { OdsCheckbox } from "@/components/orderly/OdsCheckbox"; import { getOrderlyEnvConfig } from "@/utils/environment"; import { hasCompleteStoredAdminApiKey, @@ -222,12 +223,11 @@ export function ApiKeyRequiredOverlay() { )}