НОВОЕ: Helius приобретает Light Protocol
Как начать разработку с Solana Web3.js 2.0 SDK
Блог/Разработка

Как начать разработку с Solana Web3.js 2.0 SDK

Инженер по удобству разработкиAnam Ansari в XAnam Ansari в LinkedIn
Обучение разработчиковMike MacCana в XMike MacCana в LinkedIn
Чтение: 10 мин

Прежде чем начать, мы хотим поблагодарить Evan и Nick за рецензирование этой статьи. Мы очень ценим их полезные отзывы и идеи.

Введение

Solana Web3.js SDK — мощная библиотека TypeScript и JavaScript для создания приложений Solana на платформах Node.js, Web и React Native. 7 ноября 2024 года Anza представила долгожданное обновление SDK 2.0 с множеством современных возможностей и улучшений JavaScript. Среди основных нововведений — стандартные типы JS для больших целых чисел и криптографии, а также уменьшенный размер бандла. Это существенное обновление для разработчиков. 

Если вы использовали @solana/web3.js, вам потребуется либо перенести свое ПО на новый пакет v2.0, либо явно указать версию, чтобы зафиксировать ее на v1.x. 

В этой статье мы рассмотрим последние обновления Web3.js 2.0 SDK, проведем вас через процесс миграции и приведем пример, который поможет начать работу. 

Предполагается, что вы хорошо понимаете основные концепции Solana: отправку транзакций, модель аккаунтов Solana, хеши блоков и приоритетные комиссии, а также имеете опыт работы с TypeScript или JavaScript. Знакомство с предыдущей версией Web3.js SDK желательно, но не обязательно. Приступим!

Что нового в Web3.js 2.0?

Кратко рассмотрим возможности нового Web3.js 2.0 SDK: 

1. Повышенная производительность

Ускоренные криптографические операции: генерация пар ключей, подписание транзакций и проверка сообщений выполняются до 10 раз быстрее благодаря нативным криптографическим API в современных средах JavaScript, таких как Node.js и актуальные браузеры.

2. Компактные и эффективные приложения

Web3.js 2.0 полностью поддерживает tree shaking, поэтому вы можете включать только используемые части библиотеки и тем самым уменьшать размер бандла. Кроме того, у нового SDK нет внешних зависимостей, что обеспечивает легкую и безопасную сборку.

3. Повышенная гибкость

Теперь разработчики могут создавать собственные решения:

  • Определять экземпляры RPC с собственными методами  
  • Использовать специализированные сетевые транспорты или средства подписи транзакций  
  • Комбинировать собственные примитивы для сетевого взаимодействия, подтверждения транзакций и кодеков  

‍Новые клиенты TypeScript для ончейн-программ теперь размещаются в организации @solana-program на GitHub. Эти клиенты автоматически генерируются с помощью Codama, что позволяет разработчикам быстро создавать клиенты для собственных программ. 

Стоит ли уже переходить на Web3.js v2?

По состоянию на февраль 2025 года:

  • Если вы создаете новое приложение Solana на JS/TS и используете существующие программы, такие как системная программа, программа токенов, программа связанных токен-аккаунтов и другие распространенные программы, уже можно использовать web3.js v2.
  • Если вы создаете собственные ончейн-приложения с помощью Anchor, возможно, стоит подождать: Anchor пока не поддерживает web3.js v2 по умолчанию. Можно дождаться будущего обновления Anchor. Либо используйте Codama для создания клиента TypeScript для своих ончейн-приложений, хотя это потребует немного больше работы.

Миграция с web3.js версии 1

‍Если вы работали с web3.js v1, вот краткий обзор важных различий:

Пары ключей

Везде, где вы использовали Keypair, теперь нужно использовать KeyPairSigner. Keypair.generate() теперь называется generateKeyPairSigner(). Кроме того, теперь keypair везде пишется как keyPair в соответствии с обычным camelCase в JS/TS.

Секретные ключи теперь называются privateKey и доступны через keyPairSigner.privateKey. Как правило, в web3.js v2 следует использовать KeyPairSigner везде, где в web3.js v1 использовался secretKey.

Адреса / Публичные ключи

Там, где в web3.js v1 использовался PublicKey, в web3.js v2 используется просто address. Например, у  KeyPairSigner есть свойство keypairSigner.address, представляющее публичный ключ. Строковый публичный ключ можно преобразовать в адрес с помощью функции address.

Суммы SOL и токенов

Для сумм используется нативный тип JS BigInt. Поэтому в конце чисел нужно добавлять n: например, один  1n вместо 1. 

Фабрики

Многие функции можно настраивать, поэтому вместо готовой реализации (например, doThing()) используется фабрика (doThingFactory()), с помощью которой можно создать собственную функцию doThing(). Например:

  • Чтобы отправлять и подтверждать транзакции, один раз запустите sendAndConfirmTransactionFactory() с нужными параметрами и получите собственную функцию sendAndConfirmTransaction(). Затем вы сможете использовать sendAndConfirmTransaction() всякий раз, когда потребуется отправить и подтвердить транзакцию. 
  • Чтобы получить аирдроп в devnet или localnet, один раз запустите airdropFactory() и получите собственную функцию airdrop(), которую можно использовать при необходимости получить аирдроп.

Как отправлять транзакции с помощью Web3.js 2.0

Мы создадим клиентскую программу на Web3.js 2.0 для перевода лампортов на другой кошелек. Она продемонстрирует методы повышения вероятности успешного выполнения транзакций и ускорения их подтверждения. 

Мы будем следовать этим рекомендациям по отправке транзакций: 

  1. Получить последний хеш блока с уровнем обязательства confirmed  
  2. Установить приоритетные комиссии согласно рекомендациям Priority Fee API от Helius
  3. Оптимизировать вычислительные единицы  
  4. Отправить транзакцию, задав maxRetries равным 0, а skipPreflight — true  

Такой подход обеспечивает оптимальную производительность и надежность даже при перегрузке сети. 

Предварительные требования

  • Установите Node.js  
  • Используйте совместимую IDE (например, VS Code или Cursor)  

Установка

Сначала создайте базовый проект Node.js, чтобы сформировать структуру приложения.

Выполните следующую команду, чтобы создать файл package.json, который управляет зависимостями и метаданными проекта:

Shell commands
npm init -y

Создайте каталог src, а в нем — файл index.ts, где будет находиться основной код:

Shell commands
mkdir src  
touch src/index.ts

Затем с помощью npm установите зависимости, необходимые для работы с Solana Web3.js 2.0 SDK:

Shell commands
npm install @solana/web3.js@2 @solana-program/system @solana-program/compute-budget esrun

Описание каждого пакета:

  • @solana/web3.js: Solana Web3.js 2.0 SDK необходим для создания транзакций Solana и управления ими
  • @solana-program/system: предоставляет доступ к системной программе Solana и позволяет выполнять такие операции, как переводы лампортов
  • @solana-program/compute-budget: используется для установки приоритетных комиссий и оптимизации вычислительных единиц транзакций
  • esrun — простой способ запускать приложения TypeScript из командной строки без конфигурации или функций-оберток.

Определение адресов для перевода

В index.ts определим адреса источника и назначения для перевода лампортов. С помощью функции address() мы создадим публичный ключ назначения из указанной строки.

Для источника мы получим KeyPair с помощью его secretKey.

send-transaction.ts
import { address, createKeyPairSignerFromBytes, getBase58Encoder } from "@solana/web3.js";

const destinationAddress = address("public-key-to-send-lamports-to");
const secretKey = "add-your-private-key";
const sourceKeypair = await createKeyPairSignerFromBytes(getBase58Encoder().encode(secretKey));

Настройка RPC-подключений

Теперь можно настроить необходимые RPC-подключения. Функция createSolanaRpc устанавливает связь с RPC-сервером через транспорт HTTP по умолчанию. Этого достаточно для большинства сценариев.

Аналогичным образом с помощью createSolanaRpcSubscriptions мы устанавливаем подключение WebSocket. Значения rpc_url и wss_url находятся в панели управления Helius: просто зарегистрируйтесь или войдите в аккаунт и перейдите в раздел «Endpoints».

Функция sendAndConfirmTransactionFactory создает переиспользуемый механизм отправки транзакций. Для отправки транзакций ему требуется RPC-подключение, а для отслеживания их статуса — подписка RPC.

send-transaction.ts
import {
  // ...
  createSolanaRpcSubscriptions,
  createSolanaRpc,
  sendAndConfirmTransactionFactory,
} from "@solana/web3.js";

const rpc_url = "https://mainnet.helius-rpc.com/?api-key=<your-key>";
const wss_url = "wss://mainnet.helius-rpc.com/?api-key=<your-key>";

const rpc = createSolanaRpc(rpc_url);
const rpcSubscriptions = createSolanaRpcSubscriptions(wss_url);

const sendAndConfirmTransaction = sendAndConfirmTransactionFactory({
  rpc,
  rpcSubscriptions,
});

Создание инструкции перевода

Включение актуального хеша блока предотвращает дублирование и задает срок действия транзакции: каждая транзакция должна содержать действительный хеш блока, иначе она не будет принята к исполнению. Для этой транзакции мы получим последний хеш блока с уровнем обязательства confirmed.

Затем с помощью getTransferSolInstruction() создадим готовую инструкцию перевода, предоставляемую системной программой. Для нее необходимо указать сумму, источник

и назначение. Источник всегда должен быть Signer, а назначение — публичным адресом.

send-transaction.ts
import {
  // ...
  lamports,
} from "@solana/web3.js";
import { getTransferSolInstruction } from "@solana-program/system";

/**
 * STEP 1: CREATE THE TRANSFER TRANSACTION
 */
const { value: latestBlockhash } = await rpc.getLatestBlockhash().send();

const instruction = getTransferSolInstruction({
  amount: lamports(1n),
  destination: destinationAddress,
  source: sourceKeypair,
});

Создание сообщения транзакции

Теперь создадим сообщение транзакции. Все сообщения транзакций теперь учитывают версию, поэтому обрабатывать разные типы (например, Transaction и VersionedTransaction) больше не нужно. 

Мы назначим источник плательщиком комиссии, включим хеш блока и добавим инструкцию для перевода лампортов.

send-transaction.ts
import {
  // ...
  pipe,
  createTransactionMessage,
  setTransactionMessageFeePayer,
  setTransactionMessageLifetimeUsingBlockhash,
  appendTransactionMessageInstruction,
} from "@solana/web3.js";

// ...

const transactionMessage = pipe(
  createTransactionMessage({ version: 0 }),
  (message) => setTransactionMessageFeePayer(sourceKeypair.address, message),
  (message) => setTransactionMessageLifetimeUsingBlockhash(latestBlockhash, message),
  (message) => appendTransactionMessageInstruction(instruction, message),
);

console.log("Transaction message created");

Функция pipe, часто используемая в функциональном программировании, создает последовательность функций, где результат одной становится входными данными для следующей. Здесь она пошагово формирует сообщение транзакции, применяя преобразования: назначает плательщика комиссии и срок действия, а также добавляет инструкции.

Инициализация сообщения транзакции:

createTransactionMessage({ version: 0 }) начинает с базового сообщения транзакции.

Назначение плательщика комиссии:

message => setTransactionMessageFeePayer(fromKeypair.address, message) добавляет адрес плательщика комиссии.

Установка срока действия с помощью хеша блока

message => setTransactionMessageLifetimeUsingBlockhash(latestBlockhash, message) использует последний хеш блока, чтобы обеспечить действительность транзакции в течение заданного периода.

Добавление инструкции перевода

message => appendTransactionMessageInstruction(instruction, message) добавляет к сообщению действие, например перевод лампортов.

Каждая стрелочная функция message => (...) изменяет сообщение и передает обновленную версию на следующий этап. В результате формируется полностью новое сообщение транзакции.

Подписание транзакции

Мы подпишем транзакцию с помощью указанного подписанта — исходного Keypair.

send-transaction.ts
import {
  // ...
  signTransactionMessageWithSigners,
} from "@solana/web3.js";
// ...

/**
 * STEP 2: SIGN THE TRANSACTION
 */
const signedTransaction = await signTransactionMessageWithSigners(transactionMessage);
console.log("Transaction signed");

Оценка приоритетных комиссий

На этом этапе можно перейти к отправке и подтверждению транзакции. Однако сначала следует оптимизировать ее, задав приоритетные комиссии и скорректировав вычислительные единицы. Эти оптимизации повышают вероятность успешного выполнения транзакции и сокращают время подтверждения, особенно при перегрузке сети.

Чтобы задать приоритетные комиссии, мы воспользуемся Priority Fee API от Helius. Для этого требуется сериализованная транзакция в формате Base64. API также поддерживает кодировку Base58, но текущая версия SDK напрямую предоставляет транзакцию в формате Base64, что упрощает процесс.

send-transaction.ts
import {
  // ...
  getBase64EncodedWireTransaction,
} from "@solana/web3.js";

/**
 * STEP 3: GET PRIORITY FEE FROM SIGNED TRANSACTION
 */

const base64EncodedWireTransaction = getBase64EncodedWireTransaction(signedTransaction);

const response = await fetch(rpc_url, {
  method: "POST",
  headers: { "Content-Type": "application/json" },
  body: JSON.stringify({
    jsonrpc: "2.0",
    id: "helius-example",
    method: "getPriorityFeeEstimate",
    params: [
      {
        transaction: base64EncodedWireTransaction,
        options: {
          transactionEncoding: "base64",
          priorityLevel: "High",
        },
      },
    ],
  }),
});
const { result } = await response.json();
const priorityFee = result.priorityFeeEstimate;
console.log("Setting priority fee to ", priorityFee);

Обычно достаточно установить priorityLevel в значение High. Однако более сложные стратегии приоритетных комиссий с использованием сериализованных транзакций и ключей аккаунтов могут значительно повысить вероятность успешного выполнения транзакций при перегрузке сети.

Оптимизация вычислительных единиц

Затем оценим фактическое количество вычислительных единиц, потребляемых сообщением транзакции.

После этого добавим запас в 10%, умножив значение на 1,1. Этот запас учитывает вычислительные единицы, используемые приоритетными комиссиями и дополнительными инструкциями вычислительных единиц, которые мы добавим позднее. 

Некоторые инструкции, например перевод лампортов, могут получить низкую оценку вычислительных единиц. Чтобы гарантировать достаточный объем ресурсов, мы добавили защитный механизм: если оценка ниже порогового значения, устанавливается минимум в 1000 вычислительных единиц.

send-transaction.ts
import {
  // ...
  getComputeUnitEstimateForTransactionMessageFactory,
} from "@solana/web3.js";

/**
 * STEP 4: OPTIMIZE COMPUTE UNITS
 */
const getComputeUnitEstimateForTransactionMessage = getComputeUnitEstimateForTransactionMessageFactory({
  rpc,
});
// Request an estimate of the actual compute units this message will consume.
let computeUnitsEstimate = await getComputeUnitEstimateForTransactionMessage(transactionMessage);
computeUnitsEstimate = computeUnitsEstimate < 1000 ? 1000 : Math.ceil(computeUnitsEstimate * 1.1);
console.log("Setting compute units to ", computeUnitsEstimate);

Повторная сборка и подписание транзакции

Теперь у нас есть значения приоритетной комиссии и вычислительных единиц, необходимые для этой транзакции. Поскольку транзакция уже подписана, мы не можем напрямую добавить новые инструкции. Вместо этого полностью пересоберем сообщение транзакции с новым хешем блока. 

Хеши блоков действительны лишь около 1–2 минут, а получение данных о приоритетных комиссиях и вычислительных единицах занимает некоторое время. Чтобы хеш блока не истек во время отправки транзакции, безопаснее получить новый при ее повторной сборке.

В пересобранную транзакцию мы включим две дополнительные инструкции: 

  1. Инструкцию для установки приоритетных комиссий;
  2. Инструкцию для установки вычислительных единиц 

Наконец, подпишем обновленную транзакцию, чтобы подготовить ее к отправке:

send-transaction.ts
import {
  // ...
  appendTransactionMessageInstructions,
} from "@solana/web3.js";
import { getSetComputeUnitLimitInstruction, getSetComputeUnitPriceInstruction } from "@solana-program/compute-budget";

/**
 * STEP 5: REBUILD AND SIGN FINAL TRANSACTION
 */
const { value: finalLatestBlockhash } = await rpc.getLatestBlockhash().send();

const finalTransactionMessage = appendTransactionMessageInstructions(
  [
    getSetComputeUnitPriceInstruction({ microLamports: priorityFee }),
    getSetComputeUnitLimitInstruction({ units: computeUnitsEstimate }),
  ],
  transactionMessage,
);

setTransactionMessageLifetimeUsingBlockhash(finalLatestBlockhash, finalTransactionMessage);

const finalSignedTransaction = await signTransactionMessageWithSigners(finalTransactionMessage);
console.log("Rebuilt the transaction and signed it");

Отправка и подтверждение транзакции

Затем подписанная транзакция отправляется и подтверждается с помощью функции sendAndConfirmTransaction.

Уровень обязательства установлен в confirmed, как и для ранее полученного хеша блока, а maxRetries имеет значение 0. Для параметра skipPreflight задано значение true, поэтому предварительные проверки пропускаются для ускорения выполнения. Однако этот режим следует использовать только в том случае, если вы уверены, что подпись транзакции проверена и других ошибок нет.

Функция sendAndConfirmTransaction была создана ранее с указанием URL для RPC и подписки RPC. URL подписки RPC используется для проверки статуса транзакции, поэтому вручную опрашивать сервер не требуется.

В разделе обработки ошибок код проверяет ошибки, возникшие во время предварительных проверок. Поскольку мы установили для skipPreflight значение true, эта проверка избыточна. Но она пригодится, если вы не установите значение true.

send-transaction.ts
import {
  getSignatureFromTransaction,
  isSolanaError,
  SOLANA_ERROR__JSON_RPC__SERVER_ERROR_SEND_TRANSACTION_PREFLIGHT_FAILURE,
} from "@solana/web3.js";
import { getSystemErrorMessage, isSystemError } from "@solana-program/system";

/**
 * STEP 6: SEND AND CONFIRM THE FINAL TRANSACTION
 */

console.log("Sending and confirming transaction");
await sendAndConfirmTransaction(finalSignedTransaction, {
  commitment: "confirmed",
  maxRetries: 0n,
  skipPreflight: true,
});
console.log("Transfer confirmed: ", getSignatureFromTransaction(finalSignedTransaction));

Запуск кода

Наконец, можно запустить код: npx esrun send-transaction.ts

Заключение

Выпуск Solana Web3.js 2.0 SDK — значительное обновление, которое позволяет разработчикам создавать более быстрые, эффективные и масштабируемые приложения на Solana. Благодаря современным стандартам JavaScript и таким возможностям, как нативные криптографические API, поддержка tree shaking и автоматически генерируемые клиенты TypeScript, SDK существенно улучшает процесс разработки и производительность приложений.

Полный код примера доступен на GitHub.

Если вы дочитали до этого места — спасибо, анон! Укажите ниже свой адрес электронной почты, чтобы не пропускать новости Solana. Хотите узнать больше? Читайте свежие статьи в блоге Helius и продолжайте знакомство с Solana уже сегодня.

Ресурсы

Подпишитесь на Helius

Следите за новостями разработки на Solana и получайте уведомления о наших публикациях

Увеличенное изображение