Files
sonr/types/txns/builder.go
T
Prad NukalaandGitHub 13e6c3e84d Master (#1262)
* clear

* feat: Add everything

* fix: Commenht
2025-10-03 14:45:52 -04:00

415 lines
11 KiB
Go

package txns
import (
"context"
"fmt"
"math/big"
"cosmossdk.io/math"
"github.com/cosmos/cosmos-sdk/client"
sdk "github.com/cosmos/cosmos-sdk/types"
ethtypes "github.com/ethereum/go-ethereum/core/types"
"github.com/sonr-io/sonr/types/coins"
)
// TransactionBuilder is the main builder interface that wraps both Cosmos and EVM builders
type TransactionBuilder struct {
cosmosBuilder *CosmosBuilder
evmBuilder *EVMBuilder
coinsManager *coins.Manager
defaultChainID string
defaultEncoding EncodingType
}
// NewTransactionBuilder creates a new transaction builder
func NewTransactionBuilder(coinsManager *coins.Manager, defaultChainID string) *TransactionBuilder {
return &TransactionBuilder{
coinsManager: coinsManager,
defaultChainID: defaultChainID,
defaultEncoding: EncodingTypeProtobuf,
}
}
// Cosmos returns a Cosmos transaction builder
func (tb *TransactionBuilder) Cosmos(clientCtx client.Context) *CosmosBuilder {
if tb.cosmosBuilder == nil {
tb.cosmosBuilder = NewCosmosBuilder(clientCtx, tb.defaultChainID, tb.defaultEncoding)
}
return tb.cosmosBuilder
}
// EVM returns an EVM transaction builder
func (tb *TransactionBuilder) EVM(chainID *big.Int) *EVMBuilder {
if tb.evmBuilder == nil {
tb.evmBuilder = NewEVMBuilder(chainID)
}
return tb.evmBuilder
}
// CreateSigner creates a signer from wallet
func (tb *TransactionBuilder) CreateSigner(
wallet *coins.Wallet,
chainType TransactionType,
) (Signer, error) {
return NewWalletSigner(wallet, chainType)
}
// DeriveAddresses derives addresses for both chains
func (tb *TransactionBuilder) DeriveAddresses(did, salt string) (*AddressDerivation, error) {
cosmosAddr, evmAddr, derivationPath, err := tb.coinsManager.DeriveAddresses(did, salt)
if err != nil {
return nil, fmt.Errorf("failed to derive addresses: %w", err)
}
// Get public key from wallet creation
wallet, err := tb.coinsManager.CreateWalletFromEntropy(did, salt)
if err != nil {
return nil, fmt.Errorf("failed to create wallet for public key: %w", err)
}
return &AddressDerivation{
CosmosAddress: cosmosAddr,
EVMAddress: evmAddr,
DerivationPath: derivationPath,
PublicKey: wallet.GetCosmosPublicKey().Bytes(),
ChainType: "multi-chain",
}, nil
}
// CosmosBuilder builds Cosmos transactions
type CosmosBuilder struct {
clientCtx client.Context
chainID string
encoding EncodingType
gasLimit uint64
gasPrice sdk.DecCoin
memo string
timeoutHeight uint64
coinsTxBuilder *coins.CosmosTransactionBuilder
}
// NewCosmosBuilder creates a new Cosmos transaction builder
func NewCosmosBuilder(
clientCtx client.Context,
chainID string,
encoding EncodingType,
) *CosmosBuilder {
coinsTxBuilder := coins.NewCosmosTransactionBuilder(clientCtx, chainID)
return &CosmosBuilder{
clientCtx: clientCtx,
chainID: chainID,
encoding: encoding,
gasLimit: 200000,
gasPrice: sdk.NewDecCoin("usnr", math.NewInt(1000)),
coinsTxBuilder: coinsTxBuilder,
}
}
// SetChainID implements Builder interface
func (cb *CosmosBuilder) SetChainID(chainID string) Builder {
cb.chainID = chainID
return cb
}
// SetGas implements Builder interface
func (cb *CosmosBuilder) SetGas(limit uint64, price any) Builder {
cb.gasLimit = limit
if gasPrice, ok := price.(sdk.DecCoin); ok {
cb.gasPrice = gasPrice
cb.coinsTxBuilder.SetGas(limit, gasPrice)
}
return cb
}
// SetMemo implements Builder interface
func (cb *CosmosBuilder) SetMemo(memo string) Builder {
cb.memo = memo
cb.coinsTxBuilder.SetMemo(memo)
return cb
}
// SetTimeoutHeight sets the timeout height for the transaction
func (cb *CosmosBuilder) SetTimeoutHeight(height uint64) *CosmosBuilder {
cb.timeoutHeight = height
return cb
}
// SetEncoding sets the encoding type
func (cb *CosmosBuilder) SetEncoding(encoding EncodingType) *CosmosBuilder {
cb.encoding = encoding
return cb
}
// BuildUnsigned implements Builder interface
func (cb *CosmosBuilder) BuildUnsigned(params Params) (UnsignedTransaction, error) {
cosmosParams, ok := params.(*CosmosTransactionParams)
if !ok {
return nil, ErrInvalidTransactionParams
}
if err := cosmosParams.Validate(); err != nil {
return nil, fmt.Errorf("invalid parameters: %w", err)
}
// Use the underlying coins transaction builder
txBuilder, err := cb.coinsTxBuilder.BuildCustomTransaction(cosmosParams.Messages)
if err != nil {
return nil, fmt.Errorf("failed to build transaction: %w", err)
}
// Set additional parameters
if cosmosParams.TimeoutHeight > 0 {
txBuilder.SetTimeoutHeight(cosmosParams.TimeoutHeight)
}
return &CosmosUnsignedTx{
TxBuilder: txBuilder,
ChainID: cb.chainID,
Encoding: cb.encoding,
}, nil
}
// EstimateFee implements Builder interface
func (cb *CosmosBuilder) EstimateFee(params Params) (*FeeEstimation, error) {
cosmosParams, ok := params.(*CosmosTransactionParams)
if !ok {
return nil, ErrInvalidTransactionParams
}
if err := cosmosParams.Validate(); err != nil {
return nil, fmt.Errorf("invalid parameters: %w", err)
}
// Simple fee estimation based on gas limit and price
fee := cb.gasPrice.Amount.MulInt64(int64(cb.gasLimit))
totalFee := sdk.NewCoins(sdk.NewCoin(cb.gasPrice.Denom, fee.TruncateInt()))
return &FeeEstimation{
GasLimit: cb.gasLimit,
GasPrice: cb.gasPrice,
Fee: totalFee,
Total: totalFee.String(),
}, nil
}
// GetTransactionType implements Builder interface
func (cb *CosmosBuilder) GetTransactionType() TransactionType {
return TransactionTypeCosmos
}
// BuildSendTransaction builds a simple send transaction
func (cb *CosmosBuilder) BuildSendTransaction(
fromAddr, toAddr string,
amount sdk.Coins,
) (UnsignedTransaction, error) {
txBuilder, err := cb.coinsTxBuilder.BuildSendTransaction(fromAddr, toAddr, amount)
if err != nil {
return nil, fmt.Errorf("failed to build send transaction: %w", err)
}
return &CosmosUnsignedTx{
TxBuilder: txBuilder,
ChainID: cb.chainID,
Encoding: cb.encoding,
}, nil
}
// EVMBuilder builds EVM transactions
type EVMBuilder struct {
chainID *big.Int
gasLimit uint64
gasPrice *big.Int
maxFeePerGas *big.Int
maxPriorityFeePerGas *big.Int
nonce uint64
coinsEthBuilder *coins.EthereumTransactionBuilder
}
// NewEVMBuilder creates a new EVM transaction builder
func NewEVMBuilder(chainID *big.Int) *EVMBuilder {
coinsEthBuilder := coins.NewEthereumTransactionBuilder(chainID)
return &EVMBuilder{
chainID: chainID,
gasLimit: 21000,
gasPrice: big.NewInt(20000000000), // 20 Gwei
coinsEthBuilder: coinsEthBuilder,
}
}
// SetChainID implements Builder interface
func (eb *EVMBuilder) SetChainID(chainID string) Builder {
if chainIDBig, ok := new(big.Int).SetString(chainID, 10); ok {
eb.chainID = chainIDBig
}
return eb
}
// SetGas implements Builder interface
func (eb *EVMBuilder) SetGas(limit uint64, price any) Builder {
eb.gasLimit = limit
if gasPrice, ok := price.(*big.Int); ok {
eb.gasPrice = gasPrice
eb.coinsEthBuilder.SetGas(limit, gasPrice)
}
return eb
}
// SetMemo implements Builder interface (no-op for EVM)
func (eb *EVMBuilder) SetMemo(memo string) Builder {
// EVM transactions don't have memos
return eb
}
// SetNonce sets the nonce for the transaction
func (eb *EVMBuilder) SetNonce(nonce uint64) *EVMBuilder {
eb.nonce = nonce
eb.coinsEthBuilder.SetNonce(nonce)
return eb
}
// SetMaxFee sets the max fee per gas for EIP-1559 transactions
func (eb *EVMBuilder) SetMaxFee(maxFeePerGas, maxPriorityFeePerGas *big.Int) *EVMBuilder {
eb.maxFeePerGas = maxFeePerGas
eb.maxPriorityFeePerGas = maxPriorityFeePerGas
return eb
}
// BuildUnsigned implements Builder interface
func (eb *EVMBuilder) BuildUnsigned(params Params) (UnsignedTransaction, error) {
evmParams, ok := params.(*EVMTransactionParams)
if !ok {
return nil, ErrInvalidTransactionParams
}
if err := evmParams.Validate(); err != nil {
return nil, fmt.Errorf("invalid parameters: %w", err)
}
var tx *ethtypes.Transaction
// Choose transaction type based on fee parameters
if evmParams.MaxFeePerGas != nil && evmParams.MaxPriorityFeePerGas != nil {
// EIP-1559 transaction
tx = eb.coinsEthBuilder.BuildEIP1559Transaction(
*evmParams.To,
evmParams.Value,
evmParams.MaxFeePerGas,
evmParams.MaxPriorityFeePerGas,
evmParams.Data,
)
} else if evmParams.Data != nil && len(evmParams.Data) > 0 {
// Contract transaction
tx = eb.coinsEthBuilder.BuildContractTransaction(
*evmParams.To,
evmParams.Value,
evmParams.Data,
)
} else {
// Simple transfer
tx = eb.coinsEthBuilder.BuildTransferTransaction(
*evmParams.To,
evmParams.Value,
)
}
return &EVMUnsignedTx{
Transaction: tx,
ChainID: eb.chainID,
}, nil
}
// EstimateFee implements Builder interface
func (eb *EVMBuilder) EstimateFee(params Params) (*FeeEstimation, error) {
evmParams, ok := params.(*EVMTransactionParams)
if !ok {
return nil, ErrInvalidTransactionParams
}
if err := evmParams.Validate(); err != nil {
return nil, fmt.Errorf("invalid parameters: %w", err)
}
// Simple fee estimation
totalFee := new(big.Int).Mul(eb.gasPrice, big.NewInt(int64(eb.gasLimit)))
return &FeeEstimation{
GasLimit: eb.gasLimit,
GasPrice: eb.gasPrice,
Fee: totalFee,
Total: totalFee.String(),
}, nil
}
// GetTransactionType implements Builder interface
func (eb *EVMBuilder) GetTransactionType() TransactionType {
return TransactionTypeEVM
}
// EstimateGas estimates gas for a transaction
func (eb *EVMBuilder) EstimateGas(
ctx context.Context,
params *EVMTransactionParams,
) (uint64, error) {
// Use the underlying coins builder for gas estimation
tx := eb.coinsEthBuilder.BuildContractTransaction(*params.To, params.Value, params.Data)
return eb.coinsEthBuilder.EstimateGas(ctx, tx)
}
// WalletSigner implements Signer interface using coins.Wallet
type WalletSigner struct {
wallet *coins.Wallet
chainType TransactionType
}
// NewWalletSigner creates a new wallet-based signer
func NewWalletSigner(wallet *coins.Wallet, chainType TransactionType) (*WalletSigner, error) {
if wallet == nil {
return nil, fmt.Errorf("wallet cannot be nil")
}
return &WalletSigner{
wallet: wallet,
chainType: chainType,
}, nil
}
// Sign implements Signer interface
func (ws *WalletSigner) Sign(txBytes []byte) ([]byte, error) {
switch ws.chainType {
case TransactionTypeCosmos:
return ws.wallet.SignMessage(txBytes)
case TransactionTypeEVM:
return ws.wallet.SignEthereumMessage(txBytes)
default:
return nil, ErrUnsupportedChainType
}
}
// GetPublicKey implements Signer interface
func (ws *WalletSigner) GetPublicKey() []byte {
switch ws.chainType {
case TransactionTypeCosmos:
return ws.wallet.GetCosmosPublicKey().Bytes()
case TransactionTypeEVM:
pubKey := ws.wallet.GetEthereumPublicKey()
// Convert ECDSA public key to bytes
return append(pubKey.X.Bytes(), pubKey.Y.Bytes()...)
default:
return nil
}
}
// GetAddress implements Signer interface
func (ws *WalletSigner) GetAddress(chainType TransactionType) (string, error) {
switch chainType {
case TransactionTypeCosmos:
return ws.wallet.CosmosAddress, nil
case TransactionTypeEVM:
return ws.wallet.EthereumAddress, nil
default:
return "", ErrUnsupportedChainType
}
}