211 lines
5.7 KiB
Go
211 lines
5.7 KiB
Go
![]() |
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{}
|