tx.new_swap: swap to ERC20 token support
This commit is contained in:
parent
ccfd3ee8c7
commit
38ea93a51f
6 changed files with 171 additions and 31 deletions
|
|
@ -12,8 +12,9 @@
|
|||
swap.proto.thorchain.thornode: THORChain swap protocol network query ops
|
||||
"""
|
||||
|
||||
import json
|
||||
import time, json
|
||||
from collections import namedtuple
|
||||
|
||||
from ....amt import UniAmt
|
||||
|
||||
_gd = namedtuple('gas_unit_data', ['code', 'disp'])
|
||||
|
|
@ -60,15 +61,36 @@ class Thornode:
|
|||
self.rpc = ThornodeRPCClient(tx)
|
||||
|
||||
def get_quote(self):
|
||||
self.get_str = '/thorchain/quote/swap?from_asset={a}&to_asset={b}&amount={c}'.format(
|
||||
a = self.tx.send_asset.full_name,
|
||||
b = self.tx.recv_asset.full_name,
|
||||
c = self.in_amt.to_unit('satoshi'))
|
||||
self.result = self.rpc.get(self.get_str)
|
||||
self.data = json.loads(self.result.content)
|
||||
if not 'expiry' in self.data:
|
||||
from ....util import pp_fmt, die
|
||||
die(2, pp_fmt(self.data))
|
||||
|
||||
def get_data(send, recv, amt):
|
||||
get_str = f'/thorchain/quote/swap?from_asset={send}&to_asset={recv}&amount={amt}'
|
||||
data = json.loads(self.rpc.get(get_str).content)
|
||||
if not 'expiry' in data:
|
||||
from ....util import pp_fmt, die
|
||||
die(2, pp_fmt(data))
|
||||
return data
|
||||
|
||||
if self.tx.proto.tokensym or self.tx.recv_asset.asset: # token swap
|
||||
in_data = get_data(
|
||||
self.tx.send_asset.full_name,
|
||||
'THOR.RUNE',
|
||||
self.in_amt.to_unit('satoshi'))
|
||||
if self.tx.proto.network != 'regtest':
|
||||
time.sleep(1.1) # ninerealms max request rate 1/sec
|
||||
out_data = get_data(
|
||||
'THOR.RUNE',
|
||||
self.tx.recv_asset.full_name,
|
||||
in_data['expected_amount_out'])
|
||||
self.data = in_data | {
|
||||
'expected_amount_out': out_data['expected_amount_out'],
|
||||
'fees': out_data['fees'],
|
||||
'expiry': min(in_data['expiry'], out_data['expiry'])
|
||||
}
|
||||
else:
|
||||
self.data = get_data(
|
||||
self.tx.send_asset.full_name,
|
||||
self.tx.recv_asset.full_name,
|
||||
self.in_amt.to_unit('satoshi'))
|
||||
|
||||
async def format_quote(self, trade_limit, usr_trade_limit, *, deduct_est_fee=False):
|
||||
from ....util import make_timestr, ymsg
|
||||
|
|
|
|||
|
|
@ -79,8 +79,12 @@ class Completed(Base):
|
|||
assert p.function == 'SWAP', f'‘{p.function}’: unsupported function in swap memo ‘{text}’'
|
||||
aname = p.chain + (f'.{p.asset}' if p.asset != p.chain else '')
|
||||
assert aname == self.recv_asset.name, f'invalid memo: {aname} != {self.recv_asset.name}'
|
||||
assert p.chain == p.asset, f'{p.chain} != {p.asset}: chain/asset mismatch in swap memo ‘{text}’'
|
||||
proto = init_proto(self.cfg, p.asset, network=self.cfg.network, need_amt=True)
|
||||
proto = init_proto(
|
||||
self.cfg,
|
||||
p.chain,
|
||||
network = self.cfg.network,
|
||||
tokensym = None if p.chain == p.asset else p.asset,
|
||||
need_amt = True)
|
||||
if self.swap_recv_addr_mmid:
|
||||
mmid = self.swap_recv_addr_mmid
|
||||
elif self.cfg.allow_non_wallet_swap:
|
||||
|
|
|
|||
|
|
@ -68,17 +68,20 @@ class CmdTestEthSwap(CmdTestSwapMethods, CmdTestRegtest):
|
|||
eth_group = 'ethswap_eth'
|
||||
|
||||
cmd_group_in = (
|
||||
('setup', 'regtest (Bob and Alice) mode setup'),
|
||||
('eth_setup', 'Ethereum devnet setup'),
|
||||
('subgroup.init', []),
|
||||
('subgroup.fund', ['init']),
|
||||
('subgroup.eth_init', []),
|
||||
('subgroup.eth_fund', ['eth_init']),
|
||||
('subgroup.swap', ['fund', 'eth_fund']),
|
||||
('subgroup.eth_swap', ['fund', 'eth_fund']),
|
||||
('stop', 'stopping regtest daemon'),
|
||||
('eth_stop', 'stopping Ethereum daemon'),
|
||||
('thornode_server_stop', 'stopping the Thornode server'),
|
||||
('setup', 'regtest (Bob and Alice) mode setup'),
|
||||
('eth_setup', 'Ethereum devnet setup'),
|
||||
('subgroup.init', []),
|
||||
('subgroup.fund', ['init']),
|
||||
('subgroup.eth_init', []),
|
||||
('subgroup.eth_fund', ['eth_init']),
|
||||
('subgroup.swap', ['fund', 'eth_fund']),
|
||||
('subgroup.eth_swap', ['fund', 'eth_fund']),
|
||||
('subgroup.token_init', ['eth_fund']),
|
||||
('subgroup.token_swap', ['fund', 'token_init']),
|
||||
('subgroup.eth_token_swap', ['fund', 'token_init']),
|
||||
('stop', 'stopping regtest daemon'),
|
||||
('eth_stop', 'stopping Ethereum daemon'),
|
||||
('thornode_server_stop', 'stopping the Thornode server'),
|
||||
)
|
||||
cmd_subgroups = {
|
||||
'init': (
|
||||
|
|
@ -107,6 +110,23 @@ class CmdTestEthSwap(CmdTestSwapMethods, CmdTestRegtest):
|
|||
('eth_fund_mmgen_addr2', ''),
|
||||
('eth_bal1', ''),
|
||||
),
|
||||
'token_init': (
|
||||
'deploying tokens and initializing the ETH token tracking wallet',
|
||||
('eth_token_compile1', ''),
|
||||
('eth_token_deploy_a', ''),
|
||||
('eth_token_deploy_b', ''),
|
||||
('eth_token_deploy_c', ''),
|
||||
('eth_token_fund_user', ''),
|
||||
('eth_token_addrgen', ''),
|
||||
('eth_token_addrimport', ''),
|
||||
('eth_token_bal1', ''),
|
||||
),
|
||||
'token_swap': (
|
||||
'token swap operations (BTC -> MM1)',
|
||||
('swaptxcreate3', 'creating a BTC->MM1 swap transaction'),
|
||||
('swaptxsign3', 'signing the swap transaction'),
|
||||
('swaptxsend3', 'sending the swap transaction'),
|
||||
),
|
||||
'swap': (
|
||||
'swap operations (BTC -> ETH)',
|
||||
('swaptxcreate1', 'creating a BTC->ETH swap transaction'),
|
||||
|
|
@ -131,6 +151,12 @@ class CmdTestEthSwap(CmdTestSwapMethods, CmdTestRegtest):
|
|||
('eth_swaptxstatus1', ''),
|
||||
('eth_bal2', ''),
|
||||
),
|
||||
'eth_token_swap': (
|
||||
'swap operations (ETH <-> MM1)',
|
||||
('eth_swaptxcreate3', ''),
|
||||
('eth_swaptxsign3', ''),
|
||||
('eth_swaptxsend3', ''),
|
||||
),
|
||||
}
|
||||
|
||||
eth_tests = [c[0] for v in tuple(cmd_subgroups.values()) + (cmd_group_in,)
|
||||
|
|
@ -178,8 +204,8 @@ class CmdTestEthSwap(CmdTestSwapMethods, CmdTestRegtest):
|
|||
def swaptxsend1(self):
|
||||
return self._swaptxsend()
|
||||
|
||||
swaptxsign2 = swaptxsign1
|
||||
swaptxsend2 = swaptxsend1
|
||||
swaptxsign2 = swaptxsign3 = swaptxsign1
|
||||
swaptxsend2 = swaptxsend3 = swaptxsend1
|
||||
|
||||
def swaptxbump1(self): # create one-output TX back to self to rescue funds
|
||||
return self._swaptxbump('40s', output_args=[f'{dfl_sid}:B:1'])
|
||||
|
|
@ -198,6 +224,11 @@ class CmdTestEthSwap(CmdTestSwapMethods, CmdTestRegtest):
|
|||
def bob_bal3(self):
|
||||
return self._user_bal_cli('bob', chk='499.77656902')
|
||||
|
||||
def swaptxcreate3(self):
|
||||
t = self._swaptxcreate(['BTC', '0.87654321', 'ETH.MM1', f'{dfl_sid}:E:5'])
|
||||
t.expect('OK? (Y/n): ', 'y')
|
||||
return self._swaptxcreate_ui_common(t)
|
||||
|
||||
def thornode_server_stop(self):
|
||||
self.spawn(msg_only=True)
|
||||
thornode_server.stop()
|
||||
|
|
@ -224,8 +255,19 @@ class CmdTestEthSwapEth(CmdTestEthSwapMethods, CmdTestSwapMethods, CmdTestEthdev
|
|||
('swaptxsign1', 'signing the transaction'),
|
||||
('swaptxsend1', 'sending the transaction'),
|
||||
('swaptxstatus1', 'getting the transaction status (with --verbose)'),
|
||||
('swaptxcreate3', 'creating an ETH->MM1 swap transaction'),
|
||||
('swaptxsign3', 'signing the transaction'),
|
||||
('swaptxsend3', 'sending the transaction'),
|
||||
('bal1', 'the ETH balance'),
|
||||
('bal2', 'the ETH balance'),
|
||||
('token_compile1', 'compiling ERC20 token #1'),
|
||||
('token_deploy_a', 'deploying ERC20 token MM1 (SafeMath)'),
|
||||
('token_deploy_b', 'deploying ERC20 token MM1 (Owned)'),
|
||||
('token_deploy_c', 'deploying ERC20 token MM1 (Token)'),
|
||||
('token_fund_user', 'transferring token funds from dev to user'),
|
||||
('token_addrgen', 'generating token addresses'),
|
||||
('token_addrimport', 'importing token addresses using token address (MM1)'),
|
||||
('token_bal1', 'the token balance'),
|
||||
)
|
||||
|
||||
def swaptxcreate1(self):
|
||||
|
|
@ -240,12 +282,19 @@ class CmdTestEthSwapEth(CmdTestEthSwapMethods, CmdTestSwapMethods, CmdTestEthdev
|
|||
add_opts = ['--trade-limit=3%']),
|
||||
expect = ':2019e4/1/0')
|
||||
|
||||
def swaptxcreate3(self):
|
||||
t = self._swaptxcreate(['ETH', '8.765', 'ETH.MM1', f'{dfl_sid}:E:5'])
|
||||
return self._swaptxcreate_ui_common(t)
|
||||
|
||||
def swaptxsign1(self):
|
||||
return self._swaptxsign()
|
||||
|
||||
def swaptxsend1(self):
|
||||
return self._swaptxsend()
|
||||
|
||||
swaptxsign3 = swaptxsign1
|
||||
swaptxsend3 = swaptxsend1
|
||||
|
||||
def swaptxstatus1(self):
|
||||
self.mining_delay()
|
||||
return self._swaptxsend(add_opts=['--verbose', '--status'], status=True)
|
||||
|
|
|
|||
|
|
@ -24,10 +24,52 @@ cfg = Config()
|
|||
# https://thornode.ninerealms.com/thorchain/quote/swap?from_asset=BCH.BCH&to_asset=LTC.LTC&amount=1000000
|
||||
sample_request = 'GET /thorchain/quote/swap?from_asset=BCH.BCH&to_asset=LTC.LTC&amount=1000000000'
|
||||
request_pat = r'/thorchain/quote/swap\?from_asset=(\S+)\.(\S+)&to_asset=(\S+)\.(\S+)&amount=(\d+)'
|
||||
prices = {'BTC': 97000, 'LTC': 115, 'BCH': 330, 'ETH': 2304}
|
||||
prices = {'BTC': 97000, 'LTC': 115, 'BCH': 330, 'ETH': 2304, 'MM1': 0.998, 'RUNE': 1.4}
|
||||
gas_rate_units = {'ETH': 'gwei', 'BTC': 'satsperbyte'}
|
||||
recommended_gas_rate = {'ETH': '1', 'BTC': '6'}
|
||||
|
||||
data_template_from_rune = {
|
||||
'outbound_delay_blocks': 0,
|
||||
'outbound_delay_seconds': 0,
|
||||
'fees': {
|
||||
'asset': 'BTC.BTC',
|
||||
'affiliate': '0',
|
||||
'outbound': '1182',
|
||||
'liquidity': '110',
|
||||
'total': '1292',
|
||||
'slippage_bps': 7,
|
||||
'total_bps': 92
|
||||
},
|
||||
'warning': 'Do not cache this response. Do not send funds after the expiry.',
|
||||
'notes': 'Broadcast a MsgDeposit to the THORChain network with the appropriate memo. Do not use multi-in, multi-out transactions.',
|
||||
'max_streaming_quantity': 0,
|
||||
'streaming_swap_blocks': 0
|
||||
}
|
||||
|
||||
data_template_to_rune = {
|
||||
'inbound_confirmation_blocks': 2,
|
||||
'inbound_confirmation_seconds': 24,
|
||||
'outbound_delay_blocks': 0,
|
||||
'outbound_delay_seconds': 0,
|
||||
'fees': {
|
||||
'asset': 'THOR.RUNE',
|
||||
'affiliate': '0',
|
||||
'outbound': '2000000',
|
||||
'liquidity': '684966',
|
||||
'total': '2684966',
|
||||
'slippage_bps': 8,
|
||||
'total_bps': 31
|
||||
},
|
||||
'router': '0xD37BbE5744D730a1d98d8DC97c42F0Ca46aD7146',
|
||||
'warning': 'Do not cache this response. Do not send funds after the expiry.',
|
||||
'notes': 'Base Asset: Send the inbound_address the asset with the memo encoded in hex in the data field. Tokens: First approve router to spend tokens from user: asset.approve(router, amount). Then call router.depositWithExpiry(inbound_address, asset, amount, memo, expiry). Asset is the token contract address. Amount should be in native asset decimals (eg 1e18 for most tokens). Do not swap to smart contract addresses.',
|
||||
'dust_threshold': '1',
|
||||
'recommended_gas_rate': '1',
|
||||
'max_streaming_quantity': 0,
|
||||
'streaming_swap_blocks': 0,
|
||||
'total_swap_seconds': 24
|
||||
}
|
||||
|
||||
data_template_btc = {
|
||||
'inbound_confirmation_blocks': 4,
|
||||
'inbound_confirmation_seconds': 2400,
|
||||
|
|
@ -98,17 +140,24 @@ class ThornodeServer(HTTPD):
|
|||
out_amt = in_amt * (prices[send_asset] / prices[recv_asset])
|
||||
|
||||
data_template = (
|
||||
data_template_from_rune if send_asset == 'RUNE' else
|
||||
data_template_to_rune if recv_asset == 'RUNE' else
|
||||
data_template_eth if send_asset == 'ETH' else
|
||||
data_template_btc)
|
||||
|
||||
from mmgen.protocol import init_proto
|
||||
send_proto = init_proto(cfg, send_chain, network='regtest', need_amt=True)
|
||||
data = data_template | {
|
||||
'recommended_min_amount_in': str(int(70 * 10**8 / prices[send_asset])), # $70
|
||||
'expected_amount_out': str(out_amt.to_unit('satoshi')),
|
||||
'expiry': int(time.time()) + (10 * 60),
|
||||
'inbound_address': make_inbound_addr(send_proto, send_proto.preferred_mmtypes[0]),
|
||||
'gas_rate_units': gas_rate_units[send_proto.base_proto_coin],
|
||||
'recommended_gas_rate': recommended_gas_rate[send_proto.base_proto_coin],
|
||||
}
|
||||
|
||||
if send_asset != 'RUNE':
|
||||
from mmgen.protocol import init_proto
|
||||
send_proto = init_proto(cfg, send_chain, network='regtest', need_amt=True)
|
||||
data.update({
|
||||
'inbound_address': make_inbound_addr(send_proto, send_proto.preferred_mmtypes[0]),
|
||||
'gas_rate_units': gas_rate_units[send_proto.base_proto_coin],
|
||||
'recommended_gas_rate': recommended_gas_rate[send_proto.base_proto_coin]
|
||||
})
|
||||
|
||||
return json.dumps(data).encode()
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ class unit_tests:
|
|||
('BTC', 'BTC.BTC', 'b', 'BTC', None, 'recv'),
|
||||
('LTC', 'LTC.LTC', 'l', 'LTC', None, 'recv'),
|
||||
('BCH', 'BCH.BCH', 'c', 'BCH', None, 'recv'),
|
||||
('ETH.USDT', 'ETH.USDT', 'ETH.USDT', 'ETH', 'USDT', 'recv'),
|
||||
):
|
||||
a = SwapAsset(name, direction)
|
||||
vmsg(f' {a.name}')
|
||||
|
|
@ -36,6 +37,7 @@ class unit_tests:
|
|||
('ltc', 'bech32', 'LTC', None),
|
||||
('bch', 'compressed', 'BCH', None),
|
||||
('eth', None, 'ETH', None),
|
||||
('eth', None, 'ETH.USDT', 'USDT'),
|
||||
):
|
||||
proto = init_proto(cfg, coin, tokensym=token, need_amt=True)
|
||||
addr = make_burn_addr(proto, addrtype)
|
||||
|
|
|
|||
14
test/overlay/fakemods/mmgen/swap/proto/thorchain/asset.py
Executable file
14
test/overlay/fakemods/mmgen/swap/proto/thorchain/asset.py
Executable file
|
|
@ -0,0 +1,14 @@
|
|||
from .asset_orig import *
|
||||
|
||||
class overlay_fake_THORChainSwapAsset:
|
||||
|
||||
assets_data = {
|
||||
'ETH.MM1': THORChainSwapAsset._ad('MM1 Token (ETH)', None, 'ETH.MM1', None),
|
||||
'ETH.USDT': THORChainSwapAsset._ad('Tether (ETH)', None, 'ETH.USDT', None)
|
||||
}
|
||||
send = ('ETH.MM1',)
|
||||
recv = ('ETH.MM1', 'ETH.USDT')
|
||||
|
||||
THORChainSwapAsset.assets_data |= overlay_fake_THORChainSwapAsset.assets_data
|
||||
THORChainSwapAsset.send += overlay_fake_THORChainSwapAsset.send
|
||||
THORChainSwapAsset.recv += overlay_fake_THORChainSwapAsset.recv
|
||||
Loading…
Add table
Add a link
Reference in a new issue