mirror of
https://github.com/AthanorLabs/atomic-swap.git
synced 2026-01-09 14:18:03 -05:00
224 lines
5.5 KiB
Go
224 lines
5.5 KiB
Go
package txsender
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
"math/big"
|
|
"sync"
|
|
"time"
|
|
|
|
"github.com/noot/atomic-swap/common"
|
|
"github.com/noot/atomic-swap/common/types"
|
|
"github.com/noot/atomic-swap/swapfactory"
|
|
|
|
"github.com/ethereum/go-ethereum/accounts/abi"
|
|
ethcommon "github.com/ethereum/go-ethereum/common"
|
|
ethtypes "github.com/ethereum/go-ethereum/core/types"
|
|
"github.com/ethereum/go-ethereum/ethclient"
|
|
)
|
|
|
|
var (
|
|
errTransactionTimeout = errors.New("timed out waiting for transaction to be signed")
|
|
errNoSwapWithID = errors.New("no swap with given id")
|
|
|
|
transactionTimeout = time.Minute * 2 // arbitrary, TODO vary this based on env
|
|
)
|
|
|
|
// Transaction represents a transaction to be signed by the front-end
|
|
type Transaction struct {
|
|
To ethcommon.Address
|
|
Data string
|
|
Value string
|
|
}
|
|
|
|
type swapChs struct {
|
|
// outgoing encoded txs to be signed
|
|
out chan *Transaction
|
|
// incoming tx hashes
|
|
in chan ethcommon.Hash
|
|
}
|
|
|
|
// ExternalSender represents a transaction signer and sender that is external to the daemon (ie. a front-end)
|
|
type ExternalSender struct {
|
|
ctx context.Context
|
|
ec *ethclient.Client
|
|
abi *abi.ABI
|
|
contractAddr ethcommon.Address
|
|
|
|
sync.RWMutex
|
|
|
|
swaps map[types.Hash]*swapChs
|
|
}
|
|
|
|
// NewExternalSender returns a new ExternalSender
|
|
func NewExternalSender(ctx context.Context, ec *ethclient.Client,
|
|
contractAddr ethcommon.Address) (*ExternalSender, error) {
|
|
abi, err := swapfactory.SwapFactoryMetaData.GetAbi()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return &ExternalSender{
|
|
ctx: ctx,
|
|
ec: ec,
|
|
abi: abi,
|
|
contractAddr: contractAddr,
|
|
swaps: make(map[types.Hash]*swapChs),
|
|
}, nil
|
|
}
|
|
|
|
// SetContract ...
|
|
func (s *ExternalSender) SetContract(_ *swapfactory.SwapFactory) {}
|
|
|
|
// SetContractAddress ...
|
|
func (s *ExternalSender) SetContractAddress(addr ethcommon.Address) {
|
|
s.contractAddr = addr
|
|
}
|
|
|
|
// OngoingCh returns the channel of outgoing transactions to be signed and submitted
|
|
func (s *ExternalSender) OngoingCh(id types.Hash) (<-chan *Transaction, error) {
|
|
s.RLock()
|
|
defer s.RUnlock()
|
|
chs, has := s.swaps[id]
|
|
if !has {
|
|
return nil, errNoSwapWithID
|
|
}
|
|
|
|
return chs.out, nil
|
|
}
|
|
|
|
// IncomingCh returns the channel of incoming transaction hashes that have been signed and submitted
|
|
func (s *ExternalSender) IncomingCh(id types.Hash) (chan<- ethcommon.Hash, error) {
|
|
s.RLock()
|
|
defer s.RUnlock()
|
|
chs, has := s.swaps[id]
|
|
if !has {
|
|
return nil, errNoSwapWithID
|
|
}
|
|
return chs.in, nil
|
|
}
|
|
|
|
// AddID initialises the sender with a swap w/ the given ID
|
|
func (s *ExternalSender) AddID(id types.Hash) {
|
|
s.Lock()
|
|
defer s.Unlock()
|
|
_, has := s.swaps[id]
|
|
if has {
|
|
return
|
|
}
|
|
|
|
s.swaps[id] = &swapChs{
|
|
out: make(chan *Transaction),
|
|
in: make(chan ethcommon.Hash),
|
|
}
|
|
}
|
|
|
|
// DeleteID deletes the swap w/ the given ID from the sender
|
|
func (s *ExternalSender) DeleteID(id types.Hash) {
|
|
s.Lock()
|
|
defer s.Unlock()
|
|
delete(s.swaps, id)
|
|
}
|
|
|
|
// NewSwap prompts the external sender to sign a new_swap transaction
|
|
func (s *ExternalSender) NewSwap(id types.Hash, _pubKeyClaim [32]byte, _pubKeyRefund [32]byte,
|
|
_claimer ethcommon.Address, _timeoutDuration *big.Int, _nonce *big.Int,
|
|
value *big.Int) (ethcommon.Hash, *ethtypes.Receipt, error) {
|
|
input, err := s.abi.Pack("new_swap", _pubKeyClaim, _pubKeyRefund, _claimer, _timeoutDuration, _nonce)
|
|
if err != nil {
|
|
return ethcommon.Hash{}, nil, err
|
|
}
|
|
|
|
tx := &Transaction{
|
|
To: s.contractAddr,
|
|
Data: fmt.Sprintf("0x%x", input),
|
|
Value: fmt.Sprintf("%v", common.EtherAmount(*value).AsEther()),
|
|
}
|
|
|
|
s.RLock()
|
|
defer s.RUnlock()
|
|
chs, has := s.swaps[id]
|
|
if !has {
|
|
return ethcommon.Hash{}, nil, errNoSwapWithID
|
|
}
|
|
|
|
chs.out <- tx
|
|
var txHash ethcommon.Hash
|
|
select {
|
|
case <-time.After(transactionTimeout):
|
|
return ethcommon.Hash{}, nil, errTransactionTimeout
|
|
case txHash = <-chs.in:
|
|
}
|
|
|
|
receipt, err := waitForReceipt(s.ctx, s.ec, txHash)
|
|
if err != nil {
|
|
return ethcommon.Hash{}, nil, err
|
|
}
|
|
|
|
return txHash, receipt, nil
|
|
}
|
|
|
|
// SetReady prompts the external sender to sign a set_ready transaction
|
|
func (s *ExternalSender) SetReady(id types.Hash,
|
|
_swap swapfactory.SwapFactorySwap) (ethcommon.Hash, *ethtypes.Receipt, error) {
|
|
input, err := s.abi.Pack("set_ready", _swap)
|
|
if err != nil {
|
|
return ethcommon.Hash{}, nil, err
|
|
}
|
|
|
|
return s.sendAndReceive(id, input)
|
|
}
|
|
|
|
// Claim prompts the external sender to sign a claim transaction
|
|
func (s *ExternalSender) Claim(id types.Hash, _swap swapfactory.SwapFactorySwap,
|
|
_s [32]byte) (ethcommon.Hash, *ethtypes.Receipt, error) {
|
|
input, err := s.abi.Pack("claim", _swap, _s)
|
|
if err != nil {
|
|
return ethcommon.Hash{}, nil, err
|
|
}
|
|
|
|
return s.sendAndReceive(id, input)
|
|
}
|
|
|
|
// Refund prompts the external sender to sign a refund transaction
|
|
func (s *ExternalSender) Refund(id types.Hash, _swap swapfactory.SwapFactorySwap,
|
|
_s [32]byte) (ethcommon.Hash, *ethtypes.Receipt, error) {
|
|
input, err := s.abi.Pack("refund", _swap, _s)
|
|
if err != nil {
|
|
return ethcommon.Hash{}, nil, err
|
|
}
|
|
|
|
return s.sendAndReceive(id, input)
|
|
}
|
|
|
|
func (s *ExternalSender) sendAndReceive(id types.Hash,
|
|
input []byte) (ethcommon.Hash, *ethtypes.Receipt, error) {
|
|
tx := &Transaction{
|
|
To: s.contractAddr,
|
|
Data: fmt.Sprintf("0x%x", input),
|
|
}
|
|
|
|
s.RLock()
|
|
defer s.RUnlock()
|
|
chs, has := s.swaps[id]
|
|
if !has {
|
|
return ethcommon.Hash{}, nil, errNoSwapWithID
|
|
}
|
|
|
|
chs.out <- tx
|
|
var txHash ethcommon.Hash
|
|
select {
|
|
case <-time.After(transactionTimeout):
|
|
return ethcommon.Hash{}, nil, errTransactionTimeout
|
|
case txHash = <-chs.in:
|
|
}
|
|
|
|
receipt, err := waitForReceipt(s.ctx, s.ec, txHash)
|
|
if err != nil {
|
|
return ethcommon.Hash{}, nil, err
|
|
}
|
|
|
|
return txHash, receipt, nil
|
|
}
|