Фреймворк Anchor использует макросы Rust для сокращения шаблонного кода и упрощения реализации общих проверок безопасности, необходимых для написания программ Solana.
Думайте об Anchor как о фреймворке для программ Solana, так же как Next.js для веб- разработки. Подобно тому, как Next.js позволяет разработчикам создавать веб-сайты с использованием React вместо того, чтобы полагаться исключительно на HTML и TypeScript, Anchor предоставляет набор инструментов и абстракций, которые делают создание программ Solana более интуитивно понятным и безопасным.
Основные макросы, встречающиеся в программе Anchor, включают:
declare_id
: Определяет адрес программы on-chain#[program]
: Определяет модуль, содержащий логику инструкций программы#[derive(Accounts)]
: Применяется к структурам для указания списка аккаунтов, необходимых для выполнения инструкции#[account]
: Применяется к структурам для создания пользовательских типов учетных записей, специфичных для программы
Anchor программа #
Ниже приведена простая Anchor программа с одной инструкцией, которая создает новую учетную запись. Мы пройдем по ней, чтобы объяснить базовую структуру Anchor программы. Вы можете запустить этот пример на Solana Playground.
use anchor_lang::prelude::*;
declare_id!("11111111111111111111111111111111");
#[program]
mod hello_anchor {
use super::*;
pub fn initialize(ctx: Context<Initialize>, data: u64) -> Result<()> {
ctx.accounts.new_account.data = data;
msg!("Changed data to: {}!", data);
Ok(())
}
}
#[derive(Accounts)]
pub struct Initialize<'info> {
#[account(init, payer = signer, space = 8 + 8)]
pub new_account: Account<'info, NewAccount>,
#[account(mut)]
pub signer: Signer<'info>,
pub system_program: Program<'info, System>,
}
#[account]
pub struct NewAccount {
data: u64,
}
declare_id макрос #
Для задания on-chain адреса программы (ID программы) используется макрос
declare_id
.
use anchor_lang::prelude::*;
declare_id!("11111111111111111111111111111111");
Когда вы впервые создаете программу Anchor, платформа генерирует новую пару
ключей, используемую для развертывания программы (если не указано иное).
Открытый ключ из этой пары ключей следует использовать в качестве идентификатора
программы в declare_id
макросе.
- При использовании Solana Playground идентификатор программы обновляется автоматически и может быть экспортирован с помощью пользовательского интерфейса.
- При локальной сборке пару ключей программы можно найти в
/target/deployment /your_program_name.json
program макрос #
В макросе
#[program]
указывает модуль, содержащий все инструкции программы. Каждая публичная функция
в модуле представляет отдельную инструкцию для программы.
В каждой функции первым параметром всегда является тип Context
. Последующие
параметры, которые являются необязательными, определяют любые дополнительные
параметры data
, необходимые для инструкции.
use anchor_lang::prelude::*;
declare_id!("11111111111111111111111111111111");
#[program]
mod hello_anchor {
use super::*;
pub fn initialize(ctx: Context<Initialize>, data: u64) -> Result<()> {
ctx.accounts.new_account.data = data;
msg!("Changed data to: {}!", data);
Ok(())
}
}
#[derive(Accounts)]
pub struct Initialize<'info> {
#[account(init, payer = signer, space = 8 + 8)]
pub new_account: Account<'info, NewAccount>,
#[account(mut)]
pub signer: Signer<'info>,
pub system_program: Program<'info, System>,
}
#[account]
pub struct NewAccount {
data: u64,
}
Тип
Context
предоставляет инструкции доступ к следующим входам без аргументов:
pub struct Context<'a, 'b, 'c, 'info, T> {
/// Currently executing program id.
pub program_id: &'a Pubkey,
/// Deserialized accounts.
pub accounts: &'b mut T,
/// Remaining accounts given but not deserialized or validated.
/// Be very careful when using this directly.
pub remaining_accounts: &'c [AccountInfo<'info>],
/// Bump seeds found during constraint validation. This is provided as a
/// convenience so that handlers don't have to recalculate bump seeds or
/// pass them in as arguments.
pub bumps: BTreeMap<String, u8>,
}
Context
— это универсальный тип, в котором T
представляет набор учетных
записей, требуемых инструкции. При определении инструкции Context
типом T
является структура, реализующая Accounts
признак (Context<Initialize>
).
Этот параметр контекста позволяет инструкции получить доступ:
ctx.accounts
: Аккаунты инструкцииctx.program_id
: Адрес самой программыctx.remaining_accounts
: Все оставшиеся аккаунты, предоставленные инструкции но не указанные в структуреAccounts
ctx.bumps
: Пополнить seeds для любых Программных адресов (PDA) аккаунтов, указанные в структуреAccounts
макрос derive(Accounts) #
Макрос
#[derive(Accounts)]
применяется к структуре и реализует
Accounts
признак. Используется для определения и проверки набора аккаунтов, необходимых
для конкретной инструкции.
#[derive(Accounts)]
pub struct Initialize<'info> {
#[account(init, payer = signer, space = 8 + 8)]
pub new_account: Account<'info, NewAccount>,
#[account(mut)]
pub signer: Signer<'info>,
pub system_program: Program<'info, System>,
}
Каждое поле в структуре представляет учетную запись, требуемую инструкцией. Именование каждого поля произвольное, но рекомендуется использовать описательное имя, указывающее назначение учетной записи.
#[derive(Accounts)]
pub struct Initialize<'info> {
#[account(init, payer = signer, space = 8 + 8)]
pub new_account: Account<'info, NewAccount>,
#[account(mut)]
pub signer: Signer<'info>,
pub system_program: Program<'info, System>,
}
При создании программ Solana важно проверять учетные записи, предоставленные клиентом. Эта проверка достигается в Anchor посредством ограничений учетной записи и указания соответствующих типов учетной записи:
-
Account Constraints: Ограничения определяют дополнительные условия, которым должна удовлетворять учетная запись, чтобы считаться действительной для инструкции. Ограничения применяются с использованием атрибута
#[account(..)]
, который помещается выше поля учетной записи в структуреAccounts
.#[derive(Accounts)] pub struct Initialize<'info> { #[account(init, payer = signer, space = 8 + 8)] pub new_account: Account<'info, NewAccount>, #[account(mut)] pub signer: Signer<'info>, pub system_program: Program<'info, System>, }
-
Account Types: Anchor предоставляет различные типы учетных записей, чтобы гарантировать, что учетная запись, предоставленная клиентом, соответствует ожиданиям программы.
#[derive(Accounts)] pub struct Initialize<'info> { #[account(init, payer = signer, space = 8 + 8)] pub new_account: Account<'info, NewAccount>, #[account(mut)] pub signer: Signer<'info>, pub system_program: Program<'info, System>, }
Учетные записи в структуре Accounts
доступны в инструкции через Context
,
используя синтаксис ctx.accounts
.
use anchor_lang::prelude::*;
declare_id!("11111111111111111111111111111111");
#[program]
mod hello_anchor {
use super::*;
pub fn initialize(ctx: Context<Initialize>, data: u64) -> Result<()> {
ctx.accounts.new_account.data = data;
msg!("Changed data to: {}!", data);
Ok(())
}
}
#[derive(Accounts)]
pub struct Initialize<'info> {
#[account(init, payer = signer, space = 8 + 8)]
pub new_account: Account<'info, NewAccount>,
#[account(mut)]
pub signer: Signer<'info>,
pub system_program: Program<'info, System>,
}
#[account]
pub struct NewAccount {
data: u64,
}
Когда вызывается инструкция в программе Anchor, программа выполняет следующие
проверки, как указано в Accounts
структуре:
-
Проверка типа учетной записи: проверяет, что учетные записи, переданные в инструкцию, соответствуют типам учетных записей, определенным в Контексте инструкции.
-
Проверки ограничений: он проверяет учетные записи на соответствие любым указанным дополнительным ограничениям.
Это помогает убедиться в том, что учетные записи, передаваемые инструкции от клиента являются действительными. Если проверка не выполнена, то инструкция завершится с ошибкой до достижения основной логики функции обработчика инструкций.
Более подробные примеры см. в разделах ограничения и типы учетных записей документации Anchor.
account макрос #
Макрос
#[account]
применяется к структурам для определения формата пользовательского типа учетной
записи данных для программы. Каждое поле в структуре представляет поле, которое
будет храниться в данных аккаунта.
#[account]
pub struct NewAccount {
data: u64,
}
Этот макрос реализует различные черты описанные здесь. Ключевые функции макроса `#[account] включают:
- Назначить владельца:
При создании учетной записи владельцем учетной записи автоматически
назначается программа, указанная в
declare_id
. - [Установка дискриминатора](https://github.com/coral-xyz/anchor/blob/852fcc77beb6302474a11e0f8e6f1e688021be36/lang/attribute/account/src/lib. s#L101-L117): Уникальный 8-байтный дискриминатор, специфичный для типа аккаунта, добавляется в качестве первых 8 байт данных учетной записи во время ее инициализации. Это помогает различать типы учетных записей и проверять их.
- Сериализация и десериализация данных: Данные аккаунта, соответствующие типу учетной записи, автоматически сериализуются и десериализуются.
use anchor_lang::prelude::*;
declare_id!("11111111111111111111111111111111");
#[program]
mod hello_anchor {
use super::*;
pub fn initialize(ctx: Context<Initialize>, data: u64) -> Result<()> {
ctx.accounts.new_account.data = data;
msg!("Changed data to: {}!", data);
Ok(())
}
}
#[derive(Accounts)]
pub struct Initialize<'info> {
#[account(init, payer = signer, space = 8 + 8)]
pub new_account: Account<'info, NewAccount>,
#[account(mut)]
pub signer: Signer<'info>,
pub system_program: Program<'info, System>,
}
#[account]
pub struct NewAccount {
data: u64,
}
В Anchor дискриминатор учетной записи представляет собой 8-байтовый идентификатор, уникальный для каждого типа учетной записи. Этот идентификатор получается из первых 8 байт хэша SHA256 от имени типа учетной записи. Первые 8 байт данных аккаунта специально зарезервированы для этого дискриминатора.
#[account(init, payer = signer, space = 8 + 8)]
pub new_account: Account<'info, NewAccount>,
Дискриминатор используется в ходе двух сценариев:
- Инициализация: В процессе инициализации учётной записи дискриминатор устанавливается с помощью дискриминатора типа аккаунта.
- Десериализация: Когда данные учетной записи десериализованы, дискриминатор в рамках проверяется на соответствие ожидаемого дискриминатора типа аккаунта.
Если есть несоответствие, то это указывает на то, что клиент предоставил неожиданный аккаунт. Этот механизм служит проверкой аккаунтов в программах Anchor, гарантируя использование правильных и ожидаемых учетных записей.
IDL файл #
Когда программа Anchor создается, Anchor генерирует файл языка описания интерфейса (IDL), представляющий структуру программы. Этот IDL файл предоставляет стандартизированный формат на основе JSON для создания программных инструкций и получения учетных записей программ.
Ниже приведены примеры того, как IDL файл связан с программным кодом.
Инструкции #
Массив instructions
в IDL соответствует инструкциям программы и задает
необходимые учетные записи и параметры для каждой инструкции.
{
"version": "0.1.0",
"name": "hello_anchor",
"instructions": [
{
"name": "initialize",
"accounts": [
{ "name": "newAccount", "isMut": true, "isSigner": true },
{ "name": "signer", "isMut": true, "isSigner": true },
{ "name": "systemProgram", "isMut": false, "isSigner": false }
],
"args": [{ "name": "data", "type": "u64" }]
}
],
"accounts": [
{
"name": "NewAccount",
"type": {
"kind": "struct",
"fields": [{ "name": "data", "type": "u64" }]
}
}
]
}
use anchor_lang::prelude::*;
declare_id!("11111111111111111111111111111111");
#[program]
mod hello_anchor {
use super::*;
pub fn initialize(ctx: Context<Initialize>, data: u64) -> Result<()> {
ctx.accounts.new_account.data = data;
msg!("Changed data to: {}!", data);
Ok(())
}
}
#[derive(Accounts)]
pub struct Initialize<'info> {
#[account(init, payer = signer, space = 8 + 8)]
pub new_account: Account<'info, NewAccount>,
#[account(mut)]
pub signer: Signer<'info>,
pub system_program: Program<'info, System>,
}
#[account]
pub struct NewAccount {
data: u64,
}
Аккаунты #
Массив accounts
в IDL соответствует структурам в программе, аннотированным
макросом `#[account], который определяет структуру аккаунтов данных программы.
{
"version": "0.1.0",
"name": "hello_anchor",
"instructions": [
{
"name": "initialize",
"accounts": [
{ "name": "newAccount", "isMut": true, "isSigner": true },
{ "name": "signer", "isMut": true, "isSigner": true },
{ "name": "systemProgram", "isMut": false, "isSigner": false }
],
"args": [{ "name": "data", "type": "u64" }]
}
],
"accounts": [
{
"name": "NewAccount",
"type": {
"kind": "struct",
"fields": [{ "name": "data", "type": "u64" }]
}
}
]
}
use anchor_lang::prelude::*;
declare_id!("11111111111111111111111111111111");
#[program]
mod hello_anchor {
use super::*;
pub fn initialize(ctx: Context<Initialize>, data: u64) -> Result<()> {
ctx.accounts.new_account.data = data;
msg!("Changed data to: {}!", data);
Ok(())
}
}
#[derive(Accounts)]
pub struct Initialize<'info> {
#[account(init, payer = signer, space = 8 + 8)]
pub new_account: Account<'info, NewAccount>,
#[account(mut)]
pub signer: Signer<'info>,
pub system_program: Program<'info, System>,
}
#[account]
pub struct NewAccount {
data: u64,
}
Клиент #
Anchor предоставляет библиотеку Typescript
(@coral-xyz/anchor
),
которая упрощает процесс взаимодействия с программами Solana из клиента.
Для использования клиентской библиотеки необходимо сначала настроить экземпляр
Program
с использованием IDL файла, созданного Anchor.
Клиентская программа #
Создание экземпляра Program
требует IDL программы, его on-chain адрес
(programId
), а также
AnchorProvider
.
AnchorProvider
объединяет две вещи:
Connection
- соединение с кластером Solana (т.е. localhost, devnet, mainnet)Wallet
- (опционально) кошелек по умолчанию, используемый для оплаты и подписания транзакций
При локальной сборке программы Anchor настройка создания экземпляра Program
выполняется автоматически в тестовом файле. IDL файл можно найти в папке
/target
.
import * as anchor from "@coral-xyz/anchor";
import { Program, BN } from "@coral-xyz/anchor";
import { HelloAnchor } from "../target/types/hello_anchor";
const provider = anchor.AnchorProvider.env();
anchor.setProvider(provider);
const program = anchor.workspace.HelloAnchor as Program<HelloAnchor>;
При интеграции с фронтендом с помощью
wallet adapter,
вам потребуется вручную настроитьAnchorProvider
и Program
.
import { Program, Idl, AnchorProvider, setProvider } from "@coral-xyz/anchor";
import { useAnchorWallet, useConnection } from "@solana/wallet-adapter-react";
import { IDL, HelloAnchor } from "./idl";
const { connection } = useConnection();
const wallet = useAnchorWallet();
const provider = new AnchorProvider(connection, wallet, {});
setProvider(provider);
const programId = new PublicKey("...");
const program = new Program<HelloAnchor>(IDL, programId);
Альтернативно вы можете создать экземпляр Program
, используя только IDL и
соединение Connection
с кластером Solana. Это означает, что значение по
умолчанию отсутствует для Wallet
, но позволяет использовать Program
для
получения учетных записей до подключения кошелька.
import { Program } from "@coral-xyz/anchor";
import { clusterApiUrl, Connection, PublicKey } from "@solana/web3.js";
import { IDL, HelloAnchor } from "./idl";
const programId = new PublicKey("...");
const connection = new Connection(clusterApiUrl("devnet"), "confirmed");
const program = new Program<HelloAnchor>(IDL, programId, {
connection,
});
Инструкции вызова #
Как только Program
настроен, вы можете использовать
[MethodsBuilder
](https://github.
om/coral-xyz/anchor/blob/852fcc77beb6302474a11e0f8e6f1e688021be36/ts/packages/anchor/src/program/namespace/methods
s.ts#L155) в Anchor для создания инструкции, транзакции, или создания и отправки
транзакции. Базовый формат выглядит так:
program.methods
- Это API конструктора для создания инструкций вызова связанных с IDL программы.instructionName
- Специальная инструкция от программы IDL, передающая любые данные инструкции как значения, разделенные запятыми.accounts
- Передайте адрес каждой учетной записи, требуемый инструкцией, как указано в IDL.signers
- Опционально передайте массив пар ключей, необходимых в качестве дополнительных подписывающих лиц по инструкции
await program.methods
.instructionName(instructionData1, instructionData2)
.accounts({})
.signers([])
.rpc();
Ниже приведены примеры того, как вызвать инструкцию с помощью конструктора методов.
rpc() #
Метод
rpc()
отправляет
подписанную транзакцию
с указанной инструкцией и возвращает файл TransactionSignature
. При
использовании .rpc
, Wallet
из Provider
автоматически включается в качестве
подписывающей стороны.
// Generate keypair for the new account
const newAccountKp = new Keypair();
const data = new BN(42);
const transactionSignature = await program.methods
.initialize(data)
.accounts({
newAccount: newAccountKp.publicKey,
signer: wallet.publicKey,
systemProgram: SystemProgram.programId,
})
.signers([newAccountKp])
.rpc();
transaction() #
Метод
transaction()
создает
транзакцию
и добавляет указанную инструкцию в транзакцию (без автоматической отправки).
// Generate keypair for the new account
const newAccountKp = new Keypair();
const data = new BN(42);
const transaction = await program.methods
.initialize(data)
.accounts({
newAccount: newAccountKp.publicKey,
signer: wallet.publicKey,
systemProgram: SystemProgram.programId,
})
.transaction();
const transactionSignature = await connection.sendTransaction(transaction, [
wallet.payer,
newAccountKp,
]);
instruction() #
Метод
instruction()
создает
TransactionInstruction
с использованием указанной инструкции. Это полезно, если вы хотите вручную
добавить инструкцию к транзакции и объединить ее с другими инструкциями.
// Generate keypair for the new account
const newAccountKp = new Keypair();
const data = new BN(42);
const instruction = await program.methods
.initialize(data)
.accounts({
newAccount: newAccountKp.publicKey,
signer: wallet.publicKey,
systemProgram: SystemProgram.programId,
})
.instruction();
const transaction = new Transaction().add(instruction);
const transactionSignature = await connection.sendTransaction(transaction, [
wallet.payer,
newAccountKp,
]);
Получить аккаунты #
Клиент Program
также позволяет легко получать и фильтровать программы
аккаунтов. Просто используйте program.account
и укажите имя типа аккаунта в
IDL. Затем Anchor десериализует и возвращает все учетные записи, как указано.
all() #
Используйте
all()
чтобы получить все существующие аккаунты для определенного типа.
const accounts = await program.account.newAccount.all();
memcmp #
Используйте memcmp
для фильтрации данных аккаунтов, соответствующих
определенному значению с определенным смещением. При расчете смещения, помните,
что первые 8 байт зарезервированы для дискриминатора аккаунта в учетных записях,
созданных с помощью программы Anchor. Использование memcmp
требует понимания
разметки байтов поля данных для типа учетной записи, который вы получаете.
const accounts = await program.account.newAccount.all([
{
memcmp: {
offset: 8,
bytes: "",
},
},
]);
fetch() #
Используйте
fetch()
для получения данных аккаунта для конкретной учетной записи путем передачи
адреса учетной записи
const account = await program.account.newAccount.fetch(ACCOUNT_ADDRESS);
fetchMultiple() #
Используйте
fetchMultiple()
чтобы получить данные учетной записи для нескольких аккаунтов, передав адресов
учетных записей
const accounts = await program.account.newAccount.fetchMultiple([
ACCOUNT_ADDRESS_ONE,
ACCOUNT_ADDRESS_TWO,
]);