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
89 changes: 62 additions & 27 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,36 +1,71 @@
# macOS
.DS_Store
```
# Compiled and build artifacts
*.pyc
__pycache__/
*.o
*.obj
*.so
*.dll
*.exe
*.class
*.out

# Python environments and caches
# Dependencies
.venv/
venv/
env/
ENV/
__pycache__/
*.py[cod]
.pytest_cache/

# Node and Next.js
node_modules/
.next/
out/
dist/
build/
sdk/**/dist/
# Local environment files (keep documented examples)
target/
.gradle/
.mypy_cache/
.pytest_cache/

# Logs and temp files
*.log
*.tmp
*.swp
*.swo

# Environment
.env
.env.*
!.env.example
.env.local
*.env.*

# Editor and temporary files
# Editors
.vscode/
.idea/
*.swp
*.swo
*~
*.tmp
*.temp
.tmp_docx/
artifacts/
deliverables/
CEHUA_GLAZE_HACKATHON_PROMPT.md
.qoder/

# System files
.DS_Store
Thumbs.db

# Coverage
coverage/
htmlcov/
.coverage

# Compressed files
*.zip
*.gz
*.tar
*.tgz
*.bz2
*.xz
*.7z
*.rar
*.zst
*.lz4
*.lzh
*.cab
*.arj
*.rpm
*.deb
*.Z
*.lz
*.lzo
*.tar.gz
*.tar.bz2
*.tar.xz
*.tar.zst
```
12 changes: 12 additions & 0 deletions backend/app/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -313,9 +313,21 @@ async def mcp_call(request: Request) -> JSONResponse:
content={"error": "Invalid JSON body"},
)

if not isinstance(body, dict):
return JSONResponse(
status_code=400,
content={"error": "Request body must be a JSON object"},
)

tool_name = body.get("tool") or body.get("name") or ""
arguments = body.get("arguments") or body.get("params") or {}

if not isinstance(arguments, dict):
return JSONResponse(
status_code=400,
content={"error": "Arguments must be a JSON object"},
)

# Dispatch to the appropriate service function.
result = _dispatch_mcp_tool(tool_name, arguments)
if result is None:
Expand Down
2 changes: 1 addition & 1 deletion backend/app/routers/strategy.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ def strategy_audit(request: AuditRequest) -> AuditResponse:
return audit_strategy(request)


# Single route definition to avoid duplicate OpenAPI schema entries
@router.post("/stress-test", response_model=StressTestResponse)
@stress_router.post("/run", response_model=StressTestResponse)
def strategy_stress_test(request: StressTestRequest) -> StressTestResponse:
return stress_test_strategy(request)
24 changes: 22 additions & 2 deletions frontend/lib/payment-receipt.ts
Original file line number Diff line number Diff line change
Expand Up @@ -99,11 +99,31 @@ export async function verifyPaymentReceiptOnChain(receipt: PaymentReceipt): Prom
const onchainStatus = result.status === "0x1" ? "confirmed" : result.status === "0x0" ? "failed" : "unavailable";
const expectedAsset = receipt.asset?.toLowerCase();
const expectedReceiver = receipt.receiver?.toLowerCase();
const expectedAmount = receipt.amountAtomic ? BigInt(receipt.amountAtomic) : null;
let expectedAmount: bigint | null = null;
if (receipt.amountAtomic && /^\d+$/.test(receipt.amountAtomic)) {
try {
expectedAmount = BigInt(receipt.amountAtomic);
} catch {
// Invalid amount format, skip verification
return {
...receipt,
blockNumber: result.blockNumber ? Number.parseInt(result.blockNumber, 16) : null,
onchainStatus,
transferVerified: null,
};
}
}
const matchingTransfer = result.logs?.some((log) => {
if (log.address?.toLowerCase() !== expectedAsset || String(log.topics?.[0]).toLowerCase() !== TRANSFER_TOPIC) return false;
const to = topicAddress(log.topics?.[2]);
const amount = typeof log.data === "string" ? BigInt(log.data) : null;
let amount: bigint | null = null;
if (typeof log.data === "string") {
try {
amount = BigInt(log.data);
} catch {
return false;
}
}
return to === expectedReceiver && amount === expectedAmount;
});
return {
Expand Down
30 changes: 24 additions & 6 deletions sdk/python/deltazero/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -120,23 +120,41 @@ def _request(self, path: str, body: dict[str, Any]) -> dict[str, Any]:
raise DeltaZeroTimeoutError(url, self.timeout_s) from exc
raise DeltaZeroError(str(reason)) from exc

def _deserialize_model(self, data: dict[str, Any], model_cls: type[Any]) -> Any:
"""Deserialize a dict response into the appropriate model class."""
if hasattr(model_cls, "model_validate"):
# Pydantic v2
return model_cls.model_validate(data)
elif hasattr(model_cls, "parse_obj"):
# Pydantic v1
return model_cls.parse_obj(data)
else:
# Fallback: try direct instantiation
return model_cls(**data)

def build_strategy(self, request_body: BuildRequest) -> BuildResponse:
return self._request("/strategy/build", request_body) # type: ignore[return-value]
data = self._request("/strategy/build", request_body)
return self._deserialize_model(data, BuildResponse)

def audit_position(self, request_body: AuditRequest) -> AuditResponse:
return self._request("/strategy/audit", request_body) # type: ignore[return-value]
data = self._request("/strategy/audit", request_body)
return self._deserialize_model(data, AuditResponse)

def stress_test(self, request_body: StressTestRequest) -> StressTestResponse:
return self._request("/strategy/stress-test", request_body) # type: ignore[return-value]
data = self._request("/strategy/stress-test", request_body)
return self._deserialize_model(data, StressTestResponse)

def audit_wallet(self, request_body: WalletAnalyzeRequest) -> WalletPortfolioResponse:
return self._request("/wallet/analyze", request_body) # type: ignore[return-value]
data = self._request("/wallet/analyze", request_body)
return self._deserialize_model(data, WalletPortfolioResponse)

def evaluate_risk_envelope(self, request_body: RiskEnvelopeRequest) -> RiskEnvelopeV1:
return self._request("/risk-envelope/evaluate", request_body) # type: ignore[return-value]
data = self._request("/risk-envelope/evaluate", request_body)
return self._deserialize_model(data, RiskEnvelopeV1)

def verify_risk_envelope(
self,
request_body: RiskEnvelopeVerificationRequest,
) -> RiskEnvelopeProofVerification:
return self._request("/risk-envelope/verify", request_body) # type: ignore[return-value]
data = self._request("/risk-envelope/verify", request_body)
return self._deserialize_model(data, RiskEnvelopeProofVerification)
17 changes: 15 additions & 2 deletions sdk/typescript/src/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,15 @@ export class DeltaZeroClient {
private async request<T>(path: string, body: unknown): Promise<T> {
const url = `${this.baseUrl}${path}`;
const controller = new AbortController();
const timeout = globalThis.setTimeout(() => controller.abort(), this.timeoutMs);
let timeoutId: ReturnType<typeof globalThis.setTimeout> | null = null;
let isSettled = false;

const abortOnTimeout = () => {
if (!isSettled) {
controller.abort();
}
};
timeoutId = globalThis.setTimeout(abortOnTimeout, this.timeoutMs);

try {
const response = await fetch(url, {
Expand All @@ -96,6 +104,7 @@ export class DeltaZeroClient {
signal: controller.signal,
});

isSettled = true;
const parsed = await parseJsonBody(response, url);

if (!response.ok) {
Expand All @@ -113,13 +122,17 @@ export class DeltaZeroClient {

return parsed as T;
} catch (error) {
isSettled = true;
if (error instanceof DeltaZeroError) throw error;
if (error instanceof DOMException && error.name === "AbortError") {
throw new DeltaZeroTimeoutError(`Request to ${url} timed out after ${this.timeoutMs}ms.`, url);
}
throw new DeltaZeroError(error instanceof Error ? error.message : `Request to ${url} failed.`);
} finally {
globalThis.clearTimeout(timeout);
isSettled = true;
if (timeoutId !== null) {
globalThis.clearTimeout(timeoutId);
}
}
}

Expand Down
Loading