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

# Gasless Onboarding

> Custom helpers Derive UX uses for onboarding.

export const TransferHelperDiagram = ({flow}) => {
  const COL = {
    funds: 'var(--derive-flow-funds)',
    call: 'var(--derive-flow-call)',
    signal: 'var(--derive-flow-signal)',
    neutral: 'var(--derive-flow-muted)'
  };
  const KN = {
    funds: 'Funds',
    call: 'Call',
    signal: 'Signal'
  };
  const D = {
    dep: {
      CW: 1344,
      CH: 720,
      N: {
        trader: {
          t: 'Depositor',
          k: 'Sender',
          g: [48, 88, 184, 76],
          d: 'Any wallet or exchange that can make a plain transfer. No signature or approval is needed: sending to the deposit address is the deposit.'
        },
        lifi: {
          t: 'Bridge',
          k: 'Cross-chain',
          g: [48, 300, 184, 76],
          d: <>
              Delivers funds from the source chain to the user’s deposit address on Ethereum. It can be one of Derive’s{' '}
              <a href="/getting-started/custom-bridges" style={{
            textDecoration: 'underline',
            textUnderlineOffset: 2
          }}>
                custom bridges
              </a>{' '}
              or a bridge aggregator.
            </>
        },
        source: {
          t: 'Sender',
          k: 'Source chain',
          g: [48, 608, 184, 76],
          d: 'The user’s wallet on another chain. It bridges funds to Ethereum with the deposit address as the recipient.'
        },
        sweeper: {
          t: 'Keeper',
          k: 'Off-chain',
          g: [320, 88, 200, 76],
          d: 'Detects transfers to deposit addresses and, after enough confirmations, calls processSlowDeposit. Only addresses the factory owner allow-lists can process deposits.'
        },
        exchange: {
          t: 'Derive exchange',
          k: 'Off-chain',
          g: [832, 88, 200, 76],
          d: 'Reads the queued deposit and credits the subaccount. The credit is final once its batch is proven.'
        },
        escrow: {
          t: 'Deposit address',
          k: 'Ethereum · contract',
          g: [320, 280, 200, 76],
          d: 'A fixed address per (owner, subaccountId, managerId). It can receive funds before its contract is deployed. Only the factory can move funds out of it. Accepts ERC-20s and native ETH.'
        },
        factory: {
          t: 'SlowDeposit',
          k: 'Ethereum · contract',
          g: [576, 280, 200, 168],
          d: 'Hands out deposit addresses and deposits what lands in them through OnchainActionManager. subaccountId 0 creates a new subaccount under managerId.'
        },
        d_addr: {
          t: 'getDepositAddress',
          k: 'View',
          p: 'factory',
          d: 'Returns the deposit address for (owner, subaccountId, managerId), even before it is deployed.'
        },
        d_process: {
          t: 'processSlowDeposit',
          k: 'Keeper only',
          p: 'factory',
          d: 'Sweeps the deposit address, wraps native ETH to WETH, and deposits through OnchainActionManager.'
        },
        d_recover: {
          t: 'recover',
          k: 'Owner only',
          p: 'factory',
          d: 'Moves funds that can’t be deposited, such as mis-sent tokens, to the factory owner.'
        },
        oam: {
          t: 'OnchainActionManager',
          k: 'Ethereum · contract',
          g: [832, 280, 200, 132],
          d: 'Derive’s L1 entry point for deposits and withdrawals. Any wallet can call it directly; the factory is just another caller.'
        },
        o_deposit: {
          t: 'deposit',
          k: 'Function',
          p: 'oam',
          d: 'Deposits into an existing subaccount. If the deposit can’t be applied, fallbackRecipient receives the funds.'
        },
        o_new: {
          t: 'depositToNewSubaccount',
          k: 'Function',
          p: 'oam',
          d: 'Creates a new subaccount for owner under managerId and deposits into it.'
        },
        vault: {
          t: 'SpotVault',
          k: 'Ethereum · contract',
          g: [1088, 280, 200, 76],
          d: 'Holds deposited tokens. Funds leave only through the WithdrawalOutbox.'
        },
        outbox: {
          t: 'WithdrawalOutbox',
          k: 'Ethereum · contract',
          g: [1088, 440, 200, 76],
          d: 'Registry of supported assets. Depositing an unregistered asset reverts.'
        }
      },
      ROWS: {
        factory: ['d_addr', 'd_process', 'd_recover'],
        oam: ['o_deposit', 'o_new']
      },
      E: [{
        id: 'a1',
        f: 'trader',
        to: 'escrow',
        k: 'funds',
        n: 1,
        pts: [[232, 126], [276, 126], [276, 318], [320, 318]],
        b: [276, 222]
      }, {
        id: 'a1b',
        f: 'lifi',
        to: 'escrow',
        k: 'funds',
        n: 1,
        pts: [[232, 338], [320, 338]]
      }, {
        id: 'a1c',
        f: 'source',
        to: 'lifi',
        k: 'funds',
        n: 1,
        pts: [[140, 608], [140, 376]],
        b: [140, 560]
      }, {
        id: 'a2',
        f: 'escrow',
        to: 'sweeper',
        k: 'signal',
        n: 2,
        pts: [[420, 280], [420, 164]],
        b: [420, 222]
      }, {
        id: 'a3',
        f: 'sweeper',
        to: 'd_process',
        k: 'call',
        n: 3,
        pts: [[520, 126], [676, 126], [676, 280]],
        b: [600, 126]
      }, {
        id: 'a4',
        f: 'escrow',
        to: 'factory',
        k: 'funds',
        n: 4,
        pts: [[520, 318], [576, 318]],
        b: [548, 318]
      }, {
        id: 'a5',
        f: 'd_process',
        to: 'o_deposit',
        k: 'call',
        n: 5,
        pts: [[776, 391], [804, 391], [804, 355], [832, 355]],
        b: [804, 373]
      }, {
        id: 'a5b',
        f: 'd_process',
        to: 'o_new',
        k: 'call',
        n: 5,
        pts: [[776, 391], [832, 391]]
      }, {
        id: 'a6',
        f: 'oam',
        to: 'vault',
        k: 'funds',
        n: 6,
        pts: [[1032, 306], [1088, 306]],
        b: [1060, 306]
      }, {
        id: 'a6b',
        f: 'oam',
        to: 'outbox',
        k: 'neutral',
        pts: [[1032, 398], [1060, 398], [1060, 478], [1088, 478]]
      }, {
        id: 'a7',
        f: 'oam',
        to: 'exchange',
        k: 'signal',
        n: 7,
        pts: [[932, 280], [932, 164]],
        b: [932, 222]
      }]
    },
    wd: {
      CW: 1600,
      CH: 848,
      N: {
        trader: {
          t: 'Trader',
          k: 'User',
          g: [48, 88, 184, 76],
          d: 'Withdraws with the recipient set to their withdraw address for the target chain. getWithdrawAddress returns that address.'
        },
        exchange: {
          t: 'Derive exchange',
          k: 'Off-chain',
          g: [576, 88, 200, 76],
          d: 'Checks margin and applies the withdrawal. Its batch is then proven and submitted to VerifiableApp.'
        },
        keeper: {
          t: 'Keeper',
          k: 'Off-chain',
          g: [1088, 88, 200, 76],
          d: 'Detects funds at withdraw addresses and calls processWithdrawal. Only addresses the factory owner allow-lists can process withdrawals.'
        },
        oam: {
          t: 'OAM.withdraw',
          k: 'Ethereum · contract',
          g: [320, 280, 200, 76],
          d: 'The L1 escape hatch: requests a withdrawal directly on Ethereum, paying the withdrawal fee in ETH. The payout comes from the outbox once the batch is proven.'
        },
        verifiable: {
          t: 'VerifiableApp',
          k: 'Ethereum · contract',
          g: [576, 280, 200, 76],
          d: 'Verifies each batch’s proof, then hands its withdrawals to the outbox. Only proven withdrawals can be paid.'
        },
        outbox: {
          t: 'WithdrawalOutbox',
          k: 'Ethereum · contract',
          g: [832, 280, 200, 76],
          d: 'Pays each proven withdrawal once, from the SpotVault, to its recipient: here the withdraw address. Rate limits can delay payouts.'
        },
        vault: {
          t: 'SpotVault',
          k: 'Ethereum · contract',
          g: [832, 456, 200, 76],
          d: 'Holds all deposited tokens. Funds leave only through the outbox.'
        },
        escrow: {
          t: 'Withdraw address',
          k: 'Ethereum · contract',
          g: [1088, 280, 200, 76],
          d: 'A fixed address per (recipient, chainId). recipient is bytes32, so non-EVM addresses fit. It can receive funds before its contract is deployed. Only the factory can move funds out of it.'
        },
        factory: {
          t: 'WithdrawFactory',
          k: 'Ethereum · contract',
          g: [1088, 400, 200, 204],
          d: 'Hands out withdraw addresses and bridges what lands in them to the recipient on the destination chain. The factory pays the bridge fee in ETH.'
        },
        w_addr: {
          t: 'getWithdrawAddress',
          k: 'View',
          p: 'factory',
          d: 'Returns the withdraw address for (recipient, chainId), even before it is deployed.'
        },
        w_process: {
          t: 'processWithdrawal',
          k: 'Keeper only',
          p: 'factory',
          d: 'Sweeps the withdraw address and sends the funds through the route configured for the token and chain. Reverts if there is no route.'
        },
        w_config: {
          t: 'setBridgeConfig',
          k: 'Owner only',
          p: 'factory',
          d: 'Sets the bridge route for a token and destination chain.'
        },
        w_recover: {
          t: 'recover · rescue',
          k: 'Owner only',
          p: 'factory',
          d: 'recover moves funds that can’t be bridged to the factory owner. rescue withdraws the factory’s own funds.'
        },
        bridges: {
          t: 'Bridge routes',
          k: 'Ethereum · external',
          g: [1344, 400, 200, 168],
          d: 'Each token and destination chain has one configured route.'
        },
        b_oft: {
          t: 'LayerZero OFTAdapter',
          k: 'Route',
          p: 'bridges',
          d: 'Bridges the token through a LayerZero OFT adapter.'
        },
        b_native: {
          t: 'Native OFT',
          k: 'Route',
          p: 'bridges',
          d: 'The token is itself a LayerZero OFT. Derive’s custom bridges use this route.'
        },
        b_ccip: {
          t: 'Chainlink CCIP',
          k: 'Route',
          p: 'bridges',
          d: 'Bridges the token through Chainlink CCIP to an EVM recipient.'
        },
        recipient: {
          t: 'Recipient',
          k: 'Destination chain',
          g: [1344, 736, 200, 76],
          d: 'The user’s address on the destination chain receives the bridged tokens.'
        }
      },
      ROWS: {
        factory: ['w_addr', 'w_process', 'w_config', 'w_recover'],
        bridges: ['b_oft', 'b_native', 'b_ccip']
      },
      E: [{
        id: 'b1',
        f: 'trader',
        to: 'exchange',
        k: 'call',
        n: 1,
        pts: [[232, 126], [576, 126]],
        b: [404, 126]
      }, {
        id: 'b1b',
        f: 'trader',
        to: 'oam',
        k: 'call',
        n: 1,
        pts: [[140, 164], [140, 318], [320, 318]],
        b: [140, 241]
      }, {
        id: 'b1c',
        f: 'oam',
        to: 'exchange',
        k: 'signal',
        pts: [[420, 280], [420, 150], [576, 150]]
      }, {
        id: 'b2',
        f: 'exchange',
        to: 'verifiable',
        k: 'signal',
        n: 2,
        pts: [[676, 164], [676, 280]],
        b: [676, 222]
      }, {
        id: 'b3',
        f: 'verifiable',
        to: 'outbox',
        k: 'call',
        n: 3,
        pts: [[776, 318], [832, 318]],
        b: [804, 318]
      }, {
        id: 'b4a',
        f: 'outbox',
        to: 'vault',
        k: 'call',
        pts: [[932, 356], [932, 456]]
      }, {
        id: 'b4',
        f: 'vault',
        to: 'escrow',
        k: 'funds',
        n: 4,
        pts: [[1032, 494], [1060, 494], [1060, 338], [1088, 338]],
        b: [1060, 416]
      }, {
        id: 'b5',
        f: 'escrow',
        to: 'keeper',
        k: 'signal',
        n: 5,
        pts: [[1188, 280], [1188, 164]],
        b: [1188, 222]
      }, {
        id: 'b6',
        f: 'keeper',
        to: 'w_process',
        k: 'call',
        n: 6,
        pts: [[1288, 126], [1316, 126], [1316, 426], [1288, 426]],
        b: [1316, 276]
      }, {
        id: 'b7',
        f: 'escrow',
        to: 'factory',
        k: 'funds',
        n: 7,
        pts: [[1188, 356], [1188, 400]],
        b: [1212, 378]
      }, {
        id: 'b8',
        f: 'w_process',
        to: 'bridges',
        k: 'funds',
        n: 8,
        pts: [[1288, 511], [1344, 511]],
        b: [1316, 511]
      }, {
        id: 'b9',
        f: 'bridges',
        to: 'recipient',
        k: 'funds',
        n: 9,
        pts: [[1444, 568], [1444, 736]],
        b: [1444, 652]
      }]
    }
  };
  D.dep.ROWS.factory.forEach((id, i) => {
    D.dep.N[id].g = [576, 337 + 36 * i, 200, 36];
  });
  D.dep.ROWS.oam.forEach((id, i) => {
    D.dep.N[id].g = [832, 337 + 36 * i, 200, 36];
  });
  D.wd.ROWS.factory.forEach((id, i) => {
    D.wd.N[id].g = [1088, 457 + 36 * i, 200, 36];
  });
  D.wd.ROWS.bridges.forEach((id, i) => {
    D.wd.N[id].g = [1344, 457 + 36 * i, 200, 36];
  });
  function rpath(p, r = 8) {
    let d = `M${p[0][0]},${p[0][1]}`;
    for (let i = 1; i < p.length - 1; i++) {
      const [x0, y0] = p[i - 1], [x1, y1] = p[i], [x2, y2] = p[i + 1];
      const d1 = Math.hypot(x1 - x0, y1 - y0), d2 = Math.hypot(x2 - x1, y2 - y1);
      const rr = Math.min(r, d1 / 2, d2 / 2);
      d += ` L${x1 - (x1 - x0) / d1 * rr},${y1 - (y1 - y0) / d1 * rr} Q${x1},${y1} ${x1 + (x2 - x1) / d2 * rr},${y1 + (y2 - y1) / d2 * rr}`;
    }
    const l = p[p.length - 1];
    return d + ` L${l[0]},${l[1]}`;
  }
  const CARDS = {
    dep: {
      trader: {
        sub: 'Wallet or exchange'
      },
      lifi: {
        sub: 'Custom or aggregator'
      },
      source: {
        sub: 'User’s address'
      },
      sweeper: {
        sub: 'Allow-listed by the owner'
      },
      exchange: {
        sub: 'zkVM'
      },
      escrow: {
        sub: 'One per subaccount'
      },
      factory: {
        sub: 'Deposit factory'
      },
      oam: {
        sub: 'L1 entry point',
        small: true
      },
      vault: {
        sub: 'Holds deposited tokens'
      },
      outbox: {
        sub: 'Asset registry'
      }
    },
    wd: {
      trader: {
        sub: 'Subaccount owner'
      },
      exchange: {
        sub: 'zkVM'
      },
      keeper: {
        sub: 'Allow-listed by the owner'
      },
      oam: {
        title: 'OnchainActionManager',
        sub: 'withdraw',
        small: true,
        monoSub: true
      },
      verifiable: {
        sub: 'Verifies the batch'
      },
      outbox: {
        sub: 'Rate-limited payouts'
      },
      vault: {
        sub: 'Holds protocol funds'
      },
      escrow: {
        sub: 'One per recipient and chain'
      },
      factory: {
        sub: 'Pays bridge fees in ETH'
      },
      bridges: {
        sub: 'Per token and chain',
        routes: true
      },
      recipient: {
        sub: 'User’s address'
      }
    }
  };
  const TOP_RIGHT = {
    right: 16,
    top: 14,
    textAlign: 'right'
  };
  const REGIONS = {
    dep: [{
      label: 'Senders',
      g: [24, 24, 232, 520]
    }, {
      label: 'Off-chain',
      g: [288, 24, 1032, 176],
      labelAt: TOP_RIGHT
    }, {
      label: 'Ethereum · L1',
      g: [288, 232, 1032, 312],
      solid: true,
      labelAt: TOP_RIGHT
    }, {
      label: 'Source chain',
      note: 'Any chain with a bridge to Ethereum',
      g: [24, 576, 1296, 120],
      labelAt: TOP_RIGHT
    }],
    wd: [{
      label: 'User',
      g: [24, 24, 232, 648]
    }, {
      label: 'Off-chain',
      g: [288, 24, 1288, 176]
    }, {
      label: 'Ethereum · L1',
      g: [288, 232, 1288, 440],
      solid: true,
      labelAt: {
        left: 16,
        bottom: 14
      }
    }, {
      label: 'Destination chain',
      note: 'Any chain with a configured bridge route',
      g: [288, 704, 1288, 120]
    }]
  };
  const LEGEND = [['funds', 'Funds'], ['call', 'Calls'], ['signal', 'Signals']];
  const GUTTER = 24;
  const MIN_SCALE = 0.5;
  const WIDEST = Math.max(...Object.values(D).map(f => f.CW));
  const dk = flow === 'withdraw' ? 'wd' : 'dep';
  const {N, ROWS, E, CW, CH} = D[dk];
  const rootRef = useRef(null);
  const [layout, setLayout] = useState(null);
  const [sel, setSel] = useState(null);
  const [kind, setKind] = useState(null);
  useEffect(() => {
    const root = rootRef.current;
    const measure = () => {
      const column = root.getBoundingClientRect();
      const spare = Math.max(0, document.documentElement.clientWidth - column.right - GUTTER);
      setLayout({
        column: column.width,
        avail: Math.min(WIDEST + 2, column.width + 2 * spare)
      });
    };
    measure();
    const observer = new ResizeObserver(measure);
    observer.observe(root);
    window.addEventListener('resize', measure);
    const onKey = e => {
      if (e.key === 'Escape') setSel(null);
    };
    window.addEventListener('keydown', onKey);
    return () => {
      observer.disconnect();
      window.removeEventListener('resize', measure);
      window.removeEventListener('keydown', onKey);
    };
  }, []);
  const select = id => e => {
    e.stopPropagation();
    setSel(s => s === id ? null : id);
    setKind(null);
  };
  const toggleKind = k => {
    setSel(null);
    setKind(c => c === k ? null : k);
  };
  const avail = layout ? layout.avail : WIDEST + 2;
  const scale = Math.min(1, Math.max(MIN_SCALE, (avail - 2) / WIDEST));
  const docked = CW * scale + 2 > avail;
  const stage = docked ? avail : CW * scale + 2;
  const family = id => [id, ...ROWS[id] || [], ...N[id].p ? [N[id].p] : []];
  let active = null;
  let lit = null;
  if (sel) {
    const f = family(sel);
    active = new Set(E.filter(e => f.includes(e.f) || f.includes(e.to)).map(e => e.id));
    lit = new Set(f);
  } else if (kind) {
    active = new Set(E.filter(e => e.k === kind).map(e => e.id));
    lit = new Set();
  }
  if (active) {
    E.filter(e => active.has(e.id)).forEach(e => [e.f, e.to].forEach(x => {
      lit.add(x);
      if (N[x].p) lit.add(N[x].p);
    }));
  }
  const isOn = e => !active || active.has(e.id);
  const opacity = id => !lit || lit.has(id) ? 1 : 0.3;
  const mono = 'derive-mono';
  const muted = 'var(--derive-flow-muted)';
  const sectionLabel = {
    fontSize: 11,
    letterSpacing: '0.08em',
    textTransform: 'uppercase',
    color: muted,
    marginBottom: 2
  };
  const regionLabel = {
    fontSize: 12,
    letterSpacing: '0.08em',
    textTransform: 'uppercase',
    color: muted
  };
  const cardStyle = id => {
    const [x, y, w, h] = N[id].g;
    return {
      position: 'absolute',
      left: x,
      top: y,
      width: w,
      height: h,
      boxSizing: 'border-box',
      display: 'flex',
      flexDirection: 'column',
      background: 'var(--derive-flow-card)',
      borderRadius: 6,
      border: `1px solid ${id === sel ? 'var(--derive-flow-fg)' : 'var(--derive-flow-border)'}`,
      boxShadow: id === sel ? '0 0 0 3px color-mix(in oklab, var(--derive-flow-fg) 14%, transparent)' : 'none',
      opacity: opacity(id),
      transition: 'opacity .2s, border-color .2s, box-shadow .2s'
    };
  };
  const regions = REGIONS[dk].map(r => {
    const [x, y, w, h] = r.g;
    return <div key={r.label} style={{
      position: 'absolute',
      left: x,
      top: y,
      width: w,
      height: h,
      boxSizing: 'border-box',
      padding: '14px 16px',
      borderRadius: 8,
      border: `1px ${r.solid ? 'solid' : 'dashed'} var(--derive-flow-border)`,
      background: r.solid ? 'color-mix(in oklab, var(--derive-flow-card) 40%, transparent)' : undefined
    }}>
        <div style={r.labelAt && ({
      position: 'absolute',
      ...r.labelAt
    })}>
          <div className={mono} style={regionLabel}>
            {r.label}
          </div>
          {r.note && <div style={{
      marginTop: 4,
      fontSize: 13,
      lineHeight: 1.35,
      color: muted
    }}>{r.note}</div>}
        </div>
      </div>;
  });
  const svg = <svg width={CW} height={CH} viewBox={`0 0 ${CW} ${CH}`} style={{
    position: 'absolute',
    left: 0,
    top: 0,
    pointerEvents: 'none',
    overflow: 'visible'
  }}>
      <defs>
        {Object.keys(COL).map(k => <marker key={k} id={`derive-flow-${dk}-${k}`} viewBox="0 0 10 10" refX={9.5} refY={5} markerWidth={9} markerHeight={9} markerUnits="userSpaceOnUse" orient="auto">
            <path d="M0,1 L10,5 L0,9 z" style={{
    fill: COL[k]
  }} />
          </marker>)}
      </defs>
      {E.map(e => {
    const d = rpath(e.pts);
    const hot = active && isOn(e);
    return <g key={e.id} style={{
      opacity: isOn(e) ? 1 : 0.14,
      transition: 'opacity .2s'
    }}>
            <path d={d} fill="none" markerEnd={`url(#derive-flow-${dk}-${e.k})`} strokeDasharray={e.k === 'neutral' ? '4 4' : undefined} style={{
      stroke: COL[e.k],
      strokeWidth: hot ? 2 : 1.5
    }} />
            {hot && <path d={d} fill="none" className="derive-flow-dash" strokeDasharray="2 10" strokeLinecap="round" style={{
      stroke: 'var(--derive-flow-fg)',
      strokeOpacity: 0.75,
      strokeWidth: 2
    }} />}
          </g>;
  })}
      {E.filter(e => e.b).map(e => <g key={`badge-${e.id}`} style={{
    opacity: isOn(e) ? 1 : 0.2,
    transition: 'opacity .2s'
  }}>
          <circle cx={e.b[0]} cy={e.b[1]} r={10} style={{
    fill: COL[e.k],
    stroke: 'var(--derive-flow-bg)',
    strokeWidth: 3
  }} />
          <text x={e.b[0]} y={e.b[1] + 0.5} textAnchor="middle" dominantBaseline="central" className={mono} style={{
    fill: 'oklch(14.5% 0 0)',
    fontSize: 11,
    fontWeight: 600
  }}>
            {e.n}
          </text>
        </g>)}
    </svg>;
  const cards = Object.keys(N).filter(id => !N[id].p).map(id => {
    const card = CARDS[dk][id];
    const title = <div style={{
      fontSize: card.small ? 14 : 16,
      fontWeight: 500,
      lineHeight: 1.25
    }}>{card.title || N[id].t}</div>;
    if (!ROWS[id]) {
      return <div key={id} onClick={select(id)} className="derive-flow-hit" style={{
        ...cardStyle(id),
        padding: '0 16px',
        justifyContent: 'center',
        gap: 3,
        cursor: 'pointer'
      }}>
            {title}
            <div className={card.monoSub ? mono : undefined} style={{
        fontSize: 13,
        lineHeight: 1.35,
        color: muted
      }}>
              {card.sub}
            </div>
          </div>;
    }
    return <div key={id} style={cardStyle(id)}>
          <div onClick={select(id)} className="derive-flow-hit" style={{
      height: 52,
      flexShrink: 0,
      boxSizing: 'border-box',
      padding: '0 16px',
      display: 'flex',
      flexDirection: 'column',
      justifyContent: 'center',
      gap: 2,
      borderBottom: '1px solid var(--derive-flow-border)',
      borderRadius: '6px 6px 0 0',
      cursor: 'pointer'
    }}>
            {title}
            <div style={{
      fontSize: 12,
      lineHeight: 1.3,
      color: muted
    }}>{card.sub}</div>
          </div>
          <div style={{
      padding: 4,
      display: 'flex',
      flexDirection: 'column'
    }}>
            {ROWS[id].map(row => <div key={row} onClick={select(row)} className={card.routes ? 'derive-flow-hit' : `derive-flow-hit ${mono}`} style={{
      height: 36,
      boxSizing: 'border-box',
      padding: '0 12px',
      display: 'flex',
      alignItems: 'center',
      borderRadius: 4,
      fontSize: card.routes ? 14 : 13,
      whiteSpace: 'nowrap',
      letterSpacing: card.routes ? undefined : '-0.02em',
      cursor: 'pointer',
      background: row === sel ? 'var(--derive-flow-accent)' : 'transparent',
      opacity: opacity(row),
      transition: 'opacity .2s'
    }}>
                {N[row].t}
              </div>)}
          </div>
        </div>;
  });
  const popover = () => {
    const n = N[sel];
    const f = family(sel);
    const rows = ROWS[sel] || [];
    const conns = E.filter(e => active.has(e.id)).map(e => {
      const out = f.includes(e.f);
      return {
        e,
        out,
        other: out ? e.to : e.f
      };
    });
    const place = () => {
      const [x, y, w, h] = n.g;
      const W = stage - 2;
      const H = CH * scale;
      const PW = 320;
      let left = (x + w) * scale + 12;
      let top = y * scale - 4;
      if (left + PW > W - 8) left = x * scale - 12 - PW;
      if (left < 8) {
        left = Math.max(8, Math.min(W - PW - 8, (x + w / 2) * scale - PW / 2));
        top = y * scale > H / 2 ? y * scale - 12 - 320 : (y + h) * scale + 12;
      }
      top = Math.max(8, Math.min(top, H - 360));
      return {
        position: 'absolute',
        left,
        top,
        width: PW,
        zIndex: 5
      };
    };
    const listRow = {
      display: 'flex',
      alignItems: 'center',
      padding: '6px 8px',
      margin: '0 -8px',
      borderRadius: 4,
      fontSize: 13,
      cursor: 'pointer'
    };
    const section = {
      display: 'flex',
      flexDirection: 'column',
      gap: 4,
      borderTop: '1px solid var(--derive-flow-border)',
      paddingTop: 12
    };
    return <div onClick={e => e.stopPropagation()} style={{
      ...docked ? {
        marginTop: 12
      } : place(),
      boxSizing: 'border-box',
      padding: 16,
      display: 'flex',
      flexDirection: 'column',
      gap: 12,
      background: 'var(--derive-flow-popover)',
      border: '1px solid var(--derive-flow-border)',
      borderRadius: 8,
      boxShadow: 'var(--derive-flow-shadow)'
    }}>
        <div style={{
      display: 'flex',
      alignItems: 'center',
      justifyContent: 'space-between',
      gap: 8
    }}>
          <span style={{
      border: '1px solid var(--derive-flow-border)',
      borderRadius: 4,
      padding: '2px 8px',
      fontSize: 12,
      fontWeight: 500,
      lineHeight: '16px'
    }}>
            {n.k}
          </span>
          <button type="button" aria-label="Close" onClick={() => setSel(null)} className="derive-flow-hit" style={{
      width: 28,
      height: 28,
      display: 'flex',
      alignItems: 'center',
      justifyContent: 'center',
      borderRadius: 4,
      color: 'inherit',
      cursor: 'pointer'
    }}>
            <svg width="14" height="14" viewBox="0 0 14 14" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round">
              <path d="M3 3l8 8M11 3l-8 8" />
            </svg>
          </button>
        </div>
        <div style={{
      display: 'flex',
      flexDirection: 'column',
      gap: 6
    }}>
          <div style={{
      fontSize: 18,
      fontWeight: 600,
      lineHeight: 1.3,
      overflowWrap: 'anywhere'
    }}>{n.t}</div>
          <p style={{
      margin: 0,
      fontSize: 14,
      lineHeight: 1.55
    }}>{n.d}</p>
        </div>
        {n.p && <div style={{
      display: 'flex',
      alignItems: 'center',
      gap: 6,
      fontSize: 13,
      color: muted
    }}>
            <span>Part of</span>
            <span onClick={select(n.p)} style={{
      color: 'var(--derive-flow-fg)',
      textDecoration: 'underline',
      textUnderlineOffset: 2,
      cursor: 'pointer'
    }}>
              {N[n.p].t}
            </span>
          </div>}
        {rows.length > 0 && <div style={section}>
            <div className={mono} style={sectionLabel}>
              {CARDS[dk][sel].routes ? 'Routes' : 'Functions'}
            </div>
            {rows.map(row => <div key={row} onClick={select(row)} className="derive-flow-hit" style={{
      ...listRow,
      justifyContent: 'space-between'
    }}>
                <span className={CARDS[dk][sel].routes ? undefined : mono}>{N[row].t}</span>
                <span style={{
      color: muted
    }}>{'→'}</span>
              </div>)}
          </div>}
        {conns.length > 0 && <div style={section}>
            <div className={mono} style={sectionLabel}>
              Connections
            </div>
            {conns.map(({e, out, other}) => <div key={e.id} onClick={select(other)} className="derive-flow-hit" style={{
      ...listRow,
      gap: 10
    }}>
                <span style={{
      width: 8,
      height: 8,
      borderRadius: '50%',
      flexShrink: 0,
      background: COL[e.k]
    }} />
                <span className={mono} style={{
      width: 14,
      flexShrink: 0,
      color: muted
    }}>
                  {out ? '→' : '←'}
                </span>
                <span style={{
      flex: 1,
      minWidth: 0,
      overflowWrap: 'anywhere'
    }}>{N[other].t}</span>
                <span className={mono} style={{
      flexShrink: 0,
      fontSize: 12,
      color: muted
    }}>
                  {e.n ? `Step ${e.n}` : KN[e.k] || 'Lookup'}
                </span>
              </div>)}
          </div>}
      </div>;
  };
  return <div ref={rootRef} className="derive-flow not-prose" style={{
    margin: '24px 0'
  }}>
      <div style={{
    width: layout ? stage : '100%',
    marginLeft: layout ? (layout.column - stage) / 2 : 0
  }}>
        <div style={{
    display: 'flex',
    flexWrap: 'wrap',
    alignItems: 'center',
    justifyContent: 'space-between',
    gap: 12,
    marginBottom: 12
  }}>
          <span style={{
    fontSize: 13,
    color: muted
  }}>Select any component to see what it does.</span>
          <div style={{
    display: 'flex',
    gap: 8
  }}>
            {LEGEND.map(([k, label]) => <button key={k} type="button" onClick={() => toggleKind(k)} className="derive-flow-hit" style={{
    display: 'inline-flex',
    alignItems: 'center',
    gap: 6,
    height: 28,
    padding: '0 10px',
    borderRadius: 4,
    fontSize: 13,
    fontWeight: 500,
    color: 'inherit',
    cursor: 'pointer',
    border: '1px solid var(--derive-flow-border)',
    background: kind === k ? 'var(--derive-flow-secondary)' : 'var(--derive-flow-bg)'
  }}>
                <span style={{
    width: 8,
    height: 8,
    borderRadius: '50%',
    background: COL[k]
  }} />
                {label}
              </button>)}
          </div>
        </div>
        <div onClick={() => setSel(null)} style={{
    position: 'relative',
    aspectRatio: layout ? undefined : `${CW} / ${CH}`,
    overflowX: docked ? 'auto' : 'clip',
    border: '1px solid var(--derive-flow-border)',
    borderRadius: 8,
    background: 'var(--derive-flow-bg)'
  }}>
          {layout && <div style={{
    width: CW * scale,
    height: CH * scale,
    overflow: 'hidden'
  }}>
              <div style={{
    position: 'relative',
    width: CW,
    height: CH,
    transformOrigin: '0 0',
    transform: `scale(${scale})`,
    backgroundImage: 'radial-gradient(color-mix(in oklab, var(--derive-flow-fg) 7%, transparent) 1px, transparent 1px)',
    backgroundSize: '24px 24px'
  }}>
                {regions}
                {svg}
                {cards}
              </div>
            </div>}
          {sel && !docked && popover()}
        </div>
        {sel && docked && popover()}
      </div>
    </div>;
};

This optional page explains the contracts and keepers the Derive UX uses for gasless onboarding. Like on a
centralized exchange, users deposit by sending funds to an address and withdraw straight to another chain.

<Note>
  You don't need this to onboard. You can use these contracts directly, or as inspiration for your own onboarding
  experience.
</Note>

## Deposit addresses

Funds sent to a deposit address are swept by the `SlowDeposit` factory. It deposits them through
`OnchainActionManager` into the user's own subaccount.

<TransferHelperDiagram flow="deposit" />

<Steps>
  <Step>
    Send tokens or ETH to your deposit address, directly on Ethereum or from a source chain through a
    [custom bridge](/getting-started/custom-bridges) or bridge aggregator.
  </Step>

  <Step>A keeper detects the transfer and waits for enough confirmations.</Step>
  <Step>The keeper calls `processSlowDeposit` on the `SlowDeposit` factory.</Step>
  <Step>The factory sweeps the deposit address and wraps native ETH to WETH.</Step>

  <Step>
    It deposits through `OnchainActionManager`: `deposit` for an existing subaccount, or `depositToNewSubaccount`
    when `subaccountId` is 0.
  </Step>

  <Step>`OnchainActionManager` moves the tokens into the `SpotVault` and queues the deposit.</Step>
  <Step>The Derive exchange reads the queued deposit and credits the subaccount. The credit is final once its batch is proven.</Step>
</Steps>

To get a deposit address over the API, call `public/register_deposit_address`. See the **Standard** tab in
[Programmatic Onboarding](/getting-started/depositing).

## Withdraw addresses

A withdrawal paid to a withdraw address is swept by the `WithdrawFactory`. It bridges the funds to the recipient
on the destination chain.

<Warning>
  Withdraw addresses only work for tokens with a [custom bridge](/getting-started/custom-bridges), such as HYPE,
  SOL and FXRP. The `WithdrawFactory` has no route for any other token. If one lands at a withdraw address,
  `processWithdrawal` reverts and the funds stay there. Only the factory owner can recover them.
</Warning>

<TransferHelperDiagram flow="withdraw" />

<Steps>
  <Step>
    Request a withdrawal with your withdraw address for the target chain as the recipient: a signed exchange
    operation, or `OnchainActionManager.withdraw` on L1.
  </Step>

  <Step>The Derive exchange applies it and submits its batch to `VerifiableApp`.</Step>
  <Step>`VerifiableApp` verifies the proof and hands the proven withdrawals to the `WithdrawalOutbox`.</Step>
  <Step>The outbox pays out from the `SpotVault` to the withdraw address, subject to rate limits.</Step>
  <Step>A keeper detects the funds at the withdraw address.</Step>
  <Step>It calls `processWithdrawal(recipient, chainId, token)` on the `WithdrawFactory`.</Step>
  <Step>The factory sweeps the withdraw address and wraps native ETH to WETH.</Step>
  <Step>It sends the funds through the route configured for that token and chain, paying the bridge fee in ETH.</Step>
  <Step>The recipient receives the tokens on the destination chain.</Step>
</Steps>

## Contracts

Mainnet is Ethereum and testnet is Ethereum Sepolia. For the core contracts, see
[Contracts](/getting-started/contracts).

### `SlowDeposit`

<CodeGroup>
  ```text Mainnet theme={null}
  0xBa5b26043496ad0482854c27A9C2171553725E5d
  ```

  ```text Testnet theme={null}
  0x2CF611F80f4e400D3A564029E351e338042455C5
  ```
</CodeGroup>

The deposit factory. `getDepositAddress(owner, subaccountId, managerId)` returns a deposit address.

### `WithdrawFactory`

<CodeGroup>
  ```text Mainnet theme={null}
  0x76bD60Cf28AB92B0f809D7Fa3c94Af8fA383fE5B
  ```

  ```text Testnet theme={null}
  0x11652dE462EfCE1418D05DaA459EB0D3895500f4
  ```
</CodeGroup>

`getWithdrawAddress(recipient, chainId)` returns a withdraw address. `getBridgeConfig(token, chainId)` shows whether
a token has a route to that chain.


## Related topics

- [Programmatic Onboarding](/getting-started/depositing.md)
- [Smart Contracts & Multi-sigs](/authentication/contract-owned-accounts.md)
- [DevEx improvements](/migrating/v3-improvements.md)


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.