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 }))을 지원하지만, useProgramCall: true를 사용하는 Fireblocks는 예외이며 레거시와 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이며 아래의 @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;
}

모든 서명자로 서명 및 전송하기

@solana/keychain-core의 signAndSendTransaction은 서명자의 형태와 관계없이 트랜잭션을 온체인에 올립니다. 전송형 서명자는 해당 프로바이더를 통해 브로드캐스트하며, 그 외의 모든 형태는 사용자가 전달한 sendTransaction을 사용합니다(core는 RPC 의존성이 없습니다):

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

Kit 클라이언트와 함께 사용하기 (keychainSigner 플러그인)

@solana/keychain-kit-plugin는 키체인 서명자를 Kit 클라이언트에 직접 설치하여, 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에는 메시지에서 파생된 키가 담깁니다. 바이트가 동일한 트랜잭션을 다시 보내는 것은 안전하지만, 재구성한 트랜잭션(예: 새 블록해시 사용)은 새로운 전송으로 처리됩니다. @solana/keychain-core의 providerMayHaveAccepted(error)와 providerStatus(error)로 오류를 분류할 수 있습니다. 재시도하기 전에 프로바이더와 상태를 대조하세요.

백엔드 구성

메모리

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는 전송 전용입니다. 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으로 제공하거나, requestSigner를 전달하여 KMS/HSM에 키를 보관하세요.

네이티브 자동 모드(온체인 사용 권장): chain을 설정하면 Fordefi가 트랜잭션에 직접 서명하고 브로드캐스트합니다. Fordefi는 서명 전에 블록해시나 수수료를 교체할 수 있으므로, 서명자는 Kit의 TransactionSendingSigner입니다. 위에 나온 부분 서명자 방식이 아니라 signAndSendTransactionMessageWithSigners와 함께 사용하세요. 네이티브 자동 모드는 필수 서명자가 볼트뿐인 서명되지 않은 트랜잭션만 허용하며, @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는 수수료 납부자여야 하며 다른 모든 필수 서명자보다 먼저 서명해야 합니다. 재작성된 내용은 diff로 비교되지 않으므로 브로드캐스트하기 전에 결과를 확인하세요.

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 서명자 형태별로 하나의 인터페이스를 정의합니다. 각 인터페이스는 대응하는 @solana/signers 인터페이스에 isAvailable(): Promise<boolean>을 추가합니다:

  • 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?