211 lines
5.7 KiB
Go
Raw Permalink Normal View History

2025-03-25 03:52:30 -04:00
package blockchain
import (
"context"
"fmt"
"log"
"math/big"
"os"
"time"
"github.com/ethereum/go-ethereum/accounts/abi/bind"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/ethclient"
)
const (
// MaxRetries is the maximum number of times to retry a transaction
MaxRetries = 3
// RetryDelay is the delay between retries in seconds
RetryDelay = 2
// Polygon Mumbai testnet chain ID
PolygonMumbaiChainID = 80001
// Polygon Amoy testnet chain ID
PolygonAmoyChainID = 80002
)
// BlockchainService handles interactions with the Polygon blockchain
type BlockchainService struct {
client *ethclient.Client
auth *bind.TransactOpts
contract *VoxPopContract // This will be replaced with actual contract bindings
chainID *big.Int
}
// NewBlockchainService creates a new blockchain service
func NewBlockchainService() (*BlockchainService, error) {
// Get RPC URL from environment
rpcURL := os.Getenv("POLYGON_RPC")
if rpcURL == "" {
return nil, fmt.Errorf("POLYGON_RPC environment variable not set")
}
// Connect to Polygon
client, err := ethclient.Dial(rpcURL)
if err != nil {
return nil, fmt.Errorf("failed to connect to Polygon: %v", err)
}
// Get private key from environment
privateKey := os.Getenv("PRIVATE_KEY")
if privateKey == "" {
return nil, fmt.Errorf("PRIVATE_KEY environment variable not set")
}
// Parse private key
privateKeyECDSA, err := crypto.HexToECDSA(privateKey)
if err != nil {
return nil, fmt.Errorf("failed to parse private key: %v", err)
}
// Get chain ID from environment or use default
chainID := big.NewInt(PolygonAmoyChainID)
if chainIDStr := os.Getenv("CHAIN_ID"); chainIDStr != "" {
chainID.SetString(chainIDStr, 10)
}
// Create the transactor
auth, err := bind.NewKeyedTransactorWithChainID(privateKeyECDSA, chainID)
if err != nil {
return nil, fmt.Errorf("failed to create transactor: %v", err)
}
// Get contract address from environment
contractAddress := os.Getenv("CONTRACT_ADDRESS")
if contractAddress == "" {
return nil, fmt.Errorf("CONTRACT_ADDRESS environment variable not set")
}
// Create contract instance (mock for now)
contract := &VoxPopContract{} // This will be replaced with actual contract bindings
return &BlockchainService{
client: client,
auth: auth,
contract: contract,
chainID: chainID,
}, nil
}
// SubmitPerspective submits a perspective hash to the blockchain
func (s *BlockchainService) SubmitPerspective(hash string) (string, error) {
log.Printf("Submitting perspective hash: %s", hash)
// Mock implementation - in production, this would call the actual contract
// tx, err := s.contract.SubmitPerspective(s.auth, hash)
// if err != nil {
// return "", fmt.Errorf("failed to submit perspective: %v", err)
// }
// For now, just log and return a mock transaction hash
mockTxHash := "0x" + hash[:64]
log.Printf("Mock transaction submitted: %s", mockTxHash)
return mockTxHash, nil
}
// ProposeInsight proposes a new insight
func (s *BlockchainService) ProposeInsight(insight string) (string, error) {
log.Printf("Proposing insight: %s", insight)
// Mock implementation - in production, this would call the actual contract
// tx, err := s.contract.ProposeInsight(s.auth, insight)
// if err != nil {
// return "", fmt.Errorf("failed to propose insight: %v", err)
// }
// For now, just log and return a mock transaction hash
mockTxHash := "0x" + insight[:64]
log.Printf("Mock transaction submitted: %s", mockTxHash)
return mockTxHash, nil
}
// VoteOnInsight casts a vote on an insight
func (s *BlockchainService) VoteOnInsight(insightID string, vote bool) (string, error) {
log.Printf("Voting on insight %s: %v", insightID, vote)
// Mock implementation - in production, this would call the actual contract
// tx, err := s.contract.VoteOnInsight(s.auth, insightID, vote)
// if err != nil {
// return "", fmt.Errorf("failed to vote on insight: %v", err)
// }
// For now, just log and return a mock transaction hash
mockTxHash := "0x" + insightID[:64]
log.Printf("Mock transaction submitted: %s", mockTxHash)
return mockTxHash, nil
}
// estimateGasAndSendTransaction estimates gas and sends a transaction with retries
func (s *BlockchainService) estimateGasAndSendTransaction(ctx context.Context, to *common.Address, data []byte) (string, error) {
var lastErr error
for i := 0; i < MaxRetries; i++ {
// Get the nonce
nonce, err := s.client.PendingNonceAt(ctx, s.auth.From)
if err != nil {
lastErr = err
time.Sleep(time.Second * RetryDelay)
continue
}
// Estimate gas
gasLimit, err := s.client.EstimateGas(ctx, ethereum.CallMsg{
From: s.auth.From,
To: to,
Gas: 0,
GasPrice: nil,
Value: nil,
Data: data,
})
if err != nil {
lastErr = err
time.Sleep(time.Second * RetryDelay)
continue
}
// Get gas price
gasPrice, err := s.client.SuggestGasPrice(ctx)
if err != nil {
lastErr = err
time.Sleep(time.Second * RetryDelay)
continue
}
// Create transaction
tx := types.NewTransaction(nonce, *to, nil, gasLimit, gasPrice, data)
// Sign transaction
signedTx, err := s.auth.Signer(s.auth.From, tx)
if err != nil {
lastErr = err
time.Sleep(time.Second * RetryDelay)
continue
}
// Send transaction
err = s.client.SendTransaction(ctx, signedTx)
if err != nil {
lastErr = err
time.Sleep(time.Second * RetryDelay)
continue
}
return signedTx.Hash().Hex(), nil
}
return "", fmt.Errorf("failed after %d retries: %v", MaxRetries, lastErr)
}
// Close closes the blockchain connection
func (s *BlockchainService) Close() {
if s.client != nil {
s.client.Close()
}
}
// VoxPopContract is a mock contract interface (to be replaced with actual contract bindings)
type VoxPopContract struct{}