mirror of
https://github.com/AthanorLabs/atomic-swap.git
synced 2026-01-08 21:58:07 -05:00
ui: metamask integration, swapd: implement external sender for front-end integration (#126)
This commit is contained in:
@@ -288,6 +288,11 @@ func runMake(ctx *cli.Context) error {
|
||||
fmt.Printf("Made offer with ID=%s\n", id)
|
||||
|
||||
taken := <-takenCh
|
||||
if taken == nil {
|
||||
fmt.Printf("connection closed\n")
|
||||
return nil
|
||||
}
|
||||
|
||||
fmt.Printf("Offer taken! Swap ID=%d\n", taken.ID)
|
||||
|
||||
for stage := range statusCh {
|
||||
|
||||
@@ -2,6 +2,7 @@ package main
|
||||
|
||||
import (
|
||||
"crypto/ecdsa"
|
||||
"errors"
|
||||
"fmt"
|
||||
"math/big"
|
||||
|
||||
@@ -15,6 +16,10 @@ import (
|
||||
"github.com/ethereum/go-ethereum/ethclient"
|
||||
)
|
||||
|
||||
var (
|
||||
errNoEthereumPrivateKey = errors.New("must provide --ethereum-privkey file for non-development environment")
|
||||
)
|
||||
|
||||
func getOrDeploySwapFactory(address ethcommon.Address, env common.Environment, basepath string, chainID *big.Int,
|
||||
privkey *ecdsa.PrivateKey, ec *ethclient.Client) (*swapfactory.SwapFactory, ethcommon.Address, error) {
|
||||
var (
|
||||
@@ -22,6 +27,10 @@ func getOrDeploySwapFactory(address ethcommon.Address, env common.Environment, b
|
||||
)
|
||||
|
||||
if env != common.Mainnet && (address == ethcommon.Address{}) {
|
||||
if privkey == nil {
|
||||
return nil, ethcommon.Address{}, errNoEthereumPrivateKey
|
||||
}
|
||||
|
||||
txOpts, err := bind.NewKeyedTransactorWithChainID(privkey, chainID)
|
||||
if err != nil {
|
||||
return nil, ethcommon.Address{}, fmt.Errorf("failed to make transactor: %w", err)
|
||||
|
||||
@@ -2,6 +2,7 @@ package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/ecdsa"
|
||||
"errors"
|
||||
"fmt"
|
||||
"math/big"
|
||||
@@ -69,6 +70,7 @@ const (
|
||||
flagContractAddress = "contract-address"
|
||||
flagGasPrice = "gas-price"
|
||||
flagGasLimit = "gas-limit"
|
||||
flagUseExternalSigner = "external-signer"
|
||||
|
||||
flagDevXMRTaker = "dev-xmrtaker"
|
||||
flagDevXMRMaker = "dev-xmrmaker"
|
||||
@@ -172,6 +174,10 @@ var (
|
||||
Name: flagLog,
|
||||
Usage: "set log level: one of [error|warn|info|debug]",
|
||||
},
|
||||
&cli.BoolFlag{
|
||||
Name: flagUseExternalSigner,
|
||||
Usage: "use external signer, for usage with the swap UI",
|
||||
},
|
||||
},
|
||||
}
|
||||
)
|
||||
@@ -406,11 +412,19 @@ func newBackend(ctx context.Context, c *cli.Context, env common.Environment, cfg
|
||||
ethEndpoint = common.DefaultEthEndpoint
|
||||
}
|
||||
|
||||
ethPrivKey, err := utils.GetEthereumPrivateKey(c, env, devXMRMaker)
|
||||
ethPrivKey, err := utils.GetEthereumPrivateKey(c, env, devXMRMaker, c.Bool(flagUseExternalSigner))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var pk *ecdsa.PrivateKey
|
||||
if ethPrivKey != "" {
|
||||
pk, err = ethcrypto.HexToECDSA(ethPrivKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
if c.String(flagMoneroDaemonEndpoint) != "" {
|
||||
daemonEndpoint = c.String(flagMoneroDaemonEndpoint)
|
||||
} else {
|
||||
@@ -431,11 +445,6 @@ func newBackend(ctx context.Context, c *cli.Context, env common.Environment, cfg
|
||||
contractAddr = ethcommon.HexToAddress(contractAddrStr)
|
||||
}
|
||||
|
||||
pk, err := ethcrypto.HexToECDSA(ethPrivKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ec, err := ethclient.Dial(ethEndpoint)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
||||
@@ -263,7 +263,8 @@ func createBackend(ctx context.Context, c *cli.Context, env common.Environment,
|
||||
ethEndpoint = common.DefaultEthEndpoint
|
||||
}
|
||||
|
||||
ethPrivKey, err := utils.GetEthereumPrivateKey(c, env, false)
|
||||
// TODO: add --external-signer option to allow front-end integration
|
||||
ethPrivKey, err := utils.GetEthereumPrivateKey(c, env, false, false)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -27,7 +27,8 @@ var (
|
||||
)
|
||||
|
||||
// GetEthereumPrivateKey returns an ethereum private key hex string given the CLI options.
|
||||
func GetEthereumPrivateKey(c *cli.Context, env common.Environment, devXMRMaker bool) (ethPrivKey string, err error) {
|
||||
func GetEthereumPrivateKey(c *cli.Context, env common.Environment, devXMRMaker,
|
||||
useExternal bool) (ethPrivKey string, err error) {
|
||||
if c.String(flagEthereumPrivKey) != "" {
|
||||
ethPrivKeyFile := c.String(flagEthereumPrivKey)
|
||||
key, err := os.ReadFile(filepath.Clean(ethPrivKeyFile))
|
||||
@@ -41,9 +42,10 @@ func GetEthereumPrivateKey(c *cli.Context, env common.Environment, devXMRMaker b
|
||||
|
||||
ethPrivKey = string(key)
|
||||
} else {
|
||||
if env != common.Development {
|
||||
if env != common.Development || useExternal {
|
||||
// TODO: allow this to be set via RPC
|
||||
return "", errNoEthereumPrivateKey
|
||||
log.Warnf("%s", errNoEthereumPrivateKey)
|
||||
return "", nil
|
||||
}
|
||||
|
||||
log.Warn("no ethereum private key file provided, using ganache deterministic key")
|
||||
|
||||
@@ -61,3 +61,24 @@ type MakeOfferResponse struct {
|
||||
ID string `json:"offerID"`
|
||||
InfoFile string `json:"infoFile"`
|
||||
}
|
||||
|
||||
// SignerRequest initiates the signer_subscribe handler from the front-end
|
||||
type SignerRequest struct {
|
||||
OfferID string `json:"offerID"`
|
||||
EthAddress string `json:"ethAddress"`
|
||||
XMRAddress string `json:"xmrAddress"`
|
||||
}
|
||||
|
||||
// SignerResponse sends a tx to be signed to the front-end
|
||||
type SignerResponse struct {
|
||||
OfferID string `json:"offerID"`
|
||||
To string `json:"to"`
|
||||
Data string `json:"data"`
|
||||
Value string `json:"value"`
|
||||
}
|
||||
|
||||
// SignerTxSigned is a response from the front-end saying the given tx has been submitted successfully
|
||||
type SignerTxSigned struct {
|
||||
OfferID string `json:"offerID"`
|
||||
TxHash string `json:"txHash"`
|
||||
}
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
package mcrypto
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/noot/atomic-swap/common"
|
||||
"github.com/noot/atomic-swap/crypto"
|
||||
)
|
||||
@@ -8,11 +10,25 @@ import (
|
||||
const (
|
||||
addressPrefixMainnet byte = 18
|
||||
addressPrefixStagenet byte = 24
|
||||
|
||||
// AddressLength is the length of a Monero address
|
||||
AddressLength = 1 + 32 + 32 + 4
|
||||
)
|
||||
|
||||
// Address represents a base58-encoded string
|
||||
type Address string
|
||||
|
||||
// ValidateAddress checks if the given address is valid
|
||||
// TODO: also check chain prefix
|
||||
func ValidateAddress(addr string) error {
|
||||
b := DecodeMoneroBase58(addr)
|
||||
if len(b) != AddressLength {
|
||||
return fmt.Errorf("invalid monero address length: got %d, expected %d", len(b), AddressLength)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func getChecksum(data ...[]byte) (result [4]byte) {
|
||||
keccak256 := crypto.Keccak256(data...)
|
||||
copy(result[:], keccak256[:4])
|
||||
|
||||
@@ -61,7 +61,7 @@ make build
|
||||
|
||||
10. Copy `goerli.key` into this directory. If you are using an Infura Goerli endpoint, copy-paste your API key into the field below following the `--ethereum-endpoint` flag. Otherwise, change `--ethereum-endpoint` to point to your endpoint. Finally, start the `swapd` atomic swap daemon process:
|
||||
```bash
|
||||
./swapd --env stagenet --ethereum-privkey=goerli.key --monero-endpoint=http://localhost:18083/json_rpc --wallet-file=stagenet-wallet --ethereum-endpoint=https://goerli.infura.io/v3/<your-api-key> --ethereum-chain-id=5 --contract-address=0xe532f0C720dCD102854281aeF1a8Be01f464C8fE --bootnodes /ip4/134.122.115.208/tcp/9900/p2p/12D3KooWDqCzbjexHEa8Rut7bzxHFpRMZyDRW1L6TGkL1KY24JH5,/ip4/143.198.123.27/tcp/9900/p2p/12D3KooWSc4yFkPWBFmPToTMbhChH3FAgGH96DNzSg5fio1pQYoN,/ip4/67.207.89.83/tcp/9900/p2p/12D3KooWLbfkLZZvvn8Lxs1KDU3u7gyvBk88ZNtJBbugytBr5RCG,/ip4/134.122.115.208/tcp/9900/p2p/12D3KooWDqCzbjexHEa8Rut7bzxHFpRMZyDRW1L6TGkL1KY24JH5,/ip4/164.92.103.160/tcp/9900/p2p/12D3KooWAZtRECEv7zN69zU1e7sPrHbMgfqFUn7QTLh1pKGiMuaM,/ip4/164.92.103.159/tcp/9900/p2p/12D3KooWSNQF1eNyapxC2zA3jJExgLX7jWhEyw8B3k7zMW5ZRvQz,/ip4/164.92.123.10/tcp/9900/p2p/12D3KooWG8z9fXVTB72XL8hQbahpfEjutREL9vbBQ4FzqtDKzTBu,/ip4/161.35.110.210/tcp/9900/p2p/12D3KooWS8iKxqsGTiL3Yc1VaAfg99U5km1AE7bWYQiuavXj3Yz6,/ip4/206.189.47.220/tcp/9900/p2p/12D3KooWGVzz2d2LSceVFFdqTYqmQXTqc5eWziw7PLRahCWGJhKB --rpc-port=5001
|
||||
./swapd --env stagenet --ethereum-privkey=goerli.key --monero-endpoint=http://localhost:18083/json_rpc --wallet-file=stagenet-wallet --ethereum-endpoint=https://goerli.infura.io/v3/<your-api-key> --ethereum-chain-id=5 --contract-address=0x0adc492ADe62c4BbE8c517D4B735B5268Bbf0552 --bootnodes /ip4/134.122.115.208/tcp/9900/p2p/12D3KooWDqCzbjexHEa8Rut7bzxHFpRMZyDRW1L6TGkL1KY24JH5,/ip4/143.198.123.27/tcp/9900/p2p/12D3KooWSc4yFkPWBFmPToTMbhChH3FAgGH96DNzSg5fio1pQYoN,/ip4/67.207.89.83/tcp/9900/p2p/12D3KooWLbfkLZZvvn8Lxs1KDU3u7gyvBk88ZNtJBbugytBr5RCG,/ip4/134.122.115.208/tcp/9900/p2p/12D3KooWDqCzbjexHEa8Rut7bzxHFpRMZyDRW1L6TGkL1KY24JH5,/ip4/164.92.103.160/tcp/9900/p2p/12D3KooWAZtRECEv7zN69zU1e7sPrHbMgfqFUn7QTLh1pKGiMuaM,/ip4/164.92.103.159/tcp/9900/p2p/12D3KooWSNQF1eNyapxC2zA3jJExgLX7jWhEyw8B3k7zMW5ZRvQz,/ip4/164.92.123.10/tcp/9900/p2p/12D3KooWG8z9fXVTB72XL8hQbahpfEjutREL9vbBQ4FzqtDKzTBu,/ip4/161.35.110.210/tcp/9900/p2p/12D3KooWS8iKxqsGTiL3Yc1VaAfg99U5km1AE7bWYQiuavXj3Yz6,/ip4/206.189.47.220/tcp/9900/p2p/12D3KooWGVzz2d2LSceVFFdqTYqmQXTqc5eWziw7PLRahCWGJhKB --rpc-port=5001
|
||||
```
|
||||
|
||||
> Note: please also see the [RPC documentation](./rpc.md) for complete documentation on available RPC calls and their parameters.
|
||||
|
||||
@@ -192,8 +192,8 @@ func (h *host) logPeers() {
|
||||
return
|
||||
}
|
||||
|
||||
log.Infof("peer count: %d", len(h.h.Network().Peers()))
|
||||
time.Sleep(time.Second * 30)
|
||||
log.Debugf("peer count: %d", len(h.h.Network().Peers()))
|
||||
time.Sleep(time.Minute)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -13,9 +13,11 @@ import (
|
||||
"github.com/ethereum/go-ethereum/ethclient"
|
||||
|
||||
"github.com/noot/atomic-swap/common"
|
||||
mcrypto "github.com/noot/atomic-swap/crypto/monero"
|
||||
"github.com/noot/atomic-swap/monero"
|
||||
"github.com/noot/atomic-swap/net"
|
||||
"github.com/noot/atomic-swap/protocol/swap"
|
||||
"github.com/noot/atomic-swap/protocol/txsender"
|
||||
"github.com/noot/atomic-swap/swapfactory"
|
||||
|
||||
logging "github.com/ipfs/go-log"
|
||||
@@ -38,6 +40,7 @@ type Backend interface {
|
||||
monero.Client
|
||||
monero.DaemonClient
|
||||
net.MessageSender
|
||||
txsender.Sender
|
||||
|
||||
// ethclient methods
|
||||
BalanceAt(ctx context.Context, account ethcommon.Address, blockNumber *big.Int) (*big.Int, error)
|
||||
@@ -61,10 +64,16 @@ type Backend interface {
|
||||
ContractAddr() ethcommon.Address
|
||||
Net() net.MessageSender
|
||||
SwapTimeout() time.Duration
|
||||
ExternalSender() *txsender.ExternalSender
|
||||
XMRDepositAddress() mcrypto.Address
|
||||
|
||||
// setters
|
||||
SetSwapTimeout(timeout time.Duration)
|
||||
SetGasPrice(uint64)
|
||||
SetEthAddress(ethcommon.Address)
|
||||
SetXMRDepositAddress(mcrypto.Address)
|
||||
SetContract(*swapfactory.SwapFactory)
|
||||
SetContractAddress(ethcommon.Address)
|
||||
}
|
||||
|
||||
type backend struct {
|
||||
@@ -76,6 +85,9 @@ type backend struct {
|
||||
monero.Client
|
||||
monero.DaemonClient
|
||||
|
||||
// monero deposit address (used if xmrtaker has transferBack set to true)
|
||||
xmrDepositAddr mcrypto.Address
|
||||
|
||||
// ethereum endpoint and variables
|
||||
ethClient *ethclient.Client
|
||||
ethPrivKey *ecdsa.PrivateKey
|
||||
@@ -84,6 +96,7 @@ type backend struct {
|
||||
chainID *big.Int
|
||||
gasPrice *big.Int
|
||||
gasLimit uint64
|
||||
txsender.Sender
|
||||
|
||||
// swap contract
|
||||
contract *swapfactory.SwapFactory
|
||||
@@ -127,7 +140,26 @@ func NewBackend(cfg *Config) (Backend, error) {
|
||||
defaultTimeoutDuration = time.Hour
|
||||
}
|
||||
|
||||
addr := common.EthereumPrivateKeyToAddress(cfg.EthereumPrivateKey)
|
||||
var (
|
||||
addr ethcommon.Address
|
||||
sender txsender.Sender
|
||||
)
|
||||
if cfg.EthereumPrivateKey != nil {
|
||||
txOpts, err := bind.NewKeyedTransactorWithChainID(cfg.EthereumPrivateKey, cfg.ChainID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
addr = common.EthereumPrivateKeyToAddress(cfg.EthereumPrivateKey)
|
||||
sender = txsender.NewSenderWithPrivateKey(cfg.Ctx, cfg.EthereumClient, cfg.SwapContract, txOpts)
|
||||
} else {
|
||||
log.Debugf("instantiated backend with external sender")
|
||||
var err error
|
||||
sender, err = txsender.NewExternalSender(cfg.Ctx, cfg.EthereumClient, cfg.SwapContractAddress)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
// monero-wallet-rpc client
|
||||
walletClient := monero.NewClient(cfg.MoneroWalletEndpoint)
|
||||
@@ -153,6 +185,7 @@ func NewBackend(cfg *Config) (Backend, error) {
|
||||
From: addr,
|
||||
Context: cfg.Ctx,
|
||||
},
|
||||
Sender: sender,
|
||||
ethAddress: addr,
|
||||
chainID: cfg.ChainID,
|
||||
gasPrice: cfg.GasPrice,
|
||||
@@ -197,6 +230,15 @@ func (b *backend) EthClient() *ethclient.Client {
|
||||
return b.ethClient
|
||||
}
|
||||
|
||||
func (b *backend) ExternalSender() *txsender.ExternalSender {
|
||||
s, ok := b.Sender.(*txsender.ExternalSender)
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
|
||||
return s
|
||||
}
|
||||
|
||||
func (b *backend) Net() net.MessageSender {
|
||||
return b.MessageSender
|
||||
}
|
||||
@@ -247,6 +289,10 @@ func (b *backend) TxOpts() (*bind.TransactOpts, error) {
|
||||
return txOpts, nil
|
||||
}
|
||||
|
||||
func (b *backend) XMRDepositAddress() mcrypto.Address {
|
||||
return b.xmrDepositAddr
|
||||
}
|
||||
|
||||
// WaitForReceipt waits for the receipt for the given transaction to be available and returns it.
|
||||
func (b *backend) WaitForReceipt(ctx context.Context, txHash ethcommon.Hash) (*types.Receipt, error) {
|
||||
for i := 0; i < maxRetries; i++ {
|
||||
@@ -272,3 +318,24 @@ func (b *backend) WaitForReceipt(ctx context.Context, txHash ethcommon.Hash) (*t
|
||||
func (b *backend) NewSwapFactory(addr ethcommon.Address) (*swapfactory.SwapFactory, error) {
|
||||
return swapfactory.NewSwapFactory(addr, b.ethClient)
|
||||
}
|
||||
|
||||
func (b *backend) SetEthAddress(addr ethcommon.Address) {
|
||||
if b.ExternalSender() == nil {
|
||||
return
|
||||
}
|
||||
|
||||
b.ethAddress = addr
|
||||
}
|
||||
|
||||
func (b *backend) SetXMRDepositAddress(addr mcrypto.Address) {
|
||||
b.xmrDepositAddr = addr
|
||||
}
|
||||
|
||||
func (b *backend) SetContract(contract *swapfactory.SwapFactory) {
|
||||
b.contract = contract
|
||||
b.Sender.SetContract(contract)
|
||||
}
|
||||
|
||||
func (b *backend) SetContractAddress(addr ethcommon.Address) {
|
||||
b.contractAddr = addr
|
||||
}
|
||||
|
||||
164
protocol/txsender/external_sender.go
Normal file
164
protocol/txsender/external_sender.go
Normal file
@@ -0,0 +1,164 @@
|
||||
package txsender
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"math/big"
|
||||
"time"
|
||||
|
||||
"github.com/noot/atomic-swap/common"
|
||||
"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")
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
// 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
|
||||
|
||||
// outgoing encoded txs to be signed
|
||||
out chan *Transaction
|
||||
|
||||
// incoming tx hashes
|
||||
in chan ethcommon.Hash
|
||||
}
|
||||
|
||||
// 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,
|
||||
out: make(chan *Transaction),
|
||||
in: make(chan ethcommon.Hash),
|
||||
}, 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() <-chan *Transaction {
|
||||
return s.out
|
||||
}
|
||||
|
||||
// IncomingCh returns the channel of incoming transaction hashes that have been signed and submitted
|
||||
func (s *ExternalSender) IncomingCh() chan<- ethcommon.Hash {
|
||||
return s.in
|
||||
}
|
||||
|
||||
// NewSwap prompts the external sender to sign a new_swap transaction
|
||||
func (s *ExternalSender) NewSwap(_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.out <- tx
|
||||
var txHash ethcommon.Hash
|
||||
select {
|
||||
case <-time.After(transactionTimeout):
|
||||
return ethcommon.Hash{}, nil, errTransactionTimeout
|
||||
case txHash = <-s.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(_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(input)
|
||||
}
|
||||
|
||||
// Claim prompts the external sender to sign a claim transaction
|
||||
func (s *ExternalSender) Claim(_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(input)
|
||||
}
|
||||
|
||||
// Refund prompts the external sender to sign a refund transaction
|
||||
func (s *ExternalSender) Refund(_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(input)
|
||||
}
|
||||
|
||||
func (s *ExternalSender) sendAndReceive(input []byte) (ethcommon.Hash, *ethtypes.Receipt, error) {
|
||||
tx := &Transaction{
|
||||
To: s.contractAddr,
|
||||
Data: fmt.Sprintf("0x%x", input),
|
||||
}
|
||||
|
||||
s.out <- tx
|
||||
var txHash ethcommon.Hash
|
||||
select {
|
||||
case <-time.After(transactionTimeout):
|
||||
return ethcommon.Hash{}, nil, errTransactionTimeout
|
||||
case txHash = <-s.in:
|
||||
}
|
||||
|
||||
receipt, err := waitForReceipt(s.ctx, s.ec, txHash)
|
||||
if err != nil {
|
||||
return ethcommon.Hash{}, nil, err
|
||||
}
|
||||
|
||||
return txHash, receipt, nil
|
||||
}
|
||||
137
protocol/txsender/sender.go
Normal file
137
protocol/txsender/sender.go
Normal file
@@ -0,0 +1,137 @@
|
||||
package txsender
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"math/big"
|
||||
"time"
|
||||
|
||||
"github.com/noot/atomic-swap/swapfactory"
|
||||
|
||||
"github.com/ethereum/go-ethereum/accounts/abi/bind"
|
||||
ethcommon "github.com/ethereum/go-ethereum/common"
|
||||
ethtypes "github.com/ethereum/go-ethereum/core/types"
|
||||
"github.com/ethereum/go-ethereum/ethclient"
|
||||
)
|
||||
|
||||
const (
|
||||
maxRetries = 360
|
||||
receiptSleepDuration = time.Second * 10
|
||||
)
|
||||
|
||||
var (
|
||||
errReceiptTimeOut = errors.New("failed to get receipt, timed out")
|
||||
)
|
||||
|
||||
// Sender signs and submits transactions to the chain
|
||||
type Sender interface {
|
||||
SetContract(*swapfactory.SwapFactory)
|
||||
SetContractAddress(ethcommon.Address)
|
||||
NewSwap(_pubKeyClaim [32]byte, _pubKeyRefund [32]byte, _claimer ethcommon.Address, _timeoutDuration *big.Int,
|
||||
_nonce *big.Int, amount *big.Int) (ethcommon.Hash, *ethtypes.Receipt, error)
|
||||
SetReady(_swap swapfactory.SwapFactorySwap) (ethcommon.Hash, *ethtypes.Receipt, error)
|
||||
Claim(_swap swapfactory.SwapFactorySwap, _s [32]byte) (ethcommon.Hash, *ethtypes.Receipt, error)
|
||||
Refund(_swap swapfactory.SwapFactorySwap, _s [32]byte) (ethcommon.Hash, *ethtypes.Receipt, error)
|
||||
}
|
||||
|
||||
type privateKeySender struct {
|
||||
ctx context.Context
|
||||
ec *ethclient.Client
|
||||
contract *swapfactory.SwapFactory
|
||||
txOpts *bind.TransactOpts
|
||||
}
|
||||
|
||||
// NewSenderWithPrivateKey returns a new *privateKeySender
|
||||
func NewSenderWithPrivateKey(ctx context.Context, ec *ethclient.Client, contract *swapfactory.SwapFactory,
|
||||
txOpts *bind.TransactOpts) Sender {
|
||||
return &privateKeySender{
|
||||
ctx: ctx,
|
||||
ec: ec,
|
||||
contract: contract,
|
||||
txOpts: txOpts,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *privateKeySender) SetContract(contract *swapfactory.SwapFactory) {
|
||||
s.contract = contract
|
||||
}
|
||||
|
||||
func (s *privateKeySender) SetContractAddress(_ ethcommon.Address) {}
|
||||
|
||||
func (s *privateKeySender) NewSwap(_pubKeyClaim [32]byte, _pubKeyRefund [32]byte, _claimer ethcommon.Address,
|
||||
_timeoutDuration *big.Int, _nonce *big.Int, value *big.Int) (ethcommon.Hash, *ethtypes.Receipt, error) {
|
||||
s.txOpts.Value = value
|
||||
defer func() {
|
||||
s.txOpts.Value = nil
|
||||
}()
|
||||
|
||||
tx, err := s.contract.NewSwap(s.txOpts, _pubKeyClaim, _pubKeyRefund, _claimer, _timeoutDuration, _nonce)
|
||||
if err != nil {
|
||||
return ethcommon.Hash{}, nil, err
|
||||
}
|
||||
|
||||
receipt, err := waitForReceipt(s.ctx, s.ec, tx.Hash())
|
||||
if err != nil {
|
||||
return ethcommon.Hash{}, nil, err
|
||||
}
|
||||
|
||||
return tx.Hash(), receipt, nil
|
||||
}
|
||||
|
||||
func (s *privateKeySender) SetReady(_swap swapfactory.SwapFactorySwap) (ethcommon.Hash, *ethtypes.Receipt, error) {
|
||||
tx, err := s.contract.SetReady(s.txOpts, _swap)
|
||||
if err != nil {
|
||||
return ethcommon.Hash{}, nil, err
|
||||
}
|
||||
|
||||
receipt, err := waitForReceipt(s.ctx, s.ec, tx.Hash())
|
||||
if err != nil {
|
||||
return ethcommon.Hash{}, nil, err
|
||||
}
|
||||
|
||||
return tx.Hash(), receipt, nil
|
||||
}
|
||||
|
||||
func (s *privateKeySender) Claim(_swap swapfactory.SwapFactorySwap,
|
||||
_s [32]byte) (ethcommon.Hash, *ethtypes.Receipt, error) {
|
||||
tx, err := s.contract.Claim(s.txOpts, _swap, _s)
|
||||
if err != nil {
|
||||
return ethcommon.Hash{}, nil, err
|
||||
}
|
||||
|
||||
receipt, err := waitForReceipt(s.ctx, s.ec, tx.Hash())
|
||||
if err != nil {
|
||||
return ethcommon.Hash{}, nil, err
|
||||
}
|
||||
|
||||
return tx.Hash(), receipt, nil
|
||||
}
|
||||
|
||||
func (s *privateKeySender) Refund(_swap swapfactory.SwapFactorySwap,
|
||||
_s [32]byte) (ethcommon.Hash, *ethtypes.Receipt, error) {
|
||||
tx, err := s.contract.Refund(s.txOpts, _swap, _s)
|
||||
if err != nil {
|
||||
return ethcommon.Hash{}, nil, err
|
||||
}
|
||||
|
||||
receipt, err := waitForReceipt(s.ctx, s.ec, tx.Hash())
|
||||
if err != nil {
|
||||
return ethcommon.Hash{}, nil, err
|
||||
}
|
||||
|
||||
return tx.Hash(), receipt, nil
|
||||
}
|
||||
|
||||
func waitForReceipt(ctx context.Context, ec *ethclient.Client, txHash ethcommon.Hash) (*ethtypes.Receipt, error) {
|
||||
for i := 0; i < maxRetries; i++ {
|
||||
receipt, err := ec.TransactionReceipt(ctx, txHash)
|
||||
if err != nil {
|
||||
time.Sleep(receiptSleepDuration)
|
||||
continue
|
||||
}
|
||||
|
||||
return receipt, nil
|
||||
}
|
||||
|
||||
return nil, errReceiptTimeOut
|
||||
}
|
||||
@@ -150,7 +150,7 @@ func (s *swapState) handleNotifyETHLocked(msg *message.NotifyETHLocked) (net.Mes
|
||||
}
|
||||
|
||||
contractAddr := ethcommon.HexToAddress(msg.Address)
|
||||
if err := checkContractCode(s.ctx, s.backend, contractAddr); err != nil {
|
||||
if err := checkContractCode(s.ctx, s, contractAddr); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -208,7 +208,7 @@ func (s *swapState) handleNotifyETHLocked(msg *message.NotifyETHLocked) (net.Mes
|
||||
s.clearNextExpectedMessage(types.CompletedSuccess)
|
||||
|
||||
// send *message.NotifyClaimed
|
||||
if err := s.backend.SendSwapMessage(&message.NotifyClaimed{
|
||||
if err := s.SendSwapMessage(&message.NotifyClaimed{
|
||||
TxHash: txHash.String(),
|
||||
}); err != nil {
|
||||
log.Errorf("failed to send NotifyClaimed message: err=%s", err)
|
||||
@@ -244,7 +244,7 @@ func (s *swapState) handleSendKeysMessage(msg *net.SendKeysMessage) error {
|
||||
}
|
||||
|
||||
func (s *swapState) handleRefund(txHash string) (mcrypto.Address, error) {
|
||||
receipt, err := s.backend.TransactionReceipt(s.ctx, ethcommon.HexToHash(txHash))
|
||||
receipt, err := s.TransactionReceipt(s.ctx, ethcommon.HexToHash(txHash))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
@@ -21,6 +21,7 @@ import (
|
||||
net "github.com/noot/atomic-swap/net"
|
||||
message "github.com/noot/atomic-swap/net/message"
|
||||
swap "github.com/noot/atomic-swap/protocol/swap"
|
||||
txsender "github.com/noot/atomic-swap/protocol/txsender"
|
||||
swapfactory "github.com/noot/atomic-swap/swapfactory"
|
||||
)
|
||||
|
||||
@@ -90,6 +91,22 @@ func (mr *MockBackendMockRecorder) ChainID() *gomock.Call {
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ChainID", reflect.TypeOf((*MockBackend)(nil).ChainID))
|
||||
}
|
||||
|
||||
// Claim mocks base method.
|
||||
func (m *MockBackend) Claim(arg0 swapfactory.SwapFactorySwap, arg1 [32]byte) (common.Hash, *types.Receipt, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "Claim", arg0, arg1)
|
||||
ret0, _ := ret[0].(common.Hash)
|
||||
ret1, _ := ret[1].(*types.Receipt)
|
||||
ret2, _ := ret[2].(error)
|
||||
return ret0, ret1, ret2
|
||||
}
|
||||
|
||||
// Claim indicates an expected call of Claim.
|
||||
func (mr *MockBackendMockRecorder) Claim(arg0, arg1 interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Claim", reflect.TypeOf((*MockBackend)(nil).Claim), arg0, arg1)
|
||||
}
|
||||
|
||||
// CloseWallet mocks base method.
|
||||
func (m *MockBackend) CloseWallet() error {
|
||||
m.ctrl.T.Helper()
|
||||
@@ -203,6 +220,20 @@ func (mr *MockBackendMockRecorder) EthAddress() *gomock.Call {
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "EthAddress", reflect.TypeOf((*MockBackend)(nil).EthAddress))
|
||||
}
|
||||
|
||||
// ExternalSender mocks base method.
|
||||
func (m *MockBackend) ExternalSender() *txsender.ExternalSender {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "ExternalSender")
|
||||
ret0, _ := ret[0].(*txsender.ExternalSender)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// ExternalSender indicates an expected call of ExternalSender.
|
||||
func (mr *MockBackendMockRecorder) ExternalSender() *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ExternalSender", reflect.TypeOf((*MockBackend)(nil).ExternalSender))
|
||||
}
|
||||
|
||||
// FilterLogs mocks base method.
|
||||
func (m *MockBackend) FilterLogs(arg0 context.Context, arg1 ethereum.FilterQuery) ([]types.Log, error) {
|
||||
m.ctrl.T.Helper()
|
||||
@@ -334,6 +365,22 @@ func (mr *MockBackendMockRecorder) Net() *gomock.Call {
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Net", reflect.TypeOf((*MockBackend)(nil).Net))
|
||||
}
|
||||
|
||||
// NewSwap mocks base method.
|
||||
func (m *MockBackend) NewSwap(arg0, arg1 [32]byte, arg2 common.Address, arg3, arg4, arg5 *big.Int) (common.Hash, *types.Receipt, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "NewSwap", arg0, arg1, arg2, arg3, arg4, arg5)
|
||||
ret0, _ := ret[0].(common.Hash)
|
||||
ret1, _ := ret[1].(*types.Receipt)
|
||||
ret2, _ := ret[2].(error)
|
||||
return ret0, ret1, ret2
|
||||
}
|
||||
|
||||
// NewSwap indicates an expected call of NewSwap.
|
||||
func (mr *MockBackendMockRecorder) NewSwap(arg0, arg1, arg2, arg3, arg4, arg5 interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "NewSwap", reflect.TypeOf((*MockBackend)(nil).NewSwap), arg0, arg1, arg2, arg3, arg4, arg5)
|
||||
}
|
||||
|
||||
// NewSwapFactory mocks base method.
|
||||
func (m *MockBackend) NewSwapFactory(arg0 common.Address) (*swapfactory.SwapFactory, error) {
|
||||
m.ctrl.T.Helper()
|
||||
@@ -377,6 +424,22 @@ func (mr *MockBackendMockRecorder) Refresh() *gomock.Call {
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Refresh", reflect.TypeOf((*MockBackend)(nil).Refresh))
|
||||
}
|
||||
|
||||
// Refund mocks base method.
|
||||
func (m *MockBackend) Refund(arg0 swapfactory.SwapFactorySwap, arg1 [32]byte) (common.Hash, *types.Receipt, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "Refund", arg0, arg1)
|
||||
ret0, _ := ret[0].(common.Hash)
|
||||
ret1, _ := ret[1].(*types.Receipt)
|
||||
ret2, _ := ret[2].(error)
|
||||
return ret0, ret1, ret2
|
||||
}
|
||||
|
||||
// Refund indicates an expected call of Refund.
|
||||
func (mr *MockBackendMockRecorder) Refund(arg0, arg1 interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Refund", reflect.TypeOf((*MockBackend)(nil).Refund), arg0, arg1)
|
||||
}
|
||||
|
||||
// SendSwapMessage mocks base method.
|
||||
func (m *MockBackend) SendSwapMessage(arg0 message.Message) error {
|
||||
m.ctrl.T.Helper()
|
||||
@@ -391,6 +454,42 @@ func (mr *MockBackendMockRecorder) SendSwapMessage(arg0 interface{}) *gomock.Cal
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SendSwapMessage", reflect.TypeOf((*MockBackend)(nil).SendSwapMessage), arg0)
|
||||
}
|
||||
|
||||
// SetContract mocks base method.
|
||||
func (m *MockBackend) SetContract(arg0 *swapfactory.SwapFactory) {
|
||||
m.ctrl.T.Helper()
|
||||
m.ctrl.Call(m, "SetContract", arg0)
|
||||
}
|
||||
|
||||
// SetContract indicates an expected call of SetContract.
|
||||
func (mr *MockBackendMockRecorder) SetContract(arg0 interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetContract", reflect.TypeOf((*MockBackend)(nil).SetContract), arg0)
|
||||
}
|
||||
|
||||
// SetContractAddress mocks base method.
|
||||
func (m *MockBackend) SetContractAddress(arg0 common.Address) {
|
||||
m.ctrl.T.Helper()
|
||||
m.ctrl.Call(m, "SetContractAddress", arg0)
|
||||
}
|
||||
|
||||
// SetContractAddress indicates an expected call of SetContractAddress.
|
||||
func (mr *MockBackendMockRecorder) SetContractAddress(arg0 interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetContractAddress", reflect.TypeOf((*MockBackend)(nil).SetContractAddress), arg0)
|
||||
}
|
||||
|
||||
// SetEthAddress mocks base method.
|
||||
func (m *MockBackend) SetEthAddress(arg0 common.Address) {
|
||||
m.ctrl.T.Helper()
|
||||
m.ctrl.Call(m, "SetEthAddress", arg0)
|
||||
}
|
||||
|
||||
// SetEthAddress indicates an expected call of SetEthAddress.
|
||||
func (mr *MockBackendMockRecorder) SetEthAddress(arg0 interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetEthAddress", reflect.TypeOf((*MockBackend)(nil).SetEthAddress), arg0)
|
||||
}
|
||||
|
||||
// SetGasPrice mocks base method.
|
||||
func (m *MockBackend) SetGasPrice(arg0 uint64) {
|
||||
m.ctrl.T.Helper()
|
||||
@@ -403,6 +502,22 @@ func (mr *MockBackendMockRecorder) SetGasPrice(arg0 interface{}) *gomock.Call {
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetGasPrice", reflect.TypeOf((*MockBackend)(nil).SetGasPrice), arg0)
|
||||
}
|
||||
|
||||
// SetReady mocks base method.
|
||||
func (m *MockBackend) SetReady(arg0 swapfactory.SwapFactorySwap) (common.Hash, *types.Receipt, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "SetReady", arg0)
|
||||
ret0, _ := ret[0].(common.Hash)
|
||||
ret1, _ := ret[1].(*types.Receipt)
|
||||
ret2, _ := ret[2].(error)
|
||||
return ret0, ret1, ret2
|
||||
}
|
||||
|
||||
// SetReady indicates an expected call of SetReady.
|
||||
func (mr *MockBackendMockRecorder) SetReady(arg0 interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetReady", reflect.TypeOf((*MockBackend)(nil).SetReady), arg0)
|
||||
}
|
||||
|
||||
// SetSwapTimeout mocks base method.
|
||||
func (m *MockBackend) SetSwapTimeout(arg0 time.Duration) {
|
||||
m.ctrl.T.Helper()
|
||||
@@ -415,6 +530,18 @@ func (mr *MockBackendMockRecorder) SetSwapTimeout(arg0 interface{}) *gomock.Call
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetSwapTimeout", reflect.TypeOf((*MockBackend)(nil).SetSwapTimeout), arg0)
|
||||
}
|
||||
|
||||
// SetXMRDepositAddress mocks base method.
|
||||
func (m *MockBackend) SetXMRDepositAddress(arg0 mcrypto.Address) {
|
||||
m.ctrl.T.Helper()
|
||||
m.ctrl.Call(m, "SetXMRDepositAddress", arg0)
|
||||
}
|
||||
|
||||
// SetXMRDepositAddress indicates an expected call of SetXMRDepositAddress.
|
||||
func (mr *MockBackendMockRecorder) SetXMRDepositAddress(arg0 interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetXMRDepositAddress", reflect.TypeOf((*MockBackend)(nil).SetXMRDepositAddress), arg0)
|
||||
}
|
||||
|
||||
// SwapManager mocks base method.
|
||||
func (m *MockBackend) SwapManager() swap.Manager {
|
||||
m.ctrl.T.Helper()
|
||||
@@ -517,3 +644,17 @@ func (mr *MockBackendMockRecorder) WaitForReceipt(arg0, arg1 interface{}) *gomoc
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "WaitForReceipt", reflect.TypeOf((*MockBackend)(nil).WaitForReceipt), arg0, arg1)
|
||||
}
|
||||
|
||||
// XMRDepositAddress mocks base method.
|
||||
func (m *MockBackend) XMRDepositAddress() mcrypto.Address {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "XMRDepositAddress")
|
||||
ret0, _ := ret[0].(mcrypto.Address)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// XMRDepositAddress indicates an expected call of XMRDepositAddress.
|
||||
func (mr *MockBackendMockRecorder) XMRDepositAddress() *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "XMRDepositAddress", reflect.TypeOf((*MockBackend)(nil).XMRDepositAddress))
|
||||
}
|
||||
|
||||
@@ -44,7 +44,7 @@ func NewRecoveryState(b backend.Backend, basepath string, secret *mcrypto.Privat
|
||||
s := &swapState{
|
||||
ctx: ctx,
|
||||
cancel: cancel,
|
||||
backend: b,
|
||||
Backend: b,
|
||||
txOpts: txOpts,
|
||||
privkeys: kp,
|
||||
pubkeys: pubkp,
|
||||
|
||||
@@ -23,7 +23,7 @@ func newTestRecoveryState(t *testing.T) *recoveryState {
|
||||
require.NoError(t, err)
|
||||
newSwap(t, s, [32]byte{}, sr, big.NewInt(1), duration)
|
||||
|
||||
rs, err := NewRecoveryState(inst.backend, "/tmp/test-infofile", s.privkeys.SpendKey(), s.backend.ContractAddr(),
|
||||
rs, err := NewRecoveryState(inst.backend, "/tmp/test-infofile", s.privkeys.SpendKey(), s.ContractAddr(),
|
||||
s.contractSwapID, s.contractSwap)
|
||||
require.NoError(t, err)
|
||||
|
||||
@@ -35,7 +35,7 @@ func TestClaimOrRecover_Claim(t *testing.T) {
|
||||
rs := newTestRecoveryState(t)
|
||||
|
||||
// set contract to Ready
|
||||
_, err := rs.ss.contract.SetReady(rs.ss.txOpts, rs.ss.contractSwap)
|
||||
_, err := rs.ss.Contract().SetReady(rs.ss.txOpts, rs.ss.contractSwap)
|
||||
require.NoError(t, err)
|
||||
|
||||
// assert we can claim ether
|
||||
@@ -53,7 +53,7 @@ func TestClaimOrRecover_Recover(t *testing.T) {
|
||||
rs := newTestRecoveryState(t)
|
||||
|
||||
daemonClient := monero.NewClient(common.DefaultMoneroDaemonEndpoint)
|
||||
addr, err := rs.ss.backend.GetAddress(0)
|
||||
addr, err := rs.ss.GetAddress(0)
|
||||
require.NoError(t, err)
|
||||
_ = daemonClient.GenerateBlocks(addr.Address, 121)
|
||||
|
||||
@@ -64,7 +64,7 @@ func TestClaimOrRecover_Recover(t *testing.T) {
|
||||
|
||||
// call refund w/ XMRTaker's spend key
|
||||
sc := rs.ss.getSecret()
|
||||
_, err = rs.ss.contract.Refund(rs.ss.txOpts, rs.ss.contractSwap, sc)
|
||||
_, err = rs.ss.Contract().Refund(rs.ss.txOpts, rs.ss.contractSwap, sc)
|
||||
require.NoError(t, err)
|
||||
|
||||
// assert XMRMaker can reclaim his monero
|
||||
|
||||
@@ -39,7 +39,7 @@ var (
|
||||
)
|
||||
|
||||
type swapState struct {
|
||||
backend backend.Backend
|
||||
backend.Backend
|
||||
|
||||
ctx context.Context
|
||||
cancel context.CancelFunc
|
||||
@@ -58,10 +58,8 @@ type swapState struct {
|
||||
pubkeys *mcrypto.PublicKeyPair
|
||||
|
||||
// swap contract and timeouts in it; set once contract is deployed
|
||||
contract *swapfactory.SwapFactory
|
||||
contractSwapID [32]byte
|
||||
contractSwap swapfactory.SwapFactorySwap
|
||||
contractAddr ethcommon.Address
|
||||
t0, t1 time.Time
|
||||
txOpts *bind.TransactOpts
|
||||
|
||||
@@ -104,7 +102,7 @@ func newSwapState(b backend.Backend, offer *types.Offer, om *offerManager, statu
|
||||
s := &swapState{
|
||||
ctx: ctx,
|
||||
cancel: cancel,
|
||||
backend: b,
|
||||
Backend: b,
|
||||
offer: offer,
|
||||
offerManager: om,
|
||||
infofile: infofile,
|
||||
@@ -131,7 +129,7 @@ func (s *swapState) SendKeysMessage() (*net.SendKeysMessage, error) {
|
||||
PrivateViewKey: s.privkeys.ViewKey().Hex(),
|
||||
DLEqProof: hex.EncodeToString(s.dleqProof.Proof()),
|
||||
Secp256k1PublicKey: s.secp256k1Pub.String(),
|
||||
EthAddress: s.backend.EthAddress().String(),
|
||||
EthAddress: s.EthAddress().String(),
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -179,7 +177,7 @@ func (s *swapState) exit() error {
|
||||
defer func() {
|
||||
// stop all running goroutines
|
||||
s.cancel()
|
||||
s.backend.SwapManager().CompleteOngoingSwap()
|
||||
s.SwapManager().CompleteOngoingSwap()
|
||||
|
||||
if s.info.Status() != types.CompletedSuccess {
|
||||
// re-add offer, as it wasn't taken successfully
|
||||
@@ -271,19 +269,19 @@ func (s *swapState) reclaimMonero(skA *mcrypto.PrivateSpendKey) (mcrypto.Address
|
||||
kpAB := mcrypto.NewPrivateKeyPair(skAB, vkAB)
|
||||
|
||||
// write keys to file in case something goes wrong
|
||||
if err = pcommon.WriteSharedSwapKeyPairToFile(s.infofile, kpAB, s.backend.Env()); err != nil {
|
||||
if err = pcommon.WriteSharedSwapKeyPairToFile(s.infofile, kpAB, s.Env()); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
// TODO: check balance
|
||||
return monero.CreateMoneroWallet("xmrmaker-swap-wallet", s.backend.Env(), s.backend, kpAB)
|
||||
return monero.CreateMoneroWallet("xmrmaker-swap-wallet", s.Env(), s, kpAB)
|
||||
}
|
||||
|
||||
func (s *swapState) filterForRefund() (*mcrypto.PrivateSpendKey, error) {
|
||||
const refundedEvent = "Refunded"
|
||||
|
||||
logs, err := s.backend.FilterLogs(s.ctx, eth.FilterQuery{
|
||||
Addresses: []ethcommon.Address{s.backend.ContractAddr()},
|
||||
logs, err := s.FilterLogs(s.ctx, eth.FilterQuery{
|
||||
Addresses: []ethcommon.Address{s.ContractAddr()},
|
||||
Topics: [][]ethcommon.Hash{{refundedTopic}},
|
||||
})
|
||||
if err != nil {
|
||||
@@ -327,7 +325,7 @@ func (s *swapState) filterForRefund() (*mcrypto.PrivateSpendKey, error) {
|
||||
func (s *swapState) tryClaim() (ethcommon.Hash, error) {
|
||||
untilT0 := time.Until(s.t0)
|
||||
untilT1 := time.Until(s.t1)
|
||||
stage, err := s.backend.Contract().Swaps(s.backend.CallOpts(), s.contractSwapID)
|
||||
stage, err := s.Contract().Swaps(s.CallOpts(), s.contractSwapID)
|
||||
if err != nil {
|
||||
return ethcommon.Hash{}, err
|
||||
}
|
||||
@@ -369,7 +367,7 @@ func (s *swapState) generateAndSetKeys() error {
|
||||
s.privkeys = keysAndProof.PrivateKeyPair
|
||||
s.pubkeys = keysAndProof.PublicKeyPair
|
||||
|
||||
return pcommon.WriteKeysToFile(s.infofile, s.privkeys, s.backend.Env())
|
||||
return pcommon.WriteKeysToFile(s.infofile, s.privkeys, s.Env())
|
||||
}
|
||||
|
||||
func generateKeys() (*pcommon.KeysAndProof, error) {
|
||||
@@ -393,10 +391,15 @@ func (s *swapState) setXMRTakerPublicKeys(sk *mcrypto.PublicKeyPair, secp256k1Pu
|
||||
// setContract sets the contract in which XMRTaker has locked her ETH.
|
||||
func (s *swapState) setContract(address ethcommon.Address) error {
|
||||
var err error
|
||||
// TODO: this overrides the backend contract, need to be careful
|
||||
s.contractAddr = address
|
||||
s.contract, err = s.backend.NewSwapFactory(address)
|
||||
return err
|
||||
// note: this overrides the backend contract
|
||||
s.SetContractAddress(address)
|
||||
contract, err := s.NewSwapFactory(address)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
s.SetContract(contract)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *swapState) setTimeouts(t0, t1 *big.Int) {
|
||||
@@ -408,7 +411,7 @@ func (s *swapState) setTimeouts(t0, t1 *big.Int) {
|
||||
// if the balance doesn't match what we're expecting to receive, or the public keys in the contract
|
||||
// aren't what we expect, we error and abort the swap.
|
||||
func (s *swapState) checkContract(txHash ethcommon.Hash) error {
|
||||
receipt, err := s.backend.WaitForReceipt(s.ctx, txHash)
|
||||
receipt, err := s.WaitForReceipt(s.ctx, txHash)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get receipt for New transaction: %w", err)
|
||||
}
|
||||
@@ -418,7 +421,7 @@ func (s *swapState) checkContract(txHash ethcommon.Hash) error {
|
||||
return errCannotFindNewLog
|
||||
}
|
||||
|
||||
event, err := s.contract.ParseNew(*receipt.Logs[0])
|
||||
event, err := s.Contract().ParseNew(*receipt.Logs[0])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -458,7 +461,7 @@ func (s *swapState) lockFunds(amount common.MoneroAmount) (mcrypto.Address, erro
|
||||
kp := mcrypto.SumSpendAndViewKeys(s.xmrtakerPublicKeys, s.pubkeys)
|
||||
log.Infof("going to lock XMR funds, amount(piconero)=%d", amount)
|
||||
|
||||
balance, err := s.backend.GetBalance(0)
|
||||
balance, err := s.GetBalance(0)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
@@ -466,25 +469,25 @@ func (s *swapState) lockFunds(amount common.MoneroAmount) (mcrypto.Address, erro
|
||||
log.Debug("total XMR balance: ", balance.Balance)
|
||||
log.Info("unlocked XMR balance: ", balance.UnlockedBalance)
|
||||
|
||||
address := kp.Address(s.backend.Env())
|
||||
txResp, err := s.backend.Transfer(address, 0, uint(amount))
|
||||
address := kp.Address(s.Env())
|
||||
txResp, err := s.Transfer(address, 0, uint(amount))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
log.Infof("locked XMR, txHash=%s fee=%d", txResp.TxHash, txResp.Fee)
|
||||
|
||||
xmrmakerAddr, err := s.backend.GetAddress(0)
|
||||
xmrmakerAddr, err := s.GetAddress(0)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
// if we're on a development --regtest node, generate some blocks
|
||||
if s.backend.Env() == common.Development {
|
||||
_ = s.backend.GenerateBlocks(xmrmakerAddr.Address, 2)
|
||||
if s.Env() == common.Development {
|
||||
_ = s.GenerateBlocks(xmrmakerAddr.Address, 2)
|
||||
} else {
|
||||
// otherwise, wait for new blocks
|
||||
height, err := monero.WaitForBlocks(s.backend, 1)
|
||||
height, err := monero.WaitForBlocks(s, 1)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
@@ -492,7 +495,7 @@ func (s *swapState) lockFunds(amount common.MoneroAmount) (mcrypto.Address, erro
|
||||
log.Infof("monero block height: %d", height)
|
||||
}
|
||||
|
||||
if err := s.backend.Refresh(); err != nil {
|
||||
if err := s.Refresh(); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
@@ -502,9 +505,9 @@ func (s *swapState) lockFunds(amount common.MoneroAmount) (mcrypto.Address, erro
|
||||
|
||||
// claimFunds redeems XMRMaker's ETH funds by calling Claim() on the contract
|
||||
func (s *swapState) claimFunds() (ethcommon.Hash, error) {
|
||||
addr := s.backend.EthAddress()
|
||||
addr := s.EthAddress()
|
||||
|
||||
balance, err := s.backend.BalanceAt(s.ctx, addr, nil)
|
||||
balance, err := s.BalanceAt(s.ctx, addr, nil)
|
||||
if err != nil {
|
||||
return ethcommon.Hash{}, err
|
||||
}
|
||||
@@ -513,22 +516,18 @@ func (s *swapState) claimFunds() (ethcommon.Hash, error) {
|
||||
|
||||
// call swap.Swap.Claim() w/ b.privkeys.sk, revealing XMRMaker's secret spend key
|
||||
sc := s.getSecret()
|
||||
tx, err := s.contract.Claim(s.txOpts, s.contractSwap, sc)
|
||||
txHash, _, err := s.Claim(s.contractSwap, sc)
|
||||
if err != nil {
|
||||
return ethcommon.Hash{}, err
|
||||
}
|
||||
|
||||
log.Infof("sent claim tx, tx hash=%s", tx.Hash())
|
||||
log.Infof("sent claim tx, tx hash=%s", txHash)
|
||||
|
||||
if _, err = s.backend.WaitForReceipt(s.ctx, tx.Hash()); err != nil {
|
||||
return ethcommon.Hash{}, fmt.Errorf("failed to check claim transaction receipt: %w", err)
|
||||
}
|
||||
|
||||
balance, err = s.backend.BalanceAt(s.ctx, addr, nil)
|
||||
balance, err = s.BalanceAt(s.ctx, addr, nil)
|
||||
if err != nil {
|
||||
return ethcommon.Hash{}, err
|
||||
}
|
||||
|
||||
log.Infof("balance after claim: %v ETH", common.EtherAmount(*balance).AsEther())
|
||||
return tx.Hash(), nil
|
||||
return txHash, nil
|
||||
}
|
||||
|
||||
@@ -100,8 +100,8 @@ func newTestInstance(t *testing.T) (*Instance, *swapState) {
|
||||
swapState, err := newSwapState(xmrmaker.backend, &types.Offer{}, xmrmaker.offerManager, nil, infofile,
|
||||
common.MoneroAmount(33), desiredAmount)
|
||||
require.NoError(t, err)
|
||||
swapState.contract = xmrmaker.backend.Contract()
|
||||
swapState.contractAddr = xmrmaker.backend.ContractAddr()
|
||||
swapState.SetContract(xmrmaker.backend.Contract())
|
||||
swapState.SetContractAddress(xmrmaker.backend.ContractAddr())
|
||||
return xmrmaker, swapState
|
||||
}
|
||||
|
||||
@@ -126,7 +126,7 @@ func newSwap(t *testing.T, ss *swapState, claimKey, refundKey [32]byte, amount *
|
||||
claimKey = ss.secp256k1Pub.Keccak256()
|
||||
}
|
||||
|
||||
txOpts, err := ss.backend.TxOpts()
|
||||
txOpts, err := ss.TxOpts()
|
||||
require.NoError(t, err)
|
||||
|
||||
// TODO: this is sus, update this when signing interfaces are updated
|
||||
@@ -135,12 +135,12 @@ func newSwap(t *testing.T, ss *swapState, claimKey, refundKey [32]byte, amount *
|
||||
txOpts.Value = nil
|
||||
}()
|
||||
|
||||
ethAddr := ss.backend.EthAddress()
|
||||
ethAddr := ss.EthAddress()
|
||||
nonce := big.NewInt(0)
|
||||
tx, err := ss.backend.Contract().NewSwap(txOpts, claimKey, refundKey, ethAddr, tm, nonce)
|
||||
tx, err := ss.Contract().NewSwap(txOpts, claimKey, refundKey, ethAddr, tm, nonce)
|
||||
require.NoError(t, err)
|
||||
|
||||
receipt, err := ss.backend.TransactionReceipt(context.Background(), tx.Hash())
|
||||
receipt, err := ss.TransactionReceipt(context.Background(), tx.Hash())
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 1, len(receipt.Logs))
|
||||
ss.contractSwapID, err = swapfactory.GetIDFromLog(receipt.Logs[0])
|
||||
@@ -182,10 +182,8 @@ func TestSwapState_ClaimFunds(t *testing.T) {
|
||||
claimKey := swapState.secp256k1Pub.Keccak256()
|
||||
newSwap(t, swapState, claimKey,
|
||||
[32]byte{}, big.NewInt(33), defaultTimeoutDuration)
|
||||
swapState.contract = swapState.backend.Contract()
|
||||
swapState.contractAddr = swapState.backend.ContractAddr()
|
||||
|
||||
_, err = swapState.contract.SetReady(swapState.txOpts, swapState.contractSwap)
|
||||
_, err = swapState.Contract().SetReady(swapState.txOpts, swapState.contractSwap)
|
||||
require.NoError(t, err)
|
||||
|
||||
txHash, err := swapState.claimFunds()
|
||||
@@ -233,7 +231,7 @@ func TestSwapState_HandleProtocolMessage_NotifyETHLocked_ok(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
hash := newSwap(t, s, s.secp256k1Pub.Keccak256(), s.xmrtakerSecp256K1PublicKey.Keccak256(),
|
||||
desiredAmount.BigInt(), duration)
|
||||
addr := s.backend.ContractAddr()
|
||||
addr := s.ContractAddr()
|
||||
|
||||
msg = &message.NotifyETHLocked{
|
||||
Address: addr.String(),
|
||||
@@ -247,8 +245,6 @@ func TestSwapState_HandleProtocolMessage_NotifyETHLocked_ok(t *testing.T) {
|
||||
require.NotNil(t, resp)
|
||||
require.Equal(t, message.NotifyXMRLockType, resp.Type())
|
||||
require.False(t, done)
|
||||
require.NotNil(t, s.contract)
|
||||
require.Equal(t, addr, s.contractAddr)
|
||||
require.Equal(t, duration, s.t1.Sub(s.t0))
|
||||
require.Equal(t, &message.NotifyReady{}, s.nextExpectedMessage)
|
||||
require.True(t, s.info.Status().IsOngoing())
|
||||
@@ -280,7 +276,7 @@ func TestSwapState_HandleProtocolMessage_NotifyETHLocked_timeout(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
hash := newSwap(t, s, s.secp256k1Pub.Keccak256(), s.xmrtakerSecp256K1PublicKey.Keccak256(),
|
||||
desiredAmount.BigInt(), duration)
|
||||
addr := s.backend.ContractAddr()
|
||||
addr := s.ContractAddr()
|
||||
|
||||
msg = &message.NotifyETHLocked{
|
||||
Address: addr.String(),
|
||||
@@ -294,8 +290,6 @@ func TestSwapState_HandleProtocolMessage_NotifyETHLocked_timeout(t *testing.T) {
|
||||
require.NotNil(t, resp)
|
||||
require.Equal(t, message.NotifyXMRLockType, resp.Type())
|
||||
require.False(t, done)
|
||||
require.NotNil(t, s.contract)
|
||||
require.Equal(t, addr, s.contractAddr)
|
||||
require.Equal(t, duration, s.t1.Sub(s.t0))
|
||||
require.Equal(t, &message.NotifyReady{}, s.nextExpectedMessage)
|
||||
|
||||
@@ -307,7 +301,7 @@ func TestSwapState_HandleProtocolMessage_NotifyETHLocked_timeout(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
require.NotNil(t, s.backend.Net().(*mockNet).msg)
|
||||
require.NotNil(t, s.Net().(*mockNet).msg)
|
||||
require.Equal(t, types.CompletedSuccess, s.info.Status())
|
||||
}
|
||||
|
||||
@@ -322,7 +316,7 @@ func TestSwapState_HandleProtocolMessage_NotifyReady(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
newSwap(t, s, [32]byte{}, [32]byte{}, desiredAmount.BigInt(), duration)
|
||||
|
||||
_, err = s.contract.SetReady(s.txOpts, s.contractSwap)
|
||||
_, err = s.Contract().SetReady(s.txOpts, s.contractSwap)
|
||||
require.NoError(t, err)
|
||||
|
||||
msg := &message.NotifyReady{}
|
||||
@@ -360,7 +354,7 @@ func TestSwapState_handleRefund(t *testing.T) {
|
||||
var sc [32]byte
|
||||
copy(sc[:], common.Reverse(secret))
|
||||
|
||||
tx, err := s.contract.Refund(s.txOpts, s.contractSwap, sc)
|
||||
tx, err := s.Contract().Refund(s.txOpts, s.contractSwap, sc)
|
||||
require.NoError(t, err)
|
||||
|
||||
addr, err := s.handleRefund(tx.Hash().String())
|
||||
@@ -393,7 +387,7 @@ func TestSwapState_HandleProtocolMessage_NotifyRefund(t *testing.T) {
|
||||
var sc [32]byte
|
||||
copy(sc[:], common.Reverse(secret[:]))
|
||||
|
||||
tx, err := s.contract.Refund(s.txOpts, s.contractSwap, sc)
|
||||
tx, err := s.Contract().Refund(s.txOpts, s.contractSwap, sc)
|
||||
require.NoError(t, err)
|
||||
|
||||
msg := &message.NotifyRefund{
|
||||
@@ -433,10 +427,10 @@ func TestSwapState_Exit_Reclaim(t *testing.T) {
|
||||
var sc [32]byte
|
||||
copy(sc[:], common.Reverse(secret[:]))
|
||||
|
||||
tx, err := s.contract.Refund(s.txOpts, s.contractSwap, sc)
|
||||
tx, err := s.Contract().Refund(s.txOpts, s.contractSwap, sc)
|
||||
require.NoError(t, err)
|
||||
|
||||
receipt, err := s.backend.TransactionReceipt(s.ctx, tx.Hash())
|
||||
receipt, err := s.TransactionReceipt(s.ctx, tx.Hash())
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 1, len(receipt.Logs))
|
||||
require.Equal(t, 1, len(receipt.Logs[0].Topics))
|
||||
@@ -446,7 +440,7 @@ func TestSwapState_Exit_Reclaim(t *testing.T) {
|
||||
err = s.Exit()
|
||||
require.NoError(t, err)
|
||||
|
||||
balance, err := s.backend.GetBalance(0)
|
||||
balance, err := s.GetBalance(0)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, common.MoneroToPiconero(s.info.ProvidedAmount()).Uint64(), uint64(balance.Balance))
|
||||
require.Equal(t, types.CompletedRefund, s.info.Status())
|
||||
|
||||
@@ -29,7 +29,6 @@ type Instance struct {
|
||||
basepath string
|
||||
|
||||
walletFile, walletPassword string
|
||||
walletAddress mcrypto.Address
|
||||
transferBack bool // transfer xmr back to original account
|
||||
|
||||
// non-nil if a swap is currently happening, nil otherwise
|
||||
@@ -59,6 +58,7 @@ func NewInstance(cfg *Config) (*Instance, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cfg.Backend.SetXMRDepositAddress(address)
|
||||
}
|
||||
|
||||
// TODO: check that XMRTaker's monero-wallet-cli endpoint has wallet-dir configured
|
||||
@@ -67,7 +67,6 @@ func NewInstance(cfg *Config) (*Instance, error) {
|
||||
basepath: cfg.Basepath,
|
||||
walletFile: cfg.MoneroWalletFile,
|
||||
walletPassword: cfg.MoneroWalletPassword,
|
||||
walletAddress: address,
|
||||
}, nil
|
||||
}
|
||||
|
||||
|
||||
@@ -140,7 +140,7 @@ func (s *swapState) handleSendKeysMessage(msg *net.SendKeysMessage) (net.Message
|
||||
s.setXMRMakerKeys(sk, vk, secp256k1Pub)
|
||||
txHash, err := s.lockETH(s.providedAmountInWei())
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to deploy contract: %w", err)
|
||||
return nil, fmt.Errorf("failed to lock ETH in contract: %w", err)
|
||||
}
|
||||
|
||||
log.Info("locked ether in swap contract, waiting for XMR to be locked")
|
||||
@@ -281,13 +281,12 @@ func (s *swapState) handleNotifyXMRLock(msg *message.NotifyXMRLock) (net.Message
|
||||
}
|
||||
|
||||
close(s.xmrLockedCh)
|
||||
log.Info("XMR was locked successfully, setting contract to ready...")
|
||||
|
||||
if err := s.ready(); err != nil {
|
||||
return nil, fmt.Errorf("failed to call Ready: %w", err)
|
||||
}
|
||||
|
||||
log.Info("XMR was locked successfully, setting contract to ready...")
|
||||
|
||||
go func() {
|
||||
until := time.Until(s.t1)
|
||||
|
||||
@@ -332,6 +331,7 @@ func (s *swapState) handleNotifyXMRLock(msg *message.NotifyXMRLock) (net.Message
|
||||
// handleNotifyClaimed handles XMRMaker's reveal after he calls Claim().
|
||||
// it calls `createMoneroWallet` to create XMRTaker's wallet, allowing her to own the XMR.
|
||||
func (s *swapState) handleNotifyClaimed(txHash string) (mcrypto.Address, error) {
|
||||
log.Debugf("got NotifyClaimed, txHash=%s", txHash)
|
||||
receipt, err := s.WaitForReceipt(s.ctx, ethcommon.HexToHash(txHash))
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed check claim transaction receipt: %w", err)
|
||||
|
||||
@@ -45,7 +45,7 @@ func (a *Instance) initiate(providesAmount common.EtherAmount, receivedAmount co
|
||||
return errBalanceTooLow
|
||||
}
|
||||
|
||||
a.swapState, err = newSwapState(a.backend, pcommon.GetSwapInfoFilepath(a.basepath), a.transferBack, a.walletAddress,
|
||||
a.swapState, err = newSwapState(a.backend, pcommon.GetSwapInfoFilepath(a.basepath), a.transferBack,
|
||||
providesAmount, receivedAmount, exchangeRate)
|
||||
if err != nil {
|
||||
return err
|
||||
|
||||
@@ -27,11 +27,6 @@ type recoveryState struct {
|
||||
// which has methods to either claim ether or reclaim monero from an initiated swap.
|
||||
func NewRecoveryState(b backend.Backend, basepath string, secret *mcrypto.PrivateSpendKey,
|
||||
contractSwapID [32]byte, contractSwap swapfactory.SwapFactorySwap) (*recoveryState, error) { //nolint:revive
|
||||
txOpts, err := b.TxOpts()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
kp, err := secret.AsPrivateKeyPair()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -47,7 +42,6 @@ func NewRecoveryState(b backend.Backend, basepath string, secret *mcrypto.Privat
|
||||
ctx: ctx,
|
||||
cancel: cancel,
|
||||
Backend: b,
|
||||
txOpts: txOpts,
|
||||
privkeys: kp,
|
||||
pubkeys: pubkp,
|
||||
dleqProof: dleq.NewProofWithSecret(sc),
|
||||
|
||||
@@ -43,7 +43,10 @@ func TestClaimOrRefund_Claim(t *testing.T) {
|
||||
|
||||
// call swap.Claim()
|
||||
sc := rs.ss.getSecret()
|
||||
_, err = rs.ss.Contract().Claim(rs.ss.txOpts, rs.ss.contractSwap, sc)
|
||||
txOpts, err := rs.ss.TxOpts()
|
||||
require.NoError(t, err)
|
||||
|
||||
_, err = rs.ss.Contract().Claim(txOpts, rs.ss.contractSwap, sc)
|
||||
require.NoError(t, err)
|
||||
|
||||
t.Log("XMRMaker claimed ETH...")
|
||||
|
||||
@@ -23,7 +23,6 @@ import (
|
||||
pswap "github.com/noot/atomic-swap/protocol/swap"
|
||||
"github.com/noot/atomic-swap/swapfactory"
|
||||
|
||||
"github.com/ethereum/go-ethereum/accounts/abi/bind"
|
||||
ethcommon "github.com/ethereum/go-ethereum/common"
|
||||
"github.com/fatih/color" //nolint:misspell
|
||||
)
|
||||
@@ -37,9 +36,8 @@ type swapState struct {
|
||||
ctx context.Context
|
||||
cancel context.CancelFunc
|
||||
sync.Mutex
|
||||
infofile string
|
||||
transferBack bool
|
||||
walletAddress mcrypto.Address
|
||||
infofile string
|
||||
transferBack bool
|
||||
|
||||
info *pswap.Info
|
||||
statusCh chan types.Status
|
||||
@@ -60,7 +58,6 @@ type swapState struct {
|
||||
contractSwapID [32]byte
|
||||
contractSwap swapfactory.SwapFactorySwap
|
||||
t0, t1 time.Time
|
||||
txOpts *bind.TransactOpts
|
||||
|
||||
// next expected network message
|
||||
nextExpectedMessage net.Message
|
||||
@@ -72,22 +69,17 @@ type swapState struct {
|
||||
exited bool
|
||||
}
|
||||
|
||||
func newSwapState(b backend.Backend, infofile string, transferBack bool, walletAddress mcrypto.Address,
|
||||
func newSwapState(b backend.Backend, infofile string, transferBack bool,
|
||||
providesAmount common.EtherAmount, receivedAmount common.MoneroAmount,
|
||||
exchangeRate types.ExchangeRate) (*swapState, error) {
|
||||
if b.Contract() == nil {
|
||||
return nil, errNoSwapContractSet
|
||||
}
|
||||
|
||||
if transferBack && walletAddress == "" {
|
||||
if transferBack && b.XMRDepositAddress() == "" {
|
||||
return nil, errMustProvideWalletAddress
|
||||
}
|
||||
|
||||
txOpts, err := b.TxOpts()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
stage := types.ExpectingKeys
|
||||
statusCh := make(chan types.Status, 16)
|
||||
statusCh <- stage
|
||||
@@ -97,6 +89,10 @@ func newSwapState(b backend.Backend, infofile string, transferBack bool, walletA
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if b.ExternalSender() != nil {
|
||||
transferBack = true // front-end must set final deposit address
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithCancel(b.Ctx())
|
||||
s := &swapState{
|
||||
ctx: ctx,
|
||||
@@ -104,8 +100,6 @@ func newSwapState(b backend.Backend, infofile string, transferBack bool, walletA
|
||||
Backend: b,
|
||||
infofile: infofile,
|
||||
transferBack: transferBack,
|
||||
walletAddress: walletAddress,
|
||||
txOpts: txOpts,
|
||||
nextExpectedMessage: &net.SendKeysMessage{},
|
||||
xmrLockedCh: make(chan struct{}),
|
||||
claimedCh: make(chan struct{}),
|
||||
@@ -388,23 +382,14 @@ func (s *swapState) lockETH(amount common.EtherAmount) (ethcommon.Hash, error) {
|
||||
cmtXMRTaker := s.secp256k1Pub.Keccak256()
|
||||
cmtXMRMaker := s.xmrmakerSecp256k1PublicKey.Keccak256()
|
||||
|
||||
s.txOpts.Value = amount.BigInt()
|
||||
defer func() {
|
||||
s.txOpts.Value = nil
|
||||
}()
|
||||
|
||||
nonce := generateNonce()
|
||||
tx, err := s.Contract().NewSwap(s.txOpts, cmtXMRMaker, cmtXMRTaker,
|
||||
s.xmrmakerAddress, big.NewInt(int64(s.SwapTimeout().Seconds())), nonce)
|
||||
txHash, receipt, err := s.NewSwap(cmtXMRMaker, cmtXMRTaker,
|
||||
s.xmrmakerAddress, big.NewInt(int64(s.SwapTimeout().Seconds())), nonce, amount.BigInt())
|
||||
if err != nil {
|
||||
return ethcommon.Hash{}, fmt.Errorf("failed to instantiate swap on-chain: %w", err)
|
||||
}
|
||||
|
||||
log.Debugf("instantiating swap on-chain: amount=%s txHash=%s", amount, tx.Hash())
|
||||
receipt, err := s.WaitForReceipt(s.ctx, tx.Hash())
|
||||
if err != nil {
|
||||
return ethcommon.Hash{}, fmt.Errorf("failed to call new_swap in contract: %w", err)
|
||||
}
|
||||
log.Debugf("instantiated swap on-chain: amount=%s txHash=%s", amount, txHash)
|
||||
|
||||
if len(receipt.Logs) == 0 {
|
||||
return ethcommon.Hash{}, errSwapInstantiationNoLogs
|
||||
@@ -437,14 +422,14 @@ func (s *swapState) lockETH(amount common.EtherAmount) (ethcommon.Hash, error) {
|
||||
return ethcommon.Hash{}, err
|
||||
}
|
||||
|
||||
return tx.Hash(), nil
|
||||
return txHash, nil
|
||||
}
|
||||
|
||||
// ready calls the Ready() method on the Swap contract, indicating to XMRMaker he has until time t_1 to
|
||||
// call Claim(). Ready() should only be called once XMRTaker sees XMRMaker lock his XMR.
|
||||
// If time t_0 has passed, there is no point of calling Ready().
|
||||
func (s *swapState) ready() error {
|
||||
tx, err := s.Contract().SetReady(s.txOpts, s.contractSwap)
|
||||
_, _, err := s.SetReady(s.contractSwap)
|
||||
if err != nil {
|
||||
if strings.Contains(err.Error(), revertSwapCompleted) && !s.info.Status().IsOngoing() {
|
||||
return nil
|
||||
@@ -453,10 +438,6 @@ func (s *swapState) ready() error {
|
||||
return err
|
||||
}
|
||||
|
||||
if _, err := s.WaitForReceipt(s.ctx, tx.Hash()); err != nil {
|
||||
return fmt.Errorf("failed to call is_ready in swap contract: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -471,17 +452,13 @@ func (s *swapState) refund() (ethcommon.Hash, error) {
|
||||
sc := s.getSecret()
|
||||
|
||||
log.Infof("attempting to call Refund()...")
|
||||
tx, err := s.Contract().Refund(s.txOpts, s.contractSwap, sc)
|
||||
txHash, _, err := s.Refund(s.contractSwap, sc)
|
||||
if err != nil {
|
||||
return ethcommon.Hash{}, err
|
||||
}
|
||||
|
||||
if _, err := s.WaitForReceipt(s.ctx, tx.Hash()); err != nil {
|
||||
return ethcommon.Hash{}, fmt.Errorf("failed to call Refund function in contract: %w", err)
|
||||
}
|
||||
|
||||
s.clearNextExpectedMessage(types.CompletedRefund)
|
||||
return tx.Hash(), nil
|
||||
return txHash, nil
|
||||
}
|
||||
|
||||
func (s *swapState) claimMonero(skB *mcrypto.PrivateSpendKey) (mcrypto.Address, error) {
|
||||
@@ -509,14 +486,20 @@ func (s *swapState) claimMonero(skB *mcrypto.PrivateSpendKey) (mcrypto.Address,
|
||||
}
|
||||
|
||||
log.Infof("monero claimed in account %s; transferring to original account %s",
|
||||
addr, s.walletAddress)
|
||||
addr, s.XMRDepositAddress())
|
||||
|
||||
err = mcrypto.ValidateAddress(string(s.XMRDepositAddress()))
|
||||
if err != nil {
|
||||
log.Errorf("failed to transfer to original account, address %s is invalid", addr)
|
||||
return addr, nil
|
||||
}
|
||||
|
||||
err = s.waitUntilBalanceUnlocks()
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to wait for balance to unlock: %w", err)
|
||||
}
|
||||
|
||||
res, err := s.SweepAll(s.walletAddress, 0)
|
||||
res, err := s.SweepAll(s.XMRDepositAddress(), 0)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to send funds to original account: %w", err)
|
||||
}
|
||||
@@ -528,7 +511,7 @@ func (s *swapState) claimMonero(skB *mcrypto.PrivateSpendKey) (mcrypto.Address,
|
||||
amount := res.AmountList[0]
|
||||
log.Infof("transferred %v XMR to %s",
|
||||
common.MoneroAmount(amount).AsMonero(),
|
||||
s.walletAddress,
|
||||
s.XMRDepositAddress(),
|
||||
)
|
||||
|
||||
close(s.claimedCh)
|
||||
@@ -544,7 +527,7 @@ func (s *swapState) waitUntilBalanceUnlocks() error {
|
||||
log.Infof("checking if balance unlocked...")
|
||||
|
||||
if s.Env() == common.Development {
|
||||
_ = s.GenerateBlocks(string(s.walletAddress), 64)
|
||||
_ = s.GenerateBlocks(string(s.XMRDepositAddress()), 64)
|
||||
_ = s.Refresh()
|
||||
}
|
||||
|
||||
|
||||
@@ -103,7 +103,7 @@ func newXMRMakerBackend(t *testing.T) backend.Backend {
|
||||
|
||||
func newTestInstance(t *testing.T) *swapState {
|
||||
b := newBackend(t)
|
||||
swapState, err := newSwapState(b, infofile, false, "",
|
||||
swapState, err := newSwapState(b, infofile, false,
|
||||
common.NewEtherAmount(1), common.MoneroAmount(0), 1)
|
||||
require.NoError(t, err)
|
||||
return swapState
|
||||
@@ -340,7 +340,9 @@ func TestSwapState_NotifyClaimed(t *testing.T) {
|
||||
var sc [32]byte
|
||||
copy(sc[:], common.Reverse(secret))
|
||||
|
||||
tx, err := s.Contract().Claim(s.txOpts, s.contractSwap, sc)
|
||||
txOpts, err := s.TxOpts()
|
||||
require.NoError(t, err)
|
||||
tx, err := s.Contract().Claim(txOpts, s.contractSwap, sc)
|
||||
require.NoError(t, err)
|
||||
|
||||
// handled the claimed message should result in the monero wallet being created
|
||||
|
||||
@@ -15,6 +15,7 @@ var (
|
||||
errCannotRefund = errors.New("cannot refund if not the ETH provider")
|
||||
|
||||
// ws errors
|
||||
errUnimplemented = errors.New("unimplemented")
|
||||
errInvalidMethod = errors.New("invalid method")
|
||||
errUnimplemented = errors.New("unimplemented")
|
||||
errInvalidMethod = errors.New("invalid method")
|
||||
errSignerNotRequired = errors.New("signer not required")
|
||||
)
|
||||
|
||||
@@ -8,7 +8,9 @@ import (
|
||||
|
||||
"github.com/noot/atomic-swap/common"
|
||||
"github.com/noot/atomic-swap/common/types"
|
||||
mcrypto "github.com/noot/atomic-swap/crypto/monero"
|
||||
"github.com/noot/atomic-swap/protocol/swap"
|
||||
"github.com/noot/atomic-swap/protocol/txsender"
|
||||
|
||||
ethcommon "github.com/ethereum/go-ethereum/common"
|
||||
"github.com/gorilla/handlers"
|
||||
@@ -59,7 +61,7 @@ func NewServer(cfg *Config) (*Server, error) {
|
||||
|
||||
return &Server{
|
||||
s: s,
|
||||
wsServer: newWsServer(cfg.Ctx, cfg.ProtocolBackend.SwapManager(), ns),
|
||||
wsServer: newWsServer(cfg.Ctx, cfg.ProtocolBackend.SwapManager(), ns, cfg.ProtocolBackend, cfg.ProtocolBackend.ExternalSender()), //nolint:lll
|
||||
port: cfg.Port,
|
||||
wsPort: cfg.WsPort,
|
||||
}, nil
|
||||
@@ -115,6 +117,9 @@ type ProtocolBackend interface {
|
||||
SetGasPrice(uint64)
|
||||
SetSwapTimeout(timeout time.Duration)
|
||||
SwapManager() swap.Manager
|
||||
ExternalSender() *txsender.ExternalSender
|
||||
SetEthAddress(ethcommon.Address)
|
||||
SetXMRDepositAddress(mcrypto.Address)
|
||||
}
|
||||
|
||||
// XMRTaker ...
|
||||
|
||||
100
rpc/ws.go
100
rpc/ws.go
@@ -8,7 +8,10 @@ import (
|
||||
|
||||
"github.com/noot/atomic-swap/common/rpctypes"
|
||||
"github.com/noot/atomic-swap/common/types"
|
||||
mcrypto "github.com/noot/atomic-swap/crypto/monero"
|
||||
"github.com/noot/atomic-swap/protocol/txsender"
|
||||
|
||||
ethcommon "github.com/ethereum/go-ethereum/common"
|
||||
"github.com/gorilla/websocket"
|
||||
)
|
||||
|
||||
@@ -17,22 +20,41 @@ const (
|
||||
subscribeMakeOffer = "net_makeOfferAndSubscribe"
|
||||
subscribeTakeOffer = "net_takeOfferAndSubscribe"
|
||||
subscribeSwapStatus = "swap_subscribeStatus"
|
||||
subscribeSigner = "signer_subscribe"
|
||||
)
|
||||
|
||||
var upgrader = websocket.Upgrader{}
|
||||
|
||||
type wsServer struct {
|
||||
ctx context.Context
|
||||
sm SwapManager
|
||||
ns *NetService
|
||||
var upgrader = websocket.Upgrader{
|
||||
CheckOrigin: checkOriginFunc,
|
||||
}
|
||||
|
||||
func newWsServer(ctx context.Context, sm SwapManager, ns *NetService) *wsServer {
|
||||
return &wsServer{
|
||||
ctx: ctx,
|
||||
sm: sm,
|
||||
ns: ns,
|
||||
func checkOriginFunc(r *http.Request) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
type wsServer struct {
|
||||
ctx context.Context
|
||||
sm SwapManager
|
||||
ns *NetService
|
||||
backend ProtocolBackend
|
||||
txsOutCh <-chan *txsender.Transaction
|
||||
txsInCh chan<- ethcommon.Hash
|
||||
}
|
||||
|
||||
func newWsServer(ctx context.Context, sm SwapManager, ns *NetService, backend ProtocolBackend,
|
||||
signer *txsender.ExternalSender) *wsServer {
|
||||
s := &wsServer{
|
||||
ctx: ctx,
|
||||
sm: sm,
|
||||
ns: ns,
|
||||
backend: backend,
|
||||
}
|
||||
|
||||
if signer != nil {
|
||||
s.txsOutCh = signer.OngoingCh()
|
||||
s.txsInCh = signer.IncomingCh()
|
||||
}
|
||||
|
||||
return s
|
||||
}
|
||||
|
||||
// ServeHTTP ...
|
||||
@@ -69,6 +91,13 @@ func (s *wsServer) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
func (s *wsServer) handleRequest(conn *websocket.Conn, req *rpctypes.Request) error {
|
||||
switch req.Method {
|
||||
case subscribeSigner:
|
||||
var params *rpctypes.SignerRequest
|
||||
if err := json.Unmarshal(req.Params, ¶ms); err != nil {
|
||||
return fmt.Errorf("failed to unmarshal parameters: %w", err)
|
||||
}
|
||||
|
||||
return s.handleSigner(s.ctx, conn, params.OfferID, params.EthAddress, params.XMRAddress)
|
||||
case subscribeNewPeer:
|
||||
return errUnimplemented
|
||||
case "net_discover":
|
||||
@@ -134,6 +163,55 @@ func (s *wsServer) handleRequest(conn *websocket.Conn, req *rpctypes.Request) er
|
||||
}
|
||||
}
|
||||
|
||||
func (s *wsServer) handleSigner(ctx context.Context, conn *websocket.Conn, offerID, ethAddress, xmrAddr string) error {
|
||||
if s.txsOutCh == nil {
|
||||
return errSignerNotRequired
|
||||
}
|
||||
|
||||
if err := mcrypto.ValidateAddress(xmrAddr); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
s.backend.SetEthAddress(ethcommon.HexToAddress(ethAddress))
|
||||
s.backend.SetXMRDepositAddress(mcrypto.Address(xmrAddr))
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return nil
|
||||
case tx := <-s.txsOutCh:
|
||||
log.Debugf("outbound tx: %v", tx)
|
||||
resp := &rpctypes.SignerResponse{
|
||||
OfferID: offerID,
|
||||
To: tx.To.String(),
|
||||
Data: tx.Data,
|
||||
Value: tx.Value,
|
||||
}
|
||||
|
||||
err := conn.WriteJSON(resp)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, message, err := conn.ReadMessage()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var params *rpctypes.SignerTxSigned
|
||||
if err := json.Unmarshal(message, ¶ms); err != nil {
|
||||
return fmt.Errorf("failed to unmarshal parameters: %w", err)
|
||||
}
|
||||
|
||||
if params.OfferID != offerID {
|
||||
return fmt.Errorf("got unexpected offerID %s, expected %s", params.OfferID, offerID)
|
||||
}
|
||||
|
||||
s.txsInCh <- ethcommon.HexToHash(params.TxHash)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (s *wsServer) subscribeTakeOffer(ctx context.Context, conn *websocket.Conn,
|
||||
id uint64, statusCh <-chan types.Status, infofile string) error {
|
||||
resp := &rpctypes.TakeOfferResponse{
|
||||
|
||||
@@ -9,9 +9,11 @@ import (
|
||||
|
||||
"github.com/noot/atomic-swap/common"
|
||||
"github.com/noot/atomic-swap/common/types"
|
||||
mcrypto "github.com/noot/atomic-swap/crypto/monero"
|
||||
"github.com/noot/atomic-swap/net"
|
||||
"github.com/noot/atomic-swap/net/message"
|
||||
"github.com/noot/atomic-swap/protocol/swap"
|
||||
"github.com/noot/atomic-swap/protocol/txsender"
|
||||
"github.com/noot/atomic-swap/rpcclient/wsclient"
|
||||
|
||||
ethcommon "github.com/ethereum/go-ethereum/common"
|
||||
@@ -131,6 +133,11 @@ func (*mockProtocolBackend) SetSwapTimeout(timeout time.Duration) {}
|
||||
func (b *mockProtocolBackend) SwapManager() swap.Manager {
|
||||
return b.sm
|
||||
}
|
||||
func (*mockProtocolBackend) ExternalSender() *txsender.ExternalSender {
|
||||
return nil
|
||||
}
|
||||
func (*mockProtocolBackend) SetEthAddress(ethcommon.Address) {}
|
||||
func (*mockProtocolBackend) SetXMRDepositAddress(mcrypto.Address) {}
|
||||
|
||||
func newServer(t *testing.T) *Server {
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
|
||||
6398
ui/package-lock.json
generated
Normal file
6398
ui/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
@@ -12,6 +12,7 @@
|
||||
"devDependencies": {
|
||||
"@mdi/js": "^6.5.95",
|
||||
"@rollup/plugin-commonjs": "^17.0.0",
|
||||
"@rollup/plugin-json": "^4.1.0",
|
||||
"@rollup/plugin-node-resolve": "^11.0.0",
|
||||
"@rollup/plugin-typescript": "^8.0.0",
|
||||
"@smui/button": "^6.0.0-beta.13",
|
||||
@@ -42,7 +43,9 @@
|
||||
"typescript": "^4.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"@metamask/detect-provider": "^1.2.0",
|
||||
"axios": "0.21.1",
|
||||
"ethers": "^5.6.8",
|
||||
"sirv-cli": "^2.0.0",
|
||||
"svelte-material-ui": "^6.0.0-beta.13"
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ import { terser } from 'rollup-plugin-terser'
|
||||
import sveltePreprocess from 'svelte-preprocess'
|
||||
import typescript from '@rollup/plugin-typescript'
|
||||
import css from 'rollup-plugin-css-only'
|
||||
import json from '@rollup/plugin-json'
|
||||
|
||||
const production = !process.env.ROLLUP_WATCH
|
||||
|
||||
@@ -43,6 +44,9 @@ export default {
|
||||
file: 'public/build/bundle.js',
|
||||
},
|
||||
plugins: [
|
||||
json({
|
||||
compact: true
|
||||
}),
|
||||
svelte({
|
||||
preprocess: sveltePreprocess({ sourceMap: !production }),
|
||||
compilerOptions: {
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
import Button from '@smui/button'
|
||||
import { peers, getPeers } from './stores/peerStore'
|
||||
import { offers, refreshOffers } from './stores/offerStore'
|
||||
import { connectAccount, currentAccount } from './stores/metamask'
|
||||
import OffersTable from './components/OffersTable.svelte'
|
||||
import StatCard from './components/StatCard.svelte'
|
||||
import TakeDealDrawer from './components/TakeDealDialog.svelte'
|
||||
@@ -11,6 +12,13 @@
|
||||
const handleRefreshClick = () => {
|
||||
getPeers()
|
||||
}
|
||||
|
||||
let account = null;
|
||||
|
||||
const connectMetamask = () => {
|
||||
connectAccount()
|
||||
account = $currentAccount
|
||||
}
|
||||
</script>
|
||||
|
||||
<main>
|
||||
@@ -27,6 +35,9 @@
|
||||
<Cell class="refreshButton">
|
||||
<Button on:click={handleRefreshClick}>Refresh</Button>
|
||||
</Cell>
|
||||
<Cell class="metamask">
|
||||
<Button on:click={connectMetamask}>{account === null ? 'Connect Metamask' : `Metamask connected: ${account}`}</Button>
|
||||
</Cell>
|
||||
</InnerGrid>
|
||||
<br />
|
||||
<OffersTable />
|
||||
|
||||
@@ -11,8 +11,13 @@
|
||||
import { Svg } from '@smui/common/elements'
|
||||
import CircularProgress from '@smui/circular-progress'
|
||||
import HelperText from '@smui/textfield/helper-text'
|
||||
import { currentAccount, sign } from "../stores/metamask"
|
||||
import { onMount } from 'svelte'
|
||||
|
||||
const WS_ADDRESS = 'ws://127.0.0.1:8081'
|
||||
|
||||
let amountProvided: number | null = null
|
||||
let xmrAddress = ""
|
||||
let isSuccess = false
|
||||
let isLoadingSwap = false
|
||||
let error = ''
|
||||
@@ -44,10 +49,47 @@
|
||||
}
|
||||
|
||||
const handleSendTakeOffer = () => {
|
||||
let offerID = $selectedOffer?.id
|
||||
let webSocket = new WebSocket(WS_ADDRESS)
|
||||
|
||||
webSocket.onopen = () => {
|
||||
console.log('opened')
|
||||
console.log("sending ws signer msg")
|
||||
let req = {
|
||||
method: "signer_subscribe",
|
||||
params: {
|
||||
jsonRPC: "2.0",
|
||||
id: "0",
|
||||
offerID: offerID,
|
||||
ethAddress: $currentAccount,
|
||||
xmrAddress: xmrAddress,
|
||||
}
|
||||
}
|
||||
webSocket.send(JSON.stringify(req))
|
||||
console.log("sent ws signer msg", req)
|
||||
|
||||
}
|
||||
webSocket.onmessage = async (msg) => {
|
||||
console.log('message to sign:', msg.data)
|
||||
let txHash = await sign(msg.data)
|
||||
let out = {
|
||||
offerID: offerID,
|
||||
txHash: txHash,
|
||||
}
|
||||
webSocket.send(JSON.stringify(out))
|
||||
}
|
||||
webSocket.onclose = (e) => {
|
||||
console.log('closed:', e)
|
||||
}
|
||||
webSocket.onerror = (e) => {
|
||||
console.log('error', e)
|
||||
}
|
||||
|
||||
isLoadingSwap = true
|
||||
|
||||
rpcRequest<NetTakeOfferSyncResult | undefined>('net_takeOfferSync', {
|
||||
multiaddr: $selectedOffer?.peer,
|
||||
offerID: $selectedOffer?.id,
|
||||
offerID: offerID,
|
||||
providesAmount: Number(amountProvided),
|
||||
})
|
||||
.then(({ result }) => {
|
||||
@@ -60,6 +102,8 @@
|
||||
swapError =
|
||||
'Something went wrong. Swap funds refunded, please check the logs for more info'
|
||||
}
|
||||
|
||||
webSocket.close()
|
||||
})
|
||||
.catch((e: Error) => {
|
||||
console.error('error when swapping', e)
|
||||
@@ -89,6 +133,7 @@
|
||||
<Title class="title" id="mandatory-title">
|
||||
Swap offer {$selectedOffer.id}
|
||||
</Title>
|
||||
<span>{$selectedOffer.peer}</span>
|
||||
</div>
|
||||
<Content id="mandatory-content">
|
||||
<section class="container">
|
||||
@@ -124,6 +169,14 @@
|
||||
>
|
||||
<HelperText slot="helper">{error}</HelperText>
|
||||
</Textfield>
|
||||
<Textfield
|
||||
bind:value={xmrAddress}
|
||||
variant="outlined"
|
||||
label={"XMR address"}
|
||||
invalid={!!error}
|
||||
>
|
||||
<HelperText slot="helper">{error}</HelperText>
|
||||
</Textfield>
|
||||
<Icon class="swapIcon" component={Svg} viewBox="0 0 24 24">
|
||||
<path fill="currentColor" d={mdiSwapVertical} />
|
||||
</Icon>
|
||||
|
||||
96
ui/src/stores/metamask.ts
Normal file
96
ui/src/stores/metamask.ts
Normal file
@@ -0,0 +1,96 @@
|
||||
import {providers, Contract, utils} from "ethers"
|
||||
import detectEthereumProvider from "@metamask/detect-provider"
|
||||
import { writable } from 'svelte/store';
|
||||
import SwapFactory from "../../../ethereum/artifacts/contracts/SwapFactory.sol/SwapFactory.json"
|
||||
|
||||
ethereum.on('chainChanged', (_chainId) => window.location.reload());
|
||||
ethereum.on('accountsChanged', handleAccountsChanged);
|
||||
|
||||
export const currentAccount = writable(null);
|
||||
|
||||
export const connectAccount = async () => {
|
||||
const provider = (await detectEthereumProvider()) as any
|
||||
if (provider) {
|
||||
startApp(provider);
|
||||
await provider.request({ method: "eth_accounts" }).then(handleAccountsChanged)
|
||||
.catch((err) => {
|
||||
// Some unexpected error.
|
||||
// For backwards compatibility reasons, if no accounts are available,
|
||||
// eth_accounts will return an empty array.
|
||||
console.error(err);
|
||||
});
|
||||
await initialize()
|
||||
} else {
|
||||
console.error("Metamask is not installed")
|
||||
}
|
||||
}
|
||||
|
||||
function startApp(provider) {
|
||||
// If the provider returned by detectEthereumProvider is not the same as
|
||||
// window.ethereum, something is overwriting it, perhaps another wallet.
|
||||
if (provider !== window.ethereum) {
|
||||
console.error('Do you have multiple wallets installed?');
|
||||
}
|
||||
// Access the decentralized web!
|
||||
}
|
||||
|
||||
// Note that this event is emitted on page load.
|
||||
// If the array of accounts is non-empty, you're already
|
||||
// connected.
|
||||
ethereum.on('accountsChanged', handleAccountsChanged);
|
||||
|
||||
// For now, 'eth_accounts' will continue to always return an array
|
||||
function handleAccountsChanged(accounts) {
|
||||
if (accounts.length === 0) {
|
||||
// MetaMask is locked or the user has not connected any accounts
|
||||
console.log('Please connect to MetaMask.');
|
||||
} else if (accounts[0] !== currentAccount) {
|
||||
currentAccount.set(accounts[0]);
|
||||
console.log(currentAccount)
|
||||
console.log(accounts[0])
|
||||
// Do any other work!
|
||||
}
|
||||
}
|
||||
|
||||
let ethersProvider
|
||||
let chainId
|
||||
let contract
|
||||
let signer
|
||||
let from
|
||||
|
||||
const initialize = async () => {
|
||||
ethersProvider = new providers.Web3Provider(window.ethereum, 'any');
|
||||
window.ethersProvider = ethersProvider
|
||||
signer = ethersProvider.getSigner()
|
||||
console.log("signer:", await signer.getAddress())
|
||||
}
|
||||
|
||||
export const sign = async(msg) => {
|
||||
let tx = JSON.parse(msg)
|
||||
let value
|
||||
if (tx.value != "") {
|
||||
value = utils.parseEther(tx.value)
|
||||
}
|
||||
|
||||
let params =
|
||||
{
|
||||
from: signer.getAddress(),
|
||||
to: tx.to,
|
||||
gasPrice: window.ethersProvider.getGasPrice(),
|
||||
gasLimit: "200000",
|
||||
value: value,
|
||||
data: tx.data,
|
||||
}
|
||||
|
||||
console.log("sending transaction:", params)
|
||||
let res
|
||||
try {
|
||||
res = await signer.sendTransaction(params)
|
||||
console.log(res)
|
||||
} catch (e) {
|
||||
console.error("tx failed", e)
|
||||
return ""
|
||||
}
|
||||
|
||||
return res.hash
|
||||
}
|
||||
@@ -1,7 +1,6 @@
|
||||
export const intToHexString = (input: number[]) => {
|
||||
const hexArray = input.map((n) => {
|
||||
const num = Number(n).toString(16).padStart(2, "0")
|
||||
// console.log(n, num)
|
||||
return num
|
||||
})
|
||||
|
||||
|
||||
2961
ui/yarn.lock
2961
ui/yarn.lock
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user