Signing For Something That Does Not Exist Yet.
#plugs#pendle#strategy
Every signature you have ever made in crypto names something that already exists.
You approve a specific token for a specific contract. You sign a swap through a specific pool. You open a position in a specific market. Whatever you agreed to, the thing you agreed about had an address, that address was deployed before you signed, and the signature is worthless against anything else. This is so obviously how signatures work that nobody has ever thought of it as a limitation. It is the whole security model. A signature that could reach an address you did not name would be a signature you could not trust.
Then you hold something with an expiry date, and the limitation shows up with a bill attached.
A fixed-rate position on Pendle is a bet with a calendar. You buy a principal token below par, it walks to par on a known date, and the gap is your yield. The best version of the trade is levered: PT as collateral on Morpho, borrow against it, buy more PT, loop. Every step of that is signable today, because every market in it has an address.
Then the market matures. The day it does, three things happen at once. The PT stops earning, because it is at par and there is nowhere left for it to walk. The borrow does not stop, because Morpho does not care that your collateral just went inert. And the sensible next move, rolling into the successor market for the same asset, becomes impossible to have pre-signed, because the successor market is deployed by Pendle sometime around the old one expiring. Which is to say: months after you would have wanted to sign for it.
Run the arithmetic on a modest loop. Two hundred thousand of loan asset borrowed against three hundred thousand of matured PT at a six percent borrow rate is thirty-three dollars a day, a thousand a month, against collateral earning exactly nothing. It does not liquidate. It does not alert. It just bleeds, at a rate you chose when the position was good, until a human notices.
The industry has two answers, and you already know both. Watch the calendar and do it by hand at the right hour, on every position, forever. Or hand your keys to a bot that promises to do it for you, which means the bot decides which market your money ends up in, and the bot is a server someone else runs. Every "auto-rollover" product ever shipped is one of these two wearing a nicer interface.
There has never been a third option, because a third option requires a signature that reaches an address that does not exist yet, and that sentence sounds like a contradiction.
It is not a contradiction. It only sounds like one because of a question nobody asks out loud: when you sign a rollover, what are you actually agreeing to?
Not an address. You never cared about the address. You are agreeing to something more like the next market for this same underlying, deployed by the real Pendle factory, expiring after the one I am in. That description is completely precise. Every clause in it is a question a contract can answer. It is a description of something that does not exist yet, but it is a description that can be checked the instant the thing does.
So sign that.
Leave a typed hole in the program where the market's address goes. Write down, as ordinary steps in the plug, the tests any candidate has to pass to fill it. Sign the whole thing, tests included. Then walk away.
When Pendle deploys the successor, the solver hands the program a candidate. Before the program touches that candidate, it runs your tests against it, on-chain, using reads whose targets, Pendle's factory, Morpho's oracle factory, are addresses you signed. Pass, and the roll happens. Fail, and the transaction reverts before a single token moves.
The candidate is untrusted, in the strongest sense. It arrives in the unsigned channel, the same lane a swap route travels in. It could be a contract the solver deployed itself five minutes ago. That is fine, and it is fine for a reason worth stating precisely: the trusted half of the system never has to find the right answer. It only has to recognize a right answer when one is handed to it. Finding is hard, open-ended, and off-chain. Recognizing is a handful of reads and a require.
That asymmetry is the whole design, and it collapses into one sentence you can hold the entire thing by: the solver's only power is failing to answer.
A wrong candidate reverts at the check. A missing candidate leaves the program waiting. There is no candidate that passes the checks and does something you did not sign for, because the checks are what you signed. Which means a bug in the off-chain half, a stale index, a hostile solver, a resolver that ranks badly, is a liveness bug and never a safety bug. It can make your strategy late. It cannot make your strategy wrong.
The word "verification" suggests machinery. There is none.
The checks are ordinary plug steps. Reads, comparisons, requires, composed in the sentence exactly the way every other constraint in every other plug is composed. The hole is a block, Expect an address … End, and everything between the opener and the End is how you handle what shows up. Any predicate you can write as reads and comparisons is a valid constraint on a hole. There is no allowlist of approved checks and no special verification opcode. You author the judgment the same way you author everything else.
Two shapes cover nearly everything you will ever want.
Membership. isValidMarket(candidate) asks Pendle's own factory whether it deployed this contract. Under the hood it is a set the factory maintains of every market it has ever created; the factory is the attestation. This leaves the solver a choice within the set of real markets, and further reads narrow it: same SY as the market you are leaving, expiry later than the one you are in.
Deterministic lookup. Better, wherever a protocol lets you have it. Instead of asking whether a candidate belongs to a set, you compute what the answer must be from inputs you already signed, and require equality. The oracle for the new Morpho market resolves this way. Ask Pendle's oracle factory for the PT-to-asset oracle for this market at this TWAP duration, and require that the candidate market's base feed is that exact address. Zero solver freedom. There is only one right answer, and the program derives it.
The compiler then does the one thing a human would forget. It traces every value that descends from the hole, through every branch of the program, and refuses to compile any spend, any call target, any funded call that is fed by hole-derived bytes on a path where no require has judged that hole first. A hole you open and never judge is not a warning. It is a program that does not exist.
Not every fact about a contract can be checked by another contract, and the design is only honest if it says where that line is.
Pendle has two oracle styles. The TWAP oracle resolves through the chain above: Morpho's factory keeps a public mapping of every oracle it made, the oracle's feed is a public immutable, and Pendle's factory computes the feed address deterministically. Every link is a read against a signed anchor.
The linear-discount oracle is different. It is deployed with a plain CREATE, its factory keeps no record of what it made, only events, and its address is not derivable from anything. Nothing on-chain can attest that a given linear-discount oracle is the real one.
You can check how it is behaving right now. That is not the risk. The risk is what it does in six months, and only code identity bounds future behavior. Code identity is not provable from inside the chain when every instance carries its own immutables. So there is no predicate to write, and the correct thing to do is refuse: markets priced by that oracle cannot be late-bound. Not "can be late-bound with a warning." Cannot.
The rule this leaves behind is the best sentence in the design. Things we cannot prove become things you cannot author, never things that are unsafe. The system's answer to an unverifiable claim is to decline to let you sign it.
An armed program raises the obvious question. What if the successor never shows up?
The program cannot prove non-existence. Nothing on-chain can; there is no read that returns "no such market will ever be deployed." But the program does not need that. It needs a rule for how long to wait, so the plug carries a second branch behind a signed grace window: if no valid candidate has appeared within that many days of expiry, close the position out instead. Flash loan the debt, repay it, withdraw the matured PT, redeem it one-to-one for the underlying, repay the flash loan from the proceeds, and leave what is left sitting in your socket, deleveraged.
This is not a fallback bolted on for safety. It is the economically forced move. A matured position is pure bleed, so if the roll has stalled for any reason at all, no successor yet, a new Pendle factory version outside what you signed, a solver that has stopped answering, closing out beats waiting. Every armed plug has a guaranteed terminal state. Nothing sits forever.
The branch itself is the same primitive. An absent witness loads an empty slot; it never reverts. A conditional on that slot is what routes the program to the close-out. The solver's silence is not an error the program has to survive. It is an input the program was written to handle.
Worth naming the sharp edge, so it is a known property rather than a discovery: a solver could withhold a candidate through the grace window and force the close-out. That is griefing, not theft. You end up flat in your underlying with your debt repaid, and nothing left the position that you did not sign for. We run the solver, and it is still written down here, because a trust model that only works if you do not think about it is not a trust model.
Here is the part that took longest to see, and then could not be unseen.
Somewhere there has to be a thing that finds candidates. It has to know which contracts might be Pendle markets, rank them, try them. The obvious way to build that is to write it: a resolver for Pendle markets, a resolver for Morpho markets, each one re-implementing the predicate the user signed so it can pre-filter. And the obvious way is a trap, because now the rule lives in two places, and the copy in the resolver will drift from the copy in the signature, and the day they disagree is the day a valid roll silently never fires.
So the resolver is not written. It is derived, from two things the product already had.
First: when you build a plug and pick a market from a dropdown, something enumerates the real markets on the chain, ranks them, the ones you hold first, the newest after that, and hands you a bounded list. A hole in a signed program needs exactly that. A witness hole and an options picker are the same query at different binding times. The dropdown binds at sign time, resolved by a person. The hole binds at fire time, resolved by a solver. So a hole declares only a primitive type, an address, a 32-byte key, a number, and inherits everything else from the first input it is coiled into. Coil it into a market slot and it is a market, searched exactly the way that slot's picker has always searched, with the picker's own ranking.
Second: to decide whether a candidate is valid, the solver does not evaluate a copy of your predicate. It fills the candidate into the hole and simulates your actual signed program. If the simulation runs clean through the witness block, the candidate is valid, by definition, because the block is the definition. If a failure lands inside the block, the candidate is rejected, and the solver tries the next one. One predicate, one source of truth, executed in two contexts. There is nothing to drift.
Multiple holes prune in program order. The market being unwound binds first; the successor Pendle market binds second and is judged against the first's tokens; the successor Morpho market binds third and is judged against the second's PT. Earlier holes stay bound while later ones search, so it is a tree with early cutoff, not a cross product. And a program with an unbound hole does not fail. It parks, reading as waiting for a candidate, and wakes the moment the indexer sees a new row of the type it is waiting on.
The rollover plug is the first thing built on this, and it is built from sentences alone.
Read it top to bottom. The expiring Pendle market is the only address it is handed, and it is handed as the plug's Take. From there:
The first block expects a 32-byte key and coils it into a Morpho market slot. It requires that market's collateral to be the PT of the market you are leaving and requires your socket to actually hold collateral in it. That block is what tells the program which position it is unwinding, without you ever naming it.
The second block expects an address and coils it into a Pendle market slot. Valid V6 factory market. Same SY as the one expiring. Expiry strictly later. That is the successor.
The third block expects another 32-byte key and coils it into a Morpho market slot. Collateral is the new PT. Loan token carried over from the market being unwound. Oracle attested by Morpho's factory, and its base feed equal to the PT-to-asset oracle Pendle's factory derives for the successor at a 900-second TWAP. That is the new lending market, and it is pinned down so tightly that only one shape of market can satisfy it.
Then the roll, in one atomic transaction, every amount sized by a read that happened earlier in the same program: flash loan the old debt, repay it, withdraw the matured PT, redeem it for SY, buy the new PT with that SY, supply the new PT as collateral, borrow back what the flash loan is owed, End.
No amount in that program was typed by a human. No address in it was known when it was signed except the one being left behind. Three of the contracts it will transact with may not have existed on the day of the signature. And it is thirty-seven ordinary steps with three ordinary blocks, which anyone who has built a plug can read.
One honest note on the version that is live. It takes the expiring market as an input, which means it covers one rollover. The step to a signature that outlives every epoch is replacing that Take with a fourth hole, an expired Pendle market whose PT my socket holds as collateral, which the same reads already express. Nothing new is required for it. It is a composition, and it is next.
The memory post covered what happens when a plug writes its own history. The chain cannot remember, so the strategy remembers for itself. This is the same move pointed the other direction. The chain cannot tell you what will exist, so the strategy describes it, and holds the future to the description when it arrives.
There is an old distinction in logic between naming a thing and specifying it. A signature has only ever been able to name: this token, this pool, this market, at this moment. A statement about specific things. A hole with tests attached is a specification: every market, whenever it appears, that satisfies these conditions. A statement about every future thing at once, including things nobody has deployed yet, enforced by the same chain that would have enforced the name.
That is a new kind of thing to be able to sign. It is not a Pendle feature. Any market that has not launched, any vault a curator has not opened, any pool for a pair that does not trade yet, is a description away from being something your money can be pre-committed to, safely, with the solver holding no power except silence.
You are not naming the market. You are naming what would make a market acceptable, and letting the program hold you to it.