Search projects, hackathons, sponsors and tech

    ← All projects

    Tenor-market

    A compliance-native secondary market for Hedera ATS security tokens, settling atomically in USDC.

    Demo

    About

    Compliant bonds can already be issued on Hedera's Asset Tokenization Studio (ATS). They cannot easily be traded, because every transfer has to keep obeying the issuer's rules: KYC on both sides, control lists, freezes, a global pause. Tenor is the missing venue: a peer-to-peer secondary market where listings settle atomically against USDC and the security token itself enforces compliance on every fill. No custody. A listing does not move the seller's tokens into an escrow contract. It places an ATS hold over part of their balance with the Tenor diamond as the hold's escrow. The tokens stay in the seller's wallet, visibly reserved, until a buyer fills. The market never holds a security token, so it needs no KYC status of its own, and the seller's own ERC-20 approval to the diamond is their self-imposed selling cap. Compliance by construction, not by copy. Tenor contains no compliance logic. A fill calls executeHoldByPartition on the token, and the token checks KYC on both parties, the control list and the pause state. An unverified buyer's fill reverts, and because the USDC leg is in the same transaction, their money is untouched. Tenor cannot get compliance wrong because Tenor does not implement it. One transaction per action. Enable selling once with a single approve, then each listing is one transaction that creates the hold and records the listing together. A listing can never exist without its hold, and vice versa. Coupons. The issuer funds a coupon and books it with the Hedera Schedule Service (HIP-1215), and the network fires it at the pay date. Settlement itself is a permissionless, idempotent payCoupon, so correctness never depends on who triggers it, and a holder who never associated with USDC is recorded as skipped rather than stranding everyone else's coupon. Investors sign in with a passkey. Privy creates an embedded wallet silently, the app drips test HBAR and demo USDC, and every transaction is simulated as the connected wallet before it is offered, so no user is ever asked to sign something that will fail. The primary button's label is the blocking condition: Enter an amount, Insufficient USDC balance, Approve USDC, Buy, or Verification required. Everything runs on Hedera testnet and every deployed contract is verified on Sourcify as an exact match. On-chain evidence, linked from the README: an atomic delivery-versus-payment fill of 25 TGN27 against 2,450 USDC between two different wallets; the same transfer to an unverified holder refused by the token with InvalidKycStatus(); a 1,462.50 USDC coupon paid across two holders, triggered by a non-issuer; and a freshly generated passkey wallet funded and buying.

    How it's made

    On-chain, Tenor is a single EIP-2535 diamond composed from Lattice, my pre-existing MIT module library, consumed as a pinned Forge submodule. Lattice supplies DiamondCut, Loupe, ERC-165, AccessControl and Pausable. Two facets were written for this event: TenorMarket (listings, fills and cancels built on ATS holds) and TenorCoupon (funding, scheduling and permissionless idempotent settlement). Solidity 0.8.36 on Foundry, with the deploy recipe shared between production and the test suite. Hedera. The bond (Tenor Green Note 2027) is issued through Asset Tokenization Studio, an ERC-1400 / ERC-3643 diamond, and configured for third-party holds. A fill calls executeHoldByPartition, so the token enforces KYC, control list, freeze and pause on every trade. USDC settlement goes through the Hedera Token Service system contract at 0x167 on the HIP-906 allowance path (transferFrom), plus associateToken for new wallets. Coupons are booked with the Hedera Schedule Service at 0x16b using HIP-1215 scheduleCall / scheduleSelfCall. Privy. Passkey sign-in with silently created embedded wallets. The Next.js client drips test HBAR and demo USDC to brand-new users so they reach a first trade without leaving the page, and it simulates the exact fill as the connected wallet before offering it. The financial flow the embedded wallet signs is a single transaction moving USDC from buyer to seller and the bond from seller to buyer under the issuer's rules. The notable and hacky parts: - Lattice's HTS and HSS modules were authored during the event. Every mutating helper in them is access-controlled on msg.sender, and a facet's delegatecall frame preserves msg.sender as the original caller. Routing the permissionless fill() through them would have required the buyer to hold an operator role, so TenorHTS.sol makes the two 0x167 calls the permissionless paths need directly. - The original spec's account of the Lattice and ATS surface was written from memory and was wrong in ways that did not compile: wrong function names, wrong argument order, and no mention of the access control above. docs/GROUND-TRUTH.md records every external API as read from source, plus the seven deviations that followed. - The whole deploy chain (ATS system, USDC, bond issuance, KYC, market) was rehearsed against a local Hedera node before it was run on testnet. That found six real breaks. The scheduled coupon call fires within 25 ms of its pay date but fails with INVALID_PAYER_SIGNATURE, so settlement is completed by the permissionless payCoupon. The app copy says exactly that and no more; the evidence is in GROUND-TRUTH.md section 10. - Every deployed contract, including the ATS system contracts, is verified on Sourcify as an exact match, checked by a script that discovers nested deployments from the broadcast record. Compliance failures are asserted by selector (InvalidKycStatus, 0xfc855b1b), not just "it reverted". Built with Claude Code, which produced the contracts, tests, scripts and client under direction and, more usefully, was used to verify assumptions against actual Solidity source and runtime npm exports rather than trust the spec. Design explored in Claude Design. The spec, ground-truth notes and planning documents are committed under docs/.