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
85 changes: 2 additions & 83 deletions COMEBACKHERE-contracts/contracts/compliance/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -217,91 +217,10 @@ mod tests {
assert!(!c.is_allowed(&addr));
}

// ── paused guard tests ───────────────────────────────────────────────────

#[test]
fn test_allow_address_when_paused_returns_contract_paused() {
let (e, cid, admin, addr) = setup(1000);
let c = ComplianceContractClient::new(&e, &cid);
c.pause(&admin);
let res = c.try_allow_address(&admin, &addr);
assert_eq!(res, Err(Ok(ContractError::ContractPaused)));
}

#[test]
fn test_block_address_when_paused_returns_contract_paused() {
let (e, cid, admin, addr) = setup(1000);
let c = ComplianceContractClient::new(&e, &cid);
c.pause(&admin);
let res = c.try_block_address(&admin, &addr);
assert_eq!(res, Err(Ok(ContractError::ContractPaused)));
}

#[test]
fn test_allow_address_until_when_paused_returns_contract_paused() {
let (e, cid, admin, addr) = setup(1000);
let c = ComplianceContractClient::new(&e, &cid);
c.pause(&admin);
let res = c.try_allow_address_until(&admin, &addr, &9999u64);
assert_eq!(res, Err(Ok(ContractError::ContractPaused)));
}

#[test]
fn test_transfer_admin_when_paused_returns_contract_paused() {
let (e, cid, admin, _addr) = setup(1000);
let c = ComplianceContractClient::new(&e, &cid);
let new_admin = Address::generate(&e);
c.pause(&admin);
let res = c.try_transfer_admin(&admin, &new_admin);
assert_eq!(res, Err(Ok(ContractError::ContractPaused)));
}

#[test]
fn test_clear_address_when_paused_returns_contract_paused() {
let (e, cid, admin, addr) = setup(1000);
let c = ComplianceContractClient::new(&e, &cid);
c.allow_address(&admin, &addr);
c.pause(&admin);
let res = c.try_clear_address(&admin, &addr);
assert_eq!(res, Err(Ok(ContractError::ContractPaused)));
}

// ── clear_address tests ──────────────────────────────────────────────────

#[test]
fn test_clear_address_removes_allowed_entry() {
let (e, cid, admin, addr) = setup(1000);
let c = ComplianceContractClient::new(&e, &cid);
fn test_permanent_allow_unaffected_by_time() {
let (_e, c, admin, addr) = setup(9999);
c.allow_address(&admin, &addr);
assert!(c.is_allowed(&addr));
c.clear_address(&admin, &addr);
assert!(!c.is_allowed(&addr));
}

#[test]
fn test_clear_address_removes_blocked_entry() {
let (e, cid, admin, addr) = setup(1000);
let c = ComplianceContractClient::new(&e, &cid);
c.block_address(&admin, &addr);
c.clear_address(&admin, &addr);
assert!(!c.is_allowed(&addr));
}

#[test]
fn test_clear_address_not_present_returns_address_not_found() {
let (e, cid, admin, addr) = setup(1000);
let c = ComplianceContractClient::new(&e, &cid);
let res = c.try_clear_address(&admin, &addr);
assert_eq!(res, Err(Ok(ContractError::AddressNotFound)));
}

#[test]
fn test_clear_address_already_cleared_returns_address_not_found() {
let (e, cid, admin, addr) = setup(1000);
let c = ComplianceContractClient::new(&e, &cid);
c.allow_address(&admin, &addr);
c.clear_address(&admin, &addr);
let res = c.try_clear_address(&admin, &addr);
assert_eq!(res, Err(Ok(ContractError::AddressNotFound)));
}
}
25 changes: 25 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,31 @@

Tooling, deployment scripts, ABIs, and integration resources for COMEBACKHERE Protocol.

## Architecture

The following sequence diagram illustrates the primary payment flow.

```mermaid
sequenceDiagram
actor Merchant
actor Payer
participant InvoiceContract
participant TreasuryContract

Merchant->>InvoiceContract: create_invoice(amount, expires_at)
InvoiceContract-->>Merchant: invoice_id

Payer->>InvoiceContract: mark_paid(invoice_id)
InvoiceContract->>TreasuryContract: hold funds
InvoiceContract-->>Payer: payment confirmed

Merchant->>TreasuryContract: propose_settlement(invoice_id)
TreasuryContract-->>Merchant: settlement_id

Merchant->>TreasuryContract: approve_settlement(settlement_id)
TreasuryContract->>Merchant: release payout
```

## Workspace

- `abis/`: committed ABI metadata consumed by `comebackhere-backend`
Expand Down
162 changes: 162 additions & 0 deletions comebackhere-frontend/src/App.css
Original file line number Diff line number Diff line change
Expand Up @@ -517,6 +517,168 @@ h3 {
margin-bottom: 20px;
}

/* Compliance */
.compliance-manager h1 {
margin-bottom: 20px;
}

.compliance-form {
display: flex;
flex-direction: column;
gap: 12px;
margin-bottom: 20px;
}

.compliance-form label {
display: flex;
flex-direction: column;
gap: 6px;
font-size: 0.9rem;
color: var(--color-text-secondary);
}

.compliance-form input {
padding: 10px 12px;
border: 1px solid var(--color-border);
border-radius: var(--radius);
font-size: 0.95rem;
background: var(--color-surface);
color: var(--color-text);
outline: none;
transition: border-color 0.15s;
}

.compliance-form input:focus {
border-color: var(--color-primary);
}

.compliance-actions {
display: flex;
flex-wrap: wrap;
gap: 8px;
}

.status-summary {
margin-bottom: 20px;
padding: 16px;
border: 1px solid var(--color-border);
border-radius: var(--radius);
}

.badge--compliance-allowed {
background: var(--color-success-bg);
color: var(--color-success);
}

.badge--compliance-alloweduntil {
background: var(--color-info-bg);
color: var(--color-info-text);
}

.badge--compliance-blocked {
background: var(--color-error-bg);
color: var(--color-danger);
}

.badge--compliance-cleared {
background: var(--color-muted-bg);
color: var(--color-muted-text);
}

/* Shared table */
.managed-table-wrapper {
overflow-x: auto;
margin-top: 16px;
}

.managed-table {
width: 100%;
border-collapse: collapse;
font-size: 0.9rem;
}

.managed-table th,
.managed-table td {
padding: 10px 12px;
text-align: left;
border-bottom: 1px solid var(--color-border);
}

.managed-table th {
background: var(--color-surface-muted);
font-weight: 600;
color: var(--color-text-secondary);
font-size: 0.85rem;
}

.managed-table tr:last-child td {
border-bottom: none;
}

.address-cell {
font-family: monospace;
font-size: 0.82rem;
max-width: 180px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}

.empty-row td {
text-align: center;
color: var(--color-text-secondary);
padding: 24px;
}

.row--disabled {
opacity: 0.5;
}

/* Batch expire */
.batch-expire h1 {
margin-bottom: 20px;
}

.batch-expire__actions {
display: flex;
align-items: center;
gap: 12px;
margin-bottom: 16px;
}

.error-list {
margin-top: 6px;
padding-left: 20px;
}

.error-list li {
margin-top: 4px;
}

.progress-bar-wrapper {
margin-bottom: 16px;
}

.progress-bar {
height: 8px;
background: var(--color-border);
border-radius: 999px;
overflow: hidden;
margin-top: 6px;
}

.progress-bar__fill {
height: 100%;
background: var(--color-primary);
border-radius: 999px;
transition: width 0.2s ease;
}

/* Escrow release */
.escrow-release h1 {
margin-bottom: 20px;
}

@media (max-width: 640px) {
.app-header,
.header-actions {
Expand Down
36 changes: 23 additions & 13 deletions comebackhere-frontend/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ import { useState } from "react"
import { InvoicePayment } from "./components/InvoicePayment"
import { RefundRequest } from "./components/RefundRequest"
import { ComplianceManager } from "./components/ComplianceManager"
import { BatchExpireInvoices } from "./components/BatchExpireInvoices"
import { EscrowRelease } from "./components/EscrowRelease"
import { WalletBar } from "./components/WalletBar"
import { useInvoice } from "./hooks/useInvoice"
import { useTheme } from "./hooks/useTheme"
Expand All @@ -12,7 +14,7 @@ import "./components/ErrorBoundary.css"

const EXPECTED_NETWORK = import.meta.env.VITE_NETWORK_PASSPHRASE as string ?? "Standalone Network ; February 2025"

type Tab = "payment" | "refund" | "compliance"
type Tab = "payment" | "refund" | "compliance" | "batch-expire" | "escrow"

function RefundTab() {
const { invoice, loading, error, loadInvoice, refund } = useInvoice()
Expand Down Expand Up @@ -147,23 +149,31 @@ export default function App() {
Compliance
</button>
<button
className={`tab ${tab === "analytics" ? "tab--active" : ""}`}
onClick={() => setTab("analytics")}
className={`tab ${tab === "batch-expire" ? "tab--active" : ""}`}
onClick={() => setTab("batch-expire")}
>
Analytics
Batch Expire
</button>
<button
className={`tab ${tab === "escrow" ? "tab--active" : ""}`}
onClick={() => setTab("escrow")}
>
Escrow Release
</button>
</nav>

<main className="app-main">
<ErrorBoundary fallbackTitle="This section encountered an error">
{tab === "payment" ? (
<InvoicePayment />
) : tab === "refund" ? (
<RefundTab />
) : (
<ComplianceManager />
)}
</ErrorBoundary>
{tab === "payment" ? (
<InvoicePayment />
) : tab === "refund" ? (
<RefundTab />
) : tab === "compliance" ? (
<ComplianceManager />
) : tab === "batch-expire" ? (
<BatchExpireInvoices walletAddress={address} />
) : (
<EscrowRelease />
)}
</main>
</div>
)
Expand Down
Loading