1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
//! An orderbook-based fixed term market hosted on the Solana blockchain
//!
//! # Interaction
//!
//! To interact with the fixed term market, users will initialize a PDA called an [`MarginUser`](struct@crate::orderbook::state::user::MarginUser).
//!
//! After `MarginUser` intialization, to place an order you must deposit underlying tokens or Jet fixed term market tickets into your account.
//! This will allow you to use the [`PlaceOrder`](struct@crate::orderbook::instructions::place_order::PlaceOrder) instruction, which
//! utilizes the orderbook to match borrowers and lenders.
//!
//! ### Lending
//!
//! To lend tokens, a user will deposit the underlying token into their `MarginUser` account. Then, they may post
//! orders on the book using the [`PlaceOrder`](struct@crate::orderbook::instructions::place_order::PlaceOrder) with a given set of
//! [`OrderParams`](struct@crate::orderbook::state::OrderParams).
//!
//! For example, to lend `1_000_000` tokens at 15% interest in a given market, a lender would specify:
//! ```ignore
//! OrderParams {
//!     /// We want as many tickets as the book will give us
//!     max_ticket_qty: u64::MAX,
//!     /// we are lending 1_000_000 tokens
//!     max_underlying_token_qty: 1_000_000,
//!     /// use the crate function to generate a limit price
//!     limit_price: limit_price_from_f32((1.0 / 1.15)),
//!     /// limit the number of matches to 100
//!     match_limit: 100,
//!     /// Do not fail transaction if order crosses the spread
//!     post_only: false,
//!     /// If order does not get filled immediately, post remainder to the book
//!     post_allowed: true,
//!     /// stake generated tickets automatically, creating `SplitTicket`s
//!     auto_stake: true,
//! }
//!```
//!
//! ### Borrowing
//!
//! For borrowing, a user has two options. They can buy Jet fixed term market tickets from some market, and deposit them into their
//! `MarginUser` account. Or, they may use the `jet-margin` program to place collateralized borrow orders.
//!
//! In the case of a collateralized order, an `TermLoan` will be minted to track the debt. A user must repay or face liquidation
//! by the `jet-margin` program.
//!
//! Example borrow order, where a borrower wants no more than 10% interest to borrow 100_000_000 tokens
//! ```ignore
//! OrderParams {
//!     /// We want to pay no more than 10%
//!     max_ticket_qty: 110_000_000,
//!     /// we only need to borrow 100_000_000 tokens
//!     max_underlying_token_qty: 100_000_000,
//!     /// use the crate function to generate a limit price
//!     limit_price: limit_price_from_f32((1.0 / 1.10)),
//!     /// limit the number of matches to 100
//!     match_limit: 100,
//!     /// Do not fail transaction if order crosses the spread
//!     post_only: false,
//!     /// If order does not get filled immediately, post remainder to the book
//!     post_allowed: true,
//!     /// borrowers do not stake tickets
//!     auto_stake: false,
//! }
//! ```
//!
//! # Orderbook matching engine
//!
//! To facilitate the pairing of lenders and borrowers, the program utilizes the `agnostic-orderbook` crate to create an
//! orderbook. This orderbook allows lenders and borrowers to post orders using underlying tokens, held Jet fixed term market tickets, or, by utilizing `jet-margin` accounts,
//! a collateralized borrow order in lieu of held funds.
//!
//! ### EventQueue operation and Adapters
//!
//! The orderbook works by matching posted orders and pushing events to an `EventQueue` to be consumed by a crank operating offchain by
//! sending transactions to Solana.
//!
//! Some users may want to subscribe to events generated by the orderbook matching. To do this, a user must register with
//! the program an `Adapter` through their `MarginUser` account using the [`RegisterAdapter`](struct@crate::orderbook::instructions::event_adapter::RegisterAdapter) instruction. This instruction
//! creates an `AdapterEventQueue` PDA to which all processed orders containing the `MarginUser` account will be pushed.
//!
//! Users are responsible for handling the consumption logic for their adapter. To clear events after processing, use the [`PopAdapterEvents`](struct@crate::orderbook::instructions::event_adapter::PopAdapterEvents) instruction.
//!
//! # Jet Fixed Term Market Tickets
//!
//! Jet fixed term market tickets are fungible spl tokens that must be staked to claim their underlying value. In order to create tickets, a user must either
//! place a lend order on the orderbook, or exchange the token underlying the fixed term market (in practice, almost never
//! will users do this, as it locks their tokens for at least the tenor of the market).
//!
//! ### Ticket kinds and redemption
//!
//! The program allots for two types of ticket redemption. The [`ClaimTicket`](struct@crate::tickets::state::ClaimTicket) is given when
//! a user stakes directly with the program. As there is no information about the creation of the tickets, a `ClaimTicket` does not
//! have accounting for principal or interest, and only contains a redemptive value.
//!
//! Conversely, a [`SplitTicket`](struct@crate::tickets::state::SplitTicket) contains split principal and interest values. As well as
//! the slot it was minted. To create a `SplitTicket`, you must configure your [`OrderParams`](struct@crate::orderbook::state::OrderParams) `auto_stake` flag to
//! `true`. This will allow to program to immediately stake your tickets as the match event is processed.
//!
//! After the tenor has passed, the ticket may be redeemed for the underlying value with the program. Also included are instructions
//! for transferring ownership of a ticket.
//!
//! # Debt and Term Loans
//!
//! When using a `jet-margin` account to post a collateralized borrow order, an [`TermLoan`](struct@crate::orderbook::state::debt::TermLoan) is created to track
//! amounts owed to the program. `TermLoan`s are either repaid manually by the user, or handled by an off-chain liquidator.

/// Program instructions and structs related to authoritative control of the program state
pub mod control;
/// Program instructions, methods and structs related to the use of margin accounts with the fixed-term program
pub mod margin;
/// Program instructions and structs related to use of the on chain orderbook
pub mod orderbook;
/// Program instructions and structs related to the redeemable tickets
pub mod tickets;

mod errors;
pub mod events;
pub use errors::FixedTermErrorCode;

mod market_token_manager;
/// Utilities for safely serializing and deserializing solana accounts
pub(crate) mod serialization;
/// local utilities for the crate
pub(crate) mod utils;

pub(crate) mod instructions;
use instructions::*;

#[macro_use]
extern crate bitflags;

use anchor_lang::prelude::*;
use orderbook::state::OrderParams;

declare_id!("JBond79m9K6HqYwngCjiJHb311GTXggo46kGcT2GijUc");

#[program]
pub mod jet_fixed_term {
    use super::*;

    //
    // Control Instructions
    // =============================================
    //

    /// authorize an address to run orderbook consume_event instructions
    pub fn authorize_crank(ctx: Context<AuthorizeCrank>) -> Result<()> {
        instructions::authorize_crank::handler(ctx)
    }

    /// unauthorize an address to run orderbook consume_event instructions
    pub fn revoke_crank(ctx: Context<RevokeCrank>) -> Result<()> {
        instructions::revoke_crank::handler(ctx)
    }

    /// Initializes a Market for a fixed term market
    pub fn initialize_market(
        ctx: Context<InitializeMarket>,
        params: InitializeMarketParams,
    ) -> Result<()> {
        instructions::initialize_market::handler(ctx, params)
    }

    /// Initializes a new orderbook
    pub fn initialize_orderbook(
        ctx: Context<InitializeOrderbook>,
        params: InitializeOrderbookParams,
    ) -> Result<()> {
        instructions::initialize_orderbook::handler(ctx, params)
    }

    /// Modify a `Market` account
    /// Authority use only
    pub fn modify_market(ctx: Context<ModifyMarket>, data: Vec<u8>, offset: usize) -> Result<()> {
        instructions::modify_market::handler(ctx, data, offset)
    }

    /// Pause matching of orders placed in the orderbook
    pub fn pause_order_matching(ctx: Context<PauseOrderMatching>) -> Result<()> {
        instructions::pause_order_matching::handler(ctx)
    }

    /// Resume matching of orders placed in the orderbook
    /// NOTE: This instruction may have to be run several times to clear the
    /// existing matches. Check the `orderbook_market_state.pause_matching` variable
    /// to determine success
    pub fn resume_order_matching(ctx: Context<ResumeOrderMatching>) -> Result<()> {
        instructions::resume_order_matching::handler(ctx)
    }
    //
    // =============================================
    //

    //
    // Margin Instructions
    // =============================================
    //

    /// Create a new borrower account
    pub fn initialize_margin_user(ctx: Context<InitializeMarginUser>) -> Result<()> {
        instructions::initialize_margin_user::handler(ctx)
    }

    /// Place a borrow order by leveraging margin account value
    pub fn margin_borrow_order(
        ctx: Context<MarginBorrowOrder>,
        params: OrderParams,
        seed: Vec<u8>,
    ) -> Result<()> {
        instructions::margin_borrow_order::handler(ctx, params, seed)
    }

    /// Sell tickets that are already owned
    pub fn margin_sell_tickets_order(
        ctx: Context<MarginSellTicketsOrder>,
        params: OrderParams,
    ) -> Result<()> {
        instructions::margin_sell_tickets_order::handler(ctx, params)
    }

    /// Redeem a staked ticket
    pub fn margin_redeem_ticket(ctx: Context<MarginRedeemTicket>) -> Result<()> {
        instructions::margin_redeem_ticket::handler(ctx)
    }

    /// Place a `Lend` order to the book by depositing tokens
    pub fn margin_lend_order(
        ctx: Context<MarginLendOrder>,
        params: OrderParams,
        seed: Vec<u8>,
    ) -> Result<()> {
        instructions::margin_lend_order::handler(ctx, params, seed)
    }

    /// Refresh the associated margin account `claims` for a given `MarginUser` account
    pub fn refresh_position(ctx: Context<RefreshPosition>, expect_price: bool) -> Result<()> {
        instructions::refresh_position::handler(ctx, expect_price)
    }

    /// Repay debt on an TermLoan
    pub fn repay(ctx: Context<Repay>, amount: u64) -> Result<()> {
        instructions::repay::handler(ctx, amount)
    }

    /// Settle payments to a margin account
    pub fn settle(ctx: Context<Settle>) -> Result<()> {
        instructions::settle::handler(ctx)
    }

    //
    // =============================================
    //

    //
    // Orderbook Instructions
    // =============================================
    //

    /// Place an order to the book to sell tickets, which will burn them
    pub fn sell_tickets_order(ctx: Context<SellTicketsOrder>, params: OrderParams) -> Result<()> {
        instructions::sell_tickets_order::handler(ctx, params)
    }

    /// Cancels an order on the book
    pub fn cancel_order(ctx: Context<CancelOrder>, order_id: u128) -> Result<()> {
        instructions::cancel_order::handler(ctx, order_id)
    }

    /// Place a `Lend` order to the book by depositing tokens
    pub fn lend_order(ctx: Context<LendOrder>, params: OrderParams, seed: Vec<u8>) -> Result<()> {
        instructions::lend_order::handler(ctx, params, seed)
    }

    /// Crank specific instruction, processes the event queue
    pub fn consume_events<'a, 'b, 'info>(
        ctx: Context<'a, 'b, 'b, 'info, ConsumeEvents<'info>>,
        num_events: u32,
        seed_bytes: Vec<Vec<u8>>,
    ) -> Result<()> {
        instructions::consume_events::handler(ctx, num_events, seed_bytes)
    }

    //
    // =============================================
    //

    //
    // Ticket Instructions
    // =============================================
    //

    /// Exchange underlying token for fixed term tickets
    /// WARNING: tickets must be staked for redeption of underlying
    pub fn exchange_tokens(ctx: Context<ExchangeTokens>, amount: u64) -> Result<()> {
        instructions::exchange_tokens::handler(ctx, amount)
    }

    /// Redeems staked tickets for their underlying value
    pub fn redeem_ticket(ctx: Context<RedeemTicket>) -> Result<()> {
        instructions::redeem_ticket::handler(ctx)
    }

    /// Stakes tickets for later redemption
    pub fn stake_tickets(ctx: Context<StakeTickets>, params: StakeTicketsParams) -> Result<()> {
        instructions::stake_tickets::handler(ctx, params)
    }

    /// Transfer staked tickets to a new owner
    pub fn tranfer_ticket_ownership(
        ctx: Context<TransferTicketOwnership>,
        new_owner: Pubkey,
    ) -> Result<()> {
        instructions::transfer_ticket_ownership::handler(ctx, new_owner)
    }
    //
    // =============================================
    //

    //
    // Event Adapter Instructions
    // =============================================
    //

    /// Register a new EventAdapter for syncing to the orderbook events
    pub fn register_adapter(
        ctx: Context<RegisterAdapter>,
        params: RegisterAdapterParams,
    ) -> Result<()> {
        instructions::register_adapter::handler(ctx, params)
    }

    /// Pop the given number of events off the adapter queue
    /// Event logic is left to the outside program
    pub fn pop_adapter_events(ctx: Context<PopAdapterEvents>, num_events: u32) -> Result<()> {
        instructions::pop_adapter_events::handler(ctx, num_events)
    }
    //
    // =============================================
    //
}

pub mod seeds {
    use anchor_lang::prelude::constant;

    #[constant]
    pub const MARKET: &[u8] = b"market";

    #[constant]
    pub const TICKET_ACCOUNT: &[u8] = b"ticket_account";

    #[constant]
    pub const TICKET_MINT: &[u8] = b"ticket_mint";

    #[constant]
    pub const CLAIM_TICKET: &[u8] = b"claim_ticket";

    #[constant]
    pub const CRANK_AUTHORIZATION: &[u8] = b"crank_authorization";

    #[constant]
    pub const CLAIM_NOTES: &[u8] = b"claim_notes";

    #[constant]
    pub const COLLATERAL_NOTES: &[u8] = b"collateral_notes";

    #[constant]
    pub const SPLIT_TICKET: &[u8] = b"split_ticket";

    #[constant]
    pub const EVENT_ADAPTER: &[u8] = b"event_adapter";

    #[constant]
    pub const TERM_LOAN: &[u8] = b"term_loan";

    #[constant]
    pub const ORDERBOOK_MARKET_STATE: &[u8] = b"orderbook_market_state";

    #[constant]
    pub const MARGIN_BORROWER: &[u8] = b"margin_borrower";

    #[constant]
    pub const UNDERLYING_TOKEN_VAULT: &[u8] = b"underlying_token_vault";
}