本指南面向希望将新密钥管理解决方案集成到 solana-keychain
库中的钱包服务提供商和开发者。通过添加您的签名器实现,您将使开发者能够通过统一接口使用您的服务进行安全的 Solana 交易签名。
Keychain 的 Rust、TypeScript、Python 和 Go 实现保持功能对等。我们强烈建议 PR 同时包含这四种实现。如果您只能贡献其中一种也没问题,但在该签名器随版本发布之前,其余几种实现仍需补齐。
正在使用 Claude Code?仓库中的 add-signer-ci 技能可为通过 fork PR 贡献的新签名器配置 CI 工作流。
架构概述
该库采用基于 trait 的架构,定义在 src/traits.rs 中。每个签名器都需要实现 SolanaSigner 基础 trait(pubkey、sign_message、is_available),外加一个与提供商形态相匹配的能力 trait:
TransactionSigner(sign_transaction):提供商对调用方提供的字节进行签名,由调用方负责广播。适用于大多数后端。ModifyingSigner(modify_and_sign_transaction):提供商先重写交易,再进行签名,但不广播。SendingSigner(sign_and_send_transaction):提供商在服务端完成签名和广播。
该库还提供了一个统一的 Signer 枚举,封装了所有实现,支持在运行时选择签名后端。它只实现基础 trait;各项能力需通过 as_transaction_signer()、as_modifying_signer() 和 as_sending_signer() 获取。
快速集成检查清单
- 创建您的签名器模块及其实现
- 实现
SolanaSigner基础 trait(pubkey、sign_message、is_available),并实现与提供商形态相匹配的一个能力 trait(通常为TransactionSigner) - 在
Cargo.toml中添加功能标志(feature flag);如果您的签名器使用 reqwest,还需包含内部的_remote标记 - 更新
src/lib.rs中的Signer枚举(变体、dispatch_signer!分支,以及与您的形态相匹配的能力访问器中的一个分支) - 强制使用 HTTPS 并在 HTTP 客户端上配置超时
- 对消息字节进行签名,并在附加签名之前验证返回的签名
- 在您的模块中添加完整的单元测试(基于 wiremock)
- 添加集成测试文件并更新
.env.example - 添加 TypeScript、Python 和 Go 实现
- 更新文档
- CI 工作流更新(请与维护者协调,参见 CI 工作流更新)
- 提交 PR
第一步:创建您的签名器模块
在 src/ 下为您的实现创建一个新目录:
src/├── your_service/│ ├── mod.rs # Main implementation with SolanaSigner trait│ └── types.rs # API request/response types (if needed)
第二步:定义您的签名器结构体
在 src/your_service/mod.rs 中定义您的签名器结构体:
//! 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()}}
第三步:实现构造函数和辅助方法
远程签名器必须强制使用 HTTPS 并配置 HTTP 超时。使用共享的 HttpClientConfig
结构体进行超时设置。
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))}}
第 4 步:实现签名器 Trait
SolanaSigner 基础 trait 包含 2 个异步方法(sign_message、is_available)以及 pubkey()。交易签名位于能力 trait 中;大多数提供商实现 TransactionSigner,其 sign_transaction 返回 SignTransactionResult(一个带标签的枚举,用于标明交易是完全签名还是部分签名)。
请对消息字节签名,而不是对整个线路格式(wire)交易签名,并在附加之前,针对这些字节验证提供商返回的签名。签名和序列化请使用共享的 TransactionUtil 辅助函数。
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),))}}
如果您的提供商在签名前会重写交易,请改为实现 ModifyingSigner,并用提供商返回的字节整体替换调用方的交易。如果它在服务端广播,请实现 SendingSigner,并保持调用方的交易不变。切勿实现提供商无法提供的能力。
步骤 5:添加 API 类型(可选)
如果你的 API 需要自定义类型,请创建 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,}
步骤 6:添加特性标志
更新 Cargo.toml,将您的签名器添加为可选功能。远程后端需包含内部的 _remote 标记,它用于控制 src/error.rs 中 From<reqwest::Error> 的实现;如果没有该标记,在仅启用您的功能时,对 reqwest 调用使用 ? 将无法通过编译。
[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
步骤 7:更新签名器枚举
将您的签名器添加到 src/lib.rs。基础的 SolanaSigner 实现通过 dispatch_signer! 宏进行分发,因此需要在其中添加一个分支。能力访问位于 as_transaction_signer()、as_modifying_signer() 和 as_sending_signer() 这几个访问器中:请在与您的能力 trait 相匹配的访问器中添加一个 Some(s) 分支(对于发送型或修改型后端,还需在 as_transaction_signer() 中添加一个 None 分支)。
// 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),}}}
步骤 8:添加全面的测试
在你的模块中添加测试(位于 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());}}
还需覆盖签名验证失败的情况:模拟响应中的签名如果无法通过验证,必须返回错误。
第 9 步:添加集成测试
创建 rust/src/tests/test_<name>_integration.rs。集成测试针对真实的服务 API 运行(而非 wiremock),并受 integration-tests 功能开关控制。每个文件需要包含:
- 用于环境变量名称的
pub const声明 - 一个通过
dotenvy读取环境变量的get_signer()辅助函数 - 三个测试函数:
test_<name>_sign_message、test_<name>_sign_transaction和test_<name>_is_available - 功能开关:在模块上添加
#[cfg(feature = "your_service")],并在每个测试上添加#[cfg(feature = "integration-tests")]
完整的参考示例请见 rust/src/tests/test_para_integration.rs,并在 rust/src/tests/mod.rs 中声明您的模块:
pub mod test_your_service_integration;
第 10 步:更新环境变量
请按照现有格式,在根目录的 .env.example 中添加您的签名器所需的环境变量:
# 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
先列出必填变量并使用占位符值,可选变量则以注释形式给出其默认值。集成测试中用到的每个环境变量都必须出现在 .env.example 中,且 TypeScript、Python 和 Go 测试套件使用相同的变量名。
第 11 步:更新文档
在 README.md 的支持后端表格中添加你的签名器:
| 后端 | 使用场景 | 特性标志 |
|---|---|---|
| Memory | 本地密钥对、开发、测试 | memory |
| Vault | 使用 HashiCorp Vault 的企业级密钥管理 | vault |
| Privy | 基于 Privy 基础设施的嵌入式钱包 | privy |
| Turnkey | 通过 Turnkey 的非托管密钥管理 | turnkey |
| YourService | 你的服务的简要描述 | your_service |
同时,请将您的后端添加到各语言 README 中的签名器能力表里,并添加一个使用示例:
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(())}
测试你的集成
请使用 just 配方,而不是直接使用 cargo。SDK 功能(sdk-v2、sdk-v3、sdk-v4)互斥,因此 cargo test --all-features 会失败。
# 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 工作流更新(Fork PR)
CI 分为两个阶段。请与维护者协调,在测试您的 PR 之前先准备好 main 分支:他们会更新 fork-external-live-manual.yml,向 ci.yml 添加环境变量,并配置 GitHub Secrets。
您的 PR 必须在包含签名器代码的同时,包含以下第 2 阶段的变更:
ci.yml:将您的 Rust 功能添加到backend矩阵中(位于all之前),并将您的集成测试函数添加到test矩阵中typescript-ci.yml:将您的包添加到单元测试和集成测试矩阵中,并在集成测试步骤中添加环境变量typescript-publish.yml:将您的包添加到PUBLISH_PACKAGES、GitHub Release 的packages数组以及汇总表中python-ci.yml:将该后端添加到resolve-signers中的CI_SIGNER_*控制逻辑以及集成矩阵中go-ci.yml:将该后端添加到CI_SIGNER_*控制逻辑以及moduleBySigner中
TypeScript 签名器
如果你还要添加 TypeScript 签名器包,请在 typescript/packages/your-signer/
创建它。关键模式:
- 工厂函数
createYourSigner()返回SolanaTransactionSigner<TAddress> & SolanaMessageSigner<TAddress>;请将签名器类保持为内部实现,仅导出工厂函数及其配置类型 - 导出配置接口(
YourSignerConfig) - 如果您的后端无法对消息签名,请省略
SolanaMessageSigner,并且完全不要暴露signMessages方法:Kit 通过方法是否存在来对签名器分类,因此抛出异常的占位实现会导致签名器被错误归类 - 使用
@solana/keychain-core中的assertHttpsUrl()对apiBaseUrl配置字段强制使用 HTTPS - 通过
@solana/keychain-core中的fetchSignerJson()发起远程调用,它负责错误映射、响应清理、拒绝重定向以及默认超时 - 使用
@solana/keychain-core中的sanitizeRemoteErrorResponse()清理其他任何远程 API 错误文本 - 使用
@solana/keychain-core中的throwSignerError(SignerErrorCode.*, { cause, message }) - 在每个签名方法中遵循
config?.abortSignal,并将其传递给fetchSignerJson()和signBatchStaggered() - 使用
validateRequestDelayMs()验证requestDelayMs,并使用signBatchStaggered()进行批处理 - 向工厂函数添加
@throwsJSDoc,列出错误代码 - 将
@solana/*包声明为对等依赖(peer dependencies),版本要求为>=8.1.0 - 使用 vitest 和模拟的
fetch编写单元测试;使用@solana/keychain-test-utils中的runSignerIntegrationTest编写集成测试
发送型签名器
如果您的提供商会重写交易或在服务端广播,其签名就无法应用到调用方的交易上,因此必须使用 SolanaSendingSigner,而不是 SolanaTransactionSigner:
- 实现
signAndSendTransactions(),并且不要暴露signTransactions(也不要暴露signMessages,除非该后端确实支持对消息签名) - 遵循
config?.abortSignal。一旦提供商已接受创建请求,请将任何失败(包括中止)报告为携带提供商交易 ID 的BROADCAST_UNCONFIRMED,并将中止原因保留为cause - 添加一个返回您的发送型签名器类型的带类型
createKeychainSigner重载 - 在
typescript/packages/kit-plugin/src/keychain-plugin.ts的KeychainKitPluginConfig中排除您的后端,因为发送型签名器不能用作 Kit 客户端的payer或identity - 在单元测试中断言:对于您的实例,
isTransactionPartialSigner()为 false,isTransactionSendingSigner()为 true
更新总包
更新 typescript/packages/keychain/(需修改 7 个文件):
src/types.ts:将YourSignerConfig添加到KeychainSignerConfig可辨识联合类型中src/create-keychain-signer.ts:添加 switch 分支(每个后端包都通过动态import()加载)src/resolve-address.ts:添加到快速路径或获取路径的 switch 分支中src/index.ts:添加配置类型、命名空间和工厂函数的导出package.json:添加@solana/keychain-your-signer: "workspace:*"依赖tsconfig.json:添加{ "path": "../your-signer" }引用typescript/scripts/test-treeshake-umbrella.mjs:将您的包添加到SIGNER_MARKERS,并将您的工厂函数添加到FACTORIES
switch 语句带有穷尽式的 never 检查,因此如果您向联合类型添加了成员却遗漏了某个分支,TypeScript 会报错。之后请运行 just ts-treeshake:每个包以及整合包(umbrella)都必须保持可被 tree-shaking。
Python 签名器
在 python/src/solana_keychain/your_signer/ 创建模块(__init__.py 加 signer.py)。关键模式:
- 继承
solana_keychain.core中的SolanaSigner(pubkey属性、async sign_message()、async is_available()),并继承且仅继承一个能力类:TransactionSigner(async sign_transaction())、ModifyingSigner(async modify_and_sign_transaction())或SendingSigner(async sign_and_send_transaction()) - 导出一个
async create_your_signer()工厂函数;当后端需要远程解析其地址时,该函数会 awaitinit();在初始化之前,签名会抛出SignerErrorCode.NOT_INITIALIZED - 导出一个配置数据类(
YourSignerConfig),其中的机密字段标记为field(repr=False) - 使用
assert_https_url()对基础 URL 字段强制使用 HTTPS,并让所有请求都经由fetch_signer_json()发出 - 在返回提供商的签名之前先验证它;使用
signed_message_bytes()获取签名所覆盖的字节,切勿手写版本前缀 - 在
python/src/solana_keychain/keychain.py的_BACKENDS中注册该后端 - 如果后端需要提供商 SDK,请对导入加以保护,抛出
ImportError, 并在其中注明pip install 'solana-keychain[your-signer]',在python/pyproject.toml中声明该 extra,且绝不要从包根目录急切导入该后端 - 使用 pytest 编写单元测试:HTTP 后端使用
respx,SDK 后端使用桩客户端(stub client), 并检查抛出错误的str()或repr()中不包含任何密钥信息 - 在
python/tests/integration/test_live_signers.py中添加make_your_signer()工厂函数, 并将其注册到_MESSAGE_CAPABLE中(对于不支持sign_message的后端,则注册到_TRANSACTION_ONLY)
Go 签名器
每个 Go 后端都是独立的模块,位于 go/signers/yoursigner/,模块路径为
github.com/solana-foundation/solana-keychain/go/signers/yoursigner/v2。可参考
go/signers/para/。关键模式:
- 实现
core.SolanaSigner(Pubkey()、SignMessage(ctx, message)、IsAvailable(ctx)),并且只实现一个能力接口:core.TransactionSigner、core.ModifyingSigner或core.SendingSigner。 能力通过类型断言来检测,因此无法签署交易的后端 绝不能带有SignTransaction方法 - 导出
Config结构体和New构造函数(当后端通过远程方式解析其地址时,使用New(ctx, cfg); 在此之前,返回core.NewNotInitializedError) - 使用
core.NewHTTPClient或core.ResolveHTTPClient构建 HTTP 客户端, 并使用core.NormalizeHTTPSBaseURL校验基础 URL - 使用
core.SignTransactionWith签署交易,它会对消息字节进行签名并附加签名; 使用core.VerifySignature验证提供商返回的签名 - 使用
core.NewSignerError/core.WrapSignerError以及稳定的错误码 (如core.CodeSigningFailed)返回错误 - 使用
httptest编写单元测试;集成测试放在integration_test.go中, 并置于//go:build integration标签之下
提交检查清单
在提交 PR 之前:
- 代码编译无警告(
just build) - 所有测试通过(
just test) - 代码格式化/代码检查通过(
just fmt) - 代码中无硬编码值或密钥
- 错误消息是通用的(不含原始 API 响应文本)
- 不对不受信任的 API 响应使用
.expect()或.unwrap() - 在远程 HTTP 客户端上强制使用 HTTPS
- 通过
HttpClientConfig配置 HTTP 超时 - 返回的签名已针对本地计算的消息字节完成验证
- 遵循命名规范(Rust 和 Python 使用 snake_case, TypeScript 使用 camelCase)
- 已添加集成测试并更新
.env.example - 包含 TypeScript、Python 和 Go 实现(或已与维护者共同规划)
- 已更新聚合包(TypeScript 聚合包、Python
_BACKENDS) - 包含 CI 变更
- 已添加到 README.md 的受支持后端表和签名器能力表中
实现提示
错误处理
请始终使用现有的 SignerError 变体。SignerError 的 Display 和 Debug 输出均已脱敏,
因此请保持错误信息通用,切勿包含原始 API 响应数据。切勿对不受信任的 API 响应使用 .expect() 或 .unwrap():
// 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}")))?;
安全要求
这些并非建议:使用者正是依据这些要求来选择后端。如果
您的提供商无法满足其中某一项,请在 PR 以及仓库的
docs/SECURITY_MODEL.md 中说明。
- 验证提供商所签署的内容。 在本地计算消息字节, 并在附加签名之前,按签名器要求的位置对返回的签名进行验证。 验证不通过的签名必须视为失败,绝不能附加。
- 声明提供商是否会广播交易。 如果提供商在服务端执行交易, 请实现发送型(sending)形态,并返回可标识已上链交易的签名。 如果提供商没有仅签名的端点,请将该后端设为仅发送型, 而不要在调用方不知情的情况下提交交易。
- 绝不要让调用方持有签名未覆盖的交易。 会改写交易的提供商签署的是其自身的字节:要么保持调用方的交易不变(由广播托管的情形), 要么采用修改型(modifying)形态,并用提供商的字节整体替换原交易。
- 如实报告广播的不确定性。 提交之后发生的失败,交易仍可能已经上链。
请抛出
BROADCAST_UNCONFIRMED,而不是通用错误; 只要有提供商的交易 ID,就一并携带; 并在每次创建时发送由消息派生的幂等键,以便字节完全相同的重发是安全的。 - 初始化之前绝不签名。 在
init()之前,公钥应使用Option<Pubkey>(而不是Pubkey::default()), 若在此之前尝试签名,则以配置错误失败。 - 绝不记录敏感数据(私钥、API 密钥、原始 API 响应),
并使用会隐藏敏感字段的
Debug实现 - 强制使用 HTTPS(
https_only(true),为 wiremock 使用#[cfg(not(test))]进行条件控制), 并通过HttpClientConfig配置请求超时和连接超时(默认值:30 秒/5 秒) - 使用
zeroize::Zeroizing<Vec<u8>>对中间密钥材料进行清零
使用模拟进行测试
使用 wiremock 来模拟 HTTP API。仅对错误类型进行断言,而不是错误消息文本:
#[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}}
获取帮助
- 查看现有的签名器实现以了解模式:
src/memory/mod.rs:简单、同步src/privy/mod.rs:需要初始化src/turnkey/mod.rs:复杂的签名处理src/vault/mod.rs:外部客户端库typescript/packages/para/、python/src/solana_keychain/para/和go/signers/para/:其他语言中的完整参考实现
- 关键文件:
src/traits.rs(trait 定义)、src/transaction_util.rs和src/signature_util.rs(共享辅助函数)、src/http_client_config.rs(超时配置) - 完整的仓库指南: docs/ADDING_SIGNERS.md
- 在开始工作之前,请提交 issue 进行设计讨论
PR 结构示例
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?