仮想通貨
NFT
solana
botter
183
どのような問題がありますか?

投稿日

10秒で6,000万円稼いだ「NFT高速MINT bot」

この記事では約6,000万円(2,800 SOL)の利益をあげた、「NFT高速MINT bot」のソースコードを公開します。
NFTをmintした当時、実際に使用したソースコードです。

また、当時私がどんなことをして利益を上げたのかが分かる内容にもなっています。

botter志望でソースコードを見たいという人だけではなく、NFTの波に乗って利益を上げてみたいけど何をすればいいのかわからない人にもヒントになる...かもしれないので、何らかの形でこの記事を役立ててもらえたら嬉しいです。

注意

筆者はプログラミング歴1年未満の初心者です。
ソースコードには重大な欠陥等が存在する可能性があります。

この記事に掲載された内容によって生じた損害等について、筆者は一切責任を負いません。

何をするbotなのか

このbotは、「Aurory」というゲームプロジェクトがNFTを販売開始するタイミングに合わせて、そのNFTをMINT(購入/発行)するトランザクションを素早く・大量に送信することによって、NFTをたくさん購入するbotです。

NFTのMINTのみをbotで行い、購入したNFTは手動で二次流通市場に出品して販売します。

前提

そもそもこのbotは、MINTしたNFTをMINT時に支払う価格より高い価格で二次流通市場で販売できることを前提として作られています。

・当時、SolanaのNFT界隈は盛り上がりを見せており、SollamaskaijuCardsなどの何の価値もなさそうなNFTでも、MINT価格より高い価格で二次流通市場で売買される状態だった。
・AuroryはTwitterのフォロワー数も多く、AlamedaResearch等のVCも出資していたので、前述したNFTプロジェクトを圧倒的に上回る期待が寄せられていた。

ので、AuroryのNFTが購入できた場合はMINT価格より高い価格で販売できるだろうと予想しました。

他のNFTの販売時にはbotは作れなかったけど、Auroryでは作れた

当時のSolanaのNFTプロジェクトの多くは、販売開始時刻を過ぎてからユーザーにwebサイトの読み込みをさせてmintページを配信していました。販売開始時刻に非常に多くのアクセスが集まるので、プロジェクトの運営が管理するwebサーバーが落ちてしまうことも多かったです。

Auroryは販売開始時刻の前からmintページを配信しておくことで、販売開始時刻にアクセスが集まるのを防ぎ、webサーバーがダウンしないようにしました。
mintページが事前に配信されていたため、mintページの内部で使用されているjsファイルを読み解けば「mintボタンを押した際に発行されるトランザクションの作り方」がわかる状態であり、botを作ることが可能でした。

botを作る

「トランザクションの作り方」がわかる状態ではあったのですが、mintページの内部で使用されていたjsファイルは「bundle.js」という名前の難読化・最小化されたファイルで、人間が読み解いて理解するには難しいものでした。

難読化されたファイルを読むのは難しかったのですが、トランザクションを作成する部分さえ理解できればよかったので、「instruction」とか「publickey」とか、トランザクションを作る時に使いそうなワードを「Ctrl + F」で検索したりしながら気合で読み解いていき、botを書き進めていました。

また、このjsファイルが事前に閲覧できる状態であることに気が付いたのは、販売開始時刻の5時間前だったので、急いでjsファイルを読み解いてbotを作る必要がありました。

・難読化されたファイルを読み解く必要がある
・5時間以内にbotを完成させる必要がある

二つの壁が立ちはだかっていましたが、販売開始時刻の30分前にbotを無事完成させることができました。

ソースコード

完成させたbotのソースコードがこちらです。

main.py
import asyncio
import aiohttp
import construct
import dotenv
import os
from base64 import b64encode
from dotenv import load_dotenv
from solana import system_program
from solana.system_program import create_account, CreateAccountParams, AssignParams, assign
from solana.blockhash import Blockhash
from solana.account import Account
from solana.rpc.api import Client
from solana.rpc.providers.http import HTTPProvider
from solana.publickey import PublicKey, base58
from solana.transaction import AccountMeta, TransactionInstruction, Transaction
from solana.rpc import commitment, types
from spl.token.instructions import InitializeMintParams, initialize_mint, create_associated_token_account, mint_to, MintToParams, get_associated_token_address
from spl.token.constants import TOKEN_PROGRAM_ID
from typing import Any, Dict, List, Optional, Union

class Main:

    opts = types.TxOpts(skip_preflight = True)

    account_secret = ""
    SYSTEM_PROGRAM_ID = PublicKey("11111111111111111111111111111111")
    CANDY_PROGRAM_ID = PublicKey("cndyAnrLdpjq1Ssp1z8xxDsB8dxe7u4HL5Nxi2K5WXZ")

    owner = None
    rpc1 = "https://solana-api.projectserum.com"
    rpc2 = "https://api.mainnet-beta.solana.com"
    client = Client("https://solana-api.projectserum.com")
    TIMEOUT = 3600
    session = None
    request_id = 1
    blockhash = None
    tasks = []
    tx_count = 0

    def __init__(self, secret_key):

        self.account_secret = secret_key
        self.owner = Account(base58.b58decode(self.account_secret)[:32])
        loop = asyncio.get_event_loop()
        tasks = [
                    self.main(),
                    self.get_blockhash()
                ]
        loop.run_until_complete(asyncio.wait(tasks))

    async def get_blockhash(self):
        while True:
            res = self.client.get_recent_blockhash(commitment.Commitment("recent"))
            self.blockhash = Blockhash(res["result"]["value"]["blockhash"])
            await asyncio.sleep(0.1)

    async def main(self):
        while True:
            asyncio.create_task(self.mint())
            self.request_id += 1
            await asyncio.sleep(0.1)

    def candyInstruction(self, mintAccount):
        programId = self.CANDY_PROGRAM_ID
        metadatapubkey, _ = PublicKey.find_program_address(
            seeds=['metadata'.encode('utf-8') ,bytes(PublicKey("metaqbxxUerdq28cj1RbAWkYQm3ybzjb6a8bt518x1s")), bytes(mintAccount.public_key())], program_id=PublicKey("metaqbxxUerdq28cj1RbAWkYQm3ybzjb6a8bt518x1s")
        )
        editionpubkey, _ = PublicKey.find_program_address(
            seeds=['metadata'.encode('utf-8') ,bytes(PublicKey("metaqbxxUerdq28cj1RbAWkYQm3ybzjb6a8bt518x1s")), bytes(mintAccount.public_key()), 'edition'.encode('utf-8')], program_id=PublicKey("metaqbxxUerdq28cj1RbAWkYQm3ybzjb6a8bt518x1s")
        )
        keys = [
            AccountMeta(pubkey=PublicKey("D49FEB8s1PjKYawXE6WW64QvaDPxT5ALNzqpxkD7XKkH"), is_signer=False, is_writable=False),
            AccountMeta(pubkey=PublicKey("9vwYtcJsH1MskNaixcjgNBnvBDkTBhyg25umod1rgMQL"), is_signer=False, is_writable=True),
            AccountMeta(pubkey=self.owner.public_key(), is_signer=True, is_writable=True),
            AccountMeta(pubkey=PublicKey("aury7LJUae7a92PBo35vVbP61GX8VbyxFKausvUtBrt"), is_signer=False, is_writable=True),
            AccountMeta(pubkey=metadatapubkey, is_signer=False, is_writable=True),
            AccountMeta(pubkey=mintAccount.public_key(), is_signer=True, is_writable=True),
            AccountMeta(pubkey=self.owner.public_key(), is_signer=True, is_writable=True),
            AccountMeta(pubkey=self.owner.public_key(), is_signer=True, is_writable=True),
            AccountMeta(pubkey=editionpubkey, is_signer=False, is_writable=True),
            AccountMeta(pubkey=PublicKey("metaqbxxUerdq28cj1RbAWkYQm3ybzjb6a8bt518x1s"), is_signer=False, is_writable=False),
            AccountMeta(pubkey=TOKEN_PROGRAM_ID, is_signer=False, is_writable=False),
            AccountMeta(pubkey=self.SYSTEM_PROGRAM_ID, is_signer=False, is_writable=False),
            AccountMeta(pubkey=PublicKey("SysvarRent111111111111111111111111111111111"), is_signer=False, is_writable=False),
            AccountMeta(pubkey=PublicKey("SysvarC1ock11111111111111111111111111111111"), is_signer=False, is_writable=False),
        ]
        data = b'\xd3\x39\x06\xa7\x0f\xdb\x23\xfb'
        return TransactionInstruction(
          keys,
          programId,
          data
        )

    async def mint(self):
        transaction = Transaction()
        newaccount = Account()
        signers: List[Account] = [self.owner, newaccount]
        #create account
        ca_param = CreateAccountParams(
            from_pubkey=self.owner.public_key(),
            new_account_pubkey=newaccount.public_key(),
            lamports=1461600,
            space=82,
            program_id=self.SYSTEM_PROGRAM_ID
        )
        transaction.add(
            create_account(ca_param)
        )

        assign_param = AssignParams(
            newaccount.public_key(),
            TOKEN_PROGRAM_ID
        )
        transaction.add(
            assign(assign_param)
        )

        #initialize mint
        im_param = InitializeMintParams(
            decimals=0,
            program_id=TOKEN_PROGRAM_ID,
            mint=newaccount.public_key(),
            mint_authority=self.owner.public_key(),
            freeze_authority=self.owner.public_key()
        )
        transaction.add(
            initialize_mint(im_param)
        )

        #create associated token account
        transaction.add(
            create_associated_token_account(
                payer=self.owner.public_key(),
                owner=self.owner.public_key(),
                mint=newaccount.public_key()
            )
        )
        associated_token_address = get_associated_token_address(
            owner=self.owner.public_key(),
            mint=newaccount.public_key()
        )

        #mint to
        mint_to_param = MintToParams(
            program_id=TOKEN_PROGRAM_ID,
            mint=newaccount.public_key(),
            dest=associated_token_address,
            mint_authority=self.owner.public_key(),
            amount=1,
        )
        transaction.add(
            mint_to(mint_to_param)
        )

        #candymachine
        transaction.add(
            self.candyInstruction(newaccount)
        )


        return await self.send_tx(transaction, *signers, opts=self.opts)

    async def send_tx(self, txn: Transaction, *signers: Account, opts: types.TxOpts = types.TxOpts()):
        txn.recent_blockhash = self.blockhash
        txn.sign(*signers)
        tx = txn.serialize()
        return await self.send_raw_tx(tx, opts)

    async def send_raw_tx(self, txn: Union[bytes, str], opts: types.TxOpts = types.TxOpts()):
        if isinstance(txn, bytes):
            txn = b64encode(txn).decode("utf-8")
        self.tx_count += 1
        params = txn, {"skipPreflight":opts.skip_confirmation,"skipPreflight":opts.skip_preflight,"encoding":"base64"}
        if self.tx_count % 2 == 0:
            provider = HTTPProvider(self.rpc1)
            headers = {"Content-Type": "application/json"}
            data = provider.json_encode({"jsonrpc": "2.0", "id": self.request_id, "method": types.RPCMethod("sendTransaction"), "params": params})
            async with aiohttp.ClientSession() as session:
                response = await session.post(
                    self.rpc1,
                    data=data,
                    headers=headers
                )
                content = await response.text()
                print(content)
                return content
        else:
            provider = HTTPProvider(self.rpc2)
            headers = {"Content-Type": "application/json"}
            data = provider.json_encode({"jsonrpc": "2.0", "id": self.request_id, "method": types.RPCMethod("sendTransaction"), "params": params})
            async with aiohttp.ClientSession() as session:
                response = await session.post(
                    self.rpc2,
                    data=data,
                    headers=headers
                )
                content = await response.text()
                print(content)
                return content


if __name__ == '__main__':

    load_dotenv()
    secret_key = os.getenv("SECRET_KEY")
    Main(secret_key)

ソースコードの内容についての説明は省略します。

Pythonを使ってコーディングしたため、下記のライブラリを利用しました。

一般的にSolanaでclient側のコーディングをする際はTypeScriptを使います。
TypeScriptでコーディングする場合は、下記のライブラリを利用します。

botを動かした結果...

このbotを動かした結果...

85体のAuroryをMINT(購入/発行)することに成功しました。

aurory01.pngaurory02.png
aurory03.pngaurory04.png
(↑購入できたAuroryNFTの一部)

Aurory運営は1枚5SOLで販売すると告知していたのに、実際は1枚1SOLで販売していました。

私の予算は約90SOLだったので、十数枚程度しか買えないはずでしたが、1枚1SOLで販売してくれたので85枚も購入することができました。

購入したNFTを販売しよう

NFTがたくさん購入できたので、二次流通市場であるsolanartで販売しました。

当時Twitterでもイキっていましたが、大量に出品すると飛ぶように売れたのですごく気持ちよかったです。

結局、85体のAuroryNFTを販売して約2,800SOLの利益がでました。ROIは+3,290%でした。

おわり

売り出されたばかりのNFTをbotで大量に購入して利益を上げるという方法は、おそらく去年には存在していませんでした。
その分野に十分な資金が集まっておらず、大量に買って即利益が出るようなNFTは存在しなかったからです。

最近はGameFiやPlay to earn、メタバースなどのワードが人気を集めており、それらに関係するプロジェクトなどが新たな投資/投機の対象になっています。

botを自作するスキルを持っていると、その投資/投機の対象を利用して利益を上げる戦略の幅が広がります。

稼ぎたいならbotterになろう!!

botterが最強!!

botterしか勝たん!!


今回の記事は以上です。見ていただいてありがとうございました!

ユーザー登録して、Qiitaをもっと便利に使ってみませんか。
  1. あなたにマッチした記事をお届けします
    ユーザーやタグをフォローすることで、あなたが興味を持つ技術分野の情報をまとめてキャッチアップできます
  2. 便利な情報をあとで効率的に読み返せます
    気に入った記事を「ストック」することで、あとからすぐに検索できます
teto_btc
高校生です
この記事は以下の記事からリンクされています
Qiita週間トレンド記事一覧からリンク

コメント

リンクをコピー
このコメントを報告

NFTに詳しくないんですが、そのSOLは現金化できるんですか?

0
どのような問題がありますか?
あなたもコメントしてみませんか :)
ユーザー登録
すでにアカウントを持っている方はログイン
183
どのような問題がありますか?
ユーザー登録して、Qiitaをもっと便利に使ってみませんか

この機能を利用するにはログインする必要があります。ログインするとさらに下記の機能が使えます。

  1. ユーザーやタグのフォロー機能であなたにマッチした記事をお届け
  2. ストック機能で便利な情報を後から効率的に読み返せる
ユーザー登録ログイン
ストックするカテゴリー