Bir mesajı imzalamak, kullanıcının bir cüzdanı kontrol ettiğini kanıtlar; işlem, ücret ya da ağ çağrısı gerekmez. Uygulamalar imzalı mesajları kullanıcıların oturum açması, bir kullanıcının şartları kabul ettiğini kaydetmesi, bir cüzdanı mevcut bir hesaba bağlaması veya sipariş verme gibi zincir dışı gerçekleşen bir işlemi yetkilendirmesi 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çimde sarmalar. Bu biçim asla 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ı tam olarak imzalar; ön ek ya da bildirilmiş imzalayıcı içermez. Çoğu yazılım cüzdanı bunları destekler, ancak baytlarda bunun bir işlem değil mesaj olduğunu gösteren 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 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 | 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 işlevsiz (no-op) 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 yoklama 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 açıklayan bir ifadeyi değil, işlem baytlarını onaylar.
- Davranışsal risk. Kullanıcılardan yalnızca oturum açmak için işlem imzalamalarını istemek, onları zararlı bir işlemi imzalamaya kandırmayı kolaylaştırır.
Zincir dışı bir mesaj bunların hepsini atlar. Tek gidiş-dönüş, kendi sunucunuzadır.
Cüzdanın neyi imzaladığı
Zincir dışı bir mesaj, sabit bir imzalama alanıyla başlar: \xffsolana offchain.
Geçerli hiçbir işlem bu baytlarla başlamaz, bu nedenle imzalanmış bir mesaj asla
işlem olarak kullanılamaz. Biçimin 1. sürümü ardından gerekli imzalayanları listeler
ve mesajı uzunluk sınırı olmayan 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 de dahil olmak üzere biçimin tamamını kapsar.
Bir oturum açma yöntemi seçin
Her cüzdan henüz zincir dışı mesajları desteklemez. İmza istemeden önce bağlı hesabın özelliklerini kontrol edin ve özellik yoksa yedek yönteme geçin:
- Hesap, mesaj sürümü 1 ile
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, "Donanım cüzdanı kullanıyorum" anahtarı gibi açık bir katılım seçeneği sunun. 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 bir UiWalletAccount alır; örneğin React kılavuzundaki
useConnectedWallet tarafından döndürülen account:
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ı içeren signedOffchainMessage ile
signature değerlerini döndürür. İkisini de Base64 ile kodlayın ve cüzdan adresiyle birlikte
sunucunuza gönderin.
Meydan okuma metnini küçük ekranlarda iyi okunacak şekilde yazın. Uygulamanızın ve alan adınızın adını belirtin, imzalamanın ne yaptığını söyleyin ve sunucunuzdan alınan 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ğrulama
Doğrulama yerel bir imza kontrolüdür ve RPC bağlantısı gerektirmez. Cüzdanın imzaladığı baytları çözün, bunların verdiğiniz meydan okumayla eşleştiğini doğrulayın, 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 güvenmek yerine sunucunuzda nonce ile arayın. 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, dolayısıyla 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 ile arayın ve
doğrulama başarılı olunca 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.
Solana ile Oturum Açma
Solana ile Oturum Açma (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 mesaj olarak biçimlendirir, 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
yalnızca imzalayan hesabı gerekli imzalayan olarak içeren 1. sürüm bir zincir dışı mesajda sarmalar.
Donanım cüzdanları bunu görüntüleyip imzalayabilir ve çı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 nedenle önce özellik sürümünü kontrol edin
ve 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ğrulandığında nonce'u tüketin.
Wallet Adapter olmadan, @solana/wallet-standard-util paketindeki 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 diğer tüm zincir dışı mesajlar gibi,
İmzayı sunucunuzda doğrulama bölümünde olduğu 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 imzalayabileceğiniz mesaj sürümlerinisupportedMessageVersionsiçinde listeleyin. Bugün bu değer[1]'dir.requiredSignersiçinden sürüm 1 önsözünü (preamble) oluşturun; bu listeyi sıralayın ve yinelenen girdiler olup olmadığını kontrol edin. Hesap gerekli imzalayanlardan biri değilse isteği reddedin.- İmzalamadan önce mesaj metnini, donanım cihazlarında da dahil olmak üzere, kullanıcıya eksiksiz olarak gösterin.
- İmzaladığınız baytların tamamını, Ed25519
signatureile birliktesignedOffchainMessageolarak döndürün. - Zincir dışı (off-chain) Sign In With Solana desteği sunmak için
solana:signIniçinversion: "1.1.0"bildirin. GirdiuseOffchainMessageiçeriyorsa 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ürmeye devam edin. Bu yöntem değişmedi ve uygulamalar onu hâlâ yedek yöntem olarak kullanıyor.
Is this page helpful?