equanetwork-cli 0.0.4

The Equa Network command line interface
use anyhow::{anyhow, Result};
use borsh::BorshDeserialize;
use equanetwork::{NetworkSwap, NetworkSwapInstructionArgs, Vault};
use solana_address::Address;
use solana_instruction::Instruction;
use solana_signer::Signer;

use crate::{
    config::{Command, Config, NetworkCommand},
    log_context,
    transaction::process_instructions,
};

use super::helpers::{addr, ensure_ata, pk, require_signer, vault_pda};

fn load_vault(rpc: &solana_client::rpc_client::RpcClient, vault: Address) -> Result<Vault> {
    let account = rpc
        .get_account(&pk(vault))
        .map_err(|e| anyhow!("fetch vault {}: {e}", pk(vault)))?;
    Vault::try_from_slice(&account.data).map_err(|e| anyhow!("decode vault {}: {e}", pk(vault)))
}

pub fn handle(config: &Config) -> Result<()> {
    let Command::Network {
        command:
            NetworkCommand::Swap {
                network,
                input_mint,
                output_mint,
                amount,
                minimum_output_amount,
                allow_partial_fill,
            },
    } = &config.command
    else {
        unreachable!()
    };

    let signer = require_signer(config)?;
    let rpc = config.rpc_client();
    let user = signer.pubkey();
    let vault_in = vault_pda(network, input_mint);
    let vault_out = vault_pda(network, output_mint);

    let vault_in_acc = load_vault(&rpc, vault_in)?;
    let vault_out_acc = load_vault(&rpc, vault_out)?;

    let mut ixs: Vec<Instruction> = Vec::new();
    let user_input_ata = ensure_ata(&rpc, &mut ixs, &signer, &user, input_mint);
    let user_output_ata = ensure_ata(&rpc, &mut ixs, &signer, &user, output_mint);
    let network_input_ata = ensure_ata(&rpc, &mut ixs, &signer, network, input_mint);
    let network_output_ata = ensure_ata(&rpc, &mut ixs, &signer, network, output_mint);

    log_context!(
      action => "network_swap",
      user => user.to_string(),
      network => network.to_string(),
      input_mint => input_mint.to_string(),
      output_mint => output_mint.to_string(),
      amount => *amount,
      minimum_output_amount => *minimum_output_amount,
      allow_partial_fill => *allow_partial_fill,
    );

    ixs.push(
        NetworkSwap {
            user: addr(user),
            network: addr(*network),
            vault_in,
            vault_out,
            input_mint: addr(*input_mint),
            output_mint: addr(*output_mint),
            user_input_ata: addr(user_input_ata),
            user_output_ata: addr(user_output_ata),
            network_input_ata: addr(network_input_ata),
            network_output_ata: addr(network_output_ata),
            price_source_in: vault_in_acc.price_source,
            price_source_out: vault_out_acc.price_source,
            price_feed_in: vault_in_acc.price_feed,
            price_feed_out: vault_out_acc.price_feed,
            token_program_in: addr(spl_token_interface::ID),
            token_program_out: addr(spl_token_interface::ID),
            memo_program: addr(spl_memo_interface::v3::ID),
            instructions_sysvar: addr(solana_pubkey::pubkey!(
                "Sysvar1nstructions1111111111111111111111111"
            )),
        }
        .instruction(NetworkSwapInstructionArgs {
            input_amount: *amount,
            minimum_output_amount: *minimum_output_amount,
            allow_partial_fill: *allow_partial_fill,
        }),
    );

    process_instructions(&rpc, &[signer], &ixs)
}