Bu kılavuz, solana-keychain kütüphanesine yeni anahtar yönetim çözümlerini
entegre etmek isteyen cüzdan hizmet sağlayıcıları ve geliştiriciler içindir.
İmzalayıcı implementasyonunuzu ekleyerek, geliştiricilerin birleşik bir arayüz
üzerinden güvenli Solana işlem imzalama için hizmetinizi kullanmasını
sağlayacaksınız.
Keychain; Rust, TypeScript, Python ve Go uygulamalarını birbiriyle eşdeğer (parity) tutar. Dördünü de içeren PR'lar kesinlikle tercih edilir. Yalnızca birine katkıda bulunabiliyorsanız sorun değil, ancak signer bir sürümde yayımlanmadan önce diğerlerinin de gerekeceğini unutmayın.
Claude Code mu kullanıyorsunuz? Deponun
add-signer-ci skill'i,
fork PR'ı ile katkıda bulunulan yeni bir signer için CI iş akışlarını kurar.
Mimari Genel Bakış
Kütüphane, src/traits.rs içinde tanımlanan trait tabanlı bir mimari kullanır. Her
signer, SolanaSigner temel trait'ini (pubkey, sign_message,
is_available) ve sağlayıcının yapısına uyan tam olarak bir yetenek trait'ini uygular:
TransactionSigner(sign_transaction): sağlayıcı, çağıranın baytlarını imzalar ve yayını çağıran yapar. Çoğu arka uç bu türdendir.ModifyingSigner(modify_and_sign_transaction): sağlayıcı işlemi yeniden yazar, ardından yayınlamadan imzalar.SendingSigner(sign_and_send_transaction): sağlayıcı sunucu tarafında imzalar ve yayınlar.
Kütüphane ayrıca tüm uygulamaları saran birleşik bir Signer enum'u sunar; bu sayede imzalama arka uçları çalışma zamanında seçilebilir. Enum yalnızca temel trait'i uygular; yeteneklere as_transaction_signer(), as_modifying_signer() ve as_sending_signer() üzerinden erişilir.
Hızlı Entegrasyon Kontrol Listesi
- İmplementasyonunuzla birlikte imzalayıcı modülünüzü oluşturun
SolanaSignertemel trait'ini (pubkey,sign_message,is_available) ve sağlayıcınızın yapısına uyan tek bir yetenek trait'ini (genellikleTransactionSigner) uygulayınCargo.tomliçine bir özellik bayrağı ekleyin; signer'ınız reqwest kullanıyorsa dahili_remoteişaretini de dahil edinsrc/lib.rsiçindekiSignerenum'unu güncelleyin (variant,dispatch_signer!kolu ve yetenek erişimcisinde yapınıza uyan bir kol)- HTTP istemcilerinde HTTPS'i zorunlu kılın ve zaman aşımlarını yapılandırın
- Mesaj baytlarını imzalayın ve döndürülen imzayı eklemeden önce doğrulayın
- Kapsamlı birim testleri ekleyin (wiremock tabanlı, modülünüzün içinde)
- Bir entegrasyon testi dosyası ekleyin ve
.env.exampledosyasını güncelleyin - TypeScript, Python ve Go uygulamalarını ekleyin
- Dokümantasyonu güncelleyin
- CI iş akışı güncellemeleri (bakımcılarla koordinasyon sağlayın, bkz. CI İş Akışı Güncellemeleri)
- PR gönderin
Adım 1: İmzalayıcı Modülünüzü Oluşturun
İmplementasyonunuz için src/ altında yeni bir dizin oluşturun:
src/├── your_service/│ ├── mod.rs # Main implementation with SolanaSigner trait│ └── types.rs # API request/response types (if needed)
Adım 2: İmzalayıcı Struct'ınızı Tanımlayın
src/your_service/mod.rs içinde imzalayıcı struct'ınızı tanımlayın:
//! YourService API signer integrationuse crate::sdk_adapter::{Pubkey, Signature, VersionedTransaction};use crate::{error::SignerError, traits::SolanaSigner};use std::str::FromStr;/// YourService-based signer using YourService's API#[derive(Clone)]pub struct YourServiceSigner {api_key: String,api_secret: String,wallet_id: String,api_base_url: String,client: reqwest::Client,public_key: Pubkey,}impl std::fmt::Debug for YourServiceSigner {fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {f.debug_struct("YourServiceSigner").field("public_key", &self.public_key).finish_non_exhaustive()}}
Adım 3: Constructor ve Yardımcı Metodları İmplemente Edin
Uzak imzalayıcılar HTTPS'i zorunlu kılmalı ve HTTP zaman aşımlarını
yapılandırmalıdır. Zaman aşımı ayarları için paylaşılan HttpClientConfig
struct'ını kullanın.
use crate::http_client_config::HttpClientConfig;impl YourServiceSigner {pub fn new(api_key: String,api_secret: String,wallet_id: String,public_key: String,http_config: Option<HttpClientConfig>,) -> Result<Self, SignerError> {let pubkey = Pubkey::from_str(&public_key).map_err(|e| SignerError::InvalidPublicKey(format!("Invalid public key: {e}")))?;let http = http_config.unwrap_or_default();let builder = reqwest::Client::builder().timeout(http.resolved_request_timeout()).connect_timeout(http.resolved_connect_timeout());// Enforce HTTPS in production; wiremock uses HTTP in tests#[cfg(not(test))]let builder = builder.https_only(true);let client = builder.build().map_err(|e| {SignerError::ConfigError(format!("Failed to build HTTP client: {e}"))})?;Ok(Self {api_key,api_secret,wallet_id,api_base_url: "https://api.yourservice.com/v1".to_string(),client,public_key: pubkey,})}/// Sign raw bytes using your service's APIasync fn sign(&self, message: &[u8]) -> Result<Signature, SignerError> {let encoded_message = base64::engine::general_purpose::STANDARD.encode(message);let url = format!("{}/sign", self.api_base_url);let response = self.client.post(&url).header("Authorization", format!("Bearer {}", self.api_key)).json(&serde_json::json!({"wallet_id": self.wallet_id,"message": encoded_message,})).send().await?;// Use generic error messages: never expose raw API response textif !response.status().is_success() {let status = response.status().as_u16();return Err(SignerError::remote_api(format!("YourService API returned status {status}")));}// Parse response: always use map_err, never .expect() or .unwrap()let response_data: SignResponse = response.json().await.map_err(|e| SignerError::SerializationError(format!("Failed to parse response: {e}")))?;let sig_bytes = base64::engine::general_purpose::STANDARD.decode(&response_data.signature).map_err(|e| SignerError::SerializationError(format!("Failed to decode signature: {e}")))?;let sig_array: [u8; 64] = sig_bytes.try_into().map_err(|_| SignerError::SigningFailed("Invalid signature length".to_string()))?;Ok(Signature::from(sig_array))}}
Adım 4: Signer Trait'lerini Uygulayın
SolanaSigner temel trait'i 2 asenkron metot (sign_message,
is_available) ve ayrıca pubkey() içerir. İşlem imzalama bir yetenek trait'inde yer alır;
çoğu sağlayıcı TransactionSigner uygular. Bu trait'in sign_transaction metodu
SignTransactionResult döndürür (işlemin tamamen veya kısmen imzalandığını
işaretleyen etiketli bir enum).
Mesaj baytlarını imzalayın, asla ağ üzerindeki işlemin tamamını değil; ve sağlayıcının imzasını eklemeden önce bu baytlara karşı doğrulayın. İmzalama ve serileştirme için ortak
TransactionUtil yardımcılarını kullanın.
use crate::signature_util::verify_or_reject;use crate::traits::{SignTransactionResult, TransactionSigner};use crate::transaction_util::TransactionUtil;#[async_trait::async_trait]impl SolanaSigner for YourServiceSigner {fn pubkey(&self) -> Pubkey {self.public_key}async fn sign_message(&self, message: &[u8]) -> Result<Signature, SignerError> {let signature = self.sign(message).await?;verify_or_reject(&signature, &self.public_key, message)?;Ok(signature)}async fn is_available(&self) -> bool {let url = format!("{}/health", self.api_base_url);self.client.get(&url).send().await.map(|r| r.status().is_success()).unwrap_or(false)}}#[async_trait::async_trait]impl TransactionSigner for YourServiceSigner {async fn sign_transaction(&self,tx: &mut VersionedTransaction,) -> Result<SignTransactionResult, SignerError> {let message = tx.message.serialize();let signature = self.sign(&message).await?;verify_or_reject(&signature, &self.public_key, &message)?;TransactionUtil::add_signature_to_transaction(tx, &self.public_key, signature)?;let serialized = TransactionUtil::serialize_transaction(tx)?;Ok(TransactionUtil::classify_signed_transaction(tx,(serialized, signature),))}}
Sağlayıcınız imzalamadan önce işlemi yeniden yazıyorsa, bunun yerine
ModifyingSigner uygulayın ve çağıranın işlemini tamamen
sağlayıcının baytlarıyla değiştirin. Sağlayıcı sunucu tarafında yayın yapıyorsa SendingSigner
uygulayın ve çağıranın işlemine dokunmayın. Sağlayıcınızın sunamadığı bir yeteneği asla uygulamayın.
Adım 5: API Türlerini Ekleyin (İsteğe Bağlı)
API'niz özel türlere ihtiyaç duyuyorsa, src/your_service/types.rs oluşturun:
use serde::{Deserialize, Serialize};#[derive(Serialize)]pub struct SignRequest {pub wallet_id: String,pub message: String,}#[derive(Deserialize)]pub struct SignResponse {pub signature: String,}
Adım 6: Özellik Bayrağı Ekleyin
Signer'ınızı isteğe bağlı bir özellik olarak eklemek için Cargo.toml dosyasını güncelleyin. Uzak arka uçlar,
src/error.rs içindeki From<reqwest::Error> uygulamasını kapılayan dahili _remote işaretini
dahil eder; bu olmadan, yalnızca sizin özelliğiniz etkinken reqwest çağrılarında ? derlenmez.
[features]default = ["memory", "sdk-v2"]memory = []vault = ["dep:reqwest", "_remote"]privy = ["dep:reqwest", "dep:p256", "_remote"]your_service = ["dep:reqwest", "_remote"] # Add your featureall = ["memory", "vault", "privy", "...", "your_service"] # Update all
Adım 7: Signer Enum'unu Güncelleyin
Signer'ınızı src/lib.rs dosyasına ekleyin. Temel SolanaSigner uygulaması
dispatch_signer! makrosu üzerinden yönlendirme yapar, bu yüzden oraya bir kol ekleyin. Yetenek erişimi
as_transaction_signer(), as_modifying_signer() ve as_sending_signer()
erişimcilerinde bulunur: yetenek trait'inize uyan erişimciye bir Some(s) kolu ekleyin (ve
yayın yapan veya değiştiren bir arka uç için as_transaction_signer() içine bir None kolu ekleyin).
// Add feature-gated module#[cfg(feature = "your_service")]pub mod your_service;// Re-export your signer type#[cfg(feature = "your_service")]pub use your_service::YourServiceSigner;// Add to Signer enum#[derive(Debug)]pub enum Signer {#[cfg(feature = "memory")]Memory(MemorySigner),// ... existing variants#[cfg(feature = "your_service")]YourService(YourServiceSigner), // Add your variant}// Add constructor methodimpl Signer {#[cfg(feature = "your_service")]pub fn from_your_service(api_key: String,api_secret: String,wallet_id: String,public_key: String,) -> Result<Self, SignerError> {Ok(Self::YourService(YourServiceSigner::new(api_key,api_secret,wallet_id,public_key,None, // uses default HttpClientConfig)?))}}// Update the dispatch macro: the base SolanaSigner impl uses it for// pubkey, sign_message and is_availablemacro_rules! dispatch_signer {($self:ident, $signer:pat => $body:expr) => {match $self {// ... existing variants#[cfg(feature = "your_service")]Signer::YourService($signer) => $body,}};}// Add your arm to the capability accessor matching your shapeimpl Signer {pub fn as_transaction_signer(&self) -> Option<&dyn TransactionSigner> {match self {// ... existing variants#[cfg(feature = "your_service")]Signer::YourService(s) => Some(s),}}}
Adım 8: Kapsamlı Testler Ekleyin
Modülünüze testler ekleyin (src/your_service/mod.rs dosyasının altında):
#[cfg(test)]mod tests {use super::*;use solana_sdk::{signature::Keypair, signer::Signer};use wiremock::{matchers::{header, method, path},Mock, MockServer, ResponseTemplate,};#[tokio::test]async fn test_new() {let keypair = Keypair::new();let signer = YourServiceSigner::new("test-key".to_string(),"test-secret".to_string(),"test-wallet".to_string(),keypair.pubkey().to_string(),None,);assert!(signer.is_ok());}#[tokio::test]async fn test_sign_message() {let mock_server = MockServer::start().await;let keypair = Keypair::new();let message = b"test message";let signature = keypair.sign_message(message);Mock::given(method("POST")).and(path("/sign")).respond_with(ResponseTemplate::new(200).set_body_json(serde_json::json!({"signature": base64::engine::general_purpose::STANDARD.encode(signature.as_ref())}))).expect(1).mount(&mock_server).await;let mut signer = YourServiceSigner::new("test-key".to_string(),"test-secret".to_string(),"test-wallet".to_string(),keypair.pubkey().to_string(),None,).unwrap();signer.api_base_url = mock_server.uri();let result = signer.sign_message(message).await;assert!(result.is_ok());}#[tokio::test]async fn test_sign_unauthorized() {let mock_server = MockServer::start().await;let keypair = Keypair::new();Mock::given(method("POST")).and(path("/sign")).respond_with(ResponseTemplate::new(401)).expect(1).mount(&mock_server).await;let mut signer = YourServiceSigner::new("bad-key".to_string(),"bad-secret".to_string(),"test-wallet".to_string(),keypair.pubkey().to_string(),None,).unwrap();signer.api_base_url = mock_server.uri();let result = signer.sign_message(b"test").await;assert!(result.is_err());}}
İmza doğrulama hatasını da kapsayın: imzası doğrulanmayan sahte (mock) bir yanıt hata döndürmelidir.
Adım 9: Entegrasyon Testleri Ekleyin
rust/src/tests/test_<name>_integration.rs dosyasını oluşturun. Entegrasyon testleri
gerçek servis API'sine karşı çalışır (wiremock değil) ve integration-tests özelliğinin arkasında
kapılanır. Her dosyada şunlar bulunmalıdır:
- Ortam değişkeni adları için
pub constbildirimleri - Ortam değişkenlerini
dotenvyile okuyan birget_signer()yardımcısı - Üç test fonksiyonu:
test_<name>_sign_message,test_<name>_sign_transactionvetest_<name>_is_available - Özellik kapıları: modül üzerinde
#[cfg(feature = "your_service")]ve her testte#[cfg(feature = "integration-tests")]
Eksiksiz bir referans için rust/src/tests/test_para_integration.rs dosyasına bakın ve
modülünüzü rust/src/tests/mod.rs içinde bildirin:
pub mod test_your_service_integration;
Adım 10: Ortam Değişkenlerini Güncelleyin
Kök dizindeki .env.example dosyasını, mevcut desene uyarak signer'ınızın ortam değişkenleriyle
güncelleyin:
# YourService Configuration (for SIGNER_TYPE=your_service)YOUR_SERVICE_API_KEY=your-api-keyYOUR_SERVICE_WALLET_ID=your-wallet-id# YOUR_SERVICE_API_BASE_URL=https://api.yourservice.com/v1 # Optional, defaults to this
Gerekli değişkenleri önce yer tutucu değerlerle listeleyin, isteğe bağlı değişkenleri ise varsayılan değerleriyle birlikte yorum satırı olarak bırakın. Entegrasyon testlerinde kullanılan her ortam değişkeni
.env.example içinde yer almalıdır; TypeScript, Python ve Go test paketleri de aynı adları kullanır.
Adım 11: Dokümantasyonu Güncelleyin
README.md içindeki desteklenen arka uçlar tablosuna imzalayıcınızı ekleyin:
| Arka Uç | Kullanım Durumu | Özellik Bayrağı |
|---|---|---|
| Memory | Yerel keypair'ler, geliştirme, test | memory |
| Vault | HashiCorp Vault ile kurumsal anahtar yönetimi | vault |
| Privy | Privy altyapısı ile gömülü cüzdanlar | privy |
| Turnkey | Turnkey aracılığıyla emanetsiz anahtar yönetimi | turnkey |
| YourService | Hizmetinizin kısa açıklaması | your_service |
Ayrıca arka ucunuzu her dilin README dosyasındaki signer yetenekleri tablosuna ekleyin. Bir kullanım örneği ekleyin:
use solana_keychain::{Signer, SolanaSigner};#[tokio::main]async fn main() -> Result<(), Box<dyn std::error::Error>> {let signer = Signer::from_your_service("your-api-key".to_string(),"your-api-secret".to_string(),"your-wallet-id".to_string(),"your-public-key".to_string(),)?;let pubkey = signer.pubkey();println!("Public key: {}", pubkey);Ok(())}
Entegrasyonunuzu Test Etme
Ham cargo yerine just tariflerini kullanın. SDK özellikleri (sdk-v2,
sdk-v3, sdk-v4) birbirini dışlar, bu nedenle cargo test --all-features
başarısız olur.
# Unit tests across the sdk-v2, sdk-v3 and sdk-v4 matricesjust rust-test# Live integration tests (require credentials in .env)just rust-test-integration
CI İş Akışı Güncellemeleri (Fork PR'ları)
CI iki aşamalı bir süreçtir. PR'ınız test edilmeden önce main dalını hazırlamak için bakımcılarla koordinasyon sağlayın:
bakımcılar fork-external-live-manual.yml dosyasını günceller, ci.yml dosyasına ortam değişkenlerini ekler
ve GitHub Secrets'ı yapılandırır.
PR'ınız, signer kodunuzla birlikte bu 2. Aşama değişikliklerini de içermelidir:
ci.yml: Rust özelliğinizibackendmatrisine (all'dan önce) ve entegrasyon test fonksiyonunuzutestmatrisine ekleyintypescript-ci.yml: paketinizi birim ve entegrasyon test matrislerine ekleyin ve entegrasyon testi adımına ortam değişkenlerini ekleyintypescript-publish.yml: paketiniziPUBLISH_PACKAGES'a, GitHub Releasepackagesdizisine ve özet tablosuna ekleyinpython-ci.yml: arka ucuresolve-signersiçindekiCI_SIGNER_*kapılamasına ve entegrasyon matrisine ekleyingo-ci.yml: arka ucuCI_SIGNER_*kapılamasına vemoduleBySigner'a ekleyin
TypeScript İmzalayıcı
Ayrıca bir TypeScript imzalayıcı paketi ekliyorsanız, bunu
typescript/packages/your-signer/ konumunda oluşturun. Temel desenler:
createYourSigner()fabrika fonksiyonuSolanaTransactionSigner<TAddress> & SolanaMessageSigner<TAddress>döndürür; signer sınıfını dahili tutun ve yalnızca fabrikayı ve yapılandırma tipini dışa aktarın- Yapılandırma arayüzünü dışa aktar (
YourSignerConfig) - Arka ucunuz mesaj imzalayamıyorsa
SolanaMessageSigner'ı atlayın ve hiçbirsignMessagesmetodu sunmayın: Kit, signer'ları metodun varlığına göre sınıflandırır, bu yüzden hata fırlatan bir taslak (stub) signer'ı yanlış sınıflandırır apiBaseUrlyapılandırma alanlarında@solana/keychain-coreiçindekiassertHttpsUrl()ile HTTPS'i zorunlu kılın- Uzak çağrıları
@solana/keychain-coreiçindekifetchSignerJson()ile yapın; hata eşlemesi, yanıt temizleme, yönlendirme reddi ve varsayılan zaman aşımı bu fonksiyonun sorumluluğundadır - Diğer tüm uzak API hata metinlerini
@solana/keychain-coreiçindekisanitizeRemoteErrorResponse()ile temizleyin @solana/keychain-corepaketindenthrowSignerError(SignerErrorCode.*, { cause, message })kullan- Her imzalama metodunda
config?.abortSignaldeğerine uyun ve bunufetchSignerJson()ilesignBatchStaggered()içine iletin requestDelayMsdeğerinivalidateRequestDelayMs()ile doğrulayın ve toplu işlemlerisignBatchStaggered()ile yapın- Hata kodlarını listeleyen fabrika fonksiyonlarına
@throwsJSDoc ekle @solana/*paketlerini>=8.1.0sürümünde peer dependency olarak bildirin- vitest ve sahte (mock)
fetchile birim testleri;@solana/keychain-test-utilsiçindekirunSignerIntegrationTestile entegrasyon testleri
Sending Signer'lar
Sağlayıcınız işlemi yeniden yazıyorsa veya sunucu tarafında yayın yapıyorsa, imzası
çağıranın işlemine uygulanamaz; bu nedenle SolanaTransactionSigner yerine
SolanaSendingSigner olmalıdır:
signAndSendTransactions()uygulayın ve hiçbirsignTransactionssunmayın (arka uç gerçekten mesaj imzalamıyorsasignMessagesde sunmayın)config?.abortSignaldeğerine uyun. Sağlayıcı oluşturma isteğini kabul ettikten sonra, her hatayı (iptal dahil) sağlayıcı işlem kimliğini taşıyanBROADCAST_UNCONFIRMEDolarak bildirin ve iptal nedeninicauseolarak saklayın- Sending-signer tipinizi döndüren, tipli bir
createKeychainSigneraşırı yüklemesi (overload) ekleyin - Arka ucunuzu
typescript/packages/kit-plugin/src/keychain-plugin.tsiçindekiKeychainKitPluginConfig'den çıkarın; çünkü sending signer, bir Kit istemcisindepayerveyaidentityolarak kullanılamaz - Birim testlerinde, örnekleriniz için
isTransactionPartialSigner()değerinin false veisTransactionSendingSigner()değerinin true olduğunu doğrulayın
Şemsiye Paketini Güncelle
typescript/packages/keychain/ dizinini güncelleyin (değiştirilecek 7 dosya):
src/types.ts:YourSignerConfigtipiniKeychainSignerConfigayrımlı birleşimine (discriminated union) ekleyinsrc/create-keychain-signer.ts: switch case'i ekleyin (her arka uç paketi dinamik birimport()ile yüklenir)src/resolve-address.ts: hızlı yol (fast-path) veya getirme yolu (fetch-path) switch case'ine ekleyinsrc/index.ts: yapılandırma tipi, namespace ve fabrika fonksiyonu dışa aktarımlarını ekleyinpackage.json:@solana/keychain-your-signer: "workspace:*"bağımlılığını ekleyintsconfig.json:{ "path": "../your-signer" }referansını ekleyintypescript/scripts/test-treeshake-umbrella.mjs: paketiniziSIGNER_MARKERS'a ve fabrikanızıFACTORIES'e ekleyin
Switch ifadelerinde kapsamlı never kontrolleri vardır; bu nedenle birleşime ekleme yapıp bir case'i atlarsanız TypeScript hata verir. Ardından just ts-treeshake komutunu çalıştırın:
her paket ve umbrella paketi tree-shake edilebilir kalmalıdır.
Python Signer
Modülü python/src/solana_keychain/your_signer/ dizininde oluşturun (__init__.py
ve signer.py). Temel kalıplar:
solana_keychain.coreiçindenSolanaSignersınıfından türetin (pubkeyözelliği,async sign_message(),async is_available()) ve tam olarak bir yetenek sınıfı ekleyin:TransactionSigner(async sign_transaction()),ModifyingSigner(async modify_and_sign_transaction()) veyaSendingSigner(async sign_and_send_transaction())- Arka uç adresini uzaktan çözümlüyorsa
init()çağrısını bekleyen (await) birasync create_your_signer()fabrikası dışa aktarın; init öncesinde imzalamaSignerErrorCode.NOT_INITIALIZEDhatası verir - Gizli bilgileri
field(repr=False)ile işaretlenmiş bir yapılandırma dataclass'ı (YourSignerConfig) dışa aktarın - Temel URL alanlarında
assert_https_url()ile HTTPS'i zorunlu kılın ve her isteğifetch_signer_json()üzerinden yönlendirin - Sağlayıcının imzasını döndürmeden önce doğrulayın; bir imzanın kapsadığı baytlar için
signed_message_bytes()kullanın, asla elle yazılmış bir sürüm öneki kullanmayın - Arka ucu
python/src/solana_keychain/keychain.pyiçindeki_BACKENDSiçine kaydedin - Arka uç bir sağlayıcı SDK'sı gerektiriyorsa, import'u koruma altına alın,
pip install 'solana-keychain[your-signer]'komutunu belirten birImportErrorfırlatın, ekstrayıpython/pyproject.tomliçinde tanımlayın ve arka ucu paket kökünden asla erkenden (eager) import etmeyin - pytest ile birim testleri: HTTP arka uçları için
respx, SDK arka uçları için stub istemciler ve fırlatılan hatalarınstr()veyarepr()çıktısında hiçbir gizli bilginin görünmediğini doğrulayan bir kontrol python/tests/integration/test_live_signers.pydosyasına birmake_your_signer()fabrika fonksiyonu ekleyin ve bunu_MESSAGE_CAPABLEiçine kaydedin (sign_messagedesteği olmayan arka uçlar için_TRANSACTION_ONLY)
Go İmzalayıcı
Her Go arka ucu, go/signers/yoursigner/ konumunda ve
github.com/solana-foundation/solana-keychain/go/signers/yoursigner/v2 modül
yoluyla kendi modülüdür. Referans için go/signers/para/ dizinine bakın. Temel
kalıplar:
core.SolanaSignerarayüzünü (Pubkey(),SignMessage(ctx, message),IsAvailable(ctx)) ve yetenek arayüzlerinden tam olarak birini uygulayın:core.TransactionSigner,core.ModifyingSignerveyacore.SendingSigner. Yetenek, tür doğrulamasıyla (type assertion) tespit edilir; dolayısıyla bir işlemi imzalayamayan arka uç hiçbirSignTransactionmetodu içermemelidir- Bir
Configstruct'ı ve birNewyapıcısı dışa aktarın (arka uç adresini uzaktan çözümlüyorsaNew(ctx, cfg); bundan öncecore.NewNotInitializedErrordöndürün) - HTTP istemcilerini
core.NewHTTPClientveyacore.ResolveHTTPClientile oluşturun ve temel URL'lericore.NormalizeHTTPSBaseURLile doğrulayın - İşlemleri, mesaj baytlarını imzalayıp imzayı ekleyen
core.SignTransactionWithile imzalayın; sağlayıcı imzalarınıcore.VerifySignatureile doğrulayın - Hataları
core.NewSignerError/core.WrapSignerErrorvecore.CodeSigningFailedgibi kararlı kodlarla döndürün httptestile birim testleri;//go:build integrationetiketinin arkasındaintegration_test.goiçinde entegrasyon testleri
Gönderim Kontrol Listesi
PR'ınızı göndermeden önce:
- Kod uyarı olmadan derleniyor (
just build) - Tüm testler geçiyor (
just test) - Kod biçimlendirilmiş/linting geçiyor (
just fmt) - Kodda sabit kodlanmış değerler veya gizli bilgiler yok
- Hata mesajları geneldir (ham API yanıt metni yok)
- Güvenilmeyen API yanıtlarında
.expect()veya.unwrap()yok - Uzak HTTP istemcilerinde HTTPS zorunlu kılınmış
- HTTP zaman aşımları
HttpClientConfigüzerinden yapılandırılmış - Döndürülen imzalar, yerel olarak hesaplanan mesaj baytlarına karşı doğrulanıyor
- Adlandırma kurallarına uyuyor (Rust ve Python için snake_case, TypeScript için camelCase)
- Entegrasyon testleri eklendi ve
.env.examplegüncellendi - TypeScript, Python ve Go uygulamaları dahil (veya bakımcılarla birlikte planlandı)
- Çatı paketler güncellendi (TypeScript çatı paketi, Python
_BACKENDS) - CI değişiklikleri dahil
- README.md içindeki desteklenen arka uçlar ve imzalayıcı yetenekleri tablolarına eklendi
Uygulama İpuçları
Hata Yönetimi
Her zaman mevcut SignerError varyantlarını kullanın. SignerError üzerindeki
hem Display hem de Debug çıktıları maskelenmiştir; bu nedenle mesajları genel
tutun ve ham API yanıt verilerini asla eklemeyin. Güvenilmeyen API yanıtlarında
asla .expect() veya .unwrap() kullanmayın:
// Good: uses the constructor with a generic messagereturn Err(SignerError::remote_api(format!("YourService API returned status {status}")));// Good: converts from standard errorslet bytes = base64::decode(data).map_err(|e| SignerError::SerializationError(format!("Failed to decode: {e}")))?;
Güvenlik Gereksinimleri
Bunlar öneri değildir: kullanıcılar bir arka ucu bunlara güvenerek seçer.
Sağlayıcınız bunlardan birini imkânsız kılıyorsa, bunu PR'da ve deponun
docs/SECURITY_MODEL.md dosyasında belirtin.
- Sağlayıcının neyi imzaladığını doğrulayın. Mesaj baytlarını yerel olarak hesaplayın ve döndürülen imzayı eklemeden önce, imzalayıcınızın gerektirdiği konumda bu baytlara karşı doğrulayın. Doğrulanmayan bir imza başarısız olmalı, asla eklenmemelidir.
- Sağlayıcının yayın yapıp yapmadığını belirtin. Sağlayıcı işlemi sunucu tarafında yürütüyorsa, gönderen (sending) yapıyı uygulayın ve zincire giren işlemi tanımlayan imzayı döndürün. Sağlayıcının yalnızca imzalama yapan bir uç noktası yoksa, çağıranın haberi olmadan gönderim yapmak yerine arka ucu yalnızca gönderen olarak yapın.
- Çağıranı, imzanın kapsamadığı bir işlemle asla baş başa bırakmayın. İşlemi yeniden yazan bir sağlayıcı kendi baytlarını imzalar: ya çağıranın işlemine hiç dokunmayın (yayının sağlayıcı tarafından yönetildiği durum) ya da değiştiren (modifying) yapıyı benimseyip işlemi tümüyle sağlayıcının baytlarıyla değiştirin.
- Yayın belirsizliğini belirsizlik olarak bildirin. Gönderimden sonraki bir
hata yine de işlemin zincire girmesiyle sonuçlanmış olabilir. Genel bir hata
yerine
BROADCAST_UNCONFIRMEDdöndürün, elinizde olduğunda sağlayıcının işlem kimliğini iletin ve her oluşturma isteğinde mesajdan türetilmiş bir idempotency anahtarı gönderin; böylece bayt düzeyinde aynı olan yeniden gönderim güvenli olur. - Init'ten önce asla imzalamayın.
init()öncesinde açık anahtar içinPubkey::default()yerineOption<Pubkey>kullanın ve önce imzalama denenirse bir yapılandırma hatasıyla başarısız olun. - Hassas verileri (özel anahtarlar, API gizli anahtarları, ham API yanıtları)
asla loglamayın ve hassas alanları gizleyen bir
Debugimplementasyonu kullanın - HTTPS'i zorunlu kılın (
https_only(true), wiremock için#[cfg(not(test))]ile koşullandırılmış) ve istek ile bağlantı zaman aşımlarınıHttpClientConfigüzerinden yapılandırın (varsayılanlar: 30sn/5sn) - Ara anahtar materyalini
zeroize::Zeroizing<Vec<u8>>ile sıfırlayın
Mock'larla Test Etme
HTTP API'lerini mocklamak için wiremock kullanın. Hata mesajı metnini değil,
yalnızca hata türünü doğrulayın:
#[cfg(test)]mod tests {use wiremock::{MockServer, Mock, ResponseTemplate};#[tokio::test]async fn test_api_call() {let mock_server = MockServer::start().await;Mock::given(method("POST")).respond_with(ResponseTemplate::new(200)).mount(&mock_server).await;// Use mock_server.uri() as your api_base_url}}
Yardım Alma
- Mevcut imzalayıcı uygulamalarını desenler için inceleyin:
src/memory/mod.rs: Basit, senkronsrc/privy/mod.rs: Başlatma gerektirirsrc/turnkey/mod.rs: Karmaşık imza işlemesrc/vault/mod.rs: Harici istemci kütüphanesitypescript/packages/para/,python/src/solana_keychain/para/vego/signers/para/: diğer dillerdeki eksiksiz referanslar
- Temel dosyalar:
src/traits.rs(trait tanımları),src/transaction_util.rsvesrc/signature_util.rs(paylaşılan yardımcılar),src/http_client_config.rs(zaman aşımı yapılandırması) - Deponun tam kılavuzu: docs/ADDING_SIGNERS.md
- Çalışmaya başlamadan önce tasarım tartışmaları için bir issue açın
Örnek PR Yapısı
feat(signer): add YourService signer integrationAdds support for YourService as a signing backend.- [X] Code compiles without warnings (`just build`)- [X] Code is formatted/linting passes (`just fmt`)- [X] Add comprehensive tests with wiremock - All tests pass (`just test`)- [X] Implemented SolanaSigner and TransactionSigner for YourServiceSigner- [X] Added feature flag 'your_service'- [X] HTTPS enforced, HTTP timeouts configured- [X] Returned signatures verified before attaching- [X] Added integration tests and updated .env.example- [X] TypeScript, Python and Go implementations included- [X] CI Phase 2 changes included- [X] Added to README.md supported backends tableCloses #1337
Is this page helpful?