TypeScript

Обновляетесь с 1.x? См. руководство по миграции на 2.0.

Установка

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

# Umbrella package (includes all signers)
pnpm add @solana/keychain
# Or install individual packages
pnpm add @solana/keychain-core # Core interfaces (required for custom signers)
pnpm add @solana/keychain-memory # Local keypair signer
pnpm add @solana/keychain-vault # HashiCorp Vault
pnpm add @solana/keychain-aws-kms # AWS KMS
pnpm add @solana/keychain-gcp-kms # GCP KMS
pnpm add @solana/keychain-privy # Privy
pnpm add @solana/keychain-turnkey # Turnkey
pnpm add @solana/keychain-fireblocks # Fireblocks
pnpm add @solana/keychain-cdp # Coinbase Developer Platform
pnpm add @solana/keychain-crossmint # Crossmint
pnpm add @solana/keychain-dfns # Dfns
pnpm add @solana/keychain-openfort # Openfort
pnpm add @solana/keychain-para # Para
pnpm add @solana/keychain-utila # Utila
pnpm 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.1
const signer = await createKeychainSigner({
backend: "privy",
appId: "your-app-id",
appSecret: "your-app-secret",
walletId: "your-wallet-id"
});
// Sign an already-compiled transaction
const 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 builder
const transaction = pipe(
createTransactionMessage({ version: 0 }),
(tx) => setTransactionMessageFeePayerSigner(signer, tx)
// ... set a lifetime and add instructions
);
// Sign with the standard signers API
const 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 TransactionPartialSigner
client.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 не заданFordefiBlackBoxSignersignTransactions()
Нативный автоchain задан, pushMode опущен или "auto"FordefiNativeSignersignAndSendTransactions()
Нативный ручнойchain задан, pushMode: "manual"FordefiNativeManualSignermodifyAndSignTransactions()

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 (Kit TransactionPartialSigner): возвращает подписи для принадлежащей вам транзакции.
  • SolanaModifyingSigner (Kit TransactionModifyingSigner): переписывает транзакцию, подписывает её и возвращает новую транзакцию без отправки.
  • SolanaSendingSigner (Kit TransactionSendingSigner): подписывает и отправляет через своего провайдера.
  • SolanaMessageSigner (Kit MessagePartialSigner): подписывает офчейн-сообщения. Это отдельный тип, не относящийся к типам для транзакций.

SolanaSigner — это объединение трёх типов для транзакций. Возвращаемый тип каждой фабрики указывает точный тип её бэкенда. Если конфигурация известна только во время выполнения, сужайте тип с помощью isSolanaTransactionSigner, isSolanaModifyingSigner, isSolanaSendingSigner или isSolanaMessageSigner. signerCapabilities(signer) возвращает сведения сразу обо всех четырёх.

БэкендТип
memory, vault, aws-kms, gcp-kms, privy, turnkey, fireblocks, cdp, dfns, para, openfort, fordefi (чёрный ящик)SolanaTransactionSigner & SolanaMessageSigner
utilaSolanaTransactionSigner (без подписи сообщений)
fordefi (нативный ручной)SolanaModifyingSigner & SolanaMessageSigner
fordefi (нативный авто)SolanaSendingSigner & SolanaMessageSigner
crossmintSolanaSendingSigner (без подписи сообщений)
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?