TypeScript

Aktualizujesz z wersji 1.x? Zobacz przewodnik migracji do wersji 2.0.

Instalacja

Zainstaluj pakiet główny lub poszczególne moduły podpisujące według potrzeb:

# 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

Wymaga @solana/kit w wersji 8.1 lub nowszej. Transakcje v1 (createTransactionMessage({ version: 1 })) są obsługiwane, z wyjątkiem Fireblocks z useProgramCall: true, które akceptuje wyłącznie transakcje legacy i v0.

Podstawowe użycie

Ujednolicona fabryka (Zalecana dla aplikacji chcących obsługiwać wiele modułów podpisujących)

Użyj createKeychainSigner z rozróżnioną konfiguracją, aby utworzyć dowolny backend:

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
);

Lub zainstaluj pojedynczy pakiet modułu podpisującego, aby zmniejszyć zależności:

import { createPrivySigner } from "@solana/keychain-privy";
const signer = await createPrivySigner({
appId: "your-app-id",
appSecret: "your-app-secret",
walletId: "your-wallet-id"
});

Użycie z komunikatami transakcji

Każdy backend poza Crossmint i trybem natywnym Fordefi zwraca SolanaTransactionSigner, który jest TransactionPartialSigner z Kit i działa z poniższymi helperami częściowych signerów z @solana/signers. Backendy z zarządzanym rozgłaszaniem opisano w sekcji Możliwości signerów.

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;
}

Podpisywanie i wysyłanie z dowolnym signerem

signAndSendTransaction z @solana/keychain-core umieszcza transakcję w łańcuchu niezależnie od typu signera. Signery wysyłające rozgłaszają ją przez swojego dostawcę; każdy inny typ używa przekazanej przez Ciebie funkcji sendTransaction (core nie ma zależności od RPC):

import { signAndSendTransaction } from "@solana/keychain-core";
const signature = await signAndSendTransaction(signer, transaction, {
sendTransaction: (tx) =>
sendAndConfirmTransaction(tx, { commitment: "confirmed" })
});

Używanie z klientem Kit (wtyczka keychainSigner)

@solana/keychain-kit-plugin instaluje sygnatariusza pęku kluczy bezpośrednio na kliencie Kit, podłączając go jako payer i identity, dzięki czemu pomijasz ręczną konfigurację pipe pokazaną powyżej:

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

Plugin akceptuje tę samą konfigurację oznaczoną backendem co createKeychainSigner, z wyjątkiem Crossmint i trybu natywnego Fordefi (ustawione chain). Te backendy przepisują transakcję po stronie serwera, więc nie mogą pełnić roli klienckiego payer ani identity; plugin odrzuca je podczas kompilacji i w czasie działania. Dołączany jest tylko backend, do którego następuje przekierowanie (pakiety backendów ładowane są przez dynamiczny import()).

Użyj keychainPayer lub keychainIdentity, aby przypisać tylko jedną rolę i łączyć backendy na jednym kliencie:

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 }));

Masz już SolanaTransactionSigner? Jest on prawidłowym TransactionPartialSigner z Kit, więc możesz zamiast tego zainstalować go za pomocą @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));

Anulowanie i niepotwierdzone rozgłoszenia

signTransactions, modifyAndSignTransactions, signAndSendTransactions i signMessages przyjmują { abortSignal } jako drugi argument. Przerwanie kończy oczekiwanie, ale nie cofa pracy, którą dostawca już zaakceptował.

W trybie natywnym automatycznym Crossmint i Fordefi niepowodzenie nie oznacza, że nic nie trafiło do sieci. Gdy dostawca mógł zaakceptować transakcję, kodem błędu jest SignerErrorCode.BROADCAST_UNCONFIRMED ("SIGNER_BROADCAST_UNCONFIRMED"). context.providerTransactionId jest ustawiane, gdy utworzenie zostało zaakceptowane, a context.idempotencyKey zawiera klucz wyprowadzony z wiadomości. Ponowne wysłanie identycznej co do bajta transakcji jest bezpieczne; odbudowana transakcja (na przykład z nowym blockhashem) to nowy transfer. providerMayHaveAccepted(error) i providerStatus(error) z @solana/keychain-core pomagają sklasyfikować błąd. Przed ponowną próbą uzgodnij stan z dostawcą.

Konfiguracja backendu

Memory

Podaj dokładnie jedno z: keyPair, privateKey, privateKeyString lub 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 obsługuje wyłącznie wysyłanie. Jego API zawsze wykonuje zatwierdzoną transakcję po stronie serwera i sponsoruje opłaty gas, więc signer udostępnia tylko signAndSendTransactions(): nie ma signTransactions() ani signMessages() i nie można go zainstalować za pomocą @solana/keychain-kit-plugin. Zwrócona sygnatura identyfikuje transakcję, którą Crossmint umieścił w łańcuchu.

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 podpisuje wyłącznie transakcje; nie ma 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 ma trzy tryby podpisywania, ustalane podczas tworzenia przez chain i pushMode. Każdy tryb udostępnia dokładnie jedną metodę transakcji, a wszystkie trzy podpisują wiadomości:

TrybKonfiguracjaTypMetoda
Czarna skrzynkachain nieustawioneFordefiBlackBoxSignersignTransactions()
Natywny automatycznychain ustawione, pushMode pominięte lub "auto"FordefiNativeSignersignAndSendTransactions()
Natywny ręcznychain ustawione, pushMode: "manual"FordefiNativeManualSignermodifyAndSignTransactions()

Żądania API są uwierzytelniane sygnaturą żądania ECDSA P-256: podaj klucz podpisujący jako privateKeyPem albo przechowuj go w KMS/HSM, przekazując requestSigner.

Tryb natywny automatyczny (zalecany do użycia on-chain): ustaw chain, a Fordefi samo podpisze i rozgłosi transakcję. Ponieważ Fordefi może zastąpić blockhash lub opłaty przed podpisaniem, signer jest TransactionSendingSigner z Kit: używaj go z signAndSendTransactionMessageWithSigners, a nie z przepływem częściowych signerów pokazanym powyżej. Tryb natywny automatyczny akceptuje wyłącznie niepodpisaną transakcję, w której jedynym wymaganym signerem jest sejf, i nie można go używać z @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);

Tryb natywny ręczny: ustaw chain i pushMode: "manual". Fordefi przepisuje wiadomość (recent blockhash, instrukcje opłat Compute Budget) i podpisuje ją, ale nie rozgłasza. modifyAndSignTransactions() zwraca transakcję podpisaną przez Fordefi, która zastępuje tę, którą przesłano: zawsze kontynuuj od zwróconej transakcji. Fordefi musi być płatnikiem opłaty i musi podpisać przed każdym innym wymaganym signerem. Przepisanie nie jest porównywane (diff), więc sprawdź wynik przed rozgłoszeniem.

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
]);

Tryb czarnej skrzynki: pomiń chain. Fordefi podpisuje surowe bajty i nie rozgłasza transakcji; samodzielnie składasz i przesyłasz transakcję. Używaj tego trybu z sejfem czarnej skrzynki 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")
});

Możliwości signerów

@solana/keychain-core definiuje jeden interfejs dla każdego typu signera Kit. Każdy dodaje isAvailable(): Promise<boolean> do odpowiadającego interfejsu z @solana/signers:

  • SolanaTransactionSigner (TransactionPartialSigner z Kit): zwraca sygnatury dla transakcji, którą posiadasz.
  • SolanaModifyingSigner (TransactionModifyingSigner z Kit): przepisuje transakcję, podpisuje ją i zwraca nową transakcję bez rozgłaszania.
  • SolanaSendingSigner (TransactionSendingSigner z Kit): podpisuje i rozgłasza przez swojego dostawcę.
  • SolanaMessageSigner (MessagePartialSigner z Kit): podpisuje wiadomości off-chain. Jest to oddzielne od typów transakcyjnych.

SolanaSigner to unia trzech typów transakcyjnych. Typ zwracany przez każdą fabrykę wskazuje dokładny typ jej backendu. Gdy konfiguracja jest znana dopiero w czasie działania, zawęź typ za pomocą isSolanaTransactionSigner, isSolanaModifyingSigner, isSolanaSendingSigner lub isSolanaMessageSigner. signerCapabilities(signer) raportuje wszystkie cztery naraz.

BackendTyp
memory, vault, aws-kms, gcp-kms, privy, turnkey, fireblocks, cdp, dfns, para, openfort, fordefi (czarna skrzynka)SolanaTransactionSigner & SolanaMessageSigner
utilaSolanaTransactionSigner (bez podpisywania wiadomości)
fordefi (natywny ręczny)SolanaModifyingSigner & SolanaMessageSigner
fordefi (natywny automatyczny)SolanaSendingSigner & SolanaMessageSigner
crossmintSolanaSendingSigner (bez podpisywania wiadomości)
import {
isSolanaSendingSigner,
isSolanaTransactionSigner
} from "@solana/keychain-core";
if (isSolanaSendingSigner(signer)) {
await signer.signAndSendTransactions([transaction]);
} else if (isSolanaTransactionSigner(signer)) {
await signer.signTransactions([transaction]);
}

Tworzenie niestandardowych signerów

Zaimplementuj interfejsy możliwości obsługiwane przez Twój backend:

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);
}
}

Implementuj tylko to, co obsługuje Twój backend. Kit klasyfikuje signery według tego, jakie metody istnieją, więc nigdy nie dodawaj metody, która tylko zgłasza wyjątek.

Zobacz przewodnik Dodawanie signerów, aby zintegrować dodatkowe usługi zarządzania kluczami.

Is this page helpful?