> ## Documentation Index
> Fetch the complete documentation index at: https://docs.keystoneos.xyz/llms.txt
> Use this file to discover all available pages before exploring further.

# Real-time Settlement Tracking

> Track settlement progress with live state updates and event timelines.

This guide shows how to build real-time settlement tracking components - a step progress indicator, an event timeline, deposit status per leg, and a notification badge for settlements that need attention.

## Prerequisites

* `@keystoneos/react` installed
* `KeystoneProvider` configured with `settlements:read` scope
* A React project with TypeScript

## What you will build

Four components that work together or independently:

1. **Progress stepper** - visual indicator of where a settlement is in its lifecycle
2. **Event timeline** - chronological list of every state transition
3. **Deposit tracker** - per-leg deposit status for settlements awaiting deposits
4. **Notification badge** - compact count of settlements needing action

## Progress stepper

The settlement state machine follows a linear path (with branches for failure). Use `getCurrentStepIndex()` and `SETTLEMENT_STEPS` from `@keystoneos/react` to build a visual stepper.

```tsx theme={null}
// progress-stepper.tsx
import { useSettlement, getCurrentStepIndex, isTerminalState, SETTLEMENT_STEPS } from '@keystoneos/react';

interface ProgressStepperProps {
  settlementId: string;
}

export function ProgressStepper({ settlementId }: ProgressStepperProps) {
  const { settlement, isLoading } = useSettlement(settlementId);

  if (isLoading || !settlement) return <div>Loading...</div>;

  const currentIndex = getCurrentStepIndex(settlement.state);
  const isTerminal = isTerminalState(settlement.state);
  const isFailed = settlement.state === 'ROLLED_BACK' || settlement.state === 'TIMED_OUT';

  return (
    <div style={{ display: 'flex', flexDirection: 'column', gap: '0' }}>
      {SETTLEMENT_STEPS.map((step, index) => {
        const isCompleted = index < currentIndex;
        const isCurrent = index === currentIndex;
        const isPending = index > currentIndex;

        let dotColor = '#d1d5db'; // gray - pending
        if (isCompleted) dotColor = '#16a34a'; // green - done
        if (isCurrent && isFailed) dotColor = '#dc2626'; // red - failed
        if (isCurrent && !isFailed && !isTerminal) dotColor = '#2563eb'; // blue - active
        if (isCurrent && isTerminal && !isFailed) dotColor = '#16a34a'; // green - finalized

        let lineColor = '#e5e7eb'; // gray
        if (isCompleted) lineColor = '#16a34a'; // green

        return (
          <div key={step.state} style={{ display: 'flex', alignItems: 'flex-start', gap: '12px' }}>
            {/* Dot and connector line */}
            <div style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', width: '24px' }}>
              <div style={{
                width: isCurrent ? '14px' : '10px',
                height: isCurrent ? '14px' : '10px',
                borderRadius: '50%',
                backgroundColor: dotColor,
                border: isCurrent ? `3px solid ${dotColor}33` : 'none',
                flexShrink: 0,
              }} />
              {index < SETTLEMENT_STEPS.length - 1 && (
                <div style={{ width: '2px', height: '32px', backgroundColor: lineColor }} />
              )}
            </div>

            {/* Label and description */}
            <div style={{ paddingBottom: '16px' }}>
              <p style={{
                fontSize: '14px',
                fontWeight: isCurrent ? 700 : 500,
                color: isPending ? '#9ca3af' : '#111827',
              }}>
                {step.label}
              </p>
              {isCurrent && !isTerminal && (
                <p style={{ fontSize: '12px', color: '#6b7280', marginTop: '2px' }}>
                  {step.description}
                </p>
              )}
            </div>
          </div>
        );
      })}

      {/* Terminal state banner */}
      {isTerminal && (
        <div style={{
          marginTop: '8px',
          padding: '12px 16px',
          borderRadius: '8px',
          backgroundColor: isFailed ? '#fee2e2' : '#dcfce7',
          color: isFailed ? '#991b1b' : '#166534',
          fontSize: '14px',
          fontWeight: 600,
        }}>
          {settlement.state === 'FINALIZED' && 'Settlement complete. All assets have been transferred.'}
          {settlement.state === 'ROLLED_BACK' && 'Settlement rolled back. All deposits have been returned.'}
          {settlement.state === 'TIMED_OUT' && 'Settlement timed out. Deposits have been returned to original owners.'}
        </div>
      )}
    </div>
  );
}
```

The `SETTLEMENT_STEPS` array contains the ordered steps:

| Index | State                 | Label              | Description                                  |
| ----- | --------------------- | ------------------ | -------------------------------------------- |
| 0     | `INSTRUCTED`          | Instructed         | Settlement created from matched instructions |
| 1     | `COMPLIANCE_CHECKING` | Compliance Check   | Screening parties and wallets                |
| 2     | `COMPLIANCE_CLEARED`  | Compliance Cleared | All checks passed                            |
| 3     | `AWAITING_DEPOSITS`   | Awaiting Deposits  | Waiting for escrow deposits                  |
| 4     | `SETTLED`             | Settled            | Atomic swap executed on-chain                |
| 5     | `FINALIZED`           | Finalized          | Settlement complete                          |

## Event timeline

The `useSettlementEvents` hook returns every state transition that has occurred, in chronological order. This gives a full audit trail.

```tsx theme={null}
// event-timeline.tsx
import { useSettlementEvents, getStateInfo } from '@keystoneos/react';

interface EventTimelineProps {
  settlementId: string;
}

const TRIGGER_LABELS: Record<string, string> = {
  'system:engine': 'Settlement Engine',
  'system:contract': 'Smart Contract',
  'system:compliance': 'Compliance Check',
  'm2m:platform': 'Platform API',
  'session:user': 'User Action',
};

export function EventTimeline({ settlementId }: EventTimelineProps) {
  const { events, isLoading, error } = useSettlementEvents(settlementId);

  if (isLoading) return <div>Loading events...</div>;
  if (error) return <div style={{ color: '#dc2626' }}>Error: {error}</div>;

  return (
    <div style={{ display: 'flex', flexDirection: 'column', gap: '0' }}>
      <h3 style={{ fontSize: '16px', fontWeight: 700, marginBottom: '16px' }}>Event Timeline</h3>

      {events.length === 0 ? (
        <p style={{ color: '#6b7280', fontSize: '14px' }}>No events yet.</p>
      ) : (
        events.map((event, index) => {
          const stateInfo = getStateInfo(event.to_state);
          const isLast = index === events.length - 1;

          return (
            <div key={event.id} style={{ display: 'flex', gap: '12px' }}>
              {/* Timeline connector */}
              <div style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', width: '12px' }}>
                <div style={{
                  width: '8px', height: '8px', borderRadius: '50%',
                  backgroundColor: isLast ? '#2DD4A8' : '#9ca3af',
                  flexShrink: 0, marginTop: '6px',
                }} />
                {!isLast && (
                  <div style={{ width: '1px', height: '100%', minHeight: '40px', backgroundColor: '#e5e7eb' }} />
                )}
              </div>

              {/* Event content */}
              <div style={{ paddingBottom: '20px', flex: 1 }}>
                <div style={{ display: 'flex', alignItems: 'center', gap: '8px' }}>
                  <span style={{ fontSize: '14px', fontWeight: 600 }}>{stateInfo.label}</span>
                  {event.from_state && (
                    <span style={{ fontSize: '12px', color: '#9ca3af' }}>
                      from {getStateInfo(event.from_state).label}
                    </span>
                  )}
                </div>

                <div style={{ display: 'flex', gap: '12px', marginTop: '4px', fontSize: '12px', color: '#6b7280' }}>
                  <span>{new Date(event.created_at).toLocaleString()}</span>
                  <span>{TRIGGER_LABELS[event.triggered_by] ?? event.triggered_by}</span>
                </div>

                {event.evidence_hash && (
                  <p style={{ marginTop: '4px', fontSize: '11px', fontFamily: 'monospace', color: '#9ca3af' }}>
                    Evidence: {event.evidence_hash.slice(0, 16)}...
                  </p>
                )}
              </div>
            </div>
          );
        })
      )}
    </div>
  );
}
```

## Deposit tracker

When a settlement is in `AWAITING_DEPOSITS`, each leg needs a deposit to escrow. This component shows which legs are deposited and which are still pending.

```tsx theme={null}
// deposit-tracker.tsx
import { useSettlement, useDeposit } from '@keystoneos/react';

interface DepositTrackerProps {
  settlementId: string;
}

export function DepositTracker({ settlementId }: DepositTrackerProps) {
  const { settlement, isLoading } = useSettlement(settlementId);

  if (isLoading || !settlement) return <div>Loading...</div>;

  if (settlement.state !== 'AWAITING_DEPOSITS') {
    return null; // Only show during deposit phase
  }

  return (
    <div style={{ display: 'flex', flexDirection: 'column', gap: '12px' }}>
      <h3 style={{ fontSize: '16px', fontWeight: 700 }}>Deposit Status</h3>
      {settlement.legs.map((leg, index) => (
        <LegDepositRow
          key={index}
          settlementId={settlementId}
          legIndex={index}
          leg={leg}
        />
      ))}
    </div>
  );
}

interface LegDepositRowProps {
  settlementId: string;
  legIndex: number;
  leg: {
    instrument_id: string;
    quantity: string;
    leg_type: string;
    deposited?: boolean;
    deposit_tx_hash?: string;
  };
}

function LegDepositRow({ settlementId, legIndex, leg }: LegDepositRowProps) {
  const { status, txHash, error, deposit } = useDeposit(settlementId, legIndex);

  const isDeposited = leg.deposited || status === 'deposit_confirmed';
  const isProcessing = ['fetching_info', 'approving', 'approval_confirmed', 'depositing'].includes(status);
  const displayTxHash = txHash ?? leg.deposit_tx_hash;

  return (
    <div style={{
      display: 'flex', alignItems: 'center', justifyContent: 'space-between',
      padding: '12px 16px', borderRadius: '8px',
      border: `1px solid ${isDeposited ? '#bbf7d0' : '#e5e7eb'}`,
      backgroundColor: isDeposited ? '#f0fdf4' : '#fff',
    }}>
      <div>
        <p style={{ fontSize: '14px', fontWeight: 600 }}>
          {leg.leg_type === 'asset_delivery' ? 'Asset Leg' : 'Payment Leg'}
        </p>
        <p style={{ fontSize: '13px', color: '#6b7280', fontFamily: 'monospace' }}>
          {Number(leg.quantity).toLocaleString()} {leg.instrument_id}
        </p>
        {displayTxHash && (
          <p style={{ fontSize: '11px', color: '#9ca3af', fontFamily: 'monospace', marginTop: '2px' }}>
            TX: {displayTxHash.slice(0, 10)}...{displayTxHash.slice(-8)}
          </p>
        )}
      </div>

      <div>
        {isDeposited && (
          <span style={{
            padding: '4px 10px', borderRadius: '9999px', fontSize: '12px',
            fontWeight: 600, backgroundColor: '#dcfce7', color: '#166534',
          }}>
            Deposited
          </span>
        )}

        {!isDeposited && status === 'idle' && (
          <button
            onClick={deposit}
            style={{
              padding: '6px 14px', backgroundColor: '#2DD4A8', color: '#fff',
              border: 'none', borderRadius: '6px', fontWeight: 600, cursor: 'pointer', fontSize: '13px',
            }}
          >
            Deposit
          </button>
        )}

        {isProcessing && (
          <span style={{ fontSize: '13px', color: '#2563eb' }}>Processing...</span>
        )}

        {status === 'error' && (
          <div style={{ textAlign: 'right' }}>
            <p style={{ fontSize: '12px', color: '#dc2626' }}>{error}</p>
            <button
              onClick={deposit}
              style={{
                marginTop: '4px', padding: '4px 10px', backgroundColor: '#fee2e2',
                color: '#991b1b', border: 'none', borderRadius: '4px', cursor: 'pointer', fontSize: '12px',
              }}
            >
              Retry
            </button>
          </div>
        )}
      </div>
    </div>
  );
}
```

## Notification badge

A compact badge showing how many settlements need attention (e.g., awaiting deposits). Useful in navigation bars or sidebars.

```tsx theme={null}
// settlement-badge.tsx
import { useSettlements } from '@keystoneos/react';

interface SettlementBadgeProps {
  state: string;
  label: string;
}

export function SettlementBadge({ state, label }: SettlementBadgeProps) {
  const { total, isLoading } = useSettlements({ state, limit: 1, offset: 0 });

  if (isLoading || total === 0) return null;

  return (
    <div style={{ display: 'inline-flex', alignItems: 'center', gap: '6px' }}>
      <span>{label}</span>
      <span style={{
        display: 'inline-flex', alignItems: 'center', justifyContent: 'center',
        minWidth: '20px', height: '20px', borderRadius: '9999px',
        backgroundColor: '#ef4444', color: '#fff',
        fontSize: '11px', fontWeight: 700, padding: '0 6px',
      }}>
        {total}
      </span>
    </div>
  );
}

// Usage in a navigation bar:
function Sidebar() {
  return (
    <nav>
      <a href="/settlements">
        <SettlementBadge state="AWAITING_DEPOSITS" label="Awaiting Deposits" />
      </a>
      <a href="/settlements?state=COMPLIANCE_CHECKING">
        <SettlementBadge state="COMPLIANCE_CHECKING" label="Compliance Review" />
      </a>
    </nav>
  );
}
```

## Putting it all together

Here is a complete settlement detail page combining the stepper, timeline, and deposit tracker.

```tsx theme={null}
// settlement-detail-page.tsx
import { useSettlement, isTerminalState } from '@keystoneos/react';
import { ProgressStepper } from './progress-stepper';
import { EventTimeline } from './event-timeline';
import { DepositTracker } from './deposit-tracker';

interface SettlementDetailPageProps {
  settlementId: string;
}

export function SettlementDetailPage({ settlementId }: SettlementDetailPageProps) {
  const { settlement, isLoading, error } = useSettlement(settlementId);

  if (isLoading) return <div style={{ padding: '24px' }}>Loading settlement...</div>;
  if (error) return <div style={{ padding: '24px', color: '#dc2626' }}>Error: {error}</div>;
  if (!settlement) return <div style={{ padding: '24px' }}>Settlement not found.</div>;

  const isTerminal = isTerminalState(settlement.state);

  return (
    <div style={{ padding: '24px', maxWidth: '900px' }}>
      {/* Header */}
      <div style={{ marginBottom: '24px' }}>
        <h1 style={{ fontSize: '24px', fontWeight: 700 }}>
          {settlement.external_reference ?? `Settlement ${settlement.id.slice(0, 8)}`}
        </h1>
        <p style={{ fontSize: '14px', color: '#6b7280', marginTop: '4px' }}>
          Template: {settlement.template_slug} | Created: {new Date(settlement.created_at).toLocaleString()}
        </p>
      </div>

      {/* Two-column layout */}
      <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '32px' }}>
        {/* Left column: Progress + Deposits */}
        <div style={{ display: 'flex', flexDirection: 'column', gap: '24px' }}>
          <ProgressStepper settlementId={settlementId} />
          <DepositTracker settlementId={settlementId} />

          {/* Parties summary */}
          <div>
            <h3 style={{ fontSize: '16px', fontWeight: 700, marginBottom: '8px' }}>Parties</h3>
            {settlement.parties.map((party, i) => (
              <div key={i} style={{ padding: '8px 0', borderBottom: '1px solid #f3f4f6' }}>
                <p style={{ fontSize: '14px', fontWeight: 600 }}>
                  {party.name ?? party.external_reference}
                  <span style={{ fontWeight: 400, color: '#6b7280', marginLeft: '8px' }}>
                    ({party.role})
                  </span>
                </p>
                <p style={{ fontSize: '12px', fontFamily: 'monospace', color: '#9ca3af' }}>
                  {party.wallet_address}
                </p>
              </div>
            ))}
          </div>
        </div>

        {/* Right column: Event timeline */}
        <EventTimeline settlementId={settlementId} />
      </div>
    </div>
  );
}
```

## Auto-refresh behavior

The `useSettlement` hook subscribes to real-time updates automatically:

* While the settlement is in a non-terminal state, it polls for updates
* When a state change is detected, the settlement object updates and all components re-render
* Once the settlement reaches a terminal state (`FINALIZED`, `ROLLED_BACK`, `TIMED_OUT`), polling stops

Use `isTerminalState(settlement.state)` to conditionally render UI - for example, hiding the deposit tracker once the settlement finalizes.

```tsx theme={null}
import { isTerminalState } from '@keystoneos/react';

// Stop showing action buttons when settlement is done
if (isTerminalState(settlement.state)) {
  return <FinalizedView settlement={settlement} />;
}
```

## Next steps

<CardGroup cols={2}>
  <Card title="Settlement Dashboard" icon="table" href="/elements/guides/settlement-dashboard">
    Build a list view to monitor all settlements.
  </Card>

  <Card title="Custody Integration" icon="vault" href="/elements/guides/custody-integration">
    Route deposits through Fireblocks or BitGo.
  </Card>

  <Card title="useSettlement Reference" icon="code" href="/hooks/use-settlement">
    Full API reference for the useSettlement hook.
  </Card>

  <Card title="State Machine" icon="diagram-project" href="/concepts/state-machine">
    Understand settlement states and transitions.
  </Card>
</CardGroup>
