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
736 changes: 678 additions & 58 deletions app/devs/page.mdx

Large diffs are not rendered by default.

408 changes: 338 additions & 70 deletions app/docs/page.mdx

Large diffs are not rendered by default.

390 changes: 353 additions & 37 deletions app/trading/page.tsx

Large diffs are not rendered by default.

550 changes: 549 additions & 1 deletion app/workflow/page.tsx

Large diffs are not rendered by default.

22 changes: 20 additions & 2 deletions components/AgentBuilder.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ interface AgentFormData {
priceXlm: string;
visibility: 'public' | 'private' | 'forked';
listInMarketplace: boolean;
agentWallet: string;
}

const DRAFT_KEY = 'agent_builder_draft';
Expand All @@ -31,6 +32,7 @@ const initialData: AgentFormData = {
priceXlm: '0.01',
visibility: 'public',
listInMarketplace: true,
agentWallet: '',
};

const toolOptions = [
Expand Down Expand Up @@ -131,7 +133,7 @@ export default function AgentBuilder() {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
owner_wallet: walletAddress,
owner_wallet: form.agentWallet || walletAddress,
name: form.name,
description: form.description,
tags: form.tags.split(',').map((t) => t.trim()).filter(Boolean),
Expand Down Expand Up @@ -331,9 +333,25 @@ export default function AgentBuilder() {
</div>
</div>
</div>
<div>
<label className="block text-xs font-mono text-gray-400 mb-1.5">Agent Wallet Address (optional)</label>
<input
type="text"
value={form.agentWallet}
onChange={(e) => update('agentWallet', e.target.value)}
placeholder="Leave blank to use your connected wallet"
className="w-full px-3 py-2.5 bg-[rgba(255,255,255,0.03)] border border-[rgba(255,255,255,0.08)] rounded-lg text-white text-sm font-mono placeholder-gray-600 focus:outline-none focus:border-[rgba(0,255,229,0.4)]"
/>
{form.agentWallet && !/^G[A-Z2-7]{55}$/.test(form.agentWallet) && (
<p className="text-[10px] font-mono text-red-400 mt-1">Invalid Stellar address format.</p>
)}
<p className="text-[10px] font-mono text-gray-500 mt-1">
Build this agent for a different wallet. Enables multi-agent architecture.
</p>
</div>
<button
onClick={() => setStep('prompt')}
disabled={!form.name}
disabled={!form.name || (!!form.agentWallet && !/^G[A-Z2-7]{55}$/.test(form.agentWallet))}
className="w-full py-3 font-mono text-sm bg-[#00FFE5] text-black rounded-lg font-bold hover:bg-[#00e6ce] transition-colors disabled:opacity-40"
>
Next: System Prompt →
Expand Down
110 changes: 109 additions & 1 deletion components/AgentCard.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
'use client';

import { motion } from 'framer-motion';
import { useState } from 'react';
import { motion, AnimatePresence } from 'framer-motion';
import Link from 'next/link';
import { Agent } from '@/types';
import { truncateAddress } from '@/lib/stellar';
Expand All @@ -21,11 +22,27 @@ const modelLabel: Record<string, string> = {
};

export default function AgentCard({ agent, onFork }: AgentCardProps) {
const [showStrategyModal, setShowStrategyModal] = useState(false);
const [strategyFile, setStrategyFile] = useState<File | null>(null);
const [strategyNotes, setStrategyNotes] = useState('');
const [saveSuccess, setSaveSuccess] = useState(false);

const safeOwner = agent.owner_wallet || 'Unknown';
const totalRequests = Number(agent.total_requests ?? 0);
const priceXlm = Number(agent.price_xlm ?? 0);

const handleSaveStrategy = () => {
setSaveSuccess(true);
setTimeout(() => {
setSaveSuccess(false);
setShowStrategyModal(false);
setStrategyFile(null);
setStrategyNotes('');
}, 1500);
};

return (
<>
<motion.div
whileHover={{ y: -4, boxShadow: '0 0 24px rgba(0,255,229,0.08)' }}
className="rounded-xl border border-[rgba(0,255,229,0.12)] bg-[rgba(255,255,255,0.03)] p-5 flex flex-col gap-3 cursor-pointer transition-all"
Expand Down Expand Up @@ -88,7 +105,98 @@ export default function AgentCard({ agent, onFork }: AgentCardProps) {
>
Fork
</button>
<button
onClick={(e) => { e.stopPropagation(); setShowStrategyModal(true); }}
className="flex-1 py-1.5 text-xs font-mono border border-[rgba(0,255,229,0.2)] text-[#00FFE5] rounded hover:bg-[rgba(0,255,229,0.1)] transition-all"
>
Strategy
</button>
</div>
</motion.div>

<AnimatePresence>
{showStrategyModal && (
<motion.div
key="strategy-overlay"
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
className="fixed inset-0 z-50 flex items-center justify-center bg-black/70 backdrop-blur-sm px-4"
onClick={() => setShowStrategyModal(false)}
>
<motion.div
key="strategy-modal"
initial={{ opacity: 0, scale: 0.95, y: 16 }}
animate={{ opacity: 1, scale: 1, y: 0 }}
exit={{ opacity: 0, scale: 0.95, y: 16 }}
transition={{ duration: 0.18 }}
className="w-full max-w-md rounded-xl border border-[rgba(0,255,229,0.25)] bg-[#0d1117] p-6 space-y-5 shadow-2xl"
onClick={(e) => e.stopPropagation()}
>
<div className="flex items-center justify-between">
<h2 className="font-syne font-bold text-[#00FFE5] text-lg">Upload Strategy</h2>
<button
onClick={() => setShowStrategyModal(false)}
className="text-gray-500 hover:text-white text-lg leading-none transition-colors"
>
</button>
</div>

<div
role="button"
tabIndex={0}
className="flex flex-col items-center justify-center gap-2 rounded-lg border-2 border-dashed border-[rgba(0,255,229,0.25)] bg-[rgba(0,255,229,0.03)] py-8 px-4 cursor-pointer hover:border-[rgba(0,255,229,0.5)] transition-all"
onClick={() => document.getElementById(`strategy-file-${agent.id}`)?.click()}
onKeyDown={(e) => { if (e.key === 'Enter' || e.key === ' ') document.getElementById(`strategy-file-${agent.id}`)?.click(); }}
>
<span className="text-2xl">📄</span>
<p className="text-xs font-mono text-gray-400 text-center">
{strategyFile ? strategyFile.name : 'Drag & drop or click to upload'}
</p>
<p className="text-[10px] font-mono text-gray-600">.json, .txt, .md, .yaml</p>
<input
id={`strategy-file-${agent.id}`}
type="file"
accept=".json,.txt,.md,.yaml,.yml"
className="hidden"
onChange={(e) => setStrategyFile(e.target.files?.[0] ?? null)}
/>
</div>

<div>
<label className="block text-xs font-mono text-gray-400 mb-1.5">Strategy Notes / Prompt Customization</label>
<textarea
value={strategyNotes}
onChange={(e) => setStrategyNotes(e.target.value)}
placeholder="Add custom instructions or strategy notes for this agent..."
rows={4}
className="w-full px-3 py-2.5 bg-[rgba(255,255,255,0.03)] border border-[rgba(255,255,255,0.08)] rounded-lg text-white text-sm font-mono placeholder-gray-600 focus:outline-none focus:border-[rgba(0,255,229,0.4)] resize-none"
/>
</div>

{saveSuccess && (
<p className="text-xs font-mono text-[#00FFE5] text-center">✓ Strategy saved successfully!</p>
)}

<div className="flex gap-3">
<button
onClick={() => setShowStrategyModal(false)}
className="flex-1 py-2.5 text-xs font-mono border border-[rgba(255,255,255,0.1)] text-gray-400 rounded-lg hover:text-white transition-colors"
>
Cancel
</button>
<button
onClick={handleSaveStrategy}
className="flex-1 py-2.5 text-xs font-mono bg-[#00FFE5] text-black rounded-lg font-bold hover:bg-[#00e6ce] transition-colors"
>
Save Strategy
</button>
</div>
</motion.div>
</motion.div>
)}
</AnimatePresence>
</>
);
}
60 changes: 56 additions & 4 deletions components/CandlestickChart.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,12 @@ export interface OHLC {
volume: number;
}

export interface CloseEvent {
price: number;
type: 'tp' | 'sl' | 'manual';
pnl: number;
}

interface CandlestickChartProps {
candles: OHLC[];
width?: number;
Expand All @@ -21,6 +27,7 @@ interface CandlestickChartProps {
slLevel?: number | null;
liqLevel?: number | null;
entryLevel?: number | null;
closeEvent?: CloseEvent | null;
className?: string;
}

Expand All @@ -39,6 +46,7 @@ export default function CandlestickChart({
slLevel,
liqLevel,
entryLevel,
closeEvent,
className = '',
}: CandlestickChartProps) {
const containerRef = useRef<HTMLDivElement>(null);
Expand Down Expand Up @@ -71,10 +79,11 @@ export default function CandlestickChart({
const chartW = w - padLeft - padRight;
const chartH = h - padTop - padBottom;

// Price range
// Price range — extend to include TP/SL/entry/liq/closeEvent so reference lines are never clipped
const allValues = displayCandles.flatMap((c) => [c.high, c.low]);
const minPrice = Math.min(...allValues);
const maxPrice = Math.max(...allValues);
const refPrices = [tpLevel, slLevel, liqLevel, entryLevel, closeEvent?.price].filter((v): v is number => v != null && v > 0);
const minPrice = Math.min(...allValues, ...refPrices);
const maxPrice = Math.max(...allValues, ...refPrices);
const range = maxPrice - minPrice || minPrice * 0.01;
const pricePad = range * 0.08;
const lo = minPrice - pricePad;
Expand Down Expand Up @@ -134,6 +143,49 @@ export default function CandlestickChart({
if (liqLevel) drawRefLine(liqLevel, 'rgba(220,38,38,0.9)', 'LIQ');
if (entryLevel) drawRefLine(entryLevel, 'rgba(0,255,229,0.8)', 'ENTRY', false);

// Close-event marker: circle + PnL label on the rightmost candle
if (closeEvent && closeEvent.price >= lo && closeEvent.price <= hi) {
const closeY = py(closeEvent.price);
const closeX = px(n - 1);
const isProfit = closeEvent.pnl >= 0;
const markerColor = closeEvent.type === 'tp' ? '#FFB800' : closeEvent.type === 'sl' ? '#f87171' : '#00FFE5';
const pnlLabel = `${closeEvent.type === 'tp' ? '🎯 TP' : closeEvent.type === 'sl' ? '🛑 SL' : '✕ CLOSE'} ${isProfit ? '+' : ''}$${Math.abs(closeEvent.pnl).toFixed(2)}`;

// Horizontal dashed line at close price
ctx.save();
ctx.strokeStyle = markerColor;
ctx.lineWidth = 1.5;
ctx.setLineDash([3, 3]);
ctx.beginPath();
ctx.moveTo(padLeft, closeY);
ctx.lineTo(w - padRight, closeY);
ctx.stroke();

// Circle at close position
ctx.setLineDash([]);
ctx.beginPath();
ctx.arc(closeX, closeY, 6, 0, Math.PI * 2);
ctx.fillStyle = markerColor;
ctx.fill();
ctx.strokeStyle = '#000';
ctx.lineWidth = 1.5;
ctx.stroke();

// PnL label badge
ctx.font = 'bold 10px monospace';
const labelW = ctx.measureText(pnlLabel).width + 12;
const labelX = Math.min(closeX - labelW / 2, w - padRight - labelW);
const labelY = closeY - 18;
ctx.fillStyle = isProfit ? 'rgba(74,222,128,0.9)' : 'rgba(248,113,113,0.9)';
ctx.beginPath();
ctx.roundRect(labelX, labelY - 12, labelW, 16, 4);
ctx.fill();
ctx.fillStyle = '#000';
ctx.textAlign = 'left';
ctx.fillText(pnlLabel, labelX + 6, labelY);
ctx.restore();
}

// Candles
displayCandles.forEach((candle, i) => {
const x = px(i);
Expand Down Expand Up @@ -175,7 +227,7 @@ export default function CandlestickChart({
const label = new Date(candle.ts).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit', hour12: false });
ctx.fillText(label, px(i), h - 6);
}
}, [displayCandles, height, supportLevel, resistanceLevel, tpLevel, slLevel, liqLevel, entryLevel]);
}, [displayCandles, height, supportLevel, resistanceLevel, tpLevel, slLevel, liqLevel, entryLevel, closeEvent]);

return (
<div ref={containerRef} className={`w-full ${className}`} style={{ height }}>
Expand Down
4 changes: 4 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading