Обновляетесь с 1.x? См. руководство по миграции на 2.0.
Установка
Установите общий пакет или отдельные подписанты по мере необходимости:
# Umbrella package (includes all signers)pnpm add @solana/keychain# Or install individual packagespnpm add @solana/keychain-core # Core interfaces (required for custom signers)pnpm add @solana/keychain-memory # Local keypair signerpnpm add @solana/keychain-vault # HashiCorp Vaultpnpm add @solana/keychain-aws-kms # AWS KMSpnpm add @solana/keychain-gcp-kms # GCP KMSpnpm add @solana/keychain-privy # Privypnpm add @solana/keychain-turnkey # Turnkeypnpm add @solana/keychain-fireblocks # Fireblockspnpm add @solana/keychain-cdp # Coinbase Developer Platformpnpm add @solana/keychain-crossmint # Crossmintpnpm add @solana/keychain-dfns # Dfnspnpm add @solana/keychain-openfort # Openfortpnpm add @solana/keychain-para # Parapnpm add @solana/keychain-utila # Utilapnpm add @solana/keychain-fordefi # Fordefi# Kit client plugins (keychainSigner / keychainPayer / keychainIdentity)pnpm add @solana/keychain-kit-plugin
Требуется @solana/kit 8.1 или новее. Транзакции v1
(createTransactionMessage({ version: 1 })) поддерживаются, кроме Fireblocks
с useProgramCall: true, который принимает только legacy и v0.
Базовое использование
Унифицированная фабрика (рекомендуется для приложений, поддерживающих несколько подписантов)
Используйте createKeychainSigner с дискриминированной конфигурацией для
создания любого бэкенда:
import { createKeychainSigner } from "@solana/keychain";import { signTransactionWithSigners } from "@solana/signers"; // requires @solana/signers >= 8.1const signer = await createKeychainSigner({backend: "privy",appId: "your-app-id",appSecret: "your-app-secret",walletId: "your-wallet-id"});// Sign an already-compiled transactionconst signedTx = await signTransactionWithSigners([signer],compiledTransaction);
Или установите отдельный пакет подписанта для уменьшения зависимостей:
import { createPrivySigner } from "@solana/keychain-privy";const signer = await createPrivySigner({appId: "your-app-id",appSecret: "your-app-secret",walletId: "your-wallet-id"});
Использование с сообщениями транзакций
Каждый бэкенд, кроме Crossmint и нативного режима Fordefi, возвращает
SolanaTransactionSigner — это Kit TransactionPartialSigner, который работает
с приведёнными ниже вспомогательными функциями partial-signer из @solana/signers. Бэкенды с управляемой отправкой описаны в разделе Возможности подписантов.
import type { SolanaTransactionSigner } from "@solana/keychain-core";import { signTransactionMessageWithSigners } from "@solana/signers";import {createTransactionMessage,setTransactionMessageFeePayerSigner,pipe} from "@solana/kit";async function signWithKeychain(signer: SolanaTransactionSigner) {// Check availability (useful for remote signers)if (!(await signer.isAvailable())) {throw new Error("Signer offline");}// Use with @solana/kit transaction builderconst transaction = pipe(createTransactionMessage({ version: 0 }),(tx) => setTransactionMessageFeePayerSigner(signer, tx)// ... set a lifetime and add instructions);// Sign with the standard signers APIconst signedTx = await signTransactionMessageWithSigners(transaction);return signedTx;}
Подпись и отправка с любым подписантом
signAndSendTransaction из @solana/keychain-core отправляет транзакцию в
сеть независимо от типа подписанта. Подписанты-отправители выполняют отправку через
своего провайдера; все остальные типы используют переданную вами функцию sendTransaction (у core нет
зависимости от RPC):
import { signAndSendTransaction } from "@solana/keychain-core";const signature = await signAndSendTransaction(signer, transaction, {sendTransaction: (tx) =>sendAndConfirmTransaction(tx, { commitment: "confirmed" })});
Использование с Kit Client (плагин keychainSigner)
@solana/keychain-kit-plugin устанавливает подписант keychain непосредственно
на Kit client, подключая его в качестве
payer и identity, что позволяет пропустить ручную настройку pipe,
описанную выше:
import { createClient } from "@solana/kit";import { keychainSigner } from "@solana/keychain-kit-plugin";const client = await createClient().use(keychainSigner({backend: "privy",appId: process.env.PRIVY_APP_ID!,appSecret: process.env.PRIVY_APP_SECRET!,walletId: process.env.PRIVY_WALLET_ID!}));client.payer; // SolanaTransactionSigner, also a Kit TransactionPartialSignerclient.identity; // same signer instance
Плагин принимает ту же конфигурацию с тегом бэкенда, что и createKeychainSigner,
за исключением Crossmint и нативного режима Fordefi (когда задан chain). Эти бэкенды переписывают
транзакцию на стороне сервера, поэтому не могут выступать в роли клиентского payer или
identity; плагин отклоняет их как на этапе компиляции, так и во время выполнения. В сборку включается только тот
бэкенд, к которому выполняется диспетчеризация (пакеты бэкендов загружаются через динамический
import()).
Используйте keychainPayer или keychainIdentity, чтобы назначить только одну
роль, и комбинируйте бэкенды в рамках одного клиента:
import { createClient } from "@solana/kit";import { keychainIdentity, keychainPayer } from "@solana/keychain-kit-plugin";const client = await createClient().use(keychainPayer({backend: "memory",privateKeyPath: `${process.env.HOME}/.config/solana/id.json`})).use(keychainIdentity({ backend: "turnkey", ...turnkeyConfig }));
Уже есть SolanaTransactionSigner? Это валидный Kit
TransactionPartialSigner, поэтому вместо этого его можно установить с помощью
@solana/kit-plugin-signer
(pnpm add @solana/kit-plugin-signer):
import { createClient } from "@solana/kit";import { signer } from "@solana/kit-plugin-signer";import { createKeychainSigner } from "@solana/keychain";const mySigner = await createKeychainSigner({backend: "vault",...vaultConfig});const client = createClient().use(signer(mySigner));
Отмена и неподтверждённые отправки
signTransactions, modifyAndSignTransactions, signAndSendTransactions и
signMessages принимают { abortSignal } вторым аргументом. Отмена
прекращает ожидание, но не откатывает работу, уже принятую провайдером.
В автоматическом нативном режиме Crossmint и Fordefi сбой не означает, что ничего не
было отправлено. Если провайдер мог принять транзакцию, код ошибки —
SignerErrorCode.BROADCAST_UNCONFIRMED ("SIGNER_BROADCAST_UNCONFIRMED").
context.providerTransactionId задаётся, если создание было принято, а
context.idempotencyKey содержит ключ, полученный из сообщения. Повторная отправка
байт-в-байт идентичной транзакции безопасна; пересобранная транзакция (например, с новым
блокхэшем) — это новый перевод. Классифицировать ошибку помогают
providerMayHaveAccepted(error) и providerStatus(error) из @solana/keychain-core.
Перед повторной попыткой сверьтесь с провайдером.
Конфигурация бэкенда
Memory
Укажите ровно один из параметров: keyPair, privateKey, privateKeyString или
privateKeyPath:
import { createMemorySigner } from "@solana/keychain-memory";const signer = await createMemorySigner({privateKeyPath: "/path/to/id.json"});
HashiCorp Vault
import { createVaultSigner } from "@solana/keychain-vault";const signer = createVaultSigner({vaultAddr: "https://vault.example.com:8200",vaultToken: "hvs.xxxxx",keyName: "my-solana-key",publicKey: "base58_public_key"});
AWS KMS
import { createAwsKmsSigner } from "@solana/keychain-aws-kms";const signer = createAwsKmsSigner({keyId: "alias/my-solana-key",publicKey: "base58_public_key",region: "us-east-1" // optional});
GCP KMS
import { createGcpKmsSigner } from "@solana/keychain-gcp-kms";const signer = createGcpKmsSigner({keyName:"projects/my-project/locations/us-east1/keyRings/my-ring/cryptoKeys/my-key/cryptoKeyVersions/1",publicKey: "base58_public_key"});
Privy
import { createPrivySigner } from "@solana/keychain-privy";const signer = await createPrivySigner({appId: "app_id",appSecret: "app_secret",walletId: "wallet_id"});
Turnkey
import { createTurnkeySigner } from "@solana/keychain-turnkey";const signer = createTurnkeySigner({apiPublicKey: "api_public_key",apiPrivateKey: "api_private_key",organizationId: "org_id",privateKeyId: "private_key_id",publicKey: "base58_public_key"});
Fireblocks
import { createFireblocksSigner } from "@solana/keychain-fireblocks";const signer = await createFireblocksSigner({apiKey: "api_key",privateKeyPem: "-----BEGIN RSA PRIVATE KEY-----\n...",vaultAccountId: "0",assetId: "SOL" // or "SOL_TEST" for devnet});
CDP (Coinbase Developer Platform)
import { createCdpSigner } from "@solana/keychain-cdp";const signer = await createCdpSigner({cdpApiKeyId: "api_key_id",cdpApiKeySecret: "api_key_secret",cdpWalletSecret: "wallet_secret",address: "base58_address",network: "solana" // or "solana-devnet"; required for address lookup tables});
Crossmint
import { createCrossmintSigner } from "@solana/keychain-crossmint";const signer = await createCrossmintSigner({apiKey: "api_key",walletLocator: "wallet_locator"});
Crossmint работает только на отправку. Его API всегда выполняет одобренную транзакцию
на стороне сервера и спонсирует газ, поэтому подписант предоставляет только
signAndSendTransactions(): у него нет signTransactions() и signMessages(),
и его нельзя установить с помощью @solana/keychain-kit-plugin. Возвращаемая
подпись идентифицирует транзакцию, которую провёл Crossmint.
const [signature] = await signer.signAndSendTransactions([transaction]);// or: await signAndSendTransactionMessageWithSigners(transactionMessage);
Dfns
import { createDfnsSigner } from "@solana/keychain-dfns";const signer = await createDfnsSigner({authToken: "auth_token",credId: "cred_id",privateKeyPem: "-----BEGIN EC PRIVATE KEY-----\n...",walletId: "wallet_id"});
Openfort
import { createOpenfortSigner } from "@solana/keychain-openfort";const signer = await createOpenfortSigner({secretKey: "sk_...",walletSecret: "wallet_secret",accountId: "account_id"});
Para
import { createParaSigner } from "@solana/keychain-para";const signer = await createParaSigner({apiKey: "api_key",walletId: "wallet_id"});
Utila
Utila подписывает только транзакции; у него нет signMessages().
import { createUtilaSigner } from "@solana/keychain-utila";const signer = await createUtilaSigner({serviceAccountEmail: "sa@example.com",serviceAccountPrivateKeyPem: "-----BEGIN PRIVATE KEY-----\n...",vaultId: "vault_id",walletId: "wallet_id",network: "networks/solana-devnet"});
Fordefi
У Fordefi три режима подписи, которые фиксируются при создании с помощью chain и
pushMode. Каждый режим предоставляет ровно один метод для транзакций, и все три подписывают
сообщения:
| Режим | Конфигурация | Тип | Метод |
|---|---|---|---|
| Чёрный ящик | chain не задан | FordefiBlackBoxSigner | signTransactions() |
| Нативный авто | chain задан, pushMode опущен или "auto" | FordefiNativeSigner | signAndSendTransactions() |
| Нативный ручной | chain задан, pushMode: "manual" | FordefiNativeManualSigner | modifyAndSignTransactions() |
API-запросы аутентифицируются подписью запроса ECDSA P-256: передайте
ключ подписи как privateKeyPem или храните его в KMS/HSM, передав
requestSigner.
Автоматический нативный режим (рекомендуется для работы в сети): задайте chain, и Fordefi
сам подписывает и отправляет транзакцию. Поскольку Fordefi может заменить
блокхэш или комиссии перед подписью, подписант является Kit
TransactionSendingSigner: используйте его с
signAndSendTransactionMessageWithSigners, а не по схеме partial-signer, показанной
выше. Автоматический нативный режим принимает только неподписанную транзакцию, единственным обязательным
подписантом которой является хранилище, и его нельзя использовать с @solana/keychain-kit-plugin.
import { createFordefiSigner } from "@solana/keychain-fordefi";import { signAndSendTransactionMessageWithSigners } from "@solana/signers";import { readFileSync } from "node:fs";const signer = await createFordefiSigner({accessToken: process.env.FORDEFI_ACCESS_TOKEN!,vaultId: process.env.FORDEFI_VAULT_ID!,publicKey: process.env.FORDEFI_PUBLIC_KEY!, // Solana vault address (base58)privateKeyPem: readFileSync("./secret/private.pem", "utf8"),chain: "solana_devnet", // or "solana_mainnet"fee: { type: "custom", priority_fee: "1000" } // optional});const signature =await signAndSendTransactionMessageWithSigners(transactionMessage);
Ручной нативный режим: задайте chain и pushMode: "manual". Fordefi переписывает
сообщение (актуальный блокхэш, инструкции комиссии Compute Budget) и подписывает его,
но не отправляет. modifyAndSignTransactions() возвращает транзакцию,
подписанную Fordefi, которая заменяет отправленную вами: всегда продолжайте работу с
возвращённой транзакцией. Fordefi должен быть плательщиком комиссии и должен подписывать раньше любого
другого обязательного подписанта. Изменения не показываются в виде диффа, поэтому проверьте результат перед
отправкой.
import { createFordefiSigner } from "@solana/keychain-fordefi";import { readFileSync } from "node:fs";const signer = await createFordefiSigner({accessToken: process.env.FORDEFI_ACCESS_TOKEN!,vaultId: process.env.FORDEFI_VAULT_ID!,publicKey: process.env.FORDEFI_PUBLIC_KEY!,privateKeyPem: readFileSync("./secret/private.pem", "utf8"),chain: "solana_devnet",pushMode: "manual"});const [signedTransaction] = await signer.modifyAndSignTransactions([transaction]);
Режим чёрного ящика: не указывайте chain. Fordefi подписывает сырые байты и не выполняет отправку транзакции — сборку и отправку вы осуществляете самостоятельно. Используйте этот режим с хранилищем Fordefi типа «чёрный ящик».
import { createFordefiSigner } from "@solana/keychain-fordefi";import { readFileSync } from "node:fs";const signer = await createFordefiSigner({accessToken: process.env.FORDEFI_ACCESS_TOKEN!,vaultId: process.env.FORDEFI_BB_VAULT_ID!,publicKey: process.env.FORDEFI_BB_PUBLIC_KEY!,privateKeyPem: readFileSync("./secret/private.pem", "utf8")});
Возможности подписантов
@solana/keychain-core определяет по одному интерфейсу для каждого типа подписанта Kit. Каждый добавляет
isAvailable(): Promise<boolean> к соответствующему интерфейсу @solana/signers:
SolanaTransactionSigner(KitTransactionPartialSigner): возвращает подписи для принадлежащей вам транзакции.SolanaModifyingSigner(KitTransactionModifyingSigner): переписывает транзакцию, подписывает её и возвращает новую транзакцию без отправки.SolanaSendingSigner(KitTransactionSendingSigner): подписывает и отправляет через своего провайдера.SolanaMessageSigner(KitMessagePartialSigner): подписывает офчейн-сообщения. Это отдельный тип, не относящийся к типам для транзакций.
SolanaSigner — это объединение трёх типов для транзакций. Возвращаемый тип каждой фабрики
указывает точный тип её бэкенда. Если конфигурация известна только во время
выполнения, сужайте тип с помощью isSolanaTransactionSigner, isSolanaModifyingSigner,
isSolanaSendingSigner или isSolanaMessageSigner. signerCapabilities(signer)
возвращает сведения сразу обо всех четырёх.
| Бэкенд | Тип |
|---|---|
| memory, vault, aws-kms, gcp-kms, privy, turnkey, fireblocks, cdp, dfns, para, openfort, fordefi (чёрный ящик) | SolanaTransactionSigner & SolanaMessageSigner |
| utila | SolanaTransactionSigner (без подписи сообщений) |
| fordefi (нативный ручной) | SolanaModifyingSigner & SolanaMessageSigner |
| fordefi (нативный авто) | SolanaSendingSigner & SolanaMessageSigner |
| crossmint | SolanaSendingSigner (без подписи сообщений) |
import {isSolanaSendingSigner,isSolanaTransactionSigner} from "@solana/keychain-core";if (isSolanaSendingSigner(signer)) {await signer.signAndSendTransactions([transaction]);} else if (isSolanaTransactionSigner(signer)) {await signer.signTransactions([transaction]);}
Создание пользовательских подписантов
Реализуйте интерфейсы возможностей, которые поддерживает ваш бэкенд:
import type {SolanaMessageSigner,SolanaTransactionSigner} from "@solana/keychain-core";import type { Address } from "@solana/addresses";import type {MessagePartialSignerConfig,SignableMessage,TransactionPartialSignerConfig} from "@solana/signers";import type { Transaction } from "@solana/transactions";class MyCustomSigner implements SolanaTransactionSigner, SolanaMessageSigner {constructor(readonly address: Address) {}async isAvailable(): Promise<boolean> {return await myBackend.healthCheck();}async signMessages(messages: readonly SignableMessage[],config?: MessagePartialSignerConfig) {return await myBackend.signMessages(messages, config?.abortSignal);}async signTransactions(transactions: readonly Transaction[],config?: TransactionPartialSignerConfig) {return await myBackend.signTransactions(transactions, config?.abortSignal);}}
Реализуйте только то, что поддерживает ваш бэкенд. Kit классифицирует подписантов по тому, какие методы существуют, поэтому никогда не добавляйте метод, который просто выбрасывает исключение.
Ознакомьтесь с руководством по добавлению подписантов, чтобы интегрировать дополнительные сервисы управления ключами.
Is this page helpful?