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 tiedostossasrc/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 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()}}
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 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))}}
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 featureall = ["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 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),}}}
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 nimilleget_signer()-apufunktion, joka lukee ympäristömuuttujatdotenvy-kirjaston avulla- Kolme testifunktiota:
test_<name>_sign_message,test_<name>_sign_transactionjatest_<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-keyYOUR_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ötarkoitus | Ominaisuuslippu |
|---|---|---|
| Memory | Paikalliset avainparit, kehitys, testaus | memory |
| Vault | Yritystason avainten hallinta HashiCorp Vaultilla | vault |
| Privy | Upotetut lompakot Privy-infrastruktuurilla | privy |
| Turnkey | Ei-säilytettävä avainten hallinta Turnkey-palvelun kautta | turnkey |
| YourService | Lyhyt kuvaus palvelustasi | your_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 matricesjust 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-ominaisuutesibackend-matriisiin (ennenall-arvoa) ja integraatiotestifunktiositest-matriisiintypescript-ci.yml: lisää pakettisi yksikkö- ja integraatiotestimatriiseihin ja lisää ympäristömuuttujat integraatiotestivaiheeseentypescript-publish.yml: lisää pakettisiPUBLISH_PACKAGES-kohtaan, GitHub Releasenpackages-taulukkoon ja yhteenvetotaulukkoonpython-ci.yml: lisää taustajärjestelmäresolve-signers-vaiheenCI_SIGNER_*-rajaukseen ja integraatiomatriisiingo-ci.yml: lisää taustajärjestelmäCI_SIGNER_*-rajaukseen ja kohtaanmoduleBySigner
TypeScript-allekirjoittaja
Jos lisäät myös TypeScript-allekirjoittajapaketin, luo se sijaintiin
typescript/packages/your-signer/. Keskeiset mallit:
- Tehdasfunktio
createYourSigner()palauttaaSolanaTransactionSigner<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ä
SolanaMessageSignerpois äläkä tarjoasignMessages-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 funktioillefetchSignerJson()jasignBatchStaggered() - Validoi
requestDelayMsfunktiollavalidateRequestDelayMs()ja käsittele erät funktiollasignBatchStaggered() - Lisää
@throwsJSDoc tehdas-funktioihin listaten virhekoodit - Määritä
@solana/*-paketit peer-riippuvuuksiksi versiosta>=8.1.0 - Yksikkötestit vitestillä ja mockatulla
fetch-funktiolla; integraatiotestit funktiollarunSignerIntegrationTestpaketista@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 lainkaansignTransactions-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) arvollaBROADCAST_UNCONFIRMED, joka sisältää palveluntarjoajan transaktiotunnuksen, ja säilytä keskeytyksen syy arvossacause - Lisää tyypitetty
createKeychainSigner-ylikuormitus, joka palauttaa lähettävän allekirjoittajatyyppisi - Sulje taustajärjestelmäsi pois tyypistä
KeychainKitPluginConfigtiedostossatypescript/packages/kit-plugin/src/keychain-plugin.ts, koska lähettävä allekirjoittaja ei voi toimia Kit-asiakkaanpayer- taiidentity-roolissa - Varmista yksikkötesteissä, että
isTransactionPartialSigner()on false jaisTransactionSendingSigner()on true instansseillesi
Päivitä Kattopaketti
Päivitä typescript/packages/keychain/ (7 muokattavaa tiedostoa):
src/types.ts: LisääYourSignerConfigKeychainSignerConfig- erotteluunioniinsrc/create-keychain-signer.ts: Lisää switch-case (jokainen taustajärjestelmäpaketti ladataan dynaamisellaimport()-kutsulla)src/resolve-address.ts: Lisää pikapolun tai hakupolun switch-caseensrc/index.ts: Lisää konfiguraatiotyypin, nimiavaruuden ja tehdasfunktion viennitpackage.json: Lisää riippuvuus@solana/keychain-your-signer: "workspace:*"tsconfig.json: Lisää viittaus{ "path": "../your-signer" }typescript/scripts/test-treeshake-umbrella.mjs: Lisää pakettisi kohtaanSIGNER_MARKERSja tehdasfunktiosi kohtaanFACTORIES
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ä
SolanaSignerpaketistasolana_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()) taiSendingSigner(async sign_and_send_transaction()) - Vie
async create_your_signer()-tehdasfunktio, joka odottaainit()-kutsun, kun taustajärjestelmä selvittää osoitteensa etänä; ennen alustusta allekirjoitus nostaa virheenSignerErrorCode.NOT_INITIALIZED - Vie konfiguraatiodataluokka (
YourSignerConfig), jossa salaisuudet on merkittyfield(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
_BACKENDStiedostossapython/src/solana_keychain/keychain.py - Jos taustajärjestelmä tarvitsee palveluntarjoajan SDK:n, suojaa tuonti, nosta
ImportError, jossa mainitaanpip install 'solana-keychain[your-signer]', ilmoita lisäpaketti tiedostossapython/pyproject.toml, äläkä koskaan tuo taustajärjestelmää ennenaikaisesti paketin juuritasolta - Yksikkötestit pytestillä:
respxHTTP-taustajärjestelmille, stub-asiakkaat SDK-taustajärjestelmille sekä tarkistus, ettei mikään salaisuus näy nostettujen virheidenstr()- tairepr()-tulosteessa - Lisää
make_your_signer()-tehdasfunktio tiedostoonpython/tests/integration/test_live_signers.pyja rekisteröi se kohtaan_MESSAGE_CAPABLE(tai_TRANSACTION_ONLYtaustajärjestelmille, joilla ei olesign_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.ModifyingSignertaicore.SendingSigner. Ominaisuus tunnistetaan tyyppivarmennuksella (type assertion), joten taustajärjestelmällä, joka ei voi allekirjoittaa transaktiota, ei saa olla lainkaanSignTransaction-metodia - Vie
Config-rakenne jaNew-konstruktori (New(ctx, cfg), kun taustajärjestelmä selvittää osoitteensa etänä; sitä ennen palautacore.NewNotInitializedError) - Rakenna HTTP-asiakkaat funktiolla
core.NewHTTPClienttaicore.ResolveHTTPClientja tarkista perus-URL-osoitteet funktiollacore.NormalizeHTTPSBaseURL - Allekirjoita transaktiot funktiolla
core.SignTransactionWith, joka allekirjoittaa viestin tavut ja liittää allekirjoituksen; varmista palveluntarjoajan allekirjoitukset funktiollacore.VerifySignature - Palauta virheet funktioilla
core.NewSignerError/core.WrapSignerErrorja vakailla koodeilla, kutencore.CodeSigningFailed - Yksikkötestit
httptest-paketilla; integraatiotestit tiedostossaintegration_test.gotagin//go:build integrationtakana
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
HttpClientConfigkautta - 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.examplepä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 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}")))?;
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_UNCONFIRMEDyleisen 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>(eiPubkey::default()) julkiselle avaimelle enneninit()-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 yhteysaikakatkaisutHttpClientConfig-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, synkroninensrc/privy/mod.rs: Vaatii alustuksensrc/turnkey/mod.rs: Monimutkainen allekirjoitusten käsittelysrc/vault/mod.rs: Ulkoinen asiakaskirjastotypescript/packages/para/,python/src/solana_keychain/para/jago/signers/para/: täydelliset esimerkkitoteutukset muilla kielillä
- Keskeiset tiedostot:
src/traits.rs(trait-määritykset),src/transaction_util.rsjasrc/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 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?