Search projects, hackathons, sponsors and tech

    ← All projects

    Rialto

    Oracle-free lending for tokenized securities on Hedera. HIP-1215 scheduled call closes the loan.

    Demo

    About

    A tokenized bond is a real asset that no lending market will take as collateral, because it has no price. It was never listed, and every on-chain money market needs a feed before it will lend. So an owner who needs cash for a month has one option: sell the bond they wanted to keep. The usual fix is to invent a price, and invented prices get attacked. Rialto never asks what the bond is worth. A borrower locks the bond and says how much cash they want and for how long. Underwriters read the bond's own offering document, form a credit opinion, and bid a repayment figure. Lowest bid wins, the cash goes straight from lender to borrower, and that number is fixed for the life of the loan. Fixing it at auction removes everything a lending market normally needs to stay solvent. No price feed, so nothing to manipulate. No margin call, because nothing is ever marked. No liquidator, because a funded loan has two endings: repay on time and the bond goes back, or miss the date and it goes to the lender. And no loan-to-value parameter: the contract never checks a ratio, so the lender prices the cushion instead of a governance setting. A lender still has to trust three things, and each is held by a different Hedera service. Collateral. A real ERC-3643 security minted through Hedera's Asset Tokenization Studio. The issuer stores the offering document's hash on the token's ERC-1643 facet, and opening a loan reads that hash off the security and freezes it into the request — all 29 loans took it from the token, not from the borrower. Your browser re-fetches the document and re-hashes it, so swapping the file mid-auction shows up as a mismatch. Reasoning. The lender publishes why it is bidding to a Hedera Consensus Service topic before the bid, and the bid carries that message's hash. Consensus timestamps the reasoning before the outcome exists, so nobody can rewrite their story once they know they won. Lenders can be people; here one is an AI agent, and its limits live in a mandate contract the market checks on every bid, so a stolen key cannot exceed them. Settlement. Awarding a loan asks Hedera's Schedule Service, in the same transaction, to call the market back after maturity, with the market contract as payer. Settlement is permissionless and takes only a loan id, so a scheduled call is exactly as powerful as a passer-by. It only removes the need for one to show up. 29 loans have run end to end on testnet - 11 repaid, 11 defaulted, 7 cancelled because nobody bid, 46,000 dUSD funded and Hedera has executed 16 scheduled calls into the market with the contract itself paying the fee. Market 0x9040986Da679d00F0AA93ca21E1c9Aa2143121a4, topic 0.0.10367534, and every settlement is a schedule on Hedera's public mirror node showing the second it was booked for and the second it ran.

    How it's made

    Four Solidity contracts, no proxies, no admin keys, nothing upgradeable. Once deployed there is no address anywhere that can alter a live loan. RialtoMarket holds the lifecycle: open, bid, award, repay, claim. Mandates is a separate registry where an underwriter declares its limits and binds an agent key, so an agent's authority is enforced by a contract the market calls on every bid, not by the agent's own code. ComplianceLens exists because a blocked ERC-3643 transfer reverts with nothing at all: it checks paused(), then frozen / not-on-control-list / no-KYC for both the escrow and the beneficiary, and returns which of the six it was. CouponPassThrough routes income the bond pays while locked back to the borrower, who still owns it. ATS. Opening a loan staticcalls getDocument on the token's ERC-1643 facet and reads the issuer's own document hash out of the return data - the second word, lifted with assembly rather than decoded, so a malformed return cannot revert the open. That hash is frozen into the request, and the contract records whether it came from the token or the borrower. Every external call is a raw call with a returndata check, never an interface, because a native HTS token has no EVM bytecode — eth_getCode returns 0x — so a high-level call reverts on the extcodesize check before it is even attempted. That single decision is what lets a native Hedera token be collateral at all. HCS. The agent publishes its opinion to topic 0.0.10367534 and passes the message hash into bid() as reasoningRef. Messages over 1024 bytes are chunked, so verification reassembles them before hashing. script/verify-reasoning.sh reads reasoningRef off-chain from the contract first and then finds the message that hashes to it the reverse of the obvious direction, and necessary, because a request can carry more than one opinion. HIP-1215. award() checks hasScheduleCapacity, then calls scheduleCall on 0x16b to book claim() sixty seconds past maturity with 1.2M gas and this contract as payer. repay() deletes that schedule in the transaction that closes the loan early. Both calls are wrapped, including the capacity staticcall, and every failure path is a no-op that falls back to a manual claim: the second may be full, the contract may hold no HBAR, the chain may not be Hedera at all. A funding market must never fail to fund because a convenience could not be arranged. Three behaviours we found on testnet that are documented nowhere, and what each one changed: 1. Hedera permits one scheduled call per transaction. An award that books its own settlement and then a coupon record date is rejected outright with NO_SCHEDULING_ALLOWED_AFTER_SCHEDULED_RECURSION, and the loan does not fund at all — found when awards that had worked all day began reverting the moment a coupon fell inside the term. So settlement is booked at award, because it is the one that must not be forgotten, and a coupon record date is a second transaction that anyone may send. 2. forge script cannot reach 0x16b. The Schedule Service has no EVM bytecode, so forge's local simulation refuses the transaction before it is ever sent — with or without --skip-simulation. Everything that touches the precompile is a shell script driving cast. 3. Freezing an address on an ERC-3643 security is setAddressFrozen(address,bool), but a frozen account still reads isFrozen == false. The freeze shows up as removal from the control list. That is why ComplianceLens reports standing from isInControlList rather than isFrozen — reading the obvious getter would have told a lender a frozen counterparty was fine. The agent is TypeScript on viem: it polls open requests, verifies the document, forms an opinion, publishes to HCS, then bids — in that order, so the reasoning is timestamped before the outcome exists. The document is untrusted input, because the issuer chooses the URL. The system prompt treats it as evidence, not instructions: any instruction or claim of authority found inside it is a finding to flag and lower confidence on, never to obey. The reply is then type-checked field by field, the repayment must be a decimal integer string, and the bid is re-derived against the same mandate the contract enforces. The model proposes; the contract decides. Every revert the agent can receive is mapped to a plain-English refusal by selector, so a rejected bid reads as an underwriting outcome rather than a crash. Frontend is Next.js 15 with wagmi and viem over batched multicall reads. No server component touches a key. 463 tests, all passing: 245 in Foundry across 19 suites, 139 on the agent, 79 in the web app. The contract suite carries 10 invariants over a randomized handler and a mutation pass deleting a guard has to break a test, and where it did not, the assertion was strengthened rather than the code.