Uusien allekirjoittajien lisääminen

Tämä opas on tarkoitettu lompakopalveluntarjoajille ja kehittäjille, jotka haluavat integroida uusia avaintenhallintaratkaisuja solana-keychain-kirjastoon. Lisäämällä oman allekirjoittajatoteutuksesi mahdollistat kehittäjille palvelusi käytön turvalliseen Solana-transaktioiden allekirjoitukseen yhtenäisen rajapinnan kautta.

Keychain pitää Rust-, TypeScript-, Python- ja Go-toteutuksensa toiminnallisesti yhtenevinä. PR:t, jotka sisältävät kaikki neljä, ovat ehdottomasti suositeltavia. Jos voit toteuttaa vain yhden, se sopii, mutta odota, että muut vaaditaan ennen kuin allekirjoittaja julkaistaan versiossa.

Käytätkö Claude Codea? Repositorion add-signer-ci-skill määrittää CI-työnkulut uudelle forkki-PR:n kautta lisätylle allekirjoittajalle.

Arkkitehtuurin yleiskatsaus

Kirjasto käyttää trait-pohjaista arkkitehtuuria, joka on määritelty tiedostossa src/traits.rs. Jokainen allekirjoittaja toteuttaa SolanaSigner-perustraitin (pubkey, sign_message, is_available) sekä täsmälleen yhden palveluntarjoajan muotoa vastaavan ominaisuustraitin:

  • TransactionSigner (sign_transaction): palveluntarjoaja allekirjoittaa kutsujan tavut ja kutsuja lähettää transaktion verkkoon. Useimmat taustajärjestelmät.
  • ModifyingSigner (modify_and_sign_transaction): palveluntarjoaja kirjoittaa transaktion uudelleen ja allekirjoittaa sen lähettämättä sitä verkkoon.
  • SendingSigner (sign_and_send_transaction): palveluntarjoaja allekirjoittaa ja lähettää transaktion verkkoon palvelinpuolella.

Kirjasto tarjoaa myös yhtenäisen Signer-enumin, joka kapseloi kaikki toteutukset ja mahdollistaa allekirjoitustaustajärjestelmien valinnan ajon aikana. Se toteuttaa vain perustraitin; ominaisuuksiin päästään käsiksi metodeilla as_transaction_signer(), as_modifying_signer() ja as_sending_signer().

Pikaintegroinnin tarkistuslista

  • Luo allekirjoittajamoduulisi toteutuksineen
  • Toteuta SolanaSigner-perustraiti (pubkey, sign_message, is_available) sekä yksi palveluntarjoajasi muotoa vastaava ominaisuustraiti (yleensä TransactionSigner)
  • Lisää ominaisuuslippu tiedostoon Cargo.toml, mukaan lukien sisäinen _remote-merkki, jos allekirjoittajasi käyttää reqwestiä
  • Päivitä Signer-enum tiedostossa src/lib.rs (variantti, dispatch_signer!-haara ja haara ominaisuuksien käyttömetodissa, joka vastaa muotoasi)
  • Pakota HTTPS ja konfiguroi HTTP-asiakkaiden aikakatkaisut
  • Allekirjoita viestin tavut ja varmista palautettu allekirjoitus ennen sen liittämistä
  • Lisää kattavat yksikkötestit (wiremock-pohjaiset, omassa moduulissasi)
  • Lisää integraatiotestitiedosto ja päivitä .env.example
  • Lisää TypeScript-, Python- ja Go-toteutukset
  • Päivitä dokumentaatio
  • CI-työnkulun päivitykset (sovi ylläpitäjien kanssa, katso CI-työnkulun päivitykset)
  • Lähetä PR

Vaihe 1: Luo allekirjoittajamoduulisi

Luo uusi hakemisto src/-hakemiston alle toteutuksellesi:

src/
├── your_service/
│ ├── mod.rs # Main implementation with SolanaSigner trait
│ └── types.rs # API request/response types (if needed)

Vaihe 2: Määritä allekirjoittaja-struct

Määritä src/your_service/mod.rs-tiedostossa allekirjoittaja-struct:

//! 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()
}
}

Vaihe 3: Toteuta konstruktori ja apumetodit

Etäallekirjoittajien on pakko pakottaa HTTPS ja konfiguroida HTTP-aikakatkaisut. Käytä jaettua HttpClientConfig-structia aikakatkaisuasetusten hallintaan.

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))
}
}

Vaihe 4: Toteuta allekirjoittajan traitit

SolanaSigner-perustraitilla on 2 asynkronista metodia (sign_message, is_available) sekä pubkey(). Transaktioiden allekirjoitus kuuluu ominaisuustraitiin; useimmat palveluntarjoajat toteuttavat TransactionSigner-traitin, jonka sign_transaction palauttaa SignTransactionResult-arvon (tagattu enum, joka merkitsee transaktion täysin tai osittain allekirjoitetuksi).

Allekirjoita viestin tavut, älä koskaan koko wire-transaktiota, ja varmista palveluntarjoajan allekirjoitus niitä vasten ennen sen liittämistä. Käytä yhteisiä TransactionUtil-apufunktioita allekirjoitukseen ja serialisointiin.

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),
))
}
}

Jos palveluntarjoajasi kirjoittaa transaktion uudelleen ennen allekirjoitusta, toteuta sen sijaan ModifyingSigner ja korvaa kutsujan transaktio kokonaisuudessaan palveluntarjoajan tavuilla. Jos se lähettää transaktion verkkoon palvelinpuolella, toteuta SendingSigner ja jätä kutsujan transaktio koskemattomaksi. Älä koskaan toteuta ominaisuutta, jota palveluntarjoajasi ei pysty tarjoamaan.

Vaihe 5: Lisää API-tyypit (valinnainen)

Jos API:si tarvitsee mukautettuja tyyppejä, luo src/your_service/types.rs:

use serde::{Deserialize, Serialize};
#[derive(Serialize)]
pub struct SignRequest {
pub wallet_id: String,
pub message: String,
}
#[derive(Deserialize)]
pub struct SignResponse {
pub signature: String,
}

Vaihe 6: Lisää ominaisuuslippu

Päivitä Cargo.toml lisäämällä allekirjoittajasi valinnaiseksi ominaisuudeksi. Etätaustajärjestelmät sisältävät sisäisen _remote-merkin, joka rajaa From<reqwest::Error>- toteutuksen tiedostossa src/error.rs; ilman sitä ? reqwest-kutsuissa ei käänny, kun vain sinun ominaisuutesi on käytössä.

[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

Vaihe 7: Päivitä allekirjoittaja-enum

Lisää allekirjoittajasi tiedostoon src/lib.rs. Perus-SolanaSigner-toteutus ohjaa kutsut dispatch_signer!-makron kautta, joten lisää sinne yksi haara. Ominaisuuksien käyttö sijaitsee metodeissa as_transaction_signer(), as_modifying_signer() ja as_sending_signer(): lisää Some(s)-haara sille, joka vastaa ominaisuustraitiasi (ja lähettävälle tai muokkaavalle taustajärjestelmälle None-haara metodiin 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 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),
}
}
}

Vaihe 8: Lisää kattavat testit

Lisää testit moduuliisi (src/your_service/mod.rs-tiedoston loppuun):

#[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());
}
}

Kata myös allekirjoituksen varmennuksen epäonnistuminen: mockatun vastauksen, jonka allekirjoitus ei kelpaa, on palautettava virhe.

Vaihe 9: Lisää integraatiotestit

Luo rust/src/tests/test_<name>_integration.rs. Integraatiotestit suoritetaan todellista palvelun API:a vasten (ei wiremockia), ja ne on rajattu integration-tests-ominaisuuden taakse. Jokainen tiedosto tarvitsee:

  • pub const -määritykset ympäristömuuttujien nimille
  • get_signer()-apufunktion, joka lukee ympäristömuuttujat dotenvy-kirjaston avulla
  • Kolme testifunktiota: test_<name>_sign_message, test_<name>_sign_transaction ja test_<name>_is_available
  • Ominaisuusrajaukset: #[cfg(feature = "your_service")] moduulissa ja #[cfg(feature = "integration-tests")] jokaisessa testissä

Katso täydellinen esimerkki tiedostosta rust/src/tests/test_para_integration.rs ja määrittele moduulisi tiedostossa rust/src/tests/mod.rs:

pub mod test_your_service_integration;

Vaihe 10: Päivitä ympäristömuuttujat

Päivitä juuren .env.example allekirjoittajasi ympäristömuuttujilla noudattaen olemassa olevaa mallia:

# 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

Listaa pakolliset muuttujat ensin paikkamerkkiarvoilla ja valinnaiset muuttujat kommentoituina oletusarvoineen. Jokaisen integraatiotesteissä käytetyn ympäristömuuttujan on oltava tiedostossa .env.example, ja TypeScript-, Python- ja Go-testisarjat käyttävät samoja nimiä.

Vaihe 11: Päivitä dokumentaatio

Lisää allekirjoittajasi tuettujen taustajärjestelmien taulukkoon README.md-tiedostossa:

TaustajärjestelmäKäyttötarkoitusOminaisuuslippu
MemoryPaikalliset avainparit, kehitys, testausmemory
VaultYritystason avainten hallinta HashiCorp Vaultillavault
PrivyUpotetut lompakot Privy-infrastruktuurillaprivy
TurnkeyEi-säilytettävä avainten hallinta Turnkey-palvelun kauttaturnkey
YourServiceLyhyt kuvaus palvelustasiyour_service

Lisää taustajärjestelmäsi myös jokaisen kielen README-tiedoston allekirjoittajaominaisuuksien taulukkoon. Lisää käyttöesimerkki:

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(())
}

Integraation testaaminen

Käytä just-reseptejä raa'an cargon sijaan. SDK-ominaisuudet (sdk-v2, sdk-v3, sdk-v4) ovat toisensa poissulkevia, joten cargo test --all-features epäonnistuu.

# 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-työnkulun päivitykset (forkki-PR:t)

CI on kaksivaiheinen prosessi. Sovi ylläpitäjien kanssa main-haaran valmistelusta, ennen kuin PR:äsi voidaan testata: he päivittävät fork-external-live-manual.yml-tiedoston, lisäävät ympäristömuuttujat tiedostoon ci.yml ja määrittävät GitHub Secretsit.

PR:säsi on sisällettävä nämä vaiheen 2 muutokset allekirjoittajakoodisi rinnalla:

  • ci.yml: lisää Rust-ominaisuutesi backend-matriisiin (ennen all-arvoa) ja integraatiotestifunktiosi test-matriisiin
  • typescript-ci.yml: lisää pakettisi yksikkö- ja integraatiotestimatriiseihin ja lisää ympäristömuuttujat integraatiotestivaiheeseen
  • typescript-publish.yml: lisää pakettisi PUBLISH_PACKAGES-kohtaan, GitHub Releasen packages-taulukkoon ja yhteenvetotaulukkoon
  • python-ci.yml: lisää taustajärjestelmä resolve-signers-vaiheen CI_SIGNER_*-rajaukseen ja integraatiomatriisiin
  • go-ci.yml: lisää taustajärjestelmä CI_SIGNER_*-rajaukseen ja kohtaan moduleBySigner

TypeScript-allekirjoittaja

Jos lisäät myös TypeScript-allekirjoittajapaketin, luo se sijaintiin typescript/packages/your-signer/. Keskeiset mallit:

  • Tehdasfunktio createYourSigner() palauttaa SolanaTransactionSigner<TAddress> & SolanaMessageSigner<TAddress>; pidä allekirjoittajaluokka sisäisenä ja vie vain tehdasfunktio ja sen konfiguraatiotyyppi
  • Vie config-rajapinta (YourSignerConfig)
  • Jos taustajärjestelmäsi ei pysty allekirjoittamaan viestejä, jätä SolanaMessageSigner pois äläkä tarjoa signMessages-metodia lainkaan: Kit luokittelee allekirjoittajat metodin olemassaolon perusteella, joten virheen heittävä tynkä luokittelisi allekirjoittajan väärin
  • Pakota HTTPS apiBaseUrl-konfiguraatiokentille käyttämällä assertHttpsUrl()-funktiota paketista @solana/keychain-core
  • Tee etäkutsut fetchSignerJson()-funktiolla paketista @solana/keychain-core, joka hoitaa virheiden kartoituksen, vastausten puhdistuksen, uudelleenohjausten hylkäämisen ja oletusaikakatkaisun
  • Puhdista kaikki muu etä-API:n virheteksti sanitizeRemoteErrorResponse()-funktiolla paketista @solana/keychain-core
  • Käytä throwSignerError(SignerErrorCode.*, { cause, message }) moduulista @solana/keychain-core
  • Noudata config?.abortSignal-arvoa jokaisessa allekirjoitusmetodissa ja välitä se funktioille fetchSignerJson() ja signBatchStaggered()
  • Validoi requestDelayMs funktiolla validateRequestDelayMs() ja käsittele erät funktiolla signBatchStaggered()
  • Lisää @throws JSDoc tehdas-funktioihin listaten virhekoodit
  • Määritä @solana/*-paketit peer-riippuvuuksiksi versiosta >=8.1.0
  • Yksikkötestit vitestillä ja mockatulla fetch-funktiolla; integraatiotestit funktiolla runSignerIntegrationTest paketista @solana/keychain-test-utils

Lähettävät allekirjoittajat

Jos palveluntarjoajasi kirjoittaa transaktion uudelleen tai lähettää sen verkkoon palvelinpuolella, sen allekirjoitusta ei voi soveltaa kutsujan transaktioon, joten sen on oltava SolanaSendingSigner eikä SolanaTransactionSigner:

  • Toteuta signAndSendTransactions() äläkä tarjoa lainkaan signTransactions-metodia (eikä signMessages-metodia, ellei taustajärjestelmä todella allekirjoita viestejä)
  • Noudata config?.abortSignal-arvoa. Kun palveluntarjoaja on hyväksynyt luontipyynnön, raportoi kaikki virheet (myös keskeytys) arvolla BROADCAST_UNCONFIRMED, joka sisältää palveluntarjoajan transaktiotunnuksen, ja säilytä keskeytyksen syy arvossa cause
  • Lisää tyypitetty createKeychainSigner-ylikuormitus, joka palauttaa lähettävän allekirjoittajatyyppisi
  • Sulje taustajärjestelmäsi pois tyypistä KeychainKitPluginConfig tiedostossa typescript/packages/kit-plugin/src/keychain-plugin.ts, koska lähettävä allekirjoittaja ei voi toimia Kit-asiakkaan payer- tai identity-roolissa
  • Varmista yksikkötesteissä, että isTransactionPartialSigner() on false ja isTransactionSendingSigner() on true instansseillesi

Päivitä Kattopaketti

Päivitä typescript/packages/keychain/ (7 muokattavaa tiedostoa):

  1. src/types.ts: Lisää YourSignerConfig KeychainSignerConfig- erotteluunioniin
  2. src/create-keychain-signer.ts: Lisää switch-case (jokainen taustajärjestelmäpaketti ladataan dynaamisella import()-kutsulla)
  3. src/resolve-address.ts: Lisää pikapolun tai hakupolun switch-caseen
  4. src/index.ts: Lisää konfiguraatiotyypin, nimiavaruuden ja tehdasfunktion viennit
  5. package.json: Lisää riippuvuus @solana/keychain-your-signer: "workspace:*"
  6. tsconfig.json: Lisää viittaus { "path": "../your-signer" }
  7. typescript/scripts/test-treeshake-umbrella.mjs: Lisää pakettisi kohtaan SIGNER_MARKERS ja tehdasfunktiosi kohtaan FACTORIES

Switch-lauseissa on tyhjentävät never-tarkistukset, joten TypeScript antaa virheen, jos lisäät unioniin mutta unohdat jonkin casen. Suorita sen jälkeen just ts-treeshake: jokaisen paketin ja kattopaketin on pysyttävä tree-shake-yhteensopivina.

Python-allekirjoittaja

Luo moduuli polkuun python/src/solana_keychain/your_signer/ (__init__.py ja signer.py). Keskeiset mallit:

  • Periytä SolanaSigner paketista solana_keychain.core (pubkey-ominaisuus, async sign_message(), async is_available()) sekä täsmälleen yksi ominaisuus- luokka: TransactionSigner (async sign_transaction()), ModifyingSigner (async modify_and_sign_transaction()) tai SendingSigner (async sign_and_send_transaction())
  • Vie async create_your_signer() -tehdasfunktio, joka odottaa init()-kutsun, kun taustajärjestelmä selvittää osoitteensa etänä; ennen alustusta allekirjoitus nostaa virheen SignerErrorCode.NOT_INITIALIZED
  • Vie konfiguraatiodataluokka (YourSignerConfig), jossa salaisuudet on merkitty field(repr=False)
  • Pakota HTTPS perus-URL-kentille funktiolla assert_https_url() ja ohjaa jokainen pyyntö fetch_signer_json()-funktion kautta
  • Varmista palveluntarjoajan allekirjoitus ennen sen palauttamista; käytä signed_message_bytes()-funktiota allekirjoituksen kattamille tavuille, älä koskaan käsin rakennettua versioprefiksiä
  • Rekisteröi taustajärjestelmä kohtaan _BACKENDS tiedostossa python/src/solana_keychain/keychain.py
  • Jos taustajärjestelmä tarvitsee palveluntarjoajan SDK:n, suojaa tuonti, nosta ImportError, jossa mainitaan pip install 'solana-keychain[your-signer]', ilmoita lisäpaketti tiedostossa python/pyproject.toml, äläkä koskaan tuo taustajärjestelmää ennenaikaisesti paketin juuritasolta
  • Yksikkötestit pytestillä: respx HTTP-taustajärjestelmille, stub-asiakkaat SDK-taustajärjestelmille sekä tarkistus, ettei mikään salaisuus näy nostettujen virheiden str()- tai repr()-tulosteessa
  • Lisää make_your_signer()-tehdasfunktio tiedostoon python/tests/integration/test_live_signers.py ja rekisteröi se kohtaan _MESSAGE_CAPABLE (tai _TRANSACTION_ONLY taustajärjestelmille, joilla ei ole sign_message-metodia)

Go-allekirjoittaja

Jokainen Go-taustajärjestelmä on oma moduulinsa polussa go/signers/yoursigner/, jonka moduulipolku on github.com/solana-foundation/solana-keychain/go/signers/yoursigner/v2. Katso viitteeksi go/signers/para/. Keskeiset mallit:

  • Toteuta core.SolanaSigner (Pubkey(), SignMessage(ctx, message), IsAvailable(ctx)) sekä täsmälleen yksi ominaisuusrajapinta: core.TransactionSigner, core.ModifyingSigner tai core.SendingSigner. Ominaisuus tunnistetaan tyyppivarmennuksella (type assertion), joten taustajärjestelmällä, joka ei voi allekirjoittaa transaktiota, ei saa olla lainkaan SignTransaction-metodia
  • Vie Config-rakenne ja New-konstruktori (New(ctx, cfg), kun taustajärjestelmä selvittää osoitteensa etänä; sitä ennen palauta core.NewNotInitializedError)
  • Rakenna HTTP-asiakkaat funktiolla core.NewHTTPClient tai core.ResolveHTTPClient ja tarkista perus-URL-osoitteet funktiolla core.NormalizeHTTPSBaseURL
  • Allekirjoita transaktiot funktiolla core.SignTransactionWith, joka allekirjoittaa viestin tavut ja liittää allekirjoituksen; varmista palveluntarjoajan allekirjoitukset funktiolla core.VerifySignature
  • Palauta virheet funktioilla core.NewSignerError / core.WrapSignerError ja vakailla koodeilla, kuten core.CodeSigningFailed
  • Yksikkötestit httptest-paketilla; integraatiotestit tiedostossa integration_test.go tagin //go:build integration takana

Lähetyksen Tarkistuslista

Ennen PR:n lähettämistä:

  • Koodi kääntyy ilman varoituksia (just build)
  • Kaikki testit menevät läpi (just test)
  • Koodi on muotoiltu/linting menee läpi (just fmt)
  • Ei kovakoodattuja arvoja tai salaisuuksia koodissa
  • Virheilmoitukset ovat yleisiä (ei raakaa API-vastaustekstiä)
  • Ei .expect()- tai .unwrap()-kutsuja epäluotettaville API-vastauksille
  • HTTPS pakotettu etä-HTTP-asiakkaissa
  • HTTP-aikakatkaisut määritetty HttpClientConfig kautta
  • Palautetut allekirjoitukset on varmennettu paikallisesti laskettuja viestin tavuja vasten
  • Noudattaa nimeämiskäytäntöjä (snake_case Rustille ja Pythonille, camelCase TypeScriptille)
  • Integraatiotestit lisätty ja .env.example päivitetty
  • TypeScript-, Python- ja Go-toteutukset mukana (tai suunniteltu ylläpitäjien kanssa)
  • Kattopaketit päivitetty (TypeScript-kattopaketti, Pythonin _BACKENDS)
  • CI-muutokset mukana
  • Lisätty README.md-tiedoston tuettujen taustajärjestelmien ja allekirjoitusominaisuuksien taulukoihin

Toteutusvinkkejä

Virheenkäsittely

Käytä aina olemassa olevia SignerError-variantteja. Sekä Display että Debug SignerError-tyypillä on häivytetty, joten pidä viestit yleisinä äläkä koskaan sisällytä raakaa API- vastausdataa. Älä koskaan käytä .expect()- tai .unwrap()-kutsua epäluotettaville API-vastauksille:

// 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}")))?;

Tietoturvavaatimukset

Nämä eivät ole ehdotuksia: käyttäjät valitsevat taustajärjestelmän niiden perusteella. Jos palveluntarjoajasi tekee jostakin niistä mahdotonta, kerro siitä PR:ssä ja repositorion docs/SECURITY_MODEL.md-tiedostossa.

  • Varmista, mitä palveluntarjoaja allekirjoitti. Laske viestin tavut paikallisesti ja varmista palautettu allekirjoitus niitä vasten allekirjoittajasi vaatimassa kohdassa ennen sen liittämistä. Allekirjoituksen, joka ei täsmää, on johdettava virheeseen, eikä sitä saa koskaan liittää.
  • Ilmoita, lähettääkö palveluntarjoaja transaktion verkkoon. Jos se suorittaa lähetyksen palvelinpuolella, toteuta lähettävä muoto ja palauta allekirjoitus, joka yksilöi ketjuun päätyneen transaktion. Jos palveluntarjoajalla ei ole pelkkää allekirjoitusta tarjoavaa päätepistettä, tee taustajärjestelmästä vain lähettävä sen sijaan, että transaktio lähetettäisiin kutsujan tietämättä.
  • Älä koskaan jätä kutsujalle transaktiota, jota allekirjoitus ei kata. Palveluntarjoaja, joka kirjoittaa transaktion uudelleen, allekirjoittaa omat tavunsa: joko jätä kutsujan transaktio koskemattomaksi (lähetyksen hallinnoima tapaus) tai käytä muokkaavaa muotoa ja korvaa se kokonaan palveluntarjoajan tavuilla.
  • Raportoi lähetyksen epävarmuus epävarmuutena. Epäonnistuminen lähetyksen jälkeen voi silti olla päätynyt ketjuun. Nosta esiin BROADCAST_UNCONFIRMED yleisen virheen sijaan, liitä mukaan palveluntarjoajan transaktiotunniste aina kun se on saatavilla, ja lähetä viestistä johdettu idempotenssiavain jokaisen luonnin yhteydessä, jotta tavuiltaan identtinen uudelleenlähetys on turvallinen.
  • Älä koskaan allekirjoita ennen alustusta. Käytä Option<Pubkey> (ei Pubkey::default()) julkiselle avaimelle ennen init()-kutsua ja palauta konfiguraatiovirhe, jos allekirjoitusta yritetään ensin.
  • Älä koskaan kirjaa lokiin arkaluonteisia tietoja (yksityisiä avaimia, API-salaisuuksia, raakoja API-vastauksia) ja käytä Debug-toteutusta, joka piilottaa arkaluonteiset kentät
  • Pakota HTTPS (https_only(true), rajattuna ehdolla #[cfg(not(test))] wiremockia varten) ja määritä pyyntö- ja yhteysaikakatkaisut HttpClientConfig-rakenteella (oletukset: 30 s/5 s)
  • Nollaa välivaiheen avainmateriaali zeroize::Zeroizing<Vec<u8>>-tyypillä

Testaus mock-objekteilla

Käytä wiremock HTTP-rajapintojen mock-testaukseen. Tarkista vain virhetyyppi, ei virheviestin tekstiä:

#[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
}
}

Avun saaminen

  • Tutustu olemassa oleviin allekirjoittajatoteutuksiin malleiksi:
    • src/memory/mod.rs: Yksinkertainen, synkroninen
    • src/privy/mod.rs: Vaatii alustuksen
    • src/turnkey/mod.rs: Monimutkainen allekirjoitusten käsittely
    • src/vault/mod.rs: Ulkoinen asiakaskirjasto
    • typescript/packages/para/, python/src/solana_keychain/para/ ja go/signers/para/: täydelliset esimerkkitoteutukset muilla kielillä
  • Keskeiset tiedostot: src/traits.rs (trait-määritykset), src/transaction_util.rs ja src/signature_util.rs (jaetut apufunktiot), src/http_client_config.rs (aikakatkaisujen asetukset)
  • Koko repositorion opas: docs/ADDING_SIGNERS.md
  • Avaa issue suunnittelukeskusteluja varten ennen työn aloittamista

Esimerkki PR-rakenteesta

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 Foundation. Kaikki oikeudet pidätetään.