> ## 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 sell walkthrough

> Navigate through a crypto → fiat sell flow. Click each step to see the matching API call and visual representation side by side.

export const SELL_FLOW = {
  phases: [{
    name: 'Sell',
    accent: '#35B13D',
    badge: 'bg-[#35B13D]/20 text-[#35B13D]'
  }],
  steps: [{
    id: 'select-crypto',
    phase: 'Sell',
    title: 'Select a crypto to sell',
    screen: ({dark, onBack, onNext, setWizardState}) => <CryptoSellPickerScreen dark={dark} onBack={onBack} onSelectCrypto={crypto => {
      setWizardState(prev => ({
        ...prev,
        fromAsset: crypto
      }));
      onNext();
    }} />
  }, {
    id: 'select-destination',
    phase: 'Sell',
    title: 'Select destination',
    screen: ({dark, onBack, onNext, setWizardState, wizardState}) => <ExternalAccountListScreen dark={dark} onBack={onBack} onNext={onNext} onSelectAccount={account => setWizardState(prev => ({
      ...prev,
      toAccount: account
    }))} title={`Sell ${wizardState?.fromAsset?.code ?? 'crypto'}`} currency="USD" amount="490.00" cta="Continue" accounts={[{
      type: 'card',
      network: 'visa',
      label: 'Personal card',
      last4: '4242',
      selected: true
    }, {
      type: 'bank',
      label: 'Checking ••3344',
      sublabel: 'Chase',
      selected: false
    }]} />
  }, {
    id: 'preview-quote',
    phase: 'Sell',
    title: 'Preview quote',
    screen: ({dark, onNext, wizardState}) => {
      const fromAsset = wizardState?.fromAsset;
      return <QuoteSummaryScreen dark={dark} fromAmount={fromAsset?.balance ?? '0.0049'} fromCurrency={fromAsset?.code ?? 'BTC'} fromMethod="account" toAmount="490.00" toCurrency="USD" onNext={onNext} />;
    }
  }, {
    id: 'confirm-quote',
    phase: 'Sell',
    title: 'Confirm quote',
    autoAdvanceMs: 3000,
    screen: ({dark}) => <RampProcessingScreen dark={dark} label="Processing sell" sub="This usually takes a moment" />
  }, {
    id: 'sell-settled',
    phase: 'Sell',
    title: 'Sell complete',
    screen: ({dark}) => <SuccessScreen dark={dark} mode="sell" amount="490.00" currency="USD" />
  }]
};

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 IcoLock = () => <svg className="w-3 h-3" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
    <rect x="3" y="11" width="18" height="11" rx="2" /><path d="M7 11V7a5 5 0 0 1 10 0v4" />
  </svg>;

export const IcoCardSm = () => <svg className="w-4 h-4" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round">
    <rect x="2" y="5" width="20" height="14" rx="2" /><line x1="2" y1="10" x2="22" y2="10" />
  </svg>;

export const IcoArrowDown = () => <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 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 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 SuccessScreen = ({dark = true, amount, currency, fromAmount, fromCurrency, onDone, mode = 'deposit', buttonLabel}) => {
  const FIAT_SYM = {
    USD: '$',
    EUR: '€',
    GBP: '£'
  };
  const sym = c => FIAT_SYM[c] ?? '';
  const bgStyle = dark ? {
    backgroundColor: '#0B0D10',
    color: 'white'
  } : {
    backgroundColor: '#FAFAF7',
    color: '#0E1525'
  };
  const accent = '#35B13D';
  const fmtAmt = (a, c) => {
    const n = Number(a);
    if (!isFinite(n)) return String(a);
    if (FIAT_SYM[c]) return n.toLocaleString('en-US', {
      minimumFractionDigits: 2,
      maximumFractionDigits: 2
    });
    return String(a);
  };
  const title = mode === 'buy' ? 'Purchase complete' : mode === 'sell' ? 'Payout confirmed' : mode === 'withdraw' ? 'Withdrawal confirmed' : 'Deposit confirmed';
  const rowBg = dark ? 'rgba(255,255,255,0.06)' : 'rgba(0,0,0,0.05)';
  const textMuted = dark ? 'rgba(255,255,255,0.5)' : 'rgba(14,21,37,0.5)';
  return <div className="flex-1 flex flex-col" style={bgStyle}>
      <div className="flex-1 flex flex-col items-center justify-center px-8 text-center">
        <div className={`w-20 h-20 rounded-full flex items-center justify-center mb-6 text-[#35B13D] ${dark ? 'bg-white/[0.07]' : 'bg-black/[0.06]'}`}>
          <IcoCheck />
        </div>
        <div className="text-[26px] font-extrabold tracking-tight mb-3">{title}</div>
        {mode === 'buy' && fromAmount && fromCurrency ? <div className="w-full rounded-2xl p-4 flex flex-col gap-3 mt-1" style={{
    background: rowBg
  }}>
            <div className="flex justify-between text-sm">
              <span style={{
    color: textMuted
  }}>Spent</span>
              <span className="font-semibold">{sym(fromCurrency)}{fmtAmt(fromAmount, fromCurrency)} {fromCurrency}</span>
            </div>
            <div className="flex justify-between text-sm">
              <span style={{
    color: textMuted
  }}>Received</span>
              <span className="font-semibold" style={{
    color: '#35B13D'
  }}>{fmtAmt(amount, currency)} {currency}</span>
            </div>
          </div> : <div className="text-[44px] font-extrabold tracking-tight mb-1 tabular-nums">{sym(currency)}{fmtAmt(amount, currency)}{!FIAT_SYM[currency] ? ` ${currency}` : ''}</div>}
      </div>
    </div>;
};

export const RampProcessingScreen = ({dark = true, label = 'Processing', sub = ''}) => {
  const accent = '#35B13D';
  const bg = dark ? '#0E1116' : '#FAFAF7';
  const textPrimary = dark ? 'text-white' : 'text-[#0E1525]';
  const textMuted = dark ? 'text-white/50' : 'text-[#0E1525]/50';
  return <div className="flex-1 flex flex-col 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 ${textPrimary}`}>{label}</div>
        {sub && <div className={`text-sm ${textMuted}`}>{sub}</div>}
      </div>
    </div>;
};

export const QuoteSummaryScreen = ({dark = true, fromAmount = '500.00', fromCurrency = 'USD', fromMethod = 'card', toAmount = '0.00524', toCurrency = 'BTC', rate = null, fee = null, onNext}) => {
  const accent = '#35B13D';
  const bg = dark ? '#0E1116' : '#FAFAF7';
  const textPrimary = dark ? 'text-white' : 'text-[#0E1525]';
  const textMuted = dark ? 'text-white/50' : 'text-[#0E1525]/50';
  const textFaint = dark ? 'text-white/30' : 'text-[#0E1525]/30';
  const divider = dark ? 'border-white/10' : 'border-[#0E1525]/10';
  const badgeBg = dark ? 'bg-white/10 text-white' : 'bg-black/5 text-[#0E1525]';
  const rowBg = dark ? 'bg-white/5' : 'bg-black/5';
  const methodLabel = fromMethod === 'apple-pay' ? <span className={`text-xs px-2 py-0.5 rounded-full ${badgeBg}`}>Apple Pay</span> : fromMethod === 'google-pay' ? <span className={`text-xs px-2 py-0.5 rounded-full ${badgeBg}`}>G Pay</span> : fromMethod === 'account' ? <span className={`text-xs px-2 py-0.5 rounded-full ${badgeBg}`}>Account</span> : fromMethod === 'bank' ? <span className={`flex items-center gap-1 text-xs px-2 py-0.5 rounded-full ${badgeBg}`}><svg className="w-3.5 h-3.5" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round"><line x1="3" y1="22" x2="21" y2="22" /><line x1="6" y1="18" x2="6" y2="11" /><line x1="10" y1="18" x2="10" y2="11" /><line x1="14" y1="18" x2="14" y2="11" /><line x1="18" y1="18" x2="18" y2="11" /><polygon points="12 2 20 7 4 7" /></svg>Bank</span> : <span className={`flex items-center gap-1 text-xs px-2 py-0.5 rounded-full ${badgeBg}`}><IcoCardSm />Card</span>;
  const [countdown, setCountdown] = useState(15);
  const [displayToAmount, setDisplayToAmount] = useState(toAmount);
  const [justRefreshed, setJustRefreshed] = useState(false);
  useEffect(() => {
    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);
  }, []);
  const CurrencyBadge = ({symbol}) => <span className={`text-sm font-semibold inline-flex items-center gap-1 px-1.5 py-0.5 rounded-full ${badgeBg}`}>
      <img src={`https://cdn.uphold.com/assets/${symbol}.svg`} alt={symbol} width={14} height={14} onError={e => {
    e.currentTarget.style.display = 'none';
  }} style={{
    display: 'block',
    borderRadius: '50%',
    flexShrink: 0
  }} />
      {symbol}
    </span>;
  return <div className="flex-1 flex flex-col min-h-0" style={{
    backgroundColor: bg
  }}>
      <RampWTopBar dark={dark} title="Review" />
      <div className="flex-1 px-5 flex flex-col gap-4 overflow-hidden pt-2">
        <div className={`rounded-2xl p-4 flex flex-col gap-1 ${rowBg}`}>
          <div className={`text-xs ${textMuted}`}>You pay</div>
          <div className="flex items-baseline justify-between">
            <span className={`text-3xl font-bold ${textPrimary}`}>{fromAmount}</span>
            <CurrencyBadge symbol={fromCurrency} />
          </div>
          <div className="mt-1">{methodLabel}</div>
        </div>
        <div className={`flex items-center justify-center ${textFaint}`}><IcoArrowDown /></div>
        <div className={`rounded-2xl p-4 flex flex-col gap-1 ${rowBg}`}>
          <div className={`text-xs ${textMuted}`}>You receive</div>
          <div className="flex items-baseline justify-between">
            <span className={`text-3xl font-bold ${textPrimary}`}>{displayToAmount}</span>
            <CurrencyBadge symbol={toCurrency} />
          </div>
        </div>
        <div className={`border-t ${divider}`} />
        <div className="flex flex-col gap-2.5">
          {rate && <div className={`flex items-center justify-between text-xs ${textMuted}`}><span>Rate</span><span className={textPrimary}>{rate}</span></div>}
          {fee && <div className={`flex items-center justify-between text-xs ${textMuted}`}><span>Network fee</span><span className={textPrimary}>{fee}</span></div>}
          <div className={`flex items-center justify-between text-xs ${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 className="font-semibold text-[#35B13D]">Rate updated</span> : <span>Refreshes in {countdown}s</span>}
            </span>
            <span className={textPrimary}>Live quote</span>
          </div>
        </div>
        <button onClick={onNext} className="w-full py-4 rounded-2xl text-sm font-semibold mt-auto" style={{
    backgroundColor: accent,
    color: '#fff',
    animation: 'pulse-ring 2s ease-in-out infinite'
  }}>Confirm</button>
      </div>
      <RampWFooter dark={dark} />
    </div>;
};

export const ExternalAccountListScreen = ({dark = true, onBack, onNext, onSelectAccount, title = 'New deposit', currency = 'USD', amount = '300.00', cta = 'Continue', accounts = [{
  type: 'card',
  network: 'visa',
  label: 'Personal card',
  last4: '4242',
  selected: true
}, {
  type: 'bank',
  label: 'Checking ••3344',
  sublabel: 'Chase',
  selected: false
}]}) => {
  const bg = dark ? '#0E1116' : '#F4F5F7';
  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 selectedBorder = '#3b82f6';
  const checkBorderUnselected = dark ? 'rgba(255,255,255,0.2)' : 'rgba(0,0,0,0.2)';
  const initIndex = accounts.findIndex(a => a.selected);
  const [selectedIndex, setSelectedIndex] = useState(initIndex >= 0 ? initIndex : 0);
  const BankIcon = () => <svg className="w-5 h-5" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round">
      <line x1="3" y1="22" x2="21" y2="22" /><line x1="6" y1="18" x2="6" y2="11" /><line x1="10" y1="18" x2="10" y2="11" /><line x1="14" y1="18" x2="14" y2="11" /><line x1="18" y1="18" x2="18" y2="11" /><polygon points="12 2 20 7 4 7" />
    </svg>;
  const CheckDot = ({selected}) => <div style={{
    width: 20,
    height: 20,
    borderRadius: '50%',
    border: `2px solid ${selected ? selectedBorder : checkBorderUnselected}`,
    background: selected ? selectedBorder : 'transparent',
    display: 'flex',
    alignItems: 'center',
    justifyContent: 'center',
    flexShrink: 0
  }}>
      {selected && <svg viewBox="0 0 24 24" fill="none" stroke="white" strokeWidth="3" strokeLinecap="round" strokeLinejoin="round" style={{
    width: 10,
    height: 10
  }}><polyline points="20 6 9 17 4 12" /></svg>}
    </div>;
  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-3 overflow-hidden">
        {accounts.map((acct, i) => <div key={i} className="rounded-2xl p-4 flex items-center gap-3" onClick={() => setSelectedIndex(i)} style={{
    background: rowBg,
    cursor: 'pointer',
    ...selectedIndex === i ? {
      border: `1.5px solid ${selectedBorder}`
    } : {}
  }}>
            {acct.type === 'card' ? <div className="w-10 h-10 rounded-full flex-shrink-0 flex items-center justify-center" style={{
    background: 'rgba(26,31,113,0.25)'
  }}>
                <span style={{
    fontSize: 9,
    fontWeight: 800,
    color: '#4a6cf7',
    letterSpacing: '-0.5px'
  }}>{(acct.network ?? 'CARD').toUpperCase()}</span>
              </div> : <div className="w-10 h-10 rounded-full flex-shrink-0 flex items-center justify-center" style={{
    background: dark ? 'rgba(255,255,255,0.08)' : 'rgba(0,0,0,0.06)',
    color: textMuted
  }}>
                <BankIcon />
              </div>}
            <div className="flex-1 min-w-0">
              <div className="text-xs mb-0.5" style={{
    color: textMuted
  }}>{acct.type === 'card' ? 'Credit card' : 'Bank account'}</div>
              <div className="text-sm font-medium" style={{
    color: textPrimary
  }}>{acct.label}</div>
              {(acct.last4 || acct.sublabel) && <div className="text-xs mt-0.5" style={{
    color: textMuted
  }}>
                  {acct.last4 ? `${acct.network ? acct.network.charAt(0).toUpperCase() + acct.network.slice(1) : 'Card'} •••• ${acct.last4}` : acct.sublabel}
                </div>}
            </div>
            <CheckDot selected={selectedIndex === i} />
          </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="text-2xl font-semibold" style={{
    color: textPrimary
  }}>
            {amount}{' '}
            <span className="text-base font-normal" style={{
    color: textMuted
  }}>{currency}</span>
          </div>
        </div>
        <button onClick={() => {
    onSelectAccount?.(accounts[selectedIndex]);
    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'
  }}>
          {cta}
        </button>
      </div>
      <RampWFooter dark={dark} />
    </div>;
};

export const CryptoSellPickerScreen = ({dark = true, onBack, onSelectCrypto}) => {
  const bg = dark ? '#0E1116' : '#FAFAF7';
  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 selectedBorder = '#35B13D';
  const holdings = [{
    code: 'BTC',
    name: 'Bitcoin',
    balance: '0.1',
    value: '$6,451.60'
  }, {
    code: 'ETH',
    name: 'Ethereum',
    balance: '0.25',
    value: '$780.00'
  }];
  const [selectedIndex, setSelectedIndex] = useState(0);
  return <div className="flex-1 flex flex-col min-h-0" style={{
    backgroundColor: bg
  }}>
      <RampWTopBar dark={dark} title="Sell crypto" onBack={onBack} />
      <div className="flex-1 px-5 pt-2 flex flex-col gap-3 overflow-hidden">
        {holdings.map((h, i) => <div key={i} className="rounded-2xl p-4 flex items-center gap-3" onClick={() => setSelectedIndex(i)} style={{
    background: rowBg,
    cursor: 'pointer',
    ...selectedIndex === i ? {
      border: `1.5px solid ${selectedBorder}`
    } : {}
  }}>
            <img src={`https://cdn.uphold.com/assets/${h.code}.svg`} alt={h.code} width={40} height={40} onError={e => {
    e.currentTarget.style.display = 'none';
  }} style={{
    borderRadius: '50%',
    flexShrink: 0,
    display: 'block'
  }} />
            <div className="flex-1 min-w-0">
              <div className="text-sm font-semibold" style={{
    color: textPrimary
  }}>{h.name}</div>
              <div className="text-xs" style={{
    color: textMuted
  }}>{h.balance} {h.code}</div>
            </div>
            <div className="text-right">
              <div className="text-sm font-semibold" style={{
    color: textPrimary
  }}>{h.value}</div>
            </div>
          </div>)}
        <button onClick={() => onSelectCrypto?.(holdings[selectedIndex])} 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'
  }}>
          Continue
        </button>
      </div>
      <RampWFooter 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={SELL_FLOW}>
  <div>
    <EndpointHeader method="GET" path="/core/accounts" />

    <CodeGroup>
      ```bash cURL theme={null}
      curl -X GET "https://api.enterprise.uphold.com/core/accounts" \
        -H "Authorization: Bearer <YOUR_API_KEY>"
      ```

      ```js JavaScript theme={null}
      const response = await fetch("https://api.enterprise.uphold.com/core/accounts", {
        method: "GET",
        headers: {
          "Authorization": "Bearer <YOUR_API_KEY>",
        },
      });

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

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

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

      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/accounts"))
          .header("Authorization", "Bearer <YOUR_API_KEY>")
          .method("GET", HttpRequest.BodyPublishers.noBody())
          .build();

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

    ```json Response theme={null}
    {
      "accounts": [
        {
          "id": "f8c2d1e4-3a7b-4c9d-8e5f-2b1a6d3c7e8f",
          "asset": "BTC",
          "balance": "0.1",
          "status": "ok"
        },
        {
          "id": "9b3d2c1a-7f4e-4d2a-8c5b-1e6f3d2c4b7a",
          "asset": "ETH",
          "balance": "0.25",
          "status": "ok"
        },
        {
          "id": "a00507fe-628c-4f27-ae81-e1c40b2a8fb8",
          "asset": "USD",
          "balance": "0.00",
          "status": "ok"
        }
      ]
    }
    ```

    The account the user selects (`f8c2d1e4` / BTC in this walkthrough) becomes the `origin` of the sell.
  </div>

  <div>
    <EndpointHeader method="GET" path="/core/external-accounts" />

    <CodeGroup>
      ```bash cURL theme={null}
      curl -G "https://api.enterprise.uphold.com/core/external-accounts" \
        -H "Authorization: Bearer <YOUR_API_KEY>" \
        --data-urlencode "feature=withdraw"
      ```

      ```js JavaScript theme={null}
      const params = new URLSearchParams({ feature: 'withdraw' });
      const response = await fetch(
        `https://api.enterprise.uphold.com/core/external-accounts?${params}`,
        { headers: { Authorization: 'Bearer <YOUR_API_KEY>' } }
      );

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

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

      response = requests.get(
        "https://api.enterprise.uphold.com/core/external-accounts",
        headers={"Authorization": "Bearer <YOUR_API_KEY>"},
        params={"feature": "withdraw"},
      )

      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/external-accounts?feature=withdraw"))
          .header("Authorization", "Bearer <YOUR_API_KEY>")
          .GET()
          .build();

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

    ```json Response theme={null}
    {
      "externalAccounts": [
        {
          "id": "aa6e6efa-8d73-497c-8278-0347f459bd68",
          "ownerId": "cd21b26d-35d2-408a-9201-b8fdbef7a604",
          "type": "card",
          "status": "ok",
          "label": "Personal card",
          "asset": "USD",
          "network": "visa",
          "features": ["withdraw"],
          "details": {
            "last4Digits": "4242",
            "expiryDate": { "month": 12, "year": 2030 },
            "octSupport": "supported"
          }
        },
        {
          "id": "bb7f7fgb-9e84-5b8d-9389-1458g560ce79",
          "ownerId": "cd21b26d-35d2-408a-9201-b8fdbef7a604",
          "type": "bank",
          "status": "ok",
          "label": "Checking ••3344",
          "asset": "USD",
          "features": ["withdraw"]
        }
      ]
    }
    ```
  </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": "f8c2d1e4-3a7b-4c9d-8e5f-2b1a6d3c7e8f"
        },
        "destination": {
          "type": "external-account",
          "id": "aa6e6efa-8d73-497c-8278-0347f459bd68"
        },
        "denomination": {
          "asset": "USD",
          "amount": "490.00",
          "target": "destination"
        }
      }'
      ```

      ```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: "f8c2d1e4-3a7b-4c9d-8e5f-2b1a6d3c7e8f" },
          destination: { type: "external-account", id: "aa6e6efa-8d73-497c-8278-0347f459bd68" },
          denomination: { asset: "USD", amount: "490.00", target: "destination" },
        }),
      });

      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": "f8c2d1e4-3a7b-4c9d-8e5f-2b1a6d3c7e8f"},
          "destination": {"type": "external-account", "id": "aa6e6efa-8d73-497c-8278-0347f459bd68"},
          "denomination": {"asset": "USD", "amount": "490.00", "target": "destination"},
        }
      )

      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": "f8c2d1e4-3a7b-4c9d-8e5f-2b1a6d3c7e8f"
                },
                "destination": {
                  "type": "external-account",
                  "id": "aa6e6efa-8d73-497c-8278-0347f459bd68"
                },
                "denomination": {
                  "asset": "USD",
                  "amount": "490.00",
                  "target": "destination"
                }
              }"""))
          .build();

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

    ```json Response theme={null}
    {
      "quote": {
        "id": "a7b8c9d0-e1f2-4a3b-4c5d-e6f7a8b9c0d1",
        "origin": {
          "amount": "0.0049",
          "asset": "BTC",
          "rate": "100000",
          "node": { "type": "account", "id": "f8c2d1e4-3a7b-4c9d-8e5f-2b1a6d3c7e8f" }
        },
        "destination": {
          "amount": "490.00",
          "asset": "USD",
          "rate": "1",
          "node": { "type": "external-account", "id": "aa6e6efa-8d73-497c-8278-0347f459bd68" }
        },
        "denomination": {
          "asset": "USD",
          "amount": "490.00",
          "target": "destination",
          "rate": "1"
        },
        "fees": [],
        "expiresAt": "2026-06-08T12:00:30Z"
      }
    }
    ```
  </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": "a7b8c9d0-e1f2-4a3b-4c5d-e6f7a8b9c0d1"
      }'
      ```

      ```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: "a7b8c9d0-e1f2-4a3b-4c5d-e6f7a8b9c0d1",
        }),
      });

      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": "a7b8c9d0-e1f2-4a3b-4c5d-e6f7a8b9c0d1",
        }
      )

      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": "a7b8c9d0-e1f2-4a3b-4c5d-e6f7a8b9c0d1"
              }"""))
          .build();

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

    ```json Response theme={null}
    {
      "transaction": {
        "id": "c9d0e1f2-a3b4-4c5d-6e7f-a8b9c0d1e2f3",
        "status": "processing",
        "origin": {
          "amount": "0.0049",
          "asset": "BTC",
          "node": { "type": "account", "id": "f8c2d1e4-3a7b-4c9d-8e5f-2b1a6d3c7e8f" }
        },
        "destination": {
          "amount": "490.00",
          "asset": "USD",
          "node": { "type": "external-account", "id": "aa6e6efa-8d73-497c-8278-0347f459bd68" }
        },
        "fees": [],
        "quotedAt": "2026-06-08T12:00:00Z",
        "createdAt": "2026-06-08T12:00:05Z",
        "updatedAt": "2026-06-08T12:00:05Z"
      }
    }
    ```
  </div>

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

    ```json Webhook payload theme={null}
    {
      "id": "e3f4a5b6-c7d8-4e9f-0a1b-c2d3e4f5a6b7",
      "type": "core.transaction.status-changed",
      "createdAt": "2026-06-08T12:00:30Z",
      "data": {
        "transaction": {
          "id": "c9d0e1f2-a3b4-4c5d-6e7f-a8b9c0d1e2f3",
          "status": "completed",
          "origin": {
            "amount": "0.0049",
            "asset": "BTC",
            "node": { "type": "account", "id": "f8c2d1e4-3a7b-4c9d-8e5f-2b1a6d3c7e8f" }
          },
          "destination": {
            "amount": "490.00",
            "asset": "USD",
            "node": { "type": "external-account", "id": "aa6e6efa-8d73-497c-8278-0347f459bd68" }
          },
          "fees": [],
          "quotedAt": "2026-06-08T12:00:00Z",
          "createdAt": "2026-06-08T12:00:05Z",
          "updatedAt": "2026-06-08T12:00:30Z"
        }
      }
    }
    ```

    Show the user a final confirmation — `$490.00 USD` has been credited to their card.
  </div>
</FlowWizard>

***

For the full prose walkthrough — prerequisites, error table, and `octSupport` polling — see the [Sell via the REST API](/developer-guides/buy-and-sell/sell/via-rest-api) guide.
