#relay #express #instructions

express-relay

Pyth Express Relay program for handling permissioning and bid distribution

3 releases (breaking)

0.3.0 Sep 18, 2024
0.2.0 Sep 11, 2024
0.1.0 Sep 10, 2024

#17 in #express

Download history 450/week @ 2024-09-09 202/week @ 2024-09-16 22/week @ 2024-09-23 14/week @ 2024-09-30 5/week @ 2024-10-07 5/week @ 2024-10-14

55 downloads per month

Apache-2.0

33KB
585 lines

Express Relay Program

This subdir contains the Express Relay program and its SDK to help integrating programs check permissioning and perform testing.

Express Relay Design

The design of Express Relay on Solana utilizes the fact that Solana allows for multiple instructions to different programs within the same transaction. As a result, one of the instructions in the Express Relay transaction will be the SubmitBid instruction of the Express Relay program, using a permission account that represents the specific opportunity (e.g. trade; vault liquidation) whose permissioning is being auctioned by Express Relay. For example, this permission pubkey could be the address of a user's position (or the keccak hash of relevant identifying information for a particular position). The SubmitBid instruction will also handle validating and distributing to the relevant parties (router, relayer) the bid.

The Express Relay SubmitBid instruction has two signers: a keypair belonging to the relayer, which is set by governance, and the wallet of the searcher from which the bid will be extracted in SOL (this will often also be the keypair that signs the integrating program instruction(s)). Before submitting the transaction with the SubmitBid instruction, a searcher must sign that transaction with all the necessary keypairs except for the relayer keypair. The SubmitBid instruction will also contain an account router representing the integrating program/app. This could be a PDA of the integrating program, the address of a relevant user (e.g. limit order maker) in the transaction workflow, or an address controlled by a protocol's DAO or an app's owner--whoever the integrating program/app wants to receive the fees from Express Relay. The config_router account specifies the router-specific config PDA that, if it exists, has router-specific fee splits (which would have been set by governance).

The integrating program will need to check that the appropriate SubmitBid instruction is one of the instructions in the ongoing transaction. It can do this by calling the CheckPermission method of the Express Relay program via CPI. Note that the permissioning is not stored in any state; it is simply retrieved from the instructions of the current transaction.

To integrate with Express Relay, the integrating program needs to make the following changes:

  1. Store the pubkey of the express relay program and validate it in the program logic
  2. Add a permission account to the relevant instruction being gated, or use a suitable existing account for the permissioning check.
  3. Make a CPI to the Express Relay program CheckPermission instruction with the designated permission and router accounts.
  4. If planning to receive fees in a PDA of the program, create the relevant PDA specified by the seeds in the Express Relay SubmitBid instruction beforehand.

Example Integration

Integrating programs can use the check_permission_cpi helper method defined in the Express Relay SDK. For example:

use anchor_lang::prelude::*;
use express_relay::sdk::cpi::check_permission_cpi;

#[program]
use {
    anchor_lang::{
        prelude::*,
        solana_program::sysvar::instructions as sysvar_instructions,
    },
    express_relay::{
        cpi::accounts::CheckPermission,
        program::ExpressRelay,
        sdk::cpi::check_permission_cpi,
        state::{
            ExpressRelayMetadata,
            SEED_CONFIG_ROUTER,
            SEED_METADATA,
        },
    },
};

pub mod integrating_program {
    use super::*;

    pub fn do_something(ctx: Context<DoSomething>, data: DoSomethingArgs) -> Result<()> {
        let check_permission_accounts = CheckPermission {
            sysvar_instructions:    ctx.accounts.sysvar_instructions.to_account_info(),
            permission:             ctx.accounts.permission.to_account_info(),
            router:                 ctx.accounts.router.to_account_info(),
            config_router:          ctx.accounts.config_router.to_account_info(),
            express_relay_metadata: ctx.accounts.express_relay_metadata.to_account_info(),
        };
        let fees = check_permission_cpi(
            check_permission_accounts,
            ctx.accounts.express_relay.to_account_info(),
        )?;

        /// integrating_program do_something logic
    }
}

#[derive(Accounts)]
pub struct DoSomething<'info> {
    #[account(address = express_relay::ID)]
    pub express_relay: Program<'info, ExpressRelay>,

    #[account(seeds = [SEED_METADATA], bump, seeds::program = express_relay.key())]
    pub express_relay_metadata: Account<'info, ExpressRelayMetadata>,

    /// CHECK: this is the sysvar instructions account
    #[account(address = sysvar_instructions::ID)]
    pub sysvar_instructions: UncheckedAccount<'info>,

    /// CHECK: this is the permission key
    pub permission: UncheckedAccount<'info>,

    /// CHECK: this is the address to receive express relay fees at
    pub router: UncheckedAccount<'info>,

    /// CHECK: doesn't matter what this looks like
    #[account(seeds = [SEED_CONFIG_ROUTER, router.key().as_ref()], bump, seeds::program = express_relay.key())]
    pub config_router: UncheckedAccount<'info>,

    // other accounts
}

Some integrating programs may need to learn how much will be paid in fees in an ongoing transaction. The check_permission_cpi returns the fees paid to the router in the current transaction. An example use of this can be seen in the dummy example program.

To run Rust-based tests, an integrating program can use the helper methods defined in src/sdk/test_helpers.rs. See the dummy example to see how these methods can be used for Rust-based end-to-end testing.

Dependencies

~18–27MB
~460K SLoC