Bir mesajı imzalamak, kullanıcının bir cüzdanı kontrol ettiğini kanıtlar. İşlem, ücret veya ağ çağrısı gerekmez. Uygulamalar imzalı mesajları kullanıcıların oturum açması, bir kullanıcının şartları kabul ettiğinin kaydedilmesi, bir cüzdanın mevcut bir hesaba bağlanması veya sipariş vermek gibi zincir dışında gerçekleşen bir işlemin yetkilendirilmesi için kullanır.
Solana cüzdanları mesajları iki şekilde imzalar:
- Zincir dışı mesajlar (
solana:signOffchainMessage), metninizi zincir dışı mesaj imzalama (OCMS) spesifikasyonunda tanımlanan standart bir biçimle sarmalar. Bu biçim hiçbir zaman bir işlemle karıştırılamaz, mesajı imzalaması gereken hesapları belirtir ve donanım cüzdanlarının imzaladığı biçimdir. - Ham mesajlar (
solana:signMessage), uygulamanızın ilettiği baytları önek olmadan ve imzalayanlar belirtilmeden olduğu gibi imzalar. Çoğu yazılım cüzdanı bunları destekler, ancak baytlar içinde bunun bir işlem değil mesaj olduğunu belirten hiçbir şey yoktur.
Cüzdan destekliyorsa zincir dışı mesajları kullanın, desteklemiyorsa ham mesajlara geri dönün. Bu kılavuzun geri kalanında örnek olarak oturum açma kullanılır, ancak aynı imzalama ve doğrulama adımları her türlü mesaj için geçerlidir.
Bir mesaj biçimi seçin
| Zincir dışı mesaj | Ham mesaj | İşlem | |
|---|---|---|---|
| Cüzdan özelliği | signOffchainMessage | signMessage | signTransaction |
| Donanım cüzdanı desteği | Cihaza bağlı | Hayır | Evet |
| Kullanıcı tarafından okunabilir | Evet | Yalnızca yazılım cüzdanlarında | Cihaza bağlı |
| Gerekli imzalayanları bildirir | Evet | Hayır | Evet |
| RPC çağrıları ve ücretler | Yok | Yok | Blockhash, gönderme, ücret, onay |
Neden işlem değil?
Donanım cüzdanlarının eskiden okunabilir bir mesajı imzalama yolu yoktu. Birçok uygulama bu kullanıcılardan bunun yerine hiçbir şey yapmayan bir işlem imzalamalarını istiyordu. Bu işe yarar, ancak gecikme, maliyet ve risk ekler:
- Ağ ücretleri. Bir işlem, hiçbir şey yapmasa bile ücret öder.
- Birden fazla RPC çağrısı. Uygulamanız bir blockhash alır, simülasyon yapar, gönderir ve ardından onay için sorgulama yapar.
- Ek gecikme. Bu çağrıların her biri, kullanıcı oturum açmadan önce bir gidiş-dönüş demektir.
- Okunabilir hiçbir şey yok. Kullanıcı, neyi kabul ettiğini belirten bir ifadeyi değil, işlem baytlarını onaylar.
- Davranışsal risk. Kullanıcılardan yalnızca oturum açmak için bir işlem imzalamalarını istemek, onları zararlı bir işlemi imzalamaya kandırmayı kolaylaştırır.
Zincir dışı mesaj bunların hepsini atlar. Tek gidiş-dönüş, kendi sunucunuzadır.
Cüzdan neyi imzalar?
Zincir dışı mesaj, sabit bir imzalama alanıyla başlar: \xffsolana offchain.
Geçerli hiçbir işlem bu baytlarla başlamaz, dolayısıyla imzalanmış bir mesaj
hiçbir zaman işlem olarak kullanılamaz. Biçimin 1. sürümü ardından gerekli
imzalayanları listeler ve mesajı uzunluk sınırı olmadan UTF-8 metni olarak
taşır.
Cüzdan bu baytları oluşturur, imzalar ve imzayla birlikte geri döndürür. Spesifikasyon, eski 0. sürüm dahil olmak üzere biçimin tamamını kapsar.
Bir oturum açma yöntemi seçin
Henüz her cüzdan zincir dışı mesajları desteklemiyor. İmza istemeden önce bağlı hesabın özelliklerini kontrol edin ve özellik yoksa yedek yönteme geçin:
- Hesap, 1. mesaj sürümüyle
solana:signOffchainMessageözelliğini destekliyorsa zincir dışı bir mesaj imzalayın. - Aksi halde aynı meydan okumayı (challenge)
solana:signMessageile imzalayın. Çoğu yazılım cüzdanı bunu destekler. - Mesaj desteği olmayan donanım cüzdanı kullanıcılarına, bunun yerine bir oturum açma işlemi imzalayan açık bir isteğe bağlı seçenek sunun; örneğin "Donanım cüzdanı kullanıyorum" anahtarı. Onları varsayılan olarak bu yola yönlendirmeyin.
Tarayıcıda mesaj imzalama
Wallet Standard paketlerini Kit ile birlikte yükleyin:
$npm install @solana/kit @solana/wallet-standard-features @wallet-standard/ui @wallet-standard/ui-features @wallet-standard/ui-registry
Bu yardımcılar, React kılavuzundaki useConnectedWallet tarafından
döndürülen account gibi bir UiWalletAccount alır:
import {SolanaSignOffchainMessage,type SolanaSignOffchainMessageFeature} from "@solana/wallet-standard-features";import type { UiWalletAccount } from "@wallet-standard/ui";import { getWalletAccountFeature } from "@wallet-standard/ui-features";import { getWalletAccountForUiWalletAccount } from "@wallet-standard/ui-registry";type OffchainMessageFeature =SolanaSignOffchainMessageFeature[typeof SolanaSignOffchainMessage];export function supportsOffchainMessages(account: UiWalletAccount) {if (!account.features.includes(SolanaSignOffchainMessage)) return false;const feature = getWalletAccountFeature(account,SolanaSignOffchainMessage) as OffchainMessageFeature;return feature.supportedMessageVersions.includes(1);}export async function signOffchainMessage(account: UiWalletAccount,message: string) {const feature = getWalletAccountFeature(account,SolanaSignOffchainMessage) as OffchainMessageFeature;const [output] = await feature.signOffchainMessage({messageVersion: 1,account: getWalletAccountForUiWalletAccount(account),message,requiredSigners: [account.publicKey]});return output;}
signOffchainMessage, cüzdanın imzaladığı tüm baytlar olan
signedOffchainMessage ile signature değerini döndürür. İkisini de Base64 ile
kodlayın ve cüzdan adresiyle birlikte sunucunuza gönderin.
Meydan okuma metnini küçük ekranda rahat okunacak şekilde yazın. Uygulamanızın ve alan adınızın adını belirtin, imzalamanın ne yaptığını söyleyin ve sunucunuz tarafından oluşturulan tek kullanımlık bir nonce ile bir düzenlenme zamanı ekleyin:
swap.app wants you to sign in with your Solana account.Signing in costs nothing and does not submit a transaction.Nonce: 8f3a1c9eIssued at: 2026-09-29T17:04:00Z
İmzayı sunucunuzda doğrulayın
Doğrulama yerel bir imza kontrolüdür ve RPC bağlantısı gerektirmez. Cüzdanın imzaladığı baytların kodunu çözün, verdiğiniz meydan okumayla eşleştiğini teyit edin, ardından imzayı kontrol edin:
import {address,assertOffchainMessageV1Equal,getBase64Encoder,getOffchainMessageDecoder,verifyOffchainMessageEnvelope,type OffchainMessageBytes,type SignatureBytes} from "@solana/kit";export async function verifySignIn(input: {walletAddress: string;expectedMessage: string;signedOffchainMessage: string;signature: string;}) {const signer = address(input.walletAddress);const base64 = getBase64Encoder();const content = base64.encode(input.signedOffchainMessage) as OffchainMessageBytes;const signature = base64.encode(input.signature) as SignatureBytes;const received = getOffchainMessageDecoder().decode(content);if (received.version !== 1) {throw new Error(`Expected a v1 message, got v${received.version}`);}assertOffchainMessageV1Equal(received, {version: 1,requiredSignatories: [{ address: signer }],content: input.expectedMessage});await verifyOffchainMessageEnvelope({content,signatures: { [signer]: signature }});}
assertOffchainMessageV1Equal, içerik veya imzalayanlar beklediğinizden farklıysa
hata fırlatır. verifyOffchainMessageEnvelope, imza eksik veya geçersizse hata
fırlatır. expectedMessage değerini, istemcinin geri gönderdiği kopyaya
guvenmek yerine sunucunuzda nonce'a göre bulun. Doğrulama başarılı olur olmaz
nonce'u tüketin; böylece aynı imza iki kez oturum açamaz.
Ham mesajı doğrulama
Bir signMessage imzası doğrudan meydan okuma baytlarını kapsar; bu nedenle
istemcinin yalnızca imzayı ve cüzdan adresini göndermesi yeterlidir. İmzayı,
verdiğiniz meydan okumaya karşı kontrol edin:
import {address,getBase64Encoder,getPublicKeyFromAddress,getUtf8Encoder,signatureBytes,verifySignature} from "@solana/kit";export async function verifyRawSignIn(input: {walletAddress: string;expectedMessage: string;signature: string;}) {const publicKey = await getPublicKeyFromAddress(address(input.walletAddress));const signature = signatureBytes(getBase64Encoder().encode(input.signature));const message = getUtf8Encoder().encode(input.expectedMessage);if (!(await verifySignature(publicKey, signature, message))) {throw new Error("Invalid signature");}}
Aynı nonce kuralları geçerlidir: expectedMessage değerini nonce'a göre bulun
ve doğrulama başarılı olur olmaz nonce'u tüketin.
@solana/kit, bu yardımcıları @solana/offchain-messages paketinden yeniden
dışa aktarır. Sunucunuz Kit'e bağımlı değilse bağımsız paketi kullanın.
Sign In With Solana
Sign In With Solana (SIWS),
meydan okuma metnini standartlaştırır. Uygulamanız, domain, statement,
nonce ve issuedAt gibi yapılandırılmış alanları cüzdanın solana:signIn
özelliğine iletir. Cüzdan bunları standart bir mesaja dönüştürür, gerekirse
bağlanır ve tek adımda imzalar.
solana:signIn özelliğinin 1.1.0 sürümü bir useOffchainMessage seçeneği
ekler. useOffchainMessage: { messageVersion: 1 } ilettiğinizde cüzdan, SIWS
metnini tek gerekli imzalayanı imzalayan hesap olan 1. sürüm bir zincir dışı
mesaja sarar. Donanım cüzdanları bunu görüntüleyebilir ve imzalayabilir; çıktı
signedMessageFormat: { kind: "offchainMessage", messageVersion: 1 } değerini
bildirir. 1.0.0 sürümünü bildiren cüzdanlar bu seçeneği tanımaz; bu yüzden
önce özellik sürümünü kontrol edin, aksi halde seçeneği atlayın.
solana:signIn bir hesap özelliği değil, cüzdan özelliğidir; bu nedenle bu
yardımcılar bir UiWallet alır:
import {SolanaSignIn,type SolanaSignInFeature,type SolanaSignInInput} from "@solana/wallet-standard-features";import type { UiWallet } from "@wallet-standard/ui";import { getWalletFeature } from "@wallet-standard/ui-features";type SignInFeature = SolanaSignInFeature[typeof SolanaSignIn];export function supportsOffchainSignIn(wallet: UiWallet) {if (!wallet.features.includes(SolanaSignIn)) return false;const feature = getWalletFeature(wallet, SolanaSignIn) as SignInFeature;return feature.version === "1.1.0";}export async function signIn(wallet: UiWallet, input: SolanaSignInInput) {const feature = getWalletFeature(wallet, SolanaSignIn) as SignInFeature;const [output] = await feature.signIn({...input,...(supportsOffchainSignIn(wallet) && {useOffchainMessage: { messageVersion: 1 }})});return output;}
Girdiyi sunucunuzda, tek kullanımlık bir nonce ve bir issuedAt zamanıyla
oluşturun ve nonce'a göre anahtarlanmış bir kopyasını saklayın. Oturum açma
doğrulanır doğrulanmaz nonce'u tüketin.
Wallet Adapter olmadan, @solana/wallet-standard-util içindeki verifySignIn
yalnızca düz SIWS çıktısını kontrol eder. signedMessageFormat bir zincir dışı
mesaj bildirdiğinde, signedMessage içeriği SIWS metni olan bir zincir dışı
mesajdır. Beklenen metni, saklanan girdinizden ve imzalayan adresten
@solana/wallet-standard-util içindeki createSignInMessageText ile yeniden
oluşturun, ardından İmzayı sunucunuzda doğrulayın bölümünde olduğu
gibi, diğer zincir dışı mesajlar gibi doğrulayın.
@solana/offchain-messages README dosyası
bu akışı Kit içinde ele alır.
Cüzdanlar için
Cüzdanlar, @solana/wallet-standard-features paketindeki
solana:signOffchainMessage Wallet Standard özelliğini uygulayarak destek ekler:
version: "1.0.0"bildirin ve imzalayabildiğiniz mesaj sürümlerinisupportedMessageVersionsiçinde listeleyin. Bugün bu değer[1]olmalıdır.- Sürüm 1 preamble'ını, sıralayıp tekrarlara karşı kontrol ettiğiniz
requiredSignersdeğerinden oluşturun. Hesap gerekli imzalayanlardan biri değilse isteği reddedin. - İmzalamadan önce mesaj metnini, donanım cihazları dahil, kullanıcıya eksiksiz olarak gösterin.
- İmzaladığınız tam baytları, Ed25519
signatureile birliktesignedOffchainMessageolarak döndürün. - Zincir dışı Sign In With Solana desteği sunmak için
solana:signInadınaversion: "1.1.0"bildirin. GirdiuseOffchainMessageiçerdiğinde, SIWS metnini sürüm 1 zincir dışı mesaj olarak imzalayın, baytlarınısignedMessageolarak döndürün vesignedMessageFormatdeğerini ayarlayın. solana:signMessagedesteğini sürdürün. Bu yöntem değişmedi ve uygulamalar onu hâlâ yedek yöntem olarak kullanıyor.
Is this page helpful?