diff --git a/TABLE-OF-CONTENTS.md b/TABLE-OF-CONTENTS.md index c4b9e65b..e10ed6a5 100644 --- a/TABLE-OF-CONTENTS.md +++ b/TABLE-OF-CONTENTS.md @@ -2,11 +2,11 @@ ## Prelude -[Copyright and license](prelude/copyright.md), [dedication](prelude/dedication.md), [acknowledgements](prelude/acknowledgements.md), [about the author](prelude/about-the-author.md), and [about the technical reviewers](prelude/about-the-technical-reviewers.md). +[Copyright and license](prelude/copyright.md), [dedication](prelude/dedication.md), [about the author](prelude/about-the-author.md), and [about the technical reviewers](prelude/about-the-technical-reviewers.md). ## [Chapter 1: Introduction](prelude/introduction.md) -## Part I: The Foundations +## Part I: The Fundamentals ### Solana Building Blocks @@ -29,15 +29,21 @@ Each chapter walks one complete, tested program from the [companion repository]( - [Chapter 11: Escrow](building-financial-products-in-solana/escrow.md) - two strangers swap without trusting each other - [Chapter 12: Token Fundraiser](building-financial-products-in-solana/token-fundraiser.md) - onchain crowdfunding -- [Chapter 13: The Order Book Exchange](building-financial-products-in-solana/order-book.md) - a traditional NYSE/NASDAQ/CME style exchange -- [Chapter 14: The Constant-Product AMM](building-financial-products-in-solana/constant-product-amm.md) - instant swaps priced by a formula -- [Chapter 15: The Prop AMM](building-financial-products-in-solana/prop-amm.md) - a market maker quoting its own capital at oracle prices -- [Chapter 16: The Betting Market](building-financial-products-in-solana/betting-market.md) - a parimutuel pool with no bookmaker +- [Chapter 13: The Betting Market](building-financial-products-in-solana/betting-market.md) - a parimutuel pool with no bookmaker +- [Chapter 14: The Order Book Exchange](building-financial-products-in-solana/order-book.md) - a traditional NYSE/NASDAQ/CME style exchange +- [Chapter 15: The Constant-Product AMM](building-financial-products-in-solana/constant-product-amm.md) - instant swaps priced by a formula +- [Chapter 16: The Prop AMM](building-financial-products-in-solana/prop-amm.md) - a market maker quoting its own capital at oracle prices - [Chapter 17: Lending and Borrowing](building-financial-products-in-solana/lending.md) - deposits, collateral, interest, liquidation, and cranks - [Chapter 18: The Vault Strategy](building-financial-products-in-solana/vault-strategy.md) - an onchain index fund - [Chapter 19: Perpetual Futures](building-financial-products-in-solana/perpetual-futures.md) - leveraged bets on a price; the capstone +## [Chapter 20: Where to Go From Here](conclusion/where-to-go-from-here.md) + ## Appendices - [Glossary](appendices/glossary.md) - Index (print edition) + +## Postlude + +[Acknowledgements and credits](postlude/acknowledgements.md). diff --git a/WRITING-PLAN.md b/WRITING-PLAN.md index 67e5650a..cafdca6f 100644 --- a/WRITING-PLAN.md +++ b/WRITING-PLAN.md @@ -8,9 +8,12 @@ How the manuscript gets produced, verified, reviewed, and kept alive. The table - Diagrams are the longest-lived medium. Every chapter budget includes them: account map, state machine, flow. - Style is inherited wholesale from the [companion AI skill](https://github.com/quicknode/solana-finance-claude-plugin): onchain and offchain unhyphenated, "instruction handlers" for the functions and "instructions" for their input, "minor unit" not "base unit", one name per role enforced within each chapter, concrete two-asset examples, real handler names in lifecycle prose, persona-driven walkthroughs. - Banned phrases, enforced by `scripts/lint-manuscript.mjs` (`npm run lint`) over the manuscript files: "the oracle problem", "instrument" (say "financial software" or "program"), the hyphenated chain words, and em-dashes. The lint also rejects negative framing of offchain truth (any "cannot verify the price" phrasing): the manuscript teaches the positive mechanism, checking a fact's provenance, freshness, and confidence against a source the program trusts. +- Numbers from outside the companion repository (fees, incident losses, market sizes) name their source in the text, link the primary publisher, and say where and when they apply. A figure that contradicts a reader's own experience is the normal case rather than the exception, because a cost that is typical in one country is unheard of in another: state the scope, so a reader whose transfers have all been domestic can check the claim in one click instead of concluding the book made it up. - Nothing in the book disparages other Solana resources or projects. Where other content is referenced, it is as a neutral pointer, and preferably to a primary source (a project's own docs or repo) rather than to an aggregator or list. - Chapter titles are numbered traditional short names: "Chapter 3: Tokens", "Chapter 11: Escrow", "Chapter 9: The Adversary". Headings *inside* a chapter carry the content, so a reader skimming only the headings learns the chapter's actual claims: "A Mint Is a Factory, and the Mint Authority Is Its Boss", not "Mint Authority". A heading is close to a summary of its section, and has to work read alone, because the printed table of contents lists every one of them out of context. It states what the reader gets, not the mechanism that delivers it: "Seeds Make Data Findable", not "Seeds Make Addresses Derivable", because derivability is how it works and findability is what it buys. Title Case throughout. No numbered headings. - Headings never name characters, in any part of the book. A heading is close to a summary of its section and has to work read alone, so it states the general mechanism and the characters stay in the prose beneath: "A User's Public Key Is Their Address", not "Alice's Public Key Is Her Address"; "A Crossing Ask Fills Against the Resting Bid", not "Carol's Ask at 245 Crosses Bob's Bid". The program chapters are walkthroughs, so their headings still track the story's order and keep the specific mechanism ("The Shorted Asset Rallies and the Position Becomes Liquidatable"); it is the proper nouns that go, never the specificity. Where a role is what acts, name the role: the maker, the taker, the market authority, a liquidator. `scripts/lint-manuscript.mjs` enforces this across the manuscript. +- Handler names follow one rule across every program: **if the domain has a word for it, use the domain's word; if the handler only stamps out a container, it is `initialize_`.** The domain verbs are what make the code read like finance and they come in pairs, so `make_offer()`/`take_offer()`, `open_position()`/`close_position()`, `place_order()`/`cancel_order()`, `borrow`, `repay`, `liquidate`, `contribute`, `refund` all stay. Setup has no domain meaning and used to have four names (`init_`, `create_`, `initialize_`, and a bare `initialize`), so it is now always `initialize_x()`, naming what it initializes. The test is whether the handler moves money or takes a market action, or only writes parameters into a new account. A third family, `add_x()`, extends a container that already exists (`add_liquidity()`, `add_outcome()`, `add_asset()`) and is left alone. +- **PDA** names an address; **account** names the data sitting at it. They are not synonyms, so pick by what the sentence is about: PDA where the subject is derivation, seeds, authority, ownership, or signing ("the market PDA signs the transfers", "the offer PDA is derived from the maker's address"), and account where the subject is fields, balances, rent, creation, or closure ("the position account closes on exit", "each asset config account records a mint"). An account map lists accounts, so its caption says account, except for a dataless authority, which really is only an address. The shorthand "the config is a PDA (seed `"config"`) holding the admin" stays allowed where an account-model section is giving the seeds, because there the derivation is the point. - A chapter's What You Learnt list states what the reader now knows, not what the characters did, so it is written generally even when the chapter that taught it was a story: "a user's account holds their balance of SOL", not "Alice's holds her balance". The same goes for its opening line. - A section lives in the chapter its subject belongs to, not the chapter where it was first needed. If a section would sit equally well in a different chapter, it is in the wrong one: rent is a property of every account, so it belongs with accounts, even though PDAs is where the escrow's rent lifecycle first made it matter. Each concept is taught once, in full, in the chapter that owns it; everywhere else it gets a clause and that chapter's name, never a second explanation. When a section turns out to be two things, split it and send each half home. - A forward reference names the thing being deferred, and never routes through a concept the reader has not met yet: "covered later in this part", never "covered with PDAs later in this part", because a reader meeting rent in the opening pages has never heard of a PDA and now has two unknowns instead of one. Most deferrals need no pointer at all: name the concept, say what it does in a clause, and move on. @@ -18,7 +21,7 @@ How the manuscript gets produced, verified, reviewed, and kept alive. The table - Characters are introduced at the point they first act, never in a separate cast section. - Boxouts (rendered as blockquotes) hold self-contained asides a reader can skip without losing the thread: War Stories, origin stories, and mechanism deep-dives like the flash-loan and keypair-collision boxes. - Facts that form a grid go in a table (the cluster table, the token-extensions table, the rounding-direction table), not in a comma-packed paragraph. -- Plain language. Define things by what they concretely are, not by abstract category: an account is "some data - a Rust struct - with an address", never "the unit of state". If a definition would fit equally well in a book about a different subject, it is too vague. Prefer the short common word to the impressive one. +- Plain language. Define things by what they concretely are, not by abstract category: an account is "some data - a Rust struct - with an address", never "the unit of state". If a definition would fit equally well in a book about a different subject, it is too vague. Prefer the short common word to the impressive one: "the deployed program", not "the deployed bytecode"; "compiled code", not "compiled bytecode". Reach for the precise technical term only where the precision is the point. - Example assets are USDC and real-world assets: stocks like NVDAx, TSLAx, and SPCXx. Never meme tokens. - Talk to readers, never about the book's construction. No sentences explaining why a chapter is organized, sized, or placed the way it is ("each section is short on purpose", "that is why it comes first"). If a sentence is addressed to the author, editor, or reviewer rather than the reader, delete it. - Emphasis is meaningful. Titles of books and other works are italicized (*TCP/IP Illustrated*). Bold marks the load-bearing claim of a passage and canonical terms on first use ("the chain is the **platform**"). Use both wherever they help the reader, and never as decoration. @@ -37,7 +40,7 @@ Each program chapter runs the same eight beats: - **Testing considerations** - the property or scenario tests from the example's own suite that would catch each failure mode, described without dwelling on test libraries (the tooling is covered once, in Operating Financial Software). - **What you learnt** - a closing summary of the concepts the chapter taught and the traditional-finance roles and processes the program replaced. -The chapters in the two foundation parts (Solana Building Blocks; Financial Programming Concepts in Solana) use a lighter per-section template: claim, mechanism, consequence, War Story where applicable, and a short "What You Learnt" list closing each chapter. +The chapters in the two Fundamentals sections (Solana Building Blocks; Financial Programming Concepts in Solana) use a lighter per-section template: claim, mechanism, consequence, War Story where applicable, and a short "What You Learnt" list closing each chapter. ## Generation Pipeline (per chapter) @@ -68,14 +71,15 @@ Conventions already settled, which the visual pass must preserve: - **No arrow or leader passes through a label.** A label sitting on its own arrow's path is the most common defect after re-anchoring; the white halo cannot hide a stroke crossing a word. Labels sit beside their arrows, never on them. - **Struct boxes show real fields; text never escapes a box.** Every `key: value` line in a data-PDA box is a verbatim field from the chapter's struct definition with the story's value; notes that aren't fields are italic annotations. All text keeps ≥6px padding inside its rect — shrink the field/handler font in a crowded box rather than letting text touch or cross the border. - **Accounts stack in columns beneath their owner's dot.** A person's column reads top to bottom: wallet, their token account(s), their per-program user account, their bets/orders/positions. Program-wide accounts stack beneath the program's dot: program box, config, event/market/pool PDA, outcomes/reserves, vaults. Box top positions stay fixed across every figure in a chapter. -- **People are wallet rectangles**: a bordered rectangle with a head-and-shoulders glyph, a bold left-aligned name title (role labels like "Alice (maker)" in program chapters, "Alice's Account" in the foundation parts), and the person's SOL balance beneath. Lamport and token arrows that touch a person start or end at this rectangle's edge, never at a bare curve dot or a stick figure. Instruction handlers listed inside a program's rectangle are left-aligned, one per line, always with parens: `place_bet()`. +- **People are wallet rectangles**: a bordered rectangle with a head-and-shoulders glyph, a bold left-aligned name title (role labels like "Alice (maker)" in program chapters, "Alice's Account" in the Fundamentals chapters), and the person's SOL balance beneath. Lamport and token arrows that touch a person start or end at this rectangle's edge, never at a bare curve dot or a stick figure. Instruction handlers listed inside a program's rectangle are left-aligned, one per line, always with parens: `place_bet()`. - **Each account box carries its kind's icon** beside the title, from the fixed vocabulary: head-and-shoulders person for wallets, piggy bank for token accounts and vaults, gear for programs, table for data-struct PDAs, a columned mint building for mints. One vocabulary book-wide; the exact glyph paths live in the finance skill's DIAGRAMS.md. - **Every address dot has an account rectangle.** A dot with nothing hanging off it says the address exists but has no account, which is never what a figure means. Wherever a dot is drawn, the account at that address is drawn too, rounded, with its icon and title, so a figure never teaches that one kind of address has accounts and another does not. The one exception is a legend key, where a lone dot beside its explanatory line is the whole point. - **Rounded corners mean accounts, and nothing else.** Every box that is not an account (transaction envelopes, instruction rows, the transaction log, state-machine states, seed chips, failed or hypothetical calls, offchain parties, annotation panels) draws with square corners, so a reader can tell at a glance what lives on the chain as an account and what does not. -- **Address dots have white centers, on-curve and off-curve alike**: `fill="#fff" stroke="#111" stroke-width="2"`. A dot **on** the curve carries no label — sitting on the curve is what says the address is a public key. An off-curve dot carries its seed list and nothing else (`"OFFER" + MAKER'S ADDRESS + ID`), with no `SEEDS:` prefix. The book teaches both conventions once, in prose, where Alice and Bob and their token accounts first appear. +- **Address dots have white centers, on-curve and off-curve alike**: `fill="#fff" stroke="#111" stroke-width="2"`. A dot **on** the curve carries no label — sitting on the curve is what says the address is a public key. An off-curve dot carries its seed list and nothing else (`"OFFER" + MAKER'S ADDRESS + ID`), with no `SEEDS:` prefix. The deriving program is never in the label, because every seed list leaves it implied: an ATA reads `ALICE'S ADDRESS + TOKEN PROGRAM + USDC MINT`, its three real seeds, and not the Associated Token Program that derived it. The book teaches both conventions once, in prose, where Alice and Bob and their token accounts first appear. - **Public-key accounts hug their dot, and each column packs under its own wallet.** Put the dot where the curve comes closest to the box (its right edge below the curve, its left edge above), so the box sits ~6px away rather than floating; if a box still can't reach its dot, shrink every font in the figure one step. Columns start at different heights, following their wallets down the curve — never pad a column to align a row across people. - **Every account box reads icon, title, rule, fields.** Icon and title are centred on each other, with a hairline beneath them across the box's inner width. In a step figure, any value that step changes is **bold** — PDA field lines and token balances alike. - **Tokens move directly between accounts.** An arrow never stops on a panel and resumes; fees, gates and math go in an italic annotation beside it, and a field a step changes is shown as that field's value inside its own PDA, never as a floating callout. An arrow's label is the amount actually transferred. +- **Arrows are for movement, so instruction routing gets none.** A transaction box lists its instructions and each row names the program it is addressed to ("Instruction 1 → System Program"); no arrow is drawn from those rows to the programs or accounts the instruction touches. An arrow in these figures says something moved, and a second kind of arrow pointing at every account a handler writes sends the reader hunting for a transfer that never happened. Connectors that are not token movement (lamports, rent, authority, a program's write into the transaction log) stay dashed grey. - **Seeds are never drawn inside an account's rectangle**, because seeds are not part of the account struct. They are labeled near the small address circle that sits above or beside the account box. - **A custom PDA's rectangle shows its struct as `key: value`, one field per line**, using the walkthrough's story values where the chapter has them. - **Every walkthrough step ("Alice does X", "Bob does Y") is followed by a diagram** showing the token movements, account creations and closures, and value changes that step caused. @@ -101,8 +105,8 @@ Steps currently missing their per-step diagram, to be drawn in the visual pass: ## Milestones - **Voice samples first** (drafted): the Introduction, Escrow, and The Order Book Exchange lock voice, depth, and template. -- The remaining program chapters, one per example, in table-of-contents order (all nine drafted: Escrow, Token Fundraiser, The Order Book Exchange, The Constant-Product AMM, The Prop AMM, The Betting Market, Lending and Borrowing, The Vault Strategy, Perpetual Futures). -- The two foundation parts (Solana Building Blocks; Financial Programming Concepts in Solana), with technical reviewers engaged per chapter as each completes. +- The remaining program chapters, one per example, in table-of-contents order (all nine drafted: Escrow, Token Fundraiser, The Betting Market, The Order Book Exchange, The Constant-Product AMM, The Prop AMM, Lending and Borrowing, The Vault Strategy, Perpetual Futures). They run as three groups of three: custody and pooling, then market making, then credit and leverage. A chapter's place is set by what it assumes the reader already has, so The Betting Market sits third, where its only prerequisite is Escrow. +- The two Fundamentals sections (Solana Building Blocks; Financial Programming Concepts in Solana), with technical reviewers engaged per chapter as each completes. - The remaining appendices. The Glossary is drafted. - The placeholder front-matter pages last. - A single visual pass producing all diagrams in one consistent style. diff --git a/appendices/glossary.md b/appendices/glossary.md index 7946d9db..09ded08b 100644 --- a/appendices/glossary.md +++ b/appendices/glossary.md @@ -156,7 +156,7 @@ The input a transaction carries to a program: which handler to run, which accoun ### instruction handler -The onchain function that processes an instruction. This book always says "instruction handler" for the function and "instruction" for its input, because tooling that uses one word for both confuses every newcomer. +The onchain function that processes an instruction. This book always says "instruction handler" for the function and "instruction" for its input, because tooling that uses one word for both confuses every newcomer. Most other material does use one word: "this program has five instructions" means five functions, and Anchor's generated interface file lists a program's functions under `instructions`. Read those as instruction handlers. ### invariant @@ -312,7 +312,7 @@ A program's address: the account instructions are addressed to. The compiled cod ### ProgramData -The account holding a deployed program's compiled bytecode, the slot it was last deployed in, and its upgrade authority. Under Solana's upgradeable loader the account at the program's address is only a 36-byte stub pointing here, so a deployed program occupies two accounts; newer loaders fold them back into one. +The account holding a deployed program's compiled code, the slot it was last deployed in, and its upgrade authority. Under Solana's upgradeable loader the account at the program's address is only a 36-byte stub pointing here, so a deployed program occupies two accounts; newer loaders fold them back into one. ### proprietary AMM (prop AMM) diff --git a/building-financial-products-in-solana/betting-market.md b/building-financial-products-in-solana/betting-market.md index 7acffd86..6891fab4 100644 --- a/building-financial-products-in-solana/betting-market.md +++ b/building-financial-products-in-solana/betting-market.md @@ -1,4 +1,4 @@ -# Chapter 16: The Betting Market +# Chapter 13: The Betting Market What is going to be the top-grossing film this month? You have an opinion, and so does everyone else. This chapter builds the program that lets everyone put money on it: a **parimutuel** betting market, the kind with no bookmaker. Nobody sets odds. The odds *are* the pool: everyone's stakes go into one vault, and when the event resolves, the winners split the losers' money in proportion to how much they staked. Racetracks have run on this model since the 1870s, because the house never takes a position; it just takes a fee. @@ -29,7 +29,7 @@ Finally, each bettor gets a **user** PDA indexing their open bet accounts, cappe ## The Operator Creates the Market -Maria is the admin. Her key is recorded in the program's config, and only she can create events, add outcomes, settle, and cancel; her revenue is a 2% fee, charged only on the losing pool. She runs `initialize_config()` once, naming herself admin, USDC as the token, herself as fee recipient, and 200 basis points (2%) as the fee. For this month's market she runs `create_event()` with a description ("top-grossing film this month"), then `add_outcome()` twice: "Toy Story 5" and "Backrooms". Every one of these instruction handlers checks her signature against the config's stored admin via a `has_one` constraint and rejects anyone else with `Unauthorized`; the test `test_only_admin_can_create_event` pins it. +Maria is the admin. Her key is recorded in the program's config, and only she can create events, add outcomes, settle, and cancel; her revenue is a 2% fee, charged only on the losing pool. She runs `initialize_config()` once, naming herself admin, USDC as the token, herself as fee recipient, and 200 basis points (2%) as the fee. For this month's market she runs `initialize_event()` with a description ("top-grossing film this month"), then `add_outcome()` twice: "Toy Story 5" and "Backrooms". Every one of these instruction handlers checks her signature against the config's stored admin via a `has_one` constraint and rejects anyone else with `Unauthorized`; the test `test_only_admin_can_initialize_event` pins it. One rule protects the bettors: **the outcome list locks the moment betting starts**. `add_outcome()` fails with `BettingAlreadyStarted` once any money is in the pool, so the field of choices cannot change out from under someone who already bet. @@ -118,3 +118,5 @@ The concepts you now have: - **An exit from every state.** Open events can be canceled and refunded in full, so a burned-down film festival strands nobody's stake. The pooled payout formula and the honest look at offchain truth both return at full scale in the lending and perpetual futures chapters, where the trusted signature is a price feed and the settlement math runs continuously instead of once. + +The next chapter puts the shape on a trading floor. Everyone here bet into one pool and took a share of it; from here on, the parties quote prices at each other instead, and the price stops being a pool's arithmetic and starts being a commitment somebody posted. diff --git a/building-financial-products-in-solana/constant-product-amm.md b/building-financial-products-in-solana/constant-product-amm.md index 43033a4f..3ec4a98e 100644 --- a/building-financial-products-in-solana/constant-product-amm.md +++ b/building-financial-products-in-solana/constant-product-amm.md @@ -1,4 +1,4 @@ -# Chapter 14: The Constant-Product AMM +# Chapter 15: The Constant-Product AMM The order book chapter built an exchange where every price is a commitment someone posted. This chapter builds the other kind: an **automated market maker**, where nobody posts prices at all. Every incoming order fills instantly, at a price determined by a formula, against tokens supplied by **liquidity providers** who earn a fee on every trade. That fee is not free money, and by the end of the chapter you will be able to say exactly why, with a number attached. @@ -16,11 +16,11 @@ The spot price is just the ratio Y ÷ X, and every trade moves it: buying ACME r And the truth about K: **K is constant within a single trade and grows over time.** Deposits grow it (more of both tokens, same ratio). The liquidity providers' share of every fee stays in the pool and grows it. And the deeper K gets, the less any individual trade moves the price. Watch K in the story below; it never once stays still. -![The constant-product curve: a buy slides the pool along X × Y = K, and the spot price is the slope of the curve at the pool's current point.](figures/amm-curve.svg) +![Three buys of 100 USDC each, a panel apiece: every trade slides the pool up the curve X × Y = K, and the same money buys 40.0 ACME, then 32.7, then 27.3, as the spot price climbs from $2.25 to $4.00.](figures/amm-curve.svg) ## The Account Model: One Config, One Pool per Pair, and a Virtual Fee Ledger -![The account map: the config PDA, the pool config PDA for ACME/USDC, and the pool authority PDA that owns the two vaults and the LP mint, whose receipts sit in LP token accounts held by Maria and Alice.](figures/amm-account-map.svg) +![The account map: the config account, the pool config account for ACME/USDC, and the pool authority PDA that owns the two vaults and the LP mint, whose receipts sit in LP token accounts held by Maria and Alice.](figures/amm-account-map.svg) The **config** is a singleton PDA (seed `"config"`) holding the `admin`, the trading `fee` in basis points, and the `admin_share_bps`, the slice of each fee that belongs to the admin rather than the LPs. One config per deployed program mirrors how real venues ship. @@ -34,7 +34,7 @@ The subtle fields are `admin_fees_owed_a` and `admin_fees_owed_b` on the pool co Maria operates the AMM and is launching ACME today. She earns twice: an admin share of every trading fee, and LP fees on the liquidity she provides herself. Like every character in this book she starts with one SOL and 1,000 USDC; she also holds 400 ACME of founder stock. -Maria runs `create_config()` with a fee of 30 basis points and an admin share of 1,667 basis points, approximately one sixth of each fee, the classic Uniswap-style split. Then `create_pool()` for ACME/USDC, which creates the pool config, the authority, the LP mint, and the two empty vaults. +Maria runs `initialize_config()` with a fee of 30 basis points and an admin share of 1,667 basis points, approximately one sixth of each fee, the classic Uniswap-style split. Then `initialize_pool()` for ACME/USDC, which creates the pool config, the authority, the LP mint, and the two empty vaults. Empty pools cannot price anything, so Maria seeds it. She believes a fair launch price is $2.25, so she runs `deposit_liquidity()` with her 400 ACME and 900 USDC, the founder stock and nine-tenths of her cash. **The first deposit sets the price**, because the ratio she deposits *is* the ratio. X = 400, Y = 900, K = 360,000. diff --git a/building-financial-products-in-solana/escrow.md b/building-financial-products-in-solana/escrow.md index 8723c33d..bf1c4273 100644 --- a/building-financial-products-in-solana/escrow.md +++ b/building-financial-products-in-solana/escrow.md @@ -16,7 +16,7 @@ Traditional finance solves this with a trusted third party. Alice sends her fund ![Traditional escrow: Alice and Bob each send funds to a third party in the middle, which forwards them onward minus the slice it keeps for itself.](figures/escrow-traditional-escrow.svg) -A Solana program replaces the third party. The program holds Alice's tokens under rules that are the deployed bytecode: nobody, including the program's author, can move them except along the paths the code defines. There is no operator in this program at all: whoever deploys it earns nothing from it, takes no fee, and holds no key that matters, which is exactly the point. Each side receives one hundred percent of what the other side offered. Solana's transaction fees still apply, but they are a small fraction of a cent regardless of the size of the trade. +A Solana program replaces the third party. The program holds Alice's tokens under rules that are the deployed program itself: nobody, including the program's author, can move them except along the paths the code defines. There is no operator in this program at all: whoever deploys it earns nothing from it, takes no fee, and holds no key that matters, which is exactly the point. Each side receives one hundred percent of what the other side offered. Solana's transaction fees still apply, but they are a small fraction of a cent regardless of the size of the trade. ![The same swap through the escrow program: both sides receive the full amount, and the box in the middle is a program, not a company.](figures/escrow-program-swap.svg) @@ -126,7 +126,7 @@ The testing section of Financial Programming Concepts in Solana builds the gener ## What You Learnt from Building the Escrow -In traditional finance, an escrow means a trusted third party: a company that holds both sides' assets, charges one to six percent for the privilege, and must itself be trusted not to walk away. This chapter replaced that company with a program whose rules are the deployed bytecode, whose operator earns nothing and holds no key that matters, and whose fee is a fraction of a cent. +In traditional finance, an escrow means a trusted third party: a company that holds both sides' assets, charges one to six percent for the privilege, and must itself be trusted not to walk away. This chapter replaced that company with a program whose rules are its own deployed code, whose operator earns nothing and holds no key that matters, and whose fee is a fraction of a cent. The concepts you now have: diff --git a/building-financial-products-in-solana/figures/amm-account-map.svg b/building-financial-products-in-solana/figures/amm-account-map.svg index 2c31e7c7..674c5b9d 100644 --- a/building-financial-products-in-solana/figures/amm-account-map.svg +++ b/building-financial-products-in-solana/figures/amm-account-map.svg @@ -27,8 +27,8 @@ - ATA PROGRAM + - MARIA’S ADDRESS + + MARIA’S ADDRESS + + TOKEN PROGRAM + ACME MINT @@ -38,8 +38,8 @@ - ATA PROGRAM + - MARIA’S ADDRESS + + MARIA’S ADDRESS + + TOKEN PROGRAM + USDC MINT @@ -49,8 +49,8 @@ - ATA PROGRAM + - MARIA’S ADDRESS + + MARIA’S ADDRESS + + TOKEN PROGRAM + LP MINT @@ -69,8 +69,8 @@ - ATA PROGRAM + - ALICE’S ADDRESS + + ALICE’S ADDRESS + + TOKEN PROGRAM + ACME MINT @@ -80,8 +80,8 @@ - ATA PROGRAM + - ALICE’S ADDRESS + + ALICE’S ADDRESS + + TOKEN PROGRAM + USDC MINT @@ -91,8 +91,8 @@ - ATA PROGRAM + - ALICE’S ADDRESS + + ALICE’S ADDRESS + + TOKEN PROGRAM + LP MINT @@ -111,8 +111,8 @@ - ATA PROGRAM + - BOB’S ADDRESS + + BOB’S ADDRESS + + TOKEN PROGRAM + USDC MINT @@ -131,8 +131,8 @@ - ATA PROGRAM + - CAROL’S ADDRESS + + CAROL’S ADDRESS + + TOKEN PROGRAM + USDC MINT @@ -189,8 +189,8 @@ - ATA PROGRAM + POOL - AUTHORITY + ACME MINT + POOL AUTHORITY + + TOKEN PROGRAM + ACME MINT pool_a: the pool’s ACME @@ -200,8 +200,8 @@ - ATA PROGRAM + POOL - AUTHORITY + USDC MINT + POOL AUTHORITY + + TOKEN PROGRAM + USDC MINT pool_b: the pool’s USDC diff --git a/building-financial-products-in-solana/figures/amm-bob-swap.svg b/building-financial-products-in-solana/figures/amm-bob-swap.svg index ffdc0a51..1187f30f 100644 --- a/building-financial-products-in-solana/figures/amm-bob-swap.svg +++ b/building-financial-products-in-solana/figures/amm-bob-swap.svg @@ -24,8 +24,8 @@ - ATA PROGRAM + - MARIA’S ADDRESS + + MARIA’S ADDRESS + + TOKEN PROGRAM + ACME MINT @@ -33,8 +33,8 @@ - ATA PROGRAM + - MARIA’S ADDRESS + + MARIA’S ADDRESS + + TOKEN PROGRAM + USDC MINT @@ -42,8 +42,8 @@ - ATA PROGRAM + - MARIA’S ADDRESS + + MARIA’S ADDRESS + + TOKEN PROGRAM + LP MINT @@ -57,8 +57,8 @@ - ATA PROGRAM + - ALICE’S ADDRESS + + ALICE’S ADDRESS + + TOKEN PROGRAM + ACME MINT @@ -66,8 +66,8 @@ - ATA PROGRAM + - ALICE’S ADDRESS + + ALICE’S ADDRESS + + TOKEN PROGRAM + USDC MINT @@ -75,8 +75,8 @@ - ATA PROGRAM + - ALICE’S ADDRESS + + ALICE’S ADDRESS + + TOKEN PROGRAM + LP MINT @@ -90,8 +90,8 @@ - ATA PROGRAM + - CAROL’S ADDRESS + + CAROL’S ADDRESS + + TOKEN PROGRAM + USDC MINT @@ -108,8 +108,8 @@ - ATA PROGRAM + - BOB’S ADDRESS + + BOB’S ADDRESS + + TOKEN PROGRAM + USDC MINT @@ -119,8 +119,8 @@ - ATA PROGRAM + - BOB’S ADDRESS + + BOB’S ADDRESS + + TOKEN PROGRAM + ACME MINT @@ -178,8 +178,8 @@ - ATA PROGRAM + POOL - AUTHORITY + ACME MINT + POOL AUTHORITY + + TOKEN PROGRAM + ACME MINT pool_a: the pool’s ACME @@ -189,8 +189,8 @@ - ATA PROGRAM + POOL - AUTHORITY + USDC MINT + POOL AUTHORITY + + TOKEN PROGRAM + USDC MINT pool_b: the pool’s USDC diff --git a/building-financial-products-in-solana/figures/amm-curve.svg b/building-financial-products-in-solana/figures/amm-curve.svg index 67d92ce6..6fb36844 100644 --- a/building-financial-products-in-solana/figures/amm-curve.svg +++ b/building-financial-products-in-solana/figures/amm-curve.svg @@ -1,33 +1,56 @@ - + - - - - Y: USDC in pool - X: ACME in pool - - - X × Y = K = 360,000 - - - - 400 - 900 - - - before - - after the buy - X = 300, Y = 1,200 (K unchanged) - - - a buy slides the pool along the curve: - USDC in, ACME out - - - slope = spot price = $2.25 + USDC in the pool runs up each axis, ACME in the pool runs right, and every point sits on X × Y = K = 360,000. Fees are left out here. + + 1. first buy + 100 USDC in, 40.0 ACME out + + + + + + + + + 1,000 + 360 + spot $2.25 to $2.78 + this buyer paid $2.50 per ACME + + + 2. second buy + 100 USDC in, 32.7 ACME out + + + + + + + + + 1,100 + 327 + spot $2.78 to $3.36 + this buyer paid $3.06 per ACME + + + 3. third buy + 100 USDC in, 27.3 ACME out + + + + + + + + + 1,200 + 300 + spot $3.36 to $4.00 + this buyer paid $3.67 per ACME + diff --git a/building-financial-products-in-solana/figures/amm-first-deposit.svg b/building-financial-products-in-solana/figures/amm-first-deposit.svg index 9f335fe8..e71b0586 100644 --- a/building-financial-products-in-solana/figures/amm-first-deposit.svg +++ b/building-financial-products-in-solana/figures/amm-first-deposit.svg @@ -28,8 +28,8 @@ - ATA PROGRAM + - MARIA’S ADDRESS + + MARIA’S ADDRESS + + TOKEN PROGRAM + ACME MINT @@ -39,8 +39,8 @@ - ATA PROGRAM + - MARIA’S ADDRESS + + MARIA’S ADDRESS + + TOKEN PROGRAM + USDC MINT @@ -50,8 +50,8 @@ - ATA PROGRAM + - MARIA’S ADDRESS + + MARIA’S ADDRESS + + TOKEN PROGRAM + LP MINT @@ -69,8 +69,8 @@ - ATA PROGRAM + - ALICE’S ADDRESS + + ALICE’S ADDRESS + + TOKEN PROGRAM + ACME MINT @@ -78,8 +78,8 @@ - ATA PROGRAM + - ALICE’S ADDRESS + + ALICE’S ADDRESS + + TOKEN PROGRAM + USDC MINT @@ -87,8 +87,8 @@ - ATA PROGRAM + - ALICE’S ADDRESS + + ALICE’S ADDRESS + + TOKEN PROGRAM + LP MINT @@ -102,8 +102,8 @@ - ATA PROGRAM + - BOB’S ADDRESS + + BOB’S ADDRESS + + TOKEN PROGRAM + USDC MINT @@ -117,8 +117,8 @@ - ATA PROGRAM + - CAROL’S ADDRESS + + CAROL’S ADDRESS + + TOKEN PROGRAM + USDC MINT @@ -169,8 +169,8 @@ - ATA PROGRAM + POOL - AUTHORITY + ACME MINT + POOL AUTHORITY + + TOKEN PROGRAM + ACME MINT pool_a: the pool’s ACME @@ -180,8 +180,8 @@ - ATA PROGRAM + POOL - AUTHORITY + USDC MINT + POOL AUTHORITY + + TOKEN PROGRAM + USDC MINT pool_b: the pool’s USDC diff --git a/building-financial-products-in-solana/figures/betting-account-map.svg b/building-financial-products-in-solana/figures/betting-account-map.svg index 980e5418..9e0be578 100644 --- a/building-financial-products-in-solana/figures/betting-account-map.svg +++ b/building-financial-products-in-solana/figures/betting-account-map.svg @@ -19,8 +19,8 @@ - ATA PROGRAM + - ALICE’S ADDRESS + + ALICE’S ADDRESS + + TOKEN PROGRAM + USDC MINT @@ -61,8 +61,8 @@ - ATA PROGRAM + - BOB’S ADDRESS + + BOB’S ADDRESS + + TOKEN PROGRAM + USDC MINT @@ -103,8 +103,8 @@ - ATA PROGRAM + - CAROL’S ADDRESS + + CAROL’S ADDRESS + + TOKEN PROGRAM + USDC MINT @@ -145,8 +145,8 @@ - ATA PROGRAM + - MARIA’S ADDRESS + + MARIA’S ADDRESS + + TOKEN PROGRAM + USDC MINT @@ -217,8 +217,8 @@ - ATA PROGRAM + EVENT’S - ADDRESS + USDC MINT ADDRESS + EVENT’S ADDRESS + + TOKEN PROGRAM + USDC MINT vault: the event’s USDC diff --git a/building-financial-products-in-solana/figures/betting-alice-claim.svg b/building-financial-products-in-solana/figures/betting-alice-claim.svg index a79491b8..ec6e2b7f 100644 --- a/building-financial-products-in-solana/figures/betting-alice-claim.svg +++ b/building-financial-products-in-solana/figures/betting-alice-claim.svg @@ -19,8 +19,8 @@ - ATA PROGRAM + - ALICE’S ADDRESS + + ALICE’S ADDRESS + + TOKEN PROGRAM + USDC MINT @@ -60,8 +60,8 @@ - ATA PROGRAM + - BOB’S ADDRESS + + BOB’S ADDRESS + + TOKEN PROGRAM + USDC MINT @@ -92,8 +92,8 @@ - ATA PROGRAM + - CAROL’S ADDRESS + + CAROL’S ADDRESS + + TOKEN PROGRAM + USDC MINT @@ -124,8 +124,8 @@ - ATA PROGRAM + - MARIA’S ADDRESS + + MARIA’S ADDRESS + + TOKEN PROGRAM + USDC MINT @@ -186,8 +186,8 @@ - ATA PROGRAM + EVENT’S - ADDRESS + USDC MINT ADDRESS + EVENT’S ADDRESS + + TOKEN PROGRAM + USDC MINT vault: the event’s USDC diff --git a/building-financial-products-in-solana/figures/betting-maria-setup.svg b/building-financial-products-in-solana/figures/betting-maria-setup.svg index 68263a10..67527edf 100644 --- a/building-financial-products-in-solana/figures/betting-maria-setup.svg +++ b/building-financial-products-in-solana/figures/betting-maria-setup.svg @@ -16,8 +16,8 @@ - ATA PROGRAM + - ALICE’S ADDRESS + + ALICE’S ADDRESS + + TOKEN PROGRAM + USDC MINT @@ -31,8 +31,8 @@ - ATA PROGRAM + - BOB’S ADDRESS + + BOB’S ADDRESS + + TOKEN PROGRAM + USDC MINT @@ -46,8 +46,8 @@ - ATA PROGRAM + - CAROL’S ADDRESS + + CAROL’S ADDRESS + + TOKEN PROGRAM + USDC MINT @@ -63,8 +63,8 @@ - ATA PROGRAM + - MARIA’S ADDRESS + + MARIA’S ADDRESS + + TOKEN PROGRAM + USDC MINT @@ -136,8 +136,8 @@ - ATA PROGRAM + EVENT’S - ADDRESS + USDC MINT ADDRESS + EVENT’S ADDRESS + + TOKEN PROGRAM + USDC MINT vault: the event’s USDC diff --git a/building-financial-products-in-solana/figures/betting-place-bets.svg b/building-financial-products-in-solana/figures/betting-place-bets.svg index aa40b90a..00a01734 100644 --- a/building-financial-products-in-solana/figures/betting-place-bets.svg +++ b/building-financial-products-in-solana/figures/betting-place-bets.svg @@ -19,8 +19,8 @@ - ATA PROGRAM + - ALICE’S ADDRESS + + ALICE’S ADDRESS + + TOKEN PROGRAM + USDC MINT @@ -62,8 +62,8 @@ - ATA PROGRAM + - BOB’S ADDRESS + + BOB’S ADDRESS + + TOKEN PROGRAM + USDC MINT @@ -105,8 +105,8 @@ - ATA PROGRAM + - CAROL’S ADDRESS + + CAROL’S ADDRESS + + TOKEN PROGRAM + USDC MINT @@ -146,8 +146,8 @@ - ATA PROGRAM + - MARIA’S ADDRESS + + MARIA’S ADDRESS + + TOKEN PROGRAM + USDC MINT @@ -210,8 +210,8 @@ - ATA PROGRAM + EVENT’S - ADDRESS + USDC MINT ADDRESS + EVENT’S ADDRESS + + TOKEN PROGRAM + USDC MINT vault: the event’s USDC diff --git a/building-financial-products-in-solana/figures/betting-settlement.svg b/building-financial-products-in-solana/figures/betting-settlement.svg index 41ffc6da..8fe1a8b8 100644 --- a/building-financial-products-in-solana/figures/betting-settlement.svg +++ b/building-financial-products-in-solana/figures/betting-settlement.svg @@ -17,8 +17,8 @@ - ATA PROGRAM + - ALICE’S ADDRESS + + ALICE’S ADDRESS + + TOKEN PROGRAM + USDC MINT @@ -49,8 +49,8 @@ - ATA PROGRAM + - BOB’S ADDRESS + + BOB’S ADDRESS + + TOKEN PROGRAM + USDC MINT @@ -81,8 +81,8 @@ - ATA PROGRAM + - CAROL’S ADDRESS + + CAROL’S ADDRESS + + TOKEN PROGRAM + USDC MINT @@ -115,8 +115,8 @@ - ATA PROGRAM + - MARIA’S ADDRESS + + MARIA’S ADDRESS + + TOKEN PROGRAM + USDC MINT @@ -184,8 +184,8 @@ - ATA PROGRAM + EVENT’S - ADDRESS + USDC MINT ADDRESS + EVENT’S ADDRESS + + TOKEN PROGRAM + USDC MINT vault: the event’s USDC diff --git a/building-financial-products-in-solana/figures/escrow-account-map.svg b/building-financial-products-in-solana/figures/escrow-account-map.svg index cc8ed4e8..9975c3bc 100644 --- a/building-financial-products-in-solana/figures/escrow-account-map.svg +++ b/building-financial-products-in-solana/figures/escrow-account-map.svg @@ -32,8 +32,8 @@ - ATA PROGRAM + - ALICE’S ADDRESS + + ALICE’S ADDRESS + + TOKEN PROGRAM + USDC MINT @@ -43,8 +43,8 @@ - ATA PROGRAM + - ALICE’S ADDRESS + + ALICE’S ADDRESS + + TOKEN PROGRAM + TSLAX MINT @@ -63,8 +63,8 @@ - ATA PROGRAM + - BOB’S ADDRESS + + BOB’S ADDRESS + + TOKEN PROGRAM + USDC MINT @@ -74,8 +74,8 @@ - ATA PROGRAM + - BOB’S ADDRESS + + BOB’S ADDRESS + + TOKEN PROGRAM + TSLAX MINT @@ -110,8 +110,8 @@ - ATA PROGRAM + OFFER’S - ADDRESS + USDC MINT ADDRESS + OFFER’S ADDRESS + + TOKEN PROGRAM + USDC MINT vault: the offer’s USDC diff --git a/building-financial-products-in-solana/figures/escrow-cancel-offer.svg b/building-financial-products-in-solana/figures/escrow-cancel-offer.svg index 53fecb95..34175508 100644 --- a/building-financial-products-in-solana/figures/escrow-cancel-offer.svg +++ b/building-financial-products-in-solana/figures/escrow-cancel-offer.svg @@ -32,8 +32,8 @@ - ATA PROGRAM + - ALICE’S ADDRESS + + ALICE’S ADDRESS + + TOKEN PROGRAM + USDC MINT @@ -43,8 +43,8 @@ - ATA PROGRAM + - ALICE’S ADDRESS + + ALICE’S ADDRESS + + TOKEN PROGRAM + TSLAX MINT @@ -59,8 +59,8 @@ - ATA PROGRAM + - BOB’S ADDRESS + + BOB’S ADDRESS + + TOKEN PROGRAM + USDC MINT @@ -68,8 +68,8 @@ - ATA PROGRAM + - BOB’S ADDRESS + + BOB’S ADDRESS + + TOKEN PROGRAM + TSLAX MINT @@ -105,8 +105,8 @@ - ATA PROGRAM + OFFER’S - ADDRESS + USDC MINT ADDRESS + OFFER’S ADDRESS + + TOKEN PROGRAM + USDC MINT vault: the offer’s USDC diff --git a/building-financial-products-in-solana/figures/escrow-make-offer.svg b/building-financial-products-in-solana/figures/escrow-make-offer.svg index b61be4c4..a0c23042 100644 --- a/building-financial-products-in-solana/figures/escrow-make-offer.svg +++ b/building-financial-products-in-solana/figures/escrow-make-offer.svg @@ -32,8 +32,8 @@ - ATA PROGRAM + - ALICE’S ADDRESS + + ALICE’S ADDRESS + + TOKEN PROGRAM + USDC MINT @@ -43,8 +43,8 @@ - ATA PROGRAM + - ALICE’S ADDRESS + + ALICE’S ADDRESS + + TOKEN PROGRAM + TSLAX MINT @@ -62,8 +62,8 @@ - ATA PROGRAM + - BOB’S ADDRESS + + BOB’S ADDRESS + + TOKEN PROGRAM + USDC MINT @@ -71,8 +71,8 @@ - ATA PROGRAM + - BOB’S ADDRESS + + BOB’S ADDRESS + + TOKEN PROGRAM + TSLAX MINT @@ -106,8 +106,8 @@ - ATA PROGRAM + OFFER’S - ADDRESS + USDC MINT ADDRESS + OFFER’S ADDRESS + + TOKEN PROGRAM + USDC MINT vault: the offer’s USDC diff --git a/building-financial-products-in-solana/figures/escrow-take-offer.svg b/building-financial-products-in-solana/figures/escrow-take-offer.svg index 2696c2d7..b03088c2 100644 --- a/building-financial-products-in-solana/figures/escrow-take-offer.svg +++ b/building-financial-products-in-solana/figures/escrow-take-offer.svg @@ -32,8 +32,8 @@ - ATA PROGRAM + - ALICE’S ADDRESS + + ALICE’S ADDRESS + + TOKEN PROGRAM + USDC MINT @@ -41,8 +41,8 @@ - ATA PROGRAM + - ALICE’S ADDRESS + + ALICE’S ADDRESS + + TOKEN PROGRAM + TSLAX MINT @@ -61,8 +61,8 @@ - ATA PROGRAM + - BOB’S ADDRESS + + BOB’S ADDRESS + + TOKEN PROGRAM + USDC MINT @@ -72,8 +72,8 @@ - ATA PROGRAM + - BOB’S ADDRESS + + BOB’S ADDRESS + + TOKEN PROGRAM + TSLAX MINT @@ -111,8 +111,8 @@ - ATA PROGRAM + OFFER’S - ADDRESS + USDC MINT ADDRESS + OFFER’S ADDRESS + + TOKEN PROGRAM + USDC MINT vault: the offer’s USDC diff --git a/building-financial-products-in-solana/figures/fundraiser-account-map.svg b/building-financial-products-in-solana/figures/fundraiser-account-map.svg index 0c0ea874..cb1af921 100644 --- a/building-financial-products-in-solana/figures/fundraiser-account-map.svg +++ b/building-financial-products-in-solana/figures/fundraiser-account-map.svg @@ -24,8 +24,8 @@ - ATA PROGRAM + - ALICE’S ADDRESS + + ALICE’S ADDRESS + + TOKEN PROGRAM + USDC MINT @@ -55,8 +55,8 @@ - ATA PROGRAM + - BOB’S ADDRESS + + BOB’S ADDRESS + + TOKEN PROGRAM + USDC MINT @@ -86,8 +86,8 @@ - ATA PROGRAM + - CAROL’S ADDRESS + + CAROL’S ADDRESS + + TOKEN PROGRAM + USDC MINT @@ -118,8 +118,8 @@ - ATA PROGRAM + - MARIA’S ADDRESS + + MARIA’S ADDRESS + + TOKEN PROGRAM + USDC MINT @@ -159,8 +159,8 @@ - ATA PROGRAM + - THE FUNDRAISER’S ADDRESS + + THE FUNDRAISER’S ADDRESS + + TOKEN PROGRAM + USDC MINT ADDRESS diff --git a/building-financial-products-in-solana/figures/fundraiser-cap.svg b/building-financial-products-in-solana/figures/fundraiser-cap.svg index b50b340e..5a82c4af 100644 --- a/building-financial-products-in-solana/figures/fundraiser-cap.svg +++ b/building-financial-products-in-solana/figures/fundraiser-cap.svg @@ -1,44 +1,47 @@ - + - - - - - - - + + + + + + + + - Alice 900 - Bob 900 - Carol 450 + 450 - seven - more - backers - at - 900 - each - … + Alice 900 + Bob 900 + Carol 450 + 450 + Dave 800 + Erin 900 + Frank 700 + Grace 900 + Henry 750 + Iris 900 + Jack 550 + Kira 800 target: 9,000 USDC - - + + - one slice = the cap: - 10% of target = 900 USDC + the cap: 10% of target, + so 900 USDC, and no slice is taller - ten slices fill the thermometer - no contributor may carry more than one slice, - and the cap is cumulative across their contributions, - so a successful raise needs at least ten distinct backers - over the cap in one call: ContributionTooBig - over the cap across calls: MaximumContributionsReached - under one major unit: ContributionTooSmall + the cap forces a crowd + no backer may exceed 900, and the cap is cumulative + across their contributions, so a successful raise needs + at least ten distinct backers. This one took eleven, + because most of them gave less than the cap. + over the cap in one call: ContributionTooBig + over the cap across calls: MaximumContributionsReached + under one major unit: ContributionTooSmall diff --git a/building-financial-products-in-solana/figures/fundraiser-refunds.svg b/building-financial-products-in-solana/figures/fundraiser-refunds.svg index 3bf58454..a39371ba 100644 --- a/building-financial-products-in-solana/figures/fundraiser-refunds.svg +++ b/building-financial-products-in-solana/figures/fundraiser-refunds.svg @@ -24,8 +24,8 @@ - ATA PROGRAM + - ALICE’S ADDRESS + + ALICE’S ADDRESS + + TOKEN PROGRAM + USDC MINT @@ -57,8 +57,8 @@ - ATA PROGRAM + - BOB’S ADDRESS + + BOB’S ADDRESS + + TOKEN PROGRAM + USDC MINT @@ -90,8 +90,8 @@ - ATA PROGRAM + - CAROL’S ADDRESS + + CAROL’S ADDRESS + + TOKEN PROGRAM + USDC MINT @@ -122,8 +122,8 @@ - ATA PROGRAM + - MARIA’S ADDRESS + + MARIA’S ADDRESS + + TOKEN PROGRAM + USDC MINT @@ -161,8 +161,8 @@ - ATA PROGRAM + - THE FUNDRAISER’S ADDRESS + + THE FUNDRAISER’S ADDRESS + + TOKEN PROGRAM + USDC MINT ADDRESS diff --git a/building-financial-products-in-solana/figures/fundraiser-success.svg b/building-financial-products-in-solana/figures/fundraiser-success.svg index f33bc4cb..60acb575 100644 --- a/building-financial-products-in-solana/figures/fundraiser-success.svg +++ b/building-financial-products-in-solana/figures/fundraiser-success.svg @@ -22,8 +22,8 @@ - ATA PROGRAM + - ALICE’S ADDRESS + + ALICE’S ADDRESS + + TOKEN PROGRAM + USDC MINT @@ -47,8 +47,8 @@ - ATA PROGRAM + - BOB’S ADDRESS + + BOB’S ADDRESS + + TOKEN PROGRAM + USDC MINT @@ -72,8 +72,8 @@ - ATA PROGRAM + - CAROL’S ADDRESS + + CAROL’S ADDRESS + + TOKEN PROGRAM + USDC MINT @@ -99,8 +99,8 @@ - ATA PROGRAM + - MARIA’S ADDRESS + + MARIA’S ADDRESS + + TOKEN PROGRAM + USDC MINT @@ -142,8 +142,8 @@ - ATA PROGRAM + - THE FUNDRAISER’S ADDRESS + + THE FUNDRAISER’S ADDRESS + + TOKEN PROGRAM + USDC MINT ADDRESS diff --git a/building-financial-products-in-solana/figures/fundraiser-two-exits.svg b/building-financial-products-in-solana/figures/fundraiser-two-exits.svg index 9180be36..a4fa32ee 100644 --- a/building-financial-products-in-solana/figures/fundraiser-two-exits.svg +++ b/building-financial-products-in-solana/figures/fundraiser-two-exits.svg @@ -24,8 +24,8 @@ - ATA PROGRAM + - ALICE’S ADDRESS + + ALICE’S ADDRESS + + TOKEN PROGRAM + USDC MINT @@ -55,8 +55,8 @@ - ATA PROGRAM + - BOB’S ADDRESS + + BOB’S ADDRESS + + TOKEN PROGRAM + USDC MINT @@ -86,8 +86,8 @@ - ATA PROGRAM + - CAROL’S ADDRESS + + CAROL’S ADDRESS + + TOKEN PROGRAM + USDC MINT @@ -118,8 +118,8 @@ - ATA PROGRAM + - MARIA’S ADDRESS + + MARIA’S ADDRESS + + TOKEN PROGRAM + USDC MINT @@ -159,8 +159,8 @@ - ATA PROGRAM + - THE FUNDRAISER’S ADDRESS + + THE FUNDRAISER’S ADDRESS + + TOKEN PROGRAM + USDC MINT ADDRESS diff --git a/building-financial-products-in-solana/figures/lending-account-map.svg b/building-financial-products-in-solana/figures/lending-account-map.svg index 5f92c437..f5ae9147 100644 --- a/building-financial-products-in-solana/figures/lending-account-map.svg +++ b/building-financial-products-in-solana/figures/lending-account-map.svg @@ -30,8 +30,8 @@ - ATA PROGRAM + - ALICE’S ADDRESS + + ALICE’S ADDRESS + + TOKEN PROGRAM + USDC MINT @@ -41,8 +41,8 @@ - ATA PROGRAM + - ALICE’S ADDRESS + + ALICE’S ADDRESS + + TOKEN PROGRAM + USDC SHARE MINT @@ -52,8 +52,8 @@ - ATA PROGRAM + - ALICE’S ADDRESS + + ALICE’S ADDRESS + + TOKEN PROGRAM + SPCXX SHARE MINT @@ -71,8 +71,8 @@ - ATA PROGRAM + - BOB’S ADDRESS + + BOB’S ADDRESS + + TOKEN PROGRAM + NVDAX MINT @@ -82,8 +82,8 @@ - ATA PROGRAM + - BOB’S ADDRESS + + BOB’S ADDRESS + + TOKEN PROGRAM + NVDAX SHARE MINT @@ -93,8 +93,8 @@ - ATA PROGRAM + - BOB’S ADDRESS + + BOB’S ADDRESS + + TOKEN PROGRAM + USDC MINT @@ -137,8 +137,8 @@ - ATA PROGRAM + - CAROL’S ADDRESS + + CAROL’S ADDRESS + + TOKEN PROGRAM + USDC MINT @@ -148,8 +148,8 @@ - ATA PROGRAM + - CAROL’S ADDRESS + + CAROL’S ADDRESS + + TOKEN PROGRAM + USDC SHARE MINT @@ -159,8 +159,8 @@ - ATA PROGRAM + - CAROL’S ADDRESS + + CAROL’S ADDRESS + + TOKEN PROGRAM + SPCXX MINT @@ -203,8 +203,8 @@ - ATA PROGRAM + - DAVE’S ADDRESS + + DAVE’S ADDRESS + + TOKEN PROGRAM + SPCXX MINT diff --git a/building-financial-products-in-solana/figures/lending-borrow-ltv.svg b/building-financial-products-in-solana/figures/lending-borrow-ltv.svg index 83f9457e..3a507a97 100644 --- a/building-financial-products-in-solana/figures/lending-borrow-ltv.svg +++ b/building-financial-products-in-solana/figures/lending-borrow-ltv.svg @@ -32,8 +32,8 @@ - ATA PROGRAM + - BOB’S ADDRESS + + BOB’S ADDRESS + + TOKEN PROGRAM + NVDAX MINT @@ -43,8 +43,8 @@ - ATA PROGRAM + - BOB’S ADDRESS + + BOB’S ADDRESS + + TOKEN PROGRAM + NVDAX SHARE MINT @@ -54,8 +54,8 @@ - ATA PROGRAM + - BOB’S ADDRESS + + BOB’S ADDRESS + + TOKEN PROGRAM + USDC MINT @@ -114,8 +114,8 @@ - ATA PROGRAM + - ALICE’S ADDRESS + + ALICE’S ADDRESS + + TOKEN PROGRAM + USDC MINT @@ -123,8 +123,8 @@ - ATA PROGRAM + - ALICE’S ADDRESS + + ALICE’S ADDRESS + + TOKEN PROGRAM + USDC SHARE MINT @@ -132,8 +132,8 @@ - ATA PROGRAM + - ALICE’S ADDRESS + + ALICE’S ADDRESS + + TOKEN PROGRAM + SPCXX SHARE MINT @@ -141,8 +141,8 @@ - ATA PROGRAM + - CAROL’S ADDRESS + + CAROL’S ADDRESS + + TOKEN PROGRAM + USDC MINT @@ -150,8 +150,8 @@ - ATA PROGRAM + - DAVE’S ADDRESS + + DAVE’S ADDRESS + + TOKEN PROGRAM + SPCXX MINT diff --git a/building-financial-products-in-solana/figures/lending-deposit-shares.svg b/building-financial-products-in-solana/figures/lending-deposit-shares.svg index f0246f4b..2bfcfe8f 100644 --- a/building-financial-products-in-solana/figures/lending-deposit-shares.svg +++ b/building-financial-products-in-solana/figures/lending-deposit-shares.svg @@ -31,8 +31,8 @@ - ATA PROGRAM + - ALICE’S ADDRESS + + ALICE’S ADDRESS + + TOKEN PROGRAM + USDC MINT @@ -42,8 +42,8 @@ - ATA PROGRAM + - ALICE’S ADDRESS + + ALICE’S ADDRESS + + TOKEN PROGRAM + USDC SHARE MINT @@ -78,8 +78,8 @@ - ATA PROGRAM + - BOB’S ADDRESS + + BOB’S ADDRESS + + TOKEN PROGRAM + NVDAX MINT @@ -87,8 +87,8 @@ - ATA PROGRAM + - CAROL’S ADDRESS + + CAROL’S ADDRESS + + TOKEN PROGRAM + USDC MINT @@ -96,8 +96,8 @@ - ATA PROGRAM + - DAVE’S ADDRESS + + DAVE’S ADDRESS + + TOKEN PROGRAM + SPCXX MINT diff --git a/building-financial-products-in-solana/figures/lending-liquidation.svg b/building-financial-products-in-solana/figures/lending-liquidation.svg index 8e290fed..a3cff6ac 100644 --- a/building-financial-products-in-solana/figures/lending-liquidation.svg +++ b/building-financial-products-in-solana/figures/lending-liquidation.svg @@ -32,8 +32,8 @@ - ATA PROGRAM + - DAVE’S ADDRESS + + DAVE’S ADDRESS + + TOKEN PROGRAM + SPCXX MINT @@ -43,8 +43,8 @@ - ATA PROGRAM + - DAVE’S ADDRESS + + DAVE’S ADDRESS + + TOKEN PROGRAM + USDC SHARE MINT @@ -103,8 +103,8 @@ - ATA PROGRAM + - ALICE’S ADDRESS + + ALICE’S ADDRESS + + TOKEN PROGRAM + USDC MINT @@ -112,8 +112,8 @@ - ATA PROGRAM + - ALICE’S ADDRESS + + ALICE’S ADDRESS + + TOKEN PROGRAM + USDC SHARE MINT @@ -121,8 +121,8 @@ - ATA PROGRAM + - ALICE’S ADDRESS + + ALICE’S ADDRESS + + TOKEN PROGRAM + SPCXX SHARE MINT @@ -130,8 +130,8 @@ - ATA PROGRAM + - BOB’S ADDRESS + + BOB’S ADDRESS + + TOKEN PROGRAM + NVDAX MINT @@ -139,8 +139,8 @@ - ATA PROGRAM + - BOB’S ADDRESS + + BOB’S ADDRESS + + TOKEN PROGRAM + NVDAX SHARE MINT @@ -148,8 +148,8 @@ - ATA PROGRAM + - BOB’S ADDRESS + + BOB’S ADDRESS + + TOKEN PROGRAM + USDC MINT @@ -157,8 +157,8 @@ - ATA PROGRAM + - CAROL’S ADDRESS + + CAROL’S ADDRESS + + TOKEN PROGRAM + USDC MINT @@ -166,8 +166,8 @@ - ATA PROGRAM + - CAROL’S ADDRESS + + CAROL’S ADDRESS + + TOKEN PROGRAM + USDC SHARE MINT @@ -175,8 +175,8 @@ - ATA PROGRAM + - CAROL’S ADDRESS + + CAROL’S ADDRESS + + TOKEN PROGRAM + SPCXX MINT diff --git a/building-financial-products-in-solana/figures/order-book-accounts.svg b/building-financial-products-in-solana/figures/order-book-accounts.svg index 86253129..f0f39a50 100644 --- a/building-financial-products-in-solana/figures/order-book-accounts.svg +++ b/building-financial-products-in-solana/figures/order-book-accounts.svg @@ -49,8 +49,8 @@ - ATA PROGRAM + - ALICE’S ADDRESS + + ALICE’S ADDRESS + + TOKEN PROGRAM + NVDAX MINT @@ -60,8 +60,8 @@ - ATA PROGRAM + - BOB’S ADDRESS + + BOB’S ADDRESS + + TOKEN PROGRAM + NVDAX MINT @@ -71,8 +71,8 @@ - ATA PROGRAM + - CAROL’S ADDRESS + + CAROL’S ADDRESS + + TOKEN PROGRAM + NVDAX MINT @@ -82,8 +82,8 @@ - ATA PROGRAM + - ALICE’S ADDRESS + + ALICE’S ADDRESS + + TOKEN PROGRAM + USDC MINT @@ -93,8 +93,8 @@ - ATA PROGRAM + - BOB’S ADDRESS + + BOB’S ADDRESS + + TOKEN PROGRAM + USDC MINT @@ -104,8 +104,8 @@ - ATA PROGRAM + - CAROL’S ADDRESS + + CAROL’S ADDRESS + + TOKEN PROGRAM + USDC MINT @@ -115,8 +115,8 @@ - ATA PROGRAM + - MARIA’S ADDRESS + + MARIA’S ADDRESS + + TOKEN PROGRAM + USDC MINT diff --git a/building-financial-products-in-solana/figures/order-book-alice-ask.svg b/building-financial-products-in-solana/figures/order-book-alice-ask.svg index 908e4e66..afd13934 100644 --- a/building-financial-products-in-solana/figures/order-book-alice-ask.svg +++ b/building-financial-products-in-solana/figures/order-book-alice-ask.svg @@ -44,8 +44,8 @@ - ATA PROGRAM + - ALICE’S ADDRESS + + ALICE’S ADDRESS + + TOKEN PROGRAM + NVDAX MINT @@ -55,8 +55,8 @@ - ATA PROGRAM + - BOB’S ADDRESS + + BOB’S ADDRESS + + TOKEN PROGRAM + NVDAX MINT @@ -64,8 +64,8 @@ - ATA PROGRAM + - CAROL’S ADDRESS + + CAROL’S ADDRESS + + TOKEN PROGRAM + NVDAX MINT @@ -73,8 +73,8 @@ - ATA PROGRAM + - ALICE’S ADDRESS + + ALICE’S ADDRESS + + TOKEN PROGRAM + USDC MINT @@ -82,8 +82,8 @@ - ATA PROGRAM + - BOB’S ADDRESS + + BOB’S ADDRESS + + TOKEN PROGRAM + USDC MINT @@ -91,8 +91,8 @@ - ATA PROGRAM + - CAROL’S ADDRESS + + CAROL’S ADDRESS + + TOKEN PROGRAM + USDC MINT @@ -100,8 +100,8 @@ - ATA PROGRAM + - MARIA’S ADDRESS + + MARIA’S ADDRESS + + TOKEN PROGRAM + USDC MINT diff --git a/building-financial-products-in-solana/figures/order-book-cancel.svg b/building-financial-products-in-solana/figures/order-book-cancel.svg index a404d720..6bfa53b8 100644 --- a/building-financial-products-in-solana/figures/order-book-cancel.svg +++ b/building-financial-products-in-solana/figures/order-book-cancel.svg @@ -43,8 +43,8 @@ - ATA PROGRAM + - ALICE’S ADDRESS + + ALICE’S ADDRESS + + TOKEN PROGRAM + NVDAX MINT @@ -52,8 +52,8 @@ - ATA PROGRAM + - BOB’S ADDRESS + + BOB’S ADDRESS + + TOKEN PROGRAM + NVDAX MINT @@ -61,8 +61,8 @@ - ATA PROGRAM + - CAROL’S ADDRESS + + CAROL’S ADDRESS + + TOKEN PROGRAM + NVDAX MINT @@ -70,8 +70,8 @@ - ATA PROGRAM + - ALICE’S ADDRESS + + ALICE’S ADDRESS + + TOKEN PROGRAM + USDC MINT @@ -79,8 +79,8 @@ - ATA PROGRAM + - BOB’S ADDRESS + + BOB’S ADDRESS + + TOKEN PROGRAM + USDC MINT @@ -88,8 +88,8 @@ - ATA PROGRAM + - CAROL’S ADDRESS + + CAROL’S ADDRESS + + TOKEN PROGRAM + USDC MINT @@ -97,8 +97,8 @@ - ATA PROGRAM + - MARIA’S ADDRESS + + MARIA’S ADDRESS + + TOKEN PROGRAM + USDC MINT diff --git a/building-financial-products-in-solana/figures/order-book-initialize-market.svg b/building-financial-products-in-solana/figures/order-book-initialize-market.svg index 094a8aff..e49345f5 100644 --- a/building-financial-products-in-solana/figures/order-book-initialize-market.svg +++ b/building-financial-products-in-solana/figures/order-book-initialize-market.svg @@ -43,8 +43,8 @@ - ATA PROGRAM + - ALICE’S ADDRESS + + ALICE’S ADDRESS + + TOKEN PROGRAM + NVDAX MINT @@ -52,8 +52,8 @@ - ATA PROGRAM + - BOB’S ADDRESS + + BOB’S ADDRESS + + TOKEN PROGRAM + NVDAX MINT @@ -61,8 +61,8 @@ - ATA PROGRAM + - CAROL’S ADDRESS + + CAROL’S ADDRESS + + TOKEN PROGRAM + NVDAX MINT @@ -70,8 +70,8 @@ - ATA PROGRAM + - ALICE’S ADDRESS + + ALICE’S ADDRESS + + TOKEN PROGRAM + USDC MINT @@ -79,8 +79,8 @@ - ATA PROGRAM + - BOB’S ADDRESS + + BOB’S ADDRESS + + TOKEN PROGRAM + USDC MINT @@ -88,8 +88,8 @@ - ATA PROGRAM + - CAROL’S ADDRESS + + CAROL’S ADDRESS + + TOKEN PROGRAM + USDC MINT @@ -97,8 +97,8 @@ - ATA PROGRAM + - MARIA’S ADDRESS + + MARIA’S ADDRESS + + TOKEN PROGRAM + USDC MINT diff --git a/building-financial-products-in-solana/figures/order-book-match.svg b/building-financial-products-in-solana/figures/order-book-match.svg index 11c3c271..882fcbcd 100644 --- a/building-financial-products-in-solana/figures/order-book-match.svg +++ b/building-financial-products-in-solana/figures/order-book-match.svg @@ -44,8 +44,8 @@ - ATA PROGRAM + - ALICE’S ADDRESS + + ALICE’S ADDRESS + + TOKEN PROGRAM + NVDAX MINT @@ -53,8 +53,8 @@ - ATA PROGRAM + - BOB’S ADDRESS + + BOB’S ADDRESS + + TOKEN PROGRAM + NVDAX MINT @@ -62,8 +62,8 @@ - ATA PROGRAM + - CAROL’S ADDRESS + + CAROL’S ADDRESS + + TOKEN PROGRAM + NVDAX MINT @@ -73,8 +73,8 @@ - ATA PROGRAM + - ALICE’S ADDRESS + + ALICE’S ADDRESS + + TOKEN PROGRAM + USDC MINT @@ -82,8 +82,8 @@ - ATA PROGRAM + - BOB’S ADDRESS + + BOB’S ADDRESS + + TOKEN PROGRAM + USDC MINT @@ -91,8 +91,8 @@ - ATA PROGRAM + - CAROL’S ADDRESS + + CAROL’S ADDRESS + + TOKEN PROGRAM + USDC MINT @@ -100,8 +100,8 @@ - ATA PROGRAM + - MARIA’S ADDRESS + + MARIA’S ADDRESS + + TOKEN PROGRAM + USDC MINT diff --git a/building-financial-products-in-solana/figures/order-book-settle.svg b/building-financial-products-in-solana/figures/order-book-settle.svg index ec0ade8b..dec7d144 100644 --- a/building-financial-products-in-solana/figures/order-book-settle.svg +++ b/building-financial-products-in-solana/figures/order-book-settle.svg @@ -46,8 +46,8 @@ - ATA PROGRAM + - ALICE’S ADDRESS + + ALICE’S ADDRESS + + TOKEN PROGRAM + NVDAX MINT @@ -55,8 +55,8 @@ - ATA PROGRAM + - BOB’S ADDRESS + + BOB’S ADDRESS + + TOKEN PROGRAM + NVDAX MINT @@ -66,8 +66,8 @@ - ATA PROGRAM + - CAROL’S ADDRESS + + CAROL’S ADDRESS + + TOKEN PROGRAM + NVDAX MINT @@ -75,8 +75,8 @@ - ATA PROGRAM + - ALICE’S ADDRESS + + ALICE’S ADDRESS + + TOKEN PROGRAM + USDC MINT @@ -84,8 +84,8 @@ - ATA PROGRAM + - BOB’S ADDRESS + + BOB’S ADDRESS + + TOKEN PROGRAM + USDC MINT @@ -95,8 +95,8 @@ - ATA PROGRAM + - CAROL’S ADDRESS + + CAROL’S ADDRESS + + TOKEN PROGRAM + USDC MINT @@ -106,8 +106,8 @@ - ATA PROGRAM + - MARIA’S ADDRESS + + MARIA’S ADDRESS + + TOKEN PROGRAM + USDC MINT diff --git a/building-financial-products-in-solana/figures/order-book-withdraw-fees.svg b/building-financial-products-in-solana/figures/order-book-withdraw-fees.svg index 9fdd1b1b..4e50e36e 100644 --- a/building-financial-products-in-solana/figures/order-book-withdraw-fees.svg +++ b/building-financial-products-in-solana/figures/order-book-withdraw-fees.svg @@ -44,8 +44,8 @@ - ATA PROGRAM + - ALICE’S ADDRESS + + ALICE’S ADDRESS + + TOKEN PROGRAM + NVDAX MINT @@ -53,8 +53,8 @@ - ATA PROGRAM + - BOB’S ADDRESS + + BOB’S ADDRESS + + TOKEN PROGRAM + NVDAX MINT @@ -62,8 +62,8 @@ - ATA PROGRAM + - CAROL’S ADDRESS + + CAROL’S ADDRESS + + TOKEN PROGRAM + NVDAX MINT @@ -71,8 +71,8 @@ - ATA PROGRAM + - ALICE’S ADDRESS + + ALICE’S ADDRESS + + TOKEN PROGRAM + USDC MINT @@ -80,8 +80,8 @@ - ATA PROGRAM + - BOB’S ADDRESS + + BOB’S ADDRESS + + TOKEN PROGRAM + USDC MINT @@ -89,8 +89,8 @@ - ATA PROGRAM + - CAROL’S ADDRESS + + CAROL’S ADDRESS + + TOKEN PROGRAM + USDC MINT @@ -98,8 +98,8 @@ - ATA PROGRAM + - MARIA’S ADDRESS + + MARIA’S ADDRESS + + TOKEN PROGRAM + USDC MINT diff --git a/building-financial-products-in-solana/figures/perps-account-map.svg b/building-financial-products-in-solana/figures/perps-account-map.svg index 41ee998c..24a8a999 100644 --- a/building-financial-products-in-solana/figures/perps-account-map.svg +++ b/building-financial-products-in-solana/figures/perps-account-map.svg @@ -19,8 +19,8 @@ - ATA PROGRAM + - ALICE’S ADDRESS + + ALICE’S ADDRESS + + TOKEN PROGRAM + USDC MINT @@ -40,8 +40,8 @@ - ATA PROGRAM + - BOB’S ADDRESS + + BOB’S ADDRESS + + TOKEN PROGRAM + USDC MINT @@ -61,8 +61,8 @@ - ATA PROGRAM + - CAROL’S ADDRESS + + CAROL’S ADDRESS + + TOKEN PROGRAM + USDC MINT @@ -82,8 +82,8 @@ - ATA PROGRAM + - DAVE’S ADDRESS + + DAVE’S ADDRESS + + TOKEN PROGRAM + USDC MINT @@ -103,8 +103,8 @@ - ATA PROGRAM + - MARIA’S ADDRESS + + MARIA’S ADDRESS + + TOKEN PROGRAM + USDC MINT @@ -142,8 +142,8 @@ - ATA PROGRAM + - CAROL’S ADDRESS + + CAROL’S ADDRESS + + TOKEN PROGRAM + LP SHARE MINT diff --git a/building-financial-products-in-solana/figures/perps-liquidation.svg b/building-financial-products-in-solana/figures/perps-liquidation.svg index 31b0ceca..760d2ae8 100644 --- a/building-financial-products-in-solana/figures/perps-liquidation.svg +++ b/building-financial-products-in-solana/figures/perps-liquidation.svg @@ -16,8 +16,8 @@ - ATA PROGRAM + - ALICE’S ADDRESS + + ALICE’S ADDRESS + + TOKEN PROGRAM + USDC MINT @@ -43,8 +43,8 @@ - ATA PROGRAM + - BOB’S ADDRESS + + BOB’S ADDRESS + + TOKEN PROGRAM + USDC MINT @@ -76,8 +76,8 @@ - ATA PROGRAM + - CAROL’S ADDRESS + + CAROL’S ADDRESS + + TOKEN PROGRAM + USDC MINT @@ -85,8 +85,8 @@ - ATA PROGRAM + - CAROL’S ADDRESS + + CAROL’S ADDRESS + + TOKEN PROGRAM + LP SHARE MINT @@ -103,8 +103,8 @@ - ATA PROGRAM + - DAVE’S ADDRESS + + DAVE’S ADDRESS + + TOKEN PROGRAM + USDC MINT @@ -120,8 +120,8 @@ - ATA PROGRAM + - MARIA’S ADDRESS + + MARIA’S ADDRESS + + TOKEN PROGRAM + USDC MINT diff --git a/building-financial-products-in-solana/figures/perps-open-position.svg b/building-financial-products-in-solana/figures/perps-open-position.svg index 311904e7..8d1bd9dd 100644 --- a/building-financial-products-in-solana/figures/perps-open-position.svg +++ b/building-financial-products-in-solana/figures/perps-open-position.svg @@ -19,8 +19,8 @@ - ATA PROGRAM + - ALICE’S ADDRESS + + ALICE’S ADDRESS + + TOKEN PROGRAM + USDC MINT @@ -51,8 +51,8 @@ - ATA PROGRAM + - BOB’S ADDRESS + + BOB’S ADDRESS + + TOKEN PROGRAM + USDC MINT @@ -66,8 +66,8 @@ - ATA PROGRAM + - CAROL’S ADDRESS + + CAROL’S ADDRESS + + TOKEN PROGRAM + USDC MINT @@ -81,8 +81,8 @@ - ATA PROGRAM + - DAVE’S ADDRESS + + DAVE’S ADDRESS + + TOKEN PROGRAM + USDC MINT @@ -96,8 +96,8 @@ - ATA PROGRAM + - MARIA’S ADDRESS + + MARIA’S ADDRESS + + TOKEN PROGRAM + USDC MINT @@ -114,8 +114,8 @@ - ATA PROGRAM + - CAROL’S ADDRESS + + CAROL’S ADDRESS + + TOKEN PROGRAM + LP SHARE MINT diff --git a/building-financial-products-in-solana/figures/prop-amm-account-map.svg b/building-financial-products-in-solana/figures/prop-amm-account-map.svg index 529c688e..8c58c5cc 100644 --- a/building-financial-products-in-solana/figures/prop-amm-account-map.svg +++ b/building-financial-products-in-solana/figures/prop-amm-account-map.svg @@ -27,8 +27,8 @@ - ATA PROGRAM + - MARIA’S ADDRESS + + MARIA’S ADDRESS + + TOKEN PROGRAM + USDC MINT @@ -47,8 +47,8 @@ - ATA PROGRAM + - ALICE’S ADDRESS + + ALICE’S ADDRESS + + TOKEN PROGRAM + USDC MINT @@ -67,8 +67,8 @@ - ATA PROGRAM + - BOB’S ADDRESS + + BOB’S ADDRESS + + TOKEN PROGRAM + USDC MINT @@ -78,8 +78,8 @@ - ATA PROGRAM + - BOB’S ADDRESS + + BOB’S ADDRESS + + TOKEN PROGRAM + NVDAX MINT diff --git a/building-financial-products-in-solana/figures/vault-account-map.svg b/building-financial-products-in-solana/figures/vault-account-map.svg index 9a0acb14..51dda37e 100644 --- a/building-financial-products-in-solana/figures/vault-account-map.svg +++ b/building-financial-products-in-solana/figures/vault-account-map.svg @@ -76,8 +76,8 @@ - ATA PROGRAM + - ALICE’S ADDRESS + + ALICE’S ADDRESS + + TOKEN PROGRAM + USDC MINT @@ -87,8 +87,8 @@ - ATA PROGRAM + - ALICE’S ADDRESS + + ALICE’S ADDRESS + + TOKEN PROGRAM + SHARE MINT @@ -107,8 +107,8 @@ - ATA PROGRAM + - BOB’S ADDRESS + + BOB’S ADDRESS + + TOKEN PROGRAM + USDC MINT @@ -118,8 +118,8 @@ - ATA PROGRAM + - BOB’S ADDRESS + + BOB’S ADDRESS + + TOKEN PROGRAM + SHARE MINT @@ -201,8 +201,8 @@ - ATA PROGRAM + STRATEGY’S - ADDRESS + USDC MINT + STRATEGY’S ADDRESS + + TOKEN PROGRAM + USDC MINT USDC vault @@ -212,8 +212,8 @@ - ATA PROGRAM + STRATEGY’S - ADDRESS + TSLAX MINT + STRATEGY’S ADDRESS + + TOKEN PROGRAM + TSLAX MINT TSLAx vault @@ -223,8 +223,8 @@ - ATA PROGRAM + STRATEGY’S - ADDRESS + NVDAX MINT + STRATEGY’S ADDRESS + + TOKEN PROGRAM + NVDAX MINT NVDAx vault diff --git a/building-financial-products-in-solana/figures/vault-deposit.svg b/building-financial-products-in-solana/figures/vault-deposit.svg index 555b3c0f..feb54130 100644 --- a/building-financial-products-in-solana/figures/vault-deposit.svg +++ b/building-financial-products-in-solana/figures/vault-deposit.svg @@ -60,8 +60,8 @@ - ATA PROGRAM + - BOB’S ADDRESS + + BOB’S ADDRESS + + TOKEN PROGRAM + USDC MINT @@ -84,8 +84,8 @@ - ATA PROGRAM + - ALICE’S ADDRESS + + ALICE’S ADDRESS + + TOKEN PROGRAM + USDC MINT @@ -95,8 +95,8 @@ - ATA PROGRAM + - ALICE’S ADDRESS + + ALICE’S ADDRESS + + TOKEN PROGRAM + SHARE MINT @@ -168,8 +168,8 @@ - ATA PROGRAM + STRATEGY’S - ADDRESS + USDC MINT + STRATEGY’S ADDRESS + + TOKEN PROGRAM + USDC MINT USDC vault @@ -179,8 +179,8 @@ - ATA PROGRAM + STRATEGY’S - ADDRESS + TSLAX MINT + STRATEGY’S ADDRESS + + TOKEN PROGRAM + TSLAX MINT TSLAx vault @@ -190,8 +190,8 @@ - ATA PROGRAM + STRATEGY’S - ADDRESS + NVDAX MINT + STRATEGY’S ADDRESS + + TOKEN PROGRAM + NVDAX MINT NVDAx vault diff --git a/building-financial-products-in-solana/figures/vault-rebalance.svg b/building-financial-products-in-solana/figures/vault-rebalance.svg index 3d9a891a..e084a38e 100644 --- a/building-financial-products-in-solana/figures/vault-rebalance.svg +++ b/building-financial-products-in-solana/figures/vault-rebalance.svg @@ -60,8 +60,8 @@ - ATA PROGRAM + - ALICE’S ADDRESS + + ALICE’S ADDRESS + + TOKEN PROGRAM + USDC MINT @@ -69,8 +69,8 @@ - ATA PROGRAM + - ALICE’S ADDRESS + + ALICE’S ADDRESS + + TOKEN PROGRAM + SHARE MINT @@ -84,8 +84,8 @@ - ATA PROGRAM + - BOB’S ADDRESS + + BOB’S ADDRESS + + TOKEN PROGRAM + USDC MINT @@ -160,8 +160,8 @@ - ATA PROGRAM + STRATEGY’S - ADDRESS + USDC MINT + STRATEGY’S ADDRESS + + TOKEN PROGRAM + USDC MINT USDC vault @@ -171,8 +171,8 @@ - ATA PROGRAM + STRATEGY’S - ADDRESS + TSLAX MINT + STRATEGY’S ADDRESS + + TOKEN PROGRAM + TSLAX MINT TSLAx vault @@ -182,8 +182,8 @@ - ATA PROGRAM + STRATEGY’S - ADDRESS + NVDAX MINT + STRATEGY’S ADDRESS + + TOKEN PROGRAM + NVDAX MINT NVDAx vault diff --git a/building-financial-products-in-solana/figures/vault-registry.svg b/building-financial-products-in-solana/figures/vault-registry.svg index 68aa6348..2dd9085d 100644 --- a/building-financial-products-in-solana/figures/vault-registry.svg +++ b/building-financial-products-in-solana/figures/vault-registry.svg @@ -74,8 +74,8 @@ - ATA PROGRAM + - ALICE’S ADDRESS + + ALICE’S ADDRESS + + TOKEN PROGRAM + USDC MINT @@ -89,8 +89,8 @@ - ATA PROGRAM + - BOB’S ADDRESS + + BOB’S ADDRESS + + TOKEN PROGRAM + USDC MINT diff --git a/building-financial-products-in-solana/figures/vault-withdraw-in-kind.svg b/building-financial-products-in-solana/figures/vault-withdraw-in-kind.svg index 1c0f5c60..bb49d63f 100644 --- a/building-financial-products-in-solana/figures/vault-withdraw-in-kind.svg +++ b/building-financial-products-in-solana/figures/vault-withdraw-in-kind.svg @@ -60,8 +60,8 @@ - ATA PROGRAM + - BOB’S ADDRESS + + BOB’S ADDRESS + + TOKEN PROGRAM + USDC MINT @@ -69,8 +69,8 @@ - ATA PROGRAM + - BOB’S ADDRESS + + BOB’S ADDRESS + + TOKEN PROGRAM + SHARE MINT @@ -84,8 +84,8 @@ - ATA PROGRAM + - MARIA’S ADDRESS + + MARIA’S ADDRESS + + TOKEN PROGRAM + SHARE MINT @@ -102,8 +102,8 @@ - ATA PROGRAM + - ALICE’S ADDRESS + + ALICE’S ADDRESS + + TOKEN PROGRAM + USDC MINT @@ -113,8 +113,8 @@ - ATA PROGRAM + - ALICE’S ADDRESS + + ALICE’S ADDRESS + + TOKEN PROGRAM + SHARE MINT @@ -124,8 +124,8 @@ - ATA PROGRAM + - ALICE’S ADDRESS + + ALICE’S ADDRESS + + TOKEN PROGRAM + TSLAX MINT @@ -136,8 +136,8 @@ - ATA PROGRAM + - ALICE’S ADDRESS + + ALICE’S ADDRESS + + TOKEN PROGRAM + NVDAX MINT @@ -209,8 +209,8 @@ - ATA PROGRAM + STRATEGY’S - ADDRESS + USDC MINT + STRATEGY’S ADDRESS + + TOKEN PROGRAM + USDC MINT USDC vault @@ -220,8 +220,8 @@ - ATA PROGRAM + STRATEGY’S - ADDRESS + TSLAX MINT + STRATEGY’S ADDRESS + + TOKEN PROGRAM + TSLAX MINT TSLAx vault @@ -231,8 +231,8 @@ - ATA PROGRAM + STRATEGY’S - ADDRESS + NVDAX MINT + STRATEGY’S ADDRESS + + TOKEN PROGRAM + NVDAX MINT NVDAx vault diff --git a/building-financial-products-in-solana/lending.md b/building-financial-products-in-solana/lending.md index 42b6dc09..339fcc01 100644 --- a/building-financial-products-in-solana/lending.md +++ b/building-financial-products-in-solana/lending.md @@ -6,7 +6,7 @@ One word needs defusing before anything else. This chapter's market trades stock ## The Account Model: A Market of Reserves, and an Obligation per Borrower -![The account map: one lending market PDA, three reserve PDAs (USDC, NVDAx, SPCXx) each owning a liquidity vault and a share mint and pointing at a price feed, and Bob's and Carol's obligation PDAs holding collateral share vaults and borrow entries.](figures/lending-account-map.svg) +![The account map: one lending market account, three reserve accounts (USDC, NVDAx, SPCXx) each owning a liquidity vault and a share mint and pointing at a price feed, and Bob's and Carol's obligation accounts holding collateral share vaults and borrow entries.](figures/lending-account-map.svg) The **lending market** PDA (seeds: `"lending_market"`, a market ID) stores the `owner` (Maria, used only for authorization, never in the address) and the `quote_currency_mint`, the stable asset all values are measured in: USDC. Distinct market IDs give independent, risk-isolated pools. @@ -33,7 +33,7 @@ Every valuation in this chapter multiplies an amount by a price, and the prices ## The Market Owner Opens the Market -Maria runs the market, and her revenue is a cut of the interest: the reserve factor you just met. She runs `init_lending_market()` with USDC as the quote currency, then `init_reserve()` three times, for USDC, NVDAx, and SPCXx, each with its config and price feed. Each reserve's `cumulative_borrow_rate_index` starts at exactly 1.0, represented as 10^18 in the program's fixed-point scale, chosen so a single slot's interest does not truncate to zero while staying far inside u128's ceiling. All the math in this program is integer-only: no floats, no fixed-point crates, just u128, a scale constant, and two rounding helpers. +Maria runs the market, and her revenue is a cut of the interest: the reserve factor you just met. She runs `initialize_lending_market()` with USDC as the quote currency, then `initialize_reserve()` three times, for USDC, NVDAx, and SPCXx, each with its config and price feed. Each reserve's `cumulative_borrow_rate_index` starts at exactly 1.0, represented as 10^18 in the program's fixed-point scale, chosen so a single slot's interest does not truncate to zero while staying far inside u128's ceiling. All the math in this program is integer-only: no floats, no fixed-point crates, just u128, a scale constant, and two rounding helpers. ## Supplying: Tokens In, Shares Out @@ -46,7 +46,7 @@ Alice has her standard 1,000 USDC, plus four SPCXx (SpaceX stock, $250 each), si Bob needs cash but does not want to sell his five NVDAx (NVIDIA stock, $200 each): he wants $750 of liquidity while keeping his position. Four instruction handlers, in order: 1. `deposit_reserve_liquidity()`: his five NVDAx go into the NVDAx reserve; he receives five NVDAx shares. -2. `init_obligation()`: creates his loan file. +2. `initialize_obligation()`: creates his loan file. 3. `deposit_obligation_collateral()`: moves his five NVDAx shares into a collateral vault owned by his obligation. Note what is pledged: *shares*, not raw tokens, so his collateral keeps earning supplier yield while pledged. 4. `borrow_obligation_liquidity()`: with his collateral worth $1,000 and loan-to-value at 75%, he may borrow up to $750 of USDC, which he does, drawn from the USDC reserve's available liquidity. diff --git a/building-financial-products-in-solana/order-book.md b/building-financial-products-in-solana/order-book.md index dd94d7a0..15436848 100644 --- a/building-financial-products-in-solana/order-book.md +++ b/building-financial-products-in-solana/order-book.md @@ -1,4 +1,4 @@ -# Chapter 13: The Order Book Exchange +# Chapter 14: The Order Book Exchange NYSE, NASDAQ, and the CME are order book exchanges. On Solana, OpenBook and Phoenix are order book exchanges. By the end of this chapter you will have walked through one, end to end: how orders come in, how they rest, how they match, where the money waits, and how the operator collects fees. @@ -18,7 +18,7 @@ One naming note: this design is traditionally called a *central limit* order boo The market this chapter builds trades NVDAx over USDC: NVDAx, a stock, is the **base token**, the thing being bought and sold; USDC is the **quote token**, the thing prices are stated in. You quote in the stable asset so that "250" always means dollars. -![The account map: each trader's accounts stacked in a column beneath their spot on the Devnet curve, and the program's column on the right holding the market PDA, the order book account, and the base, quote, and fee vaults.](figures/order-book-accounts.svg) +![The account map: each trader's accounts stacked in a column beneath their spot on the Devnet curve, and the program's column on the right holding the market account, the order book account, and the base, quote, and fee vaults.](figures/order-book-accounts.svg) The **market** is a PDA holding one trading pair's configuration: the two mint addresses, the fee in basis points, the tick size, the lot sizes, the minimum order size, an `is_active` flag, and the authority (Maria). Its seeds are the string `"market"` plus the two mints, so one base/quote pair maps to one market. The market PDA is also the authority of all three vaults, so tokens leave them only through the program's own instruction handlers. @@ -44,15 +44,15 @@ So the book's two sides are **critbit trees**. The name sounds complex; the idea Maria runs the exchange. She is the market authority: she creates the market and she alone can withdraw its fees. Her revenue is a fee of 25 **basis points**, a quarter of one percent, carved out of each fill's quote proceeds when orders match, so it is borne by whichever side of the fill receives the quote token. -Maria's client first creates the empty order book account, then calls `initialize_market()` with her chosen parameters: `fee_basis_points` (25), `tick_size`, `base_lot_size`, `quote_lot_size`, and `min_order_size`. The handler validates all of them: every size must be nonzero, and the fee is capped at 10,000 basis points, because a fee above 100% is nonsense. It creates the market PDA and the three vaults with the market as authority, records every address on the market account, and stamps the order book's first bytes so each tree knows which side it is. +Maria's client first creates the empty order book account, then calls `initialize_market()` with her chosen parameters: `fee_basis_points` (25), `tick_size`, `base_lot_size`, `quote_lot_size`, and `min_order_size`. The handler validates all of them: every size must be nonzero, and the fee is capped at 10,000 basis points, because a fee above 100% is nonsense. It creates the market account and the three vaults with the market as authority, records every address on the market account, and stamps the order book's first bytes so each tree knows which side it is. The tick size and minimum order size are market hygiene. Prices must land on a multiple of `tick_size` or the order is rejected, which keeps the book from fragmenting into thousands of dust price levels. Orders below `min_order_size` are rejected outright, which keeps spam from filling the trees. -![Maria's initialize_market() creates the market PDA with its parameters and three empty vaults, and stamps two empty trees onto the pre-created order book account.](figures/order-book-initialize-market.svg) +![Maria's initialize_market() creates the market account with its parameters and three empty vaults, and stamps two empty trees onto the pre-created order book account.](figures/order-book-initialize-market.svg) ## Every Trader Gets a Ledger Line -Three traders use the market this chapter: Alice, Bob, and Carol, each starting with the standard wallet of one SOL and 1,000 USDC, plus (for the two sellers, Alice and Carol) some NVDAx. Before trading, each of them calls `create_market_user()` once. It creates their market user account with zeroed unsettled balances and an empty open-order list. This is the account the exchange will owe money to; think of it as their line in the exchange's internal ledger. +Three traders use the market this chapter: Alice, Bob, and Carol, each starting with the standard wallet of one SOL and 1,000 USDC, plus (for the two sellers, Alice and Carol) some NVDAx. Before trading, each of them calls `initialize_market_user()` once. It creates their market user account with zeroed unsettled balances and an empty open-order list. This is the account the exchange will owe money to; think of it as their line in the exchange's internal ledger. ## An Ask Rests Above the Best Bid diff --git a/building-financial-products-in-solana/perpetual-futures.md b/building-financial-products-in-solana/perpetual-futures.md index 4f0ce998..3226921c 100644 --- a/building-financial-products-in-solana/perpetual-futures.md +++ b/building-financial-products-in-solana/perpetual-futures.md @@ -14,7 +14,7 @@ That one design decision creates the problem the whole program is organized arou ## The Account Model: One Pool, One Vault, One Position per Side per Trader -![The account map: the pool PDA and its authority PDA, the single custody vault and LP share mint they control, one position PDA per trader per side, and the oracle feed the pool is pinned to.](figures/perps-account-map.svg) +![The account map: the pool account and its authority PDA, the single custody vault and LP share mint they control, one position account per trader per side, and the oracle feed the pool is pinned to.](figures/perps-account-map.svg) The **pool** is a PDA with seeds `"pool"`, the collateral mint, and the oracle feed, so one market exists per collateral-and-feed pair. Its struct, `Pool`, carries the risk parameters set at creation (`max_leverage`, `maintenance_margin_bps`, `open_fee_bps`, `close_fee_bps`, `liquidation_fee_bps`, `max_confidence_bps`, `funding_rate_per_slot`) and the live accounting this chapter keeps returning to: @@ -115,7 +115,7 @@ Plus the book's standing invariants: the vault balance always equals `liquidity - **First-depositor share inflation**: closed by `MINIMUM_LIQUIDITY`, and `test_first_deposit_below_minimum_fails` proves a dust first deposit cannot even open the pool. - **Winning too big**: profit beyond the reservation is clipped by the cap rather than bankrupting the pool; the trade-off is printed on the tin. - **Sandwiching and price drift**: every money handler takes a user-supplied bound and fails with `SlippageExceeded` instead of filling at a surprise price. -- **Double close, double liquidation**: the position PDA closes on exit; the second call finds no account. +- **Double close, double liquidation**: the position account closes on exit; the second call finds no account. - **Config foot-guns**: parameter validation at pool creation, including the maintenance-versus-close-fee cross-check. ## Testing Considerations diff --git a/building-financial-products-in-solana/prop-amm.md b/building-financial-products-in-solana/prop-amm.md index 0f682cf0..3feb5272 100644 --- a/building-financial-products-in-solana/prop-amm.md +++ b/building-financial-products-in-solana/prop-amm.md @@ -1,4 +1,4 @@ -# Chapter 15: The Prop AMM +# Chapter 16: The Prop AMM The constant-product chapter ended with the formula as the maker: one multiplication replacing the order book, at the cost of price impact and impermanent loss. This chapter builds the venue that quietly took most of Solana's swap volume away from both designs: the **proprietary AMM**, or prop AMM. A market-making firm funds a pool with its own capital and quotes both sides of it: anyone may buy the base token at the oracle price plus a spread, or sell it at the oracle price minus the spread. There is no curve, there are no liquidity providers, and there are no LP shares. The operator is the only capital in the market, which is the property the name comes from: *proprietary* trading is trading the firm's own money. @@ -12,7 +12,7 @@ Two consequences follow immediately, and both are absences. A trade's unit price ## The Account Model: One Market, Two Vaults, and Nothing Else -![The account map: the market PDA pinned to its oracle feed, the dataless authority PDA that owns the base and quote vaults, and columns for Maria the operator and the traders who swap against the vaults.](figures/prop-amm-account-map.svg) +![The account map: the market account pinned to its oracle feed, the dataless authority PDA that owns the base and quote vaults, and columns for Maria the operator and the traders who swap against the vaults.](figures/prop-amm-account-map.svg) The **market** is a PDA seeded by the two mints: one market per pair, because the deployment *is* the firm; real prop AMMs are closed programs deployed by the firms themselves. The struct holds the operator's key, the pinned oracle feed and its scale, the two token decimals (pinned at creation so the math never trusts a passed-in mint), `spread_bps`, a confidence limit, and a `paused` flag. A dataless **market authority** PDA owns the **base vault** and **quote vault**, and exists to sign. diff --git a/building-financial-products-in-solana/token-fundraiser.md b/building-financial-products-in-solana/token-fundraiser.md index ffe2983b..3882969e 100644 --- a/building-financial-products-in-solana/token-fundraiser.md +++ b/building-financial-products-in-solana/token-fundraiser.md @@ -18,10 +18,10 @@ The program calls the fundraiser's creator the **maker**: Maria here, and her in ![The account map: Maria's fundraiser PDA owns the vault token account; each contributor holds a contributor PDA recording their running total.](figures/fundraiser-account-map.svg) -The **fundraiser** account is a PDA with seeds `"fundraiser"` and the maker's address, which means one live fundraiser per maker; a second `initialize()` from the same key finds the account already in existence and fails. Its struct, `Fundraiser`, is the entire terms sheet: +The **fundraiser** account is a PDA with seeds `"fundraiser"` and the maker's address, which means one live fundraiser per maker; a second `initialize_fundraiser()` from the same key finds the account already in existence and fails. Its struct, `Fundraiser`, is the entire terms sheet: - `maker` and `mint_to_raise`: who is raising, and in what token (USDC here; the program accepts any mint, including Token Extensions mints, because every account is declared through the token interface). -- `amount_to_raise`: the target, 9,000 USDC in minor units. `initialize()` rejects a target below three major units with `InvalidAmount`, so degenerate dust raises cannot exist. +- `amount_to_raise`: the target, 9,000 USDC in minor units. `initialize_fundraiser()` rejects a target below three major units with `InvalidAmount`, so degenerate dust raises cannot exist. - `current_amount`: the running total the program itself has counted. Keep an eye on this field; the chapter's sharpest lesson lives in it. - `time_started` and `duration`: the clock's unix timestamp at creation, and the window length in days. These two fields *are* the deadline. @@ -31,13 +31,13 @@ Each backer gets a **contributor** account, a PDA with seeds `"contributor"`, th ## The Maker Opens a Seven-Day Window -Maria calls `initialize()` with a target of 9,000 USDC and a duration of 7 days, and this is the last decision she gets to make. Nothing in the program lets her extend the deadline, raise or lower the target, or touch the vault before the target is met. Compare that with the web2 version of this product, where the platform can and occasionally does change campaign rules mid-flight; here the terms are physically incapable of changing, because there is no handler whose job is changing them. +Maria calls `initialize_fundraiser()` with a target of 9,000 USDC and a duration of 7 days, and this is the last decision she gets to make. Nothing in the program lets her extend the deadline, raise or lower the target, or touch the vault before the target is met. Compare that with the web2 version of this product, where the platform can and occasionally does change campaign rules mid-flight; here the terms are physically incapable of changing, because there is no handler whose job is changing them. The handler stamps `time_started` from the cluster clock and stores the duration. Both later handlers reconstruct the deadline the same way: elapsed days are `(now − time_started) / 86,400`, integer division, floored, using the constant `SECONDS_TO_DAYS`. The Time chapter's warning applies: with day-granularity math, the window ends at the first tick of day seven, not at some intuitive "end of the seventh day". `contribute()` requires the floored day count to be *less than* the duration; `refund()` requires it to be *at least* the duration. The same expression, compared both ways, so there is no gap and no overlap between "you may still contribute" and "you may now refund": at the moment the elapsed-day count reaches 7, contributions turn off and refunds turn on, atomically, by arithmetic. ## Nobody May Carry More Than a Tenth of the Raise -Ten backers fund this raise, each starting with the standard wallet of one SOL and 1,000 USDC, and each with their own reason. Alice runs three cafes and wants Maria's machine on her counters. Bob distributes restaurant equipment and wants a new product line to carry. Carol is an espresso enthusiast who wants one at home badly enough to help it exist. None of them will trust Maria, or each other, with a refund promise; they will trust a program whose refund path they can read. +Eleven backers fund this raise, each starting with the standard wallet of one SOL and 1,000 USDC, and each with their own reason. Alice runs three cafes and wants Maria's machine on her counters. Bob distributes restaurant equipment and wants a new product line to carry. Carol is an espresso enthusiast who wants one at home badly enough to help it exist. None of them will trust Maria, or each other, with a refund promise; they will trust a program whose refund path they can read. Alice comes in hot and tries to contribute her entire 1,000 USDC. The program refuses with `ContributionTooBig`. Her limit, computed by `calculate_max_contribution()`, is 10% of the target: 900 USDC. The constants behind it, `MAX_CONTRIBUTION_PERCENTAGE` over `PERCENTAGE_SCALER`, read 10/100, and the multiply runs in u128 before one floor division, the same multiply-before-divide discipline as every other chapter. @@ -45,9 +45,9 @@ The cap is not just per call. Carol contributes 450 USDC, then comes back the ne Sit with the arithmetic consequence for a second: if no one may carry more than a tenth of the target, then **a successful raise needs at least ten distinct contributors**. That is a design statement about what this fundraiser is for. Maria is not trying to find one patron; she is trying to prove there is a market for the machine, and a raise that only closes with broad participation is evidence money alone cannot fake. It also blunts the obvious manipulation: a single whale filling the raise in one transaction to make demand look broad now needs ten wallets and still shows up as ten receipts on the fundraiser. -So the money arrives like this: Alice 900, Bob 900, Carol 450 and 450, and seven other backers at 900 each. Each `contribute()` moves tokens from the contributor's own token account into the vault (the contributor signs; inbound transfers never need PDA signatures), bumps their receipt, and adds to `current_amount`. Total: exactly 9,000 USDC. +So the money arrives like this: Alice 900, Bob 900, Carol 450 and 450, and eight more backers between 550 and 900 apiece. Ten is the floor, not the usual case: it takes eleven here precisely because most of them stopped short of the cap. Each `contribute()` moves tokens from the contributor's own token account into the vault (the contributor signs; inbound transfers never need PDA signatures), bumps their receipt, and adds to `current_amount`. Total: exactly 9,000 USDC. -![Ten contributors, none above 900 USDC: the 10% cap means the thermometer only fills with broad participation.](figures/fundraiser-cap.svg) +![Eleven contributors, none above 900 USDC, each slice as tall as what that backer gave: the 10% cap means the thermometer only fills with broad participation.](figures/fundraiser-cap.svg) ## The Counter Is the Truth; the Vault Is Just Custody @@ -61,7 +61,7 @@ The trade-off is honest bookkeeping in both directions. Tokens donated straight `check_contributions()` is the maker's claim. Maria signs, the PDA seeds re-derive her fundraiser, the counter passes the `TargetNotMet` gate, and three things happen in one transaction: the vault's entire balance transfers to Maria's token account (signed by the fundraiser PDA), the vault closes, and the fundraiser account closes too, returning both rent deposits to her. The state machine does not mark itself finished; it **ceases to exist**, which is the strongest possible "finished" and makes double-claiming a structural impossibility rather than a flag check. The test `test_check_contributions_success_pays_maker_and_closes_vault` pins all three effects. -One loose end survives success, and it is worth being honest about because you will meet it in your own designs: the ten contributor receipts. Their only closing path is `refund()`, and after the claim the fundraiser account a refund must read no longer exists, so each backer's receipt rent (about a thousandth of a SOL) is stuck in an account that can never close. The fix is one more handler that closes a receipt without paying anything once the fundraiser is gone; the program omits it, and the omission is the kind you should notice by habit: *for every account your program creates, name the handler that eventually closes it.* +One loose end survives success, and it is worth being honest about because you will meet it in your own designs: the eleven contributor receipts. Their only closing path is `refund()`, and after the claim the fundraiser account a refund must read no longer exists, so each backer's receipt rent (about a thousandth of a SOL) is stuck in an account that can never close. The fix is one more handler that closes a receipt without paying anything once the fundraiser is gone; the program omits it, and the omission is the kind you should notice by habit: *for every account your program creates, name the handler that eventually closes it.* ![Success: one transaction drains the vault to Maria and closes both the vault and the fundraiser; the contributor receipts are the one thing left standing.](figures/fundraiser-success.svg) @@ -116,4 +116,4 @@ The concepts you now have, on top of the escrow's: - **Cumulative caps.** Per-participant limits checked against running totals, which turn "broad participation" from hope into arithmetic. - **Name the closer for every account.** The stranded contributor receipts after a successful raise are this chapter's honest omission, and the habit that catches it. -An escrow held two strangers to one deal. The fundraiser holds eleven strangers to one condition, and paid for it with exactly one new mechanism: a deadline that flips which exit is open. Every program in the rest of this part is this same shape under increasing pressure: a vault owned by a data account, terms frozen at entry, and an exit from every state for every participant's money. The next chapter puts the shape on a trading floor. +An escrow held two strangers to one deal. The fundraiser holds eleven strangers to one condition, and paid for it with exactly one new mechanism: a deadline that flips which exit is open. Every program in the rest of this part is this same shape under increasing pressure: a vault owned by a data account, terms frozen at entry, and an exit from every state for every participant's money. The next chapter opens the shape to a crowd and asks the question this one never had to: who decides what happened in the real world? diff --git a/building-financial-products-in-solana/vault-strategy.md b/building-financial-products-in-solana/vault-strategy.md index d7191920..b74b53a2 100644 --- a/building-financial-products-in-solana/vault-strategy.md +++ b/building-financial-products-in-solana/vault-strategy.md @@ -18,7 +18,7 @@ The approved set is stocks, bonds, whatever investors actually want. It is not t ## The Account Model: A Strategy, Its Vaults per Asset, and a Share Mint -![The account map: the strategy PDA owns a USDC vault, one vault per basket asset, and the share mint; each asset config PDA records a mint, a feed, a weight, and a vault; Victor's registry and ApprovedAsset accounts sit behind them, and Alice's and Bob's share accounts hang off the share mint.](figures/vault-account-map.svg) +![The account map: the strategy PDA owns a USDC vault, one vault per basket asset, and the share mint; each asset config account records a mint, a feed, a weight, and a vault; Victor's registry and ApprovedAsset accounts sit behind them, and Alice's and Bob's share accounts hang off the share mint.](figures/vault-account-map.svg) The **strategy** PDA (seeds: `"strategy"`, an index) records the `manager`, the `registry` it is bound to, its `fee_bps` and `max_slippage_bps`, the `swap_router` it is allowed to trade through, its running `total_shares` and `total_weight_bps`, and `last_fee_accrual_timestamp` for the fee clock. The **share mint** is a PDA with the strategy as mint authority: shares only come into existence when the program's own math says so. @@ -95,7 +95,7 @@ The suite (with hand-built Pyth price-update accounts injected so prices are det ## What You Learnt from Building the Vault Strategy -An index fund needs a custodian, a compliance officer, a fee schedule in a prospectus, and an auditor checking that the manager's trades match the mandate. This chapter needed a registry PDA, a weight sum that must hit 10,000, a fee constant with no setter, and a slippage floor computed from a feed the manager cannot choose. +An index fund needs a custodian, a compliance officer, a fee schedule in a prospectus, and an auditor checking that the manager's trades match the mandate. This chapter needed a registry account, a weight sum that must hit 10,000, a fee constant with no setter, and a slippage floor computed from a feed the manager cannot choose. The concepts you now have: diff --git a/conclusion/where-to-go-from-here.md b/conclusion/where-to-go-from-here.md new file mode 100644 index 00000000..a1a9bb83 --- /dev/null +++ b/conclusion/where-to-go-from-here.md @@ -0,0 +1,63 @@ +# Chapter 20: Where to Go From Here + +Nine programs are behind you. An escrow that holds two strangers to one deal, and a perpetual futures venue that prices leverage continuously against an oracle, look like different species of software. They are not. Strip both of their subject matter and the same small machine is left standing, and the distance between them is mostly how much pressure that machine is under. + +## One Pattern, Under Nine Kinds of Pressure + +Every program in this book is built from the same six pieces. + +**A data account holds the terms, and a separate vault holds the money.** The offer account records what the maker wants; the vault holds what the maker gave. The obligation records a borrower's debt; the reserve holds the liquidity. Keeping the promise separate from the property is what lets a program answer "who is owed what" without counting tokens, and it is why a stray transfer into a vault never unlocks anything. + +**A PDA is the authority over the vault, so funds move only along code paths.** This is the entire content of the phrase "the program holds the funds". No key exists that could move that money by hand, which is a stronger guarantee than any promise an operator can make, and a heavier responsibility than any operator is used to carrying. + +**One instruction handler performs one state transition.** Draw the states and the legal edges between them before writing anything, and the handler list falls out of the drawing. Handlers that do two transitions, or half of one, are where partial-state bugs live. + +**Terms freeze when money commits.** The betting market copies the fee onto the event at creation; the fundraiser stamps its deadline before the first contribution. Whatever an operator can change, they cannot change it retroactively for money already inside. + +**Every state has an exit, for every participant's money.** This is the rule that finds missing requirements rather than missing code. Walk each state, ask how each party's money leaves from here, and any state without an answer is a stranded-funds bug that no amount of correct arithmetic will fix. + +**Arithmetic runs in integers of minor units, multiplying before dividing, rounding against whoever could profit from the rounding.** A sub-unit leaked on every operation is not a rounding error once someone writes the loop. + +Those six sentences are what this book was for. Frameworks will change under them, and they will keep being true. + +## What You Can Build Now + +You can take a financial product you understand in the real world and lay it out as accounts, handlers, and invariants, because you have watched that translation happen nine times with the seams left showing. + +You can read somebody else's program and ask the questions that matter: which accounts are validated and which are trusted, who holds the authorities, what freezes at entry, and which state has no exit. That is the same checklist an auditor opens with. + +You can work with an AI as a peer rather than an oracle. The companion AI skill will apply this book's rules to code your tools generate, and you can now tell when the output is wrong, which is the only thing that makes generated financial code safe to use. + +You can reason about who earns what, and why anyone shows up. Every chapter named the operator's incentive and the user's, because a mechanism nobody is paid to run does not run. + +And you can carry the responsibility that comes with all of it. Programs holding other people's money are not a place to find out whether an idea works. + +## What This Book Left Out + +Some things were left out on purpose, and knowing which is part of knowing the shape of the field. + +**Staking and reward distribution** got no chapter of its own, though you have already met its core: the reward-per-token index inside the lending market and the parimutuel pool is the same accounting a staking program runs. What is missing is the operational half, the unbonding periods and the validator relationships. + +**Vesting and streaming payments** are a natural next build and a genuinely good first project, because the state machine is small, the arithmetic is a straight line against the clock, and the failure modes are ones you have already met. + +**Formal verification** appears here only as a taster. The escrow's proofs show what a machine-checked invariant looks like, but proving as a discipline, deciding what is worth proving and modelling the parts that cannot be executed symbolically, is a subject the size of this book. + +**Governance** is deliberately absent. Every program here has an operator with a key, and the honest treatment of admin power is in Operating Financial Software. Replacing that key with a voting process is a real design problem and a different one. + +**The interface** is absent too. Nothing in these pages covers wallets, transaction building, or the frontend, and a program with no interface reaches nobody. + +## Build One, Then Have It Attacked + +The gap between reading this book and being able to ship is one project wide, and it closes faster than you expect. Pick the smallest product you actually want to exist. Draw its state machine before you write a handler. Give every state an exit. Write the tests that drive the machine down every path, especially the exits, and the property tests that assert what must never change. + +Then get a second opinion from somebody who was not in the room. Every wall in this book's failure-mode sections exists because someone's first opinion missed something, and an auditor is the cheapest adversary you will ever face, because that one writes you a report instead of a withdrawal. + +Deploy to localnet, then devnet, and let mainnet be where a finished program goes live rather than where you find out whether it works. + +## Corrections Are Welcome, and Expected + +Long-lived technical books are not written once. They are corrected, edition after edition, by readers who found the sentence that was wrong. + +The errata page and the companion repository are both at [thesolanabook.com](https://thesolanabook.com). If a number here does not match the code, if a mechanism has changed, or if an explanation left you stuck at exactly the wrong moment, that is worth reporting, and contributors are credited by name. + +The account model has been stable since Solana launched. The arithmetic will not change. Neither will the desire of adversaries to take your assets. Build carefully, and the rest of it is detail. diff --git a/financial-programming-concepts-in-solana/figures/rent-lifecycle.svg b/financial-programming-concepts-in-solana/figures/rent-lifecycle.svg index dd7510bc..ff0915a2 100644 --- a/financial-programming-concepts-in-solana/figures/rent-lifecycle.svg +++ b/financial-programming-concepts-in-solana/figures/rent-lifecycle.svg @@ -26,10 +26,15 @@ canceled both exits close the accounts and refund every lamport to the maker - - - - - no close handler: the deposit is stranded forever - (the bug this chapter forbids) + + no handler closes it + + + + vault + + tokens and rent, both the maker’s + and no handler can return them + + a state with no exit is a stranded deposit: a bug in the program diff --git a/financial-programming-concepts-in-solana/intro.md b/financial-programming-concepts-in-solana/intro.md index e079dfbf..1f87134f 100644 --- a/financial-programming-concepts-in-solana/intro.md +++ b/financial-programming-concepts-in-solana/intro.md @@ -18,7 +18,7 @@ The design rules fall straight out of the drawing: The rent deposits from Solana Building Blocks are where this rule meets real money. Rent is small, but it is somebody's, and the design rule is simple: **whoever wants the account pays for it**. The maker funds her offer account and vault; the taker funds his own token account if he never held that token. Rent burdens follow the party who chose to create the state, and the deposit comes home on every path (take, cancel, claim, refund); a program that leaves fragments of state behind on some paths has a slow leak of other people's lamports. An account that can never close is a deposit stranded forever, so for every account your program creates, name the handler that eventually closes it; the escrow's tests assert the maker's lamports round-trip exactly, and yours should too. -![The rent lifecycle: the maker funds the offer and vault at creation, both exits refund every lamport, and the crossed-out path is the stranded-deposit bug.](figures/rent-lifecycle.svg) +![The rent lifecycle: the maker funds the offer and vault at creation, and both exits refund every lamport. The third path is a vault the program has no handler to close, leaving the maker's tokens and rent stranded inside a perfectly ordinary account.](figures/rent-lifecycle.svg) ## What You Learnt diff --git a/financial-programming-concepts-in-solana/offchain-truth.md b/financial-programming-concepts-in-solana/offchain-truth.md index 92a51b12..75a8ccb7 100644 --- a/financial-programming-concepts-in-solana/offchain-truth.md +++ b/financial-programming-concepts-in-solana/offchain-truth.md @@ -1,6 +1,6 @@ # Chapter 8: Offchain Truth -A program can verify a lot on its own: signatures, balances, account ownership, its own arithmetic. But most financial products depend on facts that happen in the real world, outside anything the chain can see. What is NVIDIA trading at? Which film grossed highest this month? Did the shipment arrive? Did the borrower's collateral just crash? Every one of the book's markets turns on a fact like this: prices for the lending market and the perpetual futures exchange, an event outcome for the betting market, asset valuations for the vault strategy. And they all get onchain on the same foundation: some party signs, and a careful program checks far more than that signature. +A program can verify a lot on its own: signatures, balances, account ownership, its own arithmetic. But most financial products depend on facts that happen in the real world, outside anything the chain can see. Did the Federal Reserve cut rates this afternoon? Which film grossed highest this month? Did the shipment arrive? Did the borrower's collateral just crash? Every one of the book's markets turns on a fact like this: prices for the lending market and the perpetual futures exchange, an event outcome for the betting market, asset valuations for the vault strategy. And they all get onchain on the same foundation: some party signs, and a careful program checks far more than that signature. ## An Oracle's Publication Is Input, Not Gospel diff --git a/financial-programming-concepts-in-solana/operating-financial-software.md b/financial-programming-concepts-in-solana/operating-financial-software.md index e7265d64..cb643be7 100644 --- a/financial-programming-concepts-in-solana/operating-financial-software.md +++ b/financial-programming-concepts-in-solana/operating-financial-software.md @@ -19,7 +19,13 @@ The mechanics are cheap: LiteSVM runs the real program in-process in millisecond A test checks the inputs you thought of; a proof checks all of them. The ladder has three rungs, and financial software should climb as high as the money justifies. Tests come first: the discipline above, driving every code path with chosen values. Property tests come second: assert a law (a round trip creates no value, an index never decreases) over generated inputs, so the values are not just the ones you imagined. **Formal verification** is the top rung: a model checker like [Kani](https://github.com/model-checking/kani) explores *every* possible input to a function, bit by bit, and either proves the property holds or hands you the exact counterexample. -The escrow program in the companion repository ships a set of Kani harnesses that do precisely this for its verifiable core: `proof_take_offer_conserves_value` proves that taking an offer conserves both tokens' totals on every input, not just the tested ones; `proof_cancel_offer_returns_all_to_maker` proves the refund path whole; `proof_offer_id_seed_injective` proves two different offer IDs can never derive the same address. Onchain code that delegates to other programs cannot be symbolically executed end to end, so the technique is to mirror the program's arithmetic and ordering in pure functions and prove those: the same shape the Percolator perpetuals engine uses. When a chapter of this book says an invariant "must hold," this is what taking that sentence literally looks like. +The escrow program in the companion repository ships a set of Kani harnesses that do precisely this for its verifiable core: + +- `proof_take_offer_conserves_value` proves that taking an offer conserves both tokens' totals on every input, not just the tested ones. +- `proof_cancel_offer_returns_all_to_maker` proves the refund path whole. +- `proof_offer_id_seed_injective` proves two different offer IDs can never derive the same address. + +Onchain code that delegates to other programs cannot be symbolically executed end to end, so the technique is to mirror the program's arithmetic and ordering in pure functions and prove those: the same shape the Percolator perpetuals engine uses. When a chapter of this book says an invariant "must hold," this is what taking that sentence literally looks like. ## Ship With a Second Opinion @@ -32,7 +38,7 @@ Every market in this book has an operator with real power: Maria withdraws fees, - **Bound what configuration can express.** Validate every parameter at write time against limits that make abuse structurally impossible: the fee that cannot exceed 10,000 basis points, the loan-to-value that cannot exceed the liquidation threshold, the manager fee capped because an uncapped one drains the vault *by configuration*. A config the admin cannot make dangerous is worth more than an admin promise. - **Snapshot terms when money commits.** The betting market copies the fee onto each event at creation; the vault strategy's fee has no setter at all. Whatever the admin can change, they cannot change it retroactively for funds already inside. - **Keep admin money and user money in different pots.** The fee vault, the accumulated-fees counter carved out of the exchange rate: the admin's upside is segregated so that collecting it cannot touch what users are owed, and the withdraw path moves only from the segregated pot. -- **Give the admin no path to principal.** The strongest property in the book's programs: no instruction handler, under any parameters, moves user deposits to the operator. Rebalancing moves funds between program-owned vaults; settlement pays by formula. When that property holds, the honest claim is not "non-custodial", it is better and more precise: the rules are the deployed bytecode, and the operator is not in them. +- **Give the admin no path to principal.** The strongest property in the book's programs: no instruction handler, under any parameters, moves user deposits to the operator. Rebalancing moves funds between program-owned vaults; settlement pays by formula. When that property holds, the honest claim is not "non-custodial", it is better and more precise: the rules are the deployed program, and the operator is not in them. - **Pausing is for entrances, not exits.** A pause switch that stops new deposits and new positions during an incident protects users; one that blocks withdrawals holds them hostage. Design the pause to narrow the program toward its exits, and remember the state-machine rule: every state needs an exit *even while paused*. - **Plan for the admin going silent.** Fees claimable whenever (a no-op when empty, so a cron can run it), nothing user-facing gated on the operator showing up, and an escape hatch for the day the price feed dies: a market that cannot value collateral should be able to enter a wind-down where positions close and deposits leave, rather than freezing everyone in place waiting for truth that is not coming. - **Make upgrade authority worth trusting.** Ship fixes through a multisig with named holders rather than one laptop key, and say so. Users are not trusting that the code will never change; they are trusting that changing it takes more than one bad day. diff --git a/financial-programming-concepts-in-solana/the-adversary.md b/financial-programming-concepts-in-solana/the-adversary.md index 980cc46f..87cdda19 100644 --- a/financial-programming-concepts-in-solana/the-adversary.md +++ b/financial-programming-concepts-in-solana/the-adversary.md @@ -4,7 +4,7 @@ APIs churn; exploits recur. The same half-dozen attack classes have drained onch ## Every Mistake Is a Bounty, Claimable by Anyone -Deploying financial software on a public chain means accepting a threat model no other kind of software faces. Every instruction handler is callable by anyone on earth, pseudonymously, for a fraction of a cent. Your source, or at least your bytecode, is public. And every mistake is not a crash report but a bounty: a mispriced trade, a leaked minor unit, an unchecked account is money, claimable by the first person to notice. +Deploying financial software on a public chain means accepting a threat model no other kind of software faces. Every instruction handler is callable by anyone on earth, pseudonymously, for a fraction of a cent. Your source, or at least the compiled program, is public. And every mistake is not a crash report but a bounty: a mispriced trade, a leaked minor unit, an unchecked account is money, claimable by the first person to notice. Three consequences shape everything: @@ -30,13 +30,13 @@ Every share-based pool (vault, LP pool, lending reserve) prices shares as pool v The defenses all attack the degenerate zone directly. **Lock a minimum liquidity** that is never minted to anyone, as the AMM does with its floor of 100, so share supply can never be dust-sized relative to the pool. **Use virtual shares and virtual assets**: add a constant to both sides of the exchange-rate division so the ratio is well-conditioned even when real supply is zero. **Make donations inert**: track pool size in program state (the lending market's `available_liquidity`) rather than reading the vault's token balance, so tokens pushed into the vault sideways simply do not count. And **require a meaningful first deposit**. One of these, deliberately chosen, is enough; the programs to worry about are the ones (like the book's own vault strategy, which says so honestly) that rely on circumstance instead of a named defense. -## A True Price Can Still Be a Purchased Price +## A True Price Can Still Be a Manipulated Price Everything in Offchain Truth assumed the fact was honest, and its checks (publisher, freshness, confidence) defend against *fake* facts. This class is nastier: the fact is real, fresh, and correctly verified, and it is an accurate report of a market the attacker just bent. Push a thin market's price up for one slot, and every program consuming that price acts faithfully on it: lending markets extend credit against inflated collateral, exchanges settle at fantasy prices, liquidations fire on positions that were healthy a block ago. -> **War Story: Mango Markets, $116M, 2022.** An attacker opened large perpetual positions in Mango's own thinly-traded MNGO token, then bought MNGO aggressively on the spot markets the oracle read from, moving the price up more than twenty-fold in minutes. The oracle reported the manipulated price accurately. Mango's risk engine, believing it, marked the attacker's positions massively profitable, and the attacker borrowed $116M of the platform's deposits against that unrealized profit and left. The attacker's own description, "a highly profitable trading strategy," became the class's dark punchline: no signature was forged and no account spoofed; the input was true and the truth had been purchased. +> **War Story: Mango Markets, $116M, 2022.** An attacker opened large perpetual positions in Mango's own thinly-traded MNGO token, then bought MNGO aggressively on the spot markets the oracle read from, moving the price up more than twenty-fold in minutes. The oracle reported the manipulated price accurately. Mango's risk engine, believing it, marked the attacker's positions massively profitable, and the attacker borrowed $116M of the platform's deposits against that unrealized profit and left. The attacker's own description, "a highly profitable trading strategy," became the class's dark punchline: no signature was forged and no account spoofed; the input was true, and the market behind it had been manipulated. -The defenses are about making purchased truth expensive and slow. **Time-weight the price**: a **time-weighted average price** (TWAP) over minutes forces the attacker to hold the manipulation, at cost, against arbitrage, instead of renting one block of it. **Respect the confidence interval**: a manipulated thin market shows its stress as widened confidence, and a consumer that degrades (pausing borrows, tightening limits) when confidence widens closes the profitable window. **Bound per-block state change**: caps on how much can be borrowed or withdrawn per window turn a one-shot drain into a multi-block campaign that defenders can see. And the bluntest, most effective one: **do not build leverage on a market thinner than the credit it collateralizes.** If the cost of moving the price is less than what the move unlocks, that trade will eventually be made. +The defenses are about making price manipulation expensive and slow. **Time-weight the price**: a **time-weighted average price** (TWAP) over minutes forces the attacker to hold the manipulation, at cost, against arbitrage, instead of renting one block of it. **Respect the confidence interval**: a manipulated thin market shows its stress as widened confidence, and a consumer that degrades (pausing borrows, tightening limits) when confidence widens closes the profitable window. **Bound per-block state change**: caps on how much can be borrowed or withdrawn per window turn a one-shot drain into a multi-block campaign that defenders can see. And the bluntest, most effective one: **do not build leverage on a market thinner than the credit it collateralizes.** If the cost of moving the price is less than what the move unlocks, that trade will eventually be made. ## A Rounding Error Is a Faucet Once Someone Finds the Loop @@ -59,5 +59,5 @@ Two more boundary rules. **Validate what a CPI returns and what it was given**: - Every mistake on a public chain is a bounty: attacks are industrialized, ordering is contestable, and the boundary values are where the money is. - Every supplied account is a claim to verify, and every trust chain must terminate in something the program itself derived or recorded. - Empty pools need a named defense against inflation attacks, and rounding leaks become faucets the moment someone finds the loop. -- A true price can still be a purchased price: time-weight it, respect the confidence interval, bound per-block change, and never build leverage on a market thinner than the credit it collateralizes. +- A true price can still be a manipulated price: time-weight it, respect the confidence interval, bound per-block change, and never build leverage on a market thinner than the credit it collateralizes. - Checks-effects-interactions at every boundary: your invariants must hold at the end of your own instruction, because the instructions after yours belong to someone else. diff --git a/package.json b/package.json index 93227a54..7f4ff394 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,8 @@ "description": "Manuscript and build tooling for Building Financial Software on Solana", "scripts": { "build-pdf": "node scripts/build-pdf.mjs", - "lint": "node scripts/lint-manuscript.mjs" + "lint": "node scripts/lint-manuscript.mjs", + "preview": "node scripts/preview-page.mjs" }, "repository": { "type": "git", diff --git a/prelude/acknowledgements.md b/postlude/acknowledgements.md similarity index 100% rename from prelude/acknowledgements.md rename to postlude/acknowledgements.md diff --git a/prelude/introduction.md b/prelude/introduction.md index d8d04737..cebab802 100644 --- a/prelude/introduction.md +++ b/prelude/introduction.md @@ -12,7 +12,7 @@ Financial products built on Solana have properties no traditional rail offers: - **Settlement is atomic, instant, and global.** A trade and its settlement can be the same event, whether the two parties are in the same city or on different continents, and no failure leaves one side paid and the other empty-handed. - **Everyone reads the same accounts.** A traditional trade lands in two firms' separate books, and departments exist to check the copies against each other. Onchain there are no copies: a market's state is its accounts, and every party reads the same ones. There is nothing to reconcile. - **Money is programmable.** The rules an institution enforces with staff and procedure (release the escrow when both sides have delivered, accrue the interest, liquidate the underwater loan, pay the winners their share) run as instruction handlers: code anyone can invoke and nobody has to remember. Every chapter of this book builds rules like these. -- **Transactions cost a fraction of a penny**, regardless of the size of the transfer. Moving money the traditional way costs a percentage of it: card payments take roughly 1.5% to 3.5% of each sale plus a fixed 10 to 30 cents, and sending $200 across a border averaged 6.4% in late 2025, or close to 15% when a bank moved it. A fee that ignores the amount is a different kind of fee from one that scales with it, and it changes which products are worth building at all. +- **Transactions cost a fraction of a penny**, regardless of the size of the transfer. Moving money the traditional way costs a percentage of it: card payments take roughly 1.5% to 3.5% of each sale plus a fixed 10 to 30 cents, and sending $200 across a border runs from under 1% through a digital service like Wise to over 20% in cash over a Western Union or MoneyGram counter, in the parts of the world where cash over a counter is how money arrives. If your own transfers have always been domestic, or between wealthy countries, that upper figure will look invented: the World Bank prices every corridor quarterly at [remittanceprices.worldbank.org](https://remittanceprices.worldbank.org), and in the third quarter of 2025 thirteen corridors cost more than 20%, nine of them sending out of sub-Saharan Africa, against a global average near 6.5%. A fee that ignores the amount is a different kind of fee from one that scales with it, and it changes which products are worth building at all. - **Blockchains can move assets of any kind.** Traditional "crypto" assets like BTC and SOL, and real-world assets (RWAs): commodities, equities, real estate, and exotics. As of mid-2026, Solana is the most popular chain for real world asset trading. - **Distribution is the deployment.** A traditional fund spends months and a chain of intermediaries reaching its investors: an exchange listing, market makers, brokerage platform approvals, each a gatekeeper with its own cost and lead time. A deployed program is reachable by every wallet on the network, and by every other program, the moment it exists. The legal duties of offering a financial product do not change; the infrastructure between you and your investors is what disappears. @@ -61,7 +61,7 @@ The diagrams throughout the book share one set of conventions, and each conventi ## Programs Follow Patterns, Patterns Build Understanding -The book has two parts. **The Foundations** is everything you need before writing financial software, in two halves: Building Blocks is what is Solana-mechanical (accounts, tokens, PDAs, rent, atomicity, and time), and Financial Programming Concepts is what is money-specific (the arithmetic, offchain truth, the adversary, testing, and admin power). **Building Financial Products** is the nine program chapters. +The book has two parts. **The Fundamentals** is everything you need before writing financial software, in two halves: Building Blocks is what is Solana-mechanical (accounts, tokens, PDAs, rent, atomicity, and time), and Financial Programming Concepts is what is money-specific (the arithmetic, offchain truth, the adversary, testing, and admin power). **Building Financial Products** is the nine program chapters. Then every program chapter walks one complete financial program. Incentives for admins and individual users are discussed, and we show how individual instruction handlers modify onchain state on behalf of their users. diff --git a/scripts/build-pdf.mjs b/scripts/build-pdf.mjs index fb5ad1f5..062e5c26 100644 --- a/scripts/build-pdf.mjs +++ b/scripts/build-pdf.mjs @@ -72,6 +72,39 @@ const partForPath = new Map(); } } +// Justified text has to absorb a long unbreakable identifier somewhere, and it +// goes into the word spaces of the line before, which is why a paragraph naming +// proof_cancel_offer_returns_all_to_maker comes out visibly loose. Snake_case +// already tells the reader where the seams are, so offer a break opportunity +// after each underscore and let the line break there. is a break +// opportunity rather than a character, so the identifier a reader copies out of +// the PDF is still whole, and no hyphen appears to be mistaken for part of the +// name. Nothing else in an identifier is a legible place to break, so nothing +// else gets one: a name with no underscores stays atomic. +// +// Not every underscore is worth breaking at. Breaking at the first one in +// test_something strands "test_" at the end of a line, which reads as noise and +// saves almost nothing, so a break has to leave a substantial fragment behind +// and carry a substantial one over. These two limits are the idea behind CSS +// hyphenate-limit-chars, applied by hand because has no such knob. +const BREAK_MIN_BEFORE = 8; +const BREAK_MIN_AFTER = 5; +const breakableIdentifier = (text) => + text + .split(/(\s+)/) + .map((token) => { + if (!token.includes("_")) return token; + let out = ""; + for (let i = 0; i < token.length; i++) { + out += token[i]; + const kept = i + 1; + const carried = token.length - kept; + if (token[i] === "_" && kept >= BREAK_MIN_BEFORE && carried >= BREAK_MIN_AFTER) out += ""; + } + return out; + }) + .join(""); + const sections = chapterPaths.map((path) => { const markdown = readFileSync(join(repoRoot, path), "utf8"); const id = idForPath(path); @@ -88,12 +121,18 @@ const sections = chapterPaths.map((path) => { .replace(/([^]*>/g, (whole, src, alt) => { const svg = readFileSync(join(repoRoot, dirname(path), src), "utf8"); return `
${svg}
${alt}
`; - }); + }) + .replace(/([^<]+)<\/code>/g, (whole, text) => `${breakableIdentifier(text)}`); const title = cleanTitle((markdown.match(/^# (.+)$/m) ?? [null, basename(path, ".md")])[1]); return { id, title, html, path, subsections }; }); -const isPreludeItem = (path) => path.startsWith("prelude/") && !path.endsWith("introduction.md"); +// Front and back matter: unnumbered, no table-of-contents row, sharing one +// page flow. Acknowledgements sit at the back because that list grows with +// every edition and is for the people named, not for the reader. +const isPreludeItem = (path) => + (path.startsWith("prelude/") && !path.endsWith("introduction.md")) || path.startsWith("postlude/"); + // The printed table of contents, in the style of TCP/IP Illustrated: // "Chapter N Title" rows in bold, "N.M Section" rows beneath, page numbers diff --git a/scripts/lint-manuscript.mjs b/scripts/lint-manuscript.mjs index f24a4178..e79f61c5 100644 --- a/scripts/lint-manuscript.mjs +++ b/scripts/lint-manuscript.mjs @@ -16,7 +16,9 @@ const manuscriptGlobs = [ "solana-building-blocks/*.md", "financial-programming-concepts-in-solana/*.md", "building-financial-products-in-solana/*.md", + "conclusion/*.md", "appendices/*.md", + "postlude/*.md", ]; // Each rule: a regex and the reason it is banned. Case-insensitive unless @@ -118,6 +120,13 @@ const svgRules = [ // than the value column. [/ATA PROGRAM/, + 'seed lists name seeds, not the deriving program: an ATA is OWNER\'S ADDRESS + TOKEN PROGRAM + MINT'], // The coin mark is three rings. Two rings read as a pair of address dots at // print size, where the accent colour is gone and the mark is about 2mm. [/]*stroke="#1e7a3c"/, diff --git a/scripts/preview-page.mjs b/scripts/preview-page.mjs new file mode 100644 index 00000000..fd5791c2 --- /dev/null +++ b/scripts/preview-page.mjs @@ -0,0 +1,66 @@ +// Rasterize a page of the built PDF to PNG, for eyeballing typography and +// figure placement. Finds the page by a phrase on it, so page-number drift +// between builds never previews the wrong page. +// Usage: npm run preview -- "" +import { readFileSync, writeFileSync, mkdtempSync } from "node:fs"; +import { join, resolve, dirname } from "node:path"; +import { tmpdir } from "node:os"; +import { fileURLToPath, pathToFileURL } from "node:url"; +import { chromium } from "playwright-core"; + +const repoRoot = resolve(dirname(fileURLToPath(import.meta.url)), ".."); +const [outPrefix, phrase] = process.argv.slice(2); +const pdfPath = join(repoRoot, "build", "Building Financial Software on Solana.pdf"); +const outDir = join(repoRoot, "build"); + +// Find the page carrying the phrase, so page-number drift between builds +// never silently previews the wrong page. +const pdfjsNode = await import("pdfjs-dist/legacy/build/pdf.mjs"); +const doc = await pdfjsNode.getDocument({ data: new Uint8Array(readFileSync(pdfPath)), useSystemFonts: true }).promise; +let target = null; +for (let p = 1; p <= doc.numPages && !target; p++) { + const text = (await (await doc.getPage(p)).getTextContent()).items.map((i) => i.str).join(" "); + if (text.replace(/\s+/g, " ").includes(phrase)) target = p; +} +if (!target) throw new Error(`phrase not found: ${phrase}`); + +const browser = await chromium.launch({ + executablePath: process.env.CHROMIUM_PATH ?? "/opt/pw-browsers/chromium", + args: ["--allow-file-access-from-files"], +}); +const workDir = mkdtempSync(join(tmpdir(), "preview-")); +const stage = join(workDir, "stage.html"); +writeFileSync(stage, "preview"); +const page = await browser.newPage(); +await page.goto(pathToFileURL(stage).href); + +const dataUrl = await page.evaluate( + async ({ pdfjsUrl, workerUrl, pdfB64, pageNumber, targetWidth }) => { + const pdfjs = await import(pdfjsUrl); + pdfjs.GlobalWorkerOptions.workerSrc = workerUrl; + const bytes = Uint8Array.from(atob(pdfB64), (c) => c.charCodeAt(0)); + const d = await pdfjs.getDocument({ data: bytes }).promise; + const pg = await d.getPage(pageNumber); + const viewport = pg.getViewport({ scale: targetWidth / pg.getViewport({ scale: 1 }).width }); + const canvas = document.createElement("canvas"); + canvas.width = Math.round(viewport.width); + canvas.height = Math.round(viewport.height); + const ctx = canvas.getContext("2d"); + ctx.fillStyle = "#fff"; + ctx.fillRect(0, 0, canvas.width, canvas.height); + await pg.render({ canvasContext: ctx, viewport }).promise; + return canvas.toDataURL("image/png"); + }, + { + pdfjsUrl: pathToFileURL(join(repoRoot, "node_modules", "pdfjs-dist", "legacy", "build", "pdf.mjs")).href, + workerUrl: pathToFileURL(join(repoRoot, "node_modules", "pdfjs-dist", "legacy", "build", "pdf.worker.mjs")).href, + pdfB64: readFileSync(pdfPath).toString("base64"), + pageNumber: target, + targetWidth: 1700, + }, +); + +const out = join(outDir, `${outPrefix}-p${target}.png`); +writeFileSync(out, Buffer.from(dataUrl.split(",")[1], "base64")); +console.log(out); +await browser.close(); diff --git a/solana-building-blocks/accounts-and-instruction-handlers.md b/solana-building-blocks/accounts-and-instruction-handlers.md index 505ebea5..24505483 100644 --- a/solana-building-blocks/accounts-and-instruction-handlers.md +++ b/solana-building-blocks/accounts-and-instruction-handlers.md @@ -14,7 +14,7 @@ At Alice's address lives Alice's **account**: some data with an address. Her acc ![Alice's public key is her address, a point on the curve; her private key stays on her device.](figures/alice-on-the-curve.svg) -That drawing sets a convention this book keeps for every figure that follows. **A dot sitting on the curve is an address with a private key behind it**, so someone can sign for it. The figures never label those dots, because the dot's place on the curve already says it. Later you will meet addresses drawn *off* the curve, listed with the ingredients they were derived from instead; those have no private key, and no one can sign for them directly. +That drawing sets a convention this book keeps for every figure that follows. **A dot sitting on the curve is an address with a private key behind it**, so someone can sign for it. The figures never label those dots, because the dot's place on the curve already says it. Later you will meet addresses drawn *off* the curve, listed instead with the **seeds** (the inputs) they were derived from; those have no private key, and no one can sign for them directly. > **Key pairs are random, and that is safe.** Nobody issues Alice her key pair: her device generates it from random bytes, with no registry to consult and no permission to ask. That only works because collisions are, in practice, impossible. A Solana private key is drawn from a space of 2^256 possibilities, a number on the order of the count of atoms in the observable universe. If every person on earth generated a billion key pairs per second, the expected wait before any two ever matched would still be billions of times the age of the universe. When this book says an address "belongs" to Alice, that is the entire basis of the claim: nobody else will ever roll her number. @@ -28,7 +28,7 @@ The curve in the figure is labeled **Solana Devnet**, because there is more than | **devnet** | Identical machinery for building and testing | No: tokens are free | Globally | | **localnet** | Development and fast test loops | No | Locally, on your own machine | -There is also **testnet**, where future versions of the Solana software itself are tested before a release reaches mainnet, along with other clusters stood up to try particular features. Those exist to test the network; the three in the table are where you test what you build on it. +There is also **testnet**, where future versions of the Solana software itself are tested before a release reaches mainnet, along with other clusters stood up to try particular features. Those exist to test the network itself. Your own work happens almost entirely on localnet and devnet: localnet for the fast build-and-test loop, then devnet for a rehearsal on the same machinery real users will meet. Mainnet is where a finished program goes live, not somewhere to find out whether it works. Each cluster is reached at a URL. Programs and wallets never talk to the network directly: they send transactions to an **RPC endpoint**, a server that holds the cluster's state and relays transactions into it. The endpoint URL is the one setting that decides which Solana computer your code is talking to, so moving work from localnet to devnet to mainnet is a change of configuration rather than of code. The Solana Foundation runs a public endpoint for each cluster, rate limited and meant for learning and light development; anything with real users runs against a dedicated endpoint from an RPC provider instead. Quicknode, this book's publisher, is one of several. @@ -48,7 +48,14 @@ A transaction carries one or more **instructions**, and each instruction is addr ![Programs sit on the same curve as people: the System Program and the Memo Program each have an address, and each exposes its instruction handlers.](figures/programs-on-the-curve.svg) -An instruction names which of the program's **instruction handlers** to run (the onchain functions a program exposes), the accounts the handler may touch, and the arguments. One word for two things confuses everyone, so this book always says "instruction handler" for the function and "instruction" for its input. Good financial design keeps the mapping strict: one handler, one state transition (deposit, settle, cancel, liquidate), so the program's API is the state machine's edge list. +An instruction names which of the program's **instruction handlers** to run (the onchain functions a program exposes), the accounts the handler may touch, and the arguments. Good financial design keeps the mapping strict: one handler, one state transition (deposit, settle, cancel, liquidate), so the program's API is the state machine's edge list. + +> **One word, two meanings, everywhere but here.** A lot of Solana material uses "instruction" for both things: the function a program exposes, and the input that invokes it. You will read "this program has five instructions" meaning five functions, and the interface file Anchor generates for a program lists its functions under `instructions`. One word for two things confuses everyone, so this book never does it: +> +> - An "instruction" is always the input a transaction carries: which handler to run, which accounts it may touch, and the arguments. +> - An "instruction handler" is always the onchain function that runs. +> +> Where you meet "instruction" elsewhere and it plainly means a function, read it as "instruction handler". Watch Alice run her first transaction. It carries two instructions: the first tells the System Program's `transfer()` handler to move 0.05 SOL from her account to Bob's, and the second tells the Memo Program to write "thanks" to the log. @@ -100,14 +107,14 @@ Every account holds a lamport deposit proportional to its size, called **rent**; The programs themselves are stored the same way, and it takes more than one account to do it. Deploying a program writes its compiled code into an account of its own, separate from the address you call, which is why deploying costs rent in proportion to the size of the binary; every piece of data the program manages (every market, offer, and balance) lives in its own account elsewhere again. A program is pure logic: it reads the accounts it is handed, checks them, and writes new values back, and the only way to change the state it manages is to run its instruction handlers. This split, logic in program accounts and state in data accounts, is what makes financial state legible: to know a market's condition you read its accounts, and to change it there are only the program's declared entry points. -> **A deployed program is two accounts, and briefly three.** Under Solana's upgradeable loader, the account at the program's address is a stub of 36 bytes: it is marked executable, and its data is nothing but the address of a second account. That second account, **ProgramData**, holds the compiled bytecode, the slot the program was last deployed in, and the **upgrade authority** permitted to replace it. A third account, the **buffer**, holds new bytecode while a deploy or an upgrade is being written, and is closed once the write lands so its rent comes back. Newer loaders fold this arrangement back into a single account. What stays true across all of them is that a program's code is not the thing sitting at the address you call. +> **A deployed program is two accounts, and briefly three.** Under Solana's upgradeable loader, the account at the program's address is a stub of 36 bytes: it is marked executable, and its data is nothing but the address of a second account. That second account, **ProgramData**, holds the compiled code, the slot the program was last deployed in, and the **upgrade authority** permitted to replace it. A third account, the **buffer**, holds the new code while a deploy or an upgrade is being written, and is closed once the write lands so its rent comes back. Newer loaders fold this arrangement back into a single account. What stays true across all of them is that a program's code is not the thing sitting at the address you call. ## Addresses Come in Two Kinds Every account sits at a 32-byte **address**, usually shown base58-encoded. Addresses come in two kinds: - **Public keys.** Alice's and Bob's wallet addresses are public keys: points on the curve, each with a private key that can sign for it. -- **PDAs** (program derived addresses). A PDA is computed from a program's address (its **program ID**) and some chosen **seeds**, and has no private key at all, so no signature for it can ever exist. PDAs can still own accounts, and the program that derived one can act as it by presenting the seeds; the PDAs chapter explains how, and why Solana keeps the word "sign" for something that involves no signature. The escrow program you will build in this book stores each offer at a PDA derived from the maker's address and an offer ID. +- **PDAs** (program derived addresses). A PDA is computed from a program's address (its **program ID**) and some chosen seeds, and has no private key at all, so no signature for it can ever exist. PDAs can still own accounts, and the program that derived one can act as it by presenting the seeds; the PDAs chapter explains how, and why Solana keeps the word "sign" for something that involves no signature. The escrow program you will build in this book stores each offer at a PDA derived from the maker's address and an offer ID. ![Two kinds of address: public keys sit on the curve, each with a private key that signs for it; PDAs sit off the curve, derived from a program ID and seeds, with no private key: the deriving program "signs" by presenting the seeds.](figures/address-types.svg) diff --git a/solana-building-blocks/figures/alices-transaction.svg b/solana-building-blocks/figures/alices-transaction.svg index b47b88e2..ce814bd9 100644 --- a/solana-building-blocks/figures/alices-transaction.svg +++ b/solana-building-blocks/figures/alices-transaction.svg @@ -1,5 +1,5 @@ - + Solana Devnet @@ -42,9 +42,7 @@ Instruction 2 → Memo Program memo(): write “thanks” - - - + transaction log: “thanks” diff --git a/solana-building-blocks/figures/bobs-usdc-transaction.svg b/solana-building-blocks/figures/bobs-usdc-transaction.svg index 0b0931ae..f0a188f9 100644 --- a/solana-building-blocks/figures/bobs-usdc-transaction.svg +++ b/solana-building-blocks/figures/bobs-usdc-transaction.svg @@ -1,5 +1,5 @@ - + Solana Devnet @@ -11,8 +11,8 @@ 0.95 SOL
- ATA PROGRAM + - ALICE’S ADDRESS + + ALICE’S ADDRESS + + TOKEN PROGRAM + USDC MINT @@ -30,8 +30,8 @@ 1.05 SOL - ATA PROGRAM + - BOB’S ADDRESS + + BOB’S ADDRESS + + TOKEN PROGRAM + USDC MINT @@ -54,6 +54,5 @@ Instruction 1 → Token Program transfer(): 25,000,000 to Alice’s USDC - diff --git a/solana-building-blocks/figures/mint-factory.svg b/solana-building-blocks/figures/mint-factory.svg index 471ed498..e1d6ddd9 100644 --- a/solana-building-blocks/figures/mint-factory.svg +++ b/solana-building-blocks/figures/mint-factory.svg @@ -18,8 +18,8 @@ a wallet, a multisig, or a PDA; nothing else can create tokens - ATA PROGRAM + - ALICE’S ADDRESS + + ALICE’S ADDRESS + + TOKEN PROGRAM + USDC MINT diff --git a/solana-building-blocks/figures/token-accounts-off-the-curve.svg b/solana-building-blocks/figures/token-accounts-off-the-curve.svg index 526f3f95..6449b80a 100644 --- a/solana-building-blocks/figures/token-accounts-off-the-curve.svg +++ b/solana-building-blocks/figures/token-accounts-off-the-curve.svg @@ -14,8 +14,8 @@ 0.95 SOL - ATA PROGRAM + - ALICE’S ADDRESS + + ALICE’S ADDRESS + + TOKEN PROGRAM + USDC MINT @@ -33,8 +33,8 @@ 1.05 SOL - ATA PROGRAM + - BOB’S ADDRESS + + BOB’S ADDRESS + + TOKEN PROGRAM + USDC MINT @@ -48,6 +48,6 @@ on the curve: a public key, and someone holds the private key that signs for it - off the curve: derived from the ingredients + off the curve: derived from the seeds listed beside it, and no private key exists diff --git a/solana-building-blocks/figures/transfer-through-extensions.svg b/solana-building-blocks/figures/transfer-through-extensions.svg index 037a2867..0133dd61 100644 --- a/solana-building-blocks/figures/transfer-through-extensions.svg +++ b/solana-building-blocks/figures/transfer-through-extensions.svg @@ -9,8 +9,8 @@ transfer_checked() - ATA PROGRAM + - ALICE’S ADDRESS + + ALICE’S ADDRESS + + TOKEN PROGRAM + USDC MINT @@ -20,8 +20,8 @@ 1,000 → 900 USDC - ATA PROGRAM + - BOB’S ADDRESS + + BOB’S ADDRESS + + TOKEN PROGRAM + USDC MINT diff --git a/solana-building-blocks/pdas.md b/solana-building-blocks/pdas.md index ef46011f..804b0e50 100644 --- a/solana-building-blocks/pdas.md +++ b/solana-building-blocks/pdas.md @@ -1,8 +1,10 @@ # Chapter 4: PDAs: How Solana Programs Store Data -A **program derived address** (PDA) is an address computed from a program's ID and some chosen seeds, deliberately constructed to have no private key (a **bump** byte, chosen during derivation so the result lands off the curve where keys live, and usually stored so it never needs re-finding). Only the program whose ID derived the address can "sign" as the PDA, and what that really means gets its own boxout below. +You have been using PDAs since the Tokens chapter. Every wallet's token balance in this book lives in an associated token account, and an ATA's address is nothing anybody generated: the Associated Token Program computes it from three seeds, the owner's address, the token program that manages the mint, and the mint itself. Three things follow from that, and they are the same three jobs the rest of this chapter describes. Anyone can recompute the address, so a program finds Alice's USDC without asking her where it is. A token account either derives from her address and the USDC mint or it does not, so a program handed the wrong one can reject it. And no private key exists for the result, so nothing leaves it except along the code paths of the program that owns it. -PDAs are how Solana programs store data. Taken together, a program's PDAs behave like a **key/value store**: the seeds are the key, and the account's data is the value. ("Key" here means *identifier*, the same word a database would use; it has nothing to do with public or private keys.) Every program in this book keeps its state this way: +Generalize that and you have the mechanism. A **program derived address** (PDA) is an address computed from a program's ID and some chosen seeds (the inputs to the derivation), deliberately constructed to have no private key (a **bump** byte, chosen during derivation so the result lands off the curve where keys live, and usually stored so it never needs re-finding). Only the program whose ID derived the address can "sign" as the PDA, and what that really means gets its own boxout below. + +The Associated Token Program made one choice of seeds for one job: where a wallet's balance of a mint lives. Every other program picks seeds for its own records and gets the same three properties, which is why **PDAs are how Solana programs store data**. Taken together, a program's PDAs behave like a **key/value store**: the seeds are the key, and the account's data is the value. ("Key" here means *identifier*, the same word a database would use; it has nothing to do with public or private keys.) Every program in this book keeps its state this way: - The escrow stores each offer at `"offer"` + the maker's address + an ID. - The fundraiser stores its terms at `"fundraiser"` + the maker's address, and one receipt per backer at `"contributor"` + the fundraiser + the backer's address. @@ -10,7 +12,7 @@ PDAs are how Solana programs store data. Taken together, a program's PDAs behave - The lending market stores each borrower's loan file at `"obligation"` + the market + the borrower's address. - The perpetual futures venue stores each position at `"position"` + the pool + the owner + the side. -That one mechanism does three jobs in every chapter of this book: +Each of those three jobs again, in the general form every chapter of this book uses: ## Seeds Make Data Findable @@ -31,5 +33,6 @@ A vault whose authority is a PDA is a vault whose funds move only along the code ## What You Learnt - A **PDA** is derived from a program's ID and seeds and has no private key; only the deriving program can "sign" as it, by presenting the seeds. +- The associated token accounts you already use are PDAs of the Associated Token Program, and a program's own records work the same way with seeds it chooses. - A program's PDAs form a **key/value store**: the seeds are the identifier, the account's data is the value. - Seeds make data findable and relationships verifiable, and PDA authority over a vault is what "the program holds the funds" means. diff --git a/solana-building-blocks/tokens.md b/solana-building-blocks/tokens.md index 3fb4c9ac..f9c42e7a 100644 --- a/solana-building-blocks/tokens.md +++ b/solana-building-blocks/tokens.md @@ -26,7 +26,7 @@ Not every token account is an ATA. A token account can live at any address, and ![Alice's and Bob's USDC stores are PDAs: derived from owner and mint, they sit off the curve, with no private key.](figures/token-accounts-off-the-curve.svg) -This is the other half of the figure convention. An address drawn **off** the curve is captioned with the ingredients it was derived from (for an ATA: the token program, the owner's address and the mint), and that caption is the whole story. No private key exists for it, so only the owning program can move what it holds. +This is the other half of the figure convention. An address drawn **off** the curve is captioned with the seeds it was derived from (for an ATA: the owner's address, the token program, and the mint), and that caption is the whole story. No private key exists for it, so only the owning program can move what it holds. ## Decimals Define the Minor Unit