{
  "address": "Fv7op93iVyTGKVeD3bQvh3dsi6u5g8ULyvvg48dRqWqe",
  "metadata": {
    "name": "rwa_market",
    "version": "0.1.0",
    "spec": "0.1.0",
    "description": "RWA market program (asset listing, pricing, purchase limits)"
  },
  "instructions": [
    {
      "name": "admin_set_price",
      "docs": [
        "Set price WITHOUT the deviation cap, for legitimate large revaluations.",
        "Admin (multisig) only. Mirrors EVM adminSetPrice()."
      ],
      "discriminator": [
        96,
        189,
        55,
        97,
        154,
        239,
        114,
        180
      ],
      "accounts": [
        {
          "name": "admin",
          "signer": true
        },
        {
          "name": "asset",
          "writable": true,
          "pda": {
            "seeds": [
              {
                "kind": "const",
                "value": [
                  97,
                  115,
                  115,
                  101,
                  116
                ]
              },
              {
                "kind": "account",
                "path": "asset.mint",
                "account": "Asset"
              }
            ]
          }
        }
      ],
      "args": [
        {
          "name": "price_cents",
          "type": "u64"
        }
      ]
    },
    {
      "name": "buy",
      "docs": [
        "Buy `amount` RWA tokens for the listed price + fee. Trader signs and pays; RWA moves",
        "from the vault to the trader through `rwa_vault::market_draw_stock`, so the vault can",
        "refuse to release stock a maker is already owed. `max_cost` guards slippage.",
        "Mirrors Market.buy(token, amount, maxCost)."
      ],
      "discriminator": [
        102,
        6,
        61,
        18,
        1,
        218,
        235,
        234
      ],
      "accounts": [
        {
          "name": "trader",
          "writable": true,
          "signer": true
        },
        {
          "name": "venue_link",
          "docs": [
            "Seed-constrained, so it is this program's own account or the instruction fails. Read raw",
            "because it is legitimately absent before governance calls `set_venue_program`, and a",
            "typed Account cannot express \"may not exist yet\"."
          ],
          "pda": {
            "seeds": [
              {
                "kind": "const",
                "value": [
                  118,
                  101,
                  110,
                  117,
                  101,
                  95,
                  112,
                  114,
                  111,
                  103,
                  114,
                  97,
                  109
                ]
              }
            ]
          }
        },
        {
          "name": "maker_state",
          "docs": [
            "",
            "MANDATORY, not optional. The caller of `sell` may BE the market maker, so an account it",
            "can omit is a check it can skip — Anchor's Option would have made the restriction",
            "voluntary for exactly the party it restricts. An address with no maker record points at",
            "an empty account, which reads as \"not a maker\".",
            "",
            "Its address cannot be constrained here, because the program it belongs to is only known",
            "once venue_link has been read; `sell` derives the expected address and compares. Read raw",
            "rather than typed to keep rwa_market's dependency on rwa_venue to the program id alone:",
            "the layout is the first byte after two pubkeys, MakerState.active."
          ]
        },
        {
          "name": "maker_approval",
          "docs": [
            "",
            "Mandatory and address-checked for the same reasons as `maker_state`, and present for the",
            "same reason `rwa_venue::gates` reads both: \"is a maker\" means approved globally and",
            "active on this asset. Reading only one of the two would block a firm the venue itself",
            "has already stood down, leaving it with stock and no exit (L-3)."
          ]
        },
        {
          "name": "asset",
          "docs": [
            "BOXED. Anchor deserialises accounts onto the stack in try_accounts, and this context was",
            "already within 64 bytes of the 4096-byte SBF frame limit — appending the accrual fields to",
            "Asset tipped it over, which shows up at runtime as \"Access violation ... at address 0x0\",",
            "not as a compile error. Boxing moves the struct to the heap and takes ~290 bytes out of",
            "the frame, which also leaves room for the next field someone appends."
          ],
          "pda": {
            "seeds": [
              {
                "kind": "const",
                "value": [
                  97,
                  115,
                  115,
                  101,
                  116
                ]
              },
              {
                "kind": "account",
                "path": "asset.mint",
                "account": "Asset"
              }
            ]
          }
        },
        {
          "name": "rwa_mint"
        },
        {
          "name": "payout_mint"
        },
        {
          "name": "vault",
          "docs": [
            "The vault account itself. `buy` now draws inventory through rwa_vault rather than",
            "moving it as an SPL delegate, so the vault can refuse to release stock a maker is owed.",
            "Previously the market transferred directly and rwa_vault never ran — which is precisely",
            "how a retail buy could sell stock already delivered into a redemption queue.",
            "READ-ONLY. `market_draw_stock` / `market_draw_payment` only READ this account — they",
            "check `free_stock` / `free_payment` and sign the transfer as the vault PDA — so there is",
            "nothing here to mutate (R3-7h). It also must not be `mut`: Anchor writes a typed `mut`",
            "account's write lock, and a `mut` it does not need is a write lock it does not need."
          ]
        },
        {
          "name": "vault_program",
          "address": "A5SYMN5DGKWo8oPwDgWUxCZHZA7TaCkLbguSrLiWc91"
        },
        {
          "name": "vault_rwa_ata",
          "writable": true
        },
        {
          "name": "vault_payout_ata",
          "writable": true
        },
        {
          "name": "meta",
          "docs": [
            "Token metadata PDA owned by rwa_token, read only for `maturity_date`. Trading must",
            "stop at maturity: past it the only exit is redemption (which pays gross, no fee),",
            "and buying would hand someone a token that can never accrue or be redeemed."
          ],
          "pda": {
            "seeds": [
              {
                "kind": "const",
                "value": [
                  109,
                  101,
                  116,
                  97
                ]
              },
              {
                "kind": "account",
                "path": "asset.mint",
                "account": "Asset"
              }
            ],
            "program": {
              "kind": "const",
              "value": [
                113,
                171,
                40,
                163,
                170,
                246,
                110,
                27,
                186,
                10,
                11,
                75,
                97,
                138,
                197,
                88,
                115,
                241,
                208,
                65,
                38,
                70,
                73,
                244,
                126,
                46,
                230,
                163,
                54,
                150,
                249,
                158
              ]
            }
          }
        },
        {
          "name": "trader_rwa_ata",
          "writable": true
        },
        {
          "name": "trader_payout_ata",
          "writable": true
        },
        {
          "name": "rwa_token_program"
        },
        {
          "name": "payout_token_program"
        }
      ],
      "args": [
        {
          "name": "amount",
          "type": "u64"
        },
        {
          "name": "max_cost",
          "type": "u64"
        }
      ]
    },
    {
      "name": "init_config",
      "docs": [
        "Bind this program to its governing authority. Callable ONCE, and only by the program's",
        "current upgrade authority.",
        "",
        "Gating on the upgrade authority is what makes the bootstrap safe: at deploy time that is",
        "the deploy wallet, and after the Squads handoff it is the multisig vault. The only party",
        "who can claim a program's Config is therefore the same party who can already replace its",
        "bytecode. A permissionless `init_config` would simply move the first-caller-wins hole",
        "from the per-asset PDAs up to the global one."
      ],
      "discriminator": [
        23,
        235,
        115,
        232,
        168,
        96,
        1,
        231
      ],
      "accounts": [
        {
          "name": "payer",
          "writable": true,
          "signer": true
        },
        {
          "name": "upgrade_authority",
          "docs": [
            "Must be this program's current upgrade authority."
          ],
          "signer": true
        },
        {
          "name": "program_data"
        },
        {
          "name": "config",
          "writable": true,
          "pda": {
            "seeds": [
              {
                "kind": "const",
                "value": [
                  99,
                  111,
                  110,
                  102,
                  105,
                  103
                ]
              }
            ]
          }
        },
        {
          "name": "system_program",
          "address": "11111111111111111111111111111111"
        }
      ],
      "args": [
        {
          "name": "authority",
          "type": "pubkey"
        },
        {
          "name": "operator",
          "type": "pubkey"
        }
      ]
    },
    {
      "name": "quote",
      "docs": [
        "What will this trade actually cost? Returns payout-token units.",
        "",
        "Solana has no view functions: a client fetches an account and deserialises it locally, so",
        "nothing reads `asset.price_cents` through program code. That field is the accrual",
        "CHECKPOINT, and once a rate is live it lags by every day since the last write — on a",
        "fixed-rate note, which is meant to need zero price writes for its whole life, that drift",
        "grows for years. A client quoting from it sets `max_cost` too low and the buy reverts",
        "with SlippageExceeded; a sell silently under-quotes and succeeds.",
        "",
        "The alternative was reimplementing the accrual formula in JS, which would leave two",
        "copies of arithmetic that must agree forever — the same shape as the payout baseline",
        "files. This is the Solana-native answer instead: call it through simulateTransaction and",
        "read the return data. Simulation is free, needs no signature and costs no fee.",
        "",
        "Deliberately returns the SAME quantity buy/sell compute, not a price, so the caller does",
        "no arithmetic at all. `quote_matches_a_real_buy` in the test suite asserts it equals what",
        "the chain actually charges."
      ],
      "discriminator": [
        149,
        42,
        109,
        247,
        134,
        146,
        213,
        123
      ],
      "accounts": [
        {
          "name": "asset",
          "pda": {
            "seeds": [
              {
                "kind": "const",
                "value": [
                  97,
                  115,
                  115,
                  101,
                  116
                ]
              },
              {
                "kind": "account",
                "path": "asset.mint",
                "account": "Asset"
              }
            ]
          }
        },
        {
          "name": "rwa_mint"
        },
        {
          "name": "payout_mint"
        }
      ],
      "args": [
        {
          "name": "amount",
          "type": "u64"
        },
        {
          "name": "is_buy",
          "type": "bool"
        }
      ],
      "returns": "u64"
    },
    {
      "name": "register_asset",
      "docs": [
        "List an asset for trading. Admin (multisig) only. Mirrors registerAsset()."
      ],
      "discriminator": [
        21,
        80,
        155,
        149,
        117,
        207,
        235,
        16
      ],
      "accounts": [
        {
          "name": "config",
          "pda": {
            "seeds": [
              {
                "kind": "const",
                "value": [
                  99,
                  111,
                  110,
                  102,
                  105,
                  103
                ]
              }
            ]
          }
        },
        {
          "name": "meta",
          "docs": [
            "Read-only: the asset's TokenMeta, so payout_mint is checked rather than trusted (L-3)."
          ],
          "pda": {
            "seeds": [
              {
                "kind": "const",
                "value": [
                  109,
                  101,
                  116,
                  97
                ]
              },
              {
                "kind": "account",
                "path": "mint"
              }
            ],
            "program": {
              "kind": "const",
              "value": [
                113,
                171,
                40,
                163,
                170,
                246,
                110,
                27,
                186,
                10,
                11,
                75,
                97,
                138,
                197,
                88,
                115,
                241,
                208,
                65,
                38,
                70,
                73,
                244,
                126,
                46,
                230,
                163,
                54,
                150,
                249,
                158
              ]
            }
          }
        },
        {
          "name": "admin",
          "docs": [
            "C-2: this was an unconstrained Signer, so whoever initialised [\"asset\", mint] first",
            "became both admin and price_updater of the PDA the vault later delegates to."
          ],
          "signer": true
        },
        {
          "name": "mint"
        },
        {
          "name": "asset",
          "writable": true,
          "pda": {
            "seeds": [
              {
                "kind": "const",
                "value": [
                  97,
                  115,
                  115,
                  101,
                  116
                ]
              },
              {
                "kind": "account",
                "path": "mint"
              }
            ]
          }
        },
        {
          "name": "payer",
          "writable": true,
          "signer": true
        },
        {
          "name": "system_program",
          "address": "11111111111111111111111111111111"
        }
      ],
      "args": [
        {
          "name": "vault",
          "type": "pubkey"
        },
        {
          "name": "payout_mint",
          "type": "pubkey"
        },
        {
          "name": "price_cents",
          "type": "u64"
        },
        {
          "name": "fee_bps",
          "type": "u16"
        },
        {
          "name": "price_updater",
          "type": "pubkey"
        }
      ]
    },
    {
      "name": "sell",
      "docs": [
        "Sell `amount` RWA tokens for the listed price minus fee. Trader signs and delivers RWA",
        "to the vault; payout moves from the vault to the trader through",
        "`rwa_vault::market_draw_payment`, so a retail sell cannot spend cash a deferred",
        "redemption already owes a maker. `min_proceeds` guards slippage.",
        "Mirrors Market.sell(token, amount, minPayout)."
      ],
      "discriminator": [
        51,
        230,
        133,
        164,
        1,
        127,
        131,
        173
      ],
      "accounts": [
        {
          "name": "trader",
          "writable": true,
          "signer": true
        },
        {
          "name": "venue_link",
          "docs": [
            "Seed-constrained, so it is this program's own account or the instruction fails. Read raw",
            "because it is legitimately absent before governance calls `set_venue_program`, and a",
            "typed Account cannot express \"may not exist yet\"."
          ],
          "pda": {
            "seeds": [
              {
                "kind": "const",
                "value": [
                  118,
                  101,
                  110,
                  117,
                  101,
                  95,
                  112,
                  114,
                  111,
                  103,
                  114,
                  97,
                  109
                ]
              }
            ]
          }
        },
        {
          "name": "maker_state",
          "docs": [
            "",
            "MANDATORY, not optional. The caller of `sell` may BE the market maker, so an account it",
            "can omit is a check it can skip — Anchor's Option would have made the restriction",
            "voluntary for exactly the party it restricts. An address with no maker record points at",
            "an empty account, which reads as \"not a maker\".",
            "",
            "Its address cannot be constrained here, because the program it belongs to is only known",
            "once venue_link has been read; `sell` derives the expected address and compares. Read raw",
            "rather than typed to keep rwa_market's dependency on rwa_venue to the program id alone:",
            "the layout is the first byte after two pubkeys, MakerState.active."
          ]
        },
        {
          "name": "maker_approval",
          "docs": [
            "",
            "Mandatory and address-checked for the same reasons as `maker_state`, and present for the",
            "same reason `rwa_venue::gates` reads both: \"is a maker\" means approved globally and",
            "active on this asset. Reading only one of the two would block a firm the venue itself",
            "has already stood down, leaving it with stock and no exit (L-3)."
          ]
        },
        {
          "name": "asset",
          "docs": [
            "BOXED. Anchor deserialises accounts onto the stack in try_accounts, and this context was",
            "already within 64 bytes of the 4096-byte SBF frame limit — appending the accrual fields to",
            "Asset tipped it over, which shows up at runtime as \"Access violation ... at address 0x0\",",
            "not as a compile error. Boxing moves the struct to the heap and takes ~290 bytes out of",
            "the frame, which also leaves room for the next field someone appends."
          ],
          "pda": {
            "seeds": [
              {
                "kind": "const",
                "value": [
                  97,
                  115,
                  115,
                  101,
                  116
                ]
              },
              {
                "kind": "account",
                "path": "asset.mint",
                "account": "Asset"
              }
            ]
          }
        },
        {
          "name": "rwa_mint"
        },
        {
          "name": "payout_mint"
        },
        {
          "name": "vault",
          "docs": [
            "The vault account itself. `buy` now draws inventory through rwa_vault rather than",
            "moving it as an SPL delegate, so the vault can refuse to release stock a maker is owed.",
            "Previously the market transferred directly and rwa_vault never ran — which is precisely",
            "how a retail buy could sell stock already delivered into a redemption queue.",
            "READ-ONLY. `market_draw_stock` / `market_draw_payment` only READ this account — they",
            "check `free_stock` / `free_payment` and sign the transfer as the vault PDA — so there is",
            "nothing here to mutate (R3-7h). It also must not be `mut`: Anchor writes a typed `mut`",
            "account's write lock, and a `mut` it does not need is a write lock it does not need."
          ]
        },
        {
          "name": "vault_program",
          "address": "A5SYMN5DGKWo8oPwDgWUxCZHZA7TaCkLbguSrLiWc91"
        },
        {
          "name": "vault_rwa_ata",
          "writable": true
        },
        {
          "name": "vault_payout_ata",
          "writable": true
        },
        {
          "name": "meta",
          "docs": [
            "Token metadata PDA owned by rwa_token, read only for `maturity_date`. Trading must",
            "stop at maturity: past it the only exit is redemption (which pays gross, no fee),",
            "and buying would hand someone a token that can never accrue or be redeemed."
          ],
          "pda": {
            "seeds": [
              {
                "kind": "const",
                "value": [
                  109,
                  101,
                  116,
                  97
                ]
              },
              {
                "kind": "account",
                "path": "asset.mint",
                "account": "Asset"
              }
            ],
            "program": {
              "kind": "const",
              "value": [
                113,
                171,
                40,
                163,
                170,
                246,
                110,
                27,
                186,
                10,
                11,
                75,
                97,
                138,
                197,
                88,
                115,
                241,
                208,
                65,
                38,
                70,
                73,
                244,
                126,
                46,
                230,
                163,
                54,
                150,
                249,
                158
              ]
            }
          }
        },
        {
          "name": "trader_rwa_ata",
          "writable": true
        },
        {
          "name": "trader_payout_ata",
          "writable": true
        },
        {
          "name": "rwa_token_program"
        },
        {
          "name": "payout_token_program"
        }
      ],
      "args": [
        {
          "name": "amount",
          "type": "u64"
        },
        {
          "name": "min_proceeds",
          "type": "u64"
        }
      ]
    },
    {
      "name": "set_active",
      "docs": [
        "Halt or resume trading on an asset. The stop button H-3 called for.",
        "",
        "Asymmetric by design: **pausing** is allowed by the operator as well as the admin,",
        "**resuming** requires the admin. A stop button that needs a 2-of-3 Squads proposal is",
        "not a stop button — the proposal cycle is exactly the window a drain runs in. The",
        "asymmetry keeps that useful without making it dangerous: the worst a stolen operator",
        "key can do here is halt trading, which is loud, reversible by governance, and strictly",
        "less bad than what it could do with the time a proposal would take.",
        "",
        "`buy`/`sell` already gate on `is_active`; until now nothing could ever clear it.",
        "Redemption is deliberately unaffected — `rwa_exit::claim` does not consult this flag, so",
        "pausing the market never traps holders in a matured asset."
      ],
      "discriminator": [
        29,
        16,
        225,
        132,
        38,
        216,
        206,
        33
      ],
      "accounts": [
        {
          "name": "signer",
          "signer": true
        },
        {
          "name": "config",
          "pda": {
            "seeds": [
              {
                "kind": "const",
                "value": [
                  99,
                  111,
                  110,
                  102,
                  105,
                  103
                ]
              }
            ]
          }
        },
        {
          "name": "asset",
          "writable": true,
          "pda": {
            "seeds": [
              {
                "kind": "const",
                "value": [
                  97,
                  115,
                  115,
                  101,
                  116
                ]
              },
              {
                "kind": "account",
                "path": "asset.mint",
                "account": "Asset"
              }
            ]
          }
        }
      ],
      "args": [
        {
          "name": "active",
          "type": "bool"
        }
      ]
    },
    {
      "name": "set_config_authority",
      "docs": [
        "Rotate the governing authority (migrating to a new Squads multisig) and/or the operator",
        "hot key. Current authority only.",
        "",
        "Every per-asset account created BEFORE a rotation keeps the old admin recorded in its own",
        "state — this only changes who may create new ones. Rotating existing assets is a separate",
        "concern (see M-1 in SECURITY-AUDIT.md)."
      ],
      "discriminator": [
        16,
        200,
        212,
        18,
        95,
        43,
        107,
        89
      ],
      "accounts": [
        {
          "name": "authority",
          "signer": true
        },
        {
          "name": "config",
          "writable": true,
          "pda": {
            "seeds": [
              {
                "kind": "const",
                "value": [
                  99,
                  111,
                  110,
                  102,
                  105,
                  103
                ]
              }
            ]
          }
        }
      ],
      "args": [
        {
          "name": "authority",
          "type": "pubkey"
        },
        {
          "name": "operator",
          "type": "pubkey"
        }
      ]
    },
    {
      "name": "set_fee_bps",
      "docs": [
        "Update the trading fee. Admin (multisig) only.",
        "",
        "M-6: `fee_bps` was write-once at listing, so an out-of-range value could not be",
        "corrected — and one above 10_000 permanently breaks `sell`."
      ],
      "discriminator": [
        2,
        161,
        245,
        141,
        111,
        32,
        39,
        198
      ],
      "accounts": [
        {
          "name": "admin",
          "signer": true
        },
        {
          "name": "asset",
          "writable": true,
          "pda": {
            "seeds": [
              {
                "kind": "const",
                "value": [
                  97,
                  115,
                  115,
                  101,
                  116
                ]
              },
              {
                "kind": "account",
                "path": "asset.mint",
                "account": "Asset"
              }
            ]
          }
        }
      ],
      "args": [
        {
          "name": "fee_bps",
          "type": "u16"
        }
      ]
    },
    {
      "name": "set_max_per_tx",
      "discriminator": [
        115,
        25,
        27,
        109,
        45,
        96,
        136,
        65
      ],
      "accounts": [
        {
          "name": "admin",
          "signer": true
        },
        {
          "name": "asset",
          "writable": true,
          "pda": {
            "seeds": [
              {
                "kind": "const",
                "value": [
                  97,
                  115,
                  115,
                  101,
                  116
                ]
              },
              {
                "kind": "account",
                "path": "asset.mint",
                "account": "Asset"
              }
            ]
          }
        }
      ],
      "args": [
        {
          "name": "limit",
          "type": "u64"
        }
      ]
    },
    {
      "name": "set_max_per_user",
      "discriminator": [
        204,
        53,
        206,
        72,
        102,
        250,
        129,
        65
      ],
      "accounts": [
        {
          "name": "admin",
          "signer": true
        },
        {
          "name": "asset",
          "writable": true,
          "pda": {
            "seeds": [
              {
                "kind": "const",
                "value": [
                  97,
                  115,
                  115,
                  101,
                  116
                ]
              },
              {
                "kind": "account",
                "path": "asset.mint",
                "account": "Asset"
              }
            ]
          }
        }
      ],
      "args": [
        {
          "name": "limit",
          "type": "u64"
        }
      ]
    },
    {
      "name": "set_price",
      "docs": [
        "Update price. price_updater only. Mirrors setPrice().",
        "",
        "THREE guards, because the per-update cap alone does nothing (SECURITY-AUDIT H-2):",
        "",
        "1. per-update cap  — MAX_PRICE_DEVIATION_BPS from the previous price.",
        "2. minimum interval — a cap with no cooldown is not a cap. Solana packs many",
        "instructions into one transaction, so ~50 chained calls at 10% compound to ~117x",
        "inside a SINGLE transaction, with nothing in between to react to. This is what made",
        "the original guard cosmetic.",
        "3. rolling-window cap — total movement away from the window's opening price, so an",
        "attacker who simply waits out the interval still cannot walk the price far.",
        "",
        "Legitimate large revaluations go through `admin_set_price`, which the multisig controls",
        "and which resets the window."
      ],
      "discriminator": [
        16,
        19,
        182,
        8,
        149,
        83,
        72,
        181
      ],
      "accounts": [
        {
          "name": "signer",
          "signer": true
        },
        {
          "name": "asset",
          "writable": true,
          "pda": {
            "seeds": [
              {
                "kind": "const",
                "value": [
                  97,
                  115,
                  115,
                  101,
                  116
                ]
              },
              {
                "kind": "account",
                "path": "asset.mint",
                "account": "Asset"
              }
            ]
          }
        }
      ],
      "args": [
        {
          "name": "price_cents",
          "type": "u64"
        }
      ]
    },
    {
      "name": "set_price_limits",
      "docs": [
        "Tune the rate limits. Admin (multisig) only. 0 on either field restores the default.",
        "",
        "Mirrors EVM's setMaxPriceDeviation: the limits are policy, and an asset whose price",
        "genuinely moves faster than the defaults should be able to say so without a code change."
      ],
      "discriminator": [
        87,
        73,
        210,
        101,
        223,
        166,
        11,
        250
      ],
      "accounts": [
        {
          "name": "admin",
          "signer": true
        },
        {
          "name": "asset",
          "writable": true,
          "pda": {
            "seeds": [
              {
                "kind": "const",
                "value": [
                  97,
                  115,
                  115,
                  101,
                  116
                ]
              },
              {
                "kind": "account",
                "path": "asset.mint",
                "account": "Asset"
              }
            ]
          }
        }
      ],
      "args": [
        {
          "name": "min_interval_secs",
          "type": "u32"
        },
        {
          "name": "max_window_deviation_bps",
          "type": "u16"
        }
      ]
    },
    {
      "name": "set_price_updater",
      "docs": [
        "Replace the price_updater key. Admin (multisig) only.",
        "",
        "SECURITY-AUDIT M-1, and the part of it that bites hardest: `price_updater` was written",
        "once at listing and never again. A leaked price-updater key — the exact threat H-2's",
        "rate limit exists to bound — could therefore never be REVOKED. The only response was",
        "`set_active(false)`, which halts trading for everyone rather than removing the attacker.",
        "This rotates it and leaves the market open."
      ],
      "discriminator": [
        252,
        252,
        11,
        157,
        146,
        253,
        124,
        49
      ],
      "accounts": [
        {
          "name": "admin",
          "signer": true
        },
        {
          "name": "asset",
          "writable": true,
          "pda": {
            "seeds": [
              {
                "kind": "const",
                "value": [
                  97,
                  115,
                  115,
                  101,
                  116
                ]
              },
              {
                "kind": "account",
                "path": "asset.mint",
                "account": "Asset"
              }
            ]
          }
        }
      ],
      "args": [
        {
          "name": "price_updater",
          "type": "pubkey"
        }
      ]
    },
    {
      "name": "set_rate",
      "docs": [
        "Set an asset's accrual rate. Admin (the multisig) only.",
        "",
        "Settles at the OLD rate first, so accrual before the change is neither lost nor",
        "re-applied. Deliberately NOT available to `price_updater`: a price is a jump the H-2",
        "guards bound, a rate is a SLOPE whose damage accumulates invisibly, so it gets its own",
        "caps (MAX_RATE_BPS / MAX_RATE_DELTA_BPS / MIN_RATE_UPDATE_SECS) and a higher bar to call.",
        "",
        "No `effective_from`: a reset applies from now, and the lag between the real-world reset",
        "and this transaction is carried as a treasury cost rather than as a back-dating lever."
      ],
      "discriminator": [
        99,
        58,
        170,
        238,
        160,
        120,
        74,
        11
      ],
      "accounts": [
        {
          "name": "admin",
          "signer": true
        },
        {
          "name": "asset",
          "writable": true,
          "pda": {
            "seeds": [
              {
                "kind": "const",
                "value": [
                  97,
                  115,
                  115,
                  101,
                  116
                ]
              },
              {
                "kind": "account",
                "path": "asset.mint",
                "account": "Asset"
              }
            ]
          }
        },
        {
          "name": "meta",
          "pda": {
            "seeds": [
              {
                "kind": "const",
                "value": [
                  109,
                  101,
                  116,
                  97
                ]
              },
              {
                "kind": "account",
                "path": "asset.mint",
                "account": "Asset"
              }
            ],
            "program": {
              "kind": "const",
              "value": [
                113,
                171,
                40,
                163,
                170,
                246,
                110,
                27,
                186,
                10,
                11,
                75,
                97,
                138,
                197,
                88,
                115,
                241,
                208,
                65,
                38,
                70,
                73,
                244,
                126,
                46,
                230,
                163,
                54,
                150,
                249,
                158
              ]
            }
          }
        }
      ],
      "args": [
        {
          "name": "rate_bps",
          "type": "i32"
        }
      ]
    },
    {
      "name": "set_venue_program",
      "docs": [
        "Point this program at the rwa_venue deployment whose maker records `sell` consults.",
        "Authority (multisig) only.",
        "",
        "This lives in its own PDA rather than as a field on Config deliberately. Config is",
        "already live on mainnet at its original size, and widening it would make every live",
        "account fail to deserialise from the moment the new code is deployed until a multisig",
        "signs the migration — an outage on a running market, measured in however long signers",
        "take. A separate account has no such window: deploy, then link whenever.",
        "",
        "Until it is set, `sell` has no venue to check against and performs no maker check, which",
        "is the correct reading of \"no venue is configured\" — no maker can exist either.",
        "Name the program whose maker records `sell` consults, and `rwa_exit` resolves venues",
        "through. See docs/VENUE-SPEC.md §11 link 4.",
        "",
        "`force` is required to CHANGE a link that is already set (R4-5a). Re-pointing a live",
        "link is not routine configuration: between the old program ceasing to be consulted and",
        "the new one being wired, every maker at the old venue reads as \"not a maker\" here and",
        "may sell on the Market — the bypass I16 exists to prevent — while the old venue's",
        "queued redemptions are still live and `rwa_exit` stops seeing them. The precondition the",
        "caller asserts by passing `force` is that EVERY venue of the outgoing program is flat.",
        "That cannot be checked here (there is no enumeration), so it is documented, not guarded."
      ],
      "discriminator": [
        12,
        170,
        92,
        95,
        47,
        174,
        224,
        151
      ],
      "accounts": [
        {
          "name": "payer",
          "writable": true,
          "signer": true
        },
        {
          "name": "authority",
          "signer": true
        },
        {
          "name": "config",
          "pda": {
            "seeds": [
              {
                "kind": "const",
                "value": [
                  99,
                  111,
                  110,
                  102,
                  105,
                  103
                ]
              }
            ]
          }
        },
        {
          "name": "venue_link",
          "writable": true,
          "pda": {
            "seeds": [
              {
                "kind": "const",
                "value": [
                  118,
                  101,
                  110,
                  117,
                  101,
                  95,
                  112,
                  114,
                  111,
                  103,
                  114,
                  97,
                  109
                ]
              }
            ]
          }
        },
        {
          "name": "system_program",
          "address": "11111111111111111111111111111111"
        }
      ],
      "args": [
        {
          "name": "venue_program",
          "type": "pubkey"
        },
        {
          "name": "force",
          "type": "bool"
        }
      ]
    },
    {
      "name": "sync_admin",
      "docs": [
        "Re-point this account's `admin` at the CURRENT `config.authority`.",
        "",
        "SECURITY-AUDIT M-1: every per-asset admin was written once at creation and never again,",
        "so rotating the Squad left every existing asset pointing at the old multisig — and the",
        "more assets exist, the worse that gets. `set_config_authority` moves the program-level",
        "authority; this carries the change down to accounts that already exist.",
        "",
        "The new value is READ FROM CONFIG, never supplied by the caller, so this can only ever",
        "converge an account onto the current authority. It cannot redirect one anywhere else,",
        "which is why gating it on the operator is enough — there is no value here to abuse."
      ],
      "discriminator": [
        39,
        26,
        200,
        133,
        62,
        114,
        42,
        235
      ],
      "accounts": [
        {
          "name": "config",
          "pda": {
            "seeds": [
              {
                "kind": "const",
                "value": [
                  99,
                  111,
                  110,
                  102,
                  105,
                  103
                ]
              }
            ]
          }
        },
        {
          "name": "signer",
          "signer": true
        },
        {
          "name": "asset",
          "writable": true,
          "pda": {
            "seeds": [
              {
                "kind": "const",
                "value": [
                  97,
                  115,
                  115,
                  101,
                  116
                ]
              },
              {
                "kind": "account",
                "path": "asset.mint",
                "account": "Asset"
              }
            ]
          }
        }
      ],
      "args": []
    }
  ],
  "accounts": [
    {
      "name": "Asset",
      "discriminator": [
        234,
        180,
        241,
        252,
        139,
        224,
        160,
        8
      ]
    },
    {
      "name": "Config",
      "discriminator": [
        155,
        12,
        170,
        224,
        30,
        250,
        204,
        130
      ]
    },
    {
      "name": "TokenMeta",
      "discriminator": [
        130,
        87,
        174,
        35,
        21,
        44,
        92,
        19
      ]
    },
    {
      "name": "Vault",
      "discriminator": [
        211,
        8,
        232,
        43,
        2,
        152,
        117,
        119
      ]
    },
    {
      "name": "VenueLink",
      "discriminator": [
        197,
        70,
        34,
        152,
        183,
        67,
        68,
        38
      ]
    }
  ],
  "events": [
    {
      "name": "ActiveSet",
      "discriminator": [
        204,
        68,
        242,
        65,
        134,
        117,
        207,
        64
      ]
    },
    {
      "name": "AdminSynced",
      "discriminator": [
        31,
        149,
        231,
        47,
        182,
        14,
        227,
        231
      ]
    },
    {
      "name": "ConfigSet",
      "discriminator": [
        15,
        104,
        59,
        16,
        236,
        241,
        8,
        6
      ]
    },
    {
      "name": "PriceUpdaterRotated",
      "discriminator": [
        234,
        54,
        80,
        25,
        132,
        106,
        139,
        178
      ]
    },
    {
      "name": "RateUpdated",
      "discriminator": [
        215,
        212,
        25,
        235,
        152,
        203,
        38,
        205
      ]
    },
    {
      "name": "Traded",
      "discriminator": [
        225,
        202,
        73,
        175,
        147,
        43,
        160,
        150
      ]
    },
    {
      "name": "VenueProgramSet",
      "discriminator": [
        249,
        72,
        110,
        62,
        209,
        84,
        28,
        85
      ]
    }
  ],
  "errors": [
    {
      "code": 6000,
      "name": "Unauthorized",
      "msg": "caller is not authorized for this action"
    },
    {
      "code": 6001,
      "name": "InactiveAsset",
      "msg": "asset is not active for trading"
    },
    {
      "code": 6002,
      "name": "LimitExceeded",
      "msg": "amount exceeds the per-transaction limit"
    },
    {
      "code": 6003,
      "name": "UserLimitExceeded",
      "msg": "resulting holdings exceed the per-user limit"
    },
    {
      "code": 6004,
      "name": "AssetMatured",
      "msg": "asset has matured; redeem instead of trading"
    },
    {
      "code": 6005,
      "name": "InvalidPrice",
      "msg": "price must be greater than zero"
    },
    {
      "code": 6006,
      "name": "PriceDeviationTooHigh",
      "msg": "price moved further than the per-update deviation cap"
    },
    {
      "code": 6007,
      "name": "PriceUpdateTooSoon",
      "msg": "price updated again before the minimum interval elapsed"
    },
    {
      "code": 6008,
      "name": "PriceWindowExceeded",
      "msg": "price moved further than the rolling-window cap; use admin_set_price"
    },
    {
      "code": 6009,
      "name": "InvalidPriceLimit",
      "msg": "price limit is out of range"
    },
    {
      "code": 6010,
      "name": "InvalidFeeBps",
      "msg": "fee_bps must be 0-10000"
    },
    {
      "code": 6011,
      "name": "WrongVault",
      "msg": "vault is not the canonical vault PDA for this mint"
    },
    {
      "code": 6012,
      "name": "WrongPayoutMint",
      "msg": "payout mint is not the asset's payout token"
    },
    {
      "code": 6013,
      "name": "SlippageExceeded",
      "msg": "price moved beyond the slippage bound"
    },
    {
      "code": 6014,
      "name": "MathOverflow",
      "msg": "arithmetic overflow"
    },
    {
      "code": 6015,
      "name": "RateWithoutCheckpoint",
      "msg": "rate is set but the asset has no accrual checkpoint"
    },
    {
      "code": 6016,
      "name": "RateExceedsCap",
      "msg": "rate exceeds the absolute cap"
    },
    {
      "code": 6017,
      "name": "RateDeltaTooHigh",
      "msg": "rate moved further than the per-update delta cap"
    },
    {
      "code": 6018,
      "name": "RateUpdateTooSoon",
      "msg": "rate updated again before the minimum interval elapsed"
    },
    {
      "code": 6019,
      "name": "InvalidAmount",
      "msg": "amount is zero, or rounds to a zero-cost trade"
    },
    {
      "code": 6020,
      "name": "RateNotDeclared",
      "msg": "instrument declares no interest rate; an equity must not accrue"
    },
    {
      "code": 6021,
      "name": "InvalidAddress",
      "msg": "venue program id must not be the default pubkey"
    },
    {
      "code": 6022,
      "name": "MalformedVenueLink",
      "msg": "venue link account is too short to hold a program id"
    },
    {
      "code": 6023,
      "name": "VenueLinkAlreadySet",
      "msg": "the venue link is already set; pass force to re-point it (all venues must be flat)"
    },
    {
      "code": 6024,
      "name": "WrongMakerState",
      "msg": "maker_state is not the seller's maker record at the configured venue"
    },
    {
      "code": 6025,
      "name": "MakersMustUseTheVenue",
      "msg": "market makers must trade through the venue, not the market"
    }
  ],
  "types": [
    {
      "name": "ActiveSet",
      "type": {
        "kind": "struct",
        "fields": [
          {
            "name": "mint",
            "type": "pubkey"
          },
          {
            "name": "active",
            "type": "bool"
          },
          {
            "name": "by",
            "type": "pubkey"
          }
        ]
      }
    },
    {
      "name": "AdminSynced",
      "type": {
        "kind": "struct",
        "fields": [
          {
            "name": "previous",
            "type": "pubkey"
          },
          {
            "name": "current",
            "type": "pubkey"
          }
        ]
      }
    },
    {
      "name": "Asset",
      "docs": [
        "LAYOUT IS READ BY BYTE OFFSET OUTSIDE THIS PROGRAM. price_cents @168, rate_bps @226,",
        "checkpoint_price_scaled @230, checkpoint_at @246, maturity_ts @262 are hardcoded in",
        "solana/scripts/price-feed-job.js (the Switchboard feed a lending market prices from) and in",
        "ge-onchain-frontend utils/solana/rwaMarket.js (displayed prices and quotes). Append only. A",
        "reordered or inserted field yields a plausible wrong price, not an error, in both places."
      ],
      "type": {
        "kind": "struct",
        "fields": [
          {
            "name": "mint",
            "type": "pubkey"
          },
          {
            "name": "vault",
            "type": "pubkey"
          },
          {
            "name": "payout_mint",
            "type": "pubkey"
          },
          {
            "name": "admin",
            "type": "pubkey"
          },
          {
            "name": "price_updater",
            "type": "pubkey"
          },
          {
            "name": "price_cents",
            "type": "u64"
          },
          {
            "name": "fee_bps",
            "type": "u16"
          },
          {
            "name": "is_active",
            "type": "bool"
          },
          {
            "name": "max_per_tx",
            "type": "u64"
          },
          {
            "name": "max_per_user",
            "type": "u64"
          },
          {
            "name": "bump",
            "type": "u8"
          },
          {
            "name": "last_price_update_ts",
            "docs": [
              "When `set_price` last ran. 0 = never (a freshly listed asset's first update is free)."
            ],
            "type": "i64"
          },
          {
            "name": "window_start_ts",
            "docs": [
              "Start of the current rate-limit window, and the price it opened at. Total movement away",
              "from the anchor is what the window cap bounds; the window re-anchors once it expires."
            ],
            "type": "i64"
          },
          {
            "name": "window_anchor_cents",
            "type": "u64"
          },
          {
            "name": "min_update_interval_secs",
            "docs": [
              "Per-asset overrides. 0 = use the module default."
            ],
            "type": "u32"
          },
          {
            "name": "max_window_deviation_bps",
            "type": "u16"
          },
          {
            "name": "rate_bps",
            "docs": [
              "Annual accrual rate in bps, signed (negative = amortising down). 0 = no accrual."
            ],
            "type": "i32"
          },
          {
            "name": "checkpoint_price_scaled",
            "docs": [
              "Checkpoint price at cents x ACCRUAL_SCALE. See C-10 on base_payout."
            ],
            "type": "u128"
          },
          {
            "name": "checkpoint_at",
            "docs": [
              "When the checkpoint was taken. Accrual runs from here. 0 = never checkpointed."
            ],
            "type": "i64"
          },
          {
            "name": "last_rate_update_ts",
            "docs": [
              "When set_rate last ran. 0 = never."
            ],
            "type": "i64"
          },
          {
            "name": "maturity_ts",
            "docs": [
              "Cached maturity, read from TokenMeta by set_rate. 0 = no maturity. Cached so the C-6",
              "clamp costs nothing on the trading path; safe because maturity_date is write-once."
            ],
            "type": "i64"
          },
          {
            "name": "allowance_spent_bps",
            "docs": [
              "Allowance used by price updates, in bps of the price before each update, summed."
            ],
            "type": "u16"
          },
          {
            "name": "allowance_at",
            "docs": [
              "When allowance_spent_bps was last written (unix seconds). 0 = never: full allowance."
            ],
            "type": "u32"
          }
        ]
      }
    },
    {
      "name": "Config",
      "docs": [
        "Program-level authority. One per program, seeds [\"config\"].",
        "",
        "Exists because every per-asset `init` instruction used to accept an unconstrained `Signer`",
        "as \"admin\" and write it straight into the new account, making whoever called first the",
        "owner of a PDA our own flow would later fund. See C-1..C-5 in SECURITY-AUDIT.md."
      ],
      "type": {
        "kind": "struct",
        "fields": [
          {
            "name": "authority",
            "docs": [
              "Governing authority — the Squads vault PDA. Written into every per-asset account's",
              "admin/owner field, so a caller can never nominate itself."
            ],
            "type": "pubkey"
          },
          {
            "name": "operator",
            "docs": [
              "Operator hot key. May CREATE per-asset accounts and pay their rent, but never chooses",
              "who controls them."
            ],
            "type": "pubkey"
          },
          {
            "name": "bump",
            "type": "u8"
          }
        ]
      }
    },
    {
      "name": "ConfigSet",
      "type": {
        "kind": "struct",
        "fields": [
          {
            "name": "authority",
            "type": "pubkey"
          },
          {
            "name": "operator",
            "type": "pubkey"
          }
        ]
      }
    },
    {
      "name": "PriceUpdaterRotated",
      "type": {
        "kind": "struct",
        "fields": [
          {
            "name": "mint",
            "type": "pubkey"
          },
          {
            "name": "previous",
            "type": "pubkey"
          },
          {
            "name": "current",
            "type": "pubkey"
          }
        ]
      }
    },
    {
      "name": "RateUpdated",
      "type": {
        "kind": "struct",
        "fields": [
          {
            "name": "mint",
            "type": "pubkey"
          },
          {
            "name": "old_rate_bps",
            "type": "i32"
          },
          {
            "name": "new_rate_bps",
            "type": "i32"
          },
          {
            "name": "settled_price_cents",
            "docs": [
              "The checkpoint the old rate settled to. For a rate-accruing instrument this event stream",
              "IS the record of what a holder was owed on a given date."
            ],
            "type": "u64"
          },
          {
            "name": "maturity_ts",
            "type": "i64"
          }
        ]
      }
    },
    {
      "name": "TokenMeta",
      "type": {
        "kind": "struct",
        "fields": [
          {
            "name": "mint",
            "type": "pubkey"
          },
          {
            "name": "admin",
            "type": "pubkey"
          },
          {
            "name": "minter",
            "type": "pubkey"
          },
          {
            "name": "automation",
            "type": "pubkey"
          },
          {
            "name": "vault",
            "type": "pubkey"
          },
          {
            "name": "payout_token",
            "type": "pubkey"
          },
          {
            "name": "name",
            "type": "string"
          },
          {
            "name": "symbol",
            "type": "string"
          },
          {
            "name": "issuer",
            "type": "string"
          },
          {
            "name": "deal_url",
            "type": "string"
          },
          {
            "name": "investment_type",
            "type": "u8"
          },
          {
            "name": "investment_category",
            "type": "u8"
          },
          {
            "name": "payout_frequency",
            "type": "u8"
          },
          {
            "name": "token_entity_type",
            "type": "u8"
          },
          {
            "name": "interest_rate_bps",
            "type": "u16"
          },
          {
            "name": "maturity_date",
            "type": "i64"
          },
          {
            "name": "tenor_days",
            "type": "u32"
          },
          {
            "name": "decimals",
            "type": "u8"
          },
          {
            "name": "bump",
            "type": "u8"
          },
          {
            "name": "max_supply",
            "docs": [
              "Hard ceiling on total minted supply, in smallest units. 0 = uncapped.",
              "",
              "Appended after `bump` deliberately: MAX_SIZE already over-allocates by 64 bytes,",
              "and account data is zero-initialised, so meta accounts created before this field",
              "existed deserialise it as 0 (uncapped) rather than failing. No migration needed —",
              "call `set_max_supply` to arm the cap on an existing asset."
            ],
            "type": "u64"
          }
        ]
      }
    },
    {
      "name": "Traded",
      "type": {
        "kind": "struct",
        "fields": [
          {
            "name": "mint",
            "type": "pubkey"
          },
          {
            "name": "trader",
            "type": "pubkey"
          },
          {
            "name": "is_buy",
            "type": "bool"
          },
          {
            "name": "amount",
            "type": "u64"
          },
          {
            "name": "payout",
            "type": "u64"
          }
        ]
      }
    },
    {
      "name": "Vault",
      "type": {
        "kind": "struct",
        "fields": [
          {
            "name": "mint",
            "type": "pubkey"
          },
          {
            "name": "owner",
            "type": "pubkey"
          },
          {
            "name": "bump",
            "type": "u8"
          },
          {
            "name": "total_return_credit",
            "docs": [
              "Stock owed back to makers across ALL venues on this vault — the ring-fence floor.",
              "",
              "Per-venue `return_credit` on the allowance PDA cannot guard a withdrawal, because a",
              "withdrawal is not made in any venue's name and the vault cannot enumerate allowances.",
              "This is the number every path that releases stock has to respect, and it is the exact",
              "counterpart of VaultUpgradeable.totalReturnCredit on Base.",
              "",
              "Accounts created before this field existed are 73 bytes and must be grown by",
              "`migrate_vault` before they can be read. See that instruction."
            ],
            "type": "u64"
          },
          {
            "name": "market_program",
            "docs": [
              "The rwa_market deployment allowed to draw inventory for retail buys.",
              "",
              "Stored, never compiled in. `rwa_market` depends on this program, so depending back is a",
              "cycle cargo refuses — and a hardcoded id is a wire the compiler cannot check, which is",
              "the same mistake as a hardcoded venue id. Zero means no market is configured and",
              "`market_draw_stock` refuses outright, which fails CLOSED."
            ],
            "type": "pubkey"
          },
          {
            "name": "total_owed_payment",
            "docs": [
              "Cash a venue owes a maker but has not yet delivered — the payment-side ring-fence floor.",
              "",
              "The exact counterpart of `total_return_credit` on the stock side, and of",
              "VaultUpgradeable.totalOwedPayment on Base. A deferred payout leaves the cNGN sitting in",
              "this vault, and cash already spoken for must stop behaving like free cash: otherwise the",
              "next redemption's float includes money promised to the last one and the vault pays the",
              "same cNGN twice."
            ],
            "type": "u64"
          }
        ]
      }
    },
    {
      "name": "VenueLink",
      "docs": [
        "The rwa_venue deployment this market defers to. See `set_venue_program`."
      ],
      "type": {
        "kind": "struct",
        "fields": [
          {
            "name": "venue_program",
            "type": "pubkey"
          },
          {
            "name": "bump",
            "type": "u8"
          }
        ]
      }
    },
    {
      "name": "VenueProgramSet",
      "type": {
        "kind": "struct",
        "fields": [
          {
            "name": "previous",
            "type": "pubkey"
          },
          {
            "name": "current",
            "type": "pubkey"
          }
        ]
      }
    }
  ]
}