fix(rexchange): add demo seeding and server-rendered order book page
Replace missing folk-exchange-app.js with server-rendered HTML order book. Seed 8 demo intents, 2 trades, and 5 reputation records on startup. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
bf8e11d426
commit
efb7ee5600
|
|
@ -12,6 +12,7 @@
|
|||
*/
|
||||
|
||||
import { Hono } from 'hono';
|
||||
import * as Automerge from '@automerge/automerge';
|
||||
import { renderShell } from '../../server/shell';
|
||||
import { getModuleInfoList } from '../../shared/module';
|
||||
import type { RSpaceModule } from '../../shared/module';
|
||||
|
|
@ -19,8 +20,13 @@ import type { SyncServer } from '../../server/local-first/sync-server';
|
|||
import { renderLanding } from './landing';
|
||||
import {
|
||||
exchangeIntentsSchema, exchangeTradesSchema, exchangeReputationSchema,
|
||||
exchangeIntentsDocId, exchangeTradesDocId, exchangeReputationDocId,
|
||||
} from './schemas';
|
||||
import { createExchangeRoutes, startSolverCron, stopSolverCron } from './exchange-routes';
|
||||
import type {
|
||||
ExchangeIntentsDoc, ExchangeTradesDoc, ExchangeReputationDoc,
|
||||
ExchangeIntent, ExchangeTrade, ExchangeReputationRecord,
|
||||
} from './schemas';
|
||||
import { createExchangeRoutes, startSolverCron } from './exchange-routes';
|
||||
|
||||
const routes = new Hono();
|
||||
|
||||
|
|
@ -31,6 +37,345 @@ let _syncServer: SyncServer | null = null;
|
|||
const exchangeRoutes = createExchangeRoutes(() => _syncServer);
|
||||
routes.route('/', exchangeRoutes);
|
||||
|
||||
// ── Automerge helpers ──
|
||||
|
||||
function ensureIntentsDoc(space: string): ExchangeIntentsDoc {
|
||||
const docId = exchangeIntentsDocId(space);
|
||||
let doc = _syncServer!.getDoc<ExchangeIntentsDoc>(docId);
|
||||
if (!doc) {
|
||||
doc = Automerge.change(Automerge.init<ExchangeIntentsDoc>(), 'init exchange intents', (d) => {
|
||||
const init = exchangeIntentsSchema.init();
|
||||
Object.assign(d, init);
|
||||
d.meta.spaceSlug = space;
|
||||
});
|
||||
_syncServer!.setDoc(docId, doc);
|
||||
}
|
||||
return doc;
|
||||
}
|
||||
|
||||
function ensureTradesDoc(space: string): ExchangeTradesDoc {
|
||||
const docId = exchangeTradesDocId(space);
|
||||
let doc = _syncServer!.getDoc<ExchangeTradesDoc>(docId);
|
||||
if (!doc) {
|
||||
doc = Automerge.change(Automerge.init<ExchangeTradesDoc>(), 'init exchange trades', (d) => {
|
||||
const init = exchangeTradesSchema.init();
|
||||
Object.assign(d, init);
|
||||
d.meta.spaceSlug = space;
|
||||
});
|
||||
_syncServer!.setDoc(docId, doc);
|
||||
}
|
||||
return doc;
|
||||
}
|
||||
|
||||
function ensureReputationDoc(space: string): ExchangeReputationDoc {
|
||||
const docId = exchangeReputationDocId(space);
|
||||
let doc = _syncServer!.getDoc<ExchangeReputationDoc>(docId);
|
||||
if (!doc) {
|
||||
doc = Automerge.change(Automerge.init<ExchangeReputationDoc>(), 'init exchange reputation', (d) => {
|
||||
const init = exchangeReputationSchema.init();
|
||||
Object.assign(d, init);
|
||||
d.meta.spaceSlug = space;
|
||||
});
|
||||
_syncServer!.setDoc(docId, doc);
|
||||
}
|
||||
return doc;
|
||||
}
|
||||
|
||||
// ── Demo seeding ──
|
||||
|
||||
const DEMO_DIDS = {
|
||||
alice: 'did:key:alice-contributor-rspace-2026',
|
||||
bob: 'did:key:bob-auditor-rspace-2026',
|
||||
carol: 'did:key:carol-designer-rspace-2026',
|
||||
maya: 'did:key:maya-facilitator-rspace-2026',
|
||||
jordan: 'did:key:jordan-designer-rspace-2026',
|
||||
};
|
||||
|
||||
const DEMO_INTENTS: Omit<ExchangeIntent, 'id' | 'createdAt'>[] = [
|
||||
// Buys (want crypto, have fiat)
|
||||
{
|
||||
creatorDid: DEMO_DIDS.alice, creatorName: 'Alice',
|
||||
side: 'buy', tokenId: 'cusdc', fiatCurrency: 'EUR',
|
||||
tokenAmountMin: 50_000_000, tokenAmountMax: 200_000_000,
|
||||
rateType: 'market_plus_bps', rateMarketBps: 50,
|
||||
paymentMethods: ['SEPA', 'Revolut'], isStandingOrder: false,
|
||||
autoAccept: false, allowInstitutionalFallback: true, status: 'active',
|
||||
},
|
||||
{
|
||||
creatorDid: DEMO_DIDS.maya, creatorName: 'Maya',
|
||||
side: 'buy', tokenId: 'cusdc', fiatCurrency: 'USD',
|
||||
tokenAmountMin: 100_000_000, tokenAmountMax: 500_000_000,
|
||||
rateType: 'fixed', rateFixed: 1.00,
|
||||
paymentMethods: ['Revolut', 'Cash'], isStandingOrder: true,
|
||||
autoAccept: true, allowInstitutionalFallback: false, status: 'active',
|
||||
},
|
||||
{
|
||||
creatorDid: DEMO_DIDS.jordan, creatorName: 'Jordan',
|
||||
side: 'buy', tokenId: 'myco', fiatCurrency: 'BRL',
|
||||
tokenAmountMin: 1_000_000_000, tokenAmountMax: 5_000_000_000,
|
||||
rateType: 'market_plus_bps', rateMarketBps: 100,
|
||||
paymentMethods: ['PIX'], isStandingOrder: false,
|
||||
autoAccept: false, allowInstitutionalFallback: true, status: 'active',
|
||||
},
|
||||
{
|
||||
creatorDid: DEMO_DIDS.carol, creatorName: 'Carol',
|
||||
side: 'buy', tokenId: 'cusdc', fiatCurrency: 'GBP',
|
||||
tokenAmountMin: 25_000_000, tokenAmountMax: 100_000_000,
|
||||
rateType: 'fixed', rateFixed: 0.79,
|
||||
paymentMethods: ['SEPA', 'Revolut'], isStandingOrder: false,
|
||||
autoAccept: false, allowInstitutionalFallback: false, status: 'active',
|
||||
},
|
||||
|
||||
// Sells (have crypto, want fiat)
|
||||
{
|
||||
creatorDid: DEMO_DIDS.bob, creatorName: 'Bob',
|
||||
side: 'sell', tokenId: 'cusdc', fiatCurrency: 'EUR',
|
||||
tokenAmountMin: 100_000_000, tokenAmountMax: 300_000_000,
|
||||
rateType: 'market_plus_bps', rateMarketBps: 30,
|
||||
paymentMethods: ['SEPA'], isStandingOrder: true,
|
||||
autoAccept: true, allowInstitutionalFallback: false, status: 'active',
|
||||
},
|
||||
{
|
||||
creatorDid: DEMO_DIDS.carol, creatorName: 'Carol',
|
||||
side: 'sell', tokenId: 'cusdc', fiatCurrency: 'USD',
|
||||
tokenAmountMin: 50_000_000, tokenAmountMax: 250_000_000,
|
||||
rateType: 'fixed', rateFixed: 1.01,
|
||||
paymentMethods: ['Revolut', 'Cash'], isStandingOrder: false,
|
||||
autoAccept: false, allowInstitutionalFallback: false, status: 'active',
|
||||
},
|
||||
{
|
||||
creatorDid: DEMO_DIDS.alice, creatorName: 'Alice',
|
||||
side: 'sell', tokenId: 'myco', fiatCurrency: 'EUR',
|
||||
tokenAmountMin: 500_000_000, tokenAmountMax: 2_000_000_000,
|
||||
rateType: 'market_plus_bps', rateMarketBps: 75,
|
||||
paymentMethods: ['SEPA', 'Revolut'], isStandingOrder: false,
|
||||
autoAccept: false, allowInstitutionalFallback: true, status: 'active',
|
||||
},
|
||||
{
|
||||
creatorDid: DEMO_DIDS.maya, creatorName: 'Maya',
|
||||
side: 'sell', tokenId: 'cusdc', fiatCurrency: 'GBP',
|
||||
tokenAmountMin: 30_000_000, tokenAmountMax: 150_000_000,
|
||||
rateType: 'fixed', rateFixed: 0.80,
|
||||
paymentMethods: ['SEPA', 'Revolut'], isStandingOrder: true,
|
||||
autoAccept: true, allowInstitutionalFallback: false, status: 'active',
|
||||
},
|
||||
];
|
||||
|
||||
const DEMO_REPUTATION: ExchangeReputationRecord[] = [
|
||||
{ did: DEMO_DIDS.alice, tradesCompleted: 12, tradesCancelled: 1, disputesRaised: 0, disputesLost: 0, totalVolumeBase: 2_500_000_000, avgConfirmTimeMs: 1800_000, score: 88, badges: ['verified_seller'] },
|
||||
{ did: DEMO_DIDS.bob, tradesCompleted: 27, tradesCancelled: 0, disputesRaised: 1, disputesLost: 0, totalVolumeBase: 8_000_000_000, avgConfirmTimeMs: 900_000, score: 95, badges: ['verified_seller'] },
|
||||
{ did: DEMO_DIDS.carol, tradesCompleted: 6, tradesCancelled: 2, disputesRaised: 1, disputesLost: 1, totalVolumeBase: 1_200_000_000, avgConfirmTimeMs: 3600_000, score: 65, badges: [] },
|
||||
{ did: DEMO_DIDS.maya, tradesCompleted: 45, tradesCancelled: 1, disputesRaised: 0, disputesLost: 0, totalVolumeBase: 15_000_000_000, avgConfirmTimeMs: 600_000, score: 97, badges: ['verified_seller', 'liquidity_provider', 'top_trader'] },
|
||||
{ did: DEMO_DIDS.jordan, tradesCompleted: 3, tradesCancelled: 0, disputesRaised: 0, disputesLost: 0, totalVolumeBase: 500_000_000, avgConfirmTimeMs: 2400_000, score: 72, badges: [] },
|
||||
];
|
||||
|
||||
const DEMO_TRADES: Omit<ExchangeTrade, 'id' | 'createdAt'>[] = [
|
||||
{
|
||||
buyIntentId: '', sellIntentId: '',
|
||||
buyerDid: DEMO_DIDS.alice, buyerName: 'Alice',
|
||||
sellerDid: DEMO_DIDS.bob, sellerName: 'Bob',
|
||||
tokenId: 'cusdc', tokenAmount: 150_000_000, fiatCurrency: 'EUR',
|
||||
fiatAmount: 138.75, agreedRate: 0.925, paymentMethod: 'SEPA',
|
||||
status: 'completed', acceptances: { [DEMO_DIDS.alice]: true, [DEMO_DIDS.bob]: true },
|
||||
chatMessages: [
|
||||
{ id: 'msg-1', senderDid: DEMO_DIDS.alice, senderName: 'Alice', text: 'SEPA sent, ref: ALICE-BOB-001', timestamp: Date.now() - 86400_000 * 3 },
|
||||
{ id: 'msg-2', senderDid: DEMO_DIDS.bob, senderName: 'Bob', text: 'Received, releasing escrow', timestamp: Date.now() - 86400_000 * 3 + 1800_000 },
|
||||
],
|
||||
completedAt: Date.now() - 86400_000 * 3,
|
||||
},
|
||||
{
|
||||
buyIntentId: '', sellIntentId: '',
|
||||
buyerDid: DEMO_DIDS.jordan, buyerName: 'Jordan',
|
||||
sellerDid: DEMO_DIDS.carol, sellerName: 'Carol',
|
||||
tokenId: 'cusdc', tokenAmount: 75_000_000, fiatCurrency: 'USD',
|
||||
fiatAmount: 75.50, agreedRate: 1.007, paymentMethod: 'Revolut',
|
||||
status: 'escrow_locked', acceptances: { [DEMO_DIDS.jordan]: true, [DEMO_DIDS.carol]: true },
|
||||
chatMessages: [],
|
||||
fiatConfirmDeadline: Date.now() + 86400_000,
|
||||
},
|
||||
];
|
||||
|
||||
function seedDemoIfEmpty(space: string = 'demo') {
|
||||
if (!_syncServer) return;
|
||||
const existing = _syncServer.getDoc<ExchangeIntentsDoc>(exchangeIntentsDocId(space));
|
||||
if (existing && Object.keys(existing.intents).length > 0) return;
|
||||
|
||||
const now = Date.now();
|
||||
|
||||
// Seed intents
|
||||
ensureIntentsDoc(space);
|
||||
_syncServer.changeDoc<ExchangeIntentsDoc>(exchangeIntentsDocId(space), 'seed exchange intents', (d) => {
|
||||
for (const intent of DEMO_INTENTS) {
|
||||
const id = crypto.randomUUID();
|
||||
d.intents[id] = { id, ...intent, createdAt: now } as any;
|
||||
}
|
||||
});
|
||||
|
||||
// Seed trades
|
||||
ensureTradesDoc(space);
|
||||
_syncServer.changeDoc<ExchangeTradesDoc>(exchangeTradesDocId(space), 'seed exchange trades', (d) => {
|
||||
for (const trade of DEMO_TRADES) {
|
||||
const id = crypto.randomUUID();
|
||||
d.trades[id] = { id, ...trade, createdAt: now - 86400_000 * 5 } as any;
|
||||
}
|
||||
});
|
||||
|
||||
// Seed reputation
|
||||
ensureReputationDoc(space);
|
||||
_syncServer.changeDoc<ExchangeReputationDoc>(exchangeReputationDocId(space), 'seed exchange reputation', (d) => {
|
||||
for (const rec of DEMO_REPUTATION) {
|
||||
d.records[rec.did] = rec as any;
|
||||
}
|
||||
});
|
||||
|
||||
console.log(`[rExchange] Demo data seeded for "${space}": ${DEMO_INTENTS.length} intents, ${DEMO_TRADES.length} trades, ${DEMO_REPUTATION.length} reputation records`);
|
||||
}
|
||||
|
||||
// ── Server-rendered order book page ──
|
||||
|
||||
function renderOrderBook(space: string): string {
|
||||
const intentsDoc = _syncServer?.getDoc<ExchangeIntentsDoc>(exchangeIntentsDocId(space));
|
||||
const tradesDoc = _syncServer?.getDoc<ExchangeTradesDoc>(exchangeTradesDocId(space));
|
||||
const repDoc = _syncServer?.getDoc<ExchangeReputationDoc>(exchangeReputationDocId(space));
|
||||
|
||||
const intents = intentsDoc ? Object.values(intentsDoc.intents) : [];
|
||||
const trades = tradesDoc ? Object.values(tradesDoc.trades) : [];
|
||||
|
||||
const buyIntents = intents.filter(i => i.status === 'active' && i.side === 'buy');
|
||||
const sellIntents = intents.filter(i => i.status === 'active' && i.side === 'sell');
|
||||
const activeTrades = trades.filter(t => !['completed', 'cancelled', 'timed_out', 'resolved'].includes(t.status));
|
||||
const completedTrades = trades.filter(t => t.status === 'completed');
|
||||
|
||||
function fmtAmount(base: number) {
|
||||
return (base / 1_000_000).toFixed(2);
|
||||
}
|
||||
|
||||
function rateStr(i: { rateType: string; rateFixed?: number; rateMarketBps?: number; fiatCurrency: string }) {
|
||||
if (i.rateType === 'fixed') return `${i.rateFixed} ${i.fiatCurrency}`;
|
||||
return `mkt+${i.rateMarketBps}bps`;
|
||||
}
|
||||
|
||||
function statusBadge(s: string) {
|
||||
const colors: Record<string, string> = {
|
||||
proposed: '#f59e0b', accepted: '#3b82f6', escrow_locked: '#8b5cf6',
|
||||
fiat_sent: '#f97316', fiat_confirmed: '#10b981', completed: '#22c55e',
|
||||
disputed: '#ef4444', resolved: '#6b7280', cancelled: '#6b7280', timed_out: '#6b7280',
|
||||
};
|
||||
const c = colors[s] || '#6b7280';
|
||||
return `<span style="background:${c}22;color:${c};padding:2px 8px;border-radius:9999px;font-size:0.7rem;font-weight:600">${s.replace(/_/g, ' ')}</span>`;
|
||||
}
|
||||
|
||||
function repBadge(did: string) {
|
||||
const rec = repDoc?.records[did];
|
||||
if (!rec) return '<span style="color:#64748b;font-size:0.7rem">new</span>';
|
||||
const c = rec.score >= 80 ? '#22c55e' : rec.score >= 60 ? '#f59e0b' : '#ef4444';
|
||||
const badges = rec.badges.length ? ` ${rec.badges.map(b => b === 'verified_seller' ? '✓' : b === 'liquidity_provider' ? '💧' : b === 'top_trader' ? '🏆' : '').join('')}` : '';
|
||||
return `<span style="color:${c};font-size:0.7rem;font-weight:600">${rec.score}${badges}</span>`;
|
||||
}
|
||||
|
||||
function intentRow(i: typeof intents[0]) {
|
||||
const sideColor = i.side === 'buy' ? '#10b981' : '#f59e0b';
|
||||
const icon = i.tokenId === 'cusdc' ? '💵' : i.tokenId === 'myco' ? '🌱' : '🎮';
|
||||
return `<tr style="border-bottom:1px solid #1e293b">
|
||||
<td style="padding:8px"><span style="color:${sideColor};font-weight:600;text-transform:uppercase">${i.side}</span></td>
|
||||
<td style="padding:8px">${icon} ${i.tokenId}</td>
|
||||
<td style="padding:8px">${fmtAmount(i.tokenAmountMin)}–${fmtAmount(i.tokenAmountMax)}</td>
|
||||
<td style="padding:8px">${rateStr(i)}</td>
|
||||
<td style="padding:8px">${i.paymentMethods.join(', ')}</td>
|
||||
<td style="padding:8px">${i.creatorName} ${repBadge(i.creatorDid)}</td>
|
||||
<td style="padding:8px">${i.isStandingOrder ? '<span style="color:#3b82f6;font-size:0.7rem">LP</span>' : ''} ${i.autoAccept ? '<span style="color:#22c55e;font-size:0.7rem">auto</span>' : ''}</td>
|
||||
</tr>`;
|
||||
}
|
||||
|
||||
function tradeRow(t: typeof trades[0]) {
|
||||
const icon = t.tokenId === 'cusdc' ? '💵' : t.tokenId === 'myco' ? '🌱' : '🎮';
|
||||
return `<tr style="border-bottom:1px solid #1e293b">
|
||||
<td style="padding:8px">${icon} ${fmtAmount(t.tokenAmount)} ${t.tokenId}</td>
|
||||
<td style="padding:8px">${t.fiatAmount.toFixed(2)} ${t.fiatCurrency}</td>
|
||||
<td style="padding:8px">${t.buyerName} ${repBadge(t.buyerDid)}</td>
|
||||
<td style="padding:8px">${t.sellerName} ${repBadge(t.sellerDid)}</td>
|
||||
<td style="padding:8px">${t.paymentMethod}</td>
|
||||
<td style="padding:8px">${statusBadge(t.status)}</td>
|
||||
</tr>`;
|
||||
}
|
||||
|
||||
return `
|
||||
<div style="max-width:960px;margin:0 auto;padding:24px 16px;color:#e2e8f0;font-family:system-ui,sans-serif">
|
||||
<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:24px">
|
||||
<div>
|
||||
<h1 style="font-size:1.5rem;font-weight:700;margin:0;background:linear-gradient(to right,#f59e0b,#10b981);-webkit-background-clip:text;-webkit-text-fill-color:transparent;background-clip:text">
|
||||
💱 rExchange
|
||||
</h1>
|
||||
<p style="color:#64748b;font-size:0.85rem;margin:4px 0 0">P2P order book for ${space}</p>
|
||||
</div>
|
||||
<div style="display:flex;gap:12px;font-size:0.8rem">
|
||||
<span style="color:#10b981">${buyIntents.length} buys</span>
|
||||
<span style="color:#f59e0b">${sellIntents.length} sells</span>
|
||||
<span style="color:#3b82f6">${activeTrades.length} active</span>
|
||||
<span style="color:#22c55e">${completedTrades.length} settled</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Active Order Book -->
|
||||
<div style="background:#0f172a;border:1px solid #1e293b;border-radius:12px;padding:16px;margin-bottom:24px">
|
||||
<h2 style="font-size:1rem;font-weight:600;margin:0 0 12px;color:#94a3b8">Order Book</h2>
|
||||
${intents.filter(i => i.status === 'active').length === 0
|
||||
? '<p style="color:#475569;text-align:center;padding:24px">No active intents</p>'
|
||||
: `<table style="width:100%;border-collapse:collapse;font-size:0.85rem">
|
||||
<thead><tr style="border-bottom:2px solid #1e293b;color:#64748b;font-size:0.75rem;text-transform:uppercase">
|
||||
<th style="padding:8px;text-align:left">Side</th>
|
||||
<th style="padding:8px;text-align:left">Token</th>
|
||||
<th style="padding:8px;text-align:left">Amount</th>
|
||||
<th style="padding:8px;text-align:left">Rate</th>
|
||||
<th style="padding:8px;text-align:left">Payment</th>
|
||||
<th style="padding:8px;text-align:left">Trader</th>
|
||||
<th style="padding:8px;text-align:left">Flags</th>
|
||||
</tr></thead>
|
||||
<tbody>${buyIntents.map(intentRow).join('')}${sellIntents.map(intentRow).join('')}</tbody>
|
||||
</table>`}
|
||||
</div>
|
||||
|
||||
<!-- Active Trades -->
|
||||
${activeTrades.length > 0 ? `
|
||||
<div style="background:#0f172a;border:1px solid #1e293b;border-radius:12px;padding:16px;margin-bottom:24px">
|
||||
<h2 style="font-size:1rem;font-weight:600;margin:0 0 12px;color:#94a3b8">Active Trades</h2>
|
||||
<table style="width:100%;border-collapse:collapse;font-size:0.85rem">
|
||||
<thead><tr style="border-bottom:2px solid #1e293b;color:#64748b;font-size:0.75rem;text-transform:uppercase">
|
||||
<th style="padding:8px;text-align:left">Amount</th>
|
||||
<th style="padding:8px;text-align:left">Fiat</th>
|
||||
<th style="padding:8px;text-align:left">Buyer</th>
|
||||
<th style="padding:8px;text-align:left">Seller</th>
|
||||
<th style="padding:8px;text-align:left">Method</th>
|
||||
<th style="padding:8px;text-align:left">Status</th>
|
||||
</tr></thead>
|
||||
<tbody>${activeTrades.map(tradeRow).join('')}</tbody>
|
||||
</table>
|
||||
</div>` : ''}
|
||||
|
||||
<!-- Recent Completed -->
|
||||
${completedTrades.length > 0 ? `
|
||||
<div style="background:#0f172a;border:1px solid #1e293b;border-radius:12px;padding:16px">
|
||||
<h2 style="font-size:1rem;font-weight:600;margin:0 0 12px;color:#94a3b8">Recent Trades</h2>
|
||||
<table style="width:100%;border-collapse:collapse;font-size:0.85rem">
|
||||
<thead><tr style="border-bottom:2px solid #1e293b;color:#64748b;font-size:0.75rem;text-transform:uppercase">
|
||||
<th style="padding:8px;text-align:left">Amount</th>
|
||||
<th style="padding:8px;text-align:left">Fiat</th>
|
||||
<th style="padding:8px;text-align:left">Buyer</th>
|
||||
<th style="padding:8px;text-align:left">Seller</th>
|
||||
<th style="padding:8px;text-align:left">Method</th>
|
||||
<th style="padding:8px;text-align:left">Status</th>
|
||||
</tr></thead>
|
||||
<tbody>${completedTrades.map(tradeRow).join('')}</tbody>
|
||||
</table>
|
||||
</div>` : ''}
|
||||
</div>
|
||||
|
||||
<script>
|
||||
// Auto-refresh every 30s
|
||||
setTimeout(() => location.reload(), 30000);
|
||||
</script>`;
|
||||
}
|
||||
|
||||
// ── Page routes ──
|
||||
|
||||
routes.get('/', (c) => {
|
||||
|
|
@ -41,8 +386,7 @@ routes.get('/', (c) => {
|
|||
spaceSlug: space,
|
||||
modules: getModuleInfoList(),
|
||||
theme: 'dark',
|
||||
body: `<folk-exchange-app space="${space}"></folk-exchange-app>`,
|
||||
scripts: `<script type="module" src="/modules/rexchange/folk-exchange-app.js?v=1"></script>`,
|
||||
body: renderOrderBook(space),
|
||||
}));
|
||||
});
|
||||
|
||||
|
|
@ -63,8 +407,10 @@ export const exchangeModule: RSpaceModule = {
|
|||
],
|
||||
routes,
|
||||
landingPage: renderLanding,
|
||||
seedTemplate: seedDemoIfEmpty,
|
||||
async onInit(ctx) {
|
||||
_syncServer = ctx.syncServer;
|
||||
seedDemoIfEmpty();
|
||||
startSolverCron(() => _syncServer);
|
||||
},
|
||||
feeds: [
|
||||
|
|
|
|||
Loading…
Reference in New Issue