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

# Interactive trade walkthrough

> Navigate through an asset-to-asset trade. Click each step to see the matching API call and visual representation side by side.

export const TRADE_FLOW = {
  phases: [{
    name: "Quote",
    accent: "#3b82f6",
    badge: "bg-blue-500/20 text-blue-300"
  }, {
    name: "Settle",
    accent: "#35B13D",
    badge: "bg-[#35B13D]/20 text-[#35B13D]"
  }],
  steps: [{
    id: "pick-accounts",
    phase: "Quote",
    title: "Select accounts",
    screen: ({dark, onBack, onNext, wizardState, setWizardState}) => <TradeAccountPickerScreen dark={dark} onBack={onBack} onNext={onNext} wizardState={wizardState} setWizardState={setWizardState} />
  }, {
    id: "create-quote",
    phase: "Quote",
    title: "Create quote",
    screen: ({dark, onBack, onNext, wizardState}) => {
      const fromAsset = wizardState?.fromAccount?.asset ?? "USD";
      const toAsset = wizardState?.toAsset ?? "BTC";
      const fromAmount = wizardState?.amount || "100.00";
      const RATES = {
        USD: {
          USD: 1,
          EUR: 0.926,
          BTC: 0.0000095,
          ETH: 0.000244,
          XRP: 0.385
        },
        EUR: {
          USD: 1.08,
          EUR: 1,
          BTC: 0.0000103,
          ETH: 0.000264,
          XRP: 0.416
        },
        BTC: {
          USD: 105000,
          EUR: 97200,
          BTC: 1,
          ETH: 25.63,
          XRP: 40385
        },
        ETH: {
          USD: 4100,
          EUR: 3796,
          BTC: 0.039,
          ETH: 1,
          XRP: 1578
        },
        XRP: {
          USD: 2.60,
          EUR: 2.41,
          BTC: 0.0000248,
          ETH: 0.000634,
          XRP: 1
        }
      };
      const rate = fromAsset !== toAsset ? (RATES[fromAsset] ?? ({}))[toAsset] ?? 1 : 1;
      const raw = parseFloat(fromAmount) * rate;
      const toAmount = raw >= 1000 ? raw.toFixed(2) : raw >= 1 ? raw.toFixed(4) : parseFloat(raw.toPrecision(6)).toString();
      const rateStr = rate >= 1000 ? `1 ${fromAsset} = ${rate.toFixed(2)} ${toAsset}` : rate >= 1 ? `1 ${fromAsset} = ${rate.toFixed(4)} ${toAsset}` : `1 ${fromAsset} = ${parseFloat(rate.toPrecision(4))} ${toAsset}`;
      return <TradeQuoteReviewScreen dark={dark} fromAsset={fromAsset} fromAmount={fromAmount} toAsset={toAsset} toAmount={toAmount} rate={rateStr} sendLabel={`From ${wizardState?.fromAccount?.label ?? "My USD Account"}`} receiveLabel={`To My ${toAsset} Account`} onBack={onBack} onNext={onNext} />;
    }
  }, {
    id: "commit-transaction",
    phase: "Settle",
    title: "Commit transaction",
    autoAdvanceMs: 3000,
    screen: ({dark}) => <TradeProcessingScreen dark={dark} mode="trade" />
  }, {
    id: "settled",
    phase: "Settle",
    title: "Trade settled",
    screen: ({dark, wizardState}) => {
      const fromAsset = wizardState?.fromAccount?.asset ?? "USD";
      const toAsset = wizardState?.toAsset ?? "BTC";
      const fromAmount = wizardState?.amount || "100.00";
      const RATES = {
        USD: {
          USD: 1,
          EUR: 0.926,
          BTC: 0.0000095,
          ETH: 0.000244,
          XRP: 0.385
        },
        EUR: {
          USD: 1.08,
          EUR: 1,
          BTC: 0.0000103,
          ETH: 0.000264,
          XRP: 0.416
        },
        BTC: {
          USD: 105000,
          EUR: 97200,
          BTC: 1,
          ETH: 25.63,
          XRP: 40385
        },
        ETH: {
          USD: 4100,
          EUR: 3796,
          BTC: 0.039,
          ETH: 1,
          XRP: 1578
        },
        XRP: {
          USD: 2.60,
          EUR: 2.41,
          BTC: 0.0000248,
          ETH: 0.000634,
          XRP: 1
        }
      };
      const rate = fromAsset !== toAsset ? (RATES[fromAsset] ?? ({}))[toAsset] ?? 1 : 1;
      const raw = parseFloat(fromAmount) * rate;
      const toAmount = raw >= 1000 ? raw.toFixed(2) : raw >= 1 ? raw.toFixed(4) : parseFloat(raw.toPrecision(6)).toString();
      return <TradeCompletedScreen dark={dark} fromAsset={fromAsset} fromAmount={fromAmount} toAsset={toAsset} toAmount={toAmount} />;
    }
  }]
};

export const assetsResponse = {
  assets: [{
    code: "USD",
    name: "US Dollar",
    type: "fiat",
    status: "live"
  }, {
    code: "EUR",
    name: "Euro",
    type: "fiat",
    status: "live"
  }, {
    code: "BTC",
    name: "Bitcoin",
    type: "crypto",
    status: "live"
  }, {
    code: "ETH",
    name: "Ethereum",
    type: "crypto",
    status: "live"
  }, {
    code: "XRP",
    name: "XRP",
    type: "crypto",
    status: "live"
  }]
};

export const assetsTabs = [{
  label: "cURL",
  code: `curl -X GET "https://api.enterprise.uphold.com/core/assets" \\
  -H "Authorization: Bearer <YOUR_API_KEY>"`
}, {
  label: "JavaScript",
  code: `const response = await fetch("https://api.enterprise.uphold.com/core/assets", {
  method: "GET",
  headers: {
    "Authorization": "Bearer <YOUR_API_KEY>",
  },
});

const data = await response.json();`
}, {
  label: "Python",
  code: `import requests

response = requests.request(
  "GET",
  "https://api.enterprise.uphold.com/core/assets",
  headers={
    "Authorization": "Bearer <YOUR_API_KEY>",
  }
)

data = response.json()`
}, {
  label: "Java",
  code: `import java.net.http.*;
import java.net.URI;

var client = HttpClient.newHttpClient();
var req = HttpRequest.newBuilder()
    .uri(URI.create("https://api.enterprise.uphold.com/core/assets"))
    .header("Authorization", "Bearer <YOUR_API_KEY>")
    .method("GET", HttpRequest.BodyPublishers.noBody())
    .build();

var response = client.send(req, HttpResponse.BodyHandlers.ofString());
var body = response.body();`
}];

export const accountsResponse = {
  accounts: [{
    id: "11de2405-1f1f-4a6e-8a39-acd97dc7f3ec",
    label: "My USD account",
    asset: "USD",
    balance: {
      total: "250.00",
      available: "250.00"
    }
  }, {
    id: "c392a0de-5fc6-49a7-96e1-e4b3f9ef5b27",
    label: "My BTC account",
    asset: "BTC",
    balance: {
      total: "0.0015",
      available: "0.0015"
    }
  }, {
    id: "7a8b9c0d-1e2f-3a4b-5c6d-7e8f9a0b1c2d",
    label: "My XRP account",
    asset: "XRP",
    balance: {
      total: "120.50",
      available: "120.50"
    }
  }],
  pagination: {
    first: "https://api.enterprise.sandbox.uphold.com/core/accounts?page=1&perPage=10"
  }
};

export const accountsTabs = [{
  label: "cURL",
  code: `curl -X GET "https://api.enterprise.uphold.com/core/accounts?perPage=10" \\
  -H "Authorization: Bearer <YOUR_API_KEY>"`
}, {
  label: "JavaScript",
  code: `const response = await fetch("https://api.enterprise.uphold.com/core/accounts?perPage=10", {
  method: "GET",
  headers: {
    "Authorization": "Bearer <YOUR_API_KEY>",
  },
});

const data = await response.json();`
}, {
  label: "Python",
  code: `import requests

response = requests.request(
  "GET",
  "https://api.enterprise.uphold.com/core/accounts?perPage=10",
  headers={
    "Authorization": "Bearer <YOUR_API_KEY>",
  }
)

data = response.json()`
}, {
  label: "Java",
  code: `import java.net.http.*;
import java.net.URI;

var client = HttpClient.newHttpClient();
var req = HttpRequest.newBuilder()
    .uri(URI.create("https://api.enterprise.uphold.com/core/accounts?perPage=10"))
    .header("Authorization", "Bearer <YOUR_API_KEY>")
    .method("GET", HttpRequest.BodyPublishers.noBody())
    .build();

var response = client.send(req, HttpResponse.BodyHandlers.ofString());
var body = response.body();`
}];

export const RampWFooter = ({dark}) => <div className={`flex items-center justify-center gap-2.5 px-5 pt-4 pb-5 flex-shrink-0 text-sm ${dark ? "text-white/55" : "text-[#0E1525]/50"}`}>
    <RampLeafLogo />
    <div className={`w-px h-3.5 ${dark ? "bg-white/20" : "bg-[#0E1525]/15"}`} />
    <span>
      Powered by{" "}
      <strong style={{
  color: dark ? "#ffffff" : "#0E1525"
}}>Uphold</strong>
    </span>
  </div>;

export const RampWTopBar = ({onBack, title, dark}) => <div className="flex items-center gap-3 px-5 pt-4 pb-3">
    <button onClick={onBack} className={`w-10 h-10 rounded-full border-0 flex items-center justify-center cursor-pointer ${dark ? "bg-white/10 text-white" : "bg-black/5 text-[#0E1525]"}`}>
      <svg className="w-5 h-5" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.2" strokeLinecap="round" strokeLinejoin="round">
        <polyline points="15 18 9 12 15 6" />
      </svg>
    </button>
    {title && <div className={`flex-1 text-center pr-10 text-[17px] font-semibold ${dark ? "text-white" : "text-[#0E1525]"}`}>
        {title}
      </div>}
  </div>;

export const RampLeafLogo = () => <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 14 18" className="w-3.5 h-[18px]">
    <path fill="#35B13D" fillRule="evenodd" clipRule="evenodd" d="M11.622 9.986a9.52 9.52 0 0 1-1.536 2.343c.427-1.588.221-3.64-.702-5.631a9.941 9.941 0 0 0-1.866-2.741c1.163-.89 2.373-1.181 3.308-.771.6.262 1.062.8 1.337 1.555.521 1.43.319 3.39-.541 5.245Zm-9.744 0C1.018 8.131.815 6.17 1.337 4.741c.275-.755.737-1.293 1.337-1.555.935-.41 2.145-.12 3.308.77a9.924 9.924 0 0 0-1.866 2.742c-.924 1.992-1.13 4.043-.703 5.631a9.506 9.506 0 0 1-1.535-2.343Zm5.744 4.02c-.269.117-.562.178-.872.182a2.224 2.224 0 0 1-.872-.183c-1.801-.789-2.165-3.95-.796-6.905A8.948 8.948 0 0 1 6.75 4.642 8.926 8.926 0 0 1 8.417 7.1c1.37 2.953 1.006 6.115-.795 6.904Zm2.445-11.999C9 1.965 7.837 2.397 6.75 3.261 5.663 2.398 4.5 1.964 3.433 2.006a5.903 5.903 0 0 1 3.316-1.005c1.226 0 2.377.367 3.318 1.006Zm3.098 2.42v-.003l-.003-.006a.02.02 0 0 1-.001-.005l-.004-.01C12.255 1.77 9.684 0 6.749 0 3.807 0 1.23 1.778.334 4.424c-.001.004-.001.009-.003.013-.595 1.66-.38 3.882.58 5.952C2.226 13.223 4.61 15.19 6.72 15.19h.06c2.11 0 4.495-1.968 5.808-4.8.962-2.075 1.177-4.302.577-5.963Zm-4.61 12.321a6.517 6.517 0 0 1-1.757.25h-.084a6.53 6.53 0 0 1-1.768-.253.533.533 0 0 0-.657.337.497.497 0 0 0 .356.623 7.64 7.64 0 0 0 2.07.295h.083c.674 0 1.367-.098 2.056-.291a.497.497 0 0 0 .357-.623.533.533 0 0 0-.656-.338Z" />
  </svg>;

export const IcoArrowDownTT = () => <svg className="w-5 h-5" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.2" strokeLinecap="round" strokeLinejoin="round">
    <line x1="12" y1="5" x2="12" y2="19" />
    <polyline points="5 12 12 19 19 12" />
  </svg>;

export const IcoCheck = () => <svg className="w-7 h-7" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round" strokeLinejoin="round">
    <polyline points="20 6 9 17 4 12" />
  </svg>;

export const TradeCompletedScreen = ({dark = true, fromAsset = "USD", fromAmount = "100.00", toAsset = "BTC", toAmount = "0.00154883", webhookBadge = null, assetColors = {
  BTC: "#F7931A",
  ETH: "#627EEA",
  EUROC: "#2775CA",
  USDC: "#2775CA",
  USD: "#35B13D"
}, completionHeading = "Trade completed!", sentLabel = "You sent", receivedLabel = "You received"}) => {
  const accent = dark ? "#ffffff" : "#0E1116";
  const bg = dark ? "#0B0D10" : "#FAFAF7";
  const textPrimary = dark ? "#ffffff" : "#0E1525";
  const textMuted = dark ? "rgba(255,255,255,0.5)" : "rgba(14,21,37,0.5)";
  const rowBg = dark ? "rgba(255,255,255,0.06)" : "rgba(0,0,0,0.05)";
  return <div className="flex-1 flex flex-col min-h-0" style={{
    backgroundColor: bg
  }}>
      <div className="flex-1 flex flex-col items-center justify-center px-6 text-center gap-5">
        <div className="w-20 h-20 rounded-full flex items-center justify-center" style={{
    background: dark ? "rgba(255,255,255,0.07)" : "rgba(0,0,0,0.06)",
    color: "#35B13D"
  }}>
          <IcoCheck />
        </div>
        <div className="text-2xl font-extrabold tracking-tight" style={{
    color: textPrimary
  }}>
          {completionHeading}
        </div>
        <div className="w-full rounded-2xl p-4 flex flex-col gap-3" style={{
    background: rowBg
  }}>
          <div className="flex justify-between text-sm">
            <span style={{
    color: textMuted
  }}>{sentLabel}</span>
            <span className="font-semibold" style={{
    color: textPrimary
  }}>
              {fromAmount} {fromAsset}
            </span>
          </div>
          <div className="flex justify-between text-sm">
            <span style={{
    color: textMuted
  }}>{receivedLabel}</span>
            <span className="font-semibold" style={{
    color: "#35B13D"
  }}>
              {toAmount} {toAsset}
            </span>
          </div>
        </div>
      </div>
      {webhookBadge && <div className="px-5 pb-2">{webhookBadge}</div>}
    </div>;
};

export const TradeProcessingScreen = ({dark = true, mode = "trade", webhookBadge = null, processingMessage = "We are processing your transaction", sendingMessage = "Sending..."}) => {
  const accent = dark ? "#ffffff" : "#0E1116";
  const bg = dark ? "#0E1116" : "#FAFAF7";
  const textPrimary = dark ? "#ffffff" : "#0E1525";
  const textMuted = dark ? "rgba(255,255,255,0.5)" : "rgba(14,21,37,0.5)";
  const label = mode === "send" ? sendingMessage : processingMessage;
  const sub = mode === "send" ? "Crediting recipient…" : "Waiting for confirmation…";
  return <div className="flex-1 flex flex-col min-h-0 items-center justify-center" style={{
    backgroundColor: bg
  }}>
      <div className="flex flex-col items-center gap-4 px-8 text-center">
        <div className="w-12 h-12 rounded-full border-2 animate-spin" style={{
    borderColor: dark ? "rgba(255,255,255,0.1)" : "rgba(0,0,0,0.1)",
    borderTopColor: accent
  }} />
        <div className="text-lg font-semibold" style={{
    color: textPrimary
  }}>
          {label}
        </div>
        <div className="text-sm" style={{
    color: textMuted
  }}>
          {sub}
        </div>
        {webhookBadge && <div className="mt-2">{webhookBadge}</div>}
      </div>
    </div>;
};

export const TradeQuoteReviewScreen = ({dark = true, fromAsset = "USD", fromAmount = "100.00", toAsset = "BTC", toAmount = "0.00154883", rate = "1 USD = 0.0000155 BTC", onBack, onNext, assetColors = {
  BTC: "#F7931A",
  ETH: "#627EEA",
  EUROC: "#2775CA",
  USDC: "#2775CA",
  USD: "#35B13D"
}, sendLabel = "You send", receiveLabel = "You receive", expiresLabel = "Expires in", screenTitle = "Transaction preview", buttonLabel = "Confirm", liveQuote = true}) => {
  const assetColor = a => assetColors[a] ?? "#888";
  const badge = a => <span style={{
    display: "inline-flex",
    alignItems: "center",
    gap: 4,
    fontSize: 13,
    fontWeight: 700,
    padding: "2px 10px 2px 6px",
    borderRadius: 999,
    background: `${assetColor(a)}22`,
    color: assetColor(a)
  }}>
      <img src={`https://cdn.uphold.com/assets/${a}.svg`} alt={a} width={16} height={16} onError={e => {
    e.currentTarget.style.display = "none";
  }} style={{
    display: "block",
    borderRadius: "50%"
  }} />
      {a}
    </span>;
  const bg = dark ? "#0E1116" : "#FAFAF7";
  const textPrimary = dark ? "#ffffff" : "#0E1525";
  const textMuted = dark ? "rgba(255,255,255,0.5)" : "rgba(14,21,37,0.5)";
  const rowBg = dark ? "rgba(255,255,255,0.05)" : "rgba(0,0,0,0.05)";
  const dividerColor = dark ? "rgba(255,255,255,0.1)" : "rgba(14,21,37,0.1)";
  const [countdown, setCountdown] = useState(15);
  const [displayToAmount, setDisplayToAmount] = useState(toAmount);
  const [justRefreshed, setJustRefreshed] = useState(false);
  useEffect(() => {
    if (!liveQuote) return;
    const dec = toAmount.includes(".") ? toAmount.split(".")[1].length : 0;
    const unit = Math.pow(10, -dec);
    const steps = [3, -5, 7, -2, 4, -8, 6, -3, 5, -7, 2, -4, 8, -6, 3];
    let tick = 0;
    let cd = 15;
    let cur = parseFloat(toAmount);
    const id = setInterval(() => {
      cd -= 1;
      if (cd <= 0) {
        const delta = steps[tick % steps.length] * unit;
        cur = Math.max(0, cur + delta);
        setDisplayToAmount(cur.toFixed(dec));
        setJustRefreshed(true);
        setTimeout(() => setJustRefreshed(false), 1200);
        tick += 1;
        cd = 15;
      }
      setCountdown(cd);
    }, 1000);
    return () => clearInterval(id);
  }, []);
  return <div className="flex-1 flex flex-col min-h-0" style={{
    backgroundColor: bg
  }}>
      <RampWTopBar dark={dark} title={screenTitle} onBack={onBack} />
      <div className="flex-1 px-5 pt-2 flex flex-col gap-3 overflow-hidden">
        <div className="rounded-2xl p-4 flex flex-col gap-1" style={{
    background: rowBg
  }}>
          <div className="text-xs" style={{
    color: textMuted
  }}>
            {sendLabel}
          </div>
          <div className="flex items-baseline justify-between">
            <span className="text-3xl font-bold" style={{
    color: textPrimary
  }}>
              {fromAmount}
            </span>
            {badge(fromAsset)}
          </div>
        </div>
        <div className="flex items-center justify-center" style={{
    color: textMuted
  }}>
          <IcoArrowDownTT />
        </div>
        <div className="rounded-2xl p-4 flex flex-col gap-1" style={{
    background: rowBg
  }}>
          <div className="text-xs" style={{
    color: textMuted
  }}>
            {receiveLabel}
          </div>
          <div className="flex items-baseline justify-between">
            <span className="text-3xl font-bold" style={{
    color: textPrimary
  }}>
              {liveQuote ? displayToAmount : toAmount}
            </span>
            {badge(toAsset)}
          </div>
        </div>
        <div style={{
    borderTop: `1px solid ${dividerColor}`
  }} />
        <div className="flex flex-col gap-2">
          <div className="flex justify-between text-xs" style={{
    color: textMuted
  }}>
            <span>Rate</span>
            <span style={{
    color: textPrimary
  }}>{rate}</span>
          </div>
          {liveQuote && <div className="flex items-center justify-between text-xs" style={{
    color: textMuted
  }}>
              <span className="flex items-center gap-1.5">
                <svg style={{
    width: 12,
    height: 12,
    animation: "spin 1.2s linear infinite",
    flexShrink: 0
  }} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round">
                  <path d="M21 12a9 9 0 1 1-6.219-8.56" />
                </svg>
                {justRefreshed ? <span style={{
    color: "#35B13D",
    fontWeight: 600
  }}>
                    Rate updated
                  </span> : <span>Refreshes in {countdown}s</span>}
              </span>
              <span style={{
    color: textPrimary
  }}>Live quote</span>
            </div>}
        </div>
        <button onClick={onNext} className="w-full py-4 rounded-2xl text-sm font-semibold mt-auto mb-4" style={{
    backgroundColor: "#35B13D",
    color: "#fff",
    animation: "pulse-ring 2s ease-in-out infinite"
  }}>
          {buttonLabel}
        </button>
      </div>
      <RampWFooter dark={dark} />
    </div>;
};

export const TradeAccountPickerScreen = ({dark = true, wizardState, setWizardState, onBack, onNext, accounts = [{
  id: '11de2405-1f1f-4a6e-8a39-acd97dc7f3ec',
  asset: 'USD',
  label: 'My USD account',
  balance: '250.00'
}, {
  id: 'c392a0de-5fc6-49a7-96e1-e4b3f9ef5b27',
  asset: 'BTC',
  label: 'My BTC account',
  balance: '0.0015'
}, {
  id: '7a8b9c0d-1e2f-3a4b-5c6d-7e8f9a0b1c2d',
  asset: 'XRP',
  label: 'My XRP account',
  balance: '120.50'
}], assetColors = {
  USD: '#35B13D',
  EUR: '#2775CA',
  BTC: '#F7931A',
  ETH: '#627EEA',
  XRP: '#346AA9',
  EUROC: '#2775CA',
  USDC: '#2775CA'
}}) => {
  const [subView, setSubView] = useState('main');
  const [localAmount, setLocalAmount] = useState(wizardState?.amount ?? '');
  const fromAccount = wizardState?.fromAccount ?? null;
  const toAsset = wizardState?.toAsset ?? null;
  const dispatch = detail => {
    if (typeof window !== 'undefined') {
      window.dispatchEvent(new CustomEvent('fw:subpanel', {
        detail
      }));
    }
  };
  const bg = dark ? '#0E1116' : '#F4F5F7';
  const accent = dark ? '#ffffff' : '#0E1116';
  const textPrimary = dark ? 'rgba(255,255,255,0.9)' : 'rgba(14,21,37,0.9)';
  const textMuted = dark ? 'rgba(255,255,255,0.5)' : 'rgba(14,21,37,0.5)';
  const rowBg = dark ? 'rgba(255,255,255,0.05)' : 'rgba(0,0,0,0.05)';
  const amountNum = parseFloat(localAmount);
  const maxBalance = fromAccount ? parseFloat(fromAccount.balance) : Infinity;
  const amountError = localAmount !== '' && (isNaN(amountNum) || amountNum <= 0 || amountNum > maxBalance);
  const canProceed = fromAccount && toAsset && localAmount !== '' && !amountError && !isNaN(amountNum) && amountNum > 0;
  const handleNext = () => {
    setWizardState(prev => ({
      ...prev,
      amount: localAmount
    }));
    onNext();
  };
  const AssetIcon = ({asset, color}) => <div className="w-10 h-10 rounded-full flex-shrink-0" style={{
    background: `${color}22`,
    position: 'relative',
    display: 'flex',
    alignItems: 'center',
    justifyContent: 'center'
  }}>
      <span style={{
    position: 'absolute',
    fontSize: 10,
    fontWeight: 'bold',
    color
  }}>{asset}</span>
      <img src={`https://cdn.uphold.com/assets/${asset}.svg`} alt={asset} width={24} height={24} onError={e => {
    e.currentTarget.style.display = 'none';
  }} style={{
    display: 'block',
    position: 'relative',
    zIndex: 1
  }} />
    </div>;
  const Chevron = () => <svg className="w-4 h-4 flex-shrink-0" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" style={{
    color: textMuted
  }}>
      <polyline points="9 18 15 12 9 6" />
    </svg>;
  if (subView === 'accounts') {
    return <AccountListScreen dark={dark} title="Select source account" accounts={accounts.map(a => ({
      ...a,
      balance: `${a.balance} ${a.asset}`
    }))} onBack={() => {
      setSubView('main');
      dispatch('main');
    }} onSelectAccount={item => {
      const account = accounts.find(a => a.asset === item.asset) ?? item;
      setWizardState(prev => ({
        ...prev,
        fromAccount: account
      }));
      setSubView('main');
      dispatch('main');
    }} />;
  }
  if (subView === 'assets') {
    return <AssetListScreen dark={dark} title="Select destination asset" excludeAsset={fromAccount?.asset} onBack={() => {
      setSubView('main');
      dispatch('main');
    }} onSelectAsset={asset => {
      setWizardState(prev => ({
        ...prev,
        toAsset: asset
      }));
      setSubView('main');
      dispatch('main');
    }} />;
  }
  return <div className="flex-1 flex flex-col min-h-0" style={{
    backgroundColor: bg
  }}>
      <RampWTopBar dark={dark} title="Trade" onBack={onBack} />
      <div className="flex-1 px-5 pt-2 flex flex-col gap-3 overflow-hidden">
        <div className="rounded-2xl p-4 flex items-center gap-3 cursor-pointer" style={{
    background: rowBg
  }} onClick={() => {
    setSubView('accounts');
    dispatch('accounts');
  }}>
          {fromAccount ? <AssetIcon asset={fromAccount.asset} color={assetColors[fromAccount.asset] || accent} /> : <div className="w-10 h-10 rounded-full flex-shrink-0 flex items-center justify-center" style={{
    border: `2px dashed ${textMuted}`
  }}>
              <svg width="16" height="16" viewBox="0 0 16 16" fill="none"><path d="M8 2v12M2 8h12" stroke={textMuted} strokeWidth="2" strokeLinecap="round" /></svg>
            </div>}
          <div className="flex-1 min-w-0">
            <div className="text-xs mb-0.5" style={{
    color: textMuted
  }}>From</div>
            {fromAccount ? <>
                <div className="text-sm font-medium" style={{
    color: textPrimary
  }}>{fromAccount.label}</div>
                <div className="text-xs mt-0.5" style={{
    color: textMuted
  }}>{fromAccount.balance} {fromAccount.asset}</div>
              </> : <div className="text-sm" style={{
    color: textMuted
  }}>Select account</div>}
          </div>
          <Chevron />
        </div>

        <div className="rounded-2xl p-4 flex items-center gap-3 cursor-pointer" style={{
    background: rowBg
  }} onClick={() => {
    setSubView('assets');
    dispatch('assets');
  }}>
          {toAsset ? <AssetIcon asset={toAsset} color={assetColors[toAsset] || accent} /> : <div className="w-10 h-10 rounded-full flex-shrink-0 flex items-center justify-center" style={{
    border: `2px dashed ${textMuted}`
  }}>
              <svg width="16" height="16" viewBox="0 0 16 16" fill="none"><path d="M8 2v12M2 8h12" stroke={textMuted} strokeWidth="2" strokeLinecap="round" /></svg>
            </div>}
          <div className="flex-1 min-w-0">
            <div className="text-xs mb-0.5" style={{
    color: textMuted
  }}>To</div>
            {toAsset ? <div className="text-sm font-medium" style={{
    color: textPrimary
  }}>{toAsset}</div> : <div className="text-sm" style={{
    color: textMuted
  }}>Select asset</div>}
          </div>
          <Chevron />
        </div>

        <div className="rounded-2xl p-4 flex flex-col gap-1" style={{
    background: rowBg
  }}>
          <div className="text-xs" style={{
    color: textMuted
  }}>Amount</div>
          <div className="flex items-baseline gap-2">
            <input type="text" inputMode="decimal" value={localAmount} onChange={e => setLocalAmount(e.target.value)} placeholder="0.00" className="text-2xl font-semibold bg-transparent border-none outline-none w-full" style={{
    color: amountError ? '#ef4444' : textPrimary,
    fontFamily: 'inherit'
  }} />
            {fromAccount && <span className="text-base font-normal flex-shrink-0" style={{
    color: textMuted
  }}>{fromAccount.asset}</span>}
          </div>
          {fromAccount && !amountError && <div className="text-xs" style={{
    color: textMuted
  }}>Max: {fromAccount.balance} {fromAccount.asset}</div>}
          {amountError && <div className="text-xs" style={{
    color: '#ef4444'
  }}>
              {amountNum > maxBalance ? 'Exceeds available balance' : 'Enter a valid amount'}
            </div>}
        </div>

        <button onClick={canProceed ? handleNext : undefined} className="w-full py-4 rounded-2xl text-sm font-semibold mt-auto mb-4" style={{
    backgroundColor: canProceed ? '#35B13D' : dark ? 'rgba(255,255,255,0.1)' : 'rgba(0,0,0,0.1)',
    color: canProceed ? '#fff' : textMuted,
    animation: canProceed ? 'pulse-ring 2s ease-in-out infinite' : 'none',
    cursor: canProceed ? 'pointer' : 'not-allowed'
  }}>
          Get quote
        </button>
      </div>
      <RampWFooter dark={dark} />
    </div>;
};

export const AssetListScreen = ({dark = true, title = 'Select asset', onBack, onSelectAsset, excludeAsset, assetColors = {
  USD: '#35B13D',
  EUR: '#2775CA',
  BTC: '#F7931A',
  ETH: '#627EEA',
  XRP: '#346AA9'
}}) => {
  const bg = dark ? '#0E1116' : '#F4F5F7';
  const accent = dark ? '#ffffff' : '#0E1116';
  const textPrimary = dark ? 'rgba(255,255,255,0.9)' : 'rgba(14,21,37,0.9)';
  const textMuted = dark ? 'rgba(255,255,255,0.5)' : 'rgba(14,21,37,0.5)';
  const rowBg = dark ? 'rgba(255,255,255,0.05)' : 'rgba(0,0,0,0.05)';
  const assets = [{
    asset: 'USD',
    label: 'US Dollar'
  }, {
    asset: 'EUR',
    label: 'Euro'
  }, {
    asset: 'BTC',
    label: 'Bitcoin'
  }, {
    asset: 'ETH',
    label: 'Ethereum'
  }, {
    asset: 'XRP',
    label: 'XRP'
  }].filter(a => a.asset !== excludeAsset);
  return <div className="flex-1 flex flex-col min-h-0" style={{
    backgroundColor: bg
  }}>
      <RampWTopBar dark={dark} title={title} onBack={onBack} />
      <div className="flex-1 px-5 pt-2 flex flex-col gap-2 overflow-hidden">
        {assets.map(({asset, label}) => {
    const color = assetColors[asset] || accent;
    return <div key={asset} className="rounded-2xl px-4 py-3 flex items-center gap-3" style={{
      background: rowBg,
      cursor: onSelectAsset ? 'pointer' : 'default'
    }} onClick={onSelectAsset ? () => onSelectAsset(asset) : undefined}>
              <div className="w-10 h-10 rounded-full flex-shrink-0" style={{
      background: `${color}22`,
      position: 'relative',
      display: 'flex',
      alignItems: 'center',
      justifyContent: 'center'
    }}>
                <span style={{
      position: 'absolute',
      fontSize: 10,
      fontWeight: 'bold',
      color
    }}>{asset}</span>
                <img src={`https://cdn.uphold.com/assets/${asset}.svg`} alt={asset} width={24} height={24} onError={e => {
      e.currentTarget.style.display = 'none';
    }} style={{
      display: 'block',
      position: 'relative',
      zIndex: 1
    }} />
              </div>
              <div className="flex-1 min-w-0">
                <div className="text-sm font-medium" style={{
      color: textPrimary
    }}>{asset}</div>
                <div className="text-xs mt-0.5" style={{
      color: textMuted
    }}>{label}</div>
              </div>
              <svg className="w-4 h-4 flex-shrink-0" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" style={{
      color: textMuted
    }}>
                <polyline points="9 18 15 12 9 6" />
              </svg>
            </div>;
  })}
      </div>
      <RampWFooter dark={dark} />
    </div>;
};

export const AccountListScreen = ({dark = true, onBack, onNext, onSelectAccount, title = "Accounts", accounts = [{
  asset: "USD",
  label: "My USD account",
  balance: "250.00 USD"
}, {
  asset: "BTC",
  label: "My BTC account",
  balance: "0.0015 BTC"
}, {
  asset: "XRP",
  label: "My XRP account",
  balance: "120.50 XRP"
}], buttonLabel = "Continue", assetColors = {
  USD: "#35B13D",
  BTC: "#F7931A",
  XRP: "#346AA9"
}}) => {
  const bg = dark ? "#0E1116" : "#F4F5F7";
  const accent = dark ? "#ffffff" : "#0E1116";
  const textPrimary = dark ? "rgba(255,255,255,0.9)" : "rgba(14,21,37,0.9)";
  const textMuted = dark ? "rgba(255,255,255,0.5)" : "rgba(14,21,37,0.5)";
  const rowBg = dark ? "rgba(255,255,255,0.05)" : "rgba(0,0,0,0.05)";
  return <div className="flex-1 flex flex-col min-h-0" style={{
    backgroundColor: bg
  }}>
      <RampWTopBar dark={dark} title={title} onBack={onBack} />
      <div className="flex-1 px-5 pt-2 flex flex-col gap-2 overflow-hidden">
        {accounts.map(account => {
    const {asset, label, balance} = account;
    const color = assetColors[asset] || accent;
    const balanceDisplay = typeof balance === 'number' ? `${balance} ${asset}` : balance;
    return <div key={asset} className="rounded-2xl px-4 py-3 flex items-center gap-3" style={{
      background: rowBg,
      cursor: onSelectAccount ? 'pointer' : 'default'
    }} onClick={onSelectAccount ? () => onSelectAccount(account) : undefined}>
              <div className="w-10 h-10 rounded-full flex-shrink-0" style={{
      background: `${color}22`,
      position: "relative",
      display: "flex",
      alignItems: "center",
      justifyContent: "center"
    }}>
                <span style={{
      position: "absolute",
      fontSize: 10,
      fontWeight: "bold",
      color
    }}>
                  {asset}
                </span>
                <img src={`https://cdn.uphold.com/assets/${asset}.svg`} alt={asset} width={24} height={24} onError={e => {
      e.currentTarget.style.display = "none";
    }} style={{
      display: "block",
      position: "relative",
      zIndex: 1
    }} />
              </div>
              <div className="flex-1 min-w-0">
                <div className="text-sm font-medium" style={{
      color: textPrimary
    }}>
                  {label}
                </div>
                <div className="text-xs mt-0.5" style={{
      color: textMuted
    }}>
                  {balanceDisplay}
                </div>
              </div>
            </div>;
  })}
        {!onSelectAccount && <button onClick={onNext} className="w-full py-4 rounded-2xl text-sm font-semibold mt-auto mb-4" style={{
    backgroundColor: "#35B13D",
    color: "#fff",
    animation: "pulse-ring 2s ease-in-out infinite"
  }}>
            {buttonLabel}
          </button>}
      </div>
      <RampWFooter dark={dark} />
    </div>;
};

export const SubPanelSwitch = ({children}) => {
  const [panel, setPanel] = useState('main');
  useEffect(() => {
    const handler = e => setPanel(e.detail ?? 'main');
    window.addEventListener('fw:subpanel', handler);
    return () => window.removeEventListener('fw:subpanel', handler);
  }, []);
  const kids = React.Children.toArray(children).filter(k => typeof k !== 'string' || k.trim().length > 0);
  const idx = panel === 'accounts' ? 1 : panel === 'assets' ? 2 : 0;
  return (kids[idx] ?? kids[0]) ?? null;
};

export const WizardCodeGroup = ({tabs}) => {
  const [activeTab, setActiveTab] = useState(0);
  const [dark, setDark] = useState(true);
  const [copied, setCopied] = useState(false);
  useEffect(() => {
    const update = () => setDark(document.documentElement.classList.contains("dark"));
    update();
    const obs = new MutationObserver(update);
    obs.observe(document.documentElement, {
      attributeFilter: ["class"]
    });
    return () => obs.disconnect();
  }, []);
  useEffect(() => {
    if (!copied) return;
    const t = setTimeout(() => setCopied(false), 1400);
    return () => clearTimeout(t);
  }, [copied]);
  const activeCode = tabs[activeTab]?.code ?? "";
  const bg = dark ? "#111318" : "#f5f6f8";
  const borderColor = dark ? "rgba(255,255,255,0.08)" : "rgba(0,0,0,0.1)";
  const tabBarBg = dark ? "rgba(255,255,255,0.03)" : "rgba(0,0,0,0.03)";
  const textMuted = dark ? "rgba(255,255,255,0.4)" : "rgba(0,0,0,0.4)";
  const codeColor = dark ? "rgba(255,255,255,0.85)" : "rgba(0,0,0,0.75)";
  const handleCopy = async () => {
    if (!navigator?.clipboard?.writeText) return;
    try {
      await navigator.clipboard.writeText(activeCode);
      setCopied(true);
    } catch (_) {}
  };
  return <div className="not-prose" style={{
    borderRadius: 12,
    overflow: "hidden",
    border: `1px solid ${borderColor}`,
    marginBottom: 16
  }}>
      <div style={{
    display: "flex",
    alignItems: "center",
    background: tabBarBg,
    borderBottom: `1px solid ${borderColor}`,
    paddingLeft: 4
  }}>
        {tabs.map((tab, i) => <button key={tab.label} onClick={() => setActiveTab(i)} style={{
    background: "transparent",
    border: "none",
    borderBottom: `2px solid ${i === activeTab ? "#35B13D" : "transparent"}`,
    color: i === activeTab ? "#35B13D" : textMuted,
    fontSize: 12,
    fontWeight: i === activeTab ? 600 : 400,
    padding: "8px 12px",
    cursor: "pointer",
    fontFamily: "inherit",
    outline: "none",
    flexShrink: 0
  }}>
            {tab.label}
          </button>)}
        <button onClick={handleCopy} style={{
    marginLeft: "auto",
    marginRight: 8,
    padding: 6,
    background: "transparent",
    border: "none",
    cursor: "pointer",
    color: textMuted,
    display: "flex",
    alignItems: "center"
  }}>
          {copied ? <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round" strokeLinejoin="round">
              <polyline points="20 6 9 17 4 12" />
            </svg> : <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round">
              <rect x="9" y="9" width="11" height="11" rx="2" />
              <path d="M5 15V5a2 2 0 0 1 2-2h10" />
            </svg>}
        </button>
      </div>
      <div style={{
    background: bg,
    padding: "14px 16px",
    overflowX: "auto"
  }}>
        <pre style={{
    margin: 0,
    fontSize: 12.5,
    fontFamily: "monospace",
    lineHeight: 1.6,
    color: codeColor,
    whiteSpace: "pre"
  }}>
          {activeCode}
        </pre>
      </div>
    </div>;
};

export const WizardCopyButton = ({text}) => {
  const [copied, setCopied] = useState(false);
  useEffect(() => {
    if (!copied) return;
    const t = setTimeout(() => setCopied(false), 1400);
    return () => clearTimeout(t);
  }, [copied]);
  const handleClick = async () => {
    if (!navigator?.clipboard?.writeText) return;
    try {
      await navigator.clipboard.writeText(text);
      setCopied(true);
    } catch (_) {}
  };
  return <button onClick={handleClick} className="p-1.5 rounded hover:bg-zinc-100 dark:hover:bg-white/10 transition-colors text-zinc-500 dark:text-white/50 hover:text-zinc-800 dark:hover:text-white/90 border-0 cursor-pointer bg-transparent">
      {copied ? <svg className="w-4 h-4" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round" strokeLinejoin="round">
          <polyline points="20 6 9 17 4 12" />
        </svg> : <svg className="w-4 h-4" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round">
          <rect x="9" y="9" width="11" height="11" rx="2" />
          <path d="M5 15V5a2 2 0 0 1 2-2h10" />
        </svg>}
    </button>;
};

export const WizardJsonBlock = ({label, data, dark = true}) => {
  const [copied, setCopied] = useState(false);
  useEffect(() => {
    if (!copied) return;
    const t = setTimeout(() => setCopied(false), 1400);
    return () => clearTimeout(t);
  }, [copied]);
  const handleCopy = async text => {
    if (!navigator?.clipboard?.writeText) return;
    try {
      await navigator.clipboard.writeText(text);
      setCopied(true);
    } catch (_) {}
  };
  const tokenizeJson = str => {
    const tokens = [];
    let cursor = 0;
    while (cursor < str.length) {
      const slice = str.slice(cursor);
      const wsMatch = slice.match(/^\s+/);
      if (wsMatch) {
        tokens.push({
          type: "whitespace",
          text: wsMatch[0]
        });
        cursor += wsMatch[0].length;
        continue;
      }
      const stringMatch = slice.match(/^"(?:[^"\\]|\\.)*"/);
      if (stringMatch) {
        const text = stringMatch[0];
        const after = str.slice(cursor + text.length).replace(/^\s*/, "");
        tokens.push({
          type: after.startsWith(":") ? "key" : "string",
          text
        });
        cursor += text.length;
        continue;
      }
      const numberMatch = slice.match(/^-?\d+(?:\.\d+)?(?:[eE][+-]?\d+)?/);
      if (numberMatch) {
        tokens.push({
          type: "number",
          text: numberMatch[0]
        });
        cursor += numberMatch[0].length;
        continue;
      }
      const boolMatch = slice.match(/^(true|false)/);
      if (boolMatch) {
        tokens.push({
          type: "boolean",
          text: boolMatch[0]
        });
        cursor += boolMatch[0].length;
        continue;
      }
      if (slice.startsWith("null")) {
        tokens.push({
          type: "null",
          text: "null"
        });
        cursor += 4;
        continue;
      }
      if (("{[]:,}").includes(str[cursor])) {
        tokens.push({
          type: "punctuation",
          text: str[cursor]
        });
        cursor += 1;
        continue;
      }
      tokens.push({
        type: "unknown",
        text: str[cursor]
      });
      cursor += 1;
    }
    return tokens;
  };
  const TOKEN_COLORS = dark ? {
    key: "#79c0ff",
    string: "#f78c6c",
    number: "#79c0ff",
    boolean: "#ffd700",
    null: "rgba(255,255,255,0.5)",
    punctuation: "rgba(255,255,255,0.7)",
    whitespace: null
  } : {
    key: "#0550ae",
    string: "#b56959",
    number: "#0550ae",
    boolean: "#b5890e",
    null: "rgba(0,0,0,0.4)",
    punctuation: "rgba(0,0,0,0.5)",
    whitespace: null
  };
  const json = data != null ? JSON.stringify(data, null, 2) : null;
  return <div className="not-prose flex flex-col gap-1.5">
      <div className="flex items-center justify-between px-1">
        <span style={{
    fontSize: 10,
    color: dark ? "rgba(255,255,255,0.35)" : "rgba(0,0,0,0.4)",
    fontFamily: "monospace",
    textTransform: "uppercase",
    letterSpacing: "0.12em"
  }}>
          {label}
        </span>
        {json != null && <button onClick={() => handleCopy(json)} style={{
    padding: 6,
    background: "transparent",
    border: "none",
    cursor: "pointer",
    color: dark ? "rgba(255,255,255,0.4)" : "rgba(0,0,0,0.4)",
    display: "flex",
    alignItems: "center"
  }}>
            {copied ? <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round" strokeLinejoin="round"><polyline points="20 6 9 17 4 12" /></svg> : <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round"><rect x="9" y="9" width="11" height="11" rx="2" /><path d="M5 15V5a2 2 0 0 1 2-2h10" /></svg>}
          </button>}
      </div>
      <div className="rounded-xl p-4 max-h-[360px] overflow-auto" style={{
    backgroundColor: dark ? "#111318" : "#f5f6f8",
    border: dark ? "1px solid rgba(255,255,255,0.06)" : "1px solid rgba(0,0,0,0.08)"
  }}>
        <pre style={{
    fontSize: 13,
    fontFamily: "monospace",
    lineHeight: 1.6,
    margin: 0,
    whiteSpace: "pre"
  }}>
          {json != null ? tokenizeJson(json).map((token, i) => <span key={`${token.type}-${i}`} style={{
    color: TOKEN_COLORS[token.type] ?? (dark ? "rgba(255,255,255,0.85)" : "rgba(0,0,0,0.7)")
  }}>
                {token.text}
              </span>) : <span style={{
    color: dark ? "rgba(255,255,255,0.3)" : "rgba(0,0,0,0.3)",
    fontStyle: "italic"
  }}>
              — no payload —
            </span>}
        </pre>
      </div>
    </div>;
};

export const WizardWebhookBadge = ({event, payload, dark = true, payloadLabel = "Payload"}) => {
  const [open, setOpen] = useState(false);
  return <div className="flex flex-col gap-2">
      <span onClick={() => setOpen(o => !o)} className="inline-flex items-center gap-1.5 px-2.5 py-1 rounded-full bg-purple-500/20 text-purple-300 text-xs font-mono cursor-pointer select-none hover:bg-purple-500/30">
        <svg className="w-3.5 h-3.5" viewBox="0 0 24 24" fill="currentColor">
          <path d="M13 2L4 14h7l-1 8 9-12h-7l1-8z" />
        </svg>
        {event}
      </span>
      {open && <WizardJsonBlock label={payloadLabel} data={payload} dark={dark} />}
    </div>;
};

export const WizardStatusBar = ({light}) => <div className="h-11 flex items-center justify-between px-7 flex-shrink-0 text-[15px] font-semibold" style={{
  color: light ? "#0E1525" : "white",
  backgroundColor: light ? "white" : "black"
}}>
    <span>9:41</span>
    <div className="flex items-center gap-1.5">
      <svg className="w-[18px] h-3" viewBox="0 0 18 12" fill="currentColor">
        <rect x="0" y="8" width="3" height="4" rx="0.5" />
        <rect x="5" y="5" width="3" height="7" rx="0.5" />
        <rect x="10" y="2" width="3" height="10" rx="0.5" />
        <rect x="15" y="0" width="3" height="12" rx="0.5" />
      </svg>
      <svg className="w-4 h-3" viewBox="0 0 16 12" fill="none" stroke="currentColor" strokeWidth="1.4">
        <path d="M1,5 Q8,-1 15,5" />
        <path d="M3.5,7 Q8,3 12.5,7" />
        <circle cx="8" cy="9.5" r="1" fill="currentColor" />
      </svg>
      <svg className="w-[26px] h-3" viewBox="0 0 26 12" fill="none">
        <rect x="0.5" y="0.5" width="22" height="11" rx="2.5" stroke="currentColor" strokeOpacity="0.5" />
        <rect x="2" y="2" width="19" height="8" rx="1.5" fill="currentColor" />
        <rect x="23" y="4" width="2" height="4" rx="1" fill="currentColor" fillOpacity="0.5" />
      </svg>
    </div>
  </div>;

export const WizardPhoneShell = ({children, bgClass = "bg-zinc-50 dark:bg-[#0E1116]", lightStatus = false, borderClass = "border-zinc-200 dark:border-white/10"}) => <div className={`relative w-[320px] sm:w-[360px] h-[720px] rounded-[40px] sm:rounded-[44px] overflow-hidden border-2 flex flex-col font-sans shadow-[0_48px_96px_-28px_rgba(11,15,30,0.45),0_8px_20px_-6px_rgba(11,15,30,0.18)] ${bgClass} ${borderClass}`}>
    <div className={`absolute top-2 left-1/2 -translate-x-1/2 w-[110px] h-7 rounded-full z-10 ${lightStatus ? "bg-black" : "bg-white/20"}`} />
    <WizardStatusBar light={lightStatus} />
    <div className="flex-1 flex flex-col min-h-0 relative">{children}</div>
    <div className="absolute bottom-2 left-1/2 -translate-x-1/2 w-[134px] h-[5px] rounded-full z-20" style={{
  background: lightStatus ? "rgba(14,21,37,0.2)" : "rgba(255,255,255,0.4)"
}} />
  </div>;

export const FlowWizard = ({flow = {
  phases: [],
  steps: []
}, children}) => {
  const UpholdGreen = "#35B13D";
  const phaseColors = Object.fromEntries(flow.phases.map(p => [p.name, {
    accent: p.accent,
    badge: p.badge
  }]));
  const FALLBACK_PHASE = {
    accent: UpholdGreen,
    badge: "bg-zinc-100 dark:bg-white/10 text-zinc-500 dark:text-white/50"
  };
  const steps = flow.steps;
  const [stepIndex, setStepIndex] = useState(0);
  const [isDark, setIsDark] = useState(true);
  const [wizardState, setWizardState] = useState({
    fromAccount: null,
    toAsset: null,
    amount: ''
  });
  const step = steps[stepIndex];
  const stepChildren = React.Children.toArray(children);
  const isSkipped = s => s?.skip?.(wizardState) === true;
  const findStep = (from, dir) => {
    let i = from + dir;
    while (i >= 0 && i < steps.length && isSkipped(steps[i])) i += dir;
    return i >= 0 && i < steps.length ? i : null;
  };
  const visibleSteps = steps.filter(s => !isSkipped(s));
  const visibleStepIndex = visibleSteps.indexOf(step);
  useEffect(() => {
    const update = () => setIsDark(document.documentElement.classList.contains("dark"));
    update();
    const obs = new MutationObserver(update);
    obs.observe(document.documentElement, {
      attributeFilter: ["class"]
    });
    return () => obs.disconnect();
  }, []);
  useEffect(() => {
    if (!step?.autoAdvanceMs) return;
    const next = findStep(stepIndex, 1);
    if (next == null) return;
    const t = setTimeout(() => setStepIndex(next), step.autoAdvanceMs);
    return () => clearTimeout(t);
  }, [stepIndex]);
  if (!step) return null;
  const StepScreen = step.screen;
  const {accent: phaseAccent, badge: phaseBadge} = phaseColors[step.phase] ?? FALLBACK_PHASE;
  return <div className="not-prose w-full overflow-hidden">
      <div className="flex items-center justify-between gap-3 mb-4 flex-wrap">
        <div className="flex items-center gap-2.5">
          <span className={`${phaseBadge} text-xs font-semibold px-2.5 py-0.5 rounded-full`}>
            {step.phase}
          </span>
          <span className="text-sm font-semibold text-zinc-900 dark:text-white">
            {step.title}
          </span>
        </div>
        <span className="text-xs text-zinc-400 dark:text-white/40">
          Step {visibleStepIndex + 1} of {visibleSteps.length}
        </span>
      </div>

      <div className="w-full h-1 bg-zinc-200 dark:bg-white/10 rounded-full mb-4">
        <div className="h-full rounded-full transition-all duration-300" style={{
    width: `${((visibleStepIndex + 1) / visibleSteps.length * 100).toFixed(0)}%`,
    backgroundColor: UpholdGreen
  }} />
      </div>

      <div className="flex items-center gap-1 mb-5 flex-wrap">
        {(() => {
    const seenPhases = [];
    steps.forEach(s => {
      if (!seenPhases.includes(s.phase)) seenPhases.push(s.phase);
    });
    const els = [];
    seenPhases.forEach((phase, pi) => {
      const phaseSteps = steps.map((s, idx) => ({
        ...s,
        globalIndex: idx
      })).filter(s => s.phase === phase);
      const accent = (phaseColors[phase] ?? FALLBACK_PHASE).accent;
      const isActivePhase = phaseSteps.some(s => s.globalIndex === stepIndex);
      if (pi > 0) els.push(<div key={`sep-${phase}`} style={{
        width: 1,
        height: 20,
        background: isDark ? "rgba(255,255,255,0.1)" : "rgba(0,0,0,0.1)",
        margin: "0 6px",
        flexShrink: 0
      }} />);
      els.push(<span key={`lbl-${phase}`} style={{
        fontSize: 9,
        fontWeight: 700,
        letterSpacing: "0.1em",
        textTransform: "uppercase",
        color: isActivePhase ? accent : isDark ? "rgba(255,255,255,0.2)" : "rgba(0,0,0,0.3)",
        marginRight: 4,
        flexShrink: 0
      }}>
                {phase}
              </span>);
      phaseSteps.filter(s => !isSkipped(s)).forEach(s => {
        const isDone = s.globalIndex < stepIndex;
        const isCurrent = s.globalIndex === stepIndex;
        els.push(<button key={s.id} onClick={() => setStepIndex(s.globalIndex)} title={s.title} style={{
          width: 26,
          height: 26,
          borderRadius: "50%",
          border: "none",
          cursor: "pointer",
          fontSize: 11,
          fontWeight: 700,
          flexShrink: 0,
          display: "flex",
          alignItems: "center",
          justifyContent: "center",
          transition: "all 0.15s",
          background: isCurrent ? accent : isDone ? `${accent}26` : isDark ? "rgba(255,255,255,0.06)" : "rgba(0,0,0,0.06)",
          color: isCurrent ? "white" : isDone ? accent : isDark ? "rgba(255,255,255,0.3)" : "rgba(0,0,0,0.4)",
          boxShadow: isCurrent ? `0 0 0 3px ${accent}26` : "none",
          outline: "none"
        }}>
                  {isDone ? "✓" : s.globalIndex + 1}
                </button>);
      });
    });
    return els;
  })()}
      </div>

      <div className="w-full flex flex-col lg:flex-row gap-6 items-center lg:items-start">
        <div className="flex justify-center lg:flex-shrink-0">
          <WizardPhoneShell lightStatus={!isDark}>
            <StepScreen dark={isDark} onBack={findStep(stepIndex, -1) != null ? () => setStepIndex(findStep(stepIndex, -1)) : undefined} onNext={findStep(stepIndex, 1) != null ? () => setStepIndex(findStep(stepIndex, 1)) : undefined} wizardState={wizardState} setWizardState={setWizardState} />
          </WizardPhoneShell>
        </div>
        <div className="wizard-step-pane w-full lg:sticky lg:top-6 lg:flex-1 lg:min-w-0 lg:h-[720px] lg:overflow-y-auto prose dark:prose-invert max-w-none">
          {stepChildren[stepIndex] ?? null}
        </div>
      </div>

      <div className="flex items-center justify-between mt-5">
        <button onClick={() => setStepIndex(findStep(stepIndex, -1))} disabled={findStep(stepIndex, -1) == null} className={`flex items-center gap-2 px-4 py-2 rounded-xl text-sm font-medium border-0 cursor-pointer disabled:opacity-30 disabled:cursor-not-allowed bg-[${UpholdGreen}] hover:bg-[#2d9e34] text-white transition-colors`}>
          <svg className="w-4 h-4" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.2" strokeLinecap="round" strokeLinejoin="round">
            <polyline points="15 18 9 12 15 6" />
          </svg>
          Back
        </button>
        {findStep(stepIndex, 1) != null && <button onClick={() => setStepIndex(findStep(stepIndex, 1))} className={`flex items-center gap-2 px-4 py-2 rounded-xl text-sm font-medium border-0 cursor-pointer bg-[${UpholdGreen}] hover:bg-[#2d9e34] text-white font-semibold transition-colors`}>
            Next
            <svg className="w-4 h-4" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.2" strokeLinecap="round" strokeLinejoin="round">
              <polyline points="9 18 15 12 9 6" />
            </svg>
          </button>}
      </div>
    </div>;
};

<FlowWizard flow={TRADE_FLOW}>
  <div>
    <SubPanelSwitch>
      <div>
        Tap **From** to pick the source account, then **To** to choose the destination asset. The API panel updates to show the relevant endpoint as you interact.

        The trade amount cannot exceed the available balance of the selected account.
      </div>

      <div>
        <EndpointHeader method="GET" path="/core/accounts?perPage=10" />

        <WizardCodeGroup tabs={accountsTabs} />

        <WizardJsonBlock label="Response" data={accountsResponse} />
      </div>

      <div>
        <EndpointHeader method="GET" path="/core/assets" />

        <WizardCodeGroup tabs={assetsTabs} />

        <WizardJsonBlock label="Response" data={assetsResponse} />
      </div>
    </SubPanelSwitch>
  </div>

  <div>
    <EndpointHeader method="POST" path="/core/transactions/quote" />

    <CodeGroup>
      ```bash cURL theme={null}
      curl -X POST "https://api.enterprise.uphold.com/core/transactions/quote" \
        -H "Authorization: Bearer <YOUR_API_KEY>" \
        -H "Content-Type: application/json" \
        -d '{
        "origin": {
          "type": "account",
          "id": "11de2405-1f1f-4a6e-8a39-acd97dc7f3ec"
        },
        "destination": {
          "type": "account",
          "id": "c392a0de-5fc6-49a7-96e1-e4b3f9ef5b27"
        },
        "denomination": {
          "asset": "USD",
          "amount": "100",
          "target": "origin"
        }
      }'
      ```

      ```js JavaScript theme={null}
      const response = await fetch("https://api.enterprise.uphold.com/core/transactions/quote", {
        method: "POST",
        headers: {
          "Authorization": "Bearer <YOUR_API_KEY>",
          "Content-Type": "application/json",
        },
        body: JSON.stringify({
          origin: { type: "account", id: "11de2405-1f1f-4a6e-8a39-acd97dc7f3ec" },
          destination: { type: "account", id: "c392a0de-5fc6-49a7-96e1-e4b3f9ef5b27" },
          denomination: { asset: "USD", amount: "100", target: "origin" },
        }),
      });

      const data = await response.json();
      ```

      ```python Python theme={null}
      import requests

      response = requests.request(
        "POST",
        "https://api.enterprise.uphold.com/core/transactions/quote",
        headers={
          "Authorization": "Bearer <YOUR_API_KEY>",
          "Content-Type": "application/json",
        },
        json={
          "origin": {"type": "account", "id": "11de2405-1f1f-4a6e-8a39-acd97dc7f3ec"},
          "destination": {"type": "account", "id": "c392a0de-5fc6-49a7-96e1-e4b3f9ef5b27"},
          "denomination": {"asset": "USD", "amount": "100", "target": "origin"},
        }
      )

      data = response.json()
      ```

      ```java Java theme={null}
      import java.net.http.*;
      import java.net.URI;

      var client = HttpClient.newHttpClient();
      var req = HttpRequest.newBuilder()
          .uri(URI.create("https://api.enterprise.uphold.com/core/transactions/quote"))
          .header("Authorization", "Bearer <YOUR_API_KEY>")
          .header("Content-Type", "application/json")
          .method("POST", HttpRequest.BodyPublishers.ofString("""
              {
                "origin": {
                  "type": "account",
                  "id": "11de2405-1f1f-4a6e-8a39-acd97dc7f3ec"
                },
                "destination": {
                  "type": "account",
                  "id": "c392a0de-5fc6-49a7-96e1-e4b3f9ef5b27"
                },
                "denomination": {
                  "asset": "USD",
                  "amount": "100",
                  "target": "origin"
                }
              }"""))
          .build();

      var response = client.send(req, HttpResponse.BodyHandlers.ofString());
      var body = response.body();
      ```
    </CodeGroup>

    ```json Response theme={null}
    {
      "quote": {
        "id": "43c0c58d-6fbd-464a-bf2b-f38fbfebb4ab",
        "expiresAt": "2026-05-29T11:47:05.109Z",
        "origin": { "asset": "USD", "amount": "100.00", "rate": "64564.596" },
        "destination": { "asset": "BTC", "amount": "0.00154883", "rate": "0.0000155" },
        "denomination": {
          "asset": "USD",
          "amount": "100.00",
          "rate": "1.00",
          "target": "origin"
        },
        "fees": []
      }
    }
    ```
  </div>

  <div>
    <EndpointHeader method="POST" path="/core/transactions" />

    <CodeGroup>
      ```bash cURL theme={null}
      curl -X POST "https://api.enterprise.uphold.com/core/transactions" \
        -H "Authorization: Bearer <YOUR_API_KEY>" \
        -H "Content-Type: application/json" \
        -d '{
        "quoteId": "43c0c58d-6fbd-464a-bf2b-f38fbfebb4ab",
        "metadata": {
          "externalId": 123
        }
      }'
      ```

      ```js JavaScript theme={null}
      const response = await fetch("https://api.enterprise.uphold.com/core/transactions", {
        method: "POST",
        headers: {
          "Authorization": "Bearer <YOUR_API_KEY>",
          "Content-Type": "application/json",
        },
        body: JSON.stringify({
          quoteId: "43c0c58d-6fbd-464a-bf2b-f38fbfebb4ab",
          metadata: { externalId: 123 },
        }),
      });

      const data = await response.json();
      ```

      ```python Python theme={null}
      import requests

      response = requests.request(
        "POST",
        "https://api.enterprise.uphold.com/core/transactions",
        headers={
          "Authorization": "Bearer <YOUR_API_KEY>",
          "Content-Type": "application/json",
        },
        json={
          "quoteId": "43c0c58d-6fbd-464a-bf2b-f38fbfebb4ab",
          "metadata": {"externalId": 123},
        }
      )

      data = response.json()
      ```

      ```java Java theme={null}
      import java.net.http.*;
      import java.net.URI;

      var client = HttpClient.newHttpClient();
      var req = HttpRequest.newBuilder()
          .uri(URI.create("https://api.enterprise.uphold.com/core/transactions"))
          .header("Authorization", "Bearer <YOUR_API_KEY>")
          .header("Content-Type", "application/json")
          .method("POST", HttpRequest.BodyPublishers.ofString("""
              {
                "quoteId": "43c0c58d-6fbd-464a-bf2b-f38fbfebb4ab",
                "metadata": {
                  "externalId": 123
                }
              }"""))
          .build();

      var response = client.send(req, HttpResponse.BodyHandlers.ofString());
      var body = response.body();
      ```
    </CodeGroup>

    ```json Response theme={null}
    {
      "transaction": {
        "id": "43c0c58d-6fbd-464a-bf2b-f38fbfebb4ab",
        "status": "processing",
        "origin": { "asset": "USD", "amount": "100.00" },
        "destination": { "asset": "BTC", "amount": "0.00154883" }
      }
    }
    ```
  </div>

  <div>
    <Info>
      **Webhook event:** `core.transaction.status-changed`
    </Info>

    ```json Webhook payload theme={null}
    {
      "id": "c2d3e4f5-a6b7-4c8d-9e0f-a1b2c3d4e5f6",
      "type": "core.transaction.status-changed",
      "createdAt": "2026-05-29T11:46:53.737Z",
      "data": {
        "transaction": {
          "id": "b1c2d3e4-f5a6-4b7c-8d9e-f0a1b2c3d4e5",
          "status": "completed",
          "origin": {
            "asset": "USD",
            "amount": "100.00",
            "node": {
              "type": "account",
              "id": "11de2405-1f1f-4a6e-8a39-acd97dc7f3ec"
            }
          },
          "destination": {
            "asset": "BTC",
            "amount": "0.00154883",
            "node": {
              "type": "account",
              "id": "c392a0de-5fc6-49a7-96e1-e4b3f9ef5b27"
            }
          },
          "denomination": {
            "asset": "USD",
            "amount": "100.00",
            "rate": "1.00",
            "target": "origin"
          },
          "fees": [],
          "quotedAt": "2026-05-29T11:46:47.494Z",
          "createdAt": "2026-05-29T11:46:53.110Z",
          "updatedAt": "2026-05-29T11:46:53.737Z"
        }
      }
    }
    ```
  </div>
</FlowWizard>

***

For the full prose walkthrough — prerequisite checks, error table, and denomination logic — see the [via REST API](/developer-guides/trade-and-send/trade/via-rest-api) guide.
