UNPKG

ptokens-perc20

Version:

repo holding the code for interacting with Ether or a generic Ethereum ERC20 token

623 lines (618 loc) 22.7 kB
import Web3 from 'web3'; import Web3PromiEvent from 'web3-core-promievent'; import { NodeSelector } from 'ptokens-node-selector'; import { constants, helpers, eth, eos, algo, abi, redeemFrom } from 'ptokens-utils'; import BigNumber from 'bignumber.js'; import Web3Utils from 'web3-utils'; var minimumAmounts = { [constants.pTokens.pETH]: { issue: 1000000000, redeem: { [constants.blockchains.Eosio]: 0.000000001, [constants.blockchains.Telos]: 0.000000001 } }, [constants.pTokens.pWETH]: { issue: 1000000000, redeem: { [constants.blockchains.Eosio]: 0.000000001, [constants.blockchains.Telos]: 0.000000001 } }, [constants.pTokens.PNT]: { issue: 1000000000, redeem: { [constants.blockchains.Eosio]: 0.000000001, [constants.blockchains.Telos]: 0.000000001, [constants.blockchains.BinanceSmartChain]: 1000000000, [constants.blockchains.Xdai]: 1000000000, [constants.blockchains.Polygon]: 1000000000, [constants.blockchains.Arbitrum]: 1000000000 } }, [constants.pTokens.pLINK]: { issue: 1000000000, redeem: { [constants.blockchains.Eosio]: 0.000000001, [constants.blockchains.Telos]: 0.000000001 } }, [constants.pTokens.pMKR]: { issue: 1000000000, redeem: { [constants.blockchains.Eosio]: 0.000000001 } }, [constants.pTokens.pYFI]: { issue: 1000000000, redeem: { [constants.blockchains.Eosio]: 0.000000001 } }, [constants.pTokens.PTERIA]: { issue: 1000000000, redeem: { [constants.blockchains.Eosio]: 0.000000001, [constants.blockchains.BinanceSmartChain]: 1000000000 } }, [constants.pTokens.pUNI]: { issue: 1000000000, redeem: { [constants.blockchains.Eosio]: 0.000000001 } }, [constants.pTokens.pBAND]: { issue: 1000000000, redeem: { [constants.blockchains.Eosio]: 0.000000001 } }, [constants.pTokens.PBAL]: { issue: 1000000000, redeem: { [constants.blockchains.Eosio]: 0.000000001 } }, [constants.pTokens.pCOMP]: { issue: 1000000000, redeem: { [constants.blockchains.Eosio]: 0.000000001 } }, [constants.pTokens.pSNX]: { issue: 1000000000, redeem: { [constants.blockchains.Eosio]: 0.000000001 } }, [constants.pTokens.pOMG]: { issue: 1000000000, redeem: { [constants.blockchains.Eosio]: 0.000000001 } }, [constants.pTokens.pDAI]: { issue: 1000000000, redeem: { [constants.blockchains.Eosio]: 0.000000001 } }, [constants.pTokens.pANT]: { issue: 1000000000, redeem: { [constants.blockchains.Eosio]: 0.000000001 } }, [constants.pTokens.pLRC]: { issue: 1000000000, redeem: { [constants.blockchains.Eosio]: 0.000000001 } }, [constants.pTokens.pUOS]: { issue: 500000, redeem: { [constants.blockchains.Eosio]: 50, [constants.blockchains.Ultra]: 50 } }, [constants.pTokens.pBAT]: { issue: 1000000000, redeem: { [constants.blockchains.Eosio]: 0.000000001 } }, [constants.pTokens.pREP]: { issue: 1000000000, redeem: { [constants.blockchains.Eosio]: 0.000000001 } }, [constants.pTokens.pZRX]: { issue: 1000000000, redeem: { [constants.blockchains.Eosio]: 0.000000001 } }, [constants.pTokens.pPNK]: { issue: 1000000000, redeem: { [constants.blockchains.Eosio]: 0.000000001 } }, [constants.pTokens.pOPIUM]: { issue: 1000000000, redeem: { [constants.blockchains.BinanceSmartChain]: 1000000000 } }, [constants.pTokens.pDEFIPlusPlus]: { issue: 1000000000, redeem: { [constants.blockchains.BinanceSmartChain]: 1000000000 } }, [constants.pTokens.pBCP]: { issue: 1000000000, redeem: { [constants.blockchains.BinanceSmartChain]: 1000000000 } }, [constants.pTokens.CGG]: { issue: 1000000000, redeem: { [constants.blockchains.BinanceSmartChain]: 1000000000 } }, [constants.pTokens.pUSDC]: { issue: 5000000, redeem: { [constants.blockchains.Telos]: 0.000000001, [constants.blockchains.Algorand]: 5000000 } }, [constants.pTokens.pUSDT]: { issue: 5000000, redeem: { [constants.blockchains.Telos]: 0.000000001, [constants.blockchains.Algorand]: 5000000 } }, [constants.pTokens.pOPEN]: { issue: 1000000000, redeem: { [constants.blockchains.BinanceSmartChain]: 1000000000 } }, [constants.pTokens.ANRX]: { issue: 1000000000, redeem: { [constants.blockchains.BinanceSmartChain]: 1000000000 } }, [constants.pTokens.GALA]: { issue: 1, redeem: { [constants.blockchains.BinanceSmartChain]: 10000000000 } }, [constants.pTokens.ZMT]: { issue: 1, redeem: { [constants.blockchains.BinanceSmartChain]: 10000000000 } }, [constants.pTokens.BIST]: { issue: 10000000000, redeem: { [constants.blockchains.BinanceSmartChain]: 10000000000 } }, [constants.pTokens.LUXO]: { issue: 10000000000, redeem: { [constants.blockchains.Luxochain]: 10000000000, [constants.blockchains.BinanceSmartChain]: 10000000000 } }, [constants.pTokens.pKEYS]: { issue: 1000, redeem: { [constants.blockchains.BinanceSmartChain]: 10000000000 } }, [constants.pTokens.OATH]: { issue: 500e18, redeem: { [constants.blockchains.Ethereum]: 500e18 } }, [constants.pTokens.pFTM]: { issue: 100e18, redeem: { [constants.blockchains.Ethereum]: 100e18 } }, [constants.pTokens.pWFTM]: { issue: 100e18, redeem: { [constants.blockchains.Ethereum]: 100e18 } }, [constants.pTokens.NUCO]: { issue: 1500e18, redeem: { [constants.blockchains.Telos]: 1500 } } }; const { blockchains, networks, pTokens } = constants; var mapDecimals = { [pTokens.pDAI]: { [blockchains.Eosio]: { [networks.EosioMainnet]: 4 } }, [pTokens.pUOS]: { [blockchains.Eosio]: { [networks.EosioMainnet]: 4 }, [blockchains.Ultra]: { [networks.UltraTestnet]: 8 } }, [pTokens.pUSDT]: { [blockchains.Telos]: { [networks.TelosMainnet]: 6 }, [blockchains.Libre]: { [networks.LibreMainnet]: 9 } }, [pTokens.pUSDC]: { [blockchains.Telos]: { [networks.TelosMainnet]: 6 } } }; class pERC20 extends NodeSelector { constructor(_configs) { const { hostBlockchain, hostNetwork, nativeBlockchain, nativeNetwork } = helpers.parseParams(_configs, _configs.nativeBlockchain || constants.blockchains.Ethereum); super({ pToken: _configs.pToken.toLowerCase() === constants.pTokens.pETH ? 'pweth' : _configs.pToken, hostBlockchain, hostNetwork, nativeBlockchain, nativeNetwork, defaultNode: _configs.defaultNode }); const { ethPrivateKey, ethProvider, bscPrivateKey, bscProvider, xdaiPrivateKey, xdaiProvider, polygonPrivateKey, polygonProvider, eosPrivateKey, eosRpc, eosSignatureProvider, telosPrivateKey, telosRpc, telosSignatureProvider, ultraPrivateKey, ultraRpc, ultraSignatureProvider, arbitrumPrivateKey, arbitrumProvider, luxochainPrivateKey, luxochainProvider, algoProvider, algoClient, ftmPrivateKey, ftmProvider } = _configs; if (ftmProvider) this.web3 = new Web3(ftmProvider); if (ftmPrivateKey) { const account = this.web3.eth.accounts.privateKeyToAccount(eth.addHexPrefix(ftmPrivateKey)); this.web3.eth.defaultAccount = account.address; this.nativePrivateKey = eth.addHexPrefix(ftmPrivateKey); } else { this.nativePrivateKey = null; } if (nativeBlockchain === constants.blockchains.Fantom && (ethProvider || ethPrivateKey)) { if (ethProvider) this.hostApi = new Web3(ethProvider); if (ethPrivateKey) { const account = this.hostApi.eth.accounts.privateKeyToAccount(eth.addHexPrefix(ethPrivateKey)); this.hostApi.eth.defaultAccount = account.address; this.hostPrivateKey = eth.addHexPrefix(ethPrivateKey); } else { this.hostPrivateKey = null; } } else if (ethProvider || ethPrivateKey) { if (ethProvider) this.web3 = new Web3(ethProvider); if (ethPrivateKey) { const account = this.web3.eth.accounts.privateKeyToAccount(eth.addHexPrefix(ethPrivateKey)); this.web3.eth.defaultAccount = account.address; this.nativePrivateKey = eth.addHexPrefix(ethPrivateKey); } else { this.nativePrivateKey = null; } } if (bscProvider) this.hostApi = new Web3(bscProvider); if (bscPrivateKey) { const account = this.hostApi.eth.accounts.privateKeyToAccount(eth.addHexPrefix(bscPrivateKey)); this.hostApi.eth.defaultAccount = account.address; this.hostPrivateKey = eth.addHexPrefix(bscPrivateKey); } else { this.hostPrivateKey = null; } if (xdaiProvider) this.hostApi = new Web3(xdaiProvider); if (xdaiPrivateKey) { const account = this.hostApi.eth.accounts.privateKeyToAccount(eth.addHexPrefix(xdaiPrivateKey)); this.hostApi.eth.defaultAccount = account.address; this.hostPrivateKey = eth.addHexPrefix(xdaiPrivateKey); } else { this.hostPrivateKey = null; } if (polygonProvider) this.hostApi = new Web3(polygonProvider); if (polygonPrivateKey) { const account = this.hostApi.eth.accounts.privateKeyToAccount(eth.addHexPrefix(polygonPrivateKey)); this.hostApi.eth.defaultAccount = account.address; this.hostPrivateKey = eth.addHexPrefix(polygonPrivateKey); } else { this.hostPrivateKey = null; } if (eosSignatureProvider) { this.hostApi = eos.getApi(null, eosRpc, eosSignatureProvider); } else if (eosPrivateKey && eosRpc) { this.hostApi = eos.getApi(eosPrivateKey, eosRpc, null); this.hostPrivateKey = eosPrivateKey; } else if (!eosSignatureProvider && !eosPrivateKey && eosRpc) { this.hostApi = eos.getApi(null, eosRpc, null); } if (telosSignatureProvider) { this.hostApi = eos.getApi(null, telosRpc, telosSignatureProvider); } else if (telosPrivateKey && telosRpc) { this.hostApi = eos.getApi(telosPrivateKey, telosRpc, null); this.hostPrivateKey = telosPrivateKey; } else if (!telosSignatureProvider && !telosPrivateKey && telosRpc) { this.hostApi = eos.getApi(null, telosRpc, null); } if (ultraSignatureProvider) { this.hostApi = eos.getApi(null, ultraRpc, ultraSignatureProvider); } else if (ultraPrivateKey && ultraRpc) { this.hostApi = eos.getApi(ultraPrivateKey, ultraRpc, null); this.hostPrivateKey = ultraPrivateKey; } else if (!ultraSignatureProvider && !ultraPrivateKey && ultraRpc) { this.hostApi = eos.getApi(null, ultraRpc, null); } if (arbitrumProvider) this.hostApi = new Web3(arbitrumProvider); if (arbitrumPrivateKey) { const account = this.hostApi.eth.accounts.privateKeyToAccount(eth.addHexPrefix(arbitrumPrivateKey)); this.hostApi.eth.defaultAccount = account.address; this.hostPrivateKey = eth.addHexPrefix(arbitrumPrivateKey); } else { this.hostPrivateKey = null; } if (luxochainProvider) this.hostApi = new Web3(luxochainProvider); if (luxochainPrivateKey) { const account = this.hostApi.eth.accounts.privateKeyToAccount(eth.addHexPrefix(luxochainPrivateKey)); this.hostApi.eth.defaultAccount = account.address; this.hostPrivateKey = eth.addHexPrefix(luxochainPrivateKey); } else { this.hostPrivateKey = null; } if (algoProvider) this.hostApi = algoProvider; if (algoClient) this.algoClient = algoClient; this._peginEth = _configs.pToken.toLowerCase() === constants.pTokens.pETH || _configs.pToken.toLowerCase() === constants.pTokens.pFTM; } issue(_amount, _hostAccount, _options = {}) { return this._issue(_amount, _hostAccount, null, _options); } issueWithMetadata(_amount, _hostAccount, _metadata, _options = {}) { return this._issue(_amount, _hostAccount, _metadata, _options); } _issue(_amount, _hostAccount, _metadata, _options = {}) { const promiEvent = Web3PromiEvent(); const start = async () => { try { const { gas, gasPrice } = _options; const { blockchains } = constants; await this._loadData(); const minimumAmount = minimumAmounts[this.pToken.toLowerCase()].issue; if (BigNumber(_amount).isLessThan(minimumAmount)) { promiEvent.reject(`Impossible to issue less than ${minimumAmount}`); return; } const isValidAddress = { [constants.blockchains.Ethereum]: _address => Web3Utils.isAddress(_address), [constants.blockchains.BinanceSmartChain]: _address => Web3Utils.isAddress(_address), [constants.blockchains.Xdai]: _address => Web3Utils.isAddress(_address), [constants.blockchains.Polygon]: _address => Web3Utils.isAddress(_address), [constants.blockchains.Arbitrum]: _address => Web3Utils.isAddress(_address), [constants.blockchains.Luxochain]: _address => Web3Utils.isAddress(_address), [constants.blockchains.Eosio]: _address => eos.isValidAccountName(_address), [constants.blockchains.Telos]: _address => eos.isValidAccountName(_address), [constants.blockchains.Ultra]: _address => eos.isValidAccountName(_address), [constants.blockchains.Algorand]: _address => algo.isValidAddress(_address, true), [constants.blockchains.Libre]: _address => eos.isValidAccountName(_address) }; if (!isValidAddress[this.hostBlockchain](_hostAccount)) { promiEvent.reject('Invalid host account'); return; } if (!this.selectedNode) await this.select(); const destinationChainId = this.version === 'v2' ? constants.chainIds[this.hostBlockchain][this.hostNetwork] : null; let ethTxHash = null; const waitForEthTransaction = () => new Promise((_resolve, _reject) => { eth[this.nativePrivateKey ? 'sendSignedMethodTx' : 'makeContractSend'](this.web3, this._peginEth ? 'pegInEth' : 'pegIn', { privateKey: this.nativePrivateKey, abi: this.version === 'v1' ? abi.pERC20Vault : abi.pERC20VaultV2, gas, gasPrice, contractAddress: eth.addHexPrefix(this.nativeVaultAddress), value: this._peginEth ? _amount : 0 }, this.version === 'v1' ? _metadata ? this._peginEth ? [_hostAccount, _metadata] : [_amount, this.nativeContractAddress, _hostAccount, _metadata] : this._peginEth ? [_hostAccount] : [_amount, this.nativeContractAddress, _hostAccount] : _metadata ? this._peginEth ? [_hostAccount, destinationChainId, _metadata] : [_amount, this.nativeContractAddress, _hostAccount, _metadata, destinationChainId] : this._peginEth ? [_hostAccount, destinationChainId] : [_amount, this.nativeContractAddress, _hostAccount, destinationChainId]).once('transactionHash', _hash => { ethTxHash = _hash; promiEvent.eventEmitter.emit('nativeTxBroadcasted', ethTxHash); }).once('receipt', _resolve).once('error', _reject); }); const ethTxReceipt = await waitForEthTransaction(); promiEvent.eventEmitter.emit('nativeTxConfirmed', ethTxReceipt); const incomingTxReport = await this.selectedNode.monitorIncomingTransaction(ethTxHash, promiEvent.eventEmitter); let hostTxReceipt; if (this.hostBlockchain === blockchains.Eosio || this.hostBlockchain === blockchains.Telos || this.hostBlockchain === blockchains.Libre) hostTxReceipt = await eos.waitForTransactionConfirmation(this.hostApi, incomingTxReport.broadcast_tx_hash); if (this.hostBlockchain === blockchains.Xdai || this.hostBlockchain === blockchains.BinanceSmartChain || this.hostBlockchain === blockchains.Polygon || this.hostBlockchain === blockchains.Arbitrum || this.hostBlockchain === blockchains.Luxochain || this.hostBlockchain === blockchains.Ethereum) hostTxReceipt = await eth.waitForTransactionConfirmation(this.hostApi, incomingTxReport.broadcast_tx_hash); if (this.hostBlockchain === blockchains.Algorand) await new Promise(resolve => setTimeout(resolve, 5000)); promiEvent.eventEmitter.emit('hostTxConfirmed', hostTxReceipt); promiEvent.resolve({ to: _hostAccount, nativeTx: ethTxHash, hostTx: incomingTxReport.broadcast_tx_hash, amount: BigNumber(_amount).toFixed() }); } catch (_err) { promiEvent.reject(_err); } }; start(); return promiEvent.eventEmitter; } redeem(_amount, _nativeAccount, _options = {}) { return this._redeem(_amount, _nativeAccount, null, _options); } redeemWithMetadata(_amount, _nativeAccount, _metadata, _options = {}) { return this._redeem(_amount, _nativeAccount, _metadata, _options); } _redeem(_amount, _nativeAccount, _metadata, _options = {}) { const promiEvent = Web3PromiEvent(); const start = async () => { try { const { blocksBehind, expireSeconds, permission, gas, gasPrice, actor, from, swapInfo } = _options; const { blockchains, pTokens } = constants; await this._loadData(); const minimumAmount = minimumAmounts[this.pToken.toLowerCase()].redeem[this.hostBlockchain]; if (BigNumber(_amount).isLessThan(minimumAmount)) { promiEvent.reject(`Impossible to redeem less than ${minimumAmount}`); return; } if (this.nativeBlockchain === blockchains.Ethereum && !Web3Utils.isAddress(_nativeAccount)) { promiEvent.reject('Invalid native account'); return; } if (!this.selectedNode) await this.select(); const { redeemFromEosio, redeemFromEvmCompatible, redeemFromAlgorand } = redeemFrom; const destinationChainId = this.version === 'v2' ? constants.chainIds[this.nativeBlockchain][this.nativeNetwork] : null; let hostTxHash; if (this.hostBlockchain === blockchains.BinanceSmartChain || this.hostBlockchain === blockchains.Xdai || this.hostBlockchain === blockchains.Polygon || this.hostBlockchain === blockchains.Arbitrum || this.hostBlockchain === blockchains.Luxochain || this.hostBlockchain === blockchains.Ethereum) { const hostTxReceipt = await redeemFromEvmCompatible(this.hostApi, { privateKey: this.hostPrivateKey, gas, gasPrice, contractAddress: this.hostContractAddress, value: 0 }, this.version === 'v1' ? _metadata ? [_amount, _metadata, _nativeAccount] : [_amount, _nativeAccount] : _metadata ? [_amount, _metadata, _nativeAccount, destinationChainId] : [_amount, _nativeAccount, destinationChainId], promiEvent, 'hostTxBroadcasted', this.version || 'v1'); promiEvent.eventEmitter.emit('hostTxConfirmed', hostTxReceipt); hostTxHash = hostTxReceipt.transactionHash; } if (this.hostBlockchain === blockchains.Eosio || this.hostBlockchain === blockchains.Telos || this.hostBlockchain === blockchains.Ultra || this.hostBlockchain === blockchains.Libre) { const hostTxReceipt = await redeemFromEosio(this.hostApi, _amount, _nativeAccount, mapDecimals[this.pToken.toLowerCase()] ? mapDecimals[this.pToken.toLowerCase()][this.hostBlockchain][this.hostNetwork] : 9, this.hostContractAddress, this.pToken === pTokens.pWETH ? 'peth' : this.pToken, { blocksBehind, expireSeconds, permission, actor, destinationChainId, version: this.version }); promiEvent.eventEmitter.emit('hostTxConfirmed', hostTxReceipt); hostTxHash = hostTxReceipt.transaction_id; } if (this.hostBlockchain === constants.blockchains.Algorand) { const hostTxReceipt = await redeemFromAlgorand({ provider: this.hostApi, client: this.algoClient, amount: _amount, to: this.hostIdentity, assetIndex: this.hostContractAddress, nativeAccount: _nativeAccount, from, destinationChainId, eventEmitter: promiEvent.eventEmitter, swapInfo }); promiEvent.eventEmitter.emit('hostTxConfirmed', hostTxReceipt); hostTxHash = hostTxReceipt.txID().toString(); } const incomingTxReport = await this.selectedNode.monitorIncomingTransaction(hostTxHash, promiEvent.eventEmitter); const nativeTxReceipt = await eth.waitForTransactionConfirmation(this.web3, incomingTxReport.broadcast_tx_hash); promiEvent.eventEmitter.emit('nativeTxConfirmed', nativeTxReceipt); promiEvent.resolve({ to: _nativeAccount, nativeTx: nativeTxReceipt.transactionHash, hostTx: hostTxHash, amount: BigNumber(_amount).toFixed() }); } catch (_err) { promiEvent.reject(_err); } }; start(); return promiEvent.eventEmitter; } async _loadData() { try { if (!this.selectedNode) await this.select(); if (!this.nativeContractAddress || !this.hostContractAddress) { const { native_smart_contract_address, host_smart_contract_address, native_vault_address, versions, host_identity } = await this.selectedNode.getInfo(); this.nativeContractAddress = eth.addHexPrefix(native_smart_contract_address); this.hostContractAddress = this.hostBlockchain === constants.blockchains.Eosio || this.hostBlockchain === constants.blockchains.Telos || this.hostBlockchain === constants.blockchains.Ultra || this.hostBlockchain === constants.blockchains.Algorand || this.hostBlockchain === constants.blockchains.Libre ? host_smart_contract_address : eth.addHexPrefix(host_smart_contract_address); this.nativeVaultAddress = native_vault_address ? eth.addHexPrefix(native_vault_address) : null; this.version = versions && versions.network ? versions.network : 'v1'; this.hostIdentity = host_identity; } } catch (_err) { throw new Error(`Error during loading data: ${_err.message}`); } } } export { pERC20 };