Docs

Flit moves everything a wallet holds on Robinhood Chain to another wallet in one transaction: tokenized stock, dollars, vault shares, lending deposits, and collateral with its loan still open. Nothing is sold. The loan is never closed. The new wallet needs no ether, and it has to sign.

What Flit does

You have a wallet with things in it. You want a different wallet to have them — because you bought a hardware wallet, because a seed phrase was written down somewhere you now regret, because you want savings separate from spending, or because the address has been posted publicly once too often.

Tokens are tedious to move: one transfer each, one confirmation each. A loan is worse than tedious. On Morpho Blue a position belongs to an address and there is no function to hand it over. To get it out the ordinary way you would have to find the dollars to repay it, unpledge the collateral, send it, pledge it again from the new wallet and borrow again — five or six transactions, real money you may not have, and a position that was closed and re-opened at whatever rate the market offered by then.

Flit does the whole thing in one transaction, using contracts that were already live on this chain before it existed. It deploys nothing, holds nothing, and charges nothing.

Why a loan cannot just be sent

Three rules, none of them ours:

  1. A debt has no transfer. Morpho has borrow and repay; it has nothing that moves a position between addresses. The only way a debt appears at a new address is if that address borrows.
  2. Collateral does not come out from under a debt. Every withdrawal ends with a health check. While a dollar is owed, the shares behind it stay where they are.
  3. The debt is repaid in dollars. Which you would have to find first — and if you had them spare you would not have borrowed.

Together they make a cycle a borrower with no spare cash cannot break. The way through is a loan that lasts one call: Morpho Blue will lend its own balance of any token for the length of a single transaction, free, on the condition that it comes back before the call ends.

The transaction, in order

One call to Bundler3, Morpho's own multicall, sent by the old wallet:

permit(token -> adapter)        each loose holding, under your signature
erc20TransferFrom(-> new)       each one, straight across
setAuthorizationWithSig         the old wallet lets the adapter act for it
morphoWithdrawCollateral        collateral with no debt: out …
morphoSupplyCollateral(new)     … and pledged again for the new wallet
morphoFlashLoan(token, F)       and inside the callback:
  morphoSupply(new, value)        a deposit: the new wallet's goes in first
  morphoWithdraw(all shares)      then the old wallet's comes out
  morphoRepay(old, all shares)    the old debt, exactly
  morphoWithdrawCollateral        its collateral, out
  morphoSupplyCollateral(new)     pledged for the new wallet
  setAuthorizationWithSig         the new wallet lets Bundler3 borrow for it
  borrow(new -> adapter)          the same debt, in the new wallet's name
  setAuthorizationWithSig         and takes that permission back
morphoRepay(new, MAX)           the overshoot, off the new debt
erc20Transfer(-> new, MAX)      anything else left of that token
setAuthorizationWithSig         the old wallet takes its permission back
value: ether                    sent last

The order is not a preference. Repaying before unpledging is rule 2 above. Supplying the new deposit before withdrawing the old one is what lets a deposit leave a market that is almost fully lent out. Borrowing inside the flash loan is what makes the flash loan repayable. Each of those is a property executed on chain, and each has a control proving the same call goes through when the rule is not in the way.

What you sign, and why

Signatures, not transactions: they cost nothing and they are not sent anywhere until the one transaction goes.

  • One permit per token (EIP-2612), from the old wallet, for exactly the balance being moved and expiring in 20 minutes. The spender is GeneralAdapter1, which only ever acts for the wallet that sent the transaction. Every one of the 103 tokens Flit lists was checked on the real chain for whether it accepts one, with the domain the scanner found and three controls that must be refused.
  • Two Morpho authorisations from the old wallet — a grant and a revocation over consecutive nonces, both inside the transaction. If the adapter is already authorised, neither is asked for.
  • Two from the new wallet, if a loan is moving: the same pair, for Bundler3. See the one risk.
  • One plain message from the new wallet when no loan is moving. It proves the address can sign, which is the thing a mistyped address cannot do. The app refuses to build a move without it, and so does js/bundle.js, which checks the recovered signer before it encodes anything.

Why the debt comes out equal

A debt grows every second. Between the moment Flit builds the transaction and the moment it lands, the old position owes more than it did.

So the old debt is repaid by shares, which is exact whenever it runs — shares do not move, assets do. The new debt is borrowed as the debt accrued 20 minutes ahead, plus 1 basis point, which is always at least what the repayment cost. The difference stays in the adapter for one more call and is repaid against the new wallet's debt in the same transaction. What lands is the old debt, to within a rounding unit.

The flash loan is sized the same way, and it does not have to be the sum of everything moving: each step leaves the adapter holding at least what it started with, so it only has to cover the largest single outflow.

One consequence worth naming, because a fuzz found it rather than a person: when a lending deposit moves, it is re-supplied at what it was worth when the plan was built and withdrawn at what it is worth when the transaction runs. Those few units land in the adapter, and the last calls sweep them onto the new wallet's debt. Nothing is lost — it is the same money, in a different pocket — but the new debt can come out a unit or two smaller than the old one, which is why the suite's invariant is "nothing was lost overall" and not "the debt is identical".

What it refuses

Every refusal below is a revert the chain would otherwise produce after you had signed and paid for gas. Flit makes it first, in words, with the numbers.

  • No headroom. Re-opening a loan means borrowing slightly more than it is today, to carry the interest. A loan already at its limit has no room for that. Repay a little first.
  • The market is lent out. The new wallet borrows from the same market the old one repaid. If that market has been lent to the last dollar since, nobody can do it — Flit names the market and how much is free.
  • The lender is short. Every flash loan on this chain draws on one number: Morpho Blue's own balance of the token.
  • The deposit is locked. A deposit in a market with nothing free cannot be withdrawn, by Flit or by you.
  • The price feed is down. Morpho will not open a loan it cannot value.
  • The destination is exposed. If the new wallet already authorises Bundler3, Flit refuses to move anything there until that is revoked. See below.
  • Nothing to move, no gas, the same wallet, or one of the contracts. The obvious ones, said plainly.

The one risk

Moving a loan needs the new wallet to let Morpho's Bundler3 borrow in its name. Bundler3 is a public contract: anyone can call it. While that permission stands, anyone can make it borrow against that wallet's collateral and keep the money. The suite proves that by doing it — a stranger borrowed $50.00 against an exposed wallet through Bundler3 in one ordinary transaction.

Flit grants and revokes it in the same call, so when the block ends there is nothing standing, and the fork suite asserts that after every move.

What is still true: if the transaction reverts, its calldata is public and the grant signature could be replayed by anyone until it expires, 20 minutes after you signed. The revocation you already signed is still valid on the next nonce, so it can be sent straight afterwards; the app re-reads both wallets' authorisations after any failure and offers to send it. A brand-new wallet with nothing in it is not worth borrowing against, which is why Flit will not move anything into a destination that already has Bundler3 authorised, rather than treating it as convenience.

If you would rather not take that at all: move the loan by hand. Repay it, unpledge, send, pledge, borrow. It costs the dollars you do not have and five transactions, and it is a perfectly reasonable thing to prefer.

What it will not move

  • Uniswap liquidity positions. A v3 or v4 position is an NFT, and moving one inside a bundle means signing a permit naming Bundler3 — a public contract — as its spender. A reverted bundle would leave that permit in the open until it expired, and an NFT permit is not amount-limited. Flit counts your positions and tells you to move them yourself.
  • Tokens outside the directory. Flit moves what it can name from the chain: the tokenized stocks, the dollar, wrapped ether, and the shares of vaults built by Morpho's own Vault V2 factory. Anything else it has not seen.
  • Anything on another chain, and anything that is not a token — no NFTs, no ENS-style names, no contract ownership.
  • A wallet whose key someone else has. Flit is for a wallet you still control and want to leave behind. It is not a race against a thief, and if somebody else has your key they can send transactions too, including ahead of yours.

Addresses

Every one of these was read back on chain before it was written into js/config.js (node tools/verify-addresses.mjs), and nothing else in the site hardcodes an address.

Morpho Blue0x9D53…1010the lending engine: markets, positions, the flash loan
Bundler30x6478…44a6Morpho's multicall — the one address the transaction goes to
GeneralAdapter10xc5E1…65D6acts only for the wallet that sent the bundle
USDG0x5fc5…d168the chain's dollar, issued by Paxos
Vault V2 factory0x0FBa…803cevery vault whose shares Flit knows to move

How it was checked

Flit is not audited. What exists is a set of suites, each with a control that must fail, and each reproducible from the repository:

45/45

Properties, on the real chain

Each one executed by an eth_call with no to on Robinhood Chain itself — real creation code on the real evaluator, costing nothing and leaving nothing behind. Every refusal has a control proving the same call goes through when the guard is not in the way.

node tools/props.mjs --props
26/26

Deliberate bugs in the Solidity, caught

The model recompiled with one defect at a time, each named for the property that must notice. Six survived the first sweep; those were six holes in the properties, and the properties that close them are in the count above.

node tools/props.mjs --sabotage
15/15

The planner, and a fuzz

The page's own modules planning moves and building their calldata, executed against a model of Morpho that refuses the way Morpho refuses. Includes a 120-draw fuzz over random books, with random time passing and strangers trading in between: 66 moved, 21 refused — and every refusal forced through afterwards, to check the chain refuses it too.

node tools/test-plan.mjs
19/19

Deliberate bugs in the JavaScript, caught

One bug at a time in the planner or the transaction builder, each of which must be caught by the check named for it. Two are declared unobservable, with the measurement that says so.

node tools/sabotage.mjs
40/40

Real moves, on a fork

Books built on a fork of this chain with the owner's own calls, then moved: the loan repaid and re-opened, the collateral re-pledged, the tokens across, the permissions revoked. Every guard has a control, and every revert is classified by its selector rather than counted as a pass.

node tools/test-move.mjs
103/103

Tokens that really accept a permit

Every token Flit lists, asked on the live chain whether it accepts a permit signed with the domain the scanner found — executed, not inferred from a matching hash. Three controls must be refused, and are.

node tools/test-permits.mjs
21/21

The app, clicked through

The real page in a real browser against a fork: connect, read the book, name a destination, sign every signature, send, and check both wallets afterwards.

node tools/e2e.mjs

The numbers on this site are generated from data/ by tools/figures.mjs; a figure that is missing stops the build rather than printing a dash.

Questions

Do I have to trust a contract you wrote?

No, because there isn't one. Flit deploys nothing. The transaction your wallet signs is one call to Morpho's own Bundler3, which was live on this chain before Flit existed. What Flit contributes is the plan: which calls, in which order, for how much.

Is anything sold?

No. Not a share, not a dollar. The collateral is unpledged and pledged again; the loan is repaid and re-opened at the same size; the tokens are transferred. No pool is touched and no price is paid.

Does the new wallet need ether?

No. It signs, and the old wallet sends and pays. Flit also sends the old wallet's ether across, keeping back enough to cover the transaction itself.

What if I mistype the destination?

You cannot: the new wallet has to sign before anything is built, and a signature cannot be produced for an address you do not control. That is the whole reason the proof signature exists.

What does it cost?

Gas, and nothing else. A wallet of four stocks costs about $0.06; one with a loan in it about $0.08. Flit takes nothing and has no fee to take.

Can I move only part of it?

Yes. Every holding and every position has a switch. What Flit will not do is move collateral out from under a debt it is leaving behind, because that is a position anyone may then liquidate — the chain refuses it too.

What happens to my loan's interest rate?

Nothing: it is the same market, with the same rate curve, and the new wallet's debt is the old one to within a rounding unit. What you lose is the position's history, which Morpho does not keep anyway.

Is this audited?

No. Flit has not been audited and nothing here should be read as saying it is. What has been done is on this page, with the command to reproduce each of it, and the contracts Flit calls are Morpho's own.