-
Notifications
You must be signed in to change notification settings - Fork 0
/
evmsigner.go
141 lines (122 loc) · 3.79 KB
/
evmsigner.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
package util
import (
"context"
"crypto/ecdsa"
"errors"
"fmt"
"math/big"
"time"
"github.com/ethereum/go-ethereum"
"github.com/ethereum/go-ethereum/accounts/abi/bind"
"github.com/ethereum/go-ethereum/common"
ethtypes "github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/ethclient"
)
var (
ErrEvmBroadcastTimeout = errors.New("timed out waiting for tx to be committed to block")
// ErrEvmTxFailed is returned when a tx is committed to a block, but the receipt status is 0.
// this means the tx failed. we don't have debug_traceTransaction RPC command so the best way
// to determine the problem is to attempt to make the tx manually.
ErrEvmTxFailed = errors.New("transaction was committed but failed. likely an execution revert by contract code")
)
type EvmTxRequest struct {
Tx *ethtypes.Transaction
Data interface{}
}
type EvmTxResponse struct {
Request EvmTxRequest
TxHash common.Hash
Err error
}
type ErrEvmFailedToSign struct{ Err error }
func (e ErrEvmFailedToSign) Error() string {
return fmt.Sprintf("failed to sign tx: %s", e.Err)
}
type ErrEvmFailedToBroadcast struct{ Err error }
func (e ErrEvmFailedToBroadcast) Error() string {
return fmt.Sprintf("failed to broadcast tx: %s", e.Err)
}
// EvmSigner manages signing and broadcasting requests to transfer Erc20 tokens
// Will work for calling all contracts that have func signature `transfer(address,uint256)`
type EvmSigner struct {
signerAddress common.Address
Auth *bind.TransactOpts
EvmClient *ethclient.Client
}
func NewEvmSigner(
evmClient *ethclient.Client,
privKey *ecdsa.PrivateKey,
chainId *big.Int,
) (*EvmSigner, error) {
auth, err := bind.NewKeyedTransactorWithChainID(privKey, chainId)
if err != nil {
return &EvmSigner{}, err
}
publicKey := privKey.Public()
publicKeyECDSA, ok := publicKey.(*ecdsa.PublicKey)
if !ok {
return &EvmSigner{}, fmt.Errorf("cannot assert type: publicKey is not of type *ecdsa.PublicKey")
}
return &EvmSigner{
Auth: auth,
signerAddress: crypto.PubkeyToAddress(*publicKeyECDSA),
EvmClient: evmClient,
}, nil
}
func (s *EvmSigner) Run(requests <-chan EvmTxRequest) <-chan EvmTxResponse {
responses := make(chan EvmTxResponse)
// receive tx requests, sign & broadcast them.
// Responses are sent once the tx is added to the pending tx pool.
// To see result, use TransactionReceipt after tx has been included in a block.
go func() {
for {
// wait for incoming request
req := <-requests
signedTx, err := s.Auth.Signer(s.signerAddress, req.Tx)
if err != nil {
err = ErrEvmFailedToSign{Err: err}
} else {
err = s.EvmClient.SendTransaction(context.Background(), signedTx)
if err != nil {
err = ErrEvmFailedToBroadcast{Err: err}
}
}
responses <- EvmTxResponse{
Request: req,
TxHash: signedTx.Hash(),
Err: err,
}
}
}()
return responses
}
func (s *EvmSigner) Address() common.Address {
return s.signerAddress
}
// WaitForEvmTxReceipt polls for a tx receipt and errors on timeout.
// If the receipt comes back, but with status 0 (failed), an error is returned.
func WaitForEvmTxReceipt(client *ethclient.Client, txHash common.Hash, timeout time.Duration) (*ethtypes.Receipt, error) {
var receipt *ethtypes.Receipt
var err error
outOfTime := time.After(timeout)
for {
select {
case <-outOfTime:
err = ErrEvmBroadcastTimeout
default:
receipt, err = client.TransactionReceipt(context.Background(), txHash)
if errors.Is(err, ethereum.NotFound) {
// tx still not committed to a block. retry!
time.Sleep(100 * time.Millisecond)
continue
}
// a response status of 0 means the tx was successfully committed but failed to execute
if receipt.Status == 0 {
err = ErrEvmTxFailed
}
}
break
}
return receipt, err
}