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

# Buy tokenized SpaceX stock under a price cap

> One Ethereum transaction that buys SPCXon on the Uniswap V3 USDC/SPCXon pool whose share costs least with its fee, up to a price per share, and refunds the unspent USDC.

The program buys SPCXon, Ondo's tokenized SpaceX on Ethereum, with up to `budget` of the Pot's USDC. It reads every Uniswap V3 fee tier of the pair, swaps on the pool whose share costs least with its fee paid, and stops where a share would cost more than `priceLimit`. The shares go to `recipient` and the USDC the pool did not take goes to `refundTo`. The cook reverts when no pool has liquidity, every pool is at or above `priceLimit`, or the recipient receives fewer than `minShares`.

**Target chains:** Ethereum. The example uses SPCXon (18 decimals); inspect the registry with `sauce globals --chain ethereum --search spacex`.

## Prerequisites

Install the [Sauce CLI](https://docs.eco.com/sauce-sdk/installation.md) to compile and simulate. To send the transaction, configure a wallet with funds for gas and the Kitchen fee, and provide USDC in your Pot once it is deployed, or through [`--fund`](https://docs.eco.com/sauce-sdk/cli/funding).

## Program

`pot` is the Pot the program runs on, available without declaring it ([Names](https://docs.eco.com/sauce/names.md#balances)).

```ts buy-tokenized-stock.sauce.ts theme={null}
// Buys SpaceX stock on Ethereum (SPCXon, Ondo's tokenized SpaceX, 18 decimals) with up to
// `budget` of the Pot's USDC, on the Uniswap V3 USDC/SPCXon pool, of the four fee tiers, whose
// share costs least with its fee paid. The swap stops where a share, fee included, costs
// `priceLimit`; the shares go to `recipient` and the USDC the pool did not take to `refundTo`.
// The cook reverts, and nothing moves, when no pool has liquidity, every pool is at or above
// `priceLimit`, or `recipient` receives fewer than `minShares`.
//
//   sauce simulate buy-tokenized-stock.sauce.ts --chain ethereum --owner <address> \
//     --assume-balance USDC:<budget>n \
//     --args <budget> <priceLimit> <minShares> <recipient> <refundTo>
//   sauce run buy-tokenized-stock.sauce.ts --chain ethereum --wallet <name> \
//     --fund USDC:<budget>n --args <budget> <priceLimit> <minShares> <recipient> <refundTo>
//
//   budget: Uint256      the most USDC to spend, in USDC base units (6 decimals): 5000000000 is
//                        5,000 USDC; the Pot must hold it
//   priceLimit: Uint256  the most one share may cost, fee included, in USDC base units:
//                        149400000 is 149.40 USDC
//   minShares: Uint256   the fewest shares `recipient` may receive, in SPCXon base units
//                        (18 decimals): 10000000000000000000 is 10 shares
//   recipient: Address   receives the shares
//   refundTo: Address    receives the USDC the pool did not take
//
// On Ethereum, up to 5,000 USDC of SPCXon at no more than 149.40 USDC a share, at least 10
// shares, the shares to <recipient> and the rest of the budget to <refundTo>:
//
//   sauce run buy-tokenized-stock.sauce.ts --chain ethereum --wallet me --fund USDC:5000 \
//     --args 5000000000 149400000 10000000000000000000 \
//     <recipient> <refundTo>
import { uniswapV3 } from "@sauce/router";

function main(
  budget: Uint256,
  priceLimit: Uint256,
  minShares: Uint256,
  recipient: Address,
  refundTo: Address,
): { fee: Uint256; spent: Uint256; shares: Uint256 } {
  require(budget > 0, "budget is 0");
  require(priceLimit > 0, "priceLimit is 0");
  const before = pot.USDC;
  require(before >= budget, "budget is not in the Pot");

  // Every fee tier: keep the pool with liquidity whose share, fee included, costs least.
  let pool: Address = 0;
  let fee: Uint256 = 0;
  let cheapest: Uint256 = 0;
  for (const tier of [100, 500, 3000, 10000]) {
    const candidate = UniswapV3.Factory.getPool(USDC, SPCXon, tier);
    if (candidate == 0 || UniswapV3.Pool(candidate).liquidity() == 0) {
      continue;
    }
    // A share's price in USDC base units, from the sqrt price (token1 per token0, times 2^96;
    // the lower address is token0), then with the pool's fee on top.
    const sqrtPriceX96 = UniswapV3.Pool(candidate).slot0().sqrtPriceX96;
    const ratio = Math.mulDiv(sqrtPriceX96, sqrtPriceX96, 1 << 96);
    const price =
      USDC < SPCXon
        ? Math.mulDiv(10 ** 18, 1 << 96, ratio)
        : Math.mulDiv(ratio, 10 ** 18, 1 << 96);
    const paid = Math.mulDiv(price, 1000000, 1000000 - tier);
    if (pool == 0 || paid < cheapest) {
      pool = candidate;
      fee = tier;
      cheapest = paid;
    }
  }
  require(pool != 0, "no USDC/SPCXon pool with liquidity");
  require(cheapest < priceLimit, "every pool is at or above priceLimit");

  // The pool price at which a share with the fee on top costs priceLimit, as a sqrt price: the
  // swap stops there. Paying token0 pushes the price down, paying token1 pushes it up.
  const atLimit = Math.mulDiv(priceLimit, 1000000 - fee, 1000000);
  const limit =
    USDC < SPCXon
      ? Math.sqrt(Math.mulDiv(1 << 192, 10 ** 18, atLimit))
      : Math.sqrt(Math.mulDiv(atLimit, 1 << 192, 10 ** 18));
  const sharesBefore = recipient.SPCXon;
  uniswapV3(pool, USDC, SPCXon, budget, limit, 0, recipient);
  const shares = recipient.SPCXon - sharesBefore;
  require(shares >= minShares, "fewer shares than minShares");

  // What the pool took; the rest of the budget goes back. The Pot's other USDC stays.
  const spent = before - pot.USDC;
  if (budget > spent) {
    USDC.transfer(refundTo, budget - spent);
  }
  return { fee, spent, shares };
}
```

## How it works

* The loop asks `UniswapV3.Factory.getPool` for the 100, 500, 3000 and 10000 tiers, skips a missing pool or one with no liquidity, and compares the rest by price with the fee on top, `price * 1000000 / (1000000 - tier)`. A 1% pool with a lower mid price can lose to a 0.3% pool with a higher one.
* `priceLimit` is USDC per share with the fee included. The program turns it into the pool's sqrt price at `priceLimit * (1 - fee)`. USDC is token0 on Ethereum, so paying it lowers the pool's price and the limit is a floor: the pool stops there and the Router pays it only for what it filled.
* `spent` is the Pot's USDC balance change, and only `budget - spent` goes to `refundTo`; other USDC in the Pot stays. `shares` is what `recipient` received, compared with `minShares`.
* `require(cheapest < priceLimit, ...)` refuses a limit at or below the chosen pool's current price with its fee. Without it the limit would sit on the wrong side of the pool's price and the pool would revert with `SPL`.

## Run the example

**1. Read the pools**

```bash theme={null}
sauce pools --chain ethereum USDC SPCXon
```

It lists each pool's price per share with and without its fee. Set `priceLimit` above the cheapest one.

**2. Compile**

```bash theme={null}
sauce compile buy-tokenized-stock.sauce.ts --chain ethereum
```

**3. Simulate**

```bash theme={null}
sauce simulate buy-tokenized-stock.sauce.ts --chain ethereum --owner <your-address> \
  --assume-balance USDC:5000 \
  --args 5000000000 149400000 10000000000000000000 <recipient> <refundTo>
```

`--assume-balance USDC:5000` gives the Pot 5,000 USDC for this simulation only. The result lists the fee tier chosen, the USDC spent and the shares bought.

**4. Send**

```bash theme={null}
sauce run buy-tokenized-stock.sauce.ts --chain ethereum --wallet <name> \
  --fund USDC:5000 \
  --args 5000000000 149400000 10000000000000000000 <recipient> <refundTo>
```

`sauce tx` prints the unsigned transaction instead, for your own signer, with the same funding and arguments:

```bash theme={null}
sauce tx buy-tokenized-stock.sauce.ts --chain ethereum --owner <your-address> \
  --fund USDC:5000 \
  --args 5000000000 149400000 10000000000000000000 <recipient> <refundTo>
```

## Simulation result

Simulated on a local fork of Ethereum at block 26278321 with the current contracts. Nothing was signed or sent; values change with the block. On the fork `sauce pools` listed two pools with liquidity: the 0.3% pool at 149.129 USDC a share with its fee, and the 1% pool at 151.64.

```text theme={null}
The cook succeeds from 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266: gas used 381942 for the cook, after the Pot's deploy (1. below), returns 0x0000000000000000000000000000000000000000000000000000000000000bb800000000000000000000000000000000000000000000000000000000c22c5ce10000000000000000000000000000000000000000000000012ee1bb39b2e696f4
main returns { fee: Uint256, spent: Uint256, shares: Uint256 }: { fee: 3000, spent: 3257687265, shares: 21824931125703120628 }
...
What moved, in order:
...
  [3] 0xc910…Fa8b        -> 0x7099…79C8          21.824931125703120628 SPCXon
  [4] Pot                -> 0xc910…Fa8b          3257.687265 USDC
  [5] Pot                -> 0x3C44…93BC          1742.312735 USDC
Net balance changes:
  0xc9101FAaac077d2F7A30c60d3D89955E1a89Fa8b: -21.824931125703120628 SPCXon, +3257.687265 USDC
  0x70997970C51812dc3A010C7d01b50e0d17dc79C8: +21.824931125703120628 SPCXon
  0xDcFCc75A3C9a0EC798CAf81B6C84D4c5d5988c49 (the Pot): -5000 USDC
  0x3C44CdDdB6a900fa2b585dd299e03d12FA4293BC: +1742.312735 USDC
  0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266 (owner): -0.00003728943124295 ETH
...
```

The program chose the 0.3% pool (`fee: 3000`). It stopped at the 149.40 limit after 3,257.687265 USDC, for 21.824931125703120628 SPCXon paid to the recipient, and the other 1,742.312735 USDC went to `refundTo`.

On the same fork, a `priceLimit` of 149.00 USDC (`149000000`) is below the 0.3% pool's price with its fee, and the cook reverts before any swap:

```bash theme={null}
sauce simulate buy-tokenized-stock.sauce.ts --chain ethereum --owner <your-address> \
  --assume-balance USDC:5000 \
  --args 5000000000 149000000 10000000000000000000 <recipient> <refundTo> \
  --expect-revert "at or above priceLimit"
```

```text theme={null}
As expected, the cook reverts: the program threw "every pool is at or above priceLimit"
The cook reverts: the program threw "every pool is at or above priceLimit"
...
```

Both results, and the prices above, are from the fork at block 26278321. The pools' prices move with every trade, so at another block 149.00 can be above the cheapest pool's price with its fee, and the same command then buys instead of reverting. Read `sauce pools` at the block you simulate on and set the limit from the prices it lists.

## Next steps

* [Distributed swap](https://docs.eco.com/quickstarts/sauce/distributed-swap.md): a limit buy spread over the pools `sauce pools` lists for a pair on Base's Aerodrome Slipstream, Uniswap V3, PancakeSwap V3 and hookless Uniswap V4.
* [Tokens](https://docs.eco.com/sauce-sdk/reference/tokens.md): the tokenized stocks each chain registers.
* [`sauce pools`](https://docs.eco.com/sauce-sdk/cli/pools), [`sauce globals`](https://docs.eco.com/sauce-sdk/cli/globals), [`sauce simulate`](https://docs.eco.com/sauce-sdk/cli/simulate)


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