Signed quotes
A signed quote is the atomic unit of off-chain-to-on-chain pricing in Milky. Each one is a structured message signed by the oracle's keypair that authorizes one specific borrower to open one specific loan with one specific principal against one specific card.
This page explains exactly what a quote contains and how the protocol binds it tightly enough that an attacker can't reuse it for anything other than its intended purpose.
What a quote contains
Every quote is built from the following fields:
- Oracle public key — which oracle signed this quote. Must match an
active entry in the on-chain
AllowedOraclelist. - Quote ID — a unique 32-byte identifier. Used for replay protection.
- Pool — the pool this quote authorizes borrowing against.
- NFT mint — the specific card NFT being collateralized.
- Cert hash — a 32-byte digest of the grading certificate identity, binding the quote to the specific certified card the issuer minted the NFT against.
- Asset type ID — the canonical asset identity (cert + grader + grade), used for exposure tracking.
- Root version — the version number of the Merkle root the protocol was using when this quote was signed.
- FMV (micro-USDC) — the fair market value the oracle determined.
- LTV (basis points) — the maximum loan-to-value the oracle allows.
- Term seconds — the loan duration this quote is valid for.
- Expiry timestamp — Unix timestamp after which the quote is no longer accepted.
- Domain tag — a constant byte string (
BORROW_QUOTE_V1\0) that prevents this signature from being replayed against other Milky signature-verifying instructions.
The signature itself is Ed25519 over the canonical byte representation of the above fields.
How the protocol verifies a quote
The Solana program performs three layered checks at loan creation:
Signature verification
Solana's native Ed25519 program is invoked (via instruction introspection) to verify that the signature on the quote message is valid for the claimed oracle pubkey. The program checks that the introspected Ed25519 instruction's message bytes match the reconstructed quote.
Oracle allowlisting
The signing oracle's pubkey is checked against the
AllowedOraclePDA list. If the oracle has been removed (or deactivated) since the quote was signed, the loan creation fails.Field validation
Each field is validated against on-chain state: the pool matches the account passed, the NFT mint matches, the cert hash and asset type match the proof receipt, the root version is one the chain still recognizes, the LTV is within global and pool ceilings, the principal is within
FMV × LTV / 10,000, the expiry is in the future.
If any check fails, the entire transaction reverts and no loan is created.
Replay protection
Once a quote has been used to create a loan, a UsedQuote PDA is
written with seeds (b"used_quote", oracle_pubkey, quote_id). The
account itself contains minimal data — its mere existence is the
"used" flag.
Any future attempt to use the same (oracle pubkey, quote id) pair for
another loan will fail because the PDA already exists and the
"create-as-init" flag refuses to recreate it.
This means:
- The same signed quote cannot be used to open two loans, even if both attempts are atomically issued by the same wallet.
- An attacker who somehow gets access to a valid quote for someone else's card cannot use it to open a loan against that card more than once.
- The replay-protection record persists indefinitely; it's part of the protocol's audit trail.
Quote expiry
Two distinct expiry checks happen:
- At
loan_create: the program rejects any quote whoseexpiry_ts < current_ts. This is the primary freshness guarantee. - At
loan_draw: the program re-checksexpiry_tsagainst the current cluster time. This closes a "create-now-draw-later" window where a borrower might create the loan with a fresh quote but delay drawing until the price had moved against the lender.
Together with the short default TTL (5 minutes), these two checks ensure that the price the lender effectively underwrites is close to the price the oracle saw.
Pending draw window
After loan_create succeeds, the borrower has a pending draw window
of about 5 minutes (PENDING_WINDOW_SECS = 300). If they don't draw in
that window, the loan can be cancelled and the quote becomes
permanently used (the UsedQuote PDA persists). This prevents
abandoned loan-create transactions from cluttering on-chain state
indefinitely.
What an attacker cannot do with a leaked quote
Because every quote binds to specific accounts and a one-shot identifier, even a leaked quote has limited blast radius. An attacker who obtains a signed quote cannot:
- Open a loan against a different card. The NFT mint, cert hash, and asset type are all in the signed message and validated.
- Open a loan in a different pool. The pool address is signed.
- Open a loan for a different borrower. The signer of the
loan_createtransaction is the borrower; the quote authorizes the loan but does not authorize the actor. - Reuse the quote for a second loan against the same card. The
UsedQuotePDA blocks this. - Use the quote after the expiry timestamp.
- Use the signature against a different protocol or instruction.
The domain tag (
BORROW_QUOTE_V1\0) ensures it can only be a borrow quote.
Read next
- Merkle allowlist — the second piece the loan-create transaction needs alongside the quote.
- Trust assumptions — what's still trusted about the off-chain side.