diff --git a/packages/exchange/src/index.ts b/packages/exchange/src/index.ts index 5794a22b..9ad83715 100644 --- a/packages/exchange/src/index.ts +++ b/packages/exchange/src/index.ts @@ -5,3 +5,4 @@ export default Exchange; export { default as Pair, ValueTypes, ExchangeParams } from './pairs/Pair'; export { default as Uniswap } from './pairs/Uniswap'; export { default as XDaiBridge } from './pairs/XDaiBridge'; +export { default as Bridge } from './pairs/Bridge'; diff --git a/packages/exchange/src/pairs/Bridge.ts b/packages/exchange/src/pairs/Bridge.ts new file mode 100644 index 00000000..390f2ffa --- /dev/null +++ b/packages/exchange/src/pairs/Bridge.ts @@ -0,0 +1,55 @@ +import Pair, { ExchangeParams, ValueTypes } from './Pair'; + +interface BridgePairConstructor { + assetA: string; + assetABridge: string; + assetB: string; + assetBBridge: string; +} + +export default class Bridge extends Pair { + protected readonly assetABridge: string; + protected readonly assetBBridge: string; + + constructor({ assetA, assetABridge, assetB, assetBBridge }: BridgePairConstructor) { + super({ assetA, assetB }); + this.assetABridge = assetABridge; + this.assetBBridge = assetBBridge; + } + + async exchangeAtoB({ account, value, ether }: ExchangeParams) { + const _value = this._getValue({ value, ether }); + const asset = this.getExchange().getAsset(this.assetA); + const result = await asset.send({from: account, value: _value, to: this.assetABridge}); + await this.detectExchangeAToBFinished(account, _value, result); + return result; + } + + async exchangeBtoA({ account, value, ether }: ExchangeParams) { + const _value = this._getValue({ value, ether }); + const asset = this.getExchange().getAsset(this.assetB); + const result = await asset.send({from: account, value: _value, to: this.assetBBridge}); + await this.detectExchangeBToAFinished(account, _value, result); + return result; + } + + async estimateAtoB(value: ValueTypes) { + return this._getValue(value); + } + + async estimateBtoA(value: ValueTypes) { + return this._getValue(value); + } + + getLoadingMessage(): string { + return 'Exchanging assets.. please wait until the bridge relays the transaction'; + } + + async detectExchangeBToAFinished(account: string, value: string, sendResult: any): Promise { + throw new Error('detect exchange B to A finished not implemented'); + } + + async detectExchangeAToBFinished(account: string, value: string, sendResult: any): Promise { + throw new Error('detect exchange A to B finished not implemented'); + } +} diff --git a/packages/exchange/src/pairs/Pair.ts b/packages/exchange/src/pairs/Pair.ts index 16228fda..8e215cf3 100644 --- a/packages/exchange/src/pairs/Pair.ts +++ b/packages/exchange/src/pairs/Pair.ts @@ -53,4 +53,8 @@ export default abstract class Pair { const web3 = this.getExchange().getWeb3(this.getExchange().getAsset(this.assetA).network); return web3.utils.toWei(ether as string, 'ether'); } + + getLoadingMessage(): string { + return 'Exchanging assets...' + } } diff --git a/packages/exchange/src/pairs/XDaiBridge.ts b/packages/exchange/src/pairs/XDaiBridge.ts index 5e452bfd..8ce0dc64 100644 --- a/packages/exchange/src/pairs/XDaiBridge.ts +++ b/packages/exchange/src/pairs/XDaiBridge.ts @@ -1,39 +1,115 @@ -import Pair, { ExchangeParams, ValueTypes } from './Pair'; +import Bridge from "./Bridge"; +import { EventData } from "web3-eth-contract"; +import { AbiItem } from "web3-utils"; -const toXdaiBridge = '0x4aa42145Aa6Ebf72e164C9bBC74fbD3788045016'; -const toDaiBridge = '0x7301cfa0e1756b71869e93d4e4dca5c7d0eb0aa6'; - -export default class XDaiBridge extends Pair { - constructor() { - super({ assetA: 'xdai', assetB: 'dai' }); +const bridgeAAbi: AbiItem[] = [ + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "name": "recipient", + "type": "address" + }, + { + "indexed": false, + "name": "value", + "type": "uint256" + }, + { + "indexed": false, + "name": "transactionHash", + "type": "bytes32" + } + ], + "name": "AffirmationCompleted", + "type": "event" } +] - exchangeAtoB({ account, value, ether }: ExchangeParams) { - const _value = this._getValue({ value, ether }); - const xdai = this.getExchange().getAsset('xdai'); - return xdai.send({ - from: account, - value: _value, - to: toDaiBridge, - }); +const bridgeBAbi: AbiItem[] = [ + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "name": "recipient", + "type": "address" + }, + { + "indexed": false, + "name": "value", + "type": "uint256" + }, + { + "indexed": false, + "name": "transactionHash", + "type": "bytes32" + } + ], + "name": "RelayedMessage", + "type": "event" } +] - exchangeBtoA({ account, value, ether }: ExchangeParams) { - const _value = this._getValue({ value, ether }); +const wait = (time: number) => new Promise(resolve => setTimeout(resolve, time)); - const dai = this.getExchange().getAsset(this.assetB); - return dai.send({ - from: account, - value: _value, - to: toXdaiBridge, +const TIMEOUT = 180000 + +export default class XDaiBridge extends Bridge { + constructor() { + super({ + assetA: 'xdai', + assetABridge: '0x7301cfa0e1756b71869e93d4e4dca5c7d0eb0aa6', + assetB: 'dai', + assetBBridge: '0x4aa42145Aa6Ebf72e164C9bBC74fbD3788045016' }); } - async estimateAtoB(value: ValueTypes) { - return this._getValue(value); + async detectExchangeBToAFinished(account: string, value: string, sendResult: any) { + const asset = this.getExchange().getAsset(this.assetA); + const web3 = asset.getWeb3(); + const contract = new web3.eth.Contract(bridgeAAbi, this.assetABridge); + let fromBlock = await web3.eth.getBlockNumber() + + const stopTime = Date.now() + TIMEOUT + while (Date.now() <= stopTime) { + const currentBlock = await web3.eth.getBlockNumber() + + const events: EventData[] = await contract.getPastEvents('AffirmationCompleted', { + fromBlock, + toBlock: currentBlock + }) + + const confirmationEvent = events.filter(event => event.returnValues.transactionHash === sendResult.txHash) + + if (confirmationEvent.length > 0) { + return; + } + fromBlock = currentBlock + await wait(5000); + } } - async estimateBtoA(value: ValueTypes) { - return this._getValue(value); + async detectExchangeAToBFinished(account: string, value: string, sendResult: any) { + const web3 = this.getExchange().getAsset(this.assetB).getWeb3() + const contract = new web3.eth.Contract(bridgeBAbi, this.assetBBridge); + let fromBlock = await web3.eth.getBlockNumber() + + const stopTime = Date.now() + TIMEOUT + while (Date.now() <= stopTime) { + const currentBlock = await web3.eth.getBlockNumber() + const events: EventData[] = await contract.getPastEvents('RelayedMessage', { + fromBlock, + toBlock: currentBlock + }) + const confirmationEvent = events.filter(event => event.returnValues.transactionHash === sendResult.txHash) + + if (confirmationEvent.length > 0) { + return; + } + fromBlock = currentBlock + await wait(10000); + } } } diff --git a/packages/exchange/src/ui/ExchangePage.tsx b/packages/exchange/src/ui/ExchangePage.tsx index 801d4af9..159dc838 100644 --- a/packages/exchange/src/ui/ExchangePage.tsx +++ b/packages/exchange/src/ui/ExchangePage.tsx @@ -59,6 +59,7 @@ export default class ExchangePage extends Component {error} - + { + !isExchanging && + + } );