├── integration_tests ├── tests │ ├── helpers │ │ ├── mod.rs │ │ └── serialized_accounts.rs │ ├── tests.rs │ ├── tip_router │ │ ├── bpf │ │ │ └── mod.rs │ │ ├── switchboard_set_weight.rs │ │ ├── mod.rs │ │ ├── initialize_epoch_snapshot.rs │ │ ├── admin_update_weight_table.rs │ │ ├── snapshot_vault_operator_delegation.rs │ │ ├── initialize_ballot_box.rs │ │ ├── initialize_ncn_reward_router.rs │ │ ├── initialize_base_reward_router.rs │ │ ├── set_tie_breaker.rs │ │ ├── admin_set_parameters.rs │ │ └── admin_set_st_mint.rs │ └── fixtures │ │ ├── stake_pool.so │ │ ├── spl_stake_pool.so │ │ ├── jito_tip_payment.so │ │ ├── jito_vault_program.so │ │ ├── jito_restaking_program.so │ │ ├── jito_tip_distribution.so │ │ ├── jito_priority_fee_distribution.so │ │ └── mod.rs ├── src │ └── main.rs └── Cargo.toml ├── cli ├── .gitignore ├── src │ ├── ported │ │ ├── mod.rs │ │ ├── submit_stats.rs │ │ └── error.rs │ ├── keeper │ │ └── mod.rs │ ├── lib.rs │ └── bin │ │ └── main.rs ├── .env.example ├── Dockerfile ├── Cargo.toml └── README.md ├── security_audits ├── certora.pdf └── offside.pdf ├── .dockerignore ├── rustfmt.toml ├── generate_client.sh ├── rust-toolchain.toml ├── meta_merkle_tree ├── src │ ├── lib.rs │ ├── error.rs │ ├── utils.rs │ ├── verify.rs │ └── tree_node.rs └── Cargo.toml ├── .cargo ├── programs.env └── config.toml ├── .config └── nextest.toml ├── docs └── index.md ├── clients ├── js │ └── jito_tip_router │ │ ├── errors │ │ └── index.ts │ │ ├── programs │ │ └── index.ts │ │ ├── index.ts │ │ ├── accounts │ │ └── index.ts │ │ ├── types │ │ ├── fee.ts │ │ ├── index.ts │ │ ├── ncnFeeGroup.ts │ │ ├── baseFeeGroup.ts │ │ ├── configAdminRole.ts │ │ ├── ncnFeeGroupWeight.ts │ │ ├── baseRewardRouterRewards.ts │ │ ├── vaultRewardRoute.ts │ │ ├── progress.ts │ │ ├── ballot.ts │ │ ├── stakeWeights.ts │ │ ├── ballotTally.ts │ │ ├── ncnRewardRoute.ts │ │ ├── vaultEntry.ts │ │ ├── feeConfig.ts │ │ ├── weightEntry.ts │ │ ├── fees.ts │ │ ├── epochAccountStatus.ts │ │ ├── operatorVote.ts │ │ ├── stMintEntry.ts │ │ └── vaultOperatorStakeWeight.ts │ │ └── instructions │ │ └── index.ts └── rust │ └── jito_tip_router │ ├── src │ ├── generated │ │ ├── errors │ │ │ └── mod.rs │ │ ├── mod.rs │ │ ├── programs.rs │ │ ├── types │ │ │ ├── fee.rs │ │ │ ├── base_fee_group.rs │ │ │ ├── ncn_fee_group.rs │ │ │ ├── ncn_fee_group_weight.rs │ │ │ ├── base_reward_router_rewards.rs │ │ │ ├── progress.rs │ │ │ ├── stake_weights.rs │ │ │ ├── stake_pool_instruction.rs │ │ │ ├── ballot.rs │ │ │ ├── ballot_tally.rs │ │ │ ├── config_admin_role.rs │ │ │ ├── vault_reward_route.rs │ │ │ ├── weight_entry.rs │ │ │ ├── fees.rs │ │ │ ├── fee_config.rs │ │ │ ├── ncn_reward_route.rs │ │ │ ├── epoch_account_status.rs │ │ │ ├── vault_operator_stake_weight.rs │ │ │ ├── operator_vote.rs │ │ │ ├── vault_entry.rs │ │ │ ├── st_mint_entry.rs │ │ │ └── mod.rs │ │ └── accounts │ │ │ ├── mod.rs │ │ │ ├── epoch_marker.rs │ │ │ ├── vault_registry.rs │ │ │ ├── weight_table.rs │ │ │ ├── config.rs │ │ │ ├── epoch_snapshot.rs │ │ │ ├── ballot_box.rs │ │ │ ├── operator_snapshot.rs │ │ │ ├── base_reward_router.rs │ │ │ ├── ncn_reward_router.rs │ │ │ └── epoch_state.rs │ └── lib.rs │ └── Cargo.toml ├── tip-router-operator-cli ├── tests │ └── fixtures │ │ └── accounts │ │ ├── 3AVi9Tg9Uo68tJfuvoKvqKNWKkC5wPdSSdeBnizKZ6jT.json │ │ ├── 96gYZGLnJYVFmbjzopPSU6QiEV5fGqZNyN9nmNhvrZU5.json │ │ ├── ADaUMid9yfUytqMBgopwjb2DTLSokTSzL1zt6iGPaS49.json │ │ ├── ADuUkR4vqLUMWXxW9gh6D6L8pMSawimctcNZ5pGwDcEt.json │ │ ├── Cw8CFyM9FkoMi7K7Crf6HNQqf4uEMzpKw6QNghXLvLkY.json │ │ ├── DfXygSm4jCyNCybVYYK6DwvWqjKee8pbDmJGcLWNDXjh.json │ │ ├── DttWaMuVvTiduZRnguLF7jNxTgiMBZ1hyAumKUiL2KRL.json │ │ ├── HFqU5x63VTqvQss8hp11i4wVV8bD44PvwucfZ2bU7gRe.json │ │ └── HgzT81VF1xZ3FT9Eq1pHhea7Wcfq2bv4tWTP3VvJ8Y9D.json ├── src │ ├── priority_fees.rs │ └── tx_utils.rs ├── .env.example ├── Dockerfile └── Cargo.toml ├── tip_payment_sdk ├── Cargo.toml └── src │ └── lib.rs ├── package.json ├── core ├── src │ ├── discriminators.rs │ ├── lib.rs │ ├── utils.rs │ ├── spl_stake_pool.rs │ ├── constants.rs │ └── epoch_marker.rs └── Cargo.toml ├── tip_distribution_sdk └── Cargo.toml ├── priority_fee_distribution_sdk └── Cargo.toml ├── shank_cli └── Cargo.toml ├── gcp_uploader ├── monitor-merkle-uploads.service └── Cargo.toml ├── .gitignore ├── lines.sh ├── LICENSE-MIT ├── format.sh ├── program ├── src │ ├── initialize_vault_registry.rs │ ├── admin_set_st_mint.rs │ ├── admin_set_new_admin.rs │ ├── register_vault.rs │ ├── initialize_ballot_box.rs │ ├── admin_set_config_fees.rs │ ├── admin_set_tie_breaker.rs │ ├── admin_register_st_mint.rs │ ├── initialize_epoch_state.rs │ ├── admin_set_weight.rs │ ├── realloc_vault_registry.rs │ ├── realloc_epoch_state.rs │ ├── initialize_base_reward_router.rs │ ├── realloc_ballot_box.rs │ ├── initialize_weight_table.rs │ └── realloc_base_reward_router.rs └── Cargo.toml ├── scripts └── update-attributes.js ├── .github └── workflows │ └── build-container-images.yaml └── pipe_test_output.sh /integration_tests/tests/helpers/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod serialized_accounts; 2 | -------------------------------------------------------------------------------- /cli/.gitignore: -------------------------------------------------------------------------------- 1 | .env 2 | .env.devnet 3 | .env.mainnet 4 | .env.docker 5 | .DS_Store -------------------------------------------------------------------------------- /integration_tests/tests/tests.rs: -------------------------------------------------------------------------------- 1 | mod fixtures; 2 | mod helpers; 3 | mod tip_router; 4 | -------------------------------------------------------------------------------- /cli/src/ported/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod error; 2 | pub mod submit_stats; 3 | pub mod transactions; 4 | -------------------------------------------------------------------------------- /cli/src/keeper/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod keeper_loop; 2 | pub mod keeper_metrics; 3 | pub mod keeper_state; 4 | -------------------------------------------------------------------------------- /integration_tests/tests/tip_router/bpf/mod.rs: -------------------------------------------------------------------------------- 1 | mod set_merkle_root; 2 | mod set_priority_fee_merkle_root; 3 | -------------------------------------------------------------------------------- /security_audits/certora.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jito-foundation/jito-tip-router/HEAD/security_audits/certora.pdf -------------------------------------------------------------------------------- /security_audits/offside.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jito-foundation/jito-tip-router/HEAD/security_audits/offside.pdf -------------------------------------------------------------------------------- /integration_tests/tests/fixtures/stake_pool.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jito-foundation/jito-tip-router/HEAD/integration_tests/tests/fixtures/stake_pool.so -------------------------------------------------------------------------------- /integration_tests/tests/fixtures/spl_stake_pool.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jito-foundation/jito-tip-router/HEAD/integration_tests/tests/fixtures/spl_stake_pool.so -------------------------------------------------------------------------------- /.dockerignore: -------------------------------------------------------------------------------- 1 | /target 2 | **/*.rs.bk 3 | 4 | .git 5 | .gitignore 6 | .github 7 | 8 | *.log 9 | *.md 10 | .vscode/ 11 | .idea/ 12 | *.iml 13 | node_modules/ 14 | -------------------------------------------------------------------------------- /integration_tests/tests/fixtures/jito_tip_payment.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jito-foundation/jito-tip-router/HEAD/integration_tests/tests/fixtures/jito_tip_payment.so -------------------------------------------------------------------------------- /rustfmt.toml: -------------------------------------------------------------------------------- 1 | edition = "2018" # required by rust-analyzer 2 | # imports_granularity="Crate" 3 | # format_code_in_doc_comments = true 4 | # group_imports = "StdExternalCrate" -------------------------------------------------------------------------------- /integration_tests/tests/fixtures/jito_vault_program.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jito-foundation/jito-tip-router/HEAD/integration_tests/tests/fixtures/jito_vault_program.so -------------------------------------------------------------------------------- /generate_client.sh: -------------------------------------------------------------------------------- 1 | #! /bin/zsh 2 | 3 | cargo b 4 | ./target/debug/jito-tip-router-shank-cli && yarn install && yarn generate-clients && cargo b 5 | cargo-build-sbf 6 | cargo fmt 7 | -------------------------------------------------------------------------------- /integration_tests/src/main.rs: -------------------------------------------------------------------------------- 1 | use jito_tip_router_client::programs::JITO_TIP_ROUTER_ID; 2 | 3 | pub fn main() { 4 | println!("Hello, world! {:?}", JITO_TIP_ROUTER_ID); 5 | } 6 | -------------------------------------------------------------------------------- /integration_tests/tests/fixtures/jito_restaking_program.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jito-foundation/jito-tip-router/HEAD/integration_tests/tests/fixtures/jito_restaking_program.so -------------------------------------------------------------------------------- /integration_tests/tests/fixtures/jito_tip_distribution.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jito-foundation/jito-tip-router/HEAD/integration_tests/tests/fixtures/jito_tip_distribution.so -------------------------------------------------------------------------------- /rust-toolchain.toml: -------------------------------------------------------------------------------- 1 | # note: this file doesn't play nicely with solana-verify build 2 | [toolchain] 3 | components = ["rustfmt", "rustc-dev", "clippy", "cargo"] 4 | channel = "1.89.0" 5 | -------------------------------------------------------------------------------- /meta_merkle_tree/src/lib.rs: -------------------------------------------------------------------------------- 1 | pub mod error; 2 | pub mod generated_merkle_tree; 3 | pub mod merkle_tree; 4 | pub mod meta_merkle_tree; 5 | pub mod tree_node; 6 | pub mod utils; 7 | pub mod verify; 8 | -------------------------------------------------------------------------------- /integration_tests/tests/fixtures/jito_priority_fee_distribution.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jito-foundation/jito-tip-router/HEAD/integration_tests/tests/fixtures/jito_priority_fee_distribution.so -------------------------------------------------------------------------------- /.cargo/programs.env: -------------------------------------------------------------------------------- 1 | TIP_ROUTER_PROGRAM_ID=RouterBmuRBkPUbgEDMtdvTZ75GBdSREZR5uGUxxxpb 2 | RESTAKING_PROGRAM_ID=RestkWeAVL8fRGgzhfeoqFhsqKRchg6aa1XrcH96z4Q 3 | VAULT_PROGRAM_ID=Vau1t6sLNxnzB7ZDsef8TLbPLfyZMYXH8WTNqUdm9g8 -------------------------------------------------------------------------------- /.config/nextest.toml: -------------------------------------------------------------------------------- 1 | [profile.default] 2 | # retries = { backoff = "exponential", count = 3, delay = "10ms", jitter = true, max-delay = "100ms" } 3 | test-threads = "num-cpus" 4 | threads-required = 1 5 | fail-fast = false -------------------------------------------------------------------------------- /docs/index.md: -------------------------------------------------------------------------------- 1 | # TipRouter Documentation Moved 2 | 3 | The TipRouter documentation has been moved to the [Jito Omnidocs](https://github.com/jito-foundation/jito-omnidocs/tree/master/tiprouter). 4 | 5 | **📖 View the documentation:** [jito.network/docs/tiprouter](https://jito.network/docs/tiprouter) -------------------------------------------------------------------------------- /.cargo/config.toml: -------------------------------------------------------------------------------- 1 | # placeholders for clippy and other tools 2 | # Program IDs may not be correct 3 | [env] 4 | TIP_ROUTER_PROGRAM_ID = "RouterBmuRBkPUbgEDMtdvTZ75GBdSREZR5uGUxxxpb" 5 | RESTAKING_PROGRAM_ID = "RestkWeAVL8fRGgzhfeoqFhsqKRchg6aa1XrcH96z4Q" 6 | VAULT_PROGRAM_ID = "Vau1t6sLNxnzB7ZDsef8TLbPLfyZMYXH8WTNqUdm9g8" -------------------------------------------------------------------------------- /cli/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![allow(clippy::future_not_send)] 2 | #![allow(clippy::cognitive_complexity)] 3 | #![allow(clippy::arithmetic_side_effects)] 4 | pub mod args; 5 | pub mod getters; 6 | pub mod handler; 7 | pub mod instructions; 8 | pub mod keeper; 9 | pub mod log; 10 | pub mod spl_stake_pool; 11 | // pub mod ported; 12 | -------------------------------------------------------------------------------- /clients/js/jito_tip_router/errors/index.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This code was AUTOGENERATED using the kinobi library. 3 | * Please DO NOT EDIT THIS FILE, instead use visitors 4 | * to add features, then rerun kinobi to update it. 5 | * 6 | * @see https://github.com/kinobi-so/kinobi 7 | */ 8 | 9 | export * from './jitoTipRouter'; 10 | -------------------------------------------------------------------------------- /clients/js/jito_tip_router/programs/index.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This code was AUTOGENERATED using the kinobi library. 3 | * Please DO NOT EDIT THIS FILE, instead use visitors 4 | * to add features, then rerun kinobi to update it. 5 | * 6 | * @see https://github.com/kinobi-so/kinobi 7 | */ 8 | 9 | export * from './jitoTipRouter'; 10 | -------------------------------------------------------------------------------- /clients/rust/jito_tip_router/src/generated/errors/mod.rs: -------------------------------------------------------------------------------- 1 | //! This code was AUTOGENERATED using the kinobi library. 2 | //! Please DO NOT EDIT THIS FILE, instead use visitors 3 | //! to add features, then rerun kinobi to update it. 4 | //! 5 | //! 6 | //! 7 | 8 | pub(crate) mod jito_tip_router; 9 | 10 | pub use self::jito_tip_router::JitoTipRouterError; 11 | -------------------------------------------------------------------------------- /cli/.env.example: -------------------------------------------------------------------------------- 1 | # Instructions 2 | # 3 | # Change this file to .env and move to your current working directory 4 | # 5 | 6 | # Network Settings 7 | RPC_URL= 8 | COMMITMENT= 9 | 10 | # Program IDs 11 | TIP_ROUTER_PROGRAM_ID= 12 | RESTAKING_PROGRAM_ID= 13 | VAULT_PROGRAM_ID= 14 | TIP_DISTRIBUTION_PROGRAM_ID= 15 | 16 | # Optional Settings 17 | # Path to your Solana keypair file (e.g., /home/user/.config/solana/id.json) 18 | KEYPAIR_PATH= -------------------------------------------------------------------------------- /clients/rust/jito_tip_router/src/generated/mod.rs: -------------------------------------------------------------------------------- 1 | //! This code was AUTOGENERATED using the kinobi library. 2 | //! Please DO NOT EDIT THIS FILE, instead use visitors 3 | //! to add features, then rerun kinobi to update it. 4 | //! 5 | //! 6 | //! 7 | 8 | pub mod accounts; 9 | pub mod errors; 10 | pub mod instructions; 11 | pub mod programs; 12 | pub mod types; 13 | 14 | pub(crate) use programs::*; 15 | -------------------------------------------------------------------------------- /clients/js/jito_tip_router/index.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This code was AUTOGENERATED using the kinobi library. 3 | * Please DO NOT EDIT THIS FILE, instead use visitors 4 | * to add features, then rerun kinobi to update it. 5 | * 6 | * @see https://github.com/kinobi-so/kinobi 7 | */ 8 | 9 | export * from './accounts'; 10 | export * from './errors'; 11 | export * from './instructions'; 12 | export * from './programs'; 13 | export * from './types'; 14 | -------------------------------------------------------------------------------- /tip-router-operator-cli/tests/fixtures/accounts/3AVi9Tg9Uo68tJfuvoKvqKNWKkC5wPdSSdeBnizKZ6jT.json: -------------------------------------------------------------------------------- 1 | { 2 | "pubkey": "3AVi9Tg9Uo68tJfuvoKvqKNWKkC5wPdSSdeBnizKZ6jT", 3 | "account": { 4 | "lamports": 22451877, 5 | "data": [ 6 | "ySH0dOBEYSg=", 7 | "base64" 8 | ], 9 | "owner": "T1pyyaTNZsKv2WcRAB8oVnk93mLJw2XzjtVYqCsaHqt", 10 | "executable": false, 11 | "rentEpoch": 18446744073709551615, 12 | "space": 8 13 | } 14 | } -------------------------------------------------------------------------------- /tip-router-operator-cli/tests/fixtures/accounts/96gYZGLnJYVFmbjzopPSU6QiEV5fGqZNyN9nmNhvrZU5.json: -------------------------------------------------------------------------------- 1 | { 2 | "pubkey": "96gYZGLnJYVFmbjzopPSU6QiEV5fGqZNyN9nmNhvrZU5", 3 | "account": { 4 | "lamports": 6711717, 5 | "data": [ 6 | "ySH0dOBEYSg=", 7 | "base64" 8 | ], 9 | "owner": "T1pyyaTNZsKv2WcRAB8oVnk93mLJw2XzjtVYqCsaHqt", 10 | "executable": false, 11 | "rentEpoch": 18446744073709551615, 12 | "space": 8 13 | } 14 | } -------------------------------------------------------------------------------- /tip-router-operator-cli/tests/fixtures/accounts/ADaUMid9yfUytqMBgopwjb2DTLSokTSzL1zt6iGPaS49.json: -------------------------------------------------------------------------------- 1 | { 2 | "pubkey": "ADaUMid9yfUytqMBgopwjb2DTLSokTSzL1zt6iGPaS49", 3 | "account": { 4 | "lamports": 33817805, 5 | "data": [ 6 | "ySH0dOBEYSg=", 7 | "base64" 8 | ], 9 | "owner": "T1pyyaTNZsKv2WcRAB8oVnk93mLJw2XzjtVYqCsaHqt", 10 | "executable": false, 11 | "rentEpoch": 18446744073709551615, 12 | "space": 8 13 | } 14 | } -------------------------------------------------------------------------------- /tip-router-operator-cli/tests/fixtures/accounts/ADuUkR4vqLUMWXxW9gh6D6L8pMSawimctcNZ5pGwDcEt.json: -------------------------------------------------------------------------------- 1 | { 2 | "pubkey": "ADuUkR4vqLUMWXxW9gh6D6L8pMSawimctcNZ5pGwDcEt", 3 | "account": { 4 | "lamports": 33328288, 5 | "data": [ 6 | "ySH0dOBEYSg=", 7 | "base64" 8 | ], 9 | "owner": "T1pyyaTNZsKv2WcRAB8oVnk93mLJw2XzjtVYqCsaHqt", 10 | "executable": false, 11 | "rentEpoch": 18446744073709551615, 12 | "space": 8 13 | } 14 | } -------------------------------------------------------------------------------- /tip-router-operator-cli/tests/fixtures/accounts/Cw8CFyM9FkoMi7K7Crf6HNQqf4uEMzpKw6QNghXLvLkY.json: -------------------------------------------------------------------------------- 1 | { 2 | "pubkey": "Cw8CFyM9FkoMi7K7Crf6HNQqf4uEMzpKw6QNghXLvLkY", 3 | "account": { 4 | "lamports": 65503932, 5 | "data": [ 6 | "ySH0dOBEYSg=", 7 | "base64" 8 | ], 9 | "owner": "T1pyyaTNZsKv2WcRAB8oVnk93mLJw2XzjtVYqCsaHqt", 10 | "executable": false, 11 | "rentEpoch": 18446744073709551615, 12 | "space": 8 13 | } 14 | } -------------------------------------------------------------------------------- /tip-router-operator-cli/tests/fixtures/accounts/DfXygSm4jCyNCybVYYK6DwvWqjKee8pbDmJGcLWNDXjh.json: -------------------------------------------------------------------------------- 1 | { 2 | "pubkey": "DfXygSm4jCyNCybVYYK6DwvWqjKee8pbDmJGcLWNDXjh", 3 | "account": { 4 | "lamports": 29183078, 5 | "data": [ 6 | "ySH0dOBEYSg=", 7 | "base64" 8 | ], 9 | "owner": "T1pyyaTNZsKv2WcRAB8oVnk93mLJw2XzjtVYqCsaHqt", 10 | "executable": false, 11 | "rentEpoch": 18446744073709551615, 12 | "space": 8 13 | } 14 | } -------------------------------------------------------------------------------- /tip-router-operator-cli/tests/fixtures/accounts/DttWaMuVvTiduZRnguLF7jNxTgiMBZ1hyAumKUiL2KRL.json: -------------------------------------------------------------------------------- 1 | { 2 | "pubkey": "DttWaMuVvTiduZRnguLF7jNxTgiMBZ1hyAumKUiL2KRL", 3 | "account": { 4 | "lamports": 20696209, 5 | "data": [ 6 | "ySH0dOBEYSg=", 7 | "base64" 8 | ], 9 | "owner": "T1pyyaTNZsKv2WcRAB8oVnk93mLJw2XzjtVYqCsaHqt", 10 | "executable": false, 11 | "rentEpoch": 18446744073709551615, 12 | "space": 8 13 | } 14 | } -------------------------------------------------------------------------------- /tip-router-operator-cli/tests/fixtures/accounts/HFqU5x63VTqvQss8hp11i4wVV8bD44PvwucfZ2bU7gRe.json: -------------------------------------------------------------------------------- 1 | { 2 | "pubkey": "HFqU5x63VTqvQss8hp11i4wVV8bD44PvwucfZ2bU7gRe", 3 | "account": { 4 | "lamports": 14898352, 5 | "data": [ 6 | "ySH0dOBEYSg=", 7 | "base64" 8 | ], 9 | "owner": "T1pyyaTNZsKv2WcRAB8oVnk93mLJw2XzjtVYqCsaHqt", 10 | "executable": false, 11 | "rentEpoch": 18446744073709551615, 12 | "space": 8 13 | } 14 | } -------------------------------------------------------------------------------- /clients/rust/jito_tip_router/src/generated/programs.rs: -------------------------------------------------------------------------------- 1 | //! This code was AUTOGENERATED using the kinobi library. 2 | //! Please DO NOT EDIT THIS FILE, instead use visitors 3 | //! to add features, then rerun kinobi to update it. 4 | //! 5 | //! 6 | //! 7 | 8 | use solana_program::{pubkey, pubkey::Pubkey}; 9 | 10 | /// `jito_tip_router` program ID. 11 | pub const JITO_TIP_ROUTER_ID: Pubkey = pubkey!("RouterBmuRBkPUbgEDMtdvTZ75GBdSREZR5uGUxxxpb"); 12 | -------------------------------------------------------------------------------- /meta_merkle_tree/src/error.rs: -------------------------------------------------------------------------------- 1 | use thiserror::Error; 2 | 3 | #[derive(Error, Debug)] 4 | pub enum MerkleTreeError { 5 | #[error("Merkle Tree Validation Error: {0}")] 6 | MerkleValidationError(String), 7 | #[error("Merkle Root Error")] 8 | MerkleRootError, 9 | #[error("io Error: {0}")] 10 | IoError(#[from] std::io::Error), 11 | #[error("Serde Error: {0}")] 12 | SerdeError(#[from] serde_json::Error), 13 | #[error("Arithmetic Overflow/Underflow")] 14 | ArithmeticOverflow, 15 | } 16 | -------------------------------------------------------------------------------- /tip-router-operator-cli/tests/fixtures/accounts/HgzT81VF1xZ3FT9Eq1pHhea7Wcfq2bv4tWTP3VvJ8Y9D.json: -------------------------------------------------------------------------------- 1 | { 2 | "pubkey": "HgzT81VF1xZ3FT9Eq1pHhea7Wcfq2bv4tWTP3VvJ8Y9D", 3 | "account": { 4 | "lamports": 1610320, 5 | "data": [ 6 | "mwyq4B76zILhntA4KYK2ANRc8Zuy6hHbEcNai43yFtaHdRx9KLeNRQnmpbDQtMdTFDNgMx+GGjks50YIdL4vjXWXzmum44qpBQAAAAAAAAD+//7///z//P8=", 7 | "base64" 8 | ], 9 | "owner": "T1pyyaTNZsKv2WcRAB8oVnk93mLJw2XzjtVYqCsaHqt", 10 | "executable": false, 11 | "rentEpoch": 18446744073709551615, 12 | "space": 89 13 | } 14 | } -------------------------------------------------------------------------------- /tip_payment_sdk/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "jito-tip-payment-sdk" 3 | description = "SDK for interacting with Jito's Tip Payment Program" 4 | version = { workspace = true } 5 | authors = { workspace = true } 6 | repository = { workspace = true } 7 | homepage = { workspace = true } 8 | license = { workspace = true } 9 | edition = { workspace = true } 10 | readme = { workspace = true } 11 | 12 | [dependencies] 13 | anyhow = { workspace = true } 14 | borsh = { workspace = true } 15 | solana-program = { workspace = true } 16 | solana-pubkey = { workspace = true } 17 | -------------------------------------------------------------------------------- /clients/rust/jito_tip_router/src/generated/types/fee.rs: -------------------------------------------------------------------------------- 1 | //! This code was AUTOGENERATED using the kinobi library. 2 | //! Please DO NOT EDIT THIS FILE, instead use visitors 3 | //! to add features, then rerun kinobi to update it. 4 | //! 5 | //! 6 | //! 7 | 8 | use borsh::BorshDeserialize; 9 | use borsh::BorshSerialize; 10 | 11 | #[derive(BorshSerialize, BorshDeserialize, Clone, Debug, Eq, PartialEq)] 12 | #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] 13 | pub struct Fee { 14 | pub fee: u16, 15 | } 16 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "jito-tip-router", 3 | "version": "0.0.1", 4 | "description": "Jito MEV Tip Distribution NCN", 5 | "dependencies": { 6 | "@exo-tech-xyz/nodes-from-anchor": "0.21.3", 7 | "@exo-tech-xyz/renderers": "0.21.4", 8 | "@exo-tech-xyz/renderers-js-umi": "0.21.7", 9 | "@exo-tech-xyz/renderers-rust": "0.21.8", 10 | "corepack": "^0.29.3", 11 | "kinobi": "^0.21.1", 12 | "yarn": "2.4.3" 13 | }, 14 | "scripts": { 15 | "generate-clients": "node scripts/generate-clients.js && node scripts/update-attributes.js" 16 | } 17 | } -------------------------------------------------------------------------------- /core/src/discriminators.rs: -------------------------------------------------------------------------------- 1 | #[repr(u8)] 2 | pub enum Discriminators { 3 | // Configs 4 | Config = 0x01, 5 | VaultRegistry = 0x02, 6 | 7 | // Snapshots 8 | WeightTable = 0x10, 9 | EpochSnapshot = 0x11, 10 | OperatorSnapshot = 0x12, 11 | 12 | // Voting 13 | BallotBox = 0x20, 14 | 15 | // Validation and Consensus 16 | // - Reserved for future use 17 | 18 | // Distribution 19 | BaseRewardRouter = 0x40, 20 | NcnRewardRouter = 0x41, 21 | 22 | // State Tracking 23 | EpochState = 0x50, 24 | EpochMarker = 0x51, 25 | } 26 | -------------------------------------------------------------------------------- /clients/rust/jito_tip_router/src/generated/types/base_fee_group.rs: -------------------------------------------------------------------------------- 1 | //! This code was AUTOGENERATED using the kinobi library. 2 | //! Please DO NOT EDIT THIS FILE, instead use visitors 3 | //! to add features, then rerun kinobi to update it. 4 | //! 5 | //! 6 | //! 7 | 8 | use borsh::BorshDeserialize; 9 | use borsh::BorshSerialize; 10 | 11 | #[derive(BorshSerialize, BorshDeserialize, Clone, Debug, Eq, PartialEq)] 12 | #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] 13 | pub struct BaseFeeGroup { 14 | pub group: u8, 15 | } 16 | -------------------------------------------------------------------------------- /clients/rust/jito_tip_router/src/generated/types/ncn_fee_group.rs: -------------------------------------------------------------------------------- 1 | //! This code was AUTOGENERATED using the kinobi library. 2 | //! Please DO NOT EDIT THIS FILE, instead use visitors 3 | //! to add features, then rerun kinobi to update it. 4 | //! 5 | //! 6 | //! 7 | 8 | use borsh::BorshDeserialize; 9 | use borsh::BorshSerialize; 10 | 11 | #[derive(BorshSerialize, BorshDeserialize, Clone, Debug, Eq, PartialEq)] 12 | #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] 13 | pub struct NcnFeeGroup { 14 | pub group: u8, 15 | } 16 | -------------------------------------------------------------------------------- /tip_distribution_sdk/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "jito-tip-distribution-sdk" 3 | description = "SDK for interacting with Jito's Tip Distribution Program via declare_program" 4 | version = { workspace = true } 5 | authors = { workspace = true } 6 | repository = { workspace = true } 7 | homepage = { workspace = true } 8 | license = { workspace = true } 9 | edition = { workspace = true } 10 | readme = { workspace = true } 11 | 12 | [dependencies] 13 | anyhow = { workspace = true } 14 | borsh = { workspace = true } 15 | solana-program = { workspace = true } 16 | solana-pubkey = { workspace = true } 17 | -------------------------------------------------------------------------------- /clients/rust/jito_tip_router/src/generated/types/ncn_fee_group_weight.rs: -------------------------------------------------------------------------------- 1 | //! This code was AUTOGENERATED using the kinobi library. 2 | //! Please DO NOT EDIT THIS FILE, instead use visitors 3 | //! to add features, then rerun kinobi to update it. 4 | //! 5 | //! 6 | //! 7 | 8 | use borsh::BorshDeserialize; 9 | use borsh::BorshSerialize; 10 | 11 | #[derive(BorshSerialize, BorshDeserialize, Clone, Debug, Eq, PartialEq)] 12 | #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] 13 | pub struct NcnFeeGroupWeight { 14 | pub weight: u128, 15 | } 16 | -------------------------------------------------------------------------------- /core/src/lib.rs: -------------------------------------------------------------------------------- 1 | pub mod account_payer; 2 | pub mod ballot_box; 3 | pub mod base_fee_group; 4 | pub mod base_reward_router; 5 | pub mod config; 6 | pub mod constants; 7 | pub mod discriminators; 8 | pub mod epoch_marker; 9 | pub mod epoch_snapshot; 10 | pub mod epoch_state; 11 | pub mod error; 12 | pub mod fees; 13 | pub mod instruction; 14 | pub mod loaders; 15 | pub mod ncn_fee_group; 16 | pub mod ncn_reward_router; 17 | pub mod spl_stake_pool; 18 | pub mod stake_weight; 19 | pub mod utils; 20 | pub mod vault_registry; 21 | pub mod weight_entry; 22 | pub mod weight_table; 23 | 24 | pub use constants::ID; 25 | -------------------------------------------------------------------------------- /clients/rust/jito_tip_router/src/generated/types/base_reward_router_rewards.rs: -------------------------------------------------------------------------------- 1 | //! This code was AUTOGENERATED using the kinobi library. 2 | //! Please DO NOT EDIT THIS FILE, instead use visitors 3 | //! to add features, then rerun kinobi to update it. 4 | //! 5 | //! 6 | //! 7 | 8 | use borsh::BorshDeserialize; 9 | use borsh::BorshSerialize; 10 | 11 | #[derive(BorshSerialize, BorshDeserialize, Clone, Debug, Eq, PartialEq)] 12 | #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] 13 | pub struct BaseRewardRouterRewards { 14 | pub rewards: u64, 15 | } 16 | -------------------------------------------------------------------------------- /priority_fee_distribution_sdk/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "jito-priority-fee-distribution-sdk" 3 | description = "SDK for interacting with Jito's Priority Fee Distribution Program via declare_program" 4 | version = { workspace = true } 5 | authors = { workspace = true } 6 | repository = { workspace = true } 7 | homepage = { workspace = true } 8 | license = { workspace = true } 9 | edition = { workspace = true } 10 | readme = { workspace = true } 11 | 12 | [dependencies] 13 | anyhow = { workspace = true } 14 | borsh = { workspace = true } 15 | solana-program = { workspace = true } 16 | solana-pubkey = { workspace = true } 17 | -------------------------------------------------------------------------------- /clients/rust/jito_tip_router/src/generated/types/progress.rs: -------------------------------------------------------------------------------- 1 | //! This code was AUTOGENERATED using the kinobi library. 2 | //! Please DO NOT EDIT THIS FILE, instead use visitors 3 | //! to add features, then rerun kinobi to update it. 4 | //! 5 | //! 6 | //! 7 | 8 | use borsh::BorshDeserialize; 9 | use borsh::BorshSerialize; 10 | 11 | #[derive(BorshSerialize, BorshDeserialize, Clone, Debug, Eq, PartialEq)] 12 | #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] 13 | pub struct Progress { 14 | pub tally: u64, 15 | pub total: u64, 16 | pub reserved: [u8; 8], 17 | } 18 | -------------------------------------------------------------------------------- /shank_cli/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "jito-tip-router-shank-cli" 3 | description = "Jito MEV Tip Distribution Shank CLI" 4 | version = { workspace = true } 5 | authors = { workspace = true } 6 | repository = { workspace = true } 7 | homepage = { workspace = true } 8 | license = { workspace = true } 9 | edition = { workspace = true } 10 | readme = { workspace = true } 11 | 12 | [dependencies] 13 | anyhow = { workspace = true } 14 | clap = { workspace = true } 15 | env_logger = { workspace = true } 16 | envfile = { workspace = true } 17 | log = { workspace = true } 18 | shank = { workspace = true } 19 | shank_idl = { workspace = true } 20 | -------------------------------------------------------------------------------- /clients/js/jito_tip_router/accounts/index.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This code was AUTOGENERATED using the kinobi library. 3 | * Please DO NOT EDIT THIS FILE, instead use visitors 4 | * to add features, then rerun kinobi to update it. 5 | * 6 | * @see https://github.com/kinobi-so/kinobi 7 | */ 8 | 9 | export * from './ballotBox'; 10 | export * from './baseRewardRouter'; 11 | export * from './config'; 12 | export * from './epochMarker'; 13 | export * from './epochSnapshot'; 14 | export * from './epochState'; 15 | export * from './ncnRewardRouter'; 16 | export * from './operatorSnapshot'; 17 | export * from './vaultRegistry'; 18 | export * from './weightTable'; 19 | -------------------------------------------------------------------------------- /cli/src/ported/submit_stats.rs: -------------------------------------------------------------------------------- 1 | use super::error::JitoSendTransactionError; 2 | 3 | #[derive(Debug, Default, Clone)] 4 | pub struct SubmitStats { 5 | pub successes: u64, 6 | pub errors: u64, 7 | pub results: Vec>, 8 | } 9 | 10 | impl SubmitStats { 11 | pub fn combine(&mut self, other: &SubmitStats) { 12 | self.successes += other.successes; 13 | self.errors += other.errors; 14 | self.results.extend(other.results.clone()) 15 | } 16 | } 17 | #[derive(Debug, Default, Clone)] 18 | pub struct CreateUpdateStats { 19 | pub creates: SubmitStats, 20 | pub updates: SubmitStats, 21 | } 22 | -------------------------------------------------------------------------------- /meta_merkle_tree/src/utils.rs: -------------------------------------------------------------------------------- 1 | use crate::merkle_tree::MerkleTree; 2 | 3 | pub fn get_proof(merkle_tree: &MerkleTree, index: usize) -> Vec<[u8; 32]> { 4 | let mut proof = Vec::new(); 5 | let path = merkle_tree.find_path(index).expect("path to index"); 6 | for branch in path.get_proof_entries() { 7 | if let Some(hash) = branch.get_left_sibling() { 8 | proof.push(hash.to_bytes()); 9 | } else if let Some(hash) = branch.get_right_sibling() { 10 | proof.push(hash.to_bytes()); 11 | } else { 12 | panic!("expected some hash at each level of the tree"); 13 | } 14 | } 15 | proof 16 | } 17 | -------------------------------------------------------------------------------- /gcp_uploader/monitor-merkle-uploads.service: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=Monitor and Upload Epoch Merkle Tree and Stake Meta Files 3 | After=network.target 4 | 5 | [Service] 6 | Type=simple 7 | User=core 8 | ExecStart=/home/core/jito-tip-router/target/release/gcp_uploader \ 9 | --directory /solana/snapshots/operator-saves \ 10 | --snapshot-directory /solana/snapshots/autosnapshot \ 11 | --cluster mainnet \ 12 | --interval 600 13 | Restart=always 14 | RestartSec=10 15 | Environment="RUST_LOG=info" 16 | Environment="GCLOUD_PATH=/usr/bin/gcloud" 17 | Environment="SOLANA_METRICS_CONFIG=" 18 | Environment="RPC_URL=" 19 | 20 | [Install] 21 | WantedBy=multi-user.target 22 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | 3 | .DS_Store 4 | .developer 5 | .env 6 | .cursorrules 7 | node_modules 8 | test-ledger 9 | tip-router-operator-cli/scripts/test-validator-keys 10 | tip-router-operator-cli/scripts/validators.txt 11 | tip-router-operator-cli/tests/fixtures/local_validators.txt 12 | tip-router-operator-cli/tests/fixtures/keys/ 13 | tip-router-operator-cli/tests/fixtures/tda-accounts/ 14 | 15 | # Debugging 16 | program_errors.json 17 | test_errors.output 18 | tests.output 19 | integration_tests/tests/fixtures/jito_tip_router_program.so 20 | notes.md 21 | 22 | integration_tests/tests/fixtures/jito_tip_router_program-keypair.json 23 | lcov.info 24 | 25 | credentials/ 26 | 27 | .DS_Store -------------------------------------------------------------------------------- /clients/rust/jito_tip_router/src/generated/types/stake_weights.rs: -------------------------------------------------------------------------------- 1 | //! This code was AUTOGENERATED using the kinobi library. 2 | //! Please DO NOT EDIT THIS FILE, instead use visitors 3 | //! to add features, then rerun kinobi to update it. 4 | //! 5 | //! 6 | //! 7 | 8 | use crate::generated::types::NcnFeeGroupWeight; 9 | use borsh::BorshDeserialize; 10 | use borsh::BorshSerialize; 11 | 12 | #[derive(BorshSerialize, BorshDeserialize, Clone, Debug, Eq, PartialEq)] 13 | #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] 14 | pub struct StakeWeights { 15 | pub stake_weight: u128, 16 | pub ncn_fee_group_stake_weights: [NcnFeeGroupWeight; 8], 17 | } 18 | -------------------------------------------------------------------------------- /tip-router-operator-cli/src/priority_fees.rs: -------------------------------------------------------------------------------- 1 | use solana_compute_budget_interface::ComputeBudgetInstruction; 2 | use solana_sdk::instruction::Instruction; 3 | 4 | pub fn configure_instruction( 5 | instruction: Instruction, 6 | compute_unit_price: u64, 7 | maybe_compute_unit_limit: Option, 8 | ) -> Vec { 9 | let mut instructions = Vec::new(); 10 | if let Some(limit) = maybe_compute_unit_limit { 11 | instructions.push(ComputeBudgetInstruction::set_compute_unit_limit(limit)); 12 | } 13 | instructions.push(ComputeBudgetInstruction::set_compute_unit_price( 14 | compute_unit_price, 15 | )); 16 | instructions.push(instruction); 17 | instructions 18 | } 19 | -------------------------------------------------------------------------------- /clients/rust/jito_tip_router/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![allow(clippy::all)] 2 | #![allow(deprecated)] 3 | #![allow(clippy::nursery)] 4 | #![allow(clippy::integer_division)] 5 | #![allow(clippy::arithmetic_side_effects)] 6 | #![allow(clippy::style)] 7 | #![allow(clippy::perf)] 8 | mod generated; 9 | 10 | use generated::*; 11 | 12 | pub mod accounts { 13 | pub use super::generated::accounts::*; 14 | } 15 | 16 | pub mod instructions { 17 | pub use super::generated::instructions::*; 18 | } 19 | 20 | pub mod errors { 21 | pub use super::generated::errors::*; 22 | } 23 | 24 | pub mod types { 25 | pub use super::generated::types::*; 26 | } 27 | 28 | pub mod programs { 29 | pub use super::generated::programs::*; 30 | } 31 | -------------------------------------------------------------------------------- /gcp_uploader/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "gcp_uploader" 3 | version = "0.1.0" 4 | edition = "2021" 5 | authors = ["Your Name "] 6 | description = "A tool to continuously monitor and upload epoch-related files to Google Cloud Storage" 7 | 8 | [dependencies] 9 | anyhow = { workspace = true } 10 | clap = { workspace = true, features = ["derive", "env"] } 11 | cloud-storage = "0.11" 12 | env_logger = { workspace = true } 13 | futures-util = "0.3.31" 14 | hostname = "0.3" 15 | log = { workspace = true } 16 | regex = "1.10" 17 | serde_json = { workspace = true } 18 | solana-client = { workspace = true } 19 | solana-metrics = { workspace = true } 20 | tokio = { workspace = true, features = ["full"] } 21 | -------------------------------------------------------------------------------- /clients/rust/jito_tip_router/src/generated/types/stake_pool_instruction.rs: -------------------------------------------------------------------------------- 1 | //! This code was AUTOGENERATED using the kinobi library. 2 | //! Please DO NOT EDIT THIS FILE, instead use visitors 3 | //! to add features, then rerun kinobi to update it. 4 | //! 5 | //! 6 | //! 7 | 8 | use borsh::BorshDeserialize; 9 | use borsh::BorshSerialize; 10 | 11 | #[derive(BorshSerialize, BorshDeserialize, Clone, Debug, Eq, PartialEq)] 12 | #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] 13 | pub enum StakePoolInstruction { 14 | DepositSolWithSlippage { 15 | lamports_in: u64, 16 | minimum_pool_tokens_out: u64, 17 | }, 18 | DepositSol(u64), 19 | } 20 | -------------------------------------------------------------------------------- /clients/rust/jito_tip_router/src/generated/types/ballot.rs: -------------------------------------------------------------------------------- 1 | //! This code was AUTOGENERATED using the kinobi library. 2 | //! Please DO NOT EDIT THIS FILE, instead use visitors 3 | //! to add features, then rerun kinobi to update it. 4 | //! 5 | //! 6 | //! 7 | 8 | use borsh::BorshDeserialize; 9 | use borsh::BorshSerialize; 10 | 11 | #[derive(BorshSerialize, BorshDeserialize, Clone, Debug, Eq, PartialEq)] 12 | #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] 13 | pub struct Ballot { 14 | pub meta_merkle_root: [u8; 32], 15 | pub is_valid: bool, 16 | #[cfg_attr(feature = "serde", serde(with = "serde_big_array::BigArray"))] 17 | pub reserved: [u8; 63], 18 | } 19 | -------------------------------------------------------------------------------- /clients/rust/jito_tip_router/src/generated/types/ballot_tally.rs: -------------------------------------------------------------------------------- 1 | //! This code was AUTOGENERATED using the kinobi library. 2 | //! Please DO NOT EDIT THIS FILE, instead use visitors 3 | //! to add features, then rerun kinobi to update it. 4 | //! 5 | //! 6 | //! 7 | 8 | use crate::generated::types::Ballot; 9 | use crate::generated::types::StakeWeights; 10 | use borsh::BorshDeserialize; 11 | use borsh::BorshSerialize; 12 | 13 | #[derive(BorshSerialize, BorshDeserialize, Clone, Debug, Eq, PartialEq)] 14 | #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] 15 | pub struct BallotTally { 16 | pub index: u16, 17 | pub ballot: Ballot, 18 | pub stake_weights: StakeWeights, 19 | pub tally: u64, 20 | } 21 | -------------------------------------------------------------------------------- /clients/rust/jito_tip_router/src/generated/types/config_admin_role.rs: -------------------------------------------------------------------------------- 1 | //! This code was AUTOGENERATED using the kinobi library. 2 | //! Please DO NOT EDIT THIS FILE, instead use visitors 3 | //! to add features, then rerun kinobi to update it. 4 | //! 5 | //! 6 | //! 7 | 8 | use borsh::BorshDeserialize; 9 | use borsh::BorshSerialize; 10 | use num_derive::FromPrimitive; 11 | 12 | #[derive( 13 | BorshSerialize, 14 | BorshDeserialize, 15 | Clone, 16 | Debug, 17 | Eq, 18 | PartialEq, 19 | Copy, 20 | PartialOrd, 21 | Hash, 22 | FromPrimitive, 23 | )] 24 | #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] 25 | pub enum ConfigAdminRole { 26 | FeeAdmin, 27 | TieBreakerAdmin, 28 | } 29 | -------------------------------------------------------------------------------- /integration_tests/tests/tip_router/switchboard_set_weight.rs: -------------------------------------------------------------------------------- 1 | #[cfg(test)] 2 | mod tests { 3 | 4 | use crate::fixtures::{test_builder::TestBuilder, TestResult}; 5 | 6 | #[tokio::test] 7 | async fn test_switchboard_set_weight_ok() -> TestResult<()> { 8 | let mut fixture = TestBuilder::new().await; 9 | 10 | const OPERATOR_COUNT: usize = 1; 11 | const VAULT_COUNT: usize = 1; 12 | 13 | let test_ncn = fixture 14 | .create_initial_test_ncn(OPERATOR_COUNT, VAULT_COUNT, None) 15 | .await?; 16 | 17 | fixture.add_epoch_state_for_test_ncn(&test_ncn).await?; 18 | 19 | fixture 20 | .add_switchboard_weights_for_test_ncn(&test_ncn) 21 | .await?; 22 | 23 | Ok(()) 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /clients/rust/jito_tip_router/src/generated/types/vault_reward_route.rs: -------------------------------------------------------------------------------- 1 | //! This code was AUTOGENERATED using the kinobi library. 2 | //! Please DO NOT EDIT THIS FILE, instead use visitors 3 | //! to add features, then rerun kinobi to update it. 4 | //! 5 | //! 6 | //! 7 | 8 | use borsh::BorshDeserialize; 9 | use borsh::BorshSerialize; 10 | use solana_program::pubkey::Pubkey; 11 | 12 | #[derive(BorshSerialize, BorshDeserialize, Clone, Debug, Eq, PartialEq)] 13 | #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] 14 | pub struct VaultRewardRoute { 15 | #[cfg_attr( 16 | feature = "serde", 17 | serde(with = "serde_with::As::") 18 | )] 19 | pub vault: Pubkey, 20 | pub rewards: u64, 21 | } 22 | -------------------------------------------------------------------------------- /integration_tests/tests/tip_router/mod.rs: -------------------------------------------------------------------------------- 1 | mod admin_set_parameters; 2 | mod admin_set_st_mint; 3 | mod admin_update_weight_table; 4 | mod bpf; 5 | mod cast_vote; 6 | mod close_epoch_accounts; 7 | mod distribute_rewards; 8 | mod epoch_state; 9 | mod initialize_ballot_box; 10 | mod initialize_base_reward_router; 11 | mod initialize_config; 12 | mod initialize_epoch_snapshot; 13 | mod initialize_ncn_reward_router; 14 | mod initialize_operator_snapshot; 15 | mod initialize_vault_registry; 16 | mod initialize_weight_table; 17 | mod meta_tests; 18 | mod register_vault; 19 | mod restaking_variations; 20 | mod set_config_fees; 21 | mod set_new_admin; 22 | mod set_tie_breaker; 23 | mod set_tracked_mint_ncn_fee_group; 24 | mod simulation_tests; 25 | mod snapshot_vault_operator_delegation; 26 | mod switchboard_set_weight; 27 | -------------------------------------------------------------------------------- /clients/rust/jito_tip_router/src/generated/types/weight_entry.rs: -------------------------------------------------------------------------------- 1 | //! This code was AUTOGENERATED using the kinobi library. 2 | //! Please DO NOT EDIT THIS FILE, instead use visitors 3 | //! to add features, then rerun kinobi to update it. 4 | //! 5 | //! 6 | //! 7 | 8 | use crate::generated::types::StMintEntry; 9 | use borsh::BorshDeserialize; 10 | use borsh::BorshSerialize; 11 | 12 | #[derive(BorshSerialize, BorshDeserialize, Clone, Debug, Eq, PartialEq)] 13 | #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] 14 | pub struct WeightEntry { 15 | pub st_mint_entry: StMintEntry, 16 | pub weight: u128, 17 | pub slot_set: u64, 18 | pub slot_updated: u64, 19 | #[cfg_attr(feature = "serde", serde(with = "serde_big_array::BigArray"))] 20 | pub reserved: [u8; 128], 21 | } 22 | -------------------------------------------------------------------------------- /core/src/utils.rs: -------------------------------------------------------------------------------- 1 | use solana_program::program_error::ProgramError; 2 | 3 | use crate::{constants::MAX_REALLOC_BYTES, error::TipRouterError}; 4 | 5 | /// Calculate new size for reallocation, capped at target size 6 | /// Returns the minimum of (current_size + MAX_REALLOC_BYTES) and target_size 7 | pub fn get_new_size(current_size: usize, target_size: usize) -> Result { 8 | Ok(current_size 9 | .checked_add(MAX_REALLOC_BYTES as usize) 10 | .ok_or(ProgramError::ArithmeticOverflow)? 11 | .min(target_size)) 12 | } 13 | 14 | #[inline(always)] 15 | #[track_caller] 16 | pub fn assert_tip_router_error( 17 | test_error: Result, 18 | tip_router_error: TipRouterError, 19 | ) { 20 | assert!(test_error.is_err()); 21 | assert_eq!(test_error.err().unwrap(), tip_router_error); 22 | } 23 | -------------------------------------------------------------------------------- /integration_tests/tests/tip_router/initialize_epoch_snapshot.rs: -------------------------------------------------------------------------------- 1 | #[cfg(test)] 2 | mod tests { 3 | 4 | use crate::fixtures::{test_builder::TestBuilder, TestResult}; 5 | 6 | #[tokio::test] 7 | async fn test_initialize_epoch_snapshot_ok() -> TestResult<()> { 8 | let mut fixture = TestBuilder::new().await; 9 | let mut tip_router_client = fixture.tip_router_client(); 10 | 11 | let test_ncn = fixture.create_initial_test_ncn(1, 1, None).await?; 12 | fixture.add_epoch_state_for_test_ncn(&test_ncn).await?; 13 | fixture.add_admin_weights_for_test_ncn(&test_ncn).await?; 14 | 15 | let epoch = fixture.clock().await.epoch; 16 | 17 | tip_router_client 18 | .do_initialize_epoch_snapshot(test_ncn.ncn_root.ncn_pubkey, epoch) 19 | .await?; 20 | 21 | Ok(()) 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /clients/rust/jito_tip_router/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "jito-tip-router-client" 3 | description = "Jito MEV Tip Distribution NCN Client" 4 | version = { workspace = true } 5 | authors = { workspace = true } 6 | repository = { workspace = true } 7 | homepage = { workspace = true } 8 | license = { workspace = true } 9 | edition = { workspace = true } 10 | readme = { workspace = true } 11 | 12 | [features] 13 | serde = [] 14 | anchor = [] 15 | anchor-idl-build = [] 16 | 17 | [dependencies] 18 | borsh = { workspace = true } 19 | bytemuck = { workspace = true } 20 | num-derive = { workspace = true } 21 | num-traits = { workspace = true } 22 | serde = { workspace = true } 23 | serde-big-array = { workspace = true } 24 | serde_with = { workspace = true } 25 | solana-program = { workspace = true } 26 | solana-sdk = { workspace = true } 27 | thiserror = { workspace = true } 28 | -------------------------------------------------------------------------------- /clients/rust/jito_tip_router/src/generated/types/fees.rs: -------------------------------------------------------------------------------- 1 | //! This code was AUTOGENERATED using the kinobi library. 2 | //! Please DO NOT EDIT THIS FILE, instead use visitors 3 | //! to add features, then rerun kinobi to update it. 4 | //! 5 | //! 6 | //! 7 | 8 | use crate::generated::types::Fee; 9 | use borsh::BorshDeserialize; 10 | use borsh::BorshSerialize; 11 | 12 | #[derive(BorshSerialize, BorshDeserialize, Clone, Debug, Eq, PartialEq)] 13 | #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] 14 | pub struct Fees { 15 | pub activation_epoch: u64, 16 | pub priority_fee_distribution_fee_bps: Fee, 17 | #[cfg_attr(feature = "serde", serde(with = "serde_big_array::BigArray"))] 18 | pub reserved: [u8; 126], 19 | pub base_fee_groups_bps: [Fee; 8], 20 | pub ncn_fee_groups_bps: [Fee; 8], 21 | } 22 | -------------------------------------------------------------------------------- /clients/rust/jito_tip_router/src/generated/types/fee_config.rs: -------------------------------------------------------------------------------- 1 | //! This code was AUTOGENERATED using the kinobi library. 2 | //! Please DO NOT EDIT THIS FILE, instead use visitors 3 | //! to add features, then rerun kinobi to update it. 4 | //! 5 | //! 6 | //! 7 | 8 | use crate::generated::types::Fees; 9 | use borsh::BorshDeserialize; 10 | use borsh::BorshSerialize; 11 | use solana_program::pubkey::Pubkey; 12 | 13 | #[derive(BorshSerialize, BorshDeserialize, Clone, Debug, Eq, PartialEq)] 14 | #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] 15 | pub struct FeeConfig { 16 | pub block_engine_fee_bps: u16, 17 | pub base_fee_wallets: [Pubkey; 8], 18 | #[cfg_attr(feature = "serde", serde(with = "serde_big_array::BigArray"))] 19 | pub reserved: [u8; 128], 20 | pub fee1: Fees, 21 | pub fee2: Fees, 22 | } 23 | -------------------------------------------------------------------------------- /clients/rust/jito_tip_router/src/generated/types/ncn_reward_route.rs: -------------------------------------------------------------------------------- 1 | //! This code was AUTOGENERATED using the kinobi library. 2 | //! Please DO NOT EDIT THIS FILE, instead use visitors 3 | //! to add features, then rerun kinobi to update it. 4 | //! 5 | //! 6 | //! 7 | 8 | use crate::generated::types::BaseRewardRouterRewards; 9 | use borsh::BorshDeserialize; 10 | use borsh::BorshSerialize; 11 | use solana_program::pubkey::Pubkey; 12 | 13 | #[derive(BorshSerialize, BorshDeserialize, Clone, Debug, Eq, PartialEq)] 14 | #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] 15 | pub struct NcnRewardRoute { 16 | #[cfg_attr( 17 | feature = "serde", 18 | serde(with = "serde_with::As::") 19 | )] 20 | pub operator: Pubkey, 21 | pub ncn_fee_group_rewards: [BaseRewardRouterRewards; 8], 22 | } 23 | -------------------------------------------------------------------------------- /tip-router-operator-cli/.env.example: -------------------------------------------------------------------------------- 1 | RUST_LOG=info 2 | KEYPAIR_PATH= 3 | LEDGER_PATH= 4 | ACCOUNT_PATHS= 5 | FULL_SNAPSHOTS_PATH= 6 | SNAPSHOT_OUTPUT_DIR= 7 | OPERATOR_ADDRESS= 8 | RPC_URL= 9 | NCN_ADDRESS=jtoF4epChkmd75V2kxXSmywatczAomDqKu6VfWUQocT 10 | TIP_DISTRIBUTION_PROGRAM_ID=4R3gSG8BpU4t19KYj8CfnbtRpnT8gtk4dvTHxVRwc2r7 11 | TIP_PAYMENT_PROGRAM_ID=T1pyyaTNZsKv2WcRAB8oVnk93mLJw2XzjtVYqCsaHqt 12 | ENABLE_SNAPSHOTS=false 13 | TIP_ROUTER_PROGRAM_ID=RouterBmuRBkPUbgEDMtdvTZ75GBdSREZR5uGUxxxpb 14 | NUM_MONITORED_EPOCHS=3 15 | START_NEXT_EPOCH=false 16 | SAVE_PATH= 17 | BACKUP_SNAPSHOTS_DIR= 18 | SOLANA_METRICS_CONFIG="" 19 | SET_MERKLE_ROOTS=false 20 | CLAIM_TIPS=false 21 | STARTING_STAGE=wait-for-next-epoch -------------------------------------------------------------------------------- /cli/Dockerfile: -------------------------------------------------------------------------------- 1 | ##### Builder image 2 | FROM rust:1.80.0-slim-bullseye as builder 3 | 4 | RUN apt-get update && apt-get install -y \ 5 | libudev-dev \ 6 | clang \ 7 | pkg-config \ 8 | libssl-dev \ 9 | build-essential \ 10 | cmake \ 11 | protobuf-compiler \ 12 | && rm -rf /var/lib/apt/lists/* \ 13 | && update-ca-certificates 14 | 15 | WORKDIR /usr/src/app 16 | 17 | COPY . . 18 | 19 | RUN cargo build --release --bin jito-tip-router-cli 20 | RUN cargo install --path ./cli --bin jito-tip-router-cli --locked 21 | 22 | ##### Final image 23 | FROM debian:bullseye-slim as jito-tip-router-ncn-keeper 24 | 25 | RUN apt-get update && apt-get install -y \ 26 | ca-certificates \ 27 | libssl1.1 \ 28 | procps \ 29 | && rm -rf /var/lib/apt/lists/* 30 | 31 | COPY --from=builder /usr/src/app/target/release/jito-tip-router-cli /usr/local/bin/jito-tip-router-cli 32 | 33 | ENTRYPOINT ["jito-tip-router-cli", "keeper"] -------------------------------------------------------------------------------- /clients/js/jito_tip_router/types/fee.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This code was AUTOGENERATED using the kinobi library. 3 | * Please DO NOT EDIT THIS FILE, instead use visitors 4 | * to add features, then rerun kinobi to update it. 5 | * 6 | * @see https://github.com/kinobi-so/kinobi 7 | */ 8 | 9 | import { 10 | combineCodec, 11 | getStructDecoder, 12 | getStructEncoder, 13 | getU16Decoder, 14 | getU16Encoder, 15 | type Codec, 16 | type Decoder, 17 | type Encoder, 18 | } from '@solana/web3.js'; 19 | 20 | export type Fee = { fee: number }; 21 | 22 | export type FeeArgs = Fee; 23 | 24 | export function getFeeEncoder(): Encoder { 25 | return getStructEncoder([['fee', getU16Encoder()]]); 26 | } 27 | 28 | export function getFeeDecoder(): Decoder { 29 | return getStructDecoder([['fee', getU16Decoder()]]); 30 | } 31 | 32 | export function getFeeCodec(): Codec { 33 | return combineCodec(getFeeEncoder(), getFeeDecoder()); 34 | } 35 | -------------------------------------------------------------------------------- /clients/rust/jito_tip_router/src/generated/types/epoch_account_status.rs: -------------------------------------------------------------------------------- 1 | //! This code was AUTOGENERATED using the kinobi library. 2 | //! Please DO NOT EDIT THIS FILE, instead use visitors 3 | //! to add features, then rerun kinobi to update it. 4 | //! 5 | //! 6 | //! 7 | 8 | use borsh::BorshDeserialize; 9 | use borsh::BorshSerialize; 10 | 11 | #[derive(BorshSerialize, BorshDeserialize, Clone, Debug, Eq, PartialEq)] 12 | #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] 13 | pub struct EpochAccountStatus { 14 | pub epoch_state: u8, 15 | pub weight_table: u8, 16 | pub epoch_snapshot: u8, 17 | #[cfg_attr(feature = "serde", serde(with = "serde_big_array::BigArray"))] 18 | pub operator_snapshot: [u8; 256], 19 | pub ballot_box: u8, 20 | pub base_reward_router: u8, 21 | #[cfg_attr(feature = "serde", serde(with = "serde_big_array::BigArray"))] 22 | pub ncn_reward_router: [u8; 2048], 23 | } 24 | -------------------------------------------------------------------------------- /clients/rust/jito_tip_router/src/generated/types/vault_operator_stake_weight.rs: -------------------------------------------------------------------------------- 1 | //! This code was AUTOGENERATED using the kinobi library. 2 | //! Please DO NOT EDIT THIS FILE, instead use visitors 3 | //! to add features, then rerun kinobi to update it. 4 | //! 5 | //! 6 | //! 7 | 8 | use crate::generated::types::NcnFeeGroup; 9 | use crate::generated::types::StakeWeights; 10 | use borsh::BorshDeserialize; 11 | use borsh::BorshSerialize; 12 | use solana_program::pubkey::Pubkey; 13 | 14 | #[derive(BorshSerialize, BorshDeserialize, Clone, Debug, Eq, PartialEq)] 15 | #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] 16 | pub struct VaultOperatorStakeWeight { 17 | #[cfg_attr( 18 | feature = "serde", 19 | serde(with = "serde_with::As::") 20 | )] 21 | pub vault: Pubkey, 22 | pub vault_index: u64, 23 | pub ncn_fee_group: NcnFeeGroup, 24 | pub stake_weight: StakeWeights, 25 | pub reserved: [u8; 32], 26 | } 27 | -------------------------------------------------------------------------------- /clients/rust/jito_tip_router/src/generated/types/operator_vote.rs: -------------------------------------------------------------------------------- 1 | //! This code was AUTOGENERATED using the kinobi library. 2 | //! Please DO NOT EDIT THIS FILE, instead use visitors 3 | //! to add features, then rerun kinobi to update it. 4 | //! 5 | //! 6 | //! 7 | 8 | use crate::generated::types::StakeWeights; 9 | use borsh::BorshDeserialize; 10 | use borsh::BorshSerialize; 11 | use solana_program::pubkey::Pubkey; 12 | 13 | #[derive(BorshSerialize, BorshDeserialize, Clone, Debug, Eq, PartialEq)] 14 | #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] 15 | pub struct OperatorVote { 16 | #[cfg_attr( 17 | feature = "serde", 18 | serde(with = "serde_with::As::") 19 | )] 20 | pub operator: Pubkey, 21 | pub slot_voted: u64, 22 | pub stake_weights: StakeWeights, 23 | pub ballot_index: u16, 24 | #[cfg_attr(feature = "serde", serde(with = "serde_big_array::BigArray"))] 25 | pub reserved: [u8; 64], 26 | } 27 | -------------------------------------------------------------------------------- /clients/rust/jito_tip_router/src/generated/accounts/mod.rs: -------------------------------------------------------------------------------- 1 | //! This code was AUTOGENERATED using the kinobi library. 2 | //! Please DO NOT EDIT THIS FILE, instead use visitors 3 | //! to add features, then rerun kinobi to update it. 4 | //! 5 | //! 6 | //! 7 | 8 | pub(crate) mod r#ballot_box; 9 | pub(crate) mod r#base_reward_router; 10 | pub(crate) mod r#config; 11 | pub(crate) mod r#epoch_marker; 12 | pub(crate) mod r#epoch_snapshot; 13 | pub(crate) mod r#epoch_state; 14 | pub(crate) mod r#ncn_reward_router; 15 | pub(crate) mod r#operator_snapshot; 16 | pub(crate) mod r#vault_registry; 17 | pub(crate) mod r#weight_table; 18 | 19 | pub use self::r#ballot_box::*; 20 | pub use self::r#base_reward_router::*; 21 | pub use self::r#config::*; 22 | pub use self::r#epoch_marker::*; 23 | pub use self::r#epoch_snapshot::*; 24 | pub use self::r#epoch_state::*; 25 | pub use self::r#ncn_reward_router::*; 26 | pub use self::r#operator_snapshot::*; 27 | pub use self::r#vault_registry::*; 28 | pub use self::r#weight_table::*; 29 | -------------------------------------------------------------------------------- /clients/js/jito_tip_router/types/index.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This code was AUTOGENERATED using the kinobi library. 3 | * Please DO NOT EDIT THIS FILE, instead use visitors 4 | * to add features, then rerun kinobi to update it. 5 | * 6 | * @see https://github.com/kinobi-so/kinobi 7 | */ 8 | 9 | export * from './ballot'; 10 | export * from './ballotTally'; 11 | export * from './baseFeeGroup'; 12 | export * from './baseRewardRouterRewards'; 13 | export * from './configAdminRole'; 14 | export * from './epochAccountStatus'; 15 | export * from './fee'; 16 | export * from './feeConfig'; 17 | export * from './fees'; 18 | export * from './ncnFeeGroup'; 19 | export * from './ncnFeeGroupWeight'; 20 | export * from './ncnRewardRoute'; 21 | export * from './operatorVote'; 22 | export * from './progress'; 23 | export * from './stakePoolInstruction'; 24 | export * from './stakeWeights'; 25 | export * from './stMintEntry'; 26 | export * from './vaultEntry'; 27 | export * from './vaultOperatorStakeWeight'; 28 | export * from './vaultRewardRoute'; 29 | export * from './weightEntry'; 30 | -------------------------------------------------------------------------------- /clients/js/jito_tip_router/types/ncnFeeGroup.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This code was AUTOGENERATED using the kinobi library. 3 | * Please DO NOT EDIT THIS FILE, instead use visitors 4 | * to add features, then rerun kinobi to update it. 5 | * 6 | * @see https://github.com/kinobi-so/kinobi 7 | */ 8 | 9 | import { 10 | combineCodec, 11 | getStructDecoder, 12 | getStructEncoder, 13 | getU8Decoder, 14 | getU8Encoder, 15 | type Codec, 16 | type Decoder, 17 | type Encoder, 18 | } from '@solana/web3.js'; 19 | 20 | export type NcnFeeGroup = { group: number }; 21 | 22 | export type NcnFeeGroupArgs = NcnFeeGroup; 23 | 24 | export function getNcnFeeGroupEncoder(): Encoder { 25 | return getStructEncoder([['group', getU8Encoder()]]); 26 | } 27 | 28 | export function getNcnFeeGroupDecoder(): Decoder { 29 | return getStructDecoder([['group', getU8Decoder()]]); 30 | } 31 | 32 | export function getNcnFeeGroupCodec(): Codec { 33 | return combineCodec(getNcnFeeGroupEncoder(), getNcnFeeGroupDecoder()); 34 | } 35 | -------------------------------------------------------------------------------- /clients/js/jito_tip_router/types/baseFeeGroup.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This code was AUTOGENERATED using the kinobi library. 3 | * Please DO NOT EDIT THIS FILE, instead use visitors 4 | * to add features, then rerun kinobi to update it. 5 | * 6 | * @see https://github.com/kinobi-so/kinobi 7 | */ 8 | 9 | import { 10 | combineCodec, 11 | getStructDecoder, 12 | getStructEncoder, 13 | getU8Decoder, 14 | getU8Encoder, 15 | type Codec, 16 | type Decoder, 17 | type Encoder, 18 | } from '@solana/web3.js'; 19 | 20 | export type BaseFeeGroup = { group: number }; 21 | 22 | export type BaseFeeGroupArgs = BaseFeeGroup; 23 | 24 | export function getBaseFeeGroupEncoder(): Encoder { 25 | return getStructEncoder([['group', getU8Encoder()]]); 26 | } 27 | 28 | export function getBaseFeeGroupDecoder(): Decoder { 29 | return getStructDecoder([['group', getU8Decoder()]]); 30 | } 31 | 32 | export function getBaseFeeGroupCodec(): Codec { 33 | return combineCodec(getBaseFeeGroupEncoder(), getBaseFeeGroupDecoder()); 34 | } 35 | -------------------------------------------------------------------------------- /clients/js/jito_tip_router/types/configAdminRole.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This code was AUTOGENERATED using the kinobi library. 3 | * Please DO NOT EDIT THIS FILE, instead use visitors 4 | * to add features, then rerun kinobi to update it. 5 | * 6 | * @see https://github.com/kinobi-so/kinobi 7 | */ 8 | 9 | import { 10 | combineCodec, 11 | getEnumDecoder, 12 | getEnumEncoder, 13 | type Codec, 14 | type Decoder, 15 | type Encoder, 16 | } from '@solana/web3.js'; 17 | 18 | export enum ConfigAdminRole { 19 | FeeAdmin, 20 | TieBreakerAdmin, 21 | } 22 | 23 | export type ConfigAdminRoleArgs = ConfigAdminRole; 24 | 25 | export function getConfigAdminRoleEncoder(): Encoder { 26 | return getEnumEncoder(ConfigAdminRole); 27 | } 28 | 29 | export function getConfigAdminRoleDecoder(): Decoder { 30 | return getEnumDecoder(ConfigAdminRole); 31 | } 32 | 33 | export function getConfigAdminRoleCodec(): Codec< 34 | ConfigAdminRoleArgs, 35 | ConfigAdminRole 36 | > { 37 | return combineCodec(getConfigAdminRoleEncoder(), getConfigAdminRoleDecoder()); 38 | } 39 | -------------------------------------------------------------------------------- /lines.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Array of directories to scan 4 | directories=( 5 | "./program" 6 | "./core" 7 | "./integration_tests" 8 | "./cli" 9 | ) 10 | 11 | total=0 12 | 13 | # Function to count lines in a directory 14 | count_lines() { 15 | local dir=$1 16 | if [ ! -d "$dir" ]; then 17 | echo "Warning: Directory $dir does not exist, skipping..." 18 | echo 0 19 | return 20 | fi 21 | 22 | local count=$(find "$dir" -name "*.rs" -type f -exec wc -l {} + | awk '{total += $1} END {print total}') 23 | 24 | # If no Rust files found, count will be empty 25 | if [ -z "$count" ]; then 26 | count=0 27 | fi 28 | 29 | echo "$dir: $count lines" 30 | echo $count 31 | } 32 | 33 | # Process each directory 34 | for dir in "${directories[@]}"; do 35 | count=$(count_lines "$dir") 36 | # Get the last line of output (the count) 37 | dir_count=$(echo "$count" | tail -n 1) 38 | total=$((total + dir_count)) 39 | done 40 | 41 | echo "----------------------------------------" 42 | echo "Total lines of Rust code: $total" -------------------------------------------------------------------------------- /clients/rust/jito_tip_router/src/generated/types/vault_entry.rs: -------------------------------------------------------------------------------- 1 | //! This code was AUTOGENERATED using the kinobi library. 2 | //! Please DO NOT EDIT THIS FILE, instead use visitors 3 | //! to add features, then rerun kinobi to update it. 4 | //! 5 | //! 6 | //! 7 | 8 | use borsh::BorshDeserialize; 9 | use borsh::BorshSerialize; 10 | use solana_program::pubkey::Pubkey; 11 | 12 | #[derive(BorshSerialize, BorshDeserialize, Clone, Debug, Eq, PartialEq)] 13 | #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] 14 | pub struct VaultEntry { 15 | #[cfg_attr( 16 | feature = "serde", 17 | serde(with = "serde_with::As::") 18 | )] 19 | pub vault: Pubkey, 20 | #[cfg_attr( 21 | feature = "serde", 22 | serde(with = "serde_with::As::") 23 | )] 24 | pub st_mint: Pubkey, 25 | pub vault_index: u64, 26 | pub slot_registered: u64, 27 | #[cfg_attr(feature = "serde", serde(with = "serde_big_array::BigArray"))] 28 | pub reserved: [u8; 128], 29 | } 30 | -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2024 Jito Foundation 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /cli/src/bin/main.rs: -------------------------------------------------------------------------------- 1 | use anyhow::Result; 2 | use clap::Parser; 3 | use clap_markdown::MarkdownOptions; 4 | use dotenv::dotenv; 5 | 6 | use jito_tip_router_cli::{args::Args, handler::CliHandler, log::init_logger}; 7 | use log::info; 8 | 9 | #[tokio::main] 10 | #[allow(clippy::large_stack_frames)] 11 | async fn main() -> Result<()> { 12 | dotenv().ok(); 13 | init_logger(); 14 | 15 | let args: Args = Args::parse(); 16 | 17 | if args.markdown_help { 18 | let markdown = clap_markdown::help_markdown_custom::( 19 | &MarkdownOptions::new().show_table_of_contents(false), 20 | ); 21 | println!("---"); 22 | println!("title: CLI"); 23 | println!("category: Jekyll"); 24 | println!("layout: post"); 25 | println!("weight: 1"); 26 | println!("---"); 27 | println!(); 28 | println!("{}", markdown); 29 | return Ok(()); 30 | } 31 | 32 | // if let ProgramCommand::Keeper { .. } = args.command { 33 | info!("\n{}", args); 34 | // } 35 | 36 | let handler = CliHandler::from_args(&args).await?; 37 | handler.handle(args.command).await?; 38 | 39 | Ok(()) 40 | } 41 | -------------------------------------------------------------------------------- /clients/js/jito_tip_router/types/ncnFeeGroupWeight.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This code was AUTOGENERATED using the kinobi library. 3 | * Please DO NOT EDIT THIS FILE, instead use visitors 4 | * to add features, then rerun kinobi to update it. 5 | * 6 | * @see https://github.com/kinobi-so/kinobi 7 | */ 8 | 9 | import { 10 | combineCodec, 11 | getStructDecoder, 12 | getStructEncoder, 13 | getU128Decoder, 14 | getU128Encoder, 15 | type Codec, 16 | type Decoder, 17 | type Encoder, 18 | } from '@solana/web3.js'; 19 | 20 | export type NcnFeeGroupWeight = { weight: bigint }; 21 | 22 | export type NcnFeeGroupWeightArgs = { weight: number | bigint }; 23 | 24 | export function getNcnFeeGroupWeightEncoder(): Encoder { 25 | return getStructEncoder([['weight', getU128Encoder()]]); 26 | } 27 | 28 | export function getNcnFeeGroupWeightDecoder(): Decoder { 29 | return getStructDecoder([['weight', getU128Decoder()]]); 30 | } 31 | 32 | export function getNcnFeeGroupWeightCodec(): Codec< 33 | NcnFeeGroupWeightArgs, 34 | NcnFeeGroupWeight 35 | > { 36 | return combineCodec( 37 | getNcnFeeGroupWeightEncoder(), 38 | getNcnFeeGroupWeightDecoder() 39 | ); 40 | } 41 | -------------------------------------------------------------------------------- /clients/rust/jito_tip_router/src/generated/types/st_mint_entry.rs: -------------------------------------------------------------------------------- 1 | //! This code was AUTOGENERATED using the kinobi library. 2 | //! Please DO NOT EDIT THIS FILE, instead use visitors 3 | //! to add features, then rerun kinobi to update it. 4 | //! 5 | //! 6 | //! 7 | 8 | use crate::generated::types::NcnFeeGroup; 9 | use borsh::BorshDeserialize; 10 | use borsh::BorshSerialize; 11 | use solana_program::pubkey::Pubkey; 12 | 13 | #[derive(BorshSerialize, BorshDeserialize, Clone, Debug, Eq, PartialEq)] 14 | #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] 15 | pub struct StMintEntry { 16 | #[cfg_attr( 17 | feature = "serde", 18 | serde(with = "serde_with::As::") 19 | )] 20 | pub st_mint: Pubkey, 21 | pub ncn_fee_group: NcnFeeGroup, 22 | pub reward_multiplier_bps: u64, 23 | #[cfg_attr( 24 | feature = "serde", 25 | serde(with = "serde_with::As::") 26 | )] 27 | pub switchboard_feed: Pubkey, 28 | pub no_feed_weight: u128, 29 | #[cfg_attr(feature = "serde", serde(with = "serde_big_array::BigArray"))] 30 | pub reserved: [u8; 128], 31 | } 32 | -------------------------------------------------------------------------------- /clients/rust/jito_tip_router/src/generated/accounts/epoch_marker.rs: -------------------------------------------------------------------------------- 1 | use borsh::BorshDeserialize; 2 | use borsh::BorshSerialize; 3 | use solana_program::pubkey::Pubkey; 4 | 5 | #[derive(BorshSerialize, BorshDeserialize, Clone, Debug, Eq, PartialEq)] 6 | #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] 7 | pub struct EpochMarker { 8 | pub discriminator: u64, 9 | #[cfg_attr( 10 | feature = "serde", 11 | serde(with = "serde_with::As::") 12 | )] 13 | pub ncn: Pubkey, 14 | pub epoch: u64, 15 | pub slot_closed: u64, 16 | } 17 | 18 | impl EpochMarker { 19 | #[inline(always)] 20 | pub fn from_bytes(data: &[u8]) -> Result { 21 | let mut data = data; 22 | Self::deserialize(&mut data) 23 | } 24 | } 25 | 26 | impl<'a> TryFrom<&solana_program::account_info::AccountInfo<'a>> for EpochMarker { 27 | type Error = std::io::Error; 28 | 29 | fn try_from( 30 | account_info: &solana_program::account_info::AccountInfo<'a>, 31 | ) -> Result { 32 | let mut data: &[u8] = &(*account_info.data).borrow(); 33 | Self::deserialize(&mut data) 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /meta_merkle_tree/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "meta-merkle-tree" 3 | description = "Meta Merkle Tree" 4 | version = { workspace = true } 5 | authors = { workspace = true } 6 | repository = { workspace = true } 7 | homepage = { workspace = true } 8 | license = { workspace = true } 9 | edition = { workspace = true } 10 | readme = { workspace = true } 11 | 12 | [dependencies] 13 | borsh = { workspace = true } 14 | bytemuck = { workspace = true } 15 | fast-math = { workspace = true } 16 | hex = { workspace = true } 17 | jito-bytemuck = { workspace = true } 18 | jito-jsm-core = { workspace = true } 19 | jito-restaking-core = { workspace = true } 20 | jito-restaking-sdk = { workspace = true } 21 | jito-vault-core = { workspace = true } 22 | jito-vault-sdk = { workspace = true } 23 | log = { workspace = true } 24 | rand = { workspace = true } 25 | serde = { workspace = true } 26 | serde_json = { workspace = true } 27 | shank = { workspace = true } 28 | solana-program = { workspace = true } 29 | spl-associated-token-account-interface = { workspace = true } 30 | spl-math = { workspace = true } 31 | spl-token-interface = { workspace = true } 32 | thiserror = { workspace = true } 33 | 34 | [dev-dependencies] 35 | solana-sdk = { workspace = true } 36 | -------------------------------------------------------------------------------- /clients/js/jito_tip_router/types/baseRewardRouterRewards.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This code was AUTOGENERATED using the kinobi library. 3 | * Please DO NOT EDIT THIS FILE, instead use visitors 4 | * to add features, then rerun kinobi to update it. 5 | * 6 | * @see https://github.com/kinobi-so/kinobi 7 | */ 8 | 9 | import { 10 | combineCodec, 11 | getStructDecoder, 12 | getStructEncoder, 13 | getU64Decoder, 14 | getU64Encoder, 15 | type Codec, 16 | type Decoder, 17 | type Encoder, 18 | } from '@solana/web3.js'; 19 | 20 | export type BaseRewardRouterRewards = { rewards: bigint }; 21 | 22 | export type BaseRewardRouterRewardsArgs = { rewards: number | bigint }; 23 | 24 | export function getBaseRewardRouterRewardsEncoder(): Encoder { 25 | return getStructEncoder([['rewards', getU64Encoder()]]); 26 | } 27 | 28 | export function getBaseRewardRouterRewardsDecoder(): Decoder { 29 | return getStructDecoder([['rewards', getU64Decoder()]]); 30 | } 31 | 32 | export function getBaseRewardRouterRewardsCodec(): Codec< 33 | BaseRewardRouterRewardsArgs, 34 | BaseRewardRouterRewards 35 | > { 36 | return combineCodec( 37 | getBaseRewardRouterRewardsEncoder(), 38 | getBaseRewardRouterRewardsDecoder() 39 | ); 40 | } 41 | -------------------------------------------------------------------------------- /integration_tests/tests/helpers/serialized_accounts.rs: -------------------------------------------------------------------------------- 1 | use jito_bytemuck::Discriminator; 2 | use jito_tip_router_core::{ballot_box::BallotBox, epoch_state::EpochState}; 3 | use solana_sdk::{account::Account, native_token::LAMPORTS_PER_SOL}; 4 | 5 | pub fn serialized_epoch_state_account(epoch_state: &EpochState) -> Account { 6 | // TODO add AccountSerialize to jito_restaking::bytemuck? 7 | let mut data = vec![EpochState::DISCRIMINATOR; 1]; 8 | data.extend_from_slice(&[0; 7]); 9 | data.extend_from_slice(bytemuck::bytes_of(epoch_state)); 10 | 11 | Account { 12 | lamports: LAMPORTS_PER_SOL * 5, 13 | data, 14 | owner: jito_tip_router_program::id(), 15 | executable: false, 16 | rent_epoch: 0, 17 | } 18 | } 19 | 20 | pub fn serialized_ballot_box_account(ballot_box: &BallotBox) -> Account { 21 | // TODO add AccountSerialize to jito_restaking::bytemuck? 22 | let mut data = vec![BallotBox::DISCRIMINATOR; 1]; 23 | data.extend_from_slice(&[0; 7]); 24 | data.extend_from_slice(bytemuck::bytes_of(ballot_box)); 25 | 26 | Account { 27 | lamports: LAMPORTS_PER_SOL * 5, 28 | data, 29 | owner: jito_tip_router_program::id(), 30 | executable: false, 31 | rent_epoch: 0, 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /core/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "jito-tip-router-core" 3 | description = "Jito's MEV Tip Distribution NCN Core" 4 | version = { workspace = true } 5 | authors = { workspace = true } 6 | repository = { workspace = true } 7 | homepage = { workspace = true } 8 | license = { workspace = true } 9 | edition = { workspace = true } 10 | readme = { workspace = true } 11 | 12 | [dependencies] 13 | borsh = { workspace = true } 14 | bytemuck = { workspace = true } 15 | jito-bytemuck = { workspace = true } 16 | jito-jsm-core = { workspace = true } 17 | jito-restaking-core = { workspace = true } 18 | jito-restaking-sdk = { workspace = true } 19 | jito-tip-distribution-sdk = { workspace = true } 20 | jito-vault-core = { workspace = true } 21 | jito-vault-sdk = { workspace = true } 22 | meta-merkle-tree = { workspace = true } 23 | serde = { workspace = true } 24 | serde_with = { workspace = true } 25 | shank = { workspace = true } 26 | solana-decode-error = { workspace = true } 27 | solana-program = { workspace = true } 28 | solana-system-interface = { workspace = true } 29 | spl-associated-token-account-interface = { workspace = true } 30 | spl-math = { workspace = true } 31 | spl-token-interface = { workspace = true } 32 | thiserror = { workspace = true } 33 | 34 | [dev-dependencies] 35 | assert_matches = { workspace = true } 36 | -------------------------------------------------------------------------------- /integration_tests/tests/tip_router/admin_update_weight_table.rs: -------------------------------------------------------------------------------- 1 | #[cfg(test)] 2 | mod tests { 3 | 4 | use crate::fixtures::{test_builder::TestBuilder, TestResult}; 5 | 6 | #[tokio::test] 7 | async fn test_admin_update_weight_table() -> TestResult<()> { 8 | let mut fixture = TestBuilder::new().await; 9 | let mut vault_client = fixture.vault_program_client(); 10 | let mut tip_router_client = fixture.tip_router_client(); 11 | 12 | let test_ncn = fixture.create_initial_test_ncn(1, 1, None).await?; 13 | 14 | fixture.warp_slot_incremental(1000).await?; 15 | 16 | let clock = fixture.clock().await; 17 | let epoch = clock.epoch; 18 | 19 | tip_router_client 20 | .do_full_initialize_epoch_state(test_ncn.ncn_root.ncn_pubkey, epoch) 21 | .await?; 22 | 23 | tip_router_client 24 | .do_full_initialize_weight_table(test_ncn.ncn_root.ncn_pubkey, epoch) 25 | .await?; 26 | 27 | let vault_root = test_ncn.vaults[0].clone(); 28 | let vault = vault_client.get_vault(&vault_root.vault_pubkey).await?; 29 | 30 | let mint = vault.supported_mint; 31 | let weight = 100; 32 | 33 | tip_router_client 34 | .do_admin_set_weight(test_ncn.ncn_root.ncn_pubkey, epoch, mint, weight) 35 | .await?; 36 | 37 | Ok(()) 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /clients/js/jito_tip_router/types/vaultRewardRoute.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This code was AUTOGENERATED using the kinobi library. 3 | * Please DO NOT EDIT THIS FILE, instead use visitors 4 | * to add features, then rerun kinobi to update it. 5 | * 6 | * @see https://github.com/kinobi-so/kinobi 7 | */ 8 | 9 | import { 10 | combineCodec, 11 | getAddressDecoder, 12 | getAddressEncoder, 13 | getStructDecoder, 14 | getStructEncoder, 15 | getU64Decoder, 16 | getU64Encoder, 17 | type Address, 18 | type Codec, 19 | type Decoder, 20 | type Encoder, 21 | } from '@solana/web3.js'; 22 | 23 | export type VaultRewardRoute = { vault: Address; rewards: bigint }; 24 | 25 | export type VaultRewardRouteArgs = { vault: Address; rewards: number | bigint }; 26 | 27 | export function getVaultRewardRouteEncoder(): Encoder { 28 | return getStructEncoder([ 29 | ['vault', getAddressEncoder()], 30 | ['rewards', getU64Encoder()], 31 | ]); 32 | } 33 | 34 | export function getVaultRewardRouteDecoder(): Decoder { 35 | return getStructDecoder([ 36 | ['vault', getAddressDecoder()], 37 | ['rewards', getU64Decoder()], 38 | ]); 39 | } 40 | 41 | export function getVaultRewardRouteCodec(): Codec< 42 | VaultRewardRouteArgs, 43 | VaultRewardRoute 44 | > { 45 | return combineCodec( 46 | getVaultRewardRouteEncoder(), 47 | getVaultRewardRouteDecoder() 48 | ); 49 | } 50 | -------------------------------------------------------------------------------- /format.sh: -------------------------------------------------------------------------------- 1 | #! /bin/zsh 2 | 3 | # Function to print command being executed 4 | print_executing() { 5 | echo "Executing: $1" 6 | } 7 | 8 | # Basic commands that always run 9 | print_executing "cargo sort --workspace" 10 | cargo sort --workspace 11 | 12 | print_executing "cargo fmt --all" 13 | cargo fmt --all 14 | 15 | print_executing "cargo nextest run --all-features" 16 | cargo build-sbf --sbf-out-dir integration_tests/tests/fixtures 17 | SBPF_OUT_DIR=integration_tests/tests/fixtures cargo nextest run --all-features -E 'not test(ledger_utils::tests::test_get_bank_from_ledger_success)' 18 | 19 | # Code coverage only runs with flag 20 | if [[ "$*" == *"--code-coverage"* ]]; then 21 | print_executing "cargo llvm-cov --all-features --workspace --lcov --output-path lcov.info" 22 | cargo llvm-cov --all-features --workspace --lcov --output-path lcov.info -- --skip "tip_router::bpf::set_merkle_root" 23 | fi 24 | 25 | print_executing "cargo clippy --all-features" 26 | cargo clippy --all-features -- -D warnings -D clippy::all -D clippy::nursery -D clippy::integer_division -D clippy::arithmetic_side_effects -D clippy::style -D clippy::perf 27 | 28 | print_executing "cargo b && ./target/debug/jito-tip-router-shank-cli && yarn install && yarn generate-clients && cargo b" 29 | cargo b && ./target/debug/jito-tip-router-shank-cli && yarn install && yarn generate-clients && cargo b 30 | 31 | print_executing "cargo-build-sbf" 32 | cargo-build-sbf -------------------------------------------------------------------------------- /meta_merkle_tree/src/verify.rs: -------------------------------------------------------------------------------- 1 | use solana_program::hash::hashv; 2 | 3 | #[allow(clippy::too_long_first_doc_paragraph)] 4 | /// modified version of https://github.com/saber-hq/merkle-distributor/blob/ac937d1901033ecb7fa3b0db22f7b39569c8e052/programs/merkle-distributor/src/merkle_proof.rs#L8 5 | /// This function deals with verification of Merkle trees (hash trees). 6 | /// Direct port of https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v3.4.0/contracts/cryptography/MerkleProof.sol 7 | /// Returns true if a `leaf` can be proved to be a part of a Merkle tree 8 | /// defined by `root`. For this, a `proof` must be provided, containing 9 | /// sibling hashes on the branch from the leaf to the root of the tree. Each 10 | /// pair of leaves and each pair of pre-images are assumed to be sorted. 11 | pub fn verify(proof: Vec<[u8; 32]>, root: [u8; 32], leaf: [u8; 32]) -> bool { 12 | let mut computed_hash = leaf; 13 | for proof_element in proof.into_iter() { 14 | if computed_hash <= proof_element { 15 | // Hash(current computed hash + current element of the proof) 16 | computed_hash = hashv(&[&[1u8], &computed_hash, &proof_element]).to_bytes(); 17 | } else { 18 | // Hash(current element of the proof + current computed hash) 19 | computed_hash = hashv(&[&[1u8], &proof_element, &computed_hash]).to_bytes(); 20 | } 21 | } 22 | // Check if the computed hash (root) is equal to the provided root 23 | computed_hash == root 24 | } 25 | -------------------------------------------------------------------------------- /clients/js/jito_tip_router/types/progress.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This code was AUTOGENERATED using the kinobi library. 3 | * Please DO NOT EDIT THIS FILE, instead use visitors 4 | * to add features, then rerun kinobi to update it. 5 | * 6 | * @see https://github.com/kinobi-so/kinobi 7 | */ 8 | 9 | import { 10 | combineCodec, 11 | getArrayDecoder, 12 | getArrayEncoder, 13 | getStructDecoder, 14 | getStructEncoder, 15 | getU64Decoder, 16 | getU64Encoder, 17 | getU8Decoder, 18 | getU8Encoder, 19 | type Codec, 20 | type Decoder, 21 | type Encoder, 22 | } from '@solana/web3.js'; 23 | 24 | export type Progress = { 25 | tally: bigint; 26 | total: bigint; 27 | reserved: Array; 28 | }; 29 | 30 | export type ProgressArgs = { 31 | tally: number | bigint; 32 | total: number | bigint; 33 | reserved: Array; 34 | }; 35 | 36 | export function getProgressEncoder(): Encoder { 37 | return getStructEncoder([ 38 | ['tally', getU64Encoder()], 39 | ['total', getU64Encoder()], 40 | ['reserved', getArrayEncoder(getU8Encoder(), { size: 8 })], 41 | ]); 42 | } 43 | 44 | export function getProgressDecoder(): Decoder { 45 | return getStructDecoder([ 46 | ['tally', getU64Decoder()], 47 | ['total', getU64Decoder()], 48 | ['reserved', getArrayDecoder(getU8Decoder(), { size: 8 })], 49 | ]); 50 | } 51 | 52 | export function getProgressCodec(): Codec { 53 | return combineCodec(getProgressEncoder(), getProgressDecoder()); 54 | } 55 | -------------------------------------------------------------------------------- /tip-router-operator-cli/Dockerfile: -------------------------------------------------------------------------------- 1 | # Dockerfile 2 | FROM rust:1.85.0-slim-bookworm as builder 3 | 4 | # Install dependencies 5 | RUN apt-get update && apt-get install -y \ 6 | libudev-dev \ 7 | clang \ 8 | pkg-config \ 9 | libssl-dev \ 10 | build-essential \ 11 | llvm-dev \ 12 | libclang-dev \ 13 | cmake \ 14 | protobuf-compiler \ 15 | git 16 | RUN update-ca-certificates 17 | 18 | # Set up build directory 19 | WORKDIR /usr/src/app 20 | COPY . . 21 | 22 | RUN echo "Contents of /usr/src/app:" && \ 23 | ls -la && \ 24 | echo "Cargo workspace info:" && \ 25 | cargo metadata --format-version=1 26 | 27 | # Build with cache mounting for faster builds 28 | RUN echo "Starting cargo build..." && \ 29 | cargo build --release --bin tip-router-operator-cli -vv && \ 30 | echo "Build completed, checking results:" && \ 31 | find /usr/src/app/target -type f -name "tip-router-operator-cli*" && \ 32 | ls -la /usr/src/app/target/release/ 33 | 34 | # Production image 35 | FROM debian:bookworm-slim 36 | 37 | # Install necessary runtime dependencies 38 | RUN apt-get update && apt-get install -y \ 39 | ca-certificates \ 40 | && rm -rf /var/lib/apt/lists/* 41 | 42 | # Create necessary directories 43 | RUN mkdir -p /solana/ledger /solana/snapshots /solana/snapshots/autosnapshot 44 | 45 | # Copy binary from builder 46 | COPY --from=builder /usr/src/app/target/release/tip-router-operator-cli /usr/local/bin/ 47 | 48 | # Set up environment 49 | ENV RUST_LOG=info 50 | 51 | # Command will be provided by docker-compose 52 | ENTRYPOINT ["tip-router-operator-cli"] -------------------------------------------------------------------------------- /clients/js/jito_tip_router/types/ballot.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This code was AUTOGENERATED using the kinobi library. 3 | * Please DO NOT EDIT THIS FILE, instead use visitors 4 | * to add features, then rerun kinobi to update it. 5 | * 6 | * @see https://github.com/kinobi-so/kinobi 7 | */ 8 | 9 | import { 10 | combineCodec, 11 | fixDecoderSize, 12 | fixEncoderSize, 13 | getArrayDecoder, 14 | getArrayEncoder, 15 | getBoolDecoder, 16 | getBoolEncoder, 17 | getBytesDecoder, 18 | getBytesEncoder, 19 | getStructDecoder, 20 | getStructEncoder, 21 | getU8Decoder, 22 | getU8Encoder, 23 | type Codec, 24 | type Decoder, 25 | type Encoder, 26 | type ReadonlyUint8Array, 27 | } from '@solana/web3.js'; 28 | 29 | export type Ballot = { 30 | metaMerkleRoot: ReadonlyUint8Array; 31 | isValid: number; 32 | reserved: Array; 33 | }; 34 | 35 | export type BallotArgs = Ballot; 36 | 37 | export function getBallotEncoder(): Encoder { 38 | return getStructEncoder([ 39 | ['metaMerkleRoot', fixEncoderSize(getBytesEncoder(), 32)], 40 | ['isValid', getBoolEncoder()], 41 | ['reserved', getArrayEncoder(getU8Encoder(), { size: 63 })], 42 | ]); 43 | } 44 | 45 | export function getBallotDecoder(): Decoder { 46 | return getStructDecoder([ 47 | ['metaMerkleRoot', fixDecoderSize(getBytesDecoder(), 32)], 48 | ['isValid', getBoolDecoder()], 49 | ['reserved', getArrayDecoder(getU8Decoder(), { size: 63 })], 50 | ]); 51 | } 52 | 53 | export function getBallotCodec(): Codec { 54 | return combineCodec(getBallotEncoder(), getBallotDecoder()); 55 | } 56 | -------------------------------------------------------------------------------- /clients/rust/jito_tip_router/src/generated/accounts/vault_registry.rs: -------------------------------------------------------------------------------- 1 | use crate::generated::types::StMintEntry; 2 | use crate::generated::types::VaultEntry; 3 | use borsh::BorshDeserialize; 4 | use borsh::BorshSerialize; 5 | use solana_program::pubkey::Pubkey; 6 | 7 | #[derive(BorshSerialize, BorshDeserialize, Clone, Debug, Eq, PartialEq)] 8 | #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] 9 | pub struct VaultRegistry { 10 | pub discriminator: u64, 11 | #[cfg_attr( 12 | feature = "serde", 13 | serde(with = "serde_with::As::") 14 | )] 15 | pub ncn: Pubkey, 16 | pub bump: u8, 17 | #[cfg_attr(feature = "serde", serde(with = "serde_big_array::BigArray"))] 18 | pub reserved: [u8; 127], 19 | #[cfg_attr(feature = "serde", serde(with = "serde_big_array::BigArray"))] 20 | pub st_mint_list: [StMintEntry; 64], 21 | #[cfg_attr(feature = "serde", serde(with = "serde_big_array::BigArray"))] 22 | pub vault_list: [VaultEntry; 64], 23 | } 24 | 25 | impl VaultRegistry { 26 | #[inline(always)] 27 | pub fn from_bytes(data: &[u8]) -> Result { 28 | let mut data = data; 29 | Self::deserialize(&mut data) 30 | } 31 | } 32 | 33 | impl<'a> TryFrom<&solana_program::account_info::AccountInfo<'a>> for VaultRegistry { 34 | type Error = std::io::Error; 35 | 36 | fn try_from( 37 | account_info: &solana_program::account_info::AccountInfo<'a>, 38 | ) -> Result { 39 | let mut data: &[u8] = &(*account_info.data).borrow(); 40 | Self::deserialize(&mut data) 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /clients/rust/jito_tip_router/src/generated/accounts/weight_table.rs: -------------------------------------------------------------------------------- 1 | use crate::types::VaultEntry; 2 | use crate::types::WeightEntry; 3 | use borsh::BorshDeserialize; 4 | use borsh::BorshSerialize; 5 | use solana_program::pubkey::Pubkey; 6 | 7 | #[derive(BorshSerialize, BorshDeserialize, Clone, Debug, Eq, PartialEq)] 8 | #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] 9 | pub struct WeightTable { 10 | pub discriminator: u64, 11 | #[cfg_attr( 12 | feature = "serde", 13 | serde(with = "serde_with::As::") 14 | )] 15 | pub ncn: Pubkey, 16 | pub epoch: u64, 17 | pub slot_created: u64, 18 | pub vault_count: u64, 19 | pub bump: u8, 20 | #[cfg_attr(feature = "serde", serde(with = "serde_big_array::BigArray"))] 21 | pub reserved: [u8; 128], 22 | #[cfg_attr(feature = "serde", serde(with = "serde_big_array::BigArray"))] 23 | pub vault_registry: [VaultEntry; 64], 24 | #[cfg_attr(feature = "serde", serde(with = "serde_big_array::BigArray"))] 25 | pub table: [WeightEntry; 64], 26 | } 27 | 28 | impl WeightTable { 29 | #[inline(always)] 30 | pub fn from_bytes(data: &[u8]) -> Result { 31 | let mut data = data; 32 | Self::deserialize(&mut data) 33 | } 34 | } 35 | 36 | impl<'a> TryFrom<&solana_program::account_info::AccountInfo<'a>> for WeightTable { 37 | type Error = std::io::Error; 38 | 39 | fn try_from( 40 | account_info: &solana_program::account_info::AccountInfo<'a>, 41 | ) -> Result { 42 | let mut data: &[u8] = &(*account_info.data).borrow(); 43 | Self::deserialize(&mut data) 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /cli/src/ported/error.rs: -------------------------------------------------------------------------------- 1 | use log::*; 2 | use solana_client::client_error::ClientError; 3 | use solana_client::rpc_response::RpcSimulateTransactionResult; 4 | 5 | use thiserror::Error as ThisError; 6 | use tokio::task::JoinError; 7 | 8 | #[derive(ThisError, Debug)] 9 | pub enum JitoTransactionError { 10 | #[error(transparent)] 11 | ClientError(#[from] ClientError), 12 | #[error(transparent)] 13 | TransactionExecutionError(#[from] JitoTransactionExecutionError), 14 | #[error(transparent)] 15 | MultipleAccountsError(#[from] JitoMultipleAccountsError), 16 | #[error("Custom: {0}")] 17 | Custom(String), 18 | } 19 | 20 | pub type Error = Box; 21 | #[derive(ThisError, Debug, Clone)] 22 | pub enum JitoTransactionExecutionError { 23 | #[error("RPC Client error: {0:?}")] 24 | ClientError(String), 25 | #[error("RPC Client error: {0:?}")] 26 | TransactionClientError(String, Vec>), 27 | } 28 | 29 | #[derive(ThisError, Debug)] 30 | pub enum JitoMultipleAccountsError { 31 | #[error(transparent)] 32 | ClientError(#[from] ClientError), 33 | #[error(transparent)] 34 | JoinError(#[from] JoinError), 35 | } 36 | 37 | #[derive(ThisError, Clone, Debug)] 38 | pub enum JitoSendTransactionError { 39 | #[error("Exceeded retries")] 40 | ExceededRetries, 41 | // Stores ClientError.to_string(), since ClientError does not impl Clone, and we want to track both 42 | // io/reqwest errors as well as transaction errors 43 | #[error("Transaction error: {0}")] 44 | TransactionError(String), 45 | 46 | #[error("Verbose RPC Error")] 47 | RpcSimulateTransactionResult(RpcSimulateTransactionResult), 48 | } 49 | -------------------------------------------------------------------------------- /tip-router-operator-cli/src/tx_utils.rs: -------------------------------------------------------------------------------- 1 | use solana_sdk::{instruction::Instruction, pubkey::Pubkey, transaction::Transaction}; 2 | 3 | #[allow(clippy::integer_division)] 4 | #[allow(clippy::arithmetic_side_effects)] 5 | #[allow(clippy::manual_div_ceil)] 6 | pub fn pack_transactions( 7 | instructions: Vec, 8 | payer: Pubkey, 9 | max_transaction_size: usize, 10 | ) -> Vec { 11 | let mut transactions = vec![]; 12 | let mut current_instructions = vec![]; 13 | 14 | for instruction in instructions { 15 | // Create a temporary transaction with the new instruction to measure size 16 | let mut temp_instructions = current_instructions.clone(); 17 | temp_instructions.push(instruction.clone()); 18 | let temp_transaction = Transaction::new_with_payer(&temp_instructions, Some(&payer)); 19 | let transaction_size = temp_transaction.message.serialize().len(); 20 | 21 | let estimated_base64_size = (transaction_size * 4 + 2) / 3; // Ceiling division for base64 22 | 23 | if estimated_base64_size > max_transaction_size { 24 | if !current_instructions.is_empty() { 25 | let transaction = Transaction::new_with_payer(¤t_instructions, Some(&payer)); 26 | transactions.push(transaction); 27 | } 28 | 29 | current_instructions = vec![instruction]; 30 | } else { 31 | current_instructions.push(instruction); 32 | } 33 | } 34 | 35 | if !current_instructions.is_empty() { 36 | let transaction = Transaction::new_with_payer(¤t_instructions, Some(&payer)); 37 | transactions.push(transaction); 38 | } 39 | 40 | transactions 41 | } 42 | -------------------------------------------------------------------------------- /program/src/initialize_vault_registry.rs: -------------------------------------------------------------------------------- 1 | use jito_jsm_core::loader::{load_system_account, load_system_program}; 2 | use jito_restaking_core::ncn::Ncn; 3 | use jito_tip_router_core::{ 4 | account_payer::AccountPayer, config::Config as NcnConfig, constants::MAX_REALLOC_BYTES, 5 | vault_registry::VaultRegistry, 6 | }; 7 | use solana_program::{ 8 | account_info::AccountInfo, entrypoint::ProgramResult, program_error::ProgramError, 9 | pubkey::Pubkey, 10 | }; 11 | 12 | pub fn process_initialize_vault_registry( 13 | program_id: &Pubkey, 14 | accounts: &[AccountInfo], 15 | ) -> ProgramResult { 16 | let [ncn_config, vault_registry, ncn, account_payer, system_program] = accounts else { 17 | return Err(ProgramError::NotEnoughAccountKeys); 18 | }; 19 | 20 | // Verify accounts 21 | load_system_account(vault_registry, true)?; 22 | load_system_program(system_program)?; 23 | 24 | Ncn::load(&jito_restaking_program::id(), ncn, false)?; 25 | NcnConfig::load(program_id, ncn_config, ncn.key, false)?; 26 | AccountPayer::load(program_id, account_payer, ncn.key, true)?; 27 | 28 | let (vault_registry_pda, vault_registry_bump, mut vault_registry_seeds) = 29 | VaultRegistry::find_program_address(program_id, ncn.key); 30 | vault_registry_seeds.push(vec![vault_registry_bump]); 31 | 32 | if vault_registry_pda != *vault_registry.key { 33 | return Err(ProgramError::InvalidSeeds); 34 | } 35 | 36 | AccountPayer::pay_and_create_account( 37 | program_id, 38 | ncn.key, 39 | account_payer, 40 | vault_registry, 41 | system_program, 42 | program_id, 43 | MAX_REALLOC_BYTES as usize, 44 | &vault_registry_seeds, 45 | )?; 46 | 47 | Ok(()) 48 | } 49 | -------------------------------------------------------------------------------- /clients/js/jito_tip_router/types/stakeWeights.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This code was AUTOGENERATED using the kinobi library. 3 | * Please DO NOT EDIT THIS FILE, instead use visitors 4 | * to add features, then rerun kinobi to update it. 5 | * 6 | * @see https://github.com/kinobi-so/kinobi 7 | */ 8 | 9 | import { 10 | combineCodec, 11 | getArrayDecoder, 12 | getArrayEncoder, 13 | getStructDecoder, 14 | getStructEncoder, 15 | getU128Decoder, 16 | getU128Encoder, 17 | type Codec, 18 | type Decoder, 19 | type Encoder, 20 | } from '@solana/web3.js'; 21 | import { 22 | getNcnFeeGroupWeightDecoder, 23 | getNcnFeeGroupWeightEncoder, 24 | type NcnFeeGroupWeight, 25 | type NcnFeeGroupWeightArgs, 26 | } from '.'; 27 | 28 | export type StakeWeights = { 29 | stakeWeight: bigint; 30 | ncnFeeGroupStakeWeights: Array; 31 | }; 32 | 33 | export type StakeWeightsArgs = { 34 | stakeWeight: number | bigint; 35 | ncnFeeGroupStakeWeights: Array; 36 | }; 37 | 38 | export function getStakeWeightsEncoder(): Encoder { 39 | return getStructEncoder([ 40 | ['stakeWeight', getU128Encoder()], 41 | [ 42 | 'ncnFeeGroupStakeWeights', 43 | getArrayEncoder(getNcnFeeGroupWeightEncoder(), { size: 8 }), 44 | ], 45 | ]); 46 | } 47 | 48 | export function getStakeWeightsDecoder(): Decoder { 49 | return getStructDecoder([ 50 | ['stakeWeight', getU128Decoder()], 51 | [ 52 | 'ncnFeeGroupStakeWeights', 53 | getArrayDecoder(getNcnFeeGroupWeightDecoder(), { size: 8 }), 54 | ], 55 | ]); 56 | } 57 | 58 | export function getStakeWeightsCodec(): Codec { 59 | return combineCodec(getStakeWeightsEncoder(), getStakeWeightsDecoder()); 60 | } 61 | -------------------------------------------------------------------------------- /core/src/spl_stake_pool.rs: -------------------------------------------------------------------------------- 1 | //! Minimal SPL Stake Pool utilities 2 | //! 3 | //! This module contains only the minimal code needed to interact with the SPL Stake Pool program, 4 | //! specifically for depositing SOL. This avoids depending on the full spl-stake-pool crate. 5 | 6 | use borsh::{BorshDeserialize, BorshSerialize}; 7 | use solana_program::pubkey::Pubkey; 8 | 9 | /// Seed for withdraw authority seed 10 | const AUTHORITY_WITHDRAW: &[u8] = b"withdraw"; 11 | 12 | /// Generates the withdraw authority program address for the stake pool 13 | pub fn find_withdraw_authority_program_address( 14 | program_id: &Pubkey, 15 | stake_pool_address: &Pubkey, 16 | ) -> (Pubkey, u8) { 17 | Pubkey::find_program_address( 18 | &[stake_pool_address.as_ref(), AUTHORITY_WITHDRAW], 19 | program_id, 20 | ) 21 | } 22 | 23 | /// Minimal subset of SPL Stake Pool instructions needed for this program. 24 | /// 25 | /// IMPORTANT: These variants have explicit discriminants to match the actual SPL Stake Pool program. 26 | /// DepositSol is variant #14 and DepositSolWithSlippage is variant #25 in the real enum. 27 | /// See: 28 | /// 29 | /// Note: This appears in the IDL because it's used in public functions, but only with 2 variants. 30 | #[repr(u8)] 31 | #[derive(Clone, Debug, PartialEq, Eq, BorshSerialize, BorshDeserialize)] 32 | #[borsh(use_discriminant = true)] 33 | pub enum StakePoolInstruction { 34 | /// Deposit SOL into the stake pool (variant #14) 35 | DepositSol(u64) = 14, 36 | /// Deposit SOL into the stake pool with slippage protection (variant #25) 37 | DepositSolWithSlippage { 38 | lamports_in: u64, 39 | minimum_pool_tokens_out: u64, 40 | } = 25, 41 | } 42 | -------------------------------------------------------------------------------- /clients/js/jito_tip_router/instructions/index.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This code was AUTOGENERATED using the kinobi library. 3 | * Please DO NOT EDIT THIS FILE, instead use visitors 4 | * to add features, then rerun kinobi to update it. 5 | * 6 | * @see https://github.com/kinobi-so/kinobi 7 | */ 8 | 9 | export * from './adminRegisterStMint'; 10 | export * from './adminSetConfigFees'; 11 | export * from './adminSetNewAdmin'; 12 | export * from './adminSetParameters'; 13 | export * from './adminSetStMint'; 14 | export * from './adminSetTieBreaker'; 15 | export * from './adminSetWeight'; 16 | export * from './castVote'; 17 | export * from './claimWithPayer'; 18 | export * from './closeEpochAccount'; 19 | export * from './distributeBaseNcnRewardRoute'; 20 | export * from './distributeBaseRewards'; 21 | export * from './distributeNcnOperatorRewards'; 22 | export * from './distributeNcnVaultRewards'; 23 | export * from './initializeBallotBox'; 24 | export * from './initializeBaseRewardRouter'; 25 | export * from './initializeConfig'; 26 | export * from './initializeEpochSnapshot'; 27 | export * from './initializeEpochState'; 28 | export * from './initializeNcnRewardRouter'; 29 | export * from './initializeOperatorSnapshot'; 30 | export * from './initializeVaultRegistry'; 31 | export * from './initializeWeightTable'; 32 | export * from './reallocBallotBox'; 33 | export * from './reallocBaseRewardRouter'; 34 | export * from './reallocEpochState'; 35 | export * from './reallocOperatorSnapshot'; 36 | export * from './reallocVaultRegistry'; 37 | export * from './reallocWeightTable'; 38 | export * from './registerVault'; 39 | export * from './routeBaseRewards'; 40 | export * from './routeNcnRewards'; 41 | export * from './setMerkleRoot'; 42 | export * from './snapshotVaultOperatorDelegation'; 43 | export * from './switchboardSetWeight'; 44 | -------------------------------------------------------------------------------- /clients/js/jito_tip_router/types/ballotTally.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This code was AUTOGENERATED using the kinobi library. 3 | * Please DO NOT EDIT THIS FILE, instead use visitors 4 | * to add features, then rerun kinobi to update it. 5 | * 6 | * @see https://github.com/kinobi-so/kinobi 7 | */ 8 | 9 | import { 10 | combineCodec, 11 | getStructDecoder, 12 | getStructEncoder, 13 | getU16Decoder, 14 | getU16Encoder, 15 | getU64Decoder, 16 | getU64Encoder, 17 | type Codec, 18 | type Decoder, 19 | type Encoder, 20 | } from '@solana/web3.js'; 21 | import { 22 | getBallotDecoder, 23 | getBallotEncoder, 24 | getStakeWeightsDecoder, 25 | getStakeWeightsEncoder, 26 | type Ballot, 27 | type BallotArgs, 28 | type StakeWeights, 29 | type StakeWeightsArgs, 30 | } from '.'; 31 | 32 | export type BallotTally = { 33 | index: number; 34 | ballot: Ballot; 35 | stakeWeights: StakeWeights; 36 | tally: bigint; 37 | }; 38 | 39 | export type BallotTallyArgs = { 40 | index: number; 41 | ballot: BallotArgs; 42 | stakeWeights: StakeWeightsArgs; 43 | tally: number | bigint; 44 | }; 45 | 46 | export function getBallotTallyEncoder(): Encoder { 47 | return getStructEncoder([ 48 | ['index', getU16Encoder()], 49 | ['ballot', getBallotEncoder()], 50 | ['stakeWeights', getStakeWeightsEncoder()], 51 | ['tally', getU64Encoder()], 52 | ]); 53 | } 54 | 55 | export function getBallotTallyDecoder(): Decoder { 56 | return getStructDecoder([ 57 | ['index', getU16Decoder()], 58 | ['ballot', getBallotDecoder()], 59 | ['stakeWeights', getStakeWeightsDecoder()], 60 | ['tally', getU64Decoder()], 61 | ]); 62 | } 63 | 64 | export function getBallotTallyCodec(): Codec { 65 | return combineCodec(getBallotTallyEncoder(), getBallotTallyDecoder()); 66 | } 67 | -------------------------------------------------------------------------------- /clients/js/jito_tip_router/types/ncnRewardRoute.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This code was AUTOGENERATED using the kinobi library. 3 | * Please DO NOT EDIT THIS FILE, instead use visitors 4 | * to add features, then rerun kinobi to update it. 5 | * 6 | * @see https://github.com/kinobi-so/kinobi 7 | */ 8 | 9 | import { 10 | combineCodec, 11 | getAddressDecoder, 12 | getAddressEncoder, 13 | getArrayDecoder, 14 | getArrayEncoder, 15 | getStructDecoder, 16 | getStructEncoder, 17 | type Address, 18 | type Codec, 19 | type Decoder, 20 | type Encoder, 21 | } from '@solana/web3.js'; 22 | import { 23 | getBaseRewardRouterRewardsDecoder, 24 | getBaseRewardRouterRewardsEncoder, 25 | type BaseRewardRouterRewards, 26 | type BaseRewardRouterRewardsArgs, 27 | } from '.'; 28 | 29 | export type NcnRewardRoute = { 30 | operator: Address; 31 | ncnFeeGroupRewards: Array; 32 | }; 33 | 34 | export type NcnRewardRouteArgs = { 35 | operator: Address; 36 | ncnFeeGroupRewards: Array; 37 | }; 38 | 39 | export function getNcnRewardRouteEncoder(): Encoder { 40 | return getStructEncoder([ 41 | ['operator', getAddressEncoder()], 42 | [ 43 | 'ncnFeeGroupRewards', 44 | getArrayEncoder(getBaseRewardRouterRewardsEncoder(), { size: 8 }), 45 | ], 46 | ]); 47 | } 48 | 49 | export function getNcnRewardRouteDecoder(): Decoder { 50 | return getStructDecoder([ 51 | ['operator', getAddressDecoder()], 52 | [ 53 | 'ncnFeeGroupRewards', 54 | getArrayDecoder(getBaseRewardRouterRewardsDecoder(), { size: 8 }), 55 | ], 56 | ]); 57 | } 58 | 59 | export function getNcnRewardRouteCodec(): Codec< 60 | NcnRewardRouteArgs, 61 | NcnRewardRoute 62 | > { 63 | return combineCodec(getNcnRewardRouteEncoder(), getNcnRewardRouteDecoder()); 64 | } 65 | -------------------------------------------------------------------------------- /program/src/admin_set_st_mint.rs: -------------------------------------------------------------------------------- 1 | use jito_bytemuck::AccountDeserialize; 2 | use jito_jsm_core::loader::load_signer; 3 | use jito_restaking_core::ncn::Ncn; 4 | use jito_tip_router_core::{config::Config, vault_registry::VaultRegistry}; 5 | use solana_program::{ 6 | account_info::AccountInfo, entrypoint::ProgramResult, msg, program_error::ProgramError, 7 | pubkey::Pubkey, 8 | }; 9 | 10 | pub fn process_admin_set_st_mint( 11 | program_id: &Pubkey, 12 | accounts: &[AccountInfo], 13 | st_mint: &Pubkey, 14 | ncn_fee_group: Option, 15 | reward_multiplier_bps: Option, 16 | switchboard_feed: Option, 17 | no_feed_weight: Option, 18 | ) -> ProgramResult { 19 | let [config, ncn, vault_registry, admin] = accounts else { 20 | return Err(ProgramError::NotEnoughAccountKeys); 21 | }; 22 | 23 | Config::load(program_id, config, ncn.key, false)?; 24 | VaultRegistry::load(program_id, vault_registry, ncn.key, true)?; 25 | Ncn::load(&jito_restaking_program::id(), ncn, false)?; 26 | 27 | load_signer(admin, false)?; 28 | 29 | { 30 | let ncn_data = ncn.data.borrow(); 31 | let ncn_account = Ncn::try_from_slice_unchecked(&ncn_data)?; 32 | 33 | if ncn_account.ncn_program_admin.ne(admin.key) { 34 | msg!("Admin is not the NCN program admin"); 35 | return Err(ProgramError::InvalidAccountData); 36 | } 37 | } 38 | 39 | let mut vault_registry_data = vault_registry.data.borrow_mut(); 40 | let vault_registry_account = 41 | VaultRegistry::try_from_slice_unchecked_mut(&mut vault_registry_data)?; 42 | 43 | vault_registry_account.set_st_mint( 44 | st_mint, 45 | ncn_fee_group, 46 | reward_multiplier_bps, 47 | switchboard_feed, 48 | no_feed_weight, 49 | )?; 50 | 51 | Ok(()) 52 | } 53 | -------------------------------------------------------------------------------- /integration_tests/tests/tip_router/snapshot_vault_operator_delegation.rs: -------------------------------------------------------------------------------- 1 | #[cfg(test)] 2 | mod tests { 3 | 4 | use crate::fixtures::{test_builder::TestBuilder, TestResult}; 5 | 6 | #[tokio::test] 7 | async fn test_snapshot_vault_operator_delegation() -> TestResult<()> { 8 | let mut fixture = TestBuilder::new().await; 9 | let mut vault_client = fixture.vault_program_client(); 10 | let mut tip_router_client = fixture.tip_router_client(); 11 | 12 | let test_ncn = fixture.create_initial_test_ncn(1, 1, None).await?; 13 | fixture.add_epoch_state_for_test_ncn(&test_ncn).await?; 14 | 15 | fixture.warp_slot_incremental(1000).await?; 16 | 17 | let epoch = fixture.clock().await.epoch; 18 | 19 | tip_router_client 20 | .do_full_initialize_weight_table(test_ncn.ncn_root.ncn_pubkey, epoch) 21 | .await?; 22 | 23 | let ncn = test_ncn.ncn_root.ncn_pubkey; 24 | 25 | let vault_root = test_ncn.vaults[0].clone(); 26 | let vault_address = vault_root.vault_pubkey; 27 | let vault = vault_client.get_vault(&vault_address).await?; 28 | 29 | let mint = vault.supported_mint; 30 | let weight = 100; 31 | 32 | tip_router_client 33 | .do_admin_set_weight(ncn, epoch, mint, weight) 34 | .await?; 35 | 36 | tip_router_client 37 | .do_initialize_epoch_snapshot(ncn, epoch) 38 | .await?; 39 | 40 | let operator = test_ncn.operators[0].operator_pubkey; 41 | 42 | tip_router_client 43 | .do_full_initialize_operator_snapshot(operator, ncn, epoch) 44 | .await?; 45 | 46 | tip_router_client 47 | .do_snapshot_vault_operator_delegation(vault_address, operator, ncn, epoch) 48 | .await?; 49 | 50 | Ok(()) 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /clients/rust/jito_tip_router/src/generated/accounts/config.rs: -------------------------------------------------------------------------------- 1 | use crate::generated::types::FeeConfig; 2 | use borsh::BorshDeserialize; 3 | use borsh::BorshSerialize; 4 | use solana_program::pubkey::Pubkey; 5 | 6 | #[derive(BorshSerialize, BorshDeserialize, Clone, Debug, Eq, PartialEq)] 7 | #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] 8 | pub struct Config { 9 | pub discriminator: u64, 10 | #[cfg_attr( 11 | feature = "serde", 12 | serde(with = "serde_with::As::") 13 | )] 14 | pub ncn: Pubkey, 15 | #[cfg_attr( 16 | feature = "serde", 17 | serde(with = "serde_with::As::") 18 | )] 19 | pub tie_breaker_admin: Pubkey, 20 | #[cfg_attr( 21 | feature = "serde", 22 | serde(with = "serde_with::As::") 23 | )] 24 | pub fee_admin: Pubkey, 25 | pub valid_slots_after_consensus: u64, 26 | pub epochs_before_stall: u64, 27 | pub fee_config: FeeConfig, 28 | pub bump: u8, 29 | pub epochs_after_consensus_before_close: u64, 30 | pub starting_valid_epoch: u64, 31 | #[cfg_attr(feature = "serde", serde(with = "serde_big_array::BigArray"))] 32 | pub reserved: [u8; 111], 33 | } 34 | 35 | impl Config { 36 | #[inline(always)] 37 | pub fn from_bytes(data: &[u8]) -> Result { 38 | let mut data = data; 39 | Self::deserialize(&mut data) 40 | } 41 | } 42 | 43 | impl<'a> TryFrom<&solana_program::account_info::AccountInfo<'a>> for Config { 44 | type Error = std::io::Error; 45 | 46 | fn try_from( 47 | account_info: &solana_program::account_info::AccountInfo<'a>, 48 | ) -> Result { 49 | let mut data: &[u8] = &(*account_info.data).borrow(); 50 | Self::deserialize(&mut data) 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /program/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "jito-tip-router-program" 3 | description = "Jito's MEV Tip Distribution NCN Program" 4 | version = { workspace = true } 5 | authors = { workspace = true } 6 | repository = { workspace = true } 7 | homepage = { workspace = true } 8 | license = { workspace = true } 9 | edition = { workspace = true } 10 | readme = { workspace = true } 11 | 12 | [lib] 13 | crate-type = ["cdylib", "lib"] 14 | name = "jito_tip_router_program" 15 | 16 | [features] 17 | no-entrypoint = [] 18 | no-idl = [] 19 | no-log-ix-name = [] 20 | cpi = ["no-entrypoint"] 21 | custom-heap = [] 22 | custom-panic = [] 23 | default = [] 24 | mainnet-beta = [] 25 | testnet = [] 26 | devnet = [] 27 | localhost = [] 28 | 29 | [dependencies] 30 | borsh = { workspace = true } 31 | bytemuck = { workspace = true } 32 | cfg-if = { workspace = true } 33 | const_str_to_pubkey = { workspace = true } 34 | jito-bytemuck = { workspace = true } 35 | jito-jsm-core = { workspace = true } 36 | jito-priority-fee-distribution-sdk = { workspace = true } 37 | jito-restaking-core = { workspace = true } 38 | jito-restaking-program = { workspace = true } 39 | jito-restaking-sdk = { workspace = true } 40 | jito-tip-distribution-sdk = { workspace = true } 41 | jito-tip-router-core = { workspace = true } 42 | jito-vault-core = { workspace = true } 43 | jito-vault-program = { workspace = true } 44 | jito-vault-sdk = { workspace = true } 45 | shank = { workspace = true } 46 | solana-program = { workspace = true } 47 | solana-security-txt = { workspace = true } 48 | solana-system-interface = { workspace = true } 49 | spl-associated-token-account-interface = { workspace = true } 50 | spl-token-interface = { workspace = true } 51 | switchboard-on-demand = { version = "0.10.0", default-features = false, features = ["solana-v3"] } 52 | thiserror = { workspace = true } 53 | 54 | [dev-dependencies] 55 | assert_matches = { workspace = true } 56 | -------------------------------------------------------------------------------- /program/src/admin_set_new_admin.rs: -------------------------------------------------------------------------------- 1 | use jito_bytemuck::AccountDeserialize; 2 | use jito_jsm_core::loader::load_signer; 3 | use jito_restaking_core::ncn::Ncn; 4 | use jito_tip_router_core::{ 5 | config::{Config as NcnConfig, ConfigAdminRole}, 6 | error::TipRouterError, 7 | }; 8 | use solana_program::{ 9 | account_info::AccountInfo, entrypoint::ProgramResult, msg, program_error::ProgramError, 10 | pubkey::Pubkey, 11 | }; 12 | 13 | pub fn process_admin_set_new_admin( 14 | program_id: &Pubkey, 15 | accounts: &[AccountInfo], 16 | role: ConfigAdminRole, 17 | ) -> ProgramResult { 18 | let [config, ncn_account, ncn_admin, new_admin] = accounts else { 19 | return Err(ProgramError::NotEnoughAccountKeys); 20 | }; 21 | 22 | load_signer(ncn_admin, true)?; 23 | 24 | NcnConfig::load(program_id, config, ncn_account.key, true)?; 25 | Ncn::load(&jito_restaking_program::id(), ncn_account, false)?; 26 | 27 | let mut config_data = config.try_borrow_mut_data()?; 28 | let config = NcnConfig::try_from_slice_unchecked_mut(&mut config_data)?; 29 | 30 | // Verify NCN and Admin 31 | if config.ncn != *ncn_account.key { 32 | return Err(TipRouterError::IncorrectNcn.into()); 33 | } 34 | 35 | let ncn_data = ncn_account.data.borrow(); 36 | let ncn = Ncn::try_from_slice_unchecked(&ncn_data)?; 37 | 38 | if ncn.admin != *ncn_admin.key { 39 | return Err(TipRouterError::IncorrectNcnAdmin.into()); 40 | } 41 | 42 | match role { 43 | ConfigAdminRole::FeeAdmin => { 44 | config.fee_admin = *new_admin.key; 45 | msg!("Fee admin set to {:?}", new_admin.key); 46 | } 47 | ConfigAdminRole::TieBreakerAdmin => { 48 | config.tie_breaker_admin = *new_admin.key; 49 | msg!("Tie breaker admin set to {:?}", new_admin.key); 50 | } 51 | } 52 | 53 | Ok(()) 54 | } 55 | -------------------------------------------------------------------------------- /clients/rust/jito_tip_router/src/generated/types/mod.rs: -------------------------------------------------------------------------------- 1 | //! This code was AUTOGENERATED using the kinobi library. 2 | //! Please DO NOT EDIT THIS FILE, instead use visitors 3 | //! to add features, then rerun kinobi to update it. 4 | //! 5 | //! 6 | //! 7 | 8 | pub(crate) mod r#ballot; 9 | pub(crate) mod r#ballot_tally; 10 | pub(crate) mod r#base_fee_group; 11 | pub(crate) mod r#base_reward_router_rewards; 12 | pub(crate) mod r#config_admin_role; 13 | pub(crate) mod r#epoch_account_status; 14 | pub(crate) mod r#fee; 15 | pub(crate) mod r#fee_config; 16 | pub(crate) mod r#fees; 17 | pub(crate) mod r#ncn_fee_group; 18 | pub(crate) mod r#ncn_fee_group_weight; 19 | pub(crate) mod r#ncn_reward_route; 20 | pub(crate) mod r#operator_vote; 21 | pub(crate) mod r#progress; 22 | pub(crate) mod r#st_mint_entry; 23 | pub(crate) mod r#stake_pool_instruction; 24 | pub(crate) mod r#stake_weights; 25 | pub(crate) mod r#vault_entry; 26 | pub(crate) mod r#vault_operator_stake_weight; 27 | pub(crate) mod r#vault_reward_route; 28 | pub(crate) mod r#weight_entry; 29 | 30 | pub use self::r#ballot::*; 31 | pub use self::r#ballot_tally::*; 32 | pub use self::r#base_fee_group::*; 33 | pub use self::r#base_reward_router_rewards::*; 34 | pub use self::r#config_admin_role::*; 35 | pub use self::r#epoch_account_status::*; 36 | pub use self::r#fee::*; 37 | pub use self::r#fee_config::*; 38 | pub use self::r#fees::*; 39 | pub use self::r#ncn_fee_group::*; 40 | pub use self::r#ncn_fee_group_weight::*; 41 | pub use self::r#ncn_reward_route::*; 42 | pub use self::r#operator_vote::*; 43 | pub use self::r#progress::*; 44 | pub use self::r#st_mint_entry::*; 45 | pub use self::r#stake_pool_instruction::*; 46 | pub use self::r#stake_weights::*; 47 | pub use self::r#vault_entry::*; 48 | pub use self::r#vault_operator_stake_weight::*; 49 | pub use self::r#vault_reward_route::*; 50 | pub use self::r#weight_entry::*; 51 | -------------------------------------------------------------------------------- /clients/js/jito_tip_router/types/vaultEntry.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This code was AUTOGENERATED using the kinobi library. 3 | * Please DO NOT EDIT THIS FILE, instead use visitors 4 | * to add features, then rerun kinobi to update it. 5 | * 6 | * @see https://github.com/kinobi-so/kinobi 7 | */ 8 | 9 | import { 10 | combineCodec, 11 | getAddressDecoder, 12 | getAddressEncoder, 13 | getArrayDecoder, 14 | getArrayEncoder, 15 | getStructDecoder, 16 | getStructEncoder, 17 | getU64Decoder, 18 | getU64Encoder, 19 | getU8Decoder, 20 | getU8Encoder, 21 | type Address, 22 | type Codec, 23 | type Decoder, 24 | type Encoder, 25 | } from '@solana/web3.js'; 26 | 27 | export type VaultEntry = { 28 | vault: Address; 29 | stMint: Address; 30 | vaultIndex: bigint; 31 | slotRegistered: bigint; 32 | reserved: Array; 33 | }; 34 | 35 | export type VaultEntryArgs = { 36 | vault: Address; 37 | stMint: Address; 38 | vaultIndex: number | bigint; 39 | slotRegistered: number | bigint; 40 | reserved: Array; 41 | }; 42 | 43 | export function getVaultEntryEncoder(): Encoder { 44 | return getStructEncoder([ 45 | ['vault', getAddressEncoder()], 46 | ['stMint', getAddressEncoder()], 47 | ['vaultIndex', getU64Encoder()], 48 | ['slotRegistered', getU64Encoder()], 49 | ['reserved', getArrayEncoder(getU8Encoder(), { size: 128 })], 50 | ]); 51 | } 52 | 53 | export function getVaultEntryDecoder(): Decoder { 54 | return getStructDecoder([ 55 | ['vault', getAddressDecoder()], 56 | ['stMint', getAddressDecoder()], 57 | ['vaultIndex', getU64Decoder()], 58 | ['slotRegistered', getU64Decoder()], 59 | ['reserved', getArrayDecoder(getU8Decoder(), { size: 128 })], 60 | ]); 61 | } 62 | 63 | export function getVaultEntryCodec(): Codec { 64 | return combineCodec(getVaultEntryEncoder(), getVaultEntryDecoder()); 65 | } 66 | -------------------------------------------------------------------------------- /clients/rust/jito_tip_router/src/generated/accounts/epoch_snapshot.rs: -------------------------------------------------------------------------------- 1 | //! This code was AUTOGENERATED using the kinobi library. 2 | //! Please DO NOT EDIT THIS FILE, instead use visitors 3 | //! to add features, then rerun kinobi to update it. 4 | //! 5 | //! 6 | //! 7 | 8 | use crate::generated::types::Fees; 9 | use crate::generated::types::StakeWeights; 10 | use borsh::BorshDeserialize; 11 | use borsh::BorshSerialize; 12 | use solana_program::pubkey::Pubkey; 13 | 14 | #[derive(BorshSerialize, BorshDeserialize, Clone, Debug, Eq, PartialEq)] 15 | #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] 16 | pub struct EpochSnapshot { 17 | pub discriminator: u64, 18 | #[cfg_attr( 19 | feature = "serde", 20 | serde(with = "serde_with::As::") 21 | )] 22 | pub ncn: Pubkey, 23 | pub epoch: u64, 24 | pub bump: u8, 25 | pub slot_created: u64, 26 | pub slot_finalized: u64, 27 | pub fees: Fees, 28 | pub operator_count: u64, 29 | pub vault_count: u64, 30 | pub operators_registered: u64, 31 | pub valid_operator_vault_delegations: u64, 32 | pub stake_weights: StakeWeights, 33 | #[cfg_attr(feature = "serde", serde(with = "serde_big_array::BigArray"))] 34 | pub reserved: [u8; 128], 35 | } 36 | 37 | impl EpochSnapshot { 38 | #[inline(always)] 39 | pub fn from_bytes(data: &[u8]) -> Result { 40 | let mut data = data; 41 | Self::deserialize(&mut data) 42 | } 43 | } 44 | 45 | impl<'a> TryFrom<&solana_program::account_info::AccountInfo<'a>> for EpochSnapshot { 46 | type Error = std::io::Error; 47 | 48 | fn try_from( 49 | account_info: &solana_program::account_info::AccountInfo<'a>, 50 | ) -> Result { 51 | let mut data: &[u8] = &(*account_info.data).borrow(); 52 | Self::deserialize(&mut data) 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /program/src/register_vault.rs: -------------------------------------------------------------------------------- 1 | use jito_bytemuck::AccountDeserialize; 2 | use jito_restaking_core::{ncn::Ncn, ncn_vault_ticket::NcnVaultTicket}; 3 | use jito_tip_router_core::{config::Config, vault_registry::VaultRegistry}; 4 | use jito_vault_core::vault::Vault; 5 | use solana_program::{ 6 | account_info::AccountInfo, 7 | entrypoint::ProgramResult, 8 | msg, 9 | program_error::ProgramError, 10 | pubkey::Pubkey, 11 | sysvar::{clock::Clock, Sysvar}, 12 | }; 13 | 14 | pub fn process_register_vault(program_id: &Pubkey, accounts: &[AccountInfo]) -> ProgramResult { 15 | let [config, vault_registry, ncn, vault, ncn_vault_ticket] = accounts else { 16 | return Err(ProgramError::NotEnoughAccountKeys); 17 | }; 18 | 19 | Config::load(program_id, config, ncn.key, false)?; 20 | VaultRegistry::load(program_id, vault_registry, ncn.key, true)?; 21 | Ncn::load(&jito_restaking_program::id(), ncn, false)?; 22 | Vault::load(&jito_vault_program::id(), vault, false)?; 23 | NcnVaultTicket::load( 24 | &jito_restaking_program::id(), 25 | ncn_vault_ticket, 26 | ncn, 27 | vault, 28 | false, 29 | )?; 30 | 31 | let clock = Clock::get()?; 32 | let slot = clock.slot; 33 | 34 | let mut vault_registry_data = vault_registry.try_borrow_mut_data()?; 35 | let vault_registry = VaultRegistry::try_from_slice_unchecked_mut(&mut vault_registry_data)?; 36 | 37 | let vault_data = vault.data.borrow(); 38 | let vault_account = Vault::try_from_slice_unchecked(&vault_data)?; 39 | 40 | if !vault_registry.has_st_mint(&vault_account.supported_mint) { 41 | msg!("Supported mint not registered"); 42 | return Err(ProgramError::InvalidAccountData); 43 | } 44 | 45 | vault_registry.register_vault( 46 | vault.key, 47 | &vault_account.supported_mint, 48 | vault_account.vault_index(), 49 | slot, 50 | )?; 51 | 52 | Ok(()) 53 | } 54 | -------------------------------------------------------------------------------- /integration_tests/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "jito-tip-router-integration-tests" 3 | description = "Jito's MEV Tip Distribution NCN Tests" 4 | version = { workspace = true } 5 | authors = { workspace = true } 6 | repository = { workspace = true } 7 | homepage = { workspace = true } 8 | license = { workspace = true } 9 | edition = { workspace = true } 10 | readme = { workspace = true } 11 | 12 | [dependencies] 13 | jito-tip-router-client = { workspace = true } 14 | log = "0.4.21" 15 | num-derive = { workspace = true } 16 | num-traits = { workspace = true } 17 | spl-pod = "0.7.1" 18 | 19 | [dev-dependencies] 20 | anyhow = { workspace = true } 21 | borsh = { workspace = true } 22 | bytemuck = { workspace = true } 23 | jito-bytemuck = { workspace = true } 24 | jito-jsm-core = { workspace = true } 25 | jito-priority-fee-distribution-sdk = { workspace = true } 26 | jito-restaking-core = { workspace = true } 27 | jito-restaking-program = { workspace = true } 28 | jito-restaking-sdk = { workspace = true } 29 | jito-tip-distribution-sdk = { workspace = true } 30 | jito-tip-router-core = { workspace = true } 31 | jito-tip-router-program = { workspace = true } 32 | jito-vault-core = { workspace = true } 33 | jito-vault-program = { workspace = true } 34 | jito-vault-sdk = { workspace = true } 35 | meta-merkle-tree = { workspace = true } 36 | shank = { workspace = true } 37 | solana-commitment-config = { workspace = true } 38 | solana-compute-budget-interface = { workspace = true } 39 | solana-program = { workspace = true } 40 | solana-program-test = { workspace = true } 41 | solana-sdk = { workspace = true } 42 | solana-security-txt = { workspace = true } 43 | solana-stake-interface = { workspace = true } 44 | solana-system-interface = { workspace = true } 45 | solana-vote-interface = { workspace = true } 46 | spl-associated-token-account-interface = { workspace = true } 47 | spl-token-interface = { workspace = true } 48 | thiserror = { workspace = true } 49 | tokio = { workspace = true } 50 | 51 | -------------------------------------------------------------------------------- /program/src/initialize_ballot_box.rs: -------------------------------------------------------------------------------- 1 | use jito_jsm_core::loader::{load_system_account, load_system_program}; 2 | use jito_restaking_core::ncn::Ncn; 3 | use jito_tip_router_core::{ 4 | account_payer::AccountPayer, ballot_box::BallotBox, config::Config as NcnConfig, 5 | constants::MAX_REALLOC_BYTES, epoch_marker::EpochMarker, epoch_state::EpochState, 6 | }; 7 | use solana_program::{ 8 | account_info::AccountInfo, entrypoint::ProgramResult, program_error::ProgramError, 9 | pubkey::Pubkey, 10 | }; 11 | 12 | pub fn process_initialize_ballot_box( 13 | program_id: &Pubkey, 14 | accounts: &[AccountInfo], 15 | epoch: u64, 16 | ) -> ProgramResult { 17 | let [epoch_marker, epoch_state, ncn_config, ballot_box, ncn, account_payer, system_program] = 18 | accounts 19 | else { 20 | return Err(ProgramError::NotEnoughAccountKeys); 21 | }; 22 | 23 | // Verify accounts 24 | load_system_account(ballot_box, true)?; 25 | load_system_program(system_program)?; 26 | 27 | Ncn::load(&jito_restaking_program::id(), ncn, false)?; 28 | EpochState::load_and_check_is_closing(program_id, epoch_state, ncn.key, epoch, false)?; 29 | NcnConfig::load(program_id, ncn_config, ncn.key, false)?; 30 | AccountPayer::load(program_id, account_payer, ncn.key, true)?; 31 | EpochMarker::check_dne(program_id, epoch_marker, ncn.key, epoch)?; 32 | 33 | let (ballot_box_pda, ballot_box_bump, mut ballot_box_seeds) = 34 | BallotBox::find_program_address(program_id, ncn.key, epoch); 35 | ballot_box_seeds.push(vec![ballot_box_bump]); 36 | 37 | if ballot_box_pda != *ballot_box.key { 38 | return Err(ProgramError::InvalidSeeds); 39 | } 40 | 41 | AccountPayer::pay_and_create_account( 42 | program_id, 43 | ncn.key, 44 | account_payer, 45 | ballot_box, 46 | system_program, 47 | program_id, 48 | MAX_REALLOC_BYTES as usize, 49 | &ballot_box_seeds, 50 | )?; 51 | 52 | Ok(()) 53 | } 54 | -------------------------------------------------------------------------------- /scripts/update-attributes.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const path = require('path'); 3 | 4 | // Function to recursively find all .rs files 5 | function findRustFiles(dir, fileList = []) { 6 | const files = fs.readdirSync(dir); 7 | 8 | files.forEach(file => { 9 | const filePath = path.join(dir, file); 10 | const stat = fs.statSync(filePath); 11 | 12 | if (stat.isDirectory()) { 13 | findRustFiles(filePath, fileList); 14 | } else if (path.extname(file) === '.rs') { 15 | fileList.push(filePath); 16 | } 17 | }); 18 | 19 | return fileList; 20 | } 21 | 22 | // Function to replace text in a file 23 | function replaceInFile(filePath, searchText, replaceText) { 24 | try { 25 | const content = fs.readFileSync(filePath, 'utf8'); 26 | const updatedContent = content.replace(new RegExp(searchText, 'g'), replaceText); 27 | 28 | // Only write if content changed 29 | if (content !== updatedContent) { 30 | fs.writeFileSync(filePath, updatedContent, 'utf8'); 31 | console.log(`Updated ${filePath}`); 32 | } 33 | } catch (err) { 34 | console.error(`Error processing ${filePath}:`, err); 35 | } 36 | } 37 | 38 | // Main execution 39 | try { 40 | const rustDir = path.join(__dirname, '../clients/rust'); 41 | const rustFiles = findRustFiles(rustDir); 42 | 43 | if (rustFiles.length === 0) { 44 | console.log('No .rs files found in', rustDir); 45 | process.exit(1); 46 | } 47 | 48 | // Replace text in each file 49 | const searchText = 'serde\\(with = "serde_with::As::"\\)'; 50 | const replaceText = 'serde(with = "serde_big_array::BigArray")'; 51 | 52 | rustFiles.forEach(file => { 53 | replaceInFile(file, searchText, replaceText); 54 | }); 55 | 56 | console.log('Finished processing', rustFiles.length, 'files'); 57 | } catch (err) { 58 | console.error('Script failed:', err); 59 | process.exit(1); 60 | } 61 | -------------------------------------------------------------------------------- /clients/js/jito_tip_router/types/feeConfig.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This code was AUTOGENERATED using the kinobi library. 3 | * Please DO NOT EDIT THIS FILE, instead use visitors 4 | * to add features, then rerun kinobi to update it. 5 | * 6 | * @see https://github.com/kinobi-so/kinobi 7 | */ 8 | 9 | import { 10 | combineCodec, 11 | getAddressDecoder, 12 | getAddressEncoder, 13 | getArrayDecoder, 14 | getArrayEncoder, 15 | getStructDecoder, 16 | getStructEncoder, 17 | getU16Decoder, 18 | getU16Encoder, 19 | getU8Decoder, 20 | getU8Encoder, 21 | type Address, 22 | type Codec, 23 | type Decoder, 24 | type Encoder, 25 | } from '@solana/web3.js'; 26 | import { getFeesDecoder, getFeesEncoder, type Fees, type FeesArgs } from '.'; 27 | 28 | export type FeeConfig = { 29 | blockEngineFeeBps: number; 30 | baseFeeWallets: Array
; 31 | reserved: Array; 32 | fee1: Fees; 33 | fee2: Fees; 34 | }; 35 | 36 | export type FeeConfigArgs = { 37 | blockEngineFeeBps: number; 38 | baseFeeWallets: Array
; 39 | reserved: Array; 40 | fee1: FeesArgs; 41 | fee2: FeesArgs; 42 | }; 43 | 44 | export function getFeeConfigEncoder(): Encoder { 45 | return getStructEncoder([ 46 | ['blockEngineFeeBps', getU16Encoder()], 47 | ['baseFeeWallets', getArrayEncoder(getAddressEncoder(), { size: 8 })], 48 | ['reserved', getArrayEncoder(getU8Encoder(), { size: 128 })], 49 | ['fee1', getFeesEncoder()], 50 | ['fee2', getFeesEncoder()], 51 | ]); 52 | } 53 | 54 | export function getFeeConfigDecoder(): Decoder { 55 | return getStructDecoder([ 56 | ['blockEngineFeeBps', getU16Decoder()], 57 | ['baseFeeWallets', getArrayDecoder(getAddressDecoder(), { size: 8 })], 58 | ['reserved', getArrayDecoder(getU8Decoder(), { size: 128 })], 59 | ['fee1', getFeesDecoder()], 60 | ['fee2', getFeesDecoder()], 61 | ]); 62 | } 63 | 64 | export function getFeeConfigCodec(): Codec { 65 | return combineCodec(getFeeConfigEncoder(), getFeeConfigDecoder()); 66 | } 67 | -------------------------------------------------------------------------------- /clients/js/jito_tip_router/types/weightEntry.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This code was AUTOGENERATED using the kinobi library. 3 | * Please DO NOT EDIT THIS FILE, instead use visitors 4 | * to add features, then rerun kinobi to update it. 5 | * 6 | * @see https://github.com/kinobi-so/kinobi 7 | */ 8 | 9 | import { 10 | combineCodec, 11 | getArrayDecoder, 12 | getArrayEncoder, 13 | getStructDecoder, 14 | getStructEncoder, 15 | getU128Decoder, 16 | getU128Encoder, 17 | getU64Decoder, 18 | getU64Encoder, 19 | getU8Decoder, 20 | getU8Encoder, 21 | type Codec, 22 | type Decoder, 23 | type Encoder, 24 | } from '@solana/web3.js'; 25 | import { 26 | getStMintEntryDecoder, 27 | getStMintEntryEncoder, 28 | type StMintEntry, 29 | type StMintEntryArgs, 30 | } from '.'; 31 | 32 | export type WeightEntry = { 33 | stMintEntry: StMintEntry; 34 | weight: bigint; 35 | slotSet: bigint; 36 | slotUpdated: bigint; 37 | reserved: Array; 38 | }; 39 | 40 | export type WeightEntryArgs = { 41 | stMintEntry: StMintEntryArgs; 42 | weight: number | bigint; 43 | slotSet: number | bigint; 44 | slotUpdated: number | bigint; 45 | reserved: Array; 46 | }; 47 | 48 | export function getWeightEntryEncoder(): Encoder { 49 | return getStructEncoder([ 50 | ['stMintEntry', getStMintEntryEncoder()], 51 | ['weight', getU128Encoder()], 52 | ['slotSet', getU64Encoder()], 53 | ['slotUpdated', getU64Encoder()], 54 | ['reserved', getArrayEncoder(getU8Encoder(), { size: 128 })], 55 | ]); 56 | } 57 | 58 | export function getWeightEntryDecoder(): Decoder { 59 | return getStructDecoder([ 60 | ['stMintEntry', getStMintEntryDecoder()], 61 | ['weight', getU128Decoder()], 62 | ['slotSet', getU64Decoder()], 63 | ['slotUpdated', getU64Decoder()], 64 | ['reserved', getArrayDecoder(getU8Decoder(), { size: 128 })], 65 | ]); 66 | } 67 | 68 | export function getWeightEntryCodec(): Codec { 69 | return combineCodec(getWeightEntryEncoder(), getWeightEntryDecoder()); 70 | } 71 | -------------------------------------------------------------------------------- /clients/js/jito_tip_router/types/fees.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This code was AUTOGENERATED using the kinobi library. 3 | * Please DO NOT EDIT THIS FILE, instead use visitors 4 | * to add features, then rerun kinobi to update it. 5 | * 6 | * @see https://github.com/kinobi-so/kinobi 7 | */ 8 | 9 | import { 10 | combineCodec, 11 | getArrayDecoder, 12 | getArrayEncoder, 13 | getStructDecoder, 14 | getStructEncoder, 15 | getU64Decoder, 16 | getU64Encoder, 17 | getU8Decoder, 18 | getU8Encoder, 19 | type Codec, 20 | type Decoder, 21 | type Encoder, 22 | } from '@solana/web3.js'; 23 | import { getFeeDecoder, getFeeEncoder, type Fee, type FeeArgs } from '.'; 24 | 25 | export type Fees = { 26 | activationEpoch: bigint; 27 | priorityFeeDistributionFeeBps: Fee; 28 | reserved: Array; 29 | baseFeeGroupsBps: Array; 30 | ncnFeeGroupsBps: Array; 31 | }; 32 | 33 | export type FeesArgs = { 34 | activationEpoch: number | bigint; 35 | priorityFeeDistributionFeeBps: FeeArgs; 36 | reserved: Array; 37 | baseFeeGroupsBps: Array; 38 | ncnFeeGroupsBps: Array; 39 | }; 40 | 41 | export function getFeesEncoder(): Encoder { 42 | return getStructEncoder([ 43 | ['activationEpoch', getU64Encoder()], 44 | ['priorityFeeDistributionFeeBps', getFeeEncoder()], 45 | ['reserved', getArrayEncoder(getU8Encoder(), { size: 126 })], 46 | ['baseFeeGroupsBps', getArrayEncoder(getFeeEncoder(), { size: 8 })], 47 | ['ncnFeeGroupsBps', getArrayEncoder(getFeeEncoder(), { size: 8 })], 48 | ]); 49 | } 50 | 51 | export function getFeesDecoder(): Decoder { 52 | return getStructDecoder([ 53 | ['activationEpoch', getU64Decoder()], 54 | ['priorityFeeDistributionFeeBps', getFeeDecoder()], 55 | ['reserved', getArrayDecoder(getU8Decoder(), { size: 126 })], 56 | ['baseFeeGroupsBps', getArrayDecoder(getFeeDecoder(), { size: 8 })], 57 | ['ncnFeeGroupsBps', getArrayDecoder(getFeeDecoder(), { size: 8 })], 58 | ]); 59 | } 60 | 61 | export function getFeesCodec(): Codec { 62 | return combineCodec(getFeesEncoder(), getFeesDecoder()); 63 | } 64 | -------------------------------------------------------------------------------- /clients/rust/jito_tip_router/src/generated/accounts/ballot_box.rs: -------------------------------------------------------------------------------- 1 | //! This code was AUTOGENERATED using the kinobi library. 2 | //! Please DO NOT EDIT THIS FILE, instead use visitors 3 | //! to add features, then rerun kinobi to update it. 4 | //! 5 | //! 6 | //! 7 | 8 | use crate::generated::types::Ballot; 9 | use crate::generated::types::BallotTally; 10 | use crate::generated::types::OperatorVote; 11 | use borsh::BorshDeserialize; 12 | use borsh::BorshSerialize; 13 | use solana_program::pubkey::Pubkey; 14 | 15 | #[derive(BorshSerialize, BorshDeserialize, Clone, Debug, Eq, PartialEq)] 16 | #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] 17 | pub struct BallotBox { 18 | pub discriminator: u64, 19 | #[cfg_attr( 20 | feature = "serde", 21 | serde(with = "serde_with::As::") 22 | )] 23 | pub ncn: Pubkey, 24 | pub epoch: u64, 25 | pub bump: u8, 26 | pub slot_created: u64, 27 | pub slot_consensus_reached: u64, 28 | #[cfg_attr(feature = "serde", serde(with = "serde_big_array::BigArray"))] 29 | pub reserved: [u8; 128], 30 | pub operators_voted: u64, 31 | pub unique_ballots: u64, 32 | pub winning_ballot: Ballot, 33 | #[cfg_attr(feature = "serde", serde(with = "serde_big_array::BigArray"))] 34 | pub operator_votes: [OperatorVote; 256], 35 | #[cfg_attr(feature = "serde", serde(with = "serde_big_array::BigArray"))] 36 | pub ballot_tallies: [BallotTally; 256], 37 | } 38 | 39 | impl BallotBox { 40 | #[inline(always)] 41 | pub fn from_bytes(data: &[u8]) -> Result { 42 | let mut data = data; 43 | Self::deserialize(&mut data) 44 | } 45 | } 46 | 47 | impl<'a> TryFrom<&solana_program::account_info::AccountInfo<'a>> for BallotBox { 48 | type Error = std::io::Error; 49 | 50 | fn try_from( 51 | account_info: &solana_program::account_info::AccountInfo<'a>, 52 | ) -> Result { 53 | let mut data: &[u8] = &(*account_info.data).borrow(); 54 | Self::deserialize(&mut data) 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /clients/js/jito_tip_router/types/epochAccountStatus.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This code was AUTOGENERATED using the kinobi library. 3 | * Please DO NOT EDIT THIS FILE, instead use visitors 4 | * to add features, then rerun kinobi to update it. 5 | * 6 | * @see https://github.com/kinobi-so/kinobi 7 | */ 8 | 9 | import { 10 | combineCodec, 11 | getArrayDecoder, 12 | getArrayEncoder, 13 | getStructDecoder, 14 | getStructEncoder, 15 | getU8Decoder, 16 | getU8Encoder, 17 | type Codec, 18 | type Decoder, 19 | type Encoder, 20 | } from '@solana/web3.js'; 21 | 22 | export type EpochAccountStatus = { 23 | epochState: number; 24 | weightTable: number; 25 | epochSnapshot: number; 26 | operatorSnapshot: Array; 27 | ballotBox: number; 28 | baseRewardRouter: number; 29 | ncnRewardRouter: Array; 30 | }; 31 | 32 | export type EpochAccountStatusArgs = EpochAccountStatus; 33 | 34 | export function getEpochAccountStatusEncoder(): Encoder { 35 | return getStructEncoder([ 36 | ['epochState', getU8Encoder()], 37 | ['weightTable', getU8Encoder()], 38 | ['epochSnapshot', getU8Encoder()], 39 | ['operatorSnapshot', getArrayEncoder(getU8Encoder(), { size: 256 })], 40 | ['ballotBox', getU8Encoder()], 41 | ['baseRewardRouter', getU8Encoder()], 42 | ['ncnRewardRouter', getArrayEncoder(getU8Encoder(), { size: 2048 })], 43 | ]); 44 | } 45 | 46 | export function getEpochAccountStatusDecoder(): Decoder { 47 | return getStructDecoder([ 48 | ['epochState', getU8Decoder()], 49 | ['weightTable', getU8Decoder()], 50 | ['epochSnapshot', getU8Decoder()], 51 | ['operatorSnapshot', getArrayDecoder(getU8Decoder(), { size: 256 })], 52 | ['ballotBox', getU8Decoder()], 53 | ['baseRewardRouter', getU8Decoder()], 54 | ['ncnRewardRouter', getArrayDecoder(getU8Decoder(), { size: 2048 })], 55 | ]); 56 | } 57 | 58 | export function getEpochAccountStatusCodec(): Codec< 59 | EpochAccountStatusArgs, 60 | EpochAccountStatus 61 | > { 62 | return combineCodec( 63 | getEpochAccountStatusEncoder(), 64 | getEpochAccountStatusDecoder() 65 | ); 66 | } 67 | -------------------------------------------------------------------------------- /clients/rust/jito_tip_router/src/generated/accounts/operator_snapshot.rs: -------------------------------------------------------------------------------- 1 | use crate::generated::types::StakeWeights; 2 | use crate::generated::types::VaultOperatorStakeWeight; 3 | use borsh::BorshDeserialize; 4 | use borsh::BorshSerialize; 5 | use solana_program::pubkey::Pubkey; 6 | 7 | #[derive(BorshSerialize, BorshDeserialize, Clone, Debug, Eq, PartialEq)] 8 | #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] 9 | pub struct OperatorSnapshot { 10 | pub discriminator: u64, 11 | #[cfg_attr( 12 | feature = "serde", 13 | serde(with = "serde_with::As::") 14 | )] 15 | pub operator: Pubkey, 16 | #[cfg_attr( 17 | feature = "serde", 18 | serde(with = "serde_with::As::") 19 | )] 20 | pub ncn: Pubkey, 21 | pub ncn_epoch: u64, 22 | pub bump: u8, 23 | pub slot_created: u64, 24 | pub slot_finalized: u64, 25 | pub is_active: bool, 26 | pub ncn_operator_index: u64, 27 | pub operator_index: u64, 28 | pub operator_fee_bps: u16, 29 | pub vault_operator_delegation_count: u64, 30 | pub vault_operator_delegations_registered: u64, 31 | pub valid_operator_vault_delegations: u64, 32 | pub stake_weights: StakeWeights, 33 | #[cfg_attr(feature = "serde", serde(with = "serde_big_array::BigArray"))] 34 | pub reserved: [u8; 256], 35 | #[cfg_attr(feature = "serde", serde(with = "serde_big_array::BigArray"))] 36 | pub vault_operator_stake_weight: [VaultOperatorStakeWeight; 64], 37 | } 38 | 39 | impl OperatorSnapshot { 40 | #[inline(always)] 41 | pub fn from_bytes(data: &[u8]) -> Result { 42 | let mut data = data; 43 | Self::deserialize(&mut data) 44 | } 45 | } 46 | 47 | impl<'a> TryFrom<&solana_program::account_info::AccountInfo<'a>> for OperatorSnapshot { 48 | type Error = std::io::Error; 49 | 50 | fn try_from( 51 | account_info: &solana_program::account_info::AccountInfo<'a>, 52 | ) -> Result { 53 | let mut data: &[u8] = &(*account_info.data).borrow(); 54 | Self::deserialize(&mut data) 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /clients/js/jito_tip_router/types/operatorVote.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This code was AUTOGENERATED using the kinobi library. 3 | * Please DO NOT EDIT THIS FILE, instead use visitors 4 | * to add features, then rerun kinobi to update it. 5 | * 6 | * @see https://github.com/kinobi-so/kinobi 7 | */ 8 | 9 | import { 10 | combineCodec, 11 | fixDecoderSize, 12 | fixEncoderSize, 13 | getAddressDecoder, 14 | getAddressEncoder, 15 | getBytesDecoder, 16 | getBytesEncoder, 17 | getStructDecoder, 18 | getStructEncoder, 19 | getU16Decoder, 20 | getU16Encoder, 21 | getU64Decoder, 22 | getU64Encoder, 23 | type Address, 24 | type Codec, 25 | type Decoder, 26 | type Encoder, 27 | type ReadonlyUint8Array, 28 | } from '@solana/web3.js'; 29 | import { 30 | getStakeWeightsDecoder, 31 | getStakeWeightsEncoder, 32 | type StakeWeights, 33 | type StakeWeightsArgs, 34 | } from '.'; 35 | 36 | export type OperatorVote = { 37 | operator: Address; 38 | slotVoted: bigint; 39 | stakeWeights: StakeWeights; 40 | ballotIndex: number; 41 | reserved: ReadonlyUint8Array; 42 | }; 43 | 44 | export type OperatorVoteArgs = { 45 | operator: Address; 46 | slotVoted: number | bigint; 47 | stakeWeights: StakeWeightsArgs; 48 | ballotIndex: number; 49 | reserved: ReadonlyUint8Array; 50 | }; 51 | 52 | export function getOperatorVoteEncoder(): Encoder { 53 | return getStructEncoder([ 54 | ['operator', getAddressEncoder()], 55 | ['slotVoted', getU64Encoder()], 56 | ['stakeWeights', getStakeWeightsEncoder()], 57 | ['ballotIndex', getU16Encoder()], 58 | ['reserved', fixEncoderSize(getBytesEncoder(), 64)], 59 | ]); 60 | } 61 | 62 | export function getOperatorVoteDecoder(): Decoder { 63 | return getStructDecoder([ 64 | ['operator', getAddressDecoder()], 65 | ['slotVoted', getU64Decoder()], 66 | ['stakeWeights', getStakeWeightsDecoder()], 67 | ['ballotIndex', getU16Decoder()], 68 | ['reserved', fixDecoderSize(getBytesDecoder(), 64)], 69 | ]); 70 | } 71 | 72 | export function getOperatorVoteCodec(): Codec { 73 | return combineCodec(getOperatorVoteEncoder(), getOperatorVoteDecoder()); 74 | } 75 | -------------------------------------------------------------------------------- /clients/rust/jito_tip_router/src/generated/accounts/base_reward_router.rs: -------------------------------------------------------------------------------- 1 | //! This code was AUTOGENERATED using the kinobi library. 2 | //! Please DO NOT EDIT THIS FILE, instead use visitors 3 | //! to add features, then rerun kinobi to update it. 4 | //! 5 | //! 6 | //! 7 | 8 | use crate::generated::types::BaseRewardRouterRewards; 9 | use crate::generated::types::NcnRewardRoute; 10 | use borsh::BorshDeserialize; 11 | use borsh::BorshSerialize; 12 | use solana_program::pubkey::Pubkey; 13 | 14 | #[derive(BorshSerialize, BorshDeserialize, Clone, Debug, Eq, PartialEq)] 15 | #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] 16 | pub struct BaseRewardRouter { 17 | pub discriminator: u64, 18 | #[cfg_attr( 19 | feature = "serde", 20 | serde(with = "serde_with::As::") 21 | )] 22 | pub ncn: Pubkey, 23 | pub epoch: u64, 24 | pub bump: u8, 25 | pub slot_created: u64, 26 | pub total_rewards: u64, 27 | pub reward_pool: u64, 28 | pub rewards_processed: u64, 29 | #[cfg_attr(feature = "serde", serde(with = "serde_big_array::BigArray"))] 30 | pub reserved: [u8; 128], 31 | pub last_ncn_group_index: u8, 32 | pub last_vote_index: u16, 33 | pub last_rewards_to_process: u64, 34 | pub base_fee_group_rewards: [BaseRewardRouterRewards; 8], 35 | pub ncn_fee_group_rewards: [BaseRewardRouterRewards; 8], 36 | #[cfg_attr(feature = "serde", serde(with = "serde_big_array::BigArray"))] 37 | pub ncn_fee_group_reward_routes: [NcnRewardRoute; 256], 38 | } 39 | 40 | impl BaseRewardRouter { 41 | #[inline(always)] 42 | pub fn from_bytes(data: &[u8]) -> Result { 43 | let mut data = data; 44 | Self::deserialize(&mut data) 45 | } 46 | } 47 | 48 | impl<'a> TryFrom<&solana_program::account_info::AccountInfo<'a>> for BaseRewardRouter { 49 | type Error = std::io::Error; 50 | 51 | fn try_from( 52 | account_info: &solana_program::account_info::AccountInfo<'a>, 53 | ) -> Result { 54 | let mut data: &[u8] = &(*account_info.data).borrow(); 55 | Self::deserialize(&mut data) 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /integration_tests/tests/fixtures/mod.rs: -------------------------------------------------------------------------------- 1 | use meta_merkle_tree::{error::MerkleTreeError, generated_merkle_tree::MerkleRootGeneratorError}; 2 | use solana_program::{instruction::InstructionError, program_error::ProgramError}; 3 | use solana_program_test::BanksClientError; 4 | use solana_sdk::transaction::TransactionError; 5 | use thiserror::Error; 6 | pub mod generated_switchboard_accounts; 7 | pub mod priority_fee_distribution_client; 8 | pub mod restaking_client; 9 | pub mod spl_stake_pool; 10 | pub mod stake_pool_client; 11 | pub mod test_builder; 12 | pub mod tip_distribution_client; 13 | pub mod tip_router_client; 14 | pub mod vault_client; 15 | 16 | pub type TestResult = Result; 17 | 18 | #[derive(Error, Debug)] 19 | pub enum TestError { 20 | #[error(transparent)] 21 | BanksClientError(#[from] BanksClientError), 22 | #[error(transparent)] 23 | ProgramError(#[from] ProgramError), 24 | #[error(transparent)] 25 | MerkleTreeError(#[from] MerkleTreeError), 26 | #[error(transparent)] 27 | MerkleRootGeneratorError(#[from] MerkleRootGeneratorError), 28 | #[error(transparent)] 29 | IoError(#[from] std::io::Error), 30 | } 31 | 32 | impl From for TestError { 33 | fn from(err: anyhow::Error) -> Self { 34 | Self::ProgramError(ProgramError::Custom(err.to_string().parse().unwrap_or(0))) 35 | } 36 | } 37 | 38 | impl TestError { 39 | pub fn to_transaction_error(&self) -> Option { 40 | match self { 41 | Self::BanksClientError(e) => match e { 42 | BanksClientError::TransactionError(e) => Some(e.clone()), 43 | BanksClientError::SimulationError { err, .. } => Some(err.clone()), 44 | _ => None, 45 | }, 46 | Self::ProgramError(_) => None, 47 | _ => None, 48 | } 49 | } 50 | } 51 | 52 | #[inline(always)] 53 | #[track_caller] 54 | pub fn assert_ix_error(test_error: Result, ix_error: InstructionError) { 55 | assert!(test_error.is_err()); 56 | assert_eq!( 57 | test_error.err().unwrap().to_transaction_error().unwrap(), 58 | TransactionError::InstructionError(0, ix_error) 59 | ); 60 | } 61 | -------------------------------------------------------------------------------- /program/src/admin_set_config_fees.rs: -------------------------------------------------------------------------------- 1 | use jito_bytemuck::AccountDeserialize; 2 | use jito_jsm_core::loader::load_signer; 3 | use jito_restaking_core::ncn::Ncn; 4 | use jito_tip_router_core::{ 5 | base_fee_group::BaseFeeGroup, config::Config, error::TipRouterError, ncn_fee_group::NcnFeeGroup, 6 | }; 7 | use solana_program::{ 8 | account_info::AccountInfo, clock::Clock, entrypoint::ProgramResult, 9 | program_error::ProgramError, pubkey::Pubkey, sysvar::Sysvar, 10 | }; 11 | 12 | #[allow(clippy::too_many_arguments)] 13 | pub fn process_admin_set_config_fees( 14 | program_id: &Pubkey, 15 | accounts: &[AccountInfo], 16 | new_block_engine_fee_bps: Option, 17 | base_fee_group: Option, 18 | new_base_fee_wallet: Option, 19 | new_base_fee_bps: Option, 20 | ncn_fee_group: Option, 21 | new_ncn_fee_bps: Option, 22 | new_priority_fee_distribution_fee_bps: Option, 23 | ) -> ProgramResult { 24 | let [config, ncn_account, fee_admin] = accounts else { 25 | return Err(ProgramError::NotEnoughAccountKeys); 26 | }; 27 | 28 | load_signer(fee_admin, true)?; 29 | 30 | Config::load(program_id, config, ncn_account.key, true)?; 31 | Ncn::load(&jito_restaking_program::id(), ncn_account, false)?; 32 | 33 | let epoch = Clock::get()?.epoch; 34 | 35 | let mut config_data = config.try_borrow_mut_data()?; 36 | let config = Config::try_from_slice_unchecked_mut(&mut config_data)?; 37 | 38 | // Verify NCN and Admin 39 | if config.ncn != *ncn_account.key { 40 | return Err(TipRouterError::IncorrectNcn.into()); 41 | } 42 | 43 | if config.fee_admin != *fee_admin.key { 44 | return Err(TipRouterError::IncorrectFeeAdmin.into()); 45 | } 46 | 47 | let base_fee_group = base_fee_group.map(BaseFeeGroup::try_from).transpose()?; 48 | let ncn_fee_group = ncn_fee_group.map(NcnFeeGroup::try_from).transpose()?; 49 | 50 | config.fee_config.update_fee_config( 51 | new_block_engine_fee_bps, 52 | base_fee_group, 53 | new_base_fee_wallet, 54 | new_base_fee_bps, 55 | ncn_fee_group, 56 | new_ncn_fee_bps, 57 | epoch, 58 | new_priority_fee_distribution_fee_bps, 59 | )?; 60 | 61 | Ok(()) 62 | } 63 | -------------------------------------------------------------------------------- /integration_tests/tests/tip_router/initialize_ballot_box.rs: -------------------------------------------------------------------------------- 1 | #[cfg(test)] 2 | mod tests { 3 | 4 | use jito_tip_router_core::{ 5 | ballot_box::BallotBox, 6 | constants::{DEFAULT_CONSENSUS_REACHED_SLOT, MAX_REALLOC_BYTES}, 7 | }; 8 | 9 | use crate::fixtures::{test_builder::TestBuilder, TestResult}; 10 | 11 | #[tokio::test] 12 | async fn test_initialize_ballot_box() -> TestResult<()> { 13 | let mut fixture = TestBuilder::new().await; 14 | let mut tip_router_client = fixture.tip_router_client(); 15 | 16 | let test_ncn = fixture.create_initial_test_ncn(1, 1, None).await?; 17 | 18 | fixture.add_epoch_state_for_test_ncn(&test_ncn).await?; 19 | 20 | fixture.warp_slot_incremental(1000).await?; 21 | 22 | let epoch = fixture.clock().await.epoch; 23 | 24 | let ncn = test_ncn.ncn_root.ncn_pubkey; 25 | 26 | let num_reallocs = (jito_tip_router_core::ballot_box::BallotBox::SIZE as f64 27 | / jito_tip_router_core::constants::MAX_REALLOC_BYTES as f64) 28 | .ceil() as u64 29 | - 1; 30 | 31 | tip_router_client 32 | .do_initialize_ballot_box(ncn, epoch) 33 | .await?; 34 | 35 | let address = 36 | BallotBox::find_program_address(&jito_tip_router_program::id(), &ncn, epoch).0; 37 | let raw_account = fixture.get_account(&address).await?.unwrap(); 38 | assert_eq!(raw_account.data.len(), MAX_REALLOC_BYTES as usize); 39 | assert_eq!(raw_account.owner, jito_tip_router_program::id()); 40 | assert_eq!(raw_account.data[0], 0); 41 | 42 | tip_router_client 43 | .do_realloc_ballot_box(ncn, epoch, num_reallocs) 44 | .await?; 45 | 46 | let ballot_box = tip_router_client.get_ballot_box(ncn, epoch).await?; 47 | 48 | assert_eq!(ballot_box.epoch(), epoch); 49 | assert_eq!(ballot_box.unique_ballots(), 0); 50 | assert_eq!(ballot_box.operators_voted(), 0); 51 | assert!(!ballot_box.is_consensus_reached()); 52 | assert_eq!( 53 | ballot_box.slot_consensus_reached(), 54 | DEFAULT_CONSENSUS_REACHED_SLOT 55 | ); 56 | assert!(ballot_box.get_winning_ballot_tally().is_err(),); 57 | 58 | Ok(()) 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /clients/rust/jito_tip_router/src/generated/accounts/ncn_reward_router.rs: -------------------------------------------------------------------------------- 1 | //! This code was AUTOGENERATED using the kinobi library. 2 | //! Please DO NOT EDIT THIS FILE, instead use visitors 3 | //! to add features, then rerun kinobi to update it. 4 | //! 5 | //! 6 | //! 7 | 8 | use crate::generated::types::NcnFeeGroup; 9 | use crate::generated::types::VaultRewardRoute; 10 | use borsh::BorshDeserialize; 11 | use borsh::BorshSerialize; 12 | use solana_program::pubkey::Pubkey; 13 | 14 | #[derive(BorshSerialize, BorshDeserialize, Clone, Debug, Eq, PartialEq)] 15 | #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] 16 | pub struct NcnRewardRouter { 17 | pub discriminator: u64, 18 | pub ncn_fee_group: NcnFeeGroup, 19 | #[cfg_attr( 20 | feature = "serde", 21 | serde(with = "serde_with::As::") 22 | )] 23 | pub operator: Pubkey, 24 | #[cfg_attr( 25 | feature = "serde", 26 | serde(with = "serde_with::As::") 27 | )] 28 | pub ncn: Pubkey, 29 | pub epoch: u64, 30 | pub bump: u8, 31 | pub slot_created: u64, 32 | pub ncn_operator_index: u64, 33 | pub total_rewards: u64, 34 | pub reward_pool: u64, 35 | pub rewards_processed: u64, 36 | pub operator_rewards: u64, 37 | #[cfg_attr(feature = "serde", serde(with = "serde_big_array::BigArray"))] 38 | pub reserved: [u8; 128], 39 | pub last_rewards_to_process: u64, 40 | pub last_vault_operator_delegation_index: u16, 41 | #[cfg_attr(feature = "serde", serde(with = "serde_big_array::BigArray"))] 42 | pub vault_reward_routes: [VaultRewardRoute; 64], 43 | } 44 | 45 | impl NcnRewardRouter { 46 | #[inline(always)] 47 | pub fn from_bytes(data: &[u8]) -> Result { 48 | let mut data = data; 49 | Self::deserialize(&mut data) 50 | } 51 | } 52 | 53 | impl<'a> TryFrom<&solana_program::account_info::AccountInfo<'a>> for NcnRewardRouter { 54 | type Error = std::io::Error; 55 | 56 | fn try_from( 57 | account_info: &solana_program::account_info::AccountInfo<'a>, 58 | ) -> Result { 59 | let mut data: &[u8] = &(*account_info.data).borrow(); 60 | Self::deserialize(&mut data) 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /program/src/admin_set_tie_breaker.rs: -------------------------------------------------------------------------------- 1 | use jito_bytemuck::AccountDeserialize; 2 | use jito_jsm_core::loader::load_signer; 3 | use jito_restaking_core::ncn::Ncn; 4 | use jito_tip_router_core::{ 5 | ballot_box::BallotBox, config::Config as NcnConfig, epoch_state::EpochState, 6 | error::TipRouterError, 7 | }; 8 | use solana_program::{ 9 | account_info::AccountInfo, clock::Clock, entrypoint::ProgramResult, msg, 10 | program_error::ProgramError, pubkey::Pubkey, sysvar::Sysvar, 11 | }; 12 | 13 | pub fn process_admin_set_tie_breaker( 14 | program_id: &Pubkey, 15 | accounts: &[AccountInfo], 16 | meta_merkle_root: &[u8; 32], 17 | epoch: u64, 18 | ) -> ProgramResult { 19 | let [epoch_state, ncn_config, ballot_box, ncn, tie_breaker_admin] = accounts else { 20 | return Err(ProgramError::NotEnoughAccountKeys); 21 | }; 22 | 23 | EpochState::load(program_id, epoch_state, ncn.key, epoch, true)?; 24 | NcnConfig::load(program_id, ncn_config, ncn.key, false)?; 25 | BallotBox::load(program_id, ballot_box, ncn.key, epoch, true)?; 26 | Ncn::load(&jito_restaking_program::id(), ncn, false)?; 27 | load_signer(tie_breaker_admin, false)?; 28 | 29 | let ncn_config_data = ncn_config.data.borrow(); 30 | let ncn_config = NcnConfig::try_from_slice_unchecked(&ncn_config_data)?; 31 | 32 | if ncn_config.tie_breaker_admin.ne(tie_breaker_admin.key) { 33 | msg!("Tie breaker admin invalid"); 34 | return Err(TipRouterError::TieBreakerAdminInvalid.into()); 35 | } 36 | 37 | let mut ballot_box_data = ballot_box.data.borrow_mut(); 38 | let ballot_box_account = BallotBox::try_from_slice_unchecked_mut(&mut ballot_box_data)?; 39 | 40 | let clock = Clock::get()?; 41 | let current_epoch = clock.epoch; 42 | 43 | ballot_box_account.set_tie_breaker_ballot( 44 | meta_merkle_root, 45 | current_epoch, 46 | ncn_config.epochs_before_stall(), 47 | )?; 48 | 49 | // Update Epoch State 50 | { 51 | let slot = clock.slot; 52 | let mut epoch_state_data = epoch_state.try_borrow_mut_data()?; 53 | let epoch_state_account = EpochState::try_from_slice_unchecked_mut(&mut epoch_state_data)?; 54 | epoch_state_account 55 | .update_set_tie_breaker(ballot_box_account.is_consensus_reached(), slot)?; 56 | } 57 | 58 | Ok(()) 59 | } 60 | -------------------------------------------------------------------------------- /clients/js/jito_tip_router/types/stMintEntry.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This code was AUTOGENERATED using the kinobi library. 3 | * Please DO NOT EDIT THIS FILE, instead use visitors 4 | * to add features, then rerun kinobi to update it. 5 | * 6 | * @see https://github.com/kinobi-so/kinobi 7 | */ 8 | 9 | import { 10 | combineCodec, 11 | getAddressDecoder, 12 | getAddressEncoder, 13 | getArrayDecoder, 14 | getArrayEncoder, 15 | getStructDecoder, 16 | getStructEncoder, 17 | getU128Decoder, 18 | getU128Encoder, 19 | getU64Decoder, 20 | getU64Encoder, 21 | getU8Decoder, 22 | getU8Encoder, 23 | type Address, 24 | type Codec, 25 | type Decoder, 26 | type Encoder, 27 | } from '@solana/web3.js'; 28 | import { 29 | getNcnFeeGroupDecoder, 30 | getNcnFeeGroupEncoder, 31 | type NcnFeeGroup, 32 | type NcnFeeGroupArgs, 33 | } from '.'; 34 | 35 | export type StMintEntry = { 36 | stMint: Address; 37 | ncnFeeGroup: NcnFeeGroup; 38 | rewardMultiplierBps: bigint; 39 | switchboardFeed: Address; 40 | noFeedWeight: bigint; 41 | reserved: Array; 42 | }; 43 | 44 | export type StMintEntryArgs = { 45 | stMint: Address; 46 | ncnFeeGroup: NcnFeeGroupArgs; 47 | rewardMultiplierBps: number | bigint; 48 | switchboardFeed: Address; 49 | noFeedWeight: number | bigint; 50 | reserved: Array; 51 | }; 52 | 53 | export function getStMintEntryEncoder(): Encoder { 54 | return getStructEncoder([ 55 | ['stMint', getAddressEncoder()], 56 | ['ncnFeeGroup', getNcnFeeGroupEncoder()], 57 | ['rewardMultiplierBps', getU64Encoder()], 58 | ['switchboardFeed', getAddressEncoder()], 59 | ['noFeedWeight', getU128Encoder()], 60 | ['reserved', getArrayEncoder(getU8Encoder(), { size: 128 })], 61 | ]); 62 | } 63 | 64 | export function getStMintEntryDecoder(): Decoder { 65 | return getStructDecoder([ 66 | ['stMint', getAddressDecoder()], 67 | ['ncnFeeGroup', getNcnFeeGroupDecoder()], 68 | ['rewardMultiplierBps', getU64Decoder()], 69 | ['switchboardFeed', getAddressDecoder()], 70 | ['noFeedWeight', getU128Decoder()], 71 | ['reserved', getArrayDecoder(getU8Decoder(), { size: 128 })], 72 | ]); 73 | } 74 | 75 | export function getStMintEntryCodec(): Codec { 76 | return combineCodec(getStMintEntryEncoder(), getStMintEntryDecoder()); 77 | } 78 | -------------------------------------------------------------------------------- /program/src/admin_register_st_mint.rs: -------------------------------------------------------------------------------- 1 | use jito_bytemuck::AccountDeserialize; 2 | use jito_jsm_core::loader::{load_signer, load_token_mint}; 3 | use jito_restaking_core::ncn::Ncn; 4 | use jito_tip_router_core::{ 5 | config::Config, ncn_fee_group::NcnFeeGroup, vault_registry::VaultRegistry, 6 | }; 7 | use solana_program::{ 8 | account_info::AccountInfo, entrypoint::ProgramResult, msg, program_error::ProgramError, 9 | pubkey::Pubkey, 10 | }; 11 | 12 | pub fn process_admin_register_st_mint( 13 | program_id: &Pubkey, 14 | accounts: &[AccountInfo], 15 | ncn_fee_group: u8, 16 | reward_multiplier_bps: u64, 17 | switchboard_feed: Option, 18 | no_feed_weight: Option, 19 | ) -> ProgramResult { 20 | let [config, ncn, st_mint, vault_registry, admin] = accounts else { 21 | return Err(ProgramError::NotEnoughAccountKeys); 22 | }; 23 | 24 | Config::load(program_id, config, ncn.key, false)?; 25 | VaultRegistry::load(program_id, vault_registry, ncn.key, true)?; 26 | Ncn::load(&jito_restaking_program::id(), ncn, false)?; 27 | 28 | load_token_mint(st_mint)?; 29 | 30 | load_signer(admin, false)?; 31 | 32 | { 33 | let ncn_data = ncn.data.borrow(); 34 | let ncn_account = Ncn::try_from_slice_unchecked(&ncn_data)?; 35 | 36 | if ncn_account.ncn_program_admin.ne(admin.key) { 37 | msg!("Admin is not the NCN program admin"); 38 | return Err(ProgramError::InvalidAccountData); 39 | } 40 | } 41 | 42 | let mut vault_registry_data = vault_registry.data.borrow_mut(); 43 | let vault_registry_account = 44 | VaultRegistry::try_from_slice_unchecked_mut(&mut vault_registry_data)?; 45 | 46 | let ncn_fee_group = NcnFeeGroup::try_from(ncn_fee_group)?; 47 | 48 | let switchboard_feed = switchboard_feed.unwrap_or_default(); 49 | let no_feed_weight = no_feed_weight.unwrap_or_default(); 50 | 51 | if switchboard_feed.eq(&Pubkey::default()) && no_feed_weight == 0 { 52 | msg!("Either switchboard feed or no feed weight must be set"); 53 | return Err(ProgramError::InvalidArgument); 54 | } 55 | 56 | vault_registry_account.register_st_mint( 57 | st_mint.key, 58 | ncn_fee_group, 59 | reward_multiplier_bps, 60 | &switchboard_feed, 61 | no_feed_weight, 62 | )?; 63 | 64 | Ok(()) 65 | } 66 | -------------------------------------------------------------------------------- /integration_tests/tests/tip_router/initialize_ncn_reward_router.rs: -------------------------------------------------------------------------------- 1 | #[cfg(test)] 2 | mod tests { 3 | 4 | use jito_tip_router_core::ncn_fee_group::NcnFeeGroup; 5 | use solana_sdk::pubkey::Pubkey; 6 | 7 | use crate::fixtures::{test_builder::TestBuilder, TestResult}; 8 | 9 | #[tokio::test] 10 | async fn test_initialize_ncn_reward_router() -> TestResult<()> { 11 | let mut fixture = TestBuilder::new().await; 12 | let mut tip_router_client = fixture.tip_router_client(); 13 | 14 | let test_ncn = fixture.create_initial_test_ncn(1, 1, None).await?; 15 | 16 | ///// TipRouter Setup ///// 17 | fixture.warp_slot_incremental(1000).await?; 18 | 19 | fixture.snapshot_test_ncn(&test_ncn).await?; 20 | fixture.vote_test_ncn(&test_ncn).await?; 21 | ////// 22 | 23 | let clock = fixture.clock().await; 24 | let slot = clock.slot; 25 | let epoch = clock.epoch; 26 | let ncn = test_ncn.ncn_root.ncn_pubkey; 27 | let operator = test_ncn.operators[0].operator_pubkey; 28 | let ncn_fee_group = NcnFeeGroup::default(); 29 | 30 | // Initialize NCN reward router 31 | tip_router_client 32 | .do_initialize_ncn_reward_router(ncn_fee_group, ncn, operator, epoch) 33 | .await?; 34 | 35 | // Get NCN reward router and verify initialization 36 | let ncn_reward_router = tip_router_client 37 | .get_ncn_reward_router(ncn_fee_group, operator, ncn, epoch) 38 | .await?; 39 | 40 | // Verify initial state 41 | assert_eq!(*ncn_reward_router.ncn(), ncn); 42 | assert_eq!(*ncn_reward_router.operator(), operator); 43 | assert_eq!(ncn_reward_router.epoch(), epoch); 44 | assert_eq!(ncn_reward_router.slot_created(), slot); 45 | assert_eq!(ncn_reward_router.reward_pool(), 0); 46 | assert_eq!(ncn_reward_router.rewards_processed(), 0); 47 | assert_eq!(ncn_reward_router.total_rewards(), 0); 48 | assert_eq!(ncn_reward_router.operator_rewards(), 0); 49 | 50 | // Verify a default vault reward route exists and is empty 51 | assert_eq!(ncn_reward_router.vault_reward_routes()[0].rewards(), 0); 52 | assert_eq!( 53 | ncn_reward_router.vault_reward_routes()[0].vault(), 54 | Pubkey::default() 55 | ); 56 | 57 | Ok(()) 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /program/src/initialize_epoch_state.rs: -------------------------------------------------------------------------------- 1 | use jito_bytemuck::AccountDeserialize; 2 | use jito_jsm_core::loader::{load_system_account, load_system_program}; 3 | use jito_restaking_core::ncn::Ncn; 4 | use jito_tip_router_core::{ 5 | account_payer::AccountPayer, config::Config, constants::MAX_REALLOC_BYTES, 6 | epoch_marker::EpochMarker, epoch_state::EpochState, 7 | }; 8 | use solana_program::{ 9 | account_info::AccountInfo, clock::Clock, entrypoint::ProgramResult, msg, 10 | program_error::ProgramError, pubkey::Pubkey, sysvar::Sysvar, 11 | }; 12 | 13 | pub fn process_initialize_epoch_state( 14 | program_id: &Pubkey, 15 | accounts: &[AccountInfo], 16 | epoch: u64, 17 | ) -> ProgramResult { 18 | let [epoch_marker, epoch_state, config, ncn, account_payer, system_program] = accounts else { 19 | return Err(ProgramError::NotEnoughAccountKeys); 20 | }; 21 | 22 | // Check epoch cannot be in the future 23 | if epoch > Clock::get()?.epoch { 24 | return Err(ProgramError::InvalidArgument); 25 | } 26 | 27 | // Verify accounts 28 | load_system_account(epoch_state, true)?; 29 | load_system_program(system_program)?; 30 | 31 | Ncn::load(&jito_restaking_program::id(), ncn, false)?; 32 | Config::load(program_id, config, ncn.key, false)?; 33 | AccountPayer::load(program_id, account_payer, ncn.key, true)?; 34 | EpochMarker::check_dne(program_id, epoch_marker, ncn.key, epoch)?; 35 | 36 | let config_data = config.try_borrow_data()?; 37 | let config_account = Config::try_from_slice_unchecked(&config_data)?; 38 | if config_account.starting_valid_epoch() > epoch { 39 | msg!("This epoch is before the starting_valid_epoch"); 40 | return Err(ProgramError::InvalidArgument); 41 | } 42 | 43 | let (epoch_state_pda, epoch_state_bump, mut epoch_state_seeds) = 44 | EpochState::find_program_address(program_id, ncn.key, epoch); 45 | epoch_state_seeds.push(vec![epoch_state_bump]); 46 | 47 | if epoch_state_pda != *epoch_state.key { 48 | return Err(ProgramError::InvalidSeeds); 49 | } 50 | 51 | AccountPayer::pay_and_create_account( 52 | program_id, 53 | ncn.key, 54 | account_payer, 55 | epoch_state, 56 | system_program, 57 | program_id, 58 | MAX_REALLOC_BYTES as usize, 59 | &epoch_state_seeds, 60 | )?; 61 | 62 | Ok(()) 63 | } 64 | -------------------------------------------------------------------------------- /core/src/constants.rs: -------------------------------------------------------------------------------- 1 | use solana_program::{ 2 | clock::DEFAULT_SLOTS_PER_EPOCH, entrypoint::MAX_PERMITTED_DATA_INCREASE, pubkey, pubkey::Pubkey, 3 | }; 4 | use spl_math::precise_number::PreciseNumber; 5 | 6 | use crate::error::TipRouterError; 7 | 8 | pub const MAX_FEE_BPS: u64 = 10_000; 9 | pub const MAX_ST_MINTS: usize = 64; 10 | pub const MAX_VAULTS: usize = 64; 11 | pub const MAX_OPERATORS: usize = 256; 12 | pub const MIN_EPOCHS_BEFORE_STALL: u64 = 1; 13 | pub const MAX_EPOCHS_BEFORE_STALL: u64 = 50; 14 | pub const MIN_EPOCHS_AFTER_CONSENSUS_BEFORE_CLOSE: u64 = 10; 15 | pub const MAX_EPOCHS_AFTER_CONSENSUS_BEFORE_CLOSE: u64 = 100; 16 | pub const MIN_VALID_SLOTS_AFTER_CONSENSUS: u64 = 1000; 17 | pub const MAX_VALID_SLOTS_AFTER_CONSENSUS: u64 = 50 * DEFAULT_SLOTS_PER_EPOCH; 18 | const PRECISE_CONSENSUS_NUMERATOR: u128 = 2; 19 | const PRECISE_CONSENSUS_DENOMINATOR: u128 = 3; 20 | pub fn precise_consensus() -> Result { 21 | PreciseNumber::new(PRECISE_CONSENSUS_NUMERATOR) 22 | .ok_or(TipRouterError::NewPreciseNumberError)? 23 | .checked_div( 24 | &PreciseNumber::new(PRECISE_CONSENSUS_DENOMINATOR) 25 | .ok_or(TipRouterError::NewPreciseNumberError)?, 26 | ) 27 | .ok_or(TipRouterError::DenominatorIsZero) 28 | } 29 | 30 | pub const ID: Pubkey = pubkey!("RouterBmuRBkPUbgEDMtdvTZ75GBdSREZR5uGUxxxpb"); 31 | pub const DEFAULT_CONSENSUS_REACHED_SLOT: u64 = u64::MAX; 32 | pub const MAX_REALLOC_BYTES: u64 = MAX_PERMITTED_DATA_INCREASE as u64; 33 | 34 | pub const WEIGHT_PRECISION: u128 = 1_000_000_000; 35 | pub const SWITCHBOARD_MAX_STALE_SLOTS: u64 = 100; 36 | pub const JTO_SOL_FEED: Pubkey = pubkey!("5S7ErPSkFmyXuq2aE3rZ6ofwVyZpwzUt6w7m6kqekvMe"); 37 | pub const JITOSOL_SOL_FEED: Pubkey = pubkey!("4Z1SLH9g4ikNBV8uP2ZctEouqjYmVqB2Tz5SZxKYBN7z"); 38 | 39 | pub const JITOSOL_MINT: Pubkey = pubkey!("J1toso1uCk3RLmjorhTtrVwY9HJ7X8V9yYac6Y7kGCPn"); 40 | pub const JITOSOL_POOL_ADDRESS: Pubkey = pubkey!("Jito4APyf642JPZPx3hGc6WWJ8zPKtRbRs4P815Awbb"); 41 | pub const JITOSOL_POOL_MANAGER: Pubkey = pubkey!("5eosrve6LktMZgVNszYzebgmmC7BjLK8NoWyRQtcmGTF"); 42 | pub const JITOSOL_POOL_FEE: Pubkey = pubkey!("5eosrve6LktMZgVNszYzebgmmC7BjLK8NoWyRQtcmGTF"); 43 | pub const JITOSOL_RESERVE_STAKE: Pubkey = pubkey!("BgKUXdS29YcHCFrPm5M8oLHiTzZaMDjsebggjoaQ6KFL"); 44 | 45 | // There is only one of these 46 | pub const SWITCHBOARD_QUEUE: Pubkey = pubkey!("A43DyUGA7s8eXPxqEjJY6EBu1KKbNgfxF8h17VAHn13w"); 47 | -------------------------------------------------------------------------------- /clients/js/jito_tip_router/types/vaultOperatorStakeWeight.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This code was AUTOGENERATED using the kinobi library. 3 | * Please DO NOT EDIT THIS FILE, instead use visitors 4 | * to add features, then rerun kinobi to update it. 5 | * 6 | * @see https://github.com/kinobi-so/kinobi 7 | */ 8 | 9 | import { 10 | combineCodec, 11 | fixDecoderSize, 12 | fixEncoderSize, 13 | getAddressDecoder, 14 | getAddressEncoder, 15 | getBytesDecoder, 16 | getBytesEncoder, 17 | getStructDecoder, 18 | getStructEncoder, 19 | getU64Decoder, 20 | getU64Encoder, 21 | type Address, 22 | type Codec, 23 | type Decoder, 24 | type Encoder, 25 | type ReadonlyUint8Array, 26 | } from '@solana/web3.js'; 27 | import { 28 | getNcnFeeGroupDecoder, 29 | getNcnFeeGroupEncoder, 30 | getStakeWeightsDecoder, 31 | getStakeWeightsEncoder, 32 | type NcnFeeGroup, 33 | type NcnFeeGroupArgs, 34 | type StakeWeights, 35 | type StakeWeightsArgs, 36 | } from '.'; 37 | 38 | export type VaultOperatorStakeWeight = { 39 | vault: Address; 40 | vaultIndex: bigint; 41 | ncnFeeGroup: NcnFeeGroup; 42 | stakeWeight: StakeWeights; 43 | reserved: ReadonlyUint8Array; 44 | }; 45 | 46 | export type VaultOperatorStakeWeightArgs = { 47 | vault: Address; 48 | vaultIndex: number | bigint; 49 | ncnFeeGroup: NcnFeeGroupArgs; 50 | stakeWeight: StakeWeightsArgs; 51 | reserved: ReadonlyUint8Array; 52 | }; 53 | 54 | export function getVaultOperatorStakeWeightEncoder(): Encoder { 55 | return getStructEncoder([ 56 | ['vault', getAddressEncoder()], 57 | ['vaultIndex', getU64Encoder()], 58 | ['ncnFeeGroup', getNcnFeeGroupEncoder()], 59 | ['stakeWeight', getStakeWeightsEncoder()], 60 | ['reserved', fixEncoderSize(getBytesEncoder(), 32)], 61 | ]); 62 | } 63 | 64 | export function getVaultOperatorStakeWeightDecoder(): Decoder { 65 | return getStructDecoder([ 66 | ['vault', getAddressDecoder()], 67 | ['vaultIndex', getU64Decoder()], 68 | ['ncnFeeGroup', getNcnFeeGroupDecoder()], 69 | ['stakeWeight', getStakeWeightsDecoder()], 70 | ['reserved', fixDecoderSize(getBytesDecoder(), 32)], 71 | ]); 72 | } 73 | 74 | export function getVaultOperatorStakeWeightCodec(): Codec< 75 | VaultOperatorStakeWeightArgs, 76 | VaultOperatorStakeWeight 77 | > { 78 | return combineCodec( 79 | getVaultOperatorStakeWeightEncoder(), 80 | getVaultOperatorStakeWeightDecoder() 81 | ); 82 | } 83 | -------------------------------------------------------------------------------- /program/src/admin_set_weight.rs: -------------------------------------------------------------------------------- 1 | use jito_bytemuck::AccountDeserialize; 2 | use jito_jsm_core::loader::load_signer; 3 | use jito_restaking_core::ncn::Ncn; 4 | use jito_tip_router_core::{ 5 | epoch_state::EpochState, error::TipRouterError, weight_table::WeightTable, 6 | }; 7 | use solana_program::{ 8 | account_info::AccountInfo, clock::Clock, entrypoint::ProgramResult, msg, 9 | program_error::ProgramError, pubkey::Pubkey, sysvar::Sysvar, 10 | }; 11 | 12 | /// Updates weight table 13 | pub fn process_admin_set_weight( 14 | program_id: &Pubkey, 15 | accounts: &[AccountInfo], 16 | st_mint: &Pubkey, 17 | epoch: u64, 18 | weight: u128, 19 | ) -> ProgramResult { 20 | let [epoch_state, ncn, weight_table, weight_table_admin] = accounts else { 21 | return Err(ProgramError::NotEnoughAccountKeys); 22 | }; 23 | 24 | Ncn::load(&jito_restaking_program::id(), ncn, false)?; 25 | let ncn_weight_table_admin = { 26 | let ncn_data = ncn.data.borrow(); 27 | let ncn = Ncn::try_from_slice_unchecked(&ncn_data)?; 28 | ncn.weight_table_admin 29 | }; 30 | 31 | load_signer(weight_table_admin, true)?; 32 | EpochState::load(program_id, epoch_state, ncn.key, epoch, true)?; 33 | WeightTable::load(program_id, weight_table, ncn.key, epoch, true)?; 34 | 35 | if ncn_weight_table_admin.ne(weight_table_admin.key) { 36 | msg!("Vault update delegations ticket is not at the correct PDA"); 37 | return Err(TipRouterError::IncorrectWeightTableAdmin.into()); 38 | } 39 | 40 | let mut weight_table_data = weight_table.try_borrow_mut_data()?; 41 | let weight_table_account = WeightTable::try_from_slice_unchecked_mut(&mut weight_table_data)?; 42 | 43 | weight_table_account.check_table_initialized()?; 44 | if weight_table_account.finalized() { 45 | msg!("Weight table is finalized"); 46 | return Err(ProgramError::InvalidAccountData); 47 | } 48 | 49 | weight_table_account.set_weight(st_mint, weight, Clock::get()?.slot)?; 50 | 51 | // Update Epoch State 52 | { 53 | let mut epoch_state_data = epoch_state.try_borrow_mut_data()?; 54 | let epoch_state_account = EpochState::try_from_slice_unchecked_mut(&mut epoch_state_data)?; 55 | epoch_state_account.update_set_weight( 56 | weight_table_account.weight_count() as u64, 57 | weight_table_account.st_mint_count() as u64, 58 | ); 59 | } 60 | 61 | Ok(()) 62 | } 63 | -------------------------------------------------------------------------------- /clients/rust/jito_tip_router/src/generated/accounts/epoch_state.rs: -------------------------------------------------------------------------------- 1 | //! This code was AUTOGENERATED using the kinobi library. 2 | //! Please DO NOT EDIT THIS FILE, instead use visitors 3 | //! to add features, then rerun kinobi to update it. 4 | //! 5 | //! 6 | //! 7 | 8 | use crate::generated::types::EpochAccountStatus; 9 | use crate::generated::types::Progress; 10 | use borsh::BorshDeserialize; 11 | use borsh::BorshSerialize; 12 | use solana_program::pubkey::Pubkey; 13 | 14 | #[derive(BorshSerialize, BorshDeserialize, Clone, Debug, Eq, PartialEq)] 15 | #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] 16 | pub struct EpochState { 17 | pub discriminator: u64, 18 | #[cfg_attr( 19 | feature = "serde", 20 | serde(with = "serde_with::As::") 21 | )] 22 | pub ncn: Pubkey, 23 | pub epoch: u64, 24 | pub bump: u8, 25 | pub slot_created: u64, 26 | pub was_tie_breaker_set: bool, 27 | pub slot_consensus_reached: u64, 28 | pub operator_count: u64, 29 | pub vault_count: u64, 30 | pub account_status: EpochAccountStatus, 31 | pub set_weight_progress: Progress, 32 | pub epoch_snapshot_progress: Progress, 33 | #[cfg_attr(feature = "serde", serde(with = "serde_big_array::BigArray"))] 34 | pub operator_snapshot_progress: [Progress; 256], 35 | pub voting_progress: Progress, 36 | pub validation_progress: Progress, 37 | pub upload_progress: Progress, 38 | pub total_distribution_progress: Progress, 39 | pub base_distribution_progress: Progress, 40 | #[cfg_attr(feature = "serde", serde(with = "serde_big_array::BigArray"))] 41 | pub ncn_distribution_progress: [Progress; 2048], 42 | pub is_closing: bool, 43 | #[cfg_attr(feature = "serde", serde(with = "serde_big_array::BigArray"))] 44 | pub reserved: [u8; 1023], 45 | } 46 | 47 | impl EpochState { 48 | #[inline(always)] 49 | pub fn from_bytes(data: &[u8]) -> Result { 50 | let mut data = data; 51 | Self::deserialize(&mut data) 52 | } 53 | } 54 | 55 | impl<'a> TryFrom<&solana_program::account_info::AccountInfo<'a>> for EpochState { 56 | type Error = std::io::Error; 57 | 58 | fn try_from( 59 | account_info: &solana_program::account_info::AccountInfo<'a>, 60 | ) -> Result { 61 | let mut data: &[u8] = &(*account_info.data).borrow(); 62 | Self::deserialize(&mut data) 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /.github/workflows/build-container-images.yaml: -------------------------------------------------------------------------------- 1 | name: Build Container Images 2 | 3 | on: 4 | workflow_dispatch: 5 | inputs: 6 | service_config: 7 | description: 'Service configuration' 8 | required: true 9 | default: 'ncn-keeper' 10 | type: choice 11 | options: 12 | - ncn-keeper 13 | - operator 14 | 15 | env: 16 | REGISTRY: ghcr.io 17 | 18 | jobs: 19 | build-container: 20 | runs-on: big-runner-1 21 | 22 | steps: 23 | - name: Checkout repository 24 | uses: actions/checkout@v4 25 | 26 | - name: Set up Docker Buildx 27 | uses: docker/setup-buildx-action@v3 28 | 29 | - name: Log in to Container Registry 30 | uses: docker/login-action@v3 31 | with: 32 | registry: ${{ env.REGISTRY }} 33 | username: ${{ github.actor }} 34 | password: ${{ secrets.GITHUB_TOKEN }} 35 | 36 | - name: Set image configuration 37 | id: config 38 | run: | 39 | case "${{ github.event.inputs.service_config }}" in 40 | "ncn-keeper") 41 | echo "target=jito-tip-router-ncn-keeper" >> $GITHUB_OUTPUT 42 | echo "image_name=jito-tip-router-ncn-keeper" >> $GITHUB_OUTPUT 43 | echo "dockerfile_path=./cli/Dockerfile" >> $GITHUB_OUTPUT 44 | ;; 45 | "operator") 46 | echo "target=tip-router-operator-cli" >> $GITHUB_OUTPUT 47 | echo "image_name=tip-router-operator-cli" >> $GITHUB_OUTPUT 48 | echo "dockerfile_path=./tip-router-operator-cli/Dockerfile" >> $GITHUB_OUTPUT 49 | ;; 50 | esac 51 | 52 | - name: Extract metadata 53 | id: meta 54 | uses: docker/metadata-action@v5 55 | with: 56 | images: ${{ env.REGISTRY }}/${{ github.repository }}/${{ steps.config.outputs.image_name }} 57 | tags: | 58 | type=raw,value=${{ github.ref_name }}-${{ github.event.inputs.service_config }}-{{sha}} 59 | type=raw,value=${{ github.ref_name }}-${{ github.event.inputs.service_config }}-latest 60 | 61 | - name: Build and push container 62 | uses: docker/build-push-action@v5 63 | with: 64 | context: . 65 | file: ${{ steps.config.outputs.dockerfile_path }} 66 | target: ${{ steps.config.outputs.target }} 67 | platforms: linux/amd64 68 | push: true 69 | tags: ${{ steps.meta.outputs.tags }} 70 | labels: ${{ steps.meta.outputs.labels }} 71 | cache-from: type=gha 72 | cache-to: type=gha,mode=max 73 | -------------------------------------------------------------------------------- /program/src/realloc_vault_registry.rs: -------------------------------------------------------------------------------- 1 | use jito_bytemuck::{AccountDeserialize, Discriminator}; 2 | use jito_jsm_core::loader::load_system_program; 3 | use jito_restaking_core::ncn::Ncn; 4 | use jito_tip_router_core::{ 5 | account_payer::AccountPayer, config::Config as NcnConfig, utils::get_new_size, 6 | vault_registry::VaultRegistry, 7 | }; 8 | use solana_program::{ 9 | account_info::AccountInfo, entrypoint::ProgramResult, msg, program_error::ProgramError, 10 | pubkey::Pubkey, 11 | }; 12 | 13 | pub fn process_realloc_vault_registry( 14 | program_id: &Pubkey, 15 | accounts: &[AccountInfo], 16 | ) -> ProgramResult { 17 | let [ncn_config, vault_registry, ncn, account_payer, system_program] = accounts else { 18 | return Err(ProgramError::NotEnoughAccountKeys); 19 | }; 20 | 21 | // Verify accounts 22 | load_system_program(system_program)?; 23 | Ncn::load(&jito_restaking_program::id(), ncn, false)?; 24 | NcnConfig::load(program_id, ncn_config, ncn.key, false)?; 25 | AccountPayer::load(program_id, account_payer, ncn.key, true)?; 26 | 27 | let (vault_registry_pda, vault_registry_bump, mut vault_registry_seeds) = 28 | VaultRegistry::find_program_address(program_id, ncn.key); 29 | vault_registry_seeds.push(vec![vault_registry_bump]); 30 | 31 | if vault_registry_pda != *vault_registry.key { 32 | return Err(ProgramError::InvalidSeeds); 33 | } 34 | 35 | if vault_registry.data_len() < VaultRegistry::SIZE { 36 | let new_size = get_new_size(vault_registry.data_len(), VaultRegistry::SIZE)?; 37 | msg!( 38 | "Reallocating vault registry from {} bytes to {} bytes", 39 | vault_registry.data_len(), 40 | new_size 41 | ); 42 | 43 | AccountPayer::pay_and_realloc( 44 | program_id, 45 | ncn.key, 46 | account_payer, 47 | vault_registry, 48 | new_size, 49 | )?; 50 | } 51 | 52 | let should_initialize = vault_registry.data_len() >= VaultRegistry::SIZE 53 | && vault_registry.try_borrow_data()?[0] != VaultRegistry::DISCRIMINATOR; 54 | 55 | if should_initialize { 56 | let mut vault_registry_data = vault_registry.try_borrow_mut_data()?; 57 | vault_registry_data[0] = VaultRegistry::DISCRIMINATOR; 58 | let vault_registry_account = 59 | VaultRegistry::try_from_slice_unchecked_mut(&mut vault_registry_data)?; 60 | vault_registry_account.initialize(ncn.key, vault_registry_bump); 61 | } 62 | 63 | Ok(()) 64 | } 65 | -------------------------------------------------------------------------------- /integration_tests/tests/tip_router/initialize_base_reward_router.rs: -------------------------------------------------------------------------------- 1 | #[cfg(test)] 2 | mod tests { 3 | 4 | use jito_tip_router_core::{ 5 | base_reward_router::BaseRewardRouter, constants::MAX_REALLOC_BYTES, 6 | }; 7 | 8 | use crate::fixtures::{test_builder::TestBuilder, TestResult}; 9 | 10 | #[tokio::test] 11 | async fn test_initialize_base_reward_router() -> TestResult<()> { 12 | let mut fixture = TestBuilder::new().await; 13 | let mut tip_router_client = fixture.tip_router_client(); 14 | 15 | let test_ncn = fixture.create_initial_test_ncn(1, 1, None).await?; 16 | 17 | ///// TipRouter Setup ///// 18 | fixture.warp_slot_incremental(1000).await?; 19 | 20 | fixture.snapshot_test_ncn(&test_ncn).await?; 21 | fixture.vote_test_ncn(&test_ncn).await?; 22 | ////// 23 | 24 | let clock = fixture.clock().await; 25 | let epoch = clock.epoch; 26 | let slot = clock.slot; 27 | let ncn = test_ncn.ncn_root.ncn_pubkey; 28 | 29 | // Initialize base reward router 30 | tip_router_client 31 | .do_initialize_base_reward_router(ncn, epoch) 32 | .await?; 33 | 34 | // Check initial size is MAX_REALLOC_BYTES 35 | let address = 36 | BaseRewardRouter::find_program_address(&jito_tip_router_program::id(), &ncn, epoch).0; 37 | let raw_account = fixture.get_account(&address).await?.unwrap(); 38 | assert_eq!(raw_account.data.len(), MAX_REALLOC_BYTES as usize); 39 | assert_eq!(raw_account.owner, jito_tip_router_program::id()); 40 | assert_eq!(raw_account.data[0], 0); 41 | 42 | // Calculate number of reallocs needed 43 | let num_reallocs = 44 | (BaseRewardRouter::SIZE as f64 / MAX_REALLOC_BYTES as f64).ceil() as u64 - 1; 45 | 46 | // Realloc to full size 47 | tip_router_client 48 | .do_realloc_base_reward_router(ncn, epoch, num_reallocs) 49 | .await?; 50 | 51 | // Get base reward router and verify it was initialized correctly 52 | let base_reward_router = tip_router_client.get_base_reward_router(ncn, epoch).await?; 53 | 54 | // Verify initial state 55 | assert_eq!(base_reward_router.reward_pool(), 0); 56 | assert_eq!(base_reward_router.rewards_processed(), 0); 57 | assert_eq!(base_reward_router.total_rewards(), 0); 58 | assert_eq!(base_reward_router.ncn(), &ncn); 59 | assert_eq!(base_reward_router.slot_created(), slot); 60 | 61 | Ok(()) 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /program/src/realloc_epoch_state.rs: -------------------------------------------------------------------------------- 1 | use jito_bytemuck::{AccountDeserialize, Discriminator}; 2 | use jito_jsm_core::loader::load_system_program; 3 | use jito_restaking_core::ncn::Ncn; 4 | use jito_tip_router_core::{ 5 | account_payer::AccountPayer, config::Config, epoch_state::EpochState, utils::get_new_size, 6 | }; 7 | use solana_program::{ 8 | account_info::AccountInfo, clock::Clock, entrypoint::ProgramResult, msg, 9 | program_error::ProgramError, pubkey::Pubkey, sysvar::Sysvar, 10 | }; 11 | 12 | /// Reallocates the epoch state account to its full size. 13 | /// This is needed due to Solana's account size limits during initialization. 14 | pub fn process_realloc_epoch_state( 15 | program_id: &Pubkey, 16 | accounts: &[AccountInfo], 17 | epoch: u64, 18 | ) -> ProgramResult { 19 | let [epoch_state, config, ncn, account_payer, system_program] = accounts else { 20 | return Err(ProgramError::NotEnoughAccountKeys); 21 | }; 22 | 23 | load_system_program(system_program)?; 24 | Ncn::load(&jito_restaking_program::id(), ncn, false)?; 25 | Config::load(program_id, config, ncn.key, false)?; 26 | AccountPayer::load(program_id, account_payer, ncn.key, true)?; 27 | 28 | let (epoch_state_pda, epoch_state_bump, _) = 29 | EpochState::find_program_address(program_id, ncn.key, epoch); 30 | 31 | if epoch_state_pda != *epoch_state.key { 32 | msg!("Ballot box account is not at the correct PDA"); 33 | return Err(ProgramError::InvalidAccountData); 34 | } 35 | 36 | if epoch_state.data_len() < EpochState::SIZE { 37 | let new_size = get_new_size(epoch_state.data_len(), EpochState::SIZE)?; 38 | msg!( 39 | "Reallocating epoch state from {} bytes to {} bytes", 40 | epoch_state.data_len(), 41 | new_size 42 | ); 43 | AccountPayer::pay_and_realloc(program_id, ncn.key, account_payer, epoch_state, new_size)?; 44 | } 45 | 46 | let should_initialize = epoch_state.data_len() >= EpochState::SIZE 47 | && epoch_state.try_borrow_data()?[0] != EpochState::DISCRIMINATOR; 48 | 49 | if should_initialize { 50 | let mut epoch_state_data = epoch_state.try_borrow_mut_data()?; 51 | epoch_state_data[0] = EpochState::DISCRIMINATOR; 52 | let epoch_state_account = EpochState::try_from_slice_unchecked_mut(&mut epoch_state_data)?; 53 | epoch_state_account.initialize(ncn.key, epoch, epoch_state_bump, Clock::get()?.slot); 54 | 55 | epoch_state_account.update_realloc_epoch_state(); 56 | } 57 | 58 | Ok(()) 59 | } 60 | -------------------------------------------------------------------------------- /pipe_test_output.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # File paths 4 | FULL_OUTPUT="tests.output" 5 | PROGRAM_ERRORS="program_errors.json" 6 | TEST_ERRORS="test_errors.output" 7 | 8 | # Colors 9 | GREEN='\033[0;32m' 10 | RED='\033[0;31m' 11 | BLUE='\033[0;34m' 12 | NC='\033[0m' # No Color 13 | 14 | # Help text in heredoc format for better readability 15 | usage() { 16 | cat << EOF 17 | To use this script, pipe the output of the test command to it. 18 | 19 | Examples: 20 | cargo nextest run --all-features -E 'not test(bpf)' |& ./pipe_test_output.sh 21 | cargo build-sbf --sbf-out-dir integration_tests/tests/fixtures && SBPF_OUT_DIR=integration_tests/tests/fixtures cargo nextest run jito-tip-router-integration-tests::tests tip_router::simulation_tests::tests::simulation_test |& ./pipe_test_output.sh 22 | EOF 23 | } 24 | 25 | # Check if there's input on stdin 26 | if [ -t 0 ]; then 27 | usage 28 | exit 1 29 | fi 30 | 31 | rm -f "$FULL_OUTPUT" "$PROGRAM_ERRORS" "$TEST_ERRORS" 32 | 33 | # Process input from stdin and save it to temp file 34 | cat > "$FULL_OUTPUT" 35 | 36 | # Process the output file to extract error logs 37 | process_program_logs() { 38 | # Get just the first occurrence of logs array 39 | sed -n '0,/logs: \[/p;/\], units_consumed/{p;q;}' "$FULL_OUTPUT" | \ 40 | # Remove the "logs: [" prefix and trailing content 41 | sed '1s/^.*logs: \[/[/' | \ 42 | sed '$s/\], units_consumed.*$/]/' > "$PROGRAM_ERRORS" 43 | } 44 | 45 | # Process the output file to extract test output 46 | process_test_logs() { 47 | sed -n '/--- STDOUT/,/--- STDERR/p' "$FULL_OUTPUT" | \ 48 | # Remove the STDERR line 49 | sed '$d' > "$TEST_ERRORS" 50 | } 51 | 52 | # Process the output 53 | process_program_logs 54 | process_test_logs 55 | 56 | echo " -------- FORMATTING DONE --------" 57 | echo "Test output saved to $FULL_OUTPUT" 58 | echo "Program error logs extracted to $PROGRAM_ERRORS" 59 | echo "Test error logs extracted to $TEST_ERRORS" 60 | echo " " 61 | echo " ---------- PROGRAM ERRORS ----------" 62 | echo " " 63 | cat "$PROGRAM_ERRORS" 64 | echo " " 65 | echo " ---------- TEST ERRORS ----------" 66 | cat "$TEST_ERRORS" 67 | echo " " 68 | echo " ---------- ORIGINAL OUTPUT ----------" 69 | echo " " 70 | # Color the output 71 | cat "$FULL_OUTPUT" | sed \ 72 | -e "s/PASS/$(echo -e "${GREEN}PASS${NC}")/g" \ 73 | -e "s/FAIL/$(echo -e "${RED}FAIL${NC}")/g" \ 74 | -e "s/Running/$(echo -e "${BLUE}Running${NC}")/g" \ 75 | -e "s/Starting/$(echo -e "${BLUE}Starting${NC}")/g" \ 76 | -e "s/Summary/$(echo -e "${BLUE}Summary${NC}")/g" 77 | echo " " -------------------------------------------------------------------------------- /integration_tests/tests/tip_router/set_tie_breaker.rs: -------------------------------------------------------------------------------- 1 | #[cfg(test)] 2 | mod tests { 3 | 4 | use jito_tip_router_core::{ballot_box::Ballot, constants::DEFAULT_CONSENSUS_REACHED_SLOT}; 5 | 6 | use crate::fixtures::{test_builder::TestBuilder, TestResult}; 7 | 8 | #[tokio::test] 9 | async fn test_set_tie_breaker() -> TestResult<()> { 10 | let mut fixture = TestBuilder::new().await; 11 | let mut tip_router_client = fixture.tip_router_client(); 12 | 13 | // Each operator gets 50% voting share 14 | let test_ncn = fixture.create_initial_test_ncn(2, 1, None).await?; 15 | 16 | ///// TipRouter Setup ///// 17 | fixture.snapshot_test_ncn(&test_ncn).await?; 18 | 19 | let clock = fixture.clock().await; 20 | let epoch = clock.epoch; 21 | let ncn = test_ncn.ncn_root.ncn_pubkey; 22 | 23 | tip_router_client 24 | .do_full_initialize_ballot_box(ncn, epoch) 25 | .await?; 26 | 27 | let meta_merkle_root = [1; 32]; 28 | 29 | let operator = test_ncn.operators[0].operator_pubkey; 30 | let operator_admin = &test_ncn.operators[0].operator_admin; 31 | 32 | // Cast a vote so that this vote is one of the valid options 33 | // Gets to 50% consensus weight 34 | tip_router_client 35 | .do_cast_vote(ncn, operator, operator_admin, meta_merkle_root, epoch) 36 | .await?; 37 | 38 | let ballot_box = tip_router_client.get_ballot_box(ncn, epoch).await?; 39 | assert!(ballot_box.has_ballot(&Ballot::new(&meta_merkle_root))); 40 | assert_eq!( 41 | ballot_box.slot_consensus_reached(), 42 | DEFAULT_CONSENSUS_REACHED_SLOT 43 | ); 44 | assert!(!ballot_box.is_consensus_reached()); 45 | 46 | // Wait a bunch of epochs for voting window to expire (TODO use the exact length) 47 | fixture.warp_slot_incremental(1000000).await?; 48 | 49 | tip_router_client 50 | .do_admin_set_tie_breaker(ncn, meta_merkle_root, epoch) 51 | .await?; 52 | 53 | let ballot_box = tip_router_client.get_ballot_box(ncn, epoch).await?; 54 | 55 | let ballot = Ballot::new(&meta_merkle_root); 56 | assert!(ballot_box.has_ballot(&ballot)); 57 | assert_eq!( 58 | *ballot_box.get_winning_ballot_tally().unwrap().ballot(), 59 | ballot 60 | ); 61 | // No official consensus reached so no slot set 62 | assert_eq!( 63 | ballot_box.slot_consensus_reached(), 64 | DEFAULT_CONSENSUS_REACHED_SLOT 65 | ); 66 | assert!(ballot_box.is_consensus_reached()); 67 | 68 | Ok(()) 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /tip_payment_sdk/src/lib.rs: -------------------------------------------------------------------------------- 1 | use anyhow::Result; 2 | use borsh::{BorshDeserialize, BorshSerialize}; 3 | use solana_program::pubkey::Pubkey; 4 | use std::str::FromStr; 5 | 6 | pub const CONFIG_ACCOUNT_SEED: &[u8] = b"CONFIG_ACCOUNT"; 7 | pub const TIP_ACCOUNT_SEED_0: &[u8] = b"TIP_ACCOUNT_0"; 8 | pub const TIP_ACCOUNT_SEED_1: &[u8] = b"TIP_ACCOUNT_1"; 9 | pub const TIP_ACCOUNT_SEED_2: &[u8] = b"TIP_ACCOUNT_2"; 10 | pub const TIP_ACCOUNT_SEED_3: &[u8] = b"TIP_ACCOUNT_3"; 11 | pub const TIP_ACCOUNT_SEED_4: &[u8] = b"TIP_ACCOUNT_4"; 12 | pub const TIP_ACCOUNT_SEED_5: &[u8] = b"TIP_ACCOUNT_5"; 13 | pub const TIP_ACCOUNT_SEED_6: &[u8] = b"TIP_ACCOUNT_6"; 14 | pub const TIP_ACCOUNT_SEED_7: &[u8] = b"TIP_ACCOUNT_7"; 15 | 16 | pub const HEADER_SIZE: usize = 8; 17 | // Expected size: 89 (std::mem::size_of is off for Config, hardcode it for now) 18 | pub const CONFIG_SIZE: usize = HEADER_SIZE + 32 + 32 + 8 + 9; 19 | // Expected size: 8 20 | pub const TIP_PAYMENT_ACCOUNT_SIZE: usize = HEADER_SIZE + std::mem::size_of::(); 21 | 22 | #[derive(BorshSerialize, BorshDeserialize)] 23 | pub struct InitBumps { 24 | pub config: u8, 25 | pub tip_payment_account_0: u8, 26 | pub tip_payment_account_1: u8, 27 | pub tip_payment_account_2: u8, 28 | pub tip_payment_account_3: u8, 29 | pub tip_payment_account_4: u8, 30 | pub tip_payment_account_5: u8, 31 | pub tip_payment_account_6: u8, 32 | pub tip_payment_account_7: u8, 33 | } 34 | 35 | #[derive(BorshSerialize, BorshDeserialize)] 36 | pub struct Config { 37 | /// The account claiming tips from the mev_payment accounts. 38 | pub tip_receiver: Pubkey, 39 | 40 | /// Block builder that receives a % of fees 41 | pub block_builder: Pubkey, 42 | pub block_builder_commission_pct: u64, 43 | 44 | /// Bumps used to derive PDAs 45 | pub bumps: InitBumps, 46 | } 47 | 48 | impl Config { 49 | pub const DISCRIMINATOR: [u8; 8] = [155, 12, 170, 224, 30, 250, 204, 130]; 50 | 51 | pub fn deserialize(data: &[u8]) -> Result { 52 | anyhow::ensure!(data.len() >= 8, "Account data too short"); 53 | anyhow::ensure!(data.len() >= CONFIG_SIZE, "Invalid account size"); 54 | let (discriminator, mut remainder) = data.split_at(8); 55 | anyhow::ensure!( 56 | discriminator == Self::DISCRIMINATOR, 57 | "Invalid discriminator" 58 | ); 59 | Ok(::deserialize(&mut remainder)?) 60 | } 61 | } 62 | 63 | #[derive(BorshSerialize, BorshDeserialize, Default)] 64 | pub struct TipPaymentAccount {} 65 | 66 | pub fn id() -> Pubkey { 67 | Pubkey::from_str("T1pyyaTNZsKv2WcRAB8oVnk93mLJw2XzjtVYqCsaHqt") 68 | .expect("Failed to parse program id") 69 | } 70 | -------------------------------------------------------------------------------- /tip-router-operator-cli/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "tip-router-operator-cli" 3 | version = "3.0.1" 4 | edition = "2021" 5 | description = "CLI for Jito Tip Router" 6 | 7 | [dependencies] 8 | anyhow = { workspace = true } 9 | base64 = { workspace = true } 10 | borsh = { workspace = true } 11 | clap = { workspace = true } 12 | clap_old = { workspace = true } 13 | crossbeam-channel = "0.5.15" 14 | env_logger = { workspace = true } 15 | hex = "0.4" 16 | im = "15.1" 17 | itertools = "0.11" 18 | jito-bytemuck = { workspace = true } 19 | jito-priority-fee-distribution-sdk = { workspace = true } 20 | jito-restaking-client = { workspace = true } 21 | jito-restaking-core = { workspace = true } 22 | jito-restaking-program = { workspace = true } 23 | jito-tip-distribution-sdk = { workspace = true } 24 | jito-tip-payment-sdk = { workspace = true } 25 | jito-tip-router-client = { workspace = true } 26 | jito-tip-router-core = { workspace = true } 27 | #jito-tip-router-program = { workspace = true } 28 | log = { workspace = true } 29 | meta-merkle-tree = { workspace = true } 30 | rand = { workspace = true } 31 | serde = { workspace = true } 32 | serde_json = { workspace = true } 33 | solana-account-decoder = { workspace = true } 34 | solana-accounts-db = { workspace = true } 35 | solana-clap-utils = { workspace = true } 36 | solana-client = { workspace = true } 37 | solana-commitment-config = { workspace = true } 38 | solana-compute-budget-interface = { workspace = true } 39 | solana-core = { workspace = true } 40 | solana-genesis-config = { workspace = true } 41 | solana-geyser-plugin-manager = { workspace = true } 42 | solana-gossip = { workspace = true } 43 | solana-ledger = { workspace = true } 44 | solana-measure = { workspace = true } 45 | solana-metrics = { workspace = true } 46 | solana-program = { workspace = true } 47 | solana-rpc = { workspace = true } 48 | solana-rpc-client = { workspace = true } 49 | solana-rpc-client-api = { workspace = true } 50 | solana-runtime = { workspace = true } 51 | solana-sdk = { workspace = true } 52 | solana-stake-interface = { workspace = true } 53 | solana-stake-program = { workspace = true } 54 | solana-streamer = { workspace = true } 55 | solana-system-interface = { workspace = true } 56 | solana-transaction-status = { workspace = true } 57 | solana-unified-scheduler-pool = { workspace = true } 58 | solana-vote = { workspace = true } 59 | spl-memo-interface = { workspace = true } 60 | thiserror = { workspace = true } 61 | tokio = { workspace = true } 62 | 63 | [dev-dependencies] 64 | solana-program-test = { workspace = true } 65 | solana-runtime = { workspace = true, features = ["dev-context-only-utils"] } 66 | solana-sdk = { workspace = true, features = ["dev-context-only-utils"] } 67 | tempfile = "3.2" 68 | -------------------------------------------------------------------------------- /cli/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "jito-tip-router-cli" 3 | description = "Jito MEV Tip Distribution NCN CLI" 4 | version = { workspace = true } 5 | authors = { workspace = true } 6 | repository = { workspace = true } 7 | homepage = { workspace = true } 8 | license = { workspace = true } 9 | edition = { workspace = true } 10 | readme = { workspace = true } 11 | 12 | [[bin]] 13 | name = "jito-tip-router-cli" 14 | path = "src/bin/main.rs" 15 | 16 | [dependencies] 17 | anyhow = { workspace = true } 18 | base64 = { workspace = true } 19 | borsh = { workspace = true } 20 | bytemuck = { workspace = true } 21 | chrono = { workspace = true } 22 | clap = { workspace = true } 23 | clap-markdown = { workspace = true } 24 | dotenv = { workspace = true } 25 | env_logger = { workspace = true } 26 | futures = { workspace = true } 27 | jito-bytemuck = { workspace = true } 28 | jito-jsm-core = { workspace = true } 29 | jito-priority-fee-distribution-sdk = { workspace = true } 30 | jito-restaking-client = { workspace = true } 31 | jito-restaking-core = { workspace = true } 32 | jito-restaking-program = { workspace = true } 33 | jito-restaking-sdk = { workspace = true } 34 | jito-tip-distribution-sdk = { workspace = true } 35 | jito-tip-router-client = { workspace = true } 36 | jito-tip-router-core = { workspace = true } 37 | jito-tip-router-program = { workspace = true } 38 | jito-vault-client = { workspace = true } 39 | jito-vault-core = { workspace = true } 40 | jito-vault-program = { workspace = true } 41 | jito-vault-sdk = { workspace = true } 42 | log = { workspace = true } 43 | num-derive = { workspace = true } 44 | num-traits = { workspace = true } 45 | solana-account-decoder = { workspace = true } 46 | solana-account-info = "3.0.0" 47 | solana-address-lookup-table-interface = { workspace = true } 48 | solana-cli-config = { workspace = true } 49 | solana-client = { workspace = true } 50 | solana-commitment-config = { workspace = true } 51 | solana-compute-budget-interface = { workspace = true } 52 | solana-genesis-config = { workspace = true } 53 | solana-metrics = { workspace = true } 54 | solana-program = { workspace = true } 55 | solana-rpc-client = { workspace = true } 56 | solana-sdk = { workspace = true } 57 | solana-stake-interface = { workspace = true } 58 | solana-system-interface = { workspace = true } 59 | solana-transaction-status = { workspace = true } 60 | spl-associated-token-account-interface = { workspace = true } 61 | spl-token-interface = { workspace = true } 62 | switchboard-on-demand-client = { package = "switchboard-on-demand", version = "0.10.1", default-features = false, features = ["solana-v3", "client-v3"] } 63 | thiserror = { workspace = true } 64 | tokio = { workspace = true } 65 | 66 | [dev-dependencies] 67 | assert_matches = { workspace = true } 68 | -------------------------------------------------------------------------------- /integration_tests/tests/tip_router/admin_set_parameters.rs: -------------------------------------------------------------------------------- 1 | #[cfg(test)] 2 | mod tests { 3 | use jito_tip_router_core::error::TipRouterError; 4 | 5 | use crate::fixtures::{ 6 | test_builder::TestBuilder, tip_router_client::assert_tip_router_error, TestResult, 7 | }; 8 | 9 | #[tokio::test] 10 | async fn test_admin_set_parameters() -> TestResult<()> { 11 | let mut fixture = TestBuilder::new().await; 12 | let mut tip_router_client = fixture.tip_router_client(); 13 | let ncn_root = fixture.setup_ncn().await?; 14 | tip_router_client 15 | .do_initialize_config(ncn_root.ncn_pubkey, &ncn_root.ncn_admin) 16 | .await?; 17 | 18 | // Test setting valid parameters 19 | tip_router_client 20 | .do_set_parameters( 21 | None, 22 | Some(5), // epochs_before_stall 23 | Some(10), // epochs_after_consensus_before_close 24 | Some(1000), // valid_slots_after_consensus 25 | &ncn_root, 26 | ) 27 | .await?; 28 | 29 | // Verify parameters were set 30 | let config = tip_router_client 31 | .get_ncn_config(ncn_root.ncn_pubkey) 32 | .await?; 33 | assert_eq!(config.epochs_before_stall(), 5); 34 | assert_eq!(config.epochs_after_consensus_before_close(), 10); 35 | assert_eq!(config.valid_slots_after_consensus(), 1000); 36 | 37 | // Test invalid epochs_before_stall 38 | let result = tip_router_client 39 | .do_set_parameters( 40 | None, 41 | Some(0), // Invalid - too low 42 | None, 43 | None, 44 | &ncn_root, 45 | ) 46 | .await; 47 | assert_tip_router_error(result, TipRouterError::InvalidEpochsBeforeStall); 48 | 49 | // Test invalid epochs_before_stall 50 | let result = tip_router_client 51 | .do_set_parameters( 52 | None, 53 | None, 54 | Some(0), // Invalid - too low 55 | None, 56 | &ncn_root, 57 | ) 58 | .await; 59 | assert_tip_router_error(result, TipRouterError::InvalidEpochsBeforeClose); 60 | 61 | // Test invalid valid_slots_after_consensus 62 | let result = tip_router_client 63 | .do_set_parameters( 64 | None, 65 | None, 66 | None, 67 | Some(99), // Invalid - too low 68 | &ncn_root, 69 | ) 70 | .await; 71 | assert_tip_router_error(result, TipRouterError::InvalidSlotsAfterConsensus); 72 | 73 | Ok(()) 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /integration_tests/tests/tip_router/admin_set_st_mint.rs: -------------------------------------------------------------------------------- 1 | #[cfg(test)] 2 | mod tests { 3 | 4 | use jito_tip_router_core::{constants::JITOSOL_SOL_FEED, ncn_fee_group::NcnFeeGroup}; 5 | 6 | use crate::fixtures::{test_builder::TestBuilder, TestResult}; 7 | 8 | #[tokio::test] 9 | async fn test_admin_set_st_mint() -> TestResult<()> { 10 | let mut fixture = TestBuilder::new().await; 11 | let mut tip_router_client = fixture.tip_router_client(); 12 | let mut vault_client = fixture.vault_client(); 13 | 14 | const OPERATOR_COUNT: usize = 1; 15 | const VAULT_COUNT: usize = 1; 16 | 17 | let test_ncn = fixture 18 | .create_initial_test_ncn(OPERATOR_COUNT, VAULT_COUNT, None) 19 | .await?; 20 | 21 | let ncn = test_ncn.ncn_root.ncn_pubkey; 22 | let vault = vault_client 23 | .get_vault(&test_ncn.vaults[0].vault_pubkey) 24 | .await?; 25 | let st_mint = vault.supported_mint; 26 | let ncn_fee_group = Some(NcnFeeGroup::jto()); 27 | let reward_multiplier_bps = Some(10); 28 | let switchboard_feed = Some(JITOSOL_SOL_FEED); 29 | let no_feed_weight = Some(100); 30 | 31 | tip_router_client 32 | .do_admin_set_st_mint( 33 | ncn, 34 | st_mint, 35 | ncn_fee_group, 36 | reward_multiplier_bps, 37 | switchboard_feed, 38 | no_feed_weight, 39 | ) 40 | .await?; 41 | 42 | let vault_registry = tip_router_client.get_vault_registry(ncn).await?; 43 | 44 | let mint_entry = vault_registry.get_mint_entry(&st_mint).unwrap(); 45 | 46 | assert_eq!(*mint_entry.st_mint(), st_mint); 47 | assert_eq!(mint_entry.ncn_fee_group(), ncn_fee_group.unwrap()); 48 | assert_eq!( 49 | mint_entry.reward_multiplier_bps(), 50 | reward_multiplier_bps.unwrap() 51 | ); 52 | assert_eq!(*mint_entry.switchboard_feed(), switchboard_feed.unwrap()); 53 | assert_eq!(mint_entry.no_feed_weight(), no_feed_weight.unwrap()); 54 | 55 | tip_router_client 56 | .do_admin_set_st_mint(ncn, st_mint, None, None, None, None) 57 | .await?; 58 | 59 | let mint_entry = vault_registry.get_mint_entry(&st_mint).unwrap(); 60 | 61 | assert_eq!(*mint_entry.st_mint(), st_mint); 62 | assert_eq!(mint_entry.ncn_fee_group(), ncn_fee_group.unwrap()); 63 | assert_eq!( 64 | mint_entry.reward_multiplier_bps(), 65 | reward_multiplier_bps.unwrap() 66 | ); 67 | assert_eq!(*mint_entry.switchboard_feed(), switchboard_feed.unwrap()); 68 | assert_eq!(mint_entry.no_feed_weight(), no_feed_weight.unwrap()); 69 | 70 | Ok(()) 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /meta_merkle_tree/src/tree_node.rs: -------------------------------------------------------------------------------- 1 | use serde::{Deserialize, Serialize}; 2 | use solana_program::{ 3 | hash::{hashv, Hash}, 4 | pubkey::Pubkey, 5 | }; 6 | 7 | use crate::generated_merkle_tree::GeneratedMerkleTree; 8 | 9 | /// Represents the information for activating a tip distribution account. 10 | #[derive(Debug, Clone, Eq, Hash, PartialEq, Serialize, Deserialize)] 11 | pub struct TreeNode { 12 | /// Pubkey of the vote account for setting the merkle root 13 | pub tip_distribution_account: Pubkey, 14 | /// Claimant's proof of inclusion in the Merkle Tree 15 | pub proof: Option>, 16 | /// Validator merkle root to be set for the tip distribution account 17 | pub validator_merkle_root: [u8; 32], 18 | /// Maximum total claimable for the tip distribution account 19 | pub max_total_claim: u64, 20 | /// Number of nodes to claim 21 | pub max_num_nodes: u64, 22 | } 23 | 24 | impl TreeNode { 25 | pub const fn new( 26 | tip_distribution_account: &Pubkey, 27 | validator_merkle_root: &[u8; 32], 28 | max_total_claim: u64, 29 | max_num_nodes: u64, 30 | ) -> Self { 31 | Self { 32 | tip_distribution_account: *tip_distribution_account, 33 | proof: None, 34 | validator_merkle_root: *validator_merkle_root, 35 | max_total_claim, 36 | max_num_nodes, 37 | } 38 | } 39 | 40 | pub fn hash(&self) -> Hash { 41 | hashv(&[ 42 | &self.tip_distribution_account.to_bytes(), 43 | &self.validator_merkle_root, 44 | &self.max_total_claim.to_le_bytes(), 45 | &self.max_num_nodes.to_le_bytes(), 46 | ]) 47 | } 48 | } 49 | 50 | impl From for TreeNode { 51 | fn from(generated_merkle_tree: GeneratedMerkleTree) -> Self { 52 | Self { 53 | tip_distribution_account: generated_merkle_tree.distribution_account, 54 | validator_merkle_root: generated_merkle_tree.merkle_root.to_bytes(), 55 | max_total_claim: generated_merkle_tree.max_total_claim, 56 | max_num_nodes: generated_merkle_tree.max_num_nodes, 57 | proof: None, 58 | } 59 | } 60 | } 61 | 62 | #[cfg(test)] 63 | mod tests { 64 | use super::*; 65 | 66 | #[test] 67 | fn test_serialize_tree_node() { 68 | let tree_node = TreeNode { 69 | tip_distribution_account: Pubkey::default(), 70 | proof: None, 71 | validator_merkle_root: [0; 32], 72 | max_total_claim: 0, 73 | max_num_nodes: 0, 74 | }; 75 | let serialized = serde_json::to_string(&tree_node).unwrap(); 76 | let deserialized: TreeNode = serde_json::from_str(&serialized).unwrap(); 77 | assert_eq!(tree_node, deserialized); 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /program/src/initialize_base_reward_router.rs: -------------------------------------------------------------------------------- 1 | use jito_jsm_core::loader::{load_system_account, load_system_program}; 2 | use jito_restaking_core::ncn::Ncn; 3 | use jito_tip_router_core::{ 4 | account_payer::AccountPayer, 5 | base_reward_router::{BaseRewardReceiver, BaseRewardRouter}, 6 | constants::MAX_REALLOC_BYTES, 7 | epoch_marker::EpochMarker, 8 | epoch_state::EpochState, 9 | }; 10 | use solana_program::{ 11 | account_info::AccountInfo, entrypoint::ProgramResult, msg, program_error::ProgramError, 12 | pubkey::Pubkey, rent::Rent, sysvar::Sysvar, 13 | }; 14 | 15 | /// Can be backfilled for previous epochs 16 | pub fn process_initialize_base_reward_router( 17 | program_id: &Pubkey, 18 | accounts: &[AccountInfo], 19 | epoch: u64, 20 | ) -> ProgramResult { 21 | let [epoch_marker, epoch_state, ncn, base_reward_router, base_reward_receiver, account_payer, system_program] = 22 | accounts 23 | else { 24 | return Err(ProgramError::NotEnoughAccountKeys); 25 | }; 26 | 27 | EpochState::load_and_check_is_closing(program_id, epoch_state, ncn.key, epoch, false)?; 28 | Ncn::load(&jito_restaking_program::id(), ncn, false)?; 29 | BaseRewardReceiver::load(program_id, base_reward_receiver, ncn.key, epoch, true)?; 30 | AccountPayer::load(program_id, account_payer, ncn.key, true)?; 31 | EpochMarker::check_dne(program_id, epoch_marker, ncn.key, epoch)?; 32 | 33 | load_system_account(base_reward_router, true)?; 34 | load_system_program(system_program)?; 35 | 36 | let (base_reward_router_pubkey, base_reward_router_bump, mut base_reward_router_seeds) = 37 | BaseRewardRouter::find_program_address(program_id, ncn.key, epoch); 38 | base_reward_router_seeds.push(vec![base_reward_router_bump]); 39 | 40 | if base_reward_router_pubkey.ne(base_reward_router.key) { 41 | msg!("Incorrect base reward router PDA"); 42 | return Err(ProgramError::InvalidAccountData); 43 | } 44 | 45 | msg!( 46 | "Initializing Base Reward Router {} for NCN: {} at epoch: {}", 47 | base_reward_router.key, 48 | ncn.key, 49 | epoch 50 | ); 51 | AccountPayer::pay_and_create_account( 52 | program_id, 53 | ncn.key, 54 | account_payer, 55 | base_reward_router, 56 | system_program, 57 | program_id, 58 | MAX_REALLOC_BYTES as usize, 59 | &base_reward_router_seeds, 60 | )?; 61 | 62 | let min_rent = Rent::get()?.minimum_balance(0); 63 | msg!( 64 | "Transferring rent of {} lamports to base reward receiver {}", 65 | min_rent, 66 | base_reward_receiver.key 67 | ); 68 | AccountPayer::transfer( 69 | program_id, 70 | ncn.key, 71 | account_payer, 72 | base_reward_receiver, 73 | min_rent, 74 | )?; 75 | 76 | Ok(()) 77 | } 78 | -------------------------------------------------------------------------------- /program/src/realloc_ballot_box.rs: -------------------------------------------------------------------------------- 1 | use jito_bytemuck::{AccountDeserialize, Discriminator}; 2 | use jito_jsm_core::loader::load_system_program; 3 | use jito_restaking_core::ncn::Ncn; 4 | use jito_tip_router_core::{ 5 | account_payer::AccountPayer, ballot_box::BallotBox, config::Config as NcnConfig, 6 | epoch_state::EpochState, utils::get_new_size, 7 | }; 8 | use solana_program::{ 9 | account_info::AccountInfo, clock::Clock, entrypoint::ProgramResult, msg, 10 | program_error::ProgramError, pubkey::Pubkey, sysvar::Sysvar, 11 | }; 12 | 13 | /// Reallocates the ballot box account to its full size. 14 | /// This is needed due to Solana's account size limits during initialization. 15 | pub fn process_realloc_ballot_box( 16 | program_id: &Pubkey, 17 | accounts: &[AccountInfo], 18 | epoch: u64, 19 | ) -> ProgramResult { 20 | let [epoch_state, ncn_config, ballot_box, ncn, account_payer, system_program] = accounts else { 21 | return Err(ProgramError::NotEnoughAccountKeys); 22 | }; 23 | 24 | load_system_program(system_program)?; 25 | Ncn::load(&jito_restaking_program::id(), ncn, false)?; 26 | EpochState::load(program_id, epoch_state, ncn.key, epoch, false)?; 27 | NcnConfig::load(program_id, ncn_config, ncn.key, false)?; 28 | AccountPayer::load(program_id, account_payer, ncn.key, true)?; 29 | 30 | let (ballot_box_pda, ballot_box_bump, _) = 31 | BallotBox::find_program_address(program_id, ncn.key, epoch); 32 | 33 | if ballot_box_pda != *ballot_box.key { 34 | msg!("Ballot box account is not at the correct PDA"); 35 | return Err(ProgramError::InvalidAccountData); 36 | } 37 | 38 | if ballot_box.data_len() < BallotBox::SIZE { 39 | let new_size = get_new_size(ballot_box.data_len(), BallotBox::SIZE)?; 40 | msg!( 41 | "Reallocating ballot box from {} bytes to {} bytes", 42 | ballot_box.data_len(), 43 | new_size 44 | ); 45 | 46 | AccountPayer::pay_and_realloc(program_id, ncn.key, account_payer, ballot_box, new_size)?; 47 | } 48 | 49 | let should_initialize = ballot_box.data_len() >= BallotBox::SIZE 50 | && ballot_box.try_borrow_data()?[0] != BallotBox::DISCRIMINATOR; 51 | 52 | if should_initialize { 53 | let mut ballot_box_data = ballot_box.try_borrow_mut_data()?; 54 | ballot_box_data[0] = BallotBox::DISCRIMINATOR; 55 | let ballot_box_account = BallotBox::try_from_slice_unchecked_mut(&mut ballot_box_data)?; 56 | ballot_box_account.initialize(ncn.key, epoch, ballot_box_bump, Clock::get()?.slot); 57 | 58 | // Update Epoch State 59 | { 60 | let mut epoch_state_data = epoch_state.try_borrow_mut_data()?; 61 | let epoch_state_account = 62 | EpochState::try_from_slice_unchecked_mut(&mut epoch_state_data)?; 63 | epoch_state_account.update_realloc_ballot_box(); 64 | } 65 | } 66 | 67 | Ok(()) 68 | } 69 | -------------------------------------------------------------------------------- /program/src/initialize_weight_table.rs: -------------------------------------------------------------------------------- 1 | use jito_bytemuck::AccountDeserialize; 2 | use jito_jsm_core::loader::{load_system_account, load_system_program}; 3 | use jito_restaking_core::ncn::Ncn; 4 | use jito_tip_router_core::{ 5 | account_payer::AccountPayer, constants::MAX_REALLOC_BYTES, epoch_marker::EpochMarker, 6 | epoch_state::EpochState, vault_registry::VaultRegistry, weight_table::WeightTable, 7 | }; 8 | use solana_program::{ 9 | account_info::AccountInfo, entrypoint::ProgramResult, msg, program_error::ProgramError, 10 | pubkey::Pubkey, 11 | }; 12 | 13 | /// Initializes a Weight Table 14 | /// Can be backfilled for previous epochs 15 | pub fn process_initialize_weight_table( 16 | program_id: &Pubkey, 17 | accounts: &[AccountInfo], 18 | epoch: u64, 19 | ) -> ProgramResult { 20 | let [epoch_marker, epoch_state, vault_registry, ncn, weight_table, account_payer, system_program] = 21 | accounts 22 | else { 23 | return Err(ProgramError::NotEnoughAccountKeys); 24 | }; 25 | 26 | EpochState::load_and_check_is_closing(program_id, epoch_state, ncn.key, epoch, false)?; 27 | VaultRegistry::load(program_id, vault_registry, ncn.key, false)?; 28 | Ncn::load(&jito_restaking_program::id(), ncn, false)?; 29 | AccountPayer::load(program_id, account_payer, ncn.key, true)?; 30 | EpochMarker::check_dne(program_id, epoch_marker, ncn.key, epoch)?; 31 | 32 | load_system_account(weight_table, true)?; 33 | load_system_program(system_program)?; 34 | 35 | let vault_count = { 36 | let ncn_data = ncn.data.borrow(); 37 | let ncn = Ncn::try_from_slice_unchecked(&ncn_data)?; 38 | ncn.vault_count() 39 | }; 40 | 41 | let vault_registry_count = { 42 | let vault_registry_data = vault_registry.data.borrow(); 43 | let vault_registry = VaultRegistry::try_from_slice_unchecked(&vault_registry_data)?; 44 | vault_registry.vault_count() 45 | }; 46 | 47 | if vault_count != vault_registry_count { 48 | msg!("Vault count does not match supported mint count"); 49 | return Err(ProgramError::InvalidAccountData); 50 | } 51 | 52 | let (weight_table_pubkey, weight_table_bump, mut weight_table_seeds) = 53 | WeightTable::find_program_address(program_id, ncn.key, epoch); 54 | weight_table_seeds.push(vec![weight_table_bump]); 55 | 56 | if weight_table_pubkey.ne(weight_table.key) { 57 | msg!("Incorrect weight table PDA"); 58 | return Err(ProgramError::InvalidAccountData); 59 | } 60 | 61 | msg!( 62 | "Initializing Weight Table {} for NCN: {} at epoch: {}", 63 | weight_table.key, 64 | ncn.key, 65 | epoch 66 | ); 67 | AccountPayer::pay_and_create_account( 68 | program_id, 69 | ncn.key, 70 | account_payer, 71 | weight_table, 72 | system_program, 73 | program_id, 74 | MAX_REALLOC_BYTES as usize, 75 | &weight_table_seeds, 76 | )?; 77 | 78 | Ok(()) 79 | } 80 | -------------------------------------------------------------------------------- /core/src/epoch_marker.rs: -------------------------------------------------------------------------------- 1 | use std::mem::size_of; 2 | 3 | use bytemuck::{Pod, Zeroable}; 4 | use jito_bytemuck::{types::PodU64, AccountDeserialize, Discriminator}; 5 | use shank::{ShankAccount, ShankType}; 6 | use solana_program::{account_info::AccountInfo, msg, program_error::ProgramError, pubkey::Pubkey}; 7 | 8 | use crate::{discriminators::Discriminators, error::TipRouterError}; 9 | 10 | /// 56-byte account to mark that an epoch's accounts have all been closed 11 | #[derive(Debug, Clone, Copy, Zeroable, ShankType, Pod, AccountDeserialize, ShankAccount)] 12 | #[repr(C)] 13 | pub struct EpochMarker { 14 | ncn: Pubkey, 15 | epoch: PodU64, 16 | slot_closed: PodU64, 17 | } 18 | 19 | impl Discriminator for EpochMarker { 20 | const DISCRIMINATOR: u8 = Discriminators::EpochMarker as u8; 21 | } 22 | 23 | impl EpochMarker { 24 | pub const SIZE: usize = 8 + size_of::(); 25 | 26 | pub fn new(ncn: &Pubkey, epoch: u64, slot_closed: u64) -> Self { 27 | Self { 28 | ncn: *ncn, 29 | epoch: PodU64::from(epoch), 30 | slot_closed: PodU64::from(slot_closed), 31 | } 32 | } 33 | 34 | pub const fn ncn(&self) -> &Pubkey { 35 | &self.ncn 36 | } 37 | 38 | pub fn epoch(&self) -> u64 { 39 | self.epoch.into() 40 | } 41 | 42 | pub fn slot_closed(&self) -> u64 { 43 | self.slot_closed.into() 44 | } 45 | 46 | pub fn seeds(ncn: &Pubkey, epoch: u64) -> Vec> { 47 | // Note: The second NCN is an error from the original code, most presumably a copy/paste or Claude error 48 | vec![ 49 | b"epoch_marker".to_vec(), 50 | ncn.to_bytes().to_vec(), 51 | epoch.to_le_bytes().to_vec(), 52 | ncn.to_bytes().to_vec(), 53 | ] 54 | } 55 | 56 | pub fn find_program_address( 57 | program_id: &Pubkey, 58 | ncn: &Pubkey, 59 | epoch: u64, 60 | ) -> (Pubkey, u8, Vec>) { 61 | let seeds = Self::seeds(ncn, epoch); 62 | let (address, bump) = Pubkey::find_program_address( 63 | &seeds.iter().map(|s| s.as_slice()).collect::>(), 64 | program_id, 65 | ); 66 | (address, bump, seeds) 67 | } 68 | 69 | pub fn check_dne( 70 | program_id: &Pubkey, 71 | account: &AccountInfo, 72 | ncn: &Pubkey, 73 | epoch: u64, 74 | ) -> Result<(), ProgramError> { 75 | let expected_pda = Self::find_program_address(program_id, ncn, epoch).0; 76 | 77 | if expected_pda.ne(account.key) { 78 | msg!( 79 | "Epoch marker PDA does not match {} != {}", 80 | account.key, 81 | expected_pda 82 | ); 83 | return Err(ProgramError::InvalidSeeds); 84 | } 85 | 86 | let data_length = account.data_len(); 87 | if data_length > 0 { 88 | msg!("Marker exists."); 89 | return Err(TipRouterError::MarkerExists.into()); 90 | } 91 | 92 | Ok(()) 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /cli/README.md: -------------------------------------------------------------------------------- 1 | # Jito Tip Router CLI 2 | 3 | ## Overview 4 | 5 | The Jito Tip Router CLI is a command-line tool that provides access to Jito Tip Router Program. 6 | 7 | ## Features 8 | 9 | ### Transaction Inspection 10 | 11 | You can preview transactions before sending them using the --print-tx flag. 12 | 13 | ```bash 14 | jito-tip-router-cli -- --print-tx admin-create-config --keypair-path --ncn 15 | ``` 16 | 17 | Example output: 18 | 19 | ```bash 20 | ------ IX ------ 21 | 22 | RouterBmuRBkPUbgEDMtdvTZ75GBdSREZR5uGUxxxpb 23 | 24 | 4mgyW8EGjLgq2gfPapQUYE3PGDtsUpYyuHDUj3tT3K6i W 25 | RouterBmuRBkPUbgEDMtdvTZ75GBdSREZR5uGUxxxpb 26 | 2V6Abua9BY6Ga8HUeLWSLXh4Gm6oKsn3GpTzP4eYMFqT 27 | 2V6Abua9BY6Ga8HUeLWSLXh4Gm6oKsn3GpTzP4eYMFqT S 28 | 2V6Abua9BY6Ga8HUeLWSLXh4Gm6oKsn3GpTzP4eYMFqT 29 | JAAgQEBRyA5Jx6UGWsLNGicohE57cDFsZ58vT9MMDpd9 W 30 | 11111111111111111111111111111111 31 | 32 | 33 | 1M3AwPW4zJMasH4186d3fXfvLwMoZYHroPZVyZnhZR 34 | ``` 35 | 36 | When using this flag, the transaction will not be processed - only printed for inspection. 37 | Note that instruction data shown in the output is **base58** encoded, which provides a compact text representation of binary data. 38 | 39 | ## Official Accounts 40 | 41 | | Account | Address | 42 | | -------------------------- | -------------------------------------------- | 43 | | Test Tip Router Program ID | RouterBmuRBkPUbgEDMtdvTZ75GBdSREZR5uGUxxxpb | 44 | | Test NCN | rYQFkFYXuDqJPoH2FvFtZTC8oC3CntgRjtNatx6q1z1 | 45 | 46 | ## Setup CLIs 47 | 48 | Install the Tip Router CLI 49 | 50 | ```bash 51 | cargo build --release 52 | cargo install --path ./cli --bin jito-tip-router-cli --locked 53 | ``` 54 | 55 | Ensure it has been installed 56 | 57 | ```bash 58 | jito-tip-router-cli --help 59 | ``` 60 | 61 | Clone and Install the Restaking and Vault CLI in a different directory 62 | 63 | ```bash 64 | cd .. 65 | git clone https://github.com/jito-foundation/restaking.git 66 | cd restaking 67 | cargo build --release 68 | cargo install --path ./cli --bin jito-restaking-cli 69 | ``` 70 | 71 | Ensure it works 72 | 73 | ```bash 74 | jito-restaking-cli --help 75 | ``` 76 | 77 | ## Registering Network 78 | 79 | ### For Operator 80 | 81 | - initialize_operator ( operator_fee_bps ) 82 | - set_operator_admin ( voter ) 83 | ( Give Jito the Operator Account Address ) 84 | 85 | - initialize_operator_vault_ticket ( for all vaults ) 86 | - warmup_operator_vault_ticket ( for all vaults ) 87 | 88 | ( Wait for NCN ) 89 | 90 | - operator_warmup_ncn 91 | 92 | ### For Vault 93 | 94 | ( Wait for Operator ) 95 | 96 | - initialize_vault_operator_delegation 97 | - add_delegation 98 | 99 | - initialize_vault_ncn_ticket 100 | - warmup_vault_ncn_ticket 101 | 102 | ### For NCN 103 | 104 | - initialize_ncn 105 | 106 | - initialize_ncn_vault_ticket 107 | - warmup_ncn_vault_ticket 108 | 109 | ( Wait for Operators to be created ) 110 | 111 | - initialize_ncn_operator_state 112 | - ncn_warmup_operator 113 | -------------------------------------------------------------------------------- /program/src/realloc_base_reward_router.rs: -------------------------------------------------------------------------------- 1 | use jito_bytemuck::{AccountDeserialize, Discriminator}; 2 | use jito_jsm_core::loader::load_system_program; 3 | use jito_restaking_core::ncn::Ncn; 4 | use jito_tip_router_core::{ 5 | account_payer::AccountPayer, base_reward_router::BaseRewardRouter, config::Config as NcnConfig, 6 | epoch_state::EpochState, utils::get_new_size, 7 | }; 8 | use solana_program::{ 9 | account_info::AccountInfo, clock::Clock, entrypoint::ProgramResult, msg, 10 | program_error::ProgramError, pubkey::Pubkey, sysvar::Sysvar, 11 | }; 12 | 13 | pub fn process_realloc_base_reward_router( 14 | program_id: &Pubkey, 15 | accounts: &[AccountInfo], 16 | epoch: u64, 17 | ) -> ProgramResult { 18 | let [epoch_state, ncn_config, base_reward_router, ncn, account_payer, system_program] = 19 | accounts 20 | else { 21 | return Err(ProgramError::NotEnoughAccountKeys); 22 | }; 23 | 24 | load_system_program(system_program)?; 25 | Ncn::load(&jito_restaking_program::id(), ncn, false)?; 26 | EpochState::load(program_id, epoch_state, ncn.key, epoch, true)?; 27 | NcnConfig::load(program_id, ncn_config, ncn.key, false)?; 28 | AccountPayer::load(program_id, account_payer, ncn.key, true)?; 29 | 30 | let (base_reward_router_pda, base_reward_router_bump, _) = 31 | BaseRewardRouter::find_program_address(program_id, ncn.key, epoch); 32 | 33 | if base_reward_router_pda != *base_reward_router.key { 34 | msg!("Base reward router account is not at the correct PDA"); 35 | return Err(ProgramError::InvalidAccountData); 36 | } 37 | 38 | if base_reward_router.data_len() < BaseRewardRouter::SIZE { 39 | let new_size = get_new_size(base_reward_router.data_len(), BaseRewardRouter::SIZE)?; 40 | msg!( 41 | "Reallocating base reward router from {} bytes to {} bytes", 42 | base_reward_router.data_len(), 43 | new_size 44 | ); 45 | AccountPayer::pay_and_realloc( 46 | program_id, 47 | ncn.key, 48 | account_payer, 49 | base_reward_router, 50 | new_size, 51 | )?; 52 | } 53 | 54 | let should_initialize = base_reward_router.data_len() >= BaseRewardRouter::SIZE 55 | && base_reward_router.try_borrow_data()?[0] != BaseRewardRouter::DISCRIMINATOR; 56 | 57 | if should_initialize { 58 | let mut base_reward_router_data = base_reward_router.try_borrow_mut_data()?; 59 | base_reward_router_data[0] = BaseRewardRouter::DISCRIMINATOR; 60 | let base_reward_router_account = 61 | BaseRewardRouter::try_from_slice_unchecked_mut(&mut base_reward_router_data)?; 62 | 63 | base_reward_router_account.initialize( 64 | ncn.key, 65 | epoch, 66 | base_reward_router_bump, 67 | Clock::get()?.slot, 68 | ); 69 | 70 | // Update Epoch State 71 | { 72 | let mut epoch_state_data = epoch_state.try_borrow_mut_data()?; 73 | let epoch_state_account = 74 | EpochState::try_from_slice_unchecked_mut(&mut epoch_state_data)?; 75 | epoch_state_account.update_realloc_base_reward_router(); 76 | } 77 | } 78 | 79 | Ok(()) 80 | } 81 | --------------------------------------------------------------------------------