钱包业务层 - 1. 实现业务接口及对接签名、链上 rpc

在前面两篇帖子中,我们实现了signature-machine离线签名机项目和chains-union-rpc多链统一rpc服务。在这一篇帖子中,我们正式来开始实现一个钱包的业务层。

exchange-wallet-service

在前面两篇帖子中,我们实现了 signature-machine 离线签名机项目和 chains-union-rpc 多链统一 rpc 服务。在这一篇帖子中,我们正式来开始实现一个钱包的业务层。它将和 signature-machine 签名机以及 chians-union-rpc 统一 rpc 进行联通,共同构成一个完整的交易所钱包业务流。

完整项目 github 地址(如果对您有用,请给个小 star):

  1. exchange-wallet-service:钱包业务层服务https://github.com/Shawn-Shaw-x/exchange-wallet-service
  2. signature-machine:离线签名机 https://github.com/Shawn-Shaw-x/signature-machine
  3. chains-union-rpc:多链统一 rpc https://github.com/Shawn-Shaw-x/chains-union-rpc

什么是 exchange-wallet-service

exchange-wallet-service 是一个基于 gRPCPostgreSQL 构建的高性能钱包服务,支持交易所钱包 SaaS 化部署,为多项目方提供账户体系、链上交易扫描、充值提现管理、热冷钱包归集与划转等全功能解决方案。

PS: 本钱包项目基于交易所钱包业务抽象、简化而来。添加多租户支持,可提供项目方 Sass 化支持

✨ 功能特性

  • 多项目方接入支持:每个项目方独立账户体系,隔离资金与操作权限。
  • 充值服务:支持扫链识别入账交易,自动处理充值交易,项目方自控权限归集至热/冷钱包。
  • 提现服务:离线签名与审核流程支持,确保资产安全。
  • 热转冷 & 冷转热:支持按规则由项目方执行热钱包与冷钱包资产调配。
  • 链上交易扫描:高效同步链上交易数据,触发充值/通知等业务。
  • 通知机制:支持通过 HttpgRPC 等形式将充值、提现等事件推送给业务方。
  • SaaS 化部署:支持以服务化方式快速部署,为多租户提供统一服务。

🧱 技术栈

技术 描述
gRPC 服务间通信协议,定义清晰的 protobuf 接口
GORM Go ORM 框架,简化数据库访问
PostgreSQL 持久化存储引擎
Protobuf 用于服务接口定义和数据结构描述
Makefile 标准化开发与部署流程
Go Modules 依赖管理与构建

📂 项目结构

├── cmd                 主程序入口、命令行程序框架
├── common              通用工具库
├── config              配置文件管理代码
├── database            数据库代码
├── flags               环境变量管理代码
├── migrations          数据库迁移
├── notifier            回调通知管理
├── protobuf            grpc 接口及生成代码
├── rpcclient           grpc 连接客户端
├── services            grpc 服务管理及接口实现
├── sh                  shell 命令
├── worker              核心工作代码(充值、提现、归集、热转冷)
├── exchange.go         主程序生命周期管理
├── Makefile  shell     命令管理
├── devops.md           开发步骤
├── go.mod              依赖管理
├── README.md   

搭建、实现该项目

在这个系列文章中,我将逐步搭建完整的钱包业务项目。 大概流程为:

  1. 搭建项目框架、完成数据库设计、实现服务接口
  2. 扫链同步器搭建
  3. 充值业务实现
  4. 提现业务实现
  5. 归集、转冷、转热业务实现
  6. 提现、归集、转冷、转热交易发现
  7. 回滚业务实现
  8. 通知业务实现

在这一篇中,我们只实现业务的第一步,即搭建项目框架、完成数据库设计和实现服务接口。

数据库设计

在数据库方面,我们提供 10 个基础表,如:

  • Business(businessId,notifyUrl...): 注册商户表
  • Blocks(hash,parentHash,number...): 区块信息表
  • ReorgBlocks(hash,parentHash,number): 回滚区块表(回滚时处理交易使用)
  • Address(address,addressType,publicKey...): 钱包地址表
  • Balance(address,tokenAddress,balance,lockBalance...): 地址余额表
  • Deposit(from,to,amount,confirms,blockHash...): 充值表
  • Withdraw(from,to,amount,blockHash...): 提现表
  • Internals(from,to,amount,blockHash...): 内部交易表(归集、热转冷、冷转热)
  • Transactions(from,to,amount,fee,hash...): 交易流水表
  • Token(tokenAddress,decimals,collectAmount...): token 合约表

其中 BlocksReOrgBlocksBusiness 是全局共享的,其他表为每个项目方独立的(项目方注册时,会为其生成独立的表,表名类似 Deposits_{requestId}

rpc接口设计

在本系统中的 rpc 接口设计中,我们提供 5 个接口。分别用于业务方注册、批量导出地址、构建未签名交易、构建已签名交易、设置 token 合约地址。

  /*业务方注册*/
  rpc businessRegister(BusinessRegisterRequest) returns (BusinessRegisterResponse);
  /*地址导出*/
  rpc exportAddressByPublicKeys(ExportAddressRequest) returns (ExportAddressResponse);
  /*构建未签名交易*/
  rpc buildUnSignTransaction(UnSignTransactionRequest) returns (UnSignTransactionResponse);
  /*构建已签名交易*/
  rpc buildSignedTransaction(SignedTransactionRequest) returns (SignedTransactionResponse);
  /*设置 token 地址*/
  rpc setTokenAddress(SetTokenAddressRequest) returns (SetTokenAddressResponse);

和另外的两个项目交互架构图

首先来看这三个项目的统一架构图,以项目方发送一笔交易的流程为例: image.png 在这个流程图中,我们以项目方发起一笔提现交易为例:

  1. 项目方请求钱包业务层,获取未签名交易,返回 32 字节的 messageHash 和交易的 transactionId
  2. 项目方使用 messageHash 请求离线签名机签名,返回 signature。
  3. 项目方使用 transactionIdmessageHash 调用钱包业务层,构建已签名交易。
  4. 钱包业务层收到这个已签名请求后,组装原始交易和签名,发送到 rpc 节点即可成功发起一笔提现交易。

    rpc 接口实现

    • 业务方注册
      1. 业务方携带自己的 requestId 进行注册,系统会根据 requestId 为其生成独立的 addressbalancetransactionsdepositswithdrawinternaltokens
      2. 注册成功后,其所有业务都需要携带 requestId 进行请求,数据独立在其自己的表中。
        BusinessRegister(context.Context, *BusinessRegisterRequest) (*BusinessRegisterResponse, error)
    • 批量导出地址
      1. 业务方通过 “signature-machine” 项目(项目方自己部署,自己掌控私钥和签名流程)批量生成公钥,将公钥传入此接口,批量获取地址。
      2. 此接口中,会根据用户方传入的地址类型,保存该地址信息到 address_{requestId} 表中,并初始化 balances
        ExportAddressesByPublicKeys(context.Context, *ExportAddressesRequest) (*ExportAddressesResponse, error)
    • 构建未签名交易
      1. 在此接口中,业务方传入关键参数:fromtoamountchainId 等信息,调用该接口。该接口会调用 “chains-union-rpc” 项目去获取地址的 noncegasFee 等。
      2. 然后构建 EIP-1159 的交易,调用 “chains-union-rpc” 项目去构建交易,返回 16 进制的未签名交易 messageHash32 字节)、将交易信息保存在表中。返回 messageHash 和请求的 transactionId
        BuildUnSignTransaction(context.Context, *UnSignTransactionRequest) (*UnSignTransactionResponse, error)
    • 构建已签名交易
      1. 项目方持有上述的未签名交易的 messageHash,调用 “signature-machine” 使用该交易对应的 from 地址私钥进行对此 messageHash 签名,返回 signature65 字节) 信息
      2. 项目方拿到 signaturetransactionId。 由 transactionId 从表中查出这笔交易,然后重新构造出来相同交易。调用 “chains-union-rpc” 的构建已签名接口,使用 signature 和 原交易信息发起调用 BuildSignedTransaction接口。
      3. 在“chains-union-rpc”中,会将 signature 拆分出 r、s、v 值和原交易组合起来,格式化返回一个已签名的交易(16 进制,base64 编码)
      4. 在拿到这个已签名交易的 16 进制数据后,即可调用 “chains-union-rpc” 里面的 sendTx 接口,将这笔交易公布到 rpc 网络中即可
        BuildSignedTransaction(context.Context, *SignedTransactionRequest) (*SignedTransactionResponse, error)
    • 设置合约地址
      1. 传入 ERC20 合约地址,作为合约项目白名单,存 tokens_{requestId} 表, 后续接入代币处理用。
        SetTokenAddress(context.Context, *SetTokenAddressRequest) (*SetTokenAddressResponse, error)

联调测试

  1. exchange-wallet-service 业务方注册

businessRegistRequest.png businessRegistResponse.png

  1. signature-machine 批量公钥生成

keyPairRequest.png keyPairResponse.png

  1. exchange-wallet-service 公钥转地址

addressRequest.png addressResponse.png

  1. 转资金进这个地址

metamask.png

  1. exchange-wallet-service 构建未签名交易

unsignTransactionRequest.png unsignTransactionResponse.png

  1. signature-machine 中签名操作

signatureRequest.png signatureResponse.png

  1. exchange-wallet-service 构建已签名交易

signedTxRequest.png signedTxResponse.png

  1. chains-union-rpc 发送出去交易

sendRequest.png

sendResponse.png

  1. holesky 区块浏览器中查看这笔交易

success.png

点赞 0
收藏 0
分享
本文参与登链社区写作激励计划 ,好文好收益,欢迎正在阅读的你也加入。

0 条评论

请先 登录 后评论
shawn_shaw
shawn_shaw
web3潜水员、技术爱好者、web3钱包开发工程师。欢迎闲聊唠嗑、精进技术、交流工作机会。vx:cola_ocean