Как написать диспарсер на солане
Конечно солановский блокчейн немного отличается от блокчейна эфира, но в целом все примерно также работает.
Программа - Смарт контракт только на солане.
Лампорты - Самая маленькая часть токена Солана, 1 лампорт ~ 0.000000001 sol. В них оцениваются все комиссии на транзакции
IDL - ABI только на солане. Нужно чтобы используя веб3 можно было через любой код взаимодействовать с программой.
Раст - язык программирования на котором написан блокчейн соланы, а также все программы.
Camel Case - верблюжий регистр, используется в джаваскрипте для обозначения функций и переменных. Пример: disperseSol
Snake Case - змеинный регистр, используется в расте для обозначения функций и переменных. Пример: disperse_sol
Что же понадобится для воиспроизведения всех следующих действий
Для написание самой программы нужно будет скачать раст, скачать его можно с официального сайта. Но по стольку по скольку это С-подобный язык, для него также надо скачать некоторые компоненты. (перейдя по этой ссылке автоматически начнется скачивание инсталлера нужных компонентов) После установления инсталлера, в него нужно будет зайти, и установить сами компоненты.
Выбирайте данный модуль в левом верхнем углу
Нажимайте установить в правом нижнем углу и ждите окончания установки
Да, доп компоненты занимают не мало места, но на то раст и сделан, чтобы с безопасностью и со всеми другими преспособлениями можно было легко и удобно работать с памятью и т.д, но сейчас не об этом.
Если вы успешно сделали все выше указанные действия, то можете приступать к установке самого раста. Если вы работаете на виндовс, то вы должны были скачать exe инсталлер с официального сайта о котором мы говорили выше. При запуске ехе, если вы увидите данный текст:
Значит, вы правильно установили доп компоненты, и введя в консоль 1 и нажав Enter, начнется загрузка, по окончанию которой вам сообщат что возможно надо будет добавить саму основную директорию раста (.cargo/bin) в PATH окружения вашего компьютера. Обычно конечно раст все это делает сам, но это можно легко проверить. Если открыть новую консоль, найдя коммандную строку в поиске виндовс, и ввести туда:
cargo --version
Если вывод будет примерно похож на это (у вас возможно будет другая версия), то значит все работает и ничего менять не надо:
Если же в выводе будет ошибка, и будет написано что команды cargo не существует, то вам придется самому добавит ее в PATH. По этому поводу есть много гайдов в интернете, вот один пример, поэтому с этим проблем не должно быть.
После установки раста, надо будет обязательно установить сами cli компоненты соланы. Там в зависимости от вашей системы, устанавливайте все по инструкции. Возможно у вас будут проблемы с PATH окружения, но если что все описано на самом сайте, а также при установке вам сам инсталлер все выведет в консоль и скажет что точно надо сделать, чтобы добавить солану к PATH. Для проверки добавилась ли солана правильно в PATH, можно проверить ровно также как и раст, только соответственно заменяя cargo на solana:
solana --version
Также для провождения тестов ну и так скажем для взаимодействия с самими программами нужно скачать node.js, и несколько дополнительных модулей вместе с ним. С установкой node.js больших проблем не должно быть, вот хороший гайд как установить node.js на любую линовскую ОС, если же будут проблемы с установкой на прочие ОС то можно найти кучу гайдов на том же ютубе, поэтому проблем тут быть не должно.
Чтобы установить доп модули можно просто ввести данную команду в коммандную строку/консоль и дождаться пока все установится (учитывая что node вы установили правильно и он у вас работает):
npm install -g ts-node typescript yarn @project-serum/anchor-cli
В данном гайде мы решили использовать некоторые модули, чтобы упростить саму инициализацию программы, а также чтобы не надо было писать много лишнего и не понятного кода с нуля.
anchor init disperse
Вместо disperse можно ввести любое другое значение (это название самого проекта)
После скачивания всех дополнительных компонентов и инициализации проекта, у вас в директории в которой вы сейчас находитесь создадиться папка в нашем случае под названием disperse. В этой папке сразу будет весь нужный написанный код под саму программу, а также по тесты написанные в данном случае на typescript.
Объяснение всех созданных папок и файлов
Вот все папки и файлы которые должны были создаться при выполнении функции выше:
Anchor.toml - Общий конфиг самой программы
Тут оставляйте все точно также, кроме как самой программы, которая указана в занчении disperse, а так для тестов на локалнете можно оставить тот адресс который стоит там по дефолту.
Как получить адресс программы показано ниже в статье
Если же вы захотите деплоить на мейн или дев нет, то надо будет также поменять значение programs.localnet и cluster = "localnet" на programs.devnet/programs.mainnet и cluster = "devnet"/cluster = "mainnet" соответственно.
programs - Папка где находится сам код программы (programs/disperse/src/lib.rs), а также прочие папки и файлы с нужными модулями, но их трогать не надо, потому что anchor сам установил в них все нужные модули и значения при инициализации.
tests - Папка в которой находится сам код через который мы будем проводить тесты и подключаться к программе
target - Папка в которой хранится информация и деплой файл программы, а также IDL, который надо использовать чтобы можно было взаимодействовать с программой
На все остальные папки можно не обращать внимания, потому что в них либо же различные файлы для модулей которые мы установили раньше, либо же прочие системные и тестовые файлы.
Файлы программы и тестов и подробное объяснение
Вот весь код который должен быть в файле lib.rs, если лень читать дальше можете просто скопировать и вставить, а так дальше будет подробное объяснение:
use anchor_lang::prelude::*; declare_id!("A1qXKzEUEgh9xRs6RvFwKZzTxrQEshynQm7tdc3ALEPu"); #[program] pub mod disperse { use super::*; pub fn disperse_sol<'info>(ctx: Context<'_, '_, '_, 'info, Initialize<'info>>, amount: u64) -> Result<()> { for address in ctx.remaining_accounts { anchor_lang::solana_program::program::invoke( &anchor_lang::solana_program::system_instruction::transfer( &ctx.accounts.initializer.key(), &address.key(), amount, ), &[ ctx.accounts.initializer.to_account_info(), address.to_account_info(), ], ); }; Ok(()) } } #[derive(Accounts)] pub struct Initialize<'info> { #[account(mut)] pub initializer: Signer<'info>, pub system_program: Program<'info, System>, }
Сама функция раскидки соланы на разные кошельки можно увидеть на 9 строчке, под названием disperse_sol. Также в саму функцию мы передаем контекст, в котором находится сам инициализатор программы (кошелек с которого будет солана раскидываться), плюс в контексте также передается список кошельков на которые деньги будут переведены. А также в аргументах функции можно увидеть сколько соланы будет переведено на каждый кошелек - amount: u64, amount - само количество в Лампортах, а u64 - тип данных в нашем случае положительное число с максимальным значением 18446744073709551615 (64 бита), поэтому хоть Лампорты занимают очень маленькую часть одной соланы, через эту программу можно все равно раскидывать большое количество соланы.
В самой функции можно увидеть цикл который проходится по всем кошелькам, которые мы со своей программы должны указать в переменную remainingAccounts, но об этом чуть позже, а также внутри цикла - две другие функции - transfer - инициализация самой транзакции, в эту функцию надо передать три аргумента - из какого аккаунта, на какой, сколько лампортов; а также функция invoke - подписание самой транзакции чтобы она отправилась, в эту функцию надо передать всего два аргумента - саму транзакцию, и список в котором содержиться сериализованная информация об обоих аккаунтах (сериализация производится использую функцию .to_account_info(), о ней подробней можно почитать в документации). Ну и в самом конце функции можно увидеть оператор Ok(()), который просто символизирует успешное проведение транзакции.
В самом низу кода объявляется сам класс, который мы передаем в виде контекста в нашу функцию. initializer - Инициализатор программы, с которого списывается солана и переводится на другие аккаунты. system_program - просто переменная программы, на нее в принципе можно и не обращать внимание. И да, на сколько вы могли заметить, в этот класс мы не передаем значение remaining_accounts, это все потому что солана и anchor еще не супортят списки аккаунтов (их просто нужно в определенной форме передовать), поэтому надо использовать уже существующую переменную, которая автоматом заложена в контексте.
Имплементация и тесты программы через typescript:
Вот опять же таки весь код файла disperse.ts, а также дальше подробное объяснение:
import * as anchor from "@project-serum/anchor"; import { Program, BN } from "@project-serum/anchor"; import { Disperse } from "../target/types/disperse"; describe("disperse", () => { anchor.setProvider(anchor.Provider.env()); const program = anchor.workspace.Disperse as Program<Disperse>; it("Is initialized!", async () => { const initializer = anchor.web3.Keypair.generate(); const connection = await anchor.getProvider().connection; let signature = await connection.requestAirdrop(initializer.publicKey, 10*anchor.web3.LAMPORTS_PER_SOL) await connection.confirmTransaction(signature); console.log("Main account initial balance: ", await connection.getBalance(initializer.publicKey)); const kp1 = anchor.web3.Keypair.generate(); const kp2 = anchor.web3.Keypair.generate(); const kp3 = anchor.web3.Keypair.generate(); const kp4 = anchor.web3.Keypair.generate(); const kp5 = anchor.web3.Keypair.generate(); console.log("Secondary account initial balance: ", await connection.getBalance(kp1.publicKey)); const tx = await program.rpc.disperseSol(new BN(1 * anchor.web3.LAMPORTS_PER_SOL), { accounts: { initializer: initializer.publicKey, systemProgram: anchor.web3.SystemProgram.programId }, remainingAccounts: [ { pubkey: kp1.publicKey, isWritable: true, isSigner: false }, { pubkey: kp2.publicKey, isWritable: true, isSigner: false }, { pubkey: kp3.publicKey, isWritable: true, isSigner: false }, { pubkey: kp4.publicKey, isWritable: true, isSigner: false }, { pubkey: kp5.publicKey, isWritable: true, isSigner: false }, ], signers: [initializer] }); console.log("Your transaction signature", tx); console.log("Main account final balance: ", await connection.getBalance(initializer.publicKey)); console.log("Secondary account final balance: ", await connection.getBalance(kp1.publicKey)); }); });
Сверху импортируются все нужные модули, а также сам IDL программы, дальше идет сама тестовая функция - describe, она будет работать только при тестах, потому что для этого используется специальный модуль. В ней указывается сама программа, а потом уже идет главная асинхронная функция в которой мы создаем главный и второстепенные кошельки, а также на главный кошелек аирдропаем 10 соланы, чтобы можно было провести сам тест. Так же в консоль выводятся для показания балансы кошельков до и после самой раскидки.
После создания кошельков, идет инициализация самой программы, через функцию которую мы указали в коде самой программы, только заметьте что тут все в camelCase, поэтому функция не disperse_sol а disperseSol. То же самое относится к указаннию переменной программы (systemProgram) и адресам кошельков на которые расикидывается солана (remainingAccounts).
Также заметьте, что количество соланы передается в виде Big Number - сериализация в бинарное значение.
В переменную accounts надо указать то что у нас указанно в контексте в коде самой программы, соответственно адрес самого инициализатора, ну и в переменную программы надо просто вставить то значение которое в самом начале кода мы назначини в переменную окружения.
Еще, самое главное - правильно указать адреса в remainingAccounts, опять же таки как мы писали выше, указывать кошельки туда надо в определенном формате, и хоть таким образом это делать не совсем удобно, можно используя функцию map() считать все значения кошельков и добавить их в новый список уже в нужном формате, но сейчас не об этом. Так вот что нужно указать: сам адресс кошелька, нужно ли его изменять в программе (isWritable) в нашем случае да, потому что нам надо на кошельки эти перевести солану, а также является ли кошелек подписчиком транзакции (isSigner) в нашем случае транзакцию подписывает только инициализатор, так как именно с главного аккаунта раскидывается солана, поэтому ставим false.
Ну и в самом конце нужно соответственно указать подписчика транзакции, в нашем случае - это инициализатор транзакции потому что с него как раз-таки переводится солана на другие кошельки.
Да, конечно мы написали только тесты для программы, и только на локальной сети, но в интернете есть очень много гайдов и инструкций как имплементировать свои программы в свой код.
А так чтобы выполнить тесты нужно ввести данную функцию находясь в консоле в директории основной папки проекта:
anchor test
Таким образом сразу в локальной сети без всяких валидаторов и не заплатив ни одного цента вы сможете провести тесты над программой. В нашем случае выводится такое в консоль:
Если вы хотите задеплоить и запустить свою программу, вам сначала надо создать кошелек используя данную функцию:
solana-keygen new --output <ФАЙЛ В КОТОРОМ ХОТИТЕ СОХРАНИТЬ КОШЕЛЕК>
потом указать созданный файл в конфиге программы (файл Anchor.toml), а также чтобы задеплоить саму программу и получить сам адресс программы, то вам надо ввести данную функцию:
anchor deploy
Также надо не забыть ввести данную функцию, которая запускает тестовый валидатор, если вы деплоите программу на локальной сети:
solana-test-validator
Не забывайте что нужно иметь около 3 соланы на кошельке если вы хотите задеплоить программу на любой сети.