Qiitaにログインして、便利な機能を使ってみませんか?

あなたにマッチした記事をお届けします

便利な情報をあとから読み返せます

23

botterのためのRustでSolana入門(前編)

最終更新日 投稿日 2023年12月18日

1.はじめに

仮想通貨botter Advent Calendar 2023 / Solana Advent Calenderの12/19に参加させていただきました

自己紹介

こんにちは。pico🙄.solと申します。botter界隈に足を踏み入れたのは2021年の11月頃?いわゆるrichman組ですね

2022年の5月頃からSolanaを触り始め、atomic arbに取りつかれてちまちまbotに改良を重ね、今はatomic arb botや清算botを中心に開発してます

本業は資格商売でお堅い仕事をしてます。お客さんには「え、株?FX?仮想通貨?そんな危ないもんに手出したらそのうち大損するから絶対やめた方がいいですよ!!」と言ってやめさせたりしてます。みなさん仮想通貨なんて危ないもんはやめましょう

私のX(Twitter)アカウント。あんまり役に立つことはtweetしてません。ぶつぶつそのとき思ったことをつぶやいたりドヤったりぼやいたりしてます

この記事では

  1. ウォレットの秘密鍵ファイルを作成
  2. JupiterのToken APIを使ってトークンの情報を取得
  3. ウォレット内のトークン残高を取得
  4. Pythから価格と1時間EMAを取得
  5. JupiterのSWAP APIを使ってquoteを取って発注

などを行うサンプルプログラムを紹介します

開発環境

Docker

RustでSolanaのbotを開発するためのDockerを用意しました(256haxさんからForkさせていただきました。感謝!)

私自身はAnchorとSolana cliなどのバージョン齟齬でかなりハマった記憶があります。。。

AnchorはSolanaでDappsを作るための開発者用フレームワークです。オンチェーンプログラムを作るのに便利ですが、クライアント側で使っても便利です。

Reactは今回必要ないのでDockerfileからコメントアウトしてしまっても構いません。

Docker使わない方はこのDockerfileを参考にRust, Solana-cli, Anchorをインストールしてみてください。

git clone https://github.com/pico-sol/solana-anchor-react-docker.git

この後、私の場合は
VScodeの左のツリーのdocker-compose.ymlを右クリック → Compose Up
20231218212313.jpg

で立ち上がったら
20231218212001.jpg
上図のdockerのCONTAINERSの青枠を右クリック → Visual Studio Codeをアタッチする
でdockerの中に入ってます。

Visual Studio Codeのエクステンション

私はこの辺を入れてます。rust-analyzerは必須として、ほかはどう機能してるのか正直意識してませんがだいたい便利に使えてます
20231218103807.jpg

2.ウォレット周りの情報を取得してみる

2-1. キーペアファイルを作成してみる

作成せずString型の秘密鍵をコードに埋め込んでも2-2以降は実行できます

すでにPhantomやSolflareに持っているアカウントを使いたい方はそのアカウントのキーペアファイルを作成してみましょう

キーペアファイル、[u8; 64]型の秘密鍵、String型の秘密鍵は、流出すると中の資産をすべてコントロールされることになります。絶対に流出しないように管理してください

Solflareの場合は秘密鍵エクスポートで[u8;64]型(u8型数値64個の配列)の秘密鍵が表示されるので、それをid.jsonなどとしてファイルに保存するだけでキーペアファイルが作れます。

Phantomの場合はString型の秘密鍵が表示されるので、以下のコードでキーペアファイルが作成できます。
Docker内に入ったら/appフォルダ内でRustの新規フォルダを作ります

cd /app
cargo init write_keypair_file
cd write_keypair_file

フォルダ内に自動で作成されたCargo.tomlとsrc/main.rsを編集します

Cargo.toml
[package]
name = "write_keypair_file"
version = "0.1.0"
edition = "2021"

[dependencies]
anyhow = "1.0.75"
tokio = {version = "1.35.0", features = ["full"] }
shellexpand = "3.1.0"
solana-sdk = "1.16.23"

your secret keyのところにString型の秘密鍵を貼り付けます

main.rs
use solana_sdk::signer::keypair::Keypair;
use anyhow::Result;

const SECRET_KEY: &str = "your secret key";

#[tokio::main]
async fn main() -> Result<()> {
    // String型の秘密鍵を読み込み
    let payer = Keypair::from_base58_string(SECRET_KEY);

    //[u8; 64]型の秘密鍵を表示
    let bytes = payer.to_bytes();
    println!("{:?}", bytes);
    Ok(())
}
cargo r -r
実行結果
[xx, xxx, x, xxx, xx, xx, xxx, xx, ....., xx]

これが[u8; 64]型の秘密鍵になります。
これをjsonファイルに保存すればキーペアファイルが作成できます。

cargo r -r > /app/id.json

では作成したキーペアファイルをプログラムからちゃんと読み込めるか確認してみましょう

cd /app
cargo init read_keypair_file
cd read_keypair_file
Cargo.toml
[package]
name = "read_keypair_file"
version = "0.1.0"
edition = "2021"

[dependencies]
anyhow = "1.0.75"
tokio = {version = "1.35.0", features = ["full"] }
shellexpand = "3.1.0"
solana-sdk = "1.16.23"
main.rs
use solana_sdk::{signature::Signer, signer::keypair::read_keypair_file};
use anyhow::Result;

// String型の秘密鍵を使う場合
// const SECRET_KEY: &str = "your secret key";

#[tokio::main]
async fn main() -> Result<()> {
    //[u8型の秘密鍵をキーペアファイルから読み込み]
    let payer = read_keypair_file(&*shellexpand::tilde("/app/id.json"))
        .expect("cannot read keypair");

    // String型の秘密鍵を読み込み
    // let payer = Keypair::from_base58_string(SECRETKEY);

    // pubkeyを表示
    let base58 = payer.pubkey();
    println!("pubkey:");
    println!("{:?}", base58);
    println!();

    //[u8; 64]型の秘密鍵を表示
    let bytes = payer.to_bytes();
    println!("secret_key[u8; 64]:");
    println!("{:?}", bytes);
    println!();

    //String型の秘密鍵を表示
    let base58 = payer.to_base58_string();
    println!("secret_key[String]:");
    println!("{}", base58);
    println!();
    Ok(())
}
cargo r -r
実行結果
pubkey:
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
secret_key[u8; 64]:
[xx, xxx, x, xxx, xx, xx, xxx, xx, ....., xx]
secret_key[String]:
***********************************************************************************

pubkeyや秘密鍵が適切に表示されたでしょうか

2-2. SOL, wSOL, USDC残高を取得してみる

次はウォレット内のネイティブSOLとwrapped SOL, USDCの残高を取得してみましょう

cd /app
cargo init token_list_test
cd token_list_test

フォルダ内に自動で作成されたCargo.tomlとsrc/main.rsを編集します
anchor-clientクレートの中にはanchor-lang, solana-client, solana-sdkが全部入ってます

Cargo.toml
[package]
name = "token_list_test"
version = "0.1.0"
edition = "2021"

[dependencies]
anyhow = "1.0.75"
anchor-client = "0.29.0"
anchor-spl = "0.29.0"
reqwest = { version = "0.11.22", features = ["json"] }
serde = { version = "1.0.188", features = ["derive"] }
shellexpand = "3.1.0"
spl-token = "4.0.0"
tokio = { version = "1.35.0", features = ["full"] }
main.rs
use anchor_client::{
    anchor_lang::AccountDeserialize,
    solana_client::nonblocking::rpc_client::RpcClient,
    solana_sdk::{
        pubkey::Pubkey, signature::read_keypair_file, signature::Signer, signer::keypair::Keypair,
    },
    Cluster,
};
use anchor_spl::{associated_token::get_associated_token_address, token::TokenAccount};
use anyhow::Result;
use serde::Deserialize;
use std::{collections::HashMap, path::Path, str::FromStr};

const PAYER: &str = "/app/id.json";  //キーペアファイルの場所を指定
// const SECRET_KEY: &str = "your secret key"; //シークレットキーを直で読み込む場合はこちら

...続き1へ

よく使うトークンをenum型で定義しておきます

main.rs(続き1)
//よく使うトークンをenum型に
#[derive(Debug, Clone, Copy, Eq, PartialEq, PartialOrd, Hash)]
pub enum Mint {
    Usdc,
    Sol,
}

impl Mint {
    pub fn address(&self) -> &str {
        match &self {
            Mint::Usdc => "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v",
            Mint::Sol => "So11111111111111111111111111111111111111112",
        }
    }
    pub fn pubkey(&self) -> Pubkey {
        Pubkey::from_str(&self.address()).unwrap()
    }
}
...続き2へ

各トークンの情報をJupiterのToken APIから取得しましょう。
(Json形式でAPIなどから取得したデータは同じような感じで変数として取得することができます。)
Token APIのドキュメントはこちら

ブラウザでAPIのレスポンスを見てみましょう。
https://token.jup.ag/strict

結果
[{"address":"EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v",
"chainId":101,"decimals":6,"name":"USD Coin","symbol":"USDC",
"logoURI":"https://raw.githubusercontent.com/solana-labs/token-list/main/assets/mainnet/EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v/logo.png",
"tags":["old-registry","solana-fm"],"extensions":{"coingeckoId":"usd-coin"}},

{"address":"So11111111111111111111111111111111111111112",
"chainId":101,"decimals":9,"name":"Wrapped SOL","symbol":"SOL",
"logoURI":"https://raw.githubusercontent.com/solana-labs/token-list/main/assets/mainnet/So11111111111111111111111111111111111111112/logo.png",
"tags":["old-registry"],"extensions":{"coingeckoId":"wrapped-solana"}},
....

USDCはトークンのアドレスEPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1vで、decimalsは6です。
Solanaでは各トークンの数量はu64型の整数で管理しています。UI上で表示される数量はdecimalsを小数点以下の桁数として表示したものです。
例えば1.000000 USDCはu64型で表すと1_000_000になります。
1.000000000 SOLはu64型で1_000_000_000になります。

あとでu64型の数量をUI上の数量(f64表示)に変更したいので、このAPIの結果をプログラム内で変数に取り込んでいきます。

main.rs(続き2)

//JupiterのToken APIからTokenデータを取得
pub async fn get_token_info() -> Result<HashMap<Pubkey, TokenInfo>> {
    let client = reqwest::Client::new();
    let mut token_info: HashMap<Pubkey, TokenInfo> = HashMap::new();
    let url = "https://token.jup.ag/strict"; //strict or all
    let response = client.get(url).send().await?;
    let mut json = response.json::<Vec<TokenInfo>>().await?;
    for j in json.iter_mut() {
        token_info.insert(j.pubkey(), j.clone());
    }
    Ok(token_info)
}

//Jupiter Token APIから取得したデータを格納する
#[derive(Debug, Default, Clone, Deserialize)]
pub struct TokenInfo {
    pub address: String,
    pub symbol: String,
    pub name: String,
    pub decimals: u8,
}

impl TokenInfo {
    pub fn pubkey(&self) -> Pubkey {
        Pubkey::from_str(&self.address).unwrap()
    }
}

...続き3へ

main関数でネイティブSOL残高を取得したあとwSOLとUSDC残高を取得していきます

main.rs(続き3)
#[tokio::main]
async fn main() -> Result<()> {
    // ファイルからキーペアを取得
    let payer = read_keypair_file(&*shellexpand::tilde(PAYER)).unwrap();
    // let payer = Keypair::from_base58_string(SECRET_KEY);  //シークレットキーを直で読み込む場合はこちら
    
    let rpc_client = RpcClient::new(Cluster::Mainnet.url().to_string());

    //JupiterのToken APIからToken情報を取得
    let tokens_info = get_token_info().await?;
    // println!("{:?}", token_info);

    //ネイティブSOLのToken情報を取得
    let sol_info = tokens_info.get(&Mint::Sol.pubkey()).unwrap();
    //SOLの残高を取得
    let sol_amount = rpc_client.get_balance(&payer.pubkey()).await?;
    let sol_ui_amount = spl_token::amount_to_ui_amount(sol_amount, sol_info.decimals);
    println!("{:?} {}", sol_ui_amount, sol_info.symbol);

    //wSOLとUSDCの残高を取得
    for mint in [Mint::Sol, Mint::Usdc] {
        //Token情報を取得
        let token_info = tokens_info.get(&mint.pubkey()).unwrap();
        // println!("token_info: {:?}", token_info);

        //ata(自分のウォレットのwSOL, USDCアカウント)のpubkeyを取得
        let ata = get_associated_token_address(&payer.pubkey(), &mint.pubkey());
        let data = rpc_client.get_account_data(&ata).await?;
        let token = TokenAccount::try_deserialize(&mut &data[..])?;
        // println!("{:?}", token);
        let token_amount = spl_token::amount_to_ui_amount(token.amount, token_info.decimals);
        println!("{:?} {}", token_amount, token_info.symbol);
    }
    Ok(())
}

では実行してみましょう

cargo r -r
実行結果
1.056916685 SOL
199.177870594 SOL
34312.422521 USDC

2-3. Pythオラクルを取得してみる

次は11月のエアドロで話題になったPythの価格データを取得してみましょう
PythはPyth Networkが提供するデータフィードおよびオラクルサービスです。各トークンの取引所価格情報を統合してリアルタイムで提供してくれるサービスですね(ざっくり)

例えばSOL-USDの価格はこんな感じで提供されています

pythオラクルを取得するテストプログラムを作ってみましょう

cd /app
cargo init pyth_test
cd pyth_test

フォルダ内に自動で作成されたCargo.tomlとsrc/main.rsを編集します

Cargo.toml
[package]
name = "pyth_test"
version = "0.1.0"
edition = "2021"

[dependencies]
anyhow = "1.0.75"
solana-client = "1.16.23"
pyth-sdk-solana = "0.9.0"
tokio = { version = "1.35.0", features = ["full"] }
main.rs
use anyhow::Result;
use pyth_sdk_solana::load_price_feed_from_account;
use solana_client::rpc_client::RpcClient;
use solana_sdk::pubkey::Pubkey;
use std::str::FromStr;
use std::time::{SystemTime, UNIX_EPOCH};

#[tokio::main]
async fn main() -> Result<()> {
    let url = "https://api.mainnet-beta.solana.com";
    let client = RpcClient::new(url.to_string());
    let sol_price_key = Mint::Sol.pyth_pubkey();

    let mut sol_price_account = client.get_account(&sol_price_key).unwrap();
    let sol_price_feed =
        load_price_feed_from_account(&sol_price_key, &mut sol_price_account).unwrap();

    println!(".....SOL/USD.....");

    let current_time = SystemTime::now()
        .duration_since(UNIX_EPOCH)
        .unwrap()
        .as_secs() as i64;

    let maybe_price = sol_price_feed.get_price_no_older_than(current_time, 60);
    match maybe_price {
        Some(p) => {
            let price = p.price as f64 / 10u64.pow((-p.expo).try_into().unwrap()) as f64;
            println!("price ........... {}", price);
        }
        None => {
            println!("price ........... unavailable");
        }
    }

    let maybe_ema_price = sol_price_feed.get_ema_price_no_older_than(current_time, 60);
    match maybe_ema_price {
        Some(ema_price) => {
            let ema =
                ema_price.price as f64 / 10u64.pow((-ema_price.expo).try_into().unwrap()) as f64;
            println!("ema_price ....... {}", ema);
        }
        None => {
            println!("ema_price ....... unavailable");
        }
    }
    Ok(())
}

//よく使うトークンをenum型に
#[derive(Debug, Clone, Copy, Eq, PartialEq, PartialOrd, Hash)]
pub enum Mint {
    Usdc,
    Sol,
}

impl Mint {
    pub fn address(&self) -> &str {
        match &self {
            Mint::Usdc => "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v",
            Mint::Sol => "So11111111111111111111111111111111111111112",
        }
    }
    pub fn pubkey(&self) -> Pubkey {
        Pubkey::from_str(&self.address()).unwrap()
    }
    pub fn pyth_address(&self) -> &str {
        match &self {
            Mint::Usdc => "Gnt27xtC473ZT2Mw5u8wZ68Z3gULkSTb5DuxJy7eJotD",
            Mint::Sol => "H6ARHf6YXhGYeQfUzQNGk6rDNnLBQKrenN712K4AQJEG",
        }
    }
    pub fn pyth_pubkey(&self) -> Pubkey {
        Pubkey::from_str(&self.pyth_address()).unwrap()
    }
}

実行してみましょう

cargo r -r
実行結果
.....SOL/USD.....
price ........... 70.18446345
ema_price ....... 69.432091

3.JupiterのSwap APIを使ってみる

こちらは1月エアドロ予定で期待高まるJupiterです。dex aggregatorで、Solanaでswapするなら必ず触れることになるサービスです。主要なdexはほぼ網羅されているのでほぼbest priceでswapすることができます。
ドキュメント類も充実している方ですが、RustでSwap APIを使う解説はないので、TypeScript用の解説を読みつつgithubのjupiter-api-rust-exampleを直接読み解くことになります。

手順としては、quoteを取ったあとにtxを取得するので2回APIをたたくことになります。

とりあえずブラウザからAPIをたたいてみましょう
0.001 SOLをUSDCにswapするサンプルです(スリッページの設定は1bps=0.01%)
https://quote-api.jup.ag/v6/quote?inputMint=So11111111111111111111111111111111111111112&outputMint=EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v&amount=1000000&slippageBps=1

結果
{"inputMint":"So11111111111111111111111111111111111111112","inAmount":"1000000",
"outputMint":"EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v","outAmount":"69984",
"otherAmountThreshold":"69978","swapMode":"ExactIn","slippageBps":1,
"platformFee":null,"priceImpactPct":"0",
"routePlan":[{"swapInfo":{"ammKey":"2SgUGxYDczrB6wUzXHPJH65pNhWkEzNMEx3km4xTYUTC",
"label":"Invariant",
"inputMint":"So11111111111111111111111111111111111111112",
"outputMint":"EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v",
"inAmount":"1000000","outAmount":"69984","feeAmount":"100",
"feeMint":"So11111111111111111111111111111111111111112"},"percent":100}],
"contextSlot":236539032,"timeTaken":0.050486445}

Invariantというdexで0.001 SOLを0.069984 USDCにswapできるという結果がでました。実行時に0.069978 USDCを下回るようならスリッページエラーになります

では発注部分のテストプログラムを作っていきましょう

実際の発注部分はコメントアウトしてありますが、コメントをはずして実行すると実際に0.001 SOLをUSDCにswapするtxが実行されますので注意してください

また同様にappフォルダの下に新しいフォルダを作ります

cd /app
cargo init jup_swap_api_test
cd jup_swap_api_test

同様にCargo.tomlとmain.rsを編集します

Cargo.toml
[package]
name = "jup_swap_api_test"
version = "0.1.0"
edition = "2021"

[dependencies]
anyhow = "1.0.75"
anchor-client = "0.29.0"
bincode = "1.3.3"
shellexpand = "3.1.0"
tokio = { version = "1.35.0", features = ["full"] }
jupiter-swap-api-client = { git = "https://github.com/jup-ag/jupiter-api-rust-example.git" }
main.rs
use anchor_client::{
    solana_client::{nonblocking::rpc_client::RpcClient, rpc_config::RpcSendTransactionConfig},
    solana_sdk::{
        commitment_config::CommitmentLevel, pubkey::Pubkey, signature::read_keypair_file, signature::Signer,
        signer::keypair::Keypair, transaction::VersionedTransaction,
    },
};
use anyhow::Result;
use jupiter_swap_api_client::{
    quote::QuoteRequest, swap::SwapRequest, transaction_config::TransactionConfig,
    JupiterSwapApiClient,
};
use std::env;
use std::str::FromStr;

const PAYER: &str = "/app/id.json";
// String型の秘密鍵を使う場合
// const SECRET_KEY: &str = "your secret key";

#[tokio::main]
async fn main() -> Result<()> {
    let payer = read_keypair_file(&*shellexpand::tilde(PAYER)).expect("cannot read keypair");
    // String型の秘密鍵を読み込み
    // let payer = Keypair::from_base58_string(SECRET_KEY);

    let api_base_url = env::var("API_BASE_URL").unwrap_or("https://quote-api.jup.ag/v6".into());
    println!("Using base url: {}", api_base_url);

    let jupiter_swap_api_client = JupiterSwapApiClient::new(api_base_url);
    // 0.001 SOLをスリッページ50bps(=0.5%)でUSDCにswapします
    let quote_request = QuoteRequest {
        amount: 1_000_000,
        input_mint: Mint::Sol.pubkey(),
        output_mint: Mint::Usdc.pubkey(),
        slippage_bps: 50,
        ..QuoteRequest::default()
    };

    // GET /quote  Jupiterのswap APIからquoteを取得します
    let quote_response = jupiter_swap_api_client.quote(&quote_request).await.unwrap();
    println!("{quote_response:#?}");

    // POST /swap  Jupiterのswap APIから送信するtxの内容を取得します
    let swap_response = jupiter_swap_api_client
        .swap(&SwapRequest {
            user_public_key: payer.pubkey(),
            quote_response: quote_response.clone(),
            config: TransactionConfig::default(),
        })
        .await?;

    // println!("Raw tx len: {}", swap_response.swap_transaction.len());

    let versioned_transaction: VersionedTransaction =
        bincode::deserialize(&swap_response.swap_transaction)?;

    // println!("message: {:?}", versioned_transaction.message);

    let signed_versioned_transaction =
        VersionedTransaction::try_new(versioned_transaction.message, &[&payer])?;

    // send with rpc client...
    let rpc_client = RpcClient::new("https://api.mainnet-beta.solana.com".into());

    let config = RpcSendTransactionConfig {
        preflight_commitment: Some(CommitmentLevel::Processed),
        ..RpcSendTransactionConfig::default()
    };

    // 実際にtxを実行してしまうので念のためコメントアウトしてあります。適宜はずしてください
    // let signature = rpc_client
    //     .send_transaction_with_config(&signed_versioned_transaction, config)
    //     .await?;
    // println!("send_tx: {}", signature);
    Ok(())
}


//よく使うトークンをenum型に
#[derive(Debug, Clone, Copy, Eq, PartialEq, PartialOrd, Hash)]
pub enum Mint {
    Usdc,
    Sol,
}

impl Mint {
    pub fn address(&self) -> &str {
        match &self {
            Mint::Usdc => "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v",
            Mint::Sol => "So11111111111111111111111111111111111111112",
        }
    }
    pub fn pubkey(&self) -> Pubkey {
        Pubkey::from_str(&self.address()).unwrap()
    }
}

実際に発注してよければmain関数の最後のコメントアウトを外して実行してみましょう

cargo r -r
実行結果
Using base url: https://quote-api.jup.ag/v6
QuoteResponse {
    input_mint: So11111111111111111111111111111111111111112,
    in_amount: 1000000,
    output_mint: EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v,
    out_amount: 69663,
    other_amount_threshold: 69315,
    swap_mode: ExactIn,
    slippage_bps: 50,
    platform_fee: None,
    price_impact_pct: "0",
    route_plan: [
        RoutePlanStep {
            swap_info: SwapInfo {
                amm_key: 916HUQvjHzJ3UP1LgtyoVYyxhkwqbnBxPshxNKBbz5uN,
                label: "Meteora",
                input_mint: So11111111111111111111111111111111111111112,
                output_mint: EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v,
                in_amount: 1000000,
                out_amount: 69663,
                fee_amount: 100,
                fee_mint: EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v,
            },
            percent: 100,
        },
    ],
    context_slot: 236562560,
    time_taken: 0.068366911,
}
send_tx: 5HyjWJjujzuVvqVr5X5ddXhNxrvseYMz8z68y1Kx2Yv7ieMoFaxWeZMC34Ttgq44b4hbH4TerNChEWoxWXD97HX9

Solscanで確認してみましょう
https://solscan.io/tx/5HyjWJjujzuVvqVr5X5ddXhNxrvseYMz8z68y1Kx2Yv7ieMoFaxWeZMC34Ttgq44b4hbH4TerNChEWoxWXD97HX9

前編のまとめ

ここまでのテストプログラムをgitにまとめてあります。
lib/util/const_str.rsのPAYERにキーペアファイルの場所を指定して使用してください。

lib/util/const_str.rs
//自分のキーペアのファイルパスを指定
pub const PAYER: &str = "~/.config/solana/id.json";

write_keypair_fileはbin/write_keypair_file/src/main.rsのyour secret keyを自身のアカウントのString型の秘密鍵に置き換えて実行してください。

各プログラムはbinフォルダの中に入っているので、以下のような形でそれぞれ実行してください。

git clone https://github.com/pico-sol/solana-bot-sample.git
cd solana-bot-sample

carog r -r --bin write_keypair_file

carog r -r --bin read_keypair_file

carog r -r --bin token_list_test

carog r -r --bin pyth_test

carog r -r --bin jup_swap_api_test

改めて注意事項

秘密鍵の管理は厳格に行ってください。流出するとすべての資産がコントロールされます。

jup_swa_api_testはトランザクション発行部分をコメントアウトしてありますが、はずすと実際に0.001 SOLをUSDCにswapするトランザクションが発行されます。ご注意ください。
このコードを使用したいかなる損失も補償できません。

後編に続く。。。かも

大風呂敷を広げ過ぎて4~7はめんどくさくなって挫折しました🙇
botterのためのといいつつbotまでたどり着きませんでした

とりあえず残高とレートの取得と発注ができたので、あとはロジック入れて組み合わせればbotになりますね!!(投げやり)

ブログや記事を書かれてる皆さんの偉大さがよくわかりました。こんなめんどくさいことできるのすごい!
ということで前編は終わりです。
後編があるかも知れないし、ないかも知れません。

後編
4.JupiterのLimit Orderを使ってみる
5.PythとJupter Limit Orderを使ってbot的なものを作ってみる
6.Anchorフレームワークを使ってオンチェーンbotを作ってみる
7.botで使えるRustの非同期処理

8.おまけ

8-1. ポエム+ドヤ的な自己紹介

前回バブルには完全に乗り遅れて、2021年11月頃に原資2o万円くらいでbot開発を始めました。richmanチュートリアルをはじめHoheto先生の有料記事、magitoさんのmmbotの記事などとにかく読み漁り、自分なりに改良してみたりして試行錯誤しました。

mlbotや高頻度mmbotに取りつかれていましたが、どれもコツコツドカン型でうまくいかず。裁量でも資金を溶かす日々。今思えばおこづかいの範囲内しか資金がなかったのが逆に救いでした。

2022年の5月頃からSolanaを触り始め、atomic arbに取りつかれてちまちまbotに改良を重ね、今もatomic arb botや清算botを中心に開発してます。心の師匠はROSさんとQASHさんです。マーケットリスク取らずに技術で勝負ってところがかっこいいですね!!

ほぼSolanaしか触っていなくて冬の時代はbotの収益も微々たるものでしたが、今年の夏前くらいから月次A級弱くらいになり、その後SolanaのTVL急回復とともに劇的に収益が向上し、エアドロも結構もらえて最近は調子こいてます。今月は日次A級も何日か。11月12月で急に利益が出て、都内でキャッシュでマンション買えるくらいのクリプト残高になりました。

だいたい調子こくとやらかしがちなので、今後は調子こいて減らさないようがんばります

8-2. Solanaのtestnetバリデータのインセンティブプログラム(TDS'22)

bot以外ではSolanaのtestnetバリデータのインセンティブプログラムTDS'22を1年3ヶ月ほどやっていて、サーバー代($823×15ヶ月)が現在約10万ドルのSOL(ただし1年ロック)になりました。

FTXショックの時はさすがにもうやめようかと思いましたがロックされててどうにもならないので諦め、結果的にSOLが10ドル割っても淡々とドルコスト平均で強制的に買わされ続けたのがよかったですね

SOLがだいぶ上昇してしまったのでこの1年ほどのうまみがあるかはわかりませんが、実質時価の半額でDCA(ドルコスト平均)できるのはおいしいので続けていきます。SolanaのATH超えにbetしてるのでATHの2倍くらいになるまではガチホ予定です。

当初セッティングが大変でしたが今はsolvという便利なツールがあるのであっという間に設定できます。月に何度かバージョンアップなどがあるくらいで基本放置でOKです

TDS'22について詳しくはEnvbestさんの記事

camaoさんのdiscord、Solana Japanのvalidatorチャンネルが参考になります

8-3. Solanaに関する情報収集

X(Twitter)

Albertさん、おじおじさん、くりぷとべあーさん、k5さん、RGさん、camaoさん、さるおさんなどを参考にしています

特に手動アビトラ勢の方(Albertさん、おじおじさんなど)が何をしているかは思いっきり収益源になる可能性があるのでとても気になります👀

k5さん、RGさん、camaoさんの情報収集力・発信力はすごいですね。私は新しいプロジェクトとかに全くアンテナをはってないので一方的に情報を受け取るだけで心苦しいです。camaoさんはTDS'22を最初に始めた頃にもvalidator運営について教えていただきとてもありがたかったです

くりぷとべあーさん、さるおさんは技術力が高くてこちらも何されてるのか常に気になってます

Discord

Solana JapanのDiscord、DEG鯖などがとても参考になります
DEG鯖はノリが若い!!陰キャの私には会話に入りづらくて、たまーに書き込みするくらいで、Xでエアリプしたりしなかったりしてます。見てますよ👀

8-4. Rustのハマりポイント

私が最初よく理解できていなかったのは借用の概念ですね。最初はとにかく.clone()をつけまくってました。
あとはライフタイム。こちらは今も若干よくわかってなくて苦手です。
トレイトも最初わけわからなかったかな
最近はChatGPTでなんやかんや解決できるのでかなり便利になりました。

新規登録して、もっと便利にQiitaを使ってみよう

  1. あなたにマッチした記事をお届けします
  2. 便利な情報をあとで効率的に読み返せます
  3. ダークテーマを利用できます
ログインすると使える機能について

コメント

この記事にコメントはありません。

いいね以上の気持ちはコメントで

23