Deze handleiding is bedoeld voor wallet service providers en ontwikkelaars die
nieuwe key management-oplossingen willen integreren in de solana-keychain
library. Door jouw signer-implementatie toe te voegen, stel je ontwikkelaars in
staat om jouw service te gebruiken voor veilige Solana-transactieondertekening
via een uniforme interface.
Keychain houdt zijn Rust-, TypeScript-, Python- en Go-implementaties gelijk aan elkaar. PR's die alle vier bevatten hebben sterk de voorkeur. Als je er maar één kunt bijdragen, is dat prima, maar verwacht dat de andere vereist zijn voordat de signer in een release wordt opgenomen.
Gebruik je Claude Code? De
add-signer-ci-skill
richtt van de repo de CI-workflows in voor een nieuwe signer die via een fork-PR wordt aangeleverd.
Architectuuroverzicht
De bibliotheek gebruikt een architectuur op basis van traits, gedefinieerd in src/traits.rs. Elke
signer implementeert de basistrait SolanaSigner (pubkey, sign_message,
is_available) plus precies één capability-trait die past bij de opzet van de provider:
TransactionSigner(sign_transaction): de provider ondertekent de bytes van de aanroeper en de aanroeper verzendt ze zelf. De meeste backends.ModifyingSigner(modify_and_sign_transaction): de provider herschrijft de transactie en ondertekent die vervolgens zonder te verzenden.SendingSigner(sign_and_send_transaction): de provider ondertekent en verzendt aan de serverzijde.
De bibliotheek biedt ook een uniforme Signer-enum die alle
implementaties omvat, waardoor je tijdens runtime een signing-backend kunt kiezen. De enum implementeert
alleen de basistrait; capabilities zijn bereikbaar via as_transaction_signer(),
as_modifying_signer() en as_sending_signer().
Snelle Integratie Checklist
- Creëer je signer-module met implementatie
- Implementeer de basistrait
SolanaSigner(pubkey,sign_message,is_available) plus de ene capability-trait die past bij de opzet van je provider (meestalTransactionSigner) - Voeg een feature flag toe in
Cargo.toml, inclusief de interne_remote-marker als je signer reqwest gebruikt - Update de
Signer-enum insrc/lib.rs(variant,dispatch_signer!-arm en een arm in de capability-accessor die bij jouw opzet past) - Forceer HTTPS en configureer timeouts op HTTP-clients
- Onderteken de bytes van het bericht en verifieer de geretourneerde handtekening voordat je die toevoegt
- Voeg uitgebreide unittests toe (op basis van wiremock, in je module)
- Voeg een integratietestbestand toe en update
.env.example - Voeg de TypeScript-, Python- en Go-implementaties toe
- Update documentatie
- Updates van CI-workflows (stem af met de maintainers, zie Updates van CI-workflows)
- Dien PR in
Stap 1: Creëer Je Signer-Module
Maak een nieuwe directory aan onder src/ voor jouw implementatie:
src/├── your_service/│ ├── mod.rs # Main implementation with SolanaSigner trait│ └── types.rs # API request/response types (if needed)
Stap 2: Definieer Je Signer Struct
Definieer in src/your_service/mod.rs je signer struct:
//! 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()}}
Stap 3: Implementeer Constructor en Hulpmethoden
Remote signers moeten HTTPS afdwingen en HTTP-timeouts configureren. Gebruik
de gedeelde HttpClientConfig struct voor timeout-instellingen.
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))}}
Stap 4: De signer-traits implementeren
De basistrait SolanaSigner heeft 2 asynchrone methoden (sign_message,
is_available) plus pubkey(). Het ondertekenen van transacties gebeurt in een capability-trait;
de meeste providers implementeren TransactionSigner, waarvan sign_transaction
SignTransactionResult retourneert (een getagde enum die aangeeft dat de transactie volledig of
gedeeltelijk is ondertekend).
Onderteken de bytes van het bericht, nooit de volledige wire-transactie, en verifieer de
handtekening van de provider daartegen voordat je die toevoegt. Gebruik de gedeelde
TransactionUtil-helpers voor ondertekening en serialisatie.
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),))}}
Als je provider de transactie herschrijft voordat die wordt ondertekend, implementeer dan
ModifyingSigner en vervang de transactie van de aanroeper volledig door
de bytes van de provider. Als de provider aan de serverzijde verzendt, implementeer dan SendingSigner
en laat de transactie van de aanroeper ongemoeid. Implementeer nooit een capability die je
provider niet kan bieden.
Stap 5: API-types toevoegen (optioneel)
Als je API aangepaste types nodig heeft, maak dan src/your_service/types.rs
aan:
use serde::{Deserialize, Serialize};#[derive(Serialize)]pub struct SignRequest {pub wallet_id: String,pub message: String,}#[derive(Deserialize)]pub struct SignResponse {pub signature: String,}
Stap 6: Feature flag toevoegen
Update Cargo.toml om je signer als optionele feature toe te voegen. Remote backends
nemen de interne _remote-marker op, die de From<reqwest::Error>-
impl in src/error.rs activeert; zonder deze marker compileert ? op reqwest-aanroepen niet als alleen
jouw feature is ingeschakeld.
[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
Stap 7: De signer-enum updaten
Voeg je signer toe aan src/lib.rs. De basis-impl van SolanaSigner dispatcht via
de macro dispatch_signer!, dus voeg daar één arm toe. Capability-toegang zit in
de accessors as_transaction_signer(), as_modifying_signer() en as_sending_signer():
voeg een Some(s)-arm toe aan de accessor die bij jouw capability-trait past (en,
voor een verzendende of wijzigende backend, een None-arm in as_transaction_signer()).
// 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),}}}
Stap 8: Uitgebreide tests toevoegen
Voeg tests toe aan je module (onderaan src/your_service/mod.rs):
#[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());}}
Dek ook het falen van de handtekeningverificatie af: een gemockte respons waarvan de handtekening niet verifieert, moet een fout retourneren.
Stap 9: Integratietests toevoegen
Maak rust/src/tests/test_<name>_integration.rs aan. Integratietests draaien
tegen de echte service-API (niet wiremock) en zijn afgeschermd achter de
feature integration-tests. Elk bestand heeft nodig:
pub const-declaraties voor namen van omgevingsvariabelen- Een
get_signer()-helper die omgevingsvariabelen leest viadotenvy - Drie testfuncties:
test_<name>_sign_message,test_<name>_sign_transactionentest_<name>_is_available - Feature gates:
#[cfg(feature = "your_service")]op de module en#[cfg(feature = "integration-tests")]op elke test
Zie rust/src/tests/test_para_integration.rs voor een volledige referentie en
declareer je module in rust/src/tests/mod.rs:
pub mod test_your_service_integration;
Stap 10: Omgevingsvariabelen updaten
Update de .env.example in de root met de omgevingsvariabelen van je signer, volgens het
bestaande patroon:
# 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
Zet vereiste variabelen eerst, met placeholderwaarden, en zet optionele variabelen uitgecommentarieerd
met hun standaardwaarden. Elke omgevingsvariabele die in integratietests wordt gebruikt, moet in
.env.example staan, en de TypeScript-, Python- en Go-suites gebruiken dezelfde namen.
Stap 11: Documentatie updaten
Voeg je signer toe aan de tabel met ondersteunde backends in README.md:
| Backend | Gebruikssituatie | Feature flag |
|---|---|---|
| Memory | Lokale keypairs, ontwikkeling, testen | memory |
| Vault | Enterprise key management met HashiCorp Vault | vault |
| Privy | Embedded wallets met Privy-infrastructuur | privy |
| Turnkey | Non-custodial key management via Turnkey | turnkey |
| YourService | Korte beschrijving van je service | your_service |
Voeg je backend ook toe aan de tabel met signer-capabilities in de README van elke taal. Voeg een gebruiksvoorbeeld toe:
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(())}
Je integratie testen
Gebruik de just-recipes in plaats van kale cargo. De SDK-features (sdk-v2,
sdk-v3, sdk-v4) sluiten elkaar uit, dus cargo test --all-features
faalt.
# 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
Updates van CI-workflows (fork-PR's)
CI is een proces in twee fasen. Stem af met de maintainers om main voor te bereiden voordat
jouw PR kan worden getest: zij updaten fork-external-live-manual.yml, voegen omgevingsvariabelen toe
aan ci.yml en configureren de GitHub Secrets.
Je PR moet deze wijzigingen uit fase 2 bevatten naast je signer-code:
ci.yml: voeg je Rust-feature toe aan debackend-matrix (vóórall) en je integratietestfunctie aan detest-matrixtypescript-ci.yml: voeg je package toe aan de unit- en integratietestmatrices en voeg omgevingsvariabelen toe aan de integratieteststaptypescript-publish.yml: voeg je package toe aanPUBLISH_PACKAGES, depackages-array van de GitHub Release en de overzichtstabelpython-ci.yml: voeg de backend toe aan deCI_SIGNER_*-gating inresolve-signersen aan de integratiematrixgo-ci.yml: voeg de backend toe aan deCI_SIGNER_*-gating en aanmoduleBySigner
TypeScript Signer
Als je ook een TypeScript signer-package toevoegt, maak deze dan aan op
typescript/packages/your-signer/. Belangrijkste patronen:
- De factoryfunctie
createYourSigner()retourneertSolanaTransactionSigner<TAddress> & SolanaMessageSigner<TAddress>; houd de signer-klasse intern en exporteer alleen de factory en het bijbehorende configuratietype - Exporteer config-interface (
YourSignerConfig) - Als je backend geen berichten kan ondertekenen, laat dan
SolanaMessageSignerweg en stel helemaal geensignMessages-methode beschikbaar: Kit classificeert signers op basis van de aanwezigheid van methoden, dus een stub die een fout gooit zou de signer verkeerd classificeren - Dwing HTTPS af op
apiBaseUrl-configuratievelden metassertHttpsUrl()uit@solana/keychain-core - Doe remote aanroepen via
fetchSignerJson()uit@solana/keychain-core, dat foutmapping, responssanering, het weigeren van redirects en de standaardtimeout afhandelt - Saneer alle andere foutteksten van remote API's met
sanitizeRemoteErrorResponse()uit@solana/keychain-core - Gebruik
throwSignerError(SignerErrorCode.*, { cause, message })van@solana/keychain-core - Respecteer
config?.abortSignalin elke ondertekeningsmethode en geef het door aanfetchSignerJson()ensignBatchStaggered() - Valideer
requestDelayMsmetvalidateRequestDelayMs()en verwerk batches metsignBatchStaggered() - Voeg
@throwsJSDoc toe aan factory-functies met een lijst van foutcodes - Declareer
@solana/*-packages als peer dependencies op>=8.1.0 - Unittests met vitest en gemockte
fetch; integratietests metrunSignerIntegrationTestuit@solana/keychain-test-utils
Sending signers
Als je provider de transactie herschrijft of aan de serverzijde verzendt, kan de
handtekening niet op de transactie van de aanroeper worden toegepast, dus moet het een
SolanaSendingSigner zijn in plaats van een SolanaTransactionSigner:
- Implementeer
signAndSendTransactions()en stel geensignTransactionsbeschikbaar (en ook geensignMessages, tenzij de backend echt berichten ondertekent) - Respecteer
config?.abortSignal. Zodra de provider de create heeft geaccepteerd, meld dan elke fout (inclusief een abort) alsBROADCAST_UNCONFIRMEDmet het transactie-id van de provider, waarbij de abort-reden alscausebehouden blijft - Voeg een getypeerde
createKeychainSigner-overload toe die het type van je sending signer retourneert - Sluit je backend uit van
KeychainKitPluginConfigintypescript/packages/kit-plugin/src/keychain-plugin.ts, aangezien een sending signer niet kan dienen alspayerofidentityvan een Kit-client - Controleer in unittests dat
isTransactionPartialSigner()false enisTransactionSendingSigner()true is voor je instanties
Update Overkoepelend Pakket
Update typescript/packages/keychain/ (7 bestanden om te wijzigen):
src/types.ts: voegYourSignerConfigtoe aan de discriminated unionKeychainSignerConfigsrc/create-keychain-signer.ts: voeg de switch case toe (elk backend-package wordt geladen met een dynamischeimport())src/resolve-address.ts: voeg toe aan de switch case van het fast-path of fetch-pathsrc/index.ts: voeg de exports van het configuratietype, de namespace en de factoryfunctie toepackage.json: voeg de dependency@solana/keychain-your-signer: "workspace:*"toetsconfig.json: voeg de verwijzing{ "path": "../your-signer" }toetypescript/scripts/test-treeshake-umbrella.mjs: voeg je package toe aanSIGNER_MARKERSen je factory aanFACTORIES
De switch-statements hebben exhaustive never-checks, dus TypeScript geeft een foutmelding
als je iets aan de union toevoegt maar een case mist. Draai daarna just ts-treeshake:
elk package en de umbrella moeten tree-shakable blijven.
Python-signer
Maak de module aan op python/src/solana_keychain/your_signer/ (__init__.py
plus signer.py). Belangrijke patronen:
- Maak een subklasse van
SolanaSigneruitsolana_keychain.core(pubkey-property,async sign_message(),async is_available()) plus precies één capability- klasse:TransactionSigner(async sign_transaction()),ModifyingSigner(async modify_and_sign_transaction()) ofSendingSigner(async sign_and_send_transaction()) - Exporteer een
async create_your_signer()-factory dieinit()afwacht als de backend zijn adres remote opvraagt; vóór init geeft ondertekenen de foutSignerErrorCode.NOT_INITIALIZED - Exporteer een configuratie-dataclass (
YourSignerConfig) waarbij geheimen zijn gemarkeerd metfield(repr=False) - Dwing HTTPS af op base-URL-velden met
assert_https_url()en leid elk verzoek viafetch_signer_json() - Verifieer de handtekening van de provider voordat je die retourneert; gebruik
signed_message_bytes()voor de bytes die een handtekening dekt, nooit een zelfgemaakt versieprefix - Registreer de backend in
_BACKENDSinpython/src/solana_keychain/keychain.py - Als de backend een provider-SDK nodig heeft, bescherm dan de import, geef een
ImportErrorweer metpip install 'solana-keychain[your-signer]', declareer de extra inpython/pyproject.tomlen importeer de backend nooit direct vanuit de root van het pakket - Unittests met pytest:
respxvoor HTTP-backends, stub-clients voor SDK-backends en een controle dat er geen geheim voorkomt instr()ofrepr()van opgeworpen fouten - Voeg een
make_your_signer()-factory toe aanpython/tests/integration/test_live_signers.pyen registreer deze in_MESSAGE_CAPABLE(of_TRANSACTION_ONLYvoor backends zondersign_message)
Go-signer
Elke Go-backend is een eigen module, in go/signers/yoursigner/ met modulepad
github.com/solana-foundation/solana-keychain/go/signers/yoursigner/v2. Zie
go/signers/para/ voor een referentie. Belangrijkste patronen:
- Implementeer
core.SolanaSigner(Pubkey(),SignMessage(ctx, message),IsAvailable(ctx)) plus precies één capability-interface:core.TransactionSigner,core.ModifyingSignerofcore.SendingSigner. De capability wordt gedetecteerd via type assertion, dus een backend die geen transactie kan ondertekenen mag helemaal geenSignTransaction-methode hebben - Exporteer een
Config-struct en eenNew-constructor (New(ctx, cfg)wanneer de backend zijn adres extern ophaalt; geef daarvoorcore.NewNotInitializedErrorterug) - Bouw HTTP-clients met
core.NewHTTPClientofcore.ResolveHTTPClienten valideer basis-URL's metcore.NormalizeHTTPSBaseURL - Onderteken transacties met
core.SignTransactionWith, dat de bytes van het bericht ondertekent en de handtekening toevoegt; verifieer handtekeningen van de provider metcore.VerifySignature - Geef fouten terug met
core.NewSignerError/core.WrapSignerErroren stabiele codes zoalscore.CodeSigningFailed - Unittests met
httptest; integratietests inintegration_test.goachter de tag//go:build integration
Indiening Checklist
Voordat je je PR indient:
- Code compileert zonder waarschuwingen (
just build) - Alle tests slagen (
just test) - Code is geformatteerd/linting slaagt (
just fmt) - Geen hardcoded waarden of secrets in code
- Foutmeldingen zijn generiek (geen ruwe API-response tekst)
- Geen
.expect()of.unwrap()op onbetrouwbare API-responses - HTTPS afgedwongen op externe HTTP-clients
- HTTP-timeouts geconfigureerd via
HttpClientConfig - Geretourneerde handtekeningen geverifieerd tegen lokaal berekende berichtbytes
- Volgt de naamconventies (snake_case voor Rust en Python, camelCase voor TypeScript)
- Integratietests toegevoegd en
.env.examplebijgewerkt - TypeScript-, Python- en Go-implementaties inbegrepen (of gepland met maintainers)
- Overkoepelende pakketten bijgewerkt (TypeScript-umbrella, Python
_BACKENDS) - CI-wijzigingen inbegrepen
- Toegevoegd aan de tabellen met ondersteunde backends en signer-capabilities in README.md
Implementatietips
Foutafhandeling
Gebruik altijd de bestaande SignerError-varianten. Zowel Display als Debug op
SignerError zijn geredigeerd, houd berichten dus algemeen en neem nooit ruwe API-
responsdata op. Gebruik nooit .expect() of .unwrap() op onbetrouwbare API-responses:
// 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}")))?;
Beveiligingsvereisten
Dit zijn geen suggesties: gebruikers kiezen een backend op basis hiervan. Als
uw provider er een onmogelijk maakt, vermeld dit dan in de PR en in de
docs/SECURITY_MODEL.md van de repo.
- Verifieer wat de provider heeft ondertekend. Bereken de berichtbytes lokaal en verifieer de geretourneerde handtekening ertegen op de vereiste positie van uw signer voordat u deze toevoegt. Een handtekening die niet verifieert moet mislukken en mag nooit worden toegevoegd.
- Geef aan of de provider broadcast. Als de provider server-side uitvoert, implementeer dan de sending-variant en geef de handtekening terug die de verwerkte transactie identificeert. Als de provider geen sign-only-endpoint heeft, maak de backend dan sending-only in plaats van achter de rug van de aanroeper om te versturen.
- Laat de aanroeper nooit achter met een transactie die de handtekening niet dekt. Een provider die de transactie herschrijft, ondertekent zijn eigen bytes: laat ofwel de transactie van de aanroeper ongemoeid (het geval met beheerde broadcast), of hanteer de modifying-variant en vervang de transactie volledig door de bytes van de provider.
- Meld onzekerheid over de broadcast als zodanig. Een fout na het versturen kan nog steeds
zijn verwerkt. Geef
BROADCAST_UNCONFIRMEDterug in plaats van een algemene fout, neem de transactie-id van de provider mee wanneer u die hebt, en stuur bij elke create een van het bericht afgeleide idempotency-sleutel mee, zodat een byte-identieke herhaling veilig is. - Onderteken nooit vóór init. Gebruik
Option<Pubkey>(nietPubkey::default()) voor de publieke sleutel vóórinit()en laat het mislukken met een configuratiefout als er eerder wordt geprobeerd te ondertekenen. - Log nooit gevoelige gegevens (privésleutels, API-geheimen, ruwe API-responses) en
gebruik een
Debug-implementatie die gevoelige velden verbergt - Dwing HTTPS af (
https_only(true), afgeschermd met#[cfg(not(test))]voor wiremock) en configureer request- en connect-timeouts viaHttpClientConfig(standaard: 30s/5s) - Zeroize tussenliggend sleutelmateriaal met
zeroize::Zeroizing<Vec<u8>>
Testen met Mocks
Gebruik wiremock voor het mocken van HTTP-API's. Assert alleen op het
fouttype, niet op de fouttekst:
#[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}}
Hulp krijgen
- Bekijk bestaande signer-implementaties voor patronen:
src/memory/mod.rs: Eenvoudig, synchroonsrc/privy/mod.rs: Vereist initialisatiesrc/turnkey/mod.rs: Complexe afhandeling van handtekeningensrc/vault/mod.rs: Externe clientbibliotheektypescript/packages/para/,python/src/solana_keychain/para/engo/signers/para/: volledige referenties in de andere talen
- Belangrijke bestanden:
src/traits.rs(trait-definities),src/transaction_util.rsensrc/signature_util.rs(gedeelde hulpfuncties),src/http_client_config.rs(timeout- configuratie) - De volledige repositorygids: docs/ADDING_SIGNERS.md
- Open een issue voor ontwerpbesprekingen voordat je aan het werk gaat
Voorbeeld PR-structuur
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?