Yeni İmzalayıcılar Ekleme

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
  • SolanaSigner temel trait'ini (pubkey, sign_message, is_available) ve sağlayıcınızın yapısına uyan tek bir yetenek trait'ini (genellikle TransactionSigner) uygulayın
  • Cargo.toml içine bir özellik bayrağı ekleyin; signer'ınız reqwest kullanıyorsa dahili _remote işaretini de dahil edin
  • src/lib.rs içindeki Signer enum'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.example dosyası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 integration
use 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 API
async 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 text
if !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 feature
all = ["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 method
impl 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_available
macro_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 shape
impl 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 const bildirimleri
  • Ortam değişkenlerini dotenvy ile okuyan bir get_signer() yardımcısı
  • Üç test fonksiyonu: test_<name>_sign_message, test_<name>_sign_transaction ve test_<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-key
YOUR_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ğı
MemoryYerel keypair'ler, geliştirme, testmemory
VaultHashiCorp Vault ile kurumsal anahtar yönetimivault
PrivyPrivy altyapısı ile gömülü cüzdanlarprivy
TurnkeyTurnkey aracılığıyla emanetsiz anahtar yönetimiturnkey
YourServiceHizmetinizin 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 matrices
just 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ğinizi backend matrisine (all'dan önce) ve entegrasyon test fonksiyonunuzu test matrisine ekleyin
  • typescript-ci.yml: paketinizi birim ve entegrasyon test matrislerine ekleyin ve entegrasyon testi adımına ortam değişkenlerini ekleyin
  • typescript-publish.yml: paketinizi PUBLISH_PACKAGES'a, GitHub Release packages dizisine ve özet tablosuna ekleyin
  • python-ci.yml: arka ucu resolve-signers içindeki CI_SIGNER_* kapılamasına ve entegrasyon matrisine ekleyin
  • go-ci.yml: arka ucu CI_SIGNER_* kapılamasına ve moduleBySigner'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 fonksiyonu SolanaTransactionSigner<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çbir signMessages metodu 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
  • apiBaseUrl yapılandırma alanlarında @solana/keychain-core içindeki assertHttpsUrl() ile HTTPS'i zorunlu kılın
  • Uzak çağrıları @solana/keychain-core içindeki fetchSignerJson() 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-core içindeki sanitizeRemoteErrorResponse() ile temizleyin
  • @solana/keychain-core paketinden throwSignerError(SignerErrorCode.*, { cause, message }) kullan
  • Her imzalama metodunda config?.abortSignal değerine uyun ve bunu fetchSignerJson() ile signBatchStaggered() içine iletin
  • requestDelayMs değerini validateRequestDelayMs() ile doğrulayın ve toplu işlemleri signBatchStaggered() ile yapın
  • Hata kodlarını listeleyen fabrika fonksiyonlarına @throws JSDoc ekle
  • @solana/* paketlerini >=8.1.0 sürümünde peer dependency olarak bildirin
  • vitest ve sahte (mock) fetch ile birim testleri; @solana/keychain-test-utils içindeki runSignerIntegrationTest ile 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çbir signTransactions sunmayın (arka uç gerçekten mesaj imzalamıyorsa signMessages de sunmayın)
  • config?.abortSignal değerine uyun. Sağlayıcı oluşturma isteğini kabul ettikten sonra, her hatayı (iptal dahil) sağlayıcı işlem kimliğini taşıyan BROADCAST_UNCONFIRMED olarak bildirin ve iptal nedenini cause olarak saklayın
  • Sending-signer tipinizi döndüren, tipli bir createKeychainSigner aşırı yüklemesi (overload) ekleyin
  • Arka ucunuzu typescript/packages/kit-plugin/src/keychain-plugin.ts içindeki KeychainKitPluginConfig'den çıkarın; çünkü sending signer, bir Kit istemcisinde payer veya identity olarak kullanılamaz
  • Birim testlerinde, örnekleriniz için isTransactionPartialSigner() değerinin false ve isTransactionSendingSigner() değerinin true olduğunu doğrulayın

Şemsiye Paketini Güncelle

typescript/packages/keychain/ dizinini güncelleyin (değiştirilecek 7 dosya):

  1. src/types.ts: YourSignerConfig tipini KeychainSignerConfig ayrımlı birleşimine (discriminated union) ekleyin
  2. src/create-keychain-signer.ts: switch case'i ekleyin (her arka uç paketi dinamik bir import() ile yüklenir)
  3. src/resolve-address.ts: hızlı yol (fast-path) veya getirme yolu (fetch-path) switch case'ine ekleyin
  4. src/index.ts: yapılandırma tipi, namespace ve fabrika fonksiyonu dışa aktarımlarını ekleyin
  5. package.json: @solana/keychain-your-signer: "workspace:*" bağımlılığını ekleyin
  6. tsconfig.json: { "path": "../your-signer" } referansını ekleyin
  7. typescript/scripts/test-treeshake-umbrella.mjs: paketinizi SIGNER_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.core içinden SolanaSigner sı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()) veya SendingSigner (async sign_and_send_transaction())
  • Arka uç adresini uzaktan çözümlüyorsa init() çağrısını bekleyen (await) bir async create_your_signer() fabrikası dışa aktarın; init öncesinde imzalama SignerErrorCode.NOT_INITIALIZED hatası 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ği fetch_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.py içindeki _BACKENDS iç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 bir ImportError fırlatın, ekstrayı python/pyproject.toml iç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ın str() veya repr() çıktısında hiçbir gizli bilginin görünmediğini doğrulayan bir kontrol
  • python/tests/integration/test_live_signers.py dosyasına bir make_your_signer() fabrika fonksiyonu ekleyin ve bunu _MESSAGE_CAPABLE içine kaydedin (sign_message desteğ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.SolanaSigner arayüzünü (Pubkey(), SignMessage(ctx, message), IsAvailable(ctx)) ve yetenek arayüzlerinden tam olarak birini uygulayın: core.TransactionSigner, core.ModifyingSigner veya core.SendingSigner. Yetenek, tür doğrulamasıyla (type assertion) tespit edilir; dolayısıyla bir işlemi imzalayamayan arka uç hiçbir SignTransaction metodu içermemelidir
  • Bir Config struct'ı ve bir New yapıcısı dışa aktarın (arka uç adresini uzaktan çözümlüyorsa New(ctx, cfg); bundan önce core.NewNotInitializedError döndürün)
  • HTTP istemcilerini core.NewHTTPClient veya core.ResolveHTTPClient ile oluşturun ve temel URL'leri core.NormalizeHTTPSBaseURL ile doğrulayın
  • İşlemleri, mesaj baytlarını imzalayıp imzayı ekleyen core.SignTransactionWith ile imzalayın; sağlayıcı imzalarını core.VerifySignature ile doğrulayın
  • Hataları core.NewSignerError / core.WrapSignerError ve core.CodeSigningFailed gibi kararlı kodlarla döndürün
  • httptest ile birim testleri; //go:build integration etiketinin arkasında integration_test.go iç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.example gü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 message
return Err(SignerError::remote_api(format!(
"YourService API returned status {status}"
)));
// Good: converts from standard errors
let 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_UNCONFIRMED dö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çin Pubkey::default() yerine Option<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 Debug implementasyonu 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, senkron
    • src/privy/mod.rs: Başlatma gerektirir
    • src/turnkey/mod.rs: Karmaşık imza işleme
    • src/vault/mod.rs: Harici istemci kütüphanesi
    • typescript/packages/para/, python/src/solana_keychain/para/ ve go/signers/para/: diğer dillerdeki eksiksiz referanslar
  • Temel dosyalar: src/traits.rs (trait tanımları), src/transaction_util.rs ve src/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 integration
Adds 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 table
Closes #1337

Is this page helpful?

© 2026 Solana Vakfı. Tüm hakları saklıdır.