Search projects, hackathons, sponsors and tech

    ← All projects

    TradeTokenStocks

    Our App sits between the intersection of Traditional and Tokenization of Stocks.

    Demo

    About

    Stock ownership is splitting in two. People hold shares at a regular brokerage, and more and more they also hold tokenized versions of the same company onchain, such as NVDAx from xStocks or NVDAon from Ondo. No app shows both side by side, and only the onchain half can actually be put to work. TradeTokenStocks is one portfolio across both. You sign in with Privy and get an embedded wallet. You link your brokerage through SnapTrade, which is strictly read-only: those shares count toward your total exposure and nothing more, and the SnapTrade keys never leave our server. The tokens in your wallet are the allocatable side. You can commit them to a 1inch Aqua liquidity strategy without them ever leaving your wallet. The app keeps that line visible everywhere, because the difference between observed and allocatable is the whole point. The strategy we focus on pairs two tokens of the same stock, such as NVDAx and NVDAon. They should trade close to 1:1, so we use Swap-VM's PeggedSwap curve instead of a constant-product pool. In our model, swapping 10 NVDAx into a 100 + 100 position returns about 9.96 NVDAon, while a constant-product pool with the same balances pays about 9.07. The hard problem is corporate actions. Under ERC-8056, a tokenized stock handles a split or a reinvested dividend by changing a multiplier instead of minting new tokens. After a 10-for-1 split, one raw token stands for ten shares instead of one, but ERC-20 transfers still move raw amounts. Issuers don't apply these changes at the same moment or in the same way. As soon as one token's multiplier moves, a pegged position still quoting the old ratio is mispriced, and the first arbitrageur drains it. Aqua strategies are immutable, so the position can't correct itself. That is a big part of why AMM liquidity on tokenized equities is so risky today. So we forked 1inch's Swap-VM and added a new instruction, CheckStockMultiplierRange, at opcode 0x24. Every position we ship runs this guard on both tokens before the fee and the PeggedSwap curve. On every quote and every swap, the VM reads each token's live multiplier onchain and reverts if either one has left the band the maker signed (±5% by default). The position halts itself the moment the underlying changes, instead of quietly bleeding out. The app surfaces the halt, and the maker can dock the stale strategy and ship a replacement at the corrected figures. Multiplier update events are indexed with Envio and shown in the app's activity. The whole lifecycle runs onchain: mint the stock tokens, approve, ship the guarded PeggedSwap strategy to Aqua, fill swaps against it, push a multiplier outside the band and watch the next swap revert, restore it, and dock the position to withdraw. TradeTokenStocks is a Bun and Turborepo monorepo with a Next.js web app and an Expo mobile app. Both share one platform-neutral domain package for the strategy math, Swap-VM program building and contract calls. Privy provides embedded wallets and signing, SnapTrade provides read-only brokerage data, and Envio indexes multiplier events. Everything runs on Ethereum Sepolia: 1inch Aqua, our forked Swap-VM router, and NVDA and AAPL test tokens modeled on xStocks and Ondo. Live Sample Transactions: **NVIDIA (NVDAx ↔ NVDAon)** - [Register position](https://sepolia.etherscan.io/tx/0x6f0485367aea14cc46b753ba48cd9ddeca3378f0e454cc4c3db14b8ceeb480bb) - [Successful NVDAx → NVDAon swap](https://sepolia.etherscan.io/tx/0x69a62974f7943a6661b76b2a856ed89480d2d41072f523f458f18cf519827fea) - [Dock position](https://sepolia.etherscan.io/tx/0x9f3c5447899099eebf9f1bdc7022627c4547b6339133c3cceef9b07f6da6203c) **Apple (AAPLx ↔ AAPLon)** - [Register position](https://sepolia.etherscan.io/tx/0x48e3e18a3a8af30c95b82561ad0867cd16556d17db0685b0d7b3fcdda334276f) - [Successful AAPLx → AAPLon swap](https://sepolia.etherscan.io/tx/0x098a71a558d354f5d786362f434b6d54715a4b19216bc5d6b7be787fc9b7b7f3) - [Dock position](https://sepolia.etherscan.io/tx/0x2675698839c50f8b37dbe570556d46b11f94f5d7bcd9df9aaa31802b3689878b)

    How it's made

    TradeTokenStocks is a Bun and Turborepo monorepo with a Next.js 16 web app, an Expo SDK 57 React Native app, and a shared, platform-neutral domain package. Strategy math, Swap-VM program building, contract calldata and number formatting all live in that package, so the phone and the browser can't quietly disagree about a balance. The package can't import React, Expo, Next or either Privy SDK. Layout stays in the clients. 1inch Aqua and Swap-VM are the core. We forked Swap-VM and added a new instruction, CheckStockMultiplierRange, at opcode 0x24. During a fill it reads a stock token's live multiplier and reverts if the value is outside the band the maker signed. The point is that the check runs inside the fill itself, not in a keeper or a monitoring bot. There's no window where a stale position can be filled while someone races to pull it. Aqua's design helps here too. Tokens stay in the maker's wallet and Aqua only holds approvals, so a halted position isn't capital locked in a pool. It's just a strategy that stops quoting. Each position ships a Swap-VM program of two guards (one per token), a flat fee, a PeggedSwap curve and a salt. To build that bytecode we patched @1inch/swap-vm-sdk 0.4.4 through Bun's patchedDependencies. The patch adds a CheckStockMultiplierRangeArgs coder and puts opcode 0x24 into the Aqua builder, so the SDK emits our fork's instruction set. The SDK and our deployed fork also disagreed on wire format, and the fixes are the hackiest part of the build. The fork puts both token addresses at the front of Order.data, so we build orders ourselves and point all four hook offsets in the maker traits at byte 40, where the program begins. The fork also moved swap direction into bit 7 of the 22-byte taker-traits header. We let the SDK encode normal taker traits, then set that bit in the header by hand. Finally, Aqua SDK 0.3.4 and Swap-VM SDK 0.4.4 pin different versions of sdk-core, so an Address or HexString from one is rejected by the other because of private class fields. We rebuild those values at the boundary before calling Aqua's ship(). All transaction math uses bigints: guard bounds are basis points on 1e18-scaled multipliers, and USD allocations become raw token units without ever touching floating point. Position building, guard bounds, ship calldata and order decoding are covered by unit tests in the domain package. Contract addresses live in a single deployment manifest. A deployment:check script verifies the RPC chain ID, confirms bytecode exists at every address, and calls each token's multiplier getter, supporting both multiplier() and ERC-8056's uiMultiplier(). A strategy is only saved after its ship() transaction confirms. Aqua and our forked router run on Ethereum Sepolia. Envio HyperIndex indexes the tokens' MultiplierUpdated events. A Next.js API route queries its GraphQL endpoint, and both apps render the results as rows in the activity ledger. That way a corporate action shows up in the app, not just as a reverted swap. Privy provides email login and embedded Ethereum wallets. For a demo that matters: a judge signs in with an email and has a signing wallet, with no extension and no seed phrase. SnapTrade provides the read-only brokerage side, and it produced the most instructive bug of the build. registerSnapTradeUser kept returning 400 with code 1012. The cause was our personal API key, which is itself a single SnapTrade user. Every signed-in person would have read one developer's brokerage, and anyone going through the connection portal would have linked their broker into that same account. We switched to a commercial key and now register one SnapTrade user per Privy user. That user's secret is sealed with AES-256-GCM together with the Privy user ID and checked on every open. It's stored in an HttpOnly cookie on web and in SecureStore on mobile, so a cookie replayed by a different signed-in user opens to nothing. The key mode is explicit config that defaults to commercial, so a misconfiguration fails closed into per-user isolation instead of silently sharing one account. We confirmed this by signing in with a second Privy account and seeing an empty state. SnapTrade's getUserHoldings is deprecated and returns 410 for keys registered after May 2026, so we build holdings from the accounts endpoint plus positions per account. Accounts in any currency other than USD are left out entirely rather than converted. Converting without a rate would invent a number on a screen whose whole claim is that brokerage data is reported, not modeled. The most annoying bug was on Android. @expo/ui's BottomSheet is a Material3 ModalBottomSheet in its own native window, and React Native's touch system doesn't own that window. Every Pressable inside the SnapTrade portal screen was completely dead, while the native pickers in the same sheet kept working, so it looked like a broken button rather than a hosting problem. The route was already a transparent modal, so the sheet only added a scrim and rounded corners. We rebuilt both with plain views and the screen came back to life.