- |
- {truncate(a.engagement_public_id, 14)}
+ |
+
+ |
+
+
|
{formatDate(a.window_start)} → {formatDate(a.window_end)}
diff --git a/solutions/liquidity-accord/packages/app/src/components/features/engagement-list.tsx b/solutions/liquidity-accord/packages/app/src/components/features/engagement-list.tsx
index bbae73c..efcbabc 100644
--- a/solutions/liquidity-accord/packages/app/src/components/features/engagement-list.tsx
+++ b/solutions/liquidity-accord/packages/app/src/components/features/engagement-list.tsx
@@ -1,5 +1,6 @@
import type { EngagementResponse } from '@/services/EngagementService';
import { Badge } from '@/components/ui/badge';
+import { CopyableId } from '@/components/ui/copyable-id';
import { Skeleton } from '@/components/ui/skeleton';
interface EngagementListProps {
@@ -34,6 +35,7 @@ export function EngagementList({ engagements, loading, onSelect }: EngagementLis
+ | ID |
Pair |
Venue |
Issuer |
@@ -49,6 +51,9 @@ export function EngagementList({ engagements, loading, onSelect }: EngagementLis
className="border-b border-[var(--border-dark)] last:border-0 cursor-pointer hover:bg-[var(--background-secondary)] transition-colors"
onClick={() => onSelect?.(e)}
>
+
+
+ |
{e.pair_symbol} |
{e.venue} |
diff --git a/solutions/liquidity-accord/packages/app/src/components/features/mm-profile-list.tsx b/solutions/liquidity-accord/packages/app/src/components/features/mm-profile-list.tsx
index 31bcc67..bb381f6 100644
--- a/solutions/liquidity-accord/packages/app/src/components/features/mm-profile-list.tsx
+++ b/solutions/liquidity-accord/packages/app/src/components/features/mm-profile-list.tsx
@@ -1,5 +1,6 @@
import type { MMProfileResponse } from '@/services/MMProfileService';
import { Badge } from '@/components/ui/badge';
+import { CopyableId } from '@/components/ui/copyable-id';
import { Skeleton } from '@/components/ui/skeleton';
interface MMProfileListProps {
@@ -29,6 +30,7 @@ export function MMProfileList({ profiles, loading }: MMProfileListProps) {
+ | ID |
Name |
Operator |
Tier |
@@ -40,6 +42,9 @@ export function MMProfileList({ profiles, loading }: MMProfileListProps) {
{profiles.map((p) => (
+ |
+
+ |
{p.name} |
{truncateAddress(p.operator_address)}
diff --git a/solutions/liquidity-accord/packages/app/src/components/ui/copyable-id.tsx b/solutions/liquidity-accord/packages/app/src/components/ui/copyable-id.tsx
new file mode 100644
index 0000000..3a0dac7
--- /dev/null
+++ b/solutions/liquidity-accord/packages/app/src/components/ui/copyable-id.tsx
@@ -0,0 +1,127 @@
+import { useState, type MouseEvent } from 'react';
+
+interface CopyableIdProps {
+ value: string;
+ max?: number;
+ className?: string;
+}
+
+function truncate(s: string, max: number): string {
+ return s.length > max ? `${s.slice(0, max)}…` : s;
+}
+
+async function copyToClipboard(value: string): Promise {
+ if (navigator.clipboard?.writeText) {
+ try {
+ await navigator.clipboard.writeText(value);
+ return true;
+ } catch {
+ // fall through to legacy fallback
+ }
+ }
+
+ const ta = document.createElement('textarea');
+ ta.value = value;
+ ta.setAttribute('readonly', '');
+ ta.style.position = 'fixed';
+ ta.style.top = '0';
+ ta.style.left = '0';
+ ta.style.opacity = '0';
+ document.body.appendChild(ta);
+ ta.focus();
+ ta.select();
+ let ok = false;
+ try {
+ ok = document.execCommand('copy');
+ } catch {
+ ok = false;
+ }
+ document.body.removeChild(ta);
+ return ok;
+}
+
+export function CopyableId({ value, max = 12, className = '' }: CopyableIdProps) {
+ const [copied, setCopied] = useState(false);
+
+ const handleClick = async (e: MouseEvent) => {
+ e.stopPropagation();
+ e.preventDefault();
+ const ok = await copyToClipboard(value);
+ if (!ok) return;
+ setCopied(true);
+ window.setTimeout(() => setCopied(false), 1400);
+ };
+
+ return (
+
+
+
+
+ Copied
+
+
+ );
+}
+
+function CopyIcon({ copied }: { copied: boolean }) {
+ return (
+
+
+
+
+ );
+}
diff --git a/solutions/liquidity-accord/packages/app/src/hooks/use-auth.ts b/solutions/liquidity-accord/packages/app/src/hooks/use-auth.ts
index ffb7a61..ba2873f 100644
--- a/solutions/liquidity-accord/packages/app/src/hooks/use-auth.ts
+++ b/solutions/liquidity-accord/packages/app/src/hooks/use-auth.ts
@@ -23,7 +23,7 @@ async function authenticateWithSiwe(address: string) {
const domain = window.location.host;
const origin = window.location.origin;
- const statement = 'Sign in to ReineiraOS';
+ const statement = 'Sign in to Liquidity Accord';
const message = buildSiweMessage(domain, address, statement, origin, nonce);
const signature = await useWalletStore.getState().signMessage(message);
diff --git a/solutions/liquidity-accord/packages/app/src/routes/index.tsx b/solutions/liquidity-accord/packages/app/src/routes/index.tsx
index d9fbfbf..ca4eb6e 100644
--- a/solutions/liquidity-accord/packages/app/src/routes/index.tsx
+++ b/solutions/liquidity-accord/packages/app/src/routes/index.tsx
@@ -5,8 +5,10 @@ export function WalletAuthPage() {
- Reineira Platform Modules
- Build on ReineiraOS
+ Liquidity Accord
+
+ Confidential performance bonds & delisting insurance
+
Connect your wallet to get started
diff --git a/solutions/liquidity-accord/packages/app/vercel.json b/solutions/liquidity-accord/packages/app/vercel.json
index 0b94be2..4e1fc07 100644
--- a/solutions/liquidity-accord/packages/app/vercel.json
+++ b/solutions/liquidity-accord/packages/app/vercel.json
@@ -2,7 +2,13 @@
"installCommand": "cd ../.. && pnpm install --frozen-lockfile",
"buildCommand": "pnpm run build",
"outputDirectory": "dist",
- "rewrites": [{ "source": "/((?!assets/).*)", "destination": "/index.html" }],
+ "rewrites": [
+ {
+ "source": "/api/:path*",
+ "destination": "https://liquidity-accord-backend.vercel.app/api/:path*"
+ },
+ { "source": "/((?!assets/).*)", "destination": "/index.html" }
+ ],
"headers": [
{
"source": "/(.*)",
diff --git a/solutions/liquidity-accord/packages/backend/.gitignore b/solutions/liquidity-accord/packages/backend/.gitignore
new file mode 100644
index 0000000..c8a7336
--- /dev/null
+++ b/solutions/liquidity-accord/packages/backend/.gitignore
@@ -0,0 +1,2 @@
+.vercel
+.env*.local
diff --git a/solutions/liquidity-accord/packages/backend/api/v1/api-credentials/[clientId].ts b/solutions/liquidity-accord/packages/backend/api/v1/api-credentials/[clientId].ts
deleted file mode 100644
index 3922aaf..0000000
--- a/solutions/liquidity-accord/packages/backend/api/v1/api-credentials/[clientId].ts
+++ /dev/null
@@ -1,32 +0,0 @@
-import type { VercelRequest, VercelResponse } from '@vercel/node';
-import { RevokeCredentialsUseCase } from '../../../src/application/use-case/credential/revoke-credentials.use-case.js';
-import { container } from '../../../src/infrastructure/container.js';
-import type { AuthenticatedRequest } from '../../../src/interface/handler-factory.js';
-import { sendResponse } from '../../../src/interface/handler-factory.js';
-import { withAuth } from '../../../src/interface/middleware/with-auth.js';
-import { withCors } from '../../../src/interface/middleware/with-cors.js';
-import { Response } from '../../../src/interface/response.js';
-
-const useCase = new RevokeCredentialsUseCase(container.apiCredentialRepo);
-
-const handler = async (req: VercelRequest, res: VercelResponse): Promise => {
- if (req.method !== 'DELETE') {
- sendResponse(res, Response.badRequest('Method not allowed'));
- return;
- }
-
- try {
- const clientId = req.query.clientId as string;
- const authReq = req as AuthenticatedRequest;
- await useCase.execute(clientId, authReq.authPayload!.userId);
- sendResponse(res, Response.noContent());
- } catch (error) {
- if (error instanceof Error && 'statusCode' in error) {
- sendResponse(res, Response.fromError(error, (error as { statusCode: number }).statusCode));
- return;
- }
- sendResponse(res, Response.internalServerError());
- }
-};
-
-export default withCors(withAuth(handler));
diff --git a/solutions/liquidity-accord/packages/backend/api/v1/api-credentials/index.ts b/solutions/liquidity-accord/packages/backend/api/v1/api-credentials/index.ts
deleted file mode 100644
index ec7eb39..0000000
--- a/solutions/liquidity-accord/packages/backend/api/v1/api-credentials/index.ts
+++ /dev/null
@@ -1,39 +0,0 @@
-import type { VercelRequest, VercelResponse } from '@vercel/node';
-import { GenerateCredentialsUseCase } from '../../../src/application/use-case/credential/generate-credentials.use-case.js';
-import { GetCredentialsUseCase } from '../../../src/application/use-case/credential/get-credentials.use-case.js';
-import { container } from '../../../src/infrastructure/container.js';
-import { createGetHandler, sendResponse } from '../../../src/interface/handler-factory.js';
-import { withAuth } from '../../../src/interface/middleware/with-auth.js';
-import { withCors } from '../../../src/interface/middleware/with-cors.js';
-import { Response } from '../../../src/interface/response.js';
-
-const generateUseCase = new GenerateCredentialsUseCase(container.apiCredentialRepo);
-const getUseCase = new GetCredentialsUseCase(container.apiCredentialRepo);
-
-const postHandler = createGetHandler({
- operationName: 'GenerateCredentials',
- execute: async (_req, authPayload) => {
- const result = await generateUseCase.execute(authPayload!.userId);
- return Response.created(result);
- },
-});
-
-const getHandler = createGetHandler({
- operationName: 'GetCredentials',
- execute: async (_req, authPayload) => {
- const result = await getUseCase.execute(authPayload!.userId);
- return Response.ok(result);
- },
-});
-
-const handler = async (req: VercelRequest, res: VercelResponse): Promise => {
- if (req.method === 'POST') {
- return postHandler(req, res);
- }
- if (req.method === 'GET') {
- return getHandler(req, res);
- }
- sendResponse(res, Response.badRequest('Method not allowed'));
-};
-
-export default withCors(withAuth(handler));
diff --git a/solutions/liquidity-accord/packages/backend/api/v1/api-credentials/oauth/token.ts b/solutions/liquidity-accord/packages/backend/api/v1/api-credentials/oauth/token.ts
deleted file mode 100644
index c4cba3b..0000000
--- a/solutions/liquidity-accord/packages/backend/api/v1/api-credentials/oauth/token.ts
+++ /dev/null
@@ -1,19 +0,0 @@
-import { OAuthTokenRequestSchema } from '../../../../src/application/dto/credential/credential.dto.js';
-import { OAuthTokenExchangeUseCase } from '../../../../src/application/use-case/credential/oauth-token-exchange.use-case.js';
-import { container } from '../../../../src/infrastructure/container.js';
-import { createHandler } from '../../../../src/interface/handler-factory.js';
-import { withCors } from '../../../../src/interface/middleware/with-cors.js';
-import { Response } from '../../../../src/interface/response.js';
-
-const useCase = new OAuthTokenExchangeUseCase(container.apiCredentialRepo, container.jwtService);
-
-const handler = createHandler({
- operationName: 'OAuthTokenExchange',
- schema: OAuthTokenRequestSchema,
- execute: async (dto) => {
- const result = await useCase.execute(dto);
- return Response.ok(result);
- },
-});
-
-export default withCors(handler);
diff --git a/solutions/liquidity-accord/packages/backend/api/v1/attestations/[publicId].ts b/solutions/liquidity-accord/packages/backend/api/v1/attestations/[publicId].ts
deleted file mode 100644
index 6651ad9..0000000
--- a/solutions/liquidity-accord/packages/backend/api/v1/attestations/[publicId].ts
+++ /dev/null
@@ -1,19 +0,0 @@
-import { GetAttestationByIdUseCase } from '../../../src/application/use-case/attestation/get-attestation-by-id.use-case.js';
-import { container } from '../../../src/infrastructure/container.js';
-import { createGetHandler } from '../../../src/interface/handler-factory.js';
-import { withAuth } from '../../../src/interface/middleware/with-auth.js';
-import { withCors } from '../../../src/interface/middleware/with-cors.js';
-import { Response } from '../../../src/interface/response.js';
-
-const useCase = new GetAttestationByIdUseCase(container.attestationRepo);
-
-const handler = createGetHandler({
- operationName: 'GetAttestationById',
- execute: async (req) => {
- const publicId = req.query.publicId as string;
- const result = await useCase.execute(publicId);
- return result ? Response.ok(result) : Response.notFound('Attestation not found');
- },
-});
-
-export default withCors(withAuth(handler));
diff --git a/solutions/liquidity-accord/packages/backend/api/v1/balance/index.ts b/solutions/liquidity-accord/packages/backend/api/v1/balance/index.ts
deleted file mode 100644
index 9620b03..0000000
--- a/solutions/liquidity-accord/packages/backend/api/v1/balance/index.ts
+++ /dev/null
@@ -1,17 +0,0 @@
-import { GetBalanceUseCase } from '../../../src/application/use-case/balance/get-balance.use-case.js';
-import { createGetHandler } from '../../../src/interface/handler-factory.js';
-import { withAuth } from '../../../src/interface/middleware/with-auth.js';
-import { withCors } from '../../../src/interface/middleware/with-cors.js';
-import { Response } from '../../../src/interface/response.js';
-
-const useCase = new GetBalanceUseCase();
-
-const handler = createGetHandler({
- operationName: 'GetBalance',
- execute: async (_req, authPayload) => {
- const result = await useCase.execute(authPayload!.walletAddress);
- return Response.ok(result);
- },
-});
-
-export default withCors(withAuth(handler));
diff --git a/solutions/liquidity-accord/packages/backend/api/v1/engagements/[publicId].ts b/solutions/liquidity-accord/packages/backend/api/v1/engagements/[publicId].ts
deleted file mode 100644
index 2d308e7..0000000
--- a/solutions/liquidity-accord/packages/backend/api/v1/engagements/[publicId].ts
+++ /dev/null
@@ -1,19 +0,0 @@
-import { GetEngagementByIdUseCase } from '../../../src/application/use-case/engagement/get-engagement-by-id.use-case.js';
-import { container } from '../../../src/infrastructure/container.js';
-import { createGetHandler } from '../../../src/interface/handler-factory.js';
-import { withAuth } from '../../../src/interface/middleware/with-auth.js';
-import { withCors } from '../../../src/interface/middleware/with-cors.js';
-import { Response } from '../../../src/interface/response.js';
-
-const useCase = new GetEngagementByIdUseCase(container.engagementRepo);
-
-const handler = createGetHandler({
- operationName: 'GetEngagementById',
- execute: async (req) => {
- const publicId = req.query.publicId as string;
- const result = await useCase.execute(publicId);
- return result ? Response.ok(result) : Response.notFound('Engagement not found');
- },
-});
-
-export default withCors(withAuth(handler));
diff --git a/solutions/liquidity-accord/packages/backend/api/v1/escrows/[publicId].ts b/solutions/liquidity-accord/packages/backend/api/v1/escrows/[publicId].ts
deleted file mode 100644
index fee36bb..0000000
--- a/solutions/liquidity-accord/packages/backend/api/v1/escrows/[publicId].ts
+++ /dev/null
@@ -1,19 +0,0 @@
-import { GetEscrowByIdUseCase } from '../../../src/application/use-case/escrow/get-escrow-by-id.use-case.js';
-import { container } from '../../../src/infrastructure/container.js';
-import { createGetHandler } from '../../../src/interface/handler-factory.js';
-import { withAuth } from '../../../src/interface/middleware/with-auth.js';
-import { withCors } from '../../../src/interface/middleware/with-cors.js';
-import { Response } from '../../../src/interface/response.js';
-
-const useCase = new GetEscrowByIdUseCase(container.escrowRepo);
-
-const handler = createGetHandler({
- operationName: 'GetEscrowById',
- execute: async (req) => {
- const publicId = req.query.publicId as string;
- const result = await useCase.execute(publicId);
- return Response.ok(result);
- },
-});
-
-export default withCors(withAuth(handler));
diff --git a/solutions/liquidity-accord/packages/backend/api/v1/mm-profiles/[publicId].ts b/solutions/liquidity-accord/packages/backend/api/v1/mm-profiles/[publicId].ts
deleted file mode 100644
index 1857121..0000000
--- a/solutions/liquidity-accord/packages/backend/api/v1/mm-profiles/[publicId].ts
+++ /dev/null
@@ -1,19 +0,0 @@
-import { GetMMProfileByIdUseCase } from '../../../src/application/use-case/mm-profile/get-mm-profile-by-id.use-case.js';
-import { container } from '../../../src/infrastructure/container.js';
-import { createGetHandler } from '../../../src/interface/handler-factory.js';
-import { withAuth } from '../../../src/interface/middleware/with-auth.js';
-import { withCors } from '../../../src/interface/middleware/with-cors.js';
-import { Response } from '../../../src/interface/response.js';
-
-const useCase = new GetMMProfileByIdUseCase(container.mmProfileRepo);
-
-const handler = createGetHandler({
- operationName: 'GetMMProfileById',
- execute: async (req) => {
- const publicId = req.query.publicId as string;
- const result = await useCase.execute(publicId);
- return result ? Response.ok(result) : Response.notFound('MM profile not found');
- },
-});
-
-export default withCors(withAuth(handler));
diff --git a/solutions/liquidity-accord/packages/backend/api/v1/public/escrows/[publicId].ts b/solutions/liquidity-accord/packages/backend/api/v1/public/escrows/[publicId].ts
deleted file mode 100644
index 76cc7d2..0000000
--- a/solutions/liquidity-accord/packages/backend/api/v1/public/escrows/[publicId].ts
+++ /dev/null
@@ -1,18 +0,0 @@
-import { GetPublicEscrowUseCase } from '../../../../src/application/use-case/escrow/get-public-escrow.use-case.js';
-import { container } from '../../../../src/infrastructure/container.js';
-import { createGetHandler } from '../../../../src/interface/handler-factory.js';
-import { withCors } from '../../../../src/interface/middleware/with-cors.js';
-import { Response } from '../../../../src/interface/response.js';
-
-const useCase = new GetPublicEscrowUseCase(container.escrowRepo);
-
-const handler = createGetHandler({
- operationName: 'GetPublicEscrow',
- execute: async (req) => {
- const publicId = req.query.publicId as string;
- const result = await useCase.execute(publicId);
- return Response.ok(result);
- },
-});
-
-export default withCors(handler);
diff --git a/solutions/liquidity-accord/packages/backend/api/v1/transactions/withdrawals/report.ts b/solutions/liquidity-accord/packages/backend/api/v1/transactions/withdrawals/report.ts
deleted file mode 100644
index cbd9e97..0000000
--- a/solutions/liquidity-accord/packages/backend/api/v1/transactions/withdrawals/report.ts
+++ /dev/null
@@ -1,26 +0,0 @@
-import { z } from 'zod';
-import { ReportWithdrawalTransactionDtoSchema } from '../../../../src/application/dto/transaction/report-transaction.dto.js';
-import { ReportWithdrawalTransactionUseCase } from '../../../../src/application/use-case/transaction/report-withdrawal-transaction.use-case.js';
-import { container } from '../../../../src/infrastructure/container.js';
-import { createHandler } from '../../../../src/interface/handler-factory.js';
-import { withAuth } from '../../../../src/interface/middleware/with-auth.js';
-import { withCors } from '../../../../src/interface/middleware/with-cors.js';
-import { Response } from '../../../../src/interface/response.js';
-
-const useCase = new ReportWithdrawalTransactionUseCase(container.withdrawalRepo);
-
-const RouteSchema = ReportWithdrawalTransactionDtoSchema.extend({
- withdrawal_public_id: z.string().min(1),
-});
-
-const handler = createHandler({
- operationName: 'ReportWithdrawalTransaction',
- schema: RouteSchema,
- execute: async (dto, _req, authPayload) => {
- const { withdrawal_public_id, ...reportDto } = dto;
- const result = await useCase.execute(reportDto, authPayload!.userId, withdrawal_public_id);
- return Response.ok(result);
- },
-});
-
-export default withCors(withAuth(handler));
diff --git a/solutions/liquidity-accord/packages/backend/api/v1/webhooks/quicknode.ts b/solutions/liquidity-accord/packages/backend/api/v1/webhooks/quicknode.ts
deleted file mode 100644
index bdfc599..0000000
--- a/solutions/liquidity-accord/packages/backend/api/v1/webhooks/quicknode.ts
+++ /dev/null
@@ -1,49 +0,0 @@
-import type { VercelRequest, VercelResponse } from '@vercel/node';
-import { ProcessEscrowEventUseCase } from '../../../src/application/use-case/webhook/process-escrow-event.use-case.js';
-import type { EscrowEventPayload } from '../../../src/application/use-case/webhook/process-escrow-event.use-case.js';
-import { container } from '../../../src/infrastructure/container.js';
-import { sendResponse } from '../../../src/interface/handler-factory.js';
-import { withCors } from '../../../src/interface/middleware/with-cors.js';
-import { Response } from '../../../src/interface/response.js';
-
-const useCase = new ProcessEscrowEventUseCase(container.escrowRepo, container.escrowEventRepo);
-
-const handler = async (req: VercelRequest, res: VercelResponse): Promise => {
- if (req.method !== 'POST') {
- sendResponse(res, Response.badRequest('Method not allowed'));
- return;
- }
-
- try {
- const signature = req.headers['x-qn-signature'] as string;
- const nonce = req.headers['x-qn-nonce'] as string;
- const timestamp = req.headers['x-qn-timestamp'] as string;
-
- if (!signature || !nonce || !timestamp) {
- sendResponse(res, Response.unauthorized('Missing signature headers'));
- return;
- }
-
- const rawBody = typeof req.body === 'string' ? req.body : JSON.stringify(req.body);
- const verifier = container.getQuickNodeVerifier();
- if (!verifier) {
- sendResponse(res, Response.internalServerError('Webhook secret not configured'));
- return;
- }
-
- if (!verifier.verify(rawBody, signature, nonce, timestamp)) {
- sendResponse(res, Response.unauthorized('Invalid signature'));
- return;
- }
-
- const body = typeof req.body === 'string' ? JSON.parse(req.body) : req.body;
- const events: EscrowEventPayload[] = Array.isArray(body) ? body : (body.events ?? []);
-
- await useCase.execute(events);
- sendResponse(res, Response.ok({ processed: events.length }));
- } catch {
- sendResponse(res, Response.internalServerError());
- }
-};
-
-export default withCors(handler);
diff --git a/solutions/liquidity-accord/packages/backend/api/v1/webhooks/relay-callback.ts b/solutions/liquidity-accord/packages/backend/api/v1/webhooks/relay-callback.ts
deleted file mode 100644
index 00a4e57..0000000
--- a/solutions/liquidity-accord/packages/backend/api/v1/webhooks/relay-callback.ts
+++ /dev/null
@@ -1,25 +0,0 @@
-import type { VercelRequest, VercelResponse } from '@vercel/node';
-import { RelayCallbackUseCase } from '../../../src/application/use-case/webhook/relay-callback.use-case.js';
-import { container } from '../../../src/infrastructure/container.js';
-import { sendResponse } from '../../../src/interface/handler-factory.js';
-import { withCors } from '../../../src/interface/middleware/with-cors.js';
-import { Response } from '../../../src/interface/response.js';
-
-const useCase = new RelayCallbackUseCase(container.withdrawalRepo);
-
-const handler = async (req: VercelRequest, res: VercelResponse): Promise => {
- if (req.method !== 'POST') {
- sendResponse(res, Response.badRequest('Method not allowed'));
- return;
- }
-
- try {
- const body = typeof req.body === 'string' ? JSON.parse(req.body) : req.body;
- await useCase.execute(body);
- sendResponse(res, Response.ok({ received: true }));
- } catch {
- sendResponse(res, Response.internalServerError());
- }
-};
-
-export default withCors(handler);
diff --git a/solutions/liquidity-accord/packages/backend/api/v1/withdrawals/[publicId].ts b/solutions/liquidity-accord/packages/backend/api/v1/withdrawals/[publicId].ts
deleted file mode 100644
index e6650e8..0000000
--- a/solutions/liquidity-accord/packages/backend/api/v1/withdrawals/[publicId].ts
+++ /dev/null
@@ -1,19 +0,0 @@
-import { GetWithdrawalByIdUseCase } from '../../../src/application/use-case/withdrawal/get-withdrawal-by-id.use-case.js';
-import { container } from '../../../src/infrastructure/container.js';
-import { createGetHandler } from '../../../src/interface/handler-factory.js';
-import { withAuth } from '../../../src/interface/middleware/with-auth.js';
-import { withCors } from '../../../src/interface/middleware/with-cors.js';
-import { Response } from '../../../src/interface/response.js';
-
-const useCase = new GetWithdrawalByIdUseCase(container.withdrawalRepo);
-
-const handler = createGetHandler({
- operationName: 'GetWithdrawalById',
- execute: async (req, authPayload) => {
- const publicId = req.query.publicId as string;
- const result = await useCase.execute(publicId, authPayload!.userId);
- return Response.ok(result);
- },
-});
-
-export default withCors(withAuth(handler));
diff --git a/solutions/liquidity-accord/packages/backend/api/v1/withdrawals/[publicId]/bridge-challenge.ts b/solutions/liquidity-accord/packages/backend/api/v1/withdrawals/[publicId]/bridge-challenge.ts
deleted file mode 100644
index 002e55e..0000000
--- a/solutions/liquidity-accord/packages/backend/api/v1/withdrawals/[publicId]/bridge-challenge.ts
+++ /dev/null
@@ -1,19 +0,0 @@
-import { CreateBridgeChallengeUseCase } from '../../../../src/application/use-case/withdrawal/create-bridge-challenge.use-case.js';
-import { container } from '../../../../src/infrastructure/container.js';
-import { createGetHandler } from '../../../../src/interface/handler-factory.js';
-import { withAuth } from '../../../../src/interface/middleware/with-auth.js';
-import { withCors } from '../../../../src/interface/middleware/with-cors.js';
-import { Response } from '../../../../src/interface/response.js';
-
-const useCase = new CreateBridgeChallengeUseCase(container.withdrawalRepo);
-
-const handler = createGetHandler({
- operationName: 'CreateBridgeChallenge',
- execute: async (req, authPayload) => {
- const publicId = req.query.publicId as string;
- const result = await useCase.execute(publicId, authPayload!.userId);
- return Response.ok(result);
- },
-});
-
-export default withCors(withAuth(handler));
diff --git a/solutions/liquidity-accord/packages/backend/api/v1/withdrawals/[publicId]/bridge-readiness.ts b/solutions/liquidity-accord/packages/backend/api/v1/withdrawals/[publicId]/bridge-readiness.ts
deleted file mode 100644
index d577ab0..0000000
--- a/solutions/liquidity-accord/packages/backend/api/v1/withdrawals/[publicId]/bridge-readiness.ts
+++ /dev/null
@@ -1,19 +0,0 @@
-import { CheckBridgeReadinessUseCase } from '../../../../src/application/use-case/withdrawal/check-bridge-readiness.use-case.js';
-import { container } from '../../../../src/infrastructure/container.js';
-import { createGetHandler } from '../../../../src/interface/handler-factory.js';
-import { withAuth } from '../../../../src/interface/middleware/with-auth.js';
-import { withCors } from '../../../../src/interface/middleware/with-cors.js';
-import { Response } from '../../../../src/interface/response.js';
-
-const useCase = new CheckBridgeReadinessUseCase(container.withdrawalRepo);
-
-const handler = createGetHandler({
- operationName: 'CheckBridgeReadiness',
- execute: async (req, authPayload) => {
- const publicId = req.query.publicId as string;
- const result = await useCase.execute(publicId, authPayload!.userId);
- return Response.ok(result);
- },
-});
-
-export default withCors(withAuth(handler));
diff --git a/solutions/liquidity-accord/packages/backend/api/v1/withdrawals/index.ts b/solutions/liquidity-accord/packages/backend/api/v1/withdrawals/index.ts
deleted file mode 100644
index ea745de..0000000
--- a/solutions/liquidity-accord/packages/backend/api/v1/withdrawals/index.ts
+++ /dev/null
@@ -1,48 +0,0 @@
-import type { VercelRequest, VercelResponse } from '@vercel/node';
-import { CreateWithdrawalDtoSchema } from '../../../src/application/dto/withdrawal/create-withdrawal.dto.js';
-import { CreateWithdrawalUseCase } from '../../../src/application/use-case/withdrawal/create-withdrawal.use-case.js';
-import { GetWithdrawalsUseCase } from '../../../src/application/use-case/withdrawal/get-withdrawals.use-case.js';
-import { container } from '../../../src/infrastructure/container.js';
-import { createHandler, createGetHandler, sendResponse } from '../../../src/interface/handler-factory.js';
-import { withAuth } from '../../../src/interface/middleware/with-auth.js';
-import { withCors } from '../../../src/interface/middleware/with-cors.js';
-import { Response } from '../../../src/interface/response.js';
-
-const createWithdrawalUseCase = new CreateWithdrawalUseCase(container.escrowRepo, container.withdrawalRepo);
-const getWithdrawalsUseCase = new GetWithdrawalsUseCase(container.withdrawalRepo);
-
-const postHandler = createHandler({
- operationName: 'CreateWithdrawal',
- schema: CreateWithdrawalDtoSchema,
- execute: async (dto, _req, authPayload) => {
- const result = await createWithdrawalUseCase.execute(dto, authPayload!.userId, authPayload!.walletAddress);
- return Response.created(result);
- },
-});
-
-const getHandler = createGetHandler({
- operationName: 'GetWithdrawals',
- execute: async (req, authPayload) => {
- const limit = req.query.limit ? Number(req.query.limit) : undefined;
- const cursor = req.query.continuation_token as string | undefined;
- const status = req.query.status as string | undefined;
- const result = await getWithdrawalsUseCase.execute(authPayload!.userId, {
- limit,
- cursor,
- status,
- });
- return Response.ok(result);
- },
-});
-
-const handler = async (req: VercelRequest, res: VercelResponse): Promise => {
- if (req.method === 'POST') {
- return postHandler(req, res);
- }
- if (req.method === 'GET') {
- return getHandler(req, res);
- }
- sendResponse(res, Response.badRequest('Method not allowed'));
-};
-
-export default withCors(withAuth(handler));
diff --git a/solutions/liquidity-accord/packages/backend/public/index.html b/solutions/liquidity-accord/packages/backend/public/index.html
new file mode 100644
index 0000000..0e68a24
--- /dev/null
+++ b/solutions/liquidity-accord/packages/backend/public/index.html
@@ -0,0 +1,17 @@
+
+
+
+
+ Liquidity Accord — Backend
+
+
+
+
+
+ Liquidity Accord — Backend API
+ This is a serverless API. See /api/v1/docs/openapi.json for the OpenAPI spec.
+
+
diff --git a/solutions/liquidity-accord/packages/backend/src/application/dto/auth/request-nonce.dto.ts b/solutions/liquidity-accord/packages/backend/src/application/dto/auth/request-nonce.dto.ts
index 0b9e26a..4dd5fce 100644
--- a/solutions/liquidity-accord/packages/backend/src/application/dto/auth/request-nonce.dto.ts
+++ b/solutions/liquidity-accord/packages/backend/src/application/dto/auth/request-nonce.dto.ts
@@ -1,5 +1,5 @@
import { z } from 'zod';
-import { ethAddressSchema } from '@core/validator';
+import { ethAddressSchema } from '../../../core/validator.js';
export const RequestNonceDtoSchema = z.object({
wallet_address: ethAddressSchema,
diff --git a/solutions/liquidity-accord/packages/backend/src/infrastructure/container.ts b/solutions/liquidity-accord/packages/backend/src/infrastructure/container.ts
index 953dab6..aa0801e 100644
--- a/solutions/liquidity-accord/packages/backend/src/infrastructure/container.ts
+++ b/solutions/liquidity-accord/packages/backend/src/infrastructure/container.ts
@@ -11,6 +11,11 @@ import {
MemoryAttestationRepository,
MemoryMMProfileRepository,
} from './repository/memory/index.js';
+import {
+ PostgresNonceRepository,
+ PostgresUserRepository,
+ PostgresSessionRepository,
+} from './repository/postgres/index.js';
import { JwtService } from './auth/jwt.service.js';
import { NonceService } from './auth/nonce.service.js';
import { SiweVerifier } from './auth/siwe-verifier.js';
@@ -18,9 +23,13 @@ import { FheService } from './fhe/fhe.service.js';
import { QuickNodeVerifier } from './webhook/quicknode-verifier.js';
import { getEnv } from '../core/config.js';
-const nonceRepo = new MemoryNonceRepository();
-const userRepo = new MemoryUserRepository();
-const sessionRepo = new MemorySessionRepository();
+const usePostgres = getEnv().DB_PROVIDER === 'postgres';
+
+const nonceRepo = usePostgres ? new PostgresNonceRepository() : new MemoryNonceRepository();
+const userRepo = usePostgres ? new PostgresUserRepository() : new MemoryUserRepository();
+const sessionRepo = usePostgres
+ ? new PostgresSessionRepository()
+ : new MemorySessionRepository();
const escrowRepo = new MemoryEscrowRepository();
const withdrawalRepo = new MemoryWithdrawalRepository();
const businessProfileRepo = new MemoryBusinessProfileRepository();
diff --git a/solutions/liquidity-accord/packages/backend/src/infrastructure/repository/postgres/db.ts b/solutions/liquidity-accord/packages/backend/src/infrastructure/repository/postgres/db.ts
new file mode 100644
index 0000000..8bb91ed
--- /dev/null
+++ b/solutions/liquidity-accord/packages/backend/src/infrastructure/repository/postgres/db.ts
@@ -0,0 +1,17 @@
+import { neon } from '@neondatabase/serverless';
+import { drizzle } from 'drizzle-orm/neon-http';
+import * as schema from './schema.js';
+
+let _db: ReturnType> | null = null;
+
+export function getDb() {
+ if (!_db) {
+ const url = process.env.DATABASE_URL;
+ if (!url) {
+ throw new Error('DATABASE_URL is required when DB_PROVIDER=postgres');
+ }
+ const client = neon(url);
+ _db = drizzle(client, { schema });
+ }
+ return _db;
+}
diff --git a/solutions/liquidity-accord/packages/backend/src/infrastructure/repository/postgres/index.ts b/solutions/liquidity-accord/packages/backend/src/infrastructure/repository/postgres/index.ts
new file mode 100644
index 0000000..541309c
--- /dev/null
+++ b/solutions/liquidity-accord/packages/backend/src/infrastructure/repository/postgres/index.ts
@@ -0,0 +1,3 @@
+export { PostgresNonceRepository } from './postgres-nonce.repository.js';
+export { PostgresUserRepository } from './postgres-user.repository.js';
+export { PostgresSessionRepository } from './postgres-session.repository.js';
diff --git a/solutions/liquidity-accord/packages/backend/src/infrastructure/repository/postgres/postgres-nonce.repository.ts b/solutions/liquidity-accord/packages/backend/src/infrastructure/repository/postgres/postgres-nonce.repository.ts
new file mode 100644
index 0000000..b7d89d1
--- /dev/null
+++ b/solutions/liquidity-accord/packages/backend/src/infrastructure/repository/postgres/postgres-nonce.repository.ts
@@ -0,0 +1,31 @@
+import { and, eq, gt } from 'drizzle-orm';
+import type { INonceRepository } from '../../../domain/nonce/repository/nonce.repository.js';
+import { getDb } from './db.js';
+import { nonces } from './schema.js';
+
+export class PostgresNonceRepository implements INonceRepository {
+ async save(walletAddress: string, nonce: string, ttlSeconds: number): Promise {
+ const expiresAt = new Date(Date.now() + ttlSeconds * 1000);
+ await getDb()
+ .insert(nonces)
+ .values({ walletAddress, nonce, expiresAt })
+ .onConflictDoUpdate({
+ target: [nonces.walletAddress, nonces.nonce],
+ set: { expiresAt },
+ });
+ }
+
+ async findAndDelete(walletAddress: string, nonce: string): Promise {
+ const deleted = await getDb()
+ .delete(nonces)
+ .where(
+ and(
+ eq(nonces.walletAddress, walletAddress),
+ eq(nonces.nonce, nonce),
+ gt(nonces.expiresAt, new Date()),
+ ),
+ )
+ .returning({ nonce: nonces.nonce });
+ return deleted.length > 0;
+ }
+}
diff --git a/solutions/liquidity-accord/packages/backend/src/infrastructure/repository/postgres/postgres-session.repository.ts b/solutions/liquidity-accord/packages/backend/src/infrastructure/repository/postgres/postgres-session.repository.ts
new file mode 100644
index 0000000..115e956
--- /dev/null
+++ b/solutions/liquidity-accord/packages/backend/src/infrastructure/repository/postgres/postgres-session.repository.ts
@@ -0,0 +1,71 @@
+import { eq } from 'drizzle-orm';
+import type { ISessionRepository } from '../../../domain/auth/repository/session.repository.js';
+import { Session } from '../../../domain/auth/model/session.js';
+import { getDb } from './db.js';
+import { sessions } from './schema.js';
+
+type SessionRow = typeof sessions.$inferSelect;
+
+function toModel(row: SessionRow): Session {
+ return new Session({
+ id: row.id,
+ userId: row.userId,
+ refreshToken: row.refreshToken,
+ expiresAt: row.expiresAt,
+ createdAt: row.createdAt,
+ userAgent: row.userAgent ?? undefined,
+ ipAddress: row.ipAddress ?? undefined,
+ });
+}
+
+export class PostgresSessionRepository implements ISessionRepository {
+ async findById(id: string): Promise {
+ const rows = await getDb().select().from(sessions).where(eq(sessions.id, id)).limit(1);
+ return rows[0] ? toModel(rows[0]) : null;
+ }
+
+ async findByRefreshToken(token: string): Promise {
+ const rows = await getDb()
+ .select()
+ .from(sessions)
+ .where(eq(sessions.refreshToken, token))
+ .limit(1);
+ return rows[0] ? toModel(rows[0]) : null;
+ }
+
+ async findByUserId(userId: string): Promise {
+ const rows = await getDb().select().from(sessions).where(eq(sessions.userId, userId));
+ return rows.map(toModel);
+ }
+
+ async save(session: Session): Promise {
+ await getDb()
+ .insert(sessions)
+ .values({
+ id: session.id,
+ userId: session.userId,
+ refreshToken: session.refreshToken,
+ expiresAt: session.expiresAt,
+ createdAt: session.createdAt,
+ userAgent: session.userAgent ?? null,
+ ipAddress: session.ipAddress ?? null,
+ })
+ .onConflictDoUpdate({
+ target: sessions.id,
+ set: {
+ refreshToken: session.refreshToken,
+ expiresAt: session.expiresAt,
+ userAgent: session.userAgent ?? null,
+ ipAddress: session.ipAddress ?? null,
+ },
+ });
+ }
+
+ async delete(id: string): Promise {
+ await getDb().delete(sessions).where(eq(sessions.id, id));
+ }
+
+ async deleteByUserId(userId: string): Promise {
+ await getDb().delete(sessions).where(eq(sessions.userId, userId));
+ }
+}
diff --git a/solutions/liquidity-accord/packages/backend/src/infrastructure/repository/postgres/postgres-user.repository.ts b/solutions/liquidity-accord/packages/backend/src/infrastructure/repository/postgres/postgres-user.repository.ts
new file mode 100644
index 0000000..57e31e6
--- /dev/null
+++ b/solutions/liquidity-accord/packages/backend/src/infrastructure/repository/postgres/postgres-user.repository.ts
@@ -0,0 +1,53 @@
+import { eq } from 'drizzle-orm';
+import type { IUserRepository } from '../../../domain/auth/repository/user.repository.js';
+import { User, type WalletProvider } from '../../../domain/auth/model/user.js';
+import { getDb } from './db.js';
+import { users } from './schema.js';
+
+type UserRow = typeof users.$inferSelect;
+
+function toModel(row: UserRow): User {
+ return new User({
+ id: row.id,
+ walletAddress: row.walletAddress,
+ walletProvider: row.walletProvider as WalletProvider,
+ email: row.email ?? undefined,
+ createdAt: row.createdAt,
+ });
+}
+
+export class PostgresUserRepository implements IUserRepository {
+ async findById(id: string): Promise {
+ const rows = await getDb().select().from(users).where(eq(users.id, id)).limit(1);
+ return rows[0] ? toModel(rows[0]) : null;
+ }
+
+ async findByWalletAddress(address: string): Promise {
+ const rows = await getDb()
+ .select()
+ .from(users)
+ .where(eq(users.walletAddress, address))
+ .limit(1);
+ return rows[0] ? toModel(rows[0]) : null;
+ }
+
+ async save(user: User): Promise {
+ await getDb()
+ .insert(users)
+ .values({
+ id: user.id,
+ walletAddress: user.walletAddress,
+ walletProvider: user.walletProvider,
+ email: user.email ?? null,
+ createdAt: user.createdAt,
+ })
+ .onConflictDoUpdate({
+ target: users.id,
+ set: {
+ walletAddress: user.walletAddress,
+ walletProvider: user.walletProvider,
+ email: user.email ?? null,
+ },
+ });
+ }
+}
diff --git a/solutions/liquidity-accord/packages/backend/src/infrastructure/repository/postgres/schema.ts b/solutions/liquidity-accord/packages/backend/src/infrastructure/repository/postgres/schema.ts
new file mode 100644
index 0000000..dcfaa82
--- /dev/null
+++ b/solutions/liquidity-accord/packages/backend/src/infrastructure/repository/postgres/schema.ts
@@ -0,0 +1,31 @@
+import { pgTable, text, timestamp, primaryKey } from 'drizzle-orm/pg-core';
+
+export const nonces = pgTable(
+ 'nonces',
+ {
+ walletAddress: text('wallet_address').notNull(),
+ nonce: text('nonce').notNull(),
+ expiresAt: timestamp('expires_at', { withTimezone: true }).notNull(),
+ },
+ (t) => [primaryKey({ columns: [t.walletAddress, t.nonce] })],
+);
+
+export const users = pgTable('users', {
+ id: text('id').primaryKey(),
+ walletAddress: text('wallet_address').notNull().unique(),
+ walletProvider: text('wallet_provider').notNull(),
+ email: text('email'),
+ createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(),
+});
+
+export const sessions = pgTable('sessions', {
+ id: text('id').primaryKey(),
+ userId: text('user_id')
+ .notNull()
+ .references(() => users.id, { onDelete: 'cascade' }),
+ refreshToken: text('refresh_token').notNull().unique(),
+ expiresAt: timestamp('expires_at', { withTimezone: true }).notNull(),
+ createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(),
+ userAgent: text('user_agent'),
+ ipAddress: text('ip_address'),
+});
diff --git a/solutions/liquidity-accord/packages/backend/src/interface/middleware/with-cors.ts b/solutions/liquidity-accord/packages/backend/src/interface/middleware/with-cors.ts
index f806d98..f3d1730 100644
--- a/solutions/liquidity-accord/packages/backend/src/interface/middleware/with-cors.ts
+++ b/solutions/liquidity-accord/packages/backend/src/interface/middleware/with-cors.ts
@@ -9,26 +9,34 @@ function resolveOrigin(requestOrigin: string | undefined): string {
try {
allowedRaw = getEnv().ALLOWED_ORIGINS;
} catch {
- return '*';
+ return requestOrigin ?? '*';
}
if (allowedRaw === '*') {
- return '*';
+ return requestOrigin ?? '*';
}
- const allowed = allowedRaw.split(',').map((o) => o.trim());
+ const allowed = allowedRaw
+ .split(/[,\s]+/)
+ .map((o) => o.trim())
+ .filter((o) => o.length > 0);
if (requestOrigin && allowed.includes(requestOrigin)) {
return requestOrigin;
}
- return allowed[0] ?? '*';
+ return allowed[0] ?? requestOrigin ?? '*';
}
function setCorsHeaders(res: VercelResponse, origin: string): void {
res.setHeader('Access-Control-Allow-Origin', origin);
+ res.setHeader('Vary', 'Origin');
+ res.setHeader('Access-Control-Allow-Credentials', 'true');
res.setHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS');
- res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization');
+ res.setHeader(
+ 'Access-Control-Allow-Headers',
+ 'Content-Type, Authorization, X-Idempotency-Key, X-Wallet-Provider',
+ );
res.setHeader('Access-Control-Max-Age', '86400');
}
diff --git a/solutions/liquidity-accord/packages/backend/vercel.json b/solutions/liquidity-accord/packages/backend/vercel.json
index 350643d..92a4cdf 100644
--- a/solutions/liquidity-accord/packages/backend/vercel.json
+++ b/solutions/liquidity-accord/packages/backend/vercel.json
@@ -1,19 +1,6 @@
{
"installCommand": "cd ../.. && pnpm install --frozen-lockfile",
"buildCommand": "pnpm run build",
- "rewrites": [{ "source": "/api/(.*)", "destination": "/api/$1" }],
- "headers": [
- {
- "source": "/api/(.*)",
- "headers": [
- { "key": "Access-Control-Allow-Credentials", "value": "true" },
- { "key": "Access-Control-Allow-Origin", "value": "*" },
- { "key": "Access-Control-Allow-Methods", "value": "GET,POST,PUT,DELETE,OPTIONS" },
- {
- "key": "Access-Control-Allow-Headers",
- "value": "Content-Type,Authorization,X-Idempotency-Key,X-Wallet-Provider"
- }
- ]
- }
- ]
+ "outputDirectory": "public",
+ "rewrites": [{ "source": "/api/(.*)", "destination": "/api/$1" }]
}
diff --git a/solutions/liquidity-accord/packages/landing/.gitignore b/solutions/liquidity-accord/packages/landing/.gitignore
index 3546ba7..6af91c3 100644
--- a/solutions/liquidity-accord/packages/landing/.gitignore
+++ b/solutions/liquidity-accord/packages/landing/.gitignore
@@ -37,3 +37,4 @@ coverage
# Performance testing
.lighthouseci
+.vercel
diff --git a/solutions/liquidity-accord/packages/landing/next-env.d.ts b/solutions/liquidity-accord/packages/landing/next-env.d.ts
index c4b7818..9edff1c 100644
--- a/solutions/liquidity-accord/packages/landing/next-env.d.ts
+++ b/solutions/liquidity-accord/packages/landing/next-env.d.ts
@@ -1,6 +1,6 @@
///
///
-import "./.next/dev/types/routes.d.ts";
+import "./.next/types/routes.d.ts";
// NOTE: This file should not be edited
// see https://nextjs.org/docs/app/api-reference/config/typescript for more information.
diff --git a/solutions/liquidity-accord/packages/landing/next.config.ts b/solutions/liquidity-accord/packages/landing/next.config.ts
index 62725e2..1598177 100644
--- a/solutions/liquidity-accord/packages/landing/next.config.ts
+++ b/solutions/liquidity-accord/packages/landing/next.config.ts
@@ -1,10 +1,17 @@
import type { NextConfig } from 'next'
+import { fileURLToPath } from 'node:url'
+import { dirname } from 'node:path'
+
+const projectRoot = dirname(fileURLToPath(import.meta.url))
const nextConfig: NextConfig = {
output: 'export',
images: { unoptimized: true },
trailingSlash: true,
poweredByHeader: false,
+ turbopack: {
+ root: projectRoot,
+ },
}
export default nextConfig
diff --git a/solutions/liquidity-accord/packages/landing/vercel.json b/solutions/liquidity-accord/packages/landing/vercel.json
new file mode 100644
index 0000000..00612a9
--- /dev/null
+++ b/solutions/liquidity-accord/packages/landing/vercel.json
@@ -0,0 +1,4 @@
+{
+ "buildCommand": "pnpm run build",
+ "outputDirectory": "out"
+}
| | |