{"id":14968485,"url":"https://github.com/bonfida/bonfida-utils","last_synced_at":"2025-04-04T13:11:21.134Z","repository":{"id":38395763,"uuid":"430911952","full_name":"Bonfida/bonfida-utils","owner":"Bonfida","description":"Collection of different utilities in use across various Bonfida projects","archived":false,"fork":false,"pushed_at":"2025-03-24T03:13:46.000Z","size":768,"stargazers_count":47,"open_issues_count":0,"forks_count":10,"subscribers_count":3,"default_branch":"main","last_synced_at":"2025-04-04T12:58:47.797Z","etag":null,"topics":["bonfida","rust","solana","solana-program"],"latest_commit_sha":null,"homepage":"","language":"Rust","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/Bonfida.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2021-11-23T00:46:31.000Z","updated_at":"2025-02-07T10:45:08.000Z","dependencies_parsed_at":"2023-10-13T05:19:58.714Z","dependency_job_id":"aae1d529-70f1-41c8-addc-d3d47aea16f9","html_url":"https://github.com/Bonfida/bonfida-utils","commit_stats":{"total_commits":189,"total_committers":6,"mean_commits":31.5,"dds":0.5608465608465609,"last_synced_commit":"8213f1625a7dcb5a274c267c7a7a061bb5b6eddb"},"previous_names":[],"tags_count":119,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Bonfida%2Fbonfida-utils","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Bonfida%2Fbonfida-utils/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Bonfida%2Fbonfida-utils/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Bonfida%2Fbonfida-utils/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/Bonfida","download_url":"https://codeload.github.com/Bonfida/bonfida-utils/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247182400,"owners_count":20897381,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["bonfida","rust","solana","solana-program"],"created_at":"2024-09-24T13:40:00.268Z","updated_at":"2025-04-04T13:11:21.109Z","avatar_url":"https://github.com/Bonfida.png","language":"Rust","readme":"\u003ch1 align=\"center\"\u003eBonfida utils\u003c/h1\u003e\n\u003cbr /\u003e\n\u003cp align=\"center\"\u003e\n\u003cimg width=\"250\" src=\"https://i.imgur.com/nn7LMNV.png\"/\u003e\n\u003c/p\u003e\n\u003cp align=\"center\"\u003e\n\u003ca href=\"https://twitter.com/bonfida\"\u003e\n\u003cimg src=\"https://img.shields.io/twitter/url?label=Bonfida\u0026style=social\u0026url=https%3A%2F%2Ftwitter.com%2Fbonfida\"\u003e\n\u003c/a\u003e\n\u003c/p\u003e\n\n\u003cbr /\u003e\n\n\u003cdiv align=\"center\"\u003e\n\u003cimg src=\"https://img.shields.io/badge/TypeScript-007ACC?style=for-the-badge\u0026logo=typescript\u0026logoColor=white\" /\u003e\n\u003cimg src=\"https://img.shields.io/badge/Rust-000000?style=for-the-badge\u0026logo=rust\u0026logoColor=white\" /\u003e\n\u003cimg src=\"https://img.shields.io/badge/Python-FFD43B?style=for-the-badge\u0026logo=python\u0026logoColor=blue\" /\u003e\n\u003c/div\u003e\n\n\u003cbr /\u003e\n\u003ch2 align=\"center\"\u003eTable of contents\u003c/h2\u003e\n\u003cbr /\u003e\n\n1. [Introduction](#introduction)\n2. [Installation](#installation)\n3. [Used by](#used-by)\n4. [Check functions](#check-functions)\n5. [FP32 and FP64 math functions](#fp32)\n6. [`InstructionsAccount` trait](#instructions-account)\n7. [`BorshSize` trait](#borsh-size)\n8. [Project structure](#project-structure)\n9. [Example](#examples)\n\n\u003cbr /\u003e\n\u003ca name=\"introduction\"\u003e\u003c/a\u003e\n\u003ch2 align=\"center\"\u003eIntroduction\u003c/h2\u003e\n\u003cbr /\u003e\n\nThis repo is a collection of different utilities in use across various Bonfida projects. The repository has the following structure:\n\n- `utils` : Main `bonfida-utils` utilities library\n- `autobindings` : CLI command to autogenerate Typescript or Python bindings for smart contracts written in the specific Bonfida style\n- `autoproject` : CLI command to autogenerate an extensive template smart contract\n- `autodoc`: CLI command to generate a documented `instruction.rs` file\n- `macros` : Auxiliary crate containing macros in use by the main `bonfida-utils` library\n- `cli` : CLI entrypoint for all tools\n\n\u003cbr /\u003e\n\u003ca name=\"used-by\"\u003e\u003c/a\u003e\n\u003ch2 align=\"center\"\u003eUsed by\u003c/h2\u003e\n\u003cbr /\u003e\n\n- [Serum DEX v4](https://github.com/Bonfida/dex-v4)\n- [Serum Core (asset agnostic orderbook)](https://github.com/Bonfida/agnostic-orderbook)\n\n\u003cbr /\u003e\n\u003ca name=\"installation\"\u003e\u003c/a\u003e\n\u003ch2 align=\"center\"\u003eInstallation\u003c/h2\u003e\n\u003cbr /\u003e\n\nThis repository is [published on crates.io](https://crates.io/crates/bonfida-utils), in order to use it in your Solana programs add this to your `Cargo.toml` file\n\n```\nbonfida-utils = \"0.1\"\n```\n\nInstall the main bonfida cli\n\n```\ngit clone https://github.com/Bonfida/bonfida-utils.git\ncd bonfida-utils\ncargo install --path crates/cli\n```\n\nTo automatically generate Javascript or Python bindings\n\n```\ncd /path/to/your/project\nbonfida autobindings --help\n```\n\nTo automatically generate a template smart contract\n\n```\ncd /path/to/project/parent\nbonfida autoproject project-name\n```\n\nTo automatically generate a documented `instruction.rs`:\n\n```\ncd /path/to/your/project\nbonfida autodoc\n```\n\nIn order to generate instruction bindings automatically your project needs to follow a certain structure and derive certain traits that are detailed in the following sections.\n\n\u003cbr /\u003e\n\u003ca name=\"check-functions\"\u003e\u003c/a\u003e\n\u003ch2 align=\"center\"\u003eCheck functions\u003c/h2\u003e\n\u003cbr /\u003e\n\n`bonfida-utils` contains safety verification functions:\n\n- `check_account_key`\n- `check_account_owner`\n- `check_signer`\n\n\u003cbr /\u003e\n\u003ca name=\"fp32\"\u003e\u003c/a\u003e\n\u003ch2 align=\"center\"\u003eFP32 and FP64 math functions\u003c/h2\u003e\n\u003cbr /\u003e\n\n`bonfida-utils` contains some useful math functions for FP32 and FP64:\n\n- `fp32_div`\n- `fp32_mul`\n- `ifp32_div`\n- `ifp32_mul`\n- `fp64_div`\n- `fp64_mul`\n\n\u003cbr/\u003e\n\u003ca name=\"instructions-account\"\u003e\u003c/a\u003e\n\u003ch2 align=\"center\"\u003eInstructionsAccount\u003c/h2\u003e\n\u003cbr/\u003e\n\nThe `Accounts` struct needs to derive the `InstructionsAccount` trait in order to automatically generate bindings in Rust and JS. In order to know which accounts are writable and/or signer you will have to specify constraints (`cons`) for each account of the struct:\n\n1.  For writable accounts: `#[cons(writable)]`\n2.  For signer accounts: `#[cons(signer)]`\n3.  For signer and writable accounts: `#[cons(signer, writable)]`\n\nFor example\n\n```rust\nuse bonfida_utils::{InstructionsAccount};\n\n#[derive(InstructionsAccount)]\npub struct Accounts\u003c'a, T\u003e {\n\n    pub read_only_account: \u0026'a T, // Read only account\n\n    #[cons(writable)] // This specifies that the account is writable\n    pub writable_account: \u0026'a T,\n\n    #[cons(signer)] // This specifies that the account is sginer\n    pub signer_account: \u0026'a T,\n\n    // Write the d\n    #[cons(signer, writable)]  // This specifies that the account is sginer and writable\n    pub signer_and_writable_account: \u0026'a T,\n}\n\n```\n\nTo specify accounts that are optional\n\n```rust\npub struct Accounts\u003c'a, T\u003e {\n    // Writable account that is optional\n    #[cons(writable)]\n    pub referrer_account_opt: Option\u003c\u0026'a T\u003e,\n}\n```\n\n\u003cbr/\u003e\n\u003ca name=\"borsh-size\"\u003e\u003c/a\u003e\n\u003ch2 align=\"center\"\u003eBorshSize\u003c/h2\u003e\n\u003cbr/\u003e\n\nThe struct used for the data of the instruction needs to derive the `BorshSize` trait, for example let's take the following struct\n\n```rust\n#[derive(BorshSerialize, BorshDeserialize, BorshSize)]\npub struct Params {\n    pub position_type: PositionType,\n    pub market_index: u16,\n    pub max_base_qty: u64,\n    pub max_quote_qty: u64,\n    pub limit_price: u64,\n    pub match_limit: u64,\n    pub self_trade_behavior: u8,\n    pub order_type: OrderType,\n    pub number_of_markets: u8,\n}\n```\n\nIn the above example, `BorshSize` should be derived for `PositionType` and `OrderType` as well. The derive macro can take care of this\nfor field-less enums :\n\n```rust\n#[derive(BorshSerialize, BorshDeserialize, BorshSize)]\npub enum OrderType {\n  Limit,\n  ImmediateOrCancel,\n  FillOrKill,\n  PostOnly\n}\n```\n\nYou might need to implement `BorshSize` yourself for certain types (e.g an `enum` with variants containing fields) :\n\n```rust\n#[derive(BorshDeserialize, BorshSerialize, Debug, PartialEq, FromPrimitive)]\npub enum ExampleEnum {\n    FirstVariant,\n    SecondVariant(u128),\n}\n\nimpl BorshSize for ExampleEnum {\n    fn borsh_len(\u0026self) -\u003e usize {\n        match self {\n          Self::FirstVariant =\u003e 1,\n          Self::SecondVariant(n) =\u003e 1 + n.borsh_len()\n        }\n    }\n}\n```\n\n\u003cbr /\u003e\n\u003ca name=\"project-structure\"\u003e\u003c/a\u003e\n\u003ch2 align=\"center\"\u003eProject structure\u003c/h2\u003e\n\u003cbr /\u003e\n\n🚨 The project structure is important:\n\n1. The Solana program must be in a folder called `program`\n2. The JS bindings must be in a folder called `js`\n3. The processor folder needs to contain instructions' logic in separate files. The name of each file needs to be snake case and match the name of the associated function in `instructions.rs`\n4. The instruction enum of `instructions.rs` needs to have Pascal case names that match the snake case names of the files in `processor`. This is detailed in the example below.\n5. The instruction enum of `instructions.rs` needs to be the first enum to be defined in that file.\n\n\u003cbr /\u003e\n\u003ca name=\"examples\"\u003e\u003c/a\u003e\n\u003ch2 align=\"center\"\u003eExamples\u003c/h2\u003e\n\u003cbr /\u003e\n\nLet's have a look at a real life example.\n\nThe project structure is as follow\n\n```\n├── program\n│   ├── instructions.rs\n│   ├── processor\n│   │   ├── create_market.rs\n├── js\n    ├── src\n├── python\n    ├── src\n│...\n│... The rest is omitted\n```\n\nTo simplify we will consider only one instruction `create_market.rs` and only focus on `processor` and `instructions.rs`.\n\nWe can see from the project structure that `create_market.rs` is located in the `processor` directory, let's have a look at the contents of the file:\n\n```rust\n//! Creates a perp market\n// The sentence above will be used to describe the instruction in the auto generated doc ⚠️\nuse bonfida_utils::{checks::check_signer, BorshSize, InstructionsAccount};\nuse borsh::{BorshDeserialize, BorshSerialize};\n// Other imports are omitted\n\n#[derive(InstructionsAccount)]\npub struct Accounts\u003c'a, T\u003e {\n    /// The market address\n    #[cons(writable)] // This specifies that the account is writable\n    pub market: \u0026'a T,\n\n    /// The ecosystem address\n    #[cons(writable)]\n    pub ecosystem: \u0026'a T,\n\n    /// The address of the Serum Core market\n    pub aob_orderbook: \u0026'a T,\n\n    /// The address of the Serum Core event queue\n    pub aob_event_queue: \u0026'a T,\n\n    /// The address of the Serum Core asks slab\n    pub aob_asks: \u0026'a T,\n\n    /// The address of the Serum Core bids slab\n    pub aob_bids: \u0026'a T,\n\n    /// The program ID of Serum Core\n    pub aob_program: \u0026'a T,\n\n    /// The Pyth oracle address for this market\n    pub oracle: \u0026'a T,\n\n    /// The market admin address\n    #[cons(signer)] // This specifies that the account is signer\n    pub admin: \u0026'a T,\n\n    /// The market vault address\n    pub vault: \u0026'a T,\n}\n\n// Constraints (cons) can be combined e.g\n// #[cons(signer, writable)]\n// pub some_account: \u0026'a T\n//\n// Optional accounts are supported as well\n// pub discount_account_opt: Option\u003c\u0026'a T\u003e\n\n// BorshSize might require custom impl e.g for enum\n#[derive(BorshSerialize, BorshDeserialize, BorshSize)]\npub struct Params {\n    pub market_symbol: String,\n    pub signer_nonce: u8,\n    pub coin_decimals: u8,\n    pub quote_decimals: u8,\n}\n\nimpl\u003c'a, 'b: 'a\u003e Accounts\u003c'a, AccountInfo\u003c'b\u003e\u003e {\n    pub fn parse(accounts: \u0026'a [AccountInfo\u003c'b\u003e]) -\u003e Result\u003cSelf, ProgramError\u003e {\n        let accounts_iter = \u0026mut accounts.iter();\n\n        let a = Accounts {\n            market: next_account_info(accounts_iter)?,\n            ecosystem: next_account_info(accounts_iter)?,\n            aob_orderbook: next_account_info(accounts_iter)?,\n            aob_event_queue: next_account_info(accounts_iter)?,\n            aob_asks: next_account_info(accounts_iter)?,\n            aob_bids: next_account_info(accounts_iter)?,\n            aob_program: next_account_info(accounts_iter)?,\n            oracle: next_account_info(accounts_iter)?,\n            admin: next_account_info(accounts_iter)?,\n            vault: next_account_info(accounts_iter)?,\n        };\n\n        // Account checks are omitted\n\n        Ok(a)\n    }\n}\n\npub fn process(program_id: \u0026Pubkey, accounts: \u0026[AccountInfo], params: Params) -\u003e ProgramResult {\n    let accounts = Accounts::parse(accounts)?;\n\n    let Params {\n        market_symbol,\n        signer_nonce,\n        coin_decimals,\n        quote_decimals,\n    } = params;\n\n    // Instruction logic is omitted\n\n    Ok(())\n}\n\n```\n\nWe can see that `create_market.rs` contains two important definitions:\n\n1. `Accounts` struct: its `InstructionsAccount` trait implementation should be derived, and the constraints for each account of the struct should be specified\n2. `Params` struct: its `BorshSize` trait implementation should be derived\n\nThe `instruction.rs` file can be autogenerated using `cargo autodoc`\n\n```rust\nuse bonfida_utils::InstructionsAccount;\n// Other imports are omitted\n\n#[derive(BorshSerialize, BorshDeserialize)]\npub enum PerpInstruction {\n    /// Creates a new perps market\n    ///\n    /// | Index | Writable | Signer | Description                               |\n    /// | --------------------------------------------------------------------- |\n    /// | 0     | ✅        | ❌      | The market address                        |\n    /// | 1     | ✅        | ❌      | The ecosystem address                     |\n    /// | 2     | ❌        | ❌      | The address of the Serum Core market      |\n    /// | 3     | ❌        | ❌      | The address of the Serum Core event queue |\n    /// | 4     | ❌        | ❌      | The address of the Serum Core asks slab   |\n    /// | 5     | ❌        | ❌      | The address of the Serum Core bids slab   |\n    /// | 6     | ❌        | ❌      | The program ID of Serum Core              |\n    /// | 7     | ❌        | ❌      | The Pyth oracle address for this market   |\n    /// | 8     | ❌        | ✅      | The market admin address                  |\n    /// | 9     | ❌        | ❌      | The market vault address                  |\n    CreateMarket,\n    ///\n    /// ...\n}\npub fn create_market(\n    accounts: create_market::Accounts\u003cPubkey\u003e,\n    params: create_market::Params,\n) -\u003e Instruction {\n    accounts.get_instruction(crate::ID, PerpInstruction::CreateMarket as u8, params)\n}\n```\n\nIn order to generate Javascript instruction bindings run\n\n```\ncargo autobindings\n```\n\nThis will generate a file named `raw_instructions.ts` that contains all the instructions of your program\n\n```js\n// This file is auto-generated. DO NOT EDIT\nimport BN from \"bn.js\";\nimport { Schema, serialize } from \"borsh\";\nimport { PublicKey, TransactionInstruction } from \"@solana/web3.js\";\n\nexport interface AccountKey {\n  pubkey: PublicKey;\n  isSigner: boolean;\n  isWritable: boolean;\n}\n\nexport class createMarketInstruction {\n  tag: number;\n  marketSymbol: string;\n  signerNonce: number;\n  coinDecimals: number;\n  quoteDecimals: number;\n  static schema: Schema = new Map([\n    [\n      createMarketInstruction,\n      {\n        kind: \"struct\",\n        fields: [\n          [\"tag\", \"u8\"],\n          [\"marketSymbol\", \"string\"],\n          [\"signerNonce\", \"u8\"],\n          [\"coinDecimals\", \"u8\"],\n          [\"quoteDecimals\", \"u8\"],\n        ],\n      },\n    ],\n  ]);\n  constructor(obj: {\n    marketSymbol: string,\n    signerNonce: number,\n    coinDecimals: number,\n    quoteDecimals: number,\n  }) {\n    this.tag = 0;\n    this.marketSymbol = obj.marketSymbol;\n    this.signerNonce = obj.signerNonce;\n    this.coinDecimals = obj.coinDecimals;\n    this.quoteDecimals = obj.quoteDecimals;\n  }\n  serialize(): Uint8Array {\n    return serialize(createMarketInstruction.schema, this);\n  }\n  getInstruction(\n    programId: PublicKey,\n    market: PublicKey,\n    ecosystem: PublicKey,\n    aobOrderbook: PublicKey,\n    aobEventQueue: PublicKey,\n    aobAsks: PublicKey,\n    aobBids: PublicKey,\n    aobProgram: PublicKey,\n    oracle: PublicKey,\n    admin: PublicKey,\n    vault: PublicKey\n  ): TransactionInstruction {\n    const data = Buffer.from(this.serialize());\n    let keys: AccountKey[] = [];\n    keys.push({\n      pubkey: market,\n      isSigner: false,\n      isWritable: true,\n    });\n    keys.push({\n      pubkey: ecosystem,\n      isSigner: false,\n      isWritable: true,\n    });\n    keys.push({\n      pubkey: aobOrderbook,\n      isSigner: false,\n      isWritable: false,\n    });\n    keys.push({\n      pubkey: aobEventQueue,\n      isSigner: false,\n      isWritable: false,\n    });\n    keys.push({\n      pubkey: aobAsks,\n      isSigner: false,\n      isWritable: false,\n    });\n    keys.push({\n      pubkey: aobBids,\n      isSigner: false,\n      isWritable: false,\n    });\n    keys.push({\n      pubkey: aobProgram,\n      isSigner: false,\n      isWritable: false,\n    });\n    keys.push({\n      pubkey: oracle,\n      isSigner: false,\n      isWritable: false,\n    });\n    keys.push({\n      pubkey: admin,\n      isSigner: true,\n      isWritable: false,\n    });\n    keys.push({\n      pubkey: vault,\n      isSigner: false,\n      isWritable: false,\n    });\n    return new TransactionInstruction({\n      keys,\n      programId,\n      data,\n    });\n  }\n}\n```\n\nTo generate Python bindings run\n\n```\nbonfida autobindings --target-language py\n```\n\nTo run the autobindings tests you have to:\n\n- Regenerate the js and python bindings to be sure they are up to date\n- Run `yarn`in the js folder\n- Install ts-node with: `sudo npm install -g ts-node typescript '@types/node'`\n- From the `program`folder, run `bonfida autobindings --test true`\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbonfida%2Fbonfida-utils","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fbonfida%2Fbonfida-utils","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbonfida%2Fbonfida-utils/lists"}