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{}