import { Provider } from '@liquality/provider' ; import { padHexStart } from '@liquality/crypto' ; import { SwapProvider , SwapParams , BigNumber , Transaction , Block , ethereum , EIP1559Fee } from '@liquality/types' ; import { addressToString , caseInsensitiveEqual , validateValue , validateSecret , validateSecretHash , validateSecretAndHash } from '@liquality/utils' ; import { remove0x , hexToNumber , validateAddress , validateExpiration } from '@liquality/ethereum-utils' ; import { PendingTxError , TxNotFoundError , BlockNotFoundError } from '@liquality/errors' ; export default class EthereumSwapProvider extends Provider implements Partial < SwapProvider > { createSwapScript ( swapParams ) { this . validateSwapParams ( swapParams ) ; const recipientAddress = remove0x ( addressToString ( swapParams . recipientAddress ) ) ; const refundAddress = remove0x ( addressToString ( swapParams . refundAddress ) ) ; const expirationHex = swapParams . expiration . toString ( 16 ) ; const expirationSize = 5 ; const expirationEncoded = padHexStart ( expirationHex , expirationSize ) ; const bytecode = [ '60' , 'c9' , '80' , '60' , '0b' , '60' , '00' , '39' , '60' , '00' , 'f3' , '60' , '20' , '80' , '60' , '00' , '80' , '37' , '60' , '21' , '81' , '60' , '00' , '80' , '60' , '02' , '61' , 'ffff' , 'f1' , '36' , '60' , '20' , '14' , '16' , '7f' , swapParams . secretHash , '60' , '21' , '51' , '14' , '16' , '60' , '50' , '57' , '36' , '15' , '64' , expirationEncoded , '42' , '11' , '16' , '60' , '8d' , '57' , 'fe' , '5b' , '7f' , '8c1d64e3bd87387709175b9ef4e7a1d7a8364559fc0e2ad9d77953909a0d1eb3' , '60' , '20' , '60' , '00' , 'a1' , '73' , recipientAddress , 'ff' , '5b' , '7f' , '5d26862916391bf49478b2f5103b0720a842b45ef145a268f2cd1fb2aed55178' , '60' , '00' , '80' , 'a1' , '73' , refundAddress , 'ff' ] . join ( '' ) . toLowerCase ( ) ; if ( Buffer . byteLength ( bytecode ) !== 424 ) { throw new Error ( 'Invalid swap script. Bytecode length incorrect.' ) ; } return bytecode ; } validateSwapParams ( swapParams ) { validateValue ( swapParams . value ) ; validateAddress ( swapParams . recipientAddress ) ; validateAddress ( swapParams . refundAddress ) ; validateSecretHash ( swapParams . secretHash ) ; validateExpiration ( swapParams . expiration ) ; } async initiateSwap ( swapParams , gasPrice ) { this . validateSwapParams ( swapParams ) ; const bytecode = this . createSwapScript ( swapParams ) ; return this . client . chain . sendTransaction ( { to : null , value : swapParams . value , data : bytecode , fee : gasPrice } ) ; } async fundSwap ( ) { return null ; } async claimSwap ( swapParams , initiationTxHash , secret , gasPrice ) { validateSecret ( secret ) ; validateSecretAndHash ( secret , swapParams . secretHash ) ; await this . verifyInitiateSwapTransaction ( swapParams , initiationTxHash ) ; const initiationTransactionReceipt = await this . getMethod ( 'getTransactionReceipt' ) ( initiationTxHash ) ; if ( ! initiationTransactionReceipt ) throw new PendingTxError ( `Transaction receipt is not available: ${ initiationTxHash }` ) ; await this . getMethod ( 'assertContractExists' ) ( initiationTransactionReceipt . contractAddress ) ; return this . client . chain . sendTransaction ( { to : initiationTransactionReceipt . contractAddress , value : new BigNumber ( 0 ) , data : secret , fee : gasPrice } ) ; } async refundSwap ( swapParams , initiationTxHash , gasPrice ) { await this . verifyInitiateSwapTransaction ( swapParams , initiationTxHash ) ; const initiationTransactionReceipt = await this . getMethod ( 'getTransactionReceipt' ) ( initiationTxHash ) ; if ( ! initiationTransactionReceipt ) throw new PendingTxError ( `Transaction receipt is not available: ${ initiationTxHash }` ) ; await this . getMethod ( 'assertContractExists' ) ( initiationTransactionReceipt . contractAddress ) ; return this . client . chain . sendTransaction ( { to : initiationTransactionReceipt . contractAddress , value : new BigNumber ( 0 ) , data : '' , fee : gasPrice } ) ; } doesTransactionMatchInitiation ( swapParams , transaction ) { const data = this . createSwapScript ( swapParams ) ; return ( transaction . _raw . to === null && remove0x ( transaction . _raw . input ) === data && swapParams . value . eq ( hexToNumber ( transaction . _raw . value ) ) ) ; } doesTransactionMatchClaim ( transaction , initiationTransactionReceipt ) { return ( caseInsensitiveEqual ( transaction . _raw . to , initiationTransactionReceipt . contractAddress ) && remove0x ( transaction . _raw . input ) . length === 64 ) ; } async verifyInitiateSwapTransaction ( swapParams , initiationTxHash ) { this . validateSwapParams ( swapParams ) ; const initiationTransaction = await this . getMethod ( 'getTransactionByHash' ) ( initiationTxHash ) ; if ( ! initiationTransaction ) throw new TxNotFoundError ( `Transaction not found: ${ initiationTxHash }` ) ; const initiationTransactionReceipt = await this . getMethod ( 'getTransactionReceipt' ) ( initiationTxHash ) ; if ( ! initiationTransactionReceipt ) throw new PendingTxError ( `Transaction receipt is not available: ${ initiationTxHash }` ) ; const transactionMatchesSwapParams = this . doesTransactionMatchInitiation ( swapParams , initiationTransaction ) ; return ( transactionMatchesSwapParams && initiationTransactionReceipt . contractAddress && initiationTransactionReceipt . status === '0x1' ) ; } async findSwapTransaction ( blockNumber , predicate ) { const block = await this . getMethod ( 'getBlockByNumber' ) ( blockNumber , true ) ; if ( ! block ) throw new BlockNotFoundError ( `Block not found: ${ blockNumber }` ) ; if ( block ) { return block . transactions . find ( ( tx ) => predicate ( tx , block ) ) ; } } async findInitiateSwapTransaction ( swapParams , blockNumber ) { this . validateSwapParams ( swapParams ) ; return this . findSwapTransaction ( blockNumber , ( transaction ) => this . doesTransactionMatchInitiation ( swapParams , transaction ) ) ; } async findClaimSwapTransaction ( swapParams , initiationTxHash , blockNumber ) { this . validateSwapParams ( swapParams ) ; const initiationTransactionReceipt = await this . getMethod ( 'getTransactionReceipt' ) ( initiationTxHash ) ; if ( ! initiationTransactionReceipt ) throw new PendingTxError ( `Transaction receipt is not available: ${ initiationTxHash }` ) ; const transaction = await this . findSwapTransaction ( blockNumber , ( transaction ) => this . doesTransactionMatchClaim ( transaction , initiationTransactionReceipt ) ) ; if ( ! transaction ) return ; const transactionReceipt = await this . getMethod ( 'getTransactionReceipt' ) ( transaction . hash ) ; if ( transactionReceipt && transactionReceipt . status === '0x1' ) { const secret = await this . getSwapSecret ( transaction . hash ) ; validateSecretAndHash ( secret , swapParams . secretHash ) ; transaction . secret = secret ; return transaction ; } } async findFundSwapTransaction ( ) { return null ; } async getSwapSecret ( claimTxHash ) { const claimTransaction = await this . getMethod ( 'getTransactionByHash' ) ( claimTxHash ) ; return remove0x ( claimTransaction . _raw . input ) ; } async findRefundSwapTransaction ( swapParams , initiationTxHash , blockNumber ) { this . validateSwapParams ( swapParams ) ; const initiationTransactionReceipt = await this . getMethod ( 'getTransactionReceipt' ) ( initiationTxHash ) ; if ( ! initiationTransactionReceipt ) throw new PendingTxError ( `Transaction receipt is not available: ${ initiationTxHash }` ) ; const refundSwapTransaction = await this . findSwapTransaction ( blockNumber , ( transaction , block ) => caseInsensitiveEqual ( transaction . _raw . to , initiationTransactionReceipt . contractAddress ) && transaction . _raw . input === '0x' && block . timestamp >= swapParams . expiration ) ; return refundSwapTransaction ; } }