From 911bf22c46c812624da02a4b1fb45c7865b5e13a Mon Sep 17 00:00:00 2001 From: bohdan Date: Tue, 12 Sep 2023 18:16:24 +0300 Subject: [PATCH 01/42] Added fees --- package.json | 2 +- src/plugins/contracts/api.js | 25 +++++++++++++++++++------ src/plugins/contracts/index.js | 6 ++++-- 3 files changed, 24 insertions(+), 9 deletions(-) diff --git a/package.json b/package.json index 16480f9..eab21f1 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@lumerin/wallet-core", - "version": "1.0.64", + "version": "1.0.65", "author": { "name": "Lumerin", "email": "developer@lumerin.io", diff --git a/src/plugins/contracts/api.js b/src/plugins/contracts/api.js index 8037bd2..f234fd6 100644 --- a/src/plugins/contracts/api.js +++ b/src/plugins/contracts/api.js @@ -155,11 +155,18 @@ async function getContract( return contractInfo.data } +/** + * @param {import('contracts-js').CloneFactoryContext} cloneFactory + */ +const getMarketplaceFee = (cloneFactory) => async () => { + return await cloneFactory.methods.marketplaceFee().call(); +} + /** * @param {import('web3').default} web3 * @param {import('contracts-js').CloneFactoryContext} cloneFactory */ -function createContract(web3, cloneFactory, plugins) { +function createContract(web3, cloneFactory) { if (!web3) { logger.error('Not a valid Web3 instance') return @@ -205,6 +212,8 @@ function createContract(web3, cloneFactory, plugins) { from: sellerAddress, }) + const marketplaceFee = await cloneFactory.methods.marketplaceFee().call(); + return cloneFactory.methods .setCreateNewRentalContract( price, @@ -214,14 +223,14 @@ function createContract(web3, cloneFactory, plugins) { validatorAddress, pubKey.toString('hex') ) - .send({ from: sellerAddress, gas }) + .send({ from: sellerAddress, gas, value: marketplaceFee }) } } /** * @param {import('web3').default} web3 */ -function cancelContract(web3) { +function cancelContract(web3, cloneFactory) { if (!web3) { logger.error('Not a valid Web3 instance') return @@ -245,11 +254,14 @@ function cancelContract(web3) { from: walletAddress, }) + const marketplaceFee = await cloneFactory.methods.marketplaceFee().call(); + return await Implementation(web3, contractId) .methods.setContractCloseOut(closeOutType) .send({ from: walletAddress, gas, + value: marketplaceFee }) } } @@ -335,10 +347,8 @@ function purchaseContract(web3, cloneFactory, lumerin) { throw new Error('Contract is deleted already') } - const totalPrice = (+price + price * 0.01).toString() - await lumerin.methods - .increaseAllowance(cloneFactory.options.address, totalPrice) + .increaseAllowance(cloneFactory.options.address, price) .send(sendOptions) const purchaseGas = await cloneFactory.methods @@ -346,12 +356,14 @@ function purchaseContract(web3, cloneFactory, lumerin) { .estimateGas({ from: sendOptions.from, }) + const marketplaceFee = await cloneFactory.methods.marketplaceFee().call(); const purchaseResult = await cloneFactory.methods .setPurchaseRentalContract(contractId, ciphertext.toString('hex')) .send({ ...sendOptions, gas: purchaseGas, + value: marketplaceFee }) logger.debug('Finished puchase transaction', purchaseResult) @@ -406,4 +418,5 @@ module.exports = { purchaseContract, setContractDeleteStatus, editContract, + getMarketplaceFee } diff --git a/src/plugins/contracts/index.js b/src/plugins/contracts/index.js index 9d0ae9a..f58222d 100644 --- a/src/plugins/contracts/index.js +++ b/src/plugins/contracts/index.js @@ -18,6 +18,7 @@ const { purchaseContract, setContractDeleteStatus, editContract, + getMarketplaceFee } = require('./api') const { ContractEventsListener } = require('./events-listener') @@ -91,10 +92,11 @@ function createPlugin() { return { api: { refreshContracts: refreshContracts(web3, lumerin, cloneFactory), - createContract: createContract(web3, cloneFactory, plugins), - cancelContract: cancelContract(web3), + createContract: createContract(web3, cloneFactory), + cancelContract: cancelContract(web3, cloneFactory), purchaseContract: purchaseContract(web3, cloneFactory, lumerin), editContract: editContract(web3, cloneFactory, lumerin), + getMarketplaceFee: getMarketplaceFee(cloneFactory), setContractDeleteStatus: setContractDeleteStatus( web3, cloneFactory, From bae2f514d58103d7d555fc24a5c7c1a27662e1b5 Mon Sep 17 00:00:00 2001 From: Aleksandr Kukharenko Date: Wed, 13 Sep 2023 13:48:43 +0300 Subject: [PATCH 02/42] feat: new proxy api --- package.json | 2 +- src/plugins/proxy-router/connections-manager.js | 17 ++++++----------- src/plugins/proxy-router/index.js | 2 +- 3 files changed, 8 insertions(+), 13 deletions(-) diff --git a/package.json b/package.json index eab21f1..59f61fc 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@lumerin/wallet-core", - "version": "1.0.65", + "version": "1.0.66", "author": { "name": "Lumerin", "email": "developer@lumerin.io", diff --git a/src/plugins/proxy-router/connections-manager.js b/src/plugins/proxy-router/connections-manager.js index 076b242..6225fcd 100644 --- a/src/plugins/proxy-router/connections-manager.js +++ b/src/plugins/proxy-router/connections-manager.js @@ -20,19 +20,14 @@ function createConnectionsManager(config, eventBus) { let interval - const getConnections = async (sellerUrl, buyerUrl) => { + const getConnections = async (proxyUrl) => { const getMiners = async (url) => { return (await createAxios({ baseURL: url })('/miners')).data?.Miners } - if (sellerUrl && buyerUrl) { - const sellerMiners = await getMiners(sellerUrl) - const buyerMiners = (await getMiners(buyerUrl)).map((x) => ({ - ...x, - Status: 'busy', - })) - - return [...sellerMiners, ...buyerMiners] + if (proxyUrl) { + const sellerMiners = await getMiners(proxyUrl); + return [...sellerMiners]; } return await getMiners(proxyRouterUrl) @@ -57,7 +52,7 @@ function createConnectionsManager(config, eventBus) { * * @returns {object} The event emitter. */ - function getConnectionsStream(sellerUrl, buyerUrl) { + function getConnectionsStream(proxyUrl) { const stream = new EventEmitter() let isConnected = false @@ -65,7 +60,7 @@ function createConnectionsManager(config, eventBus) { disconnect() interval = setInterval(async () => { try { - const connections = await getConnections(sellerUrl, buyerUrl) + const connections = await getConnections(proxyUrl) if (!isConnected) { isConnected = true diff --git a/src/plugins/proxy-router/index.js b/src/plugins/proxy-router/index.js index 7995915..c1a672f 100644 --- a/src/plugins/proxy-router/index.js +++ b/src/plugins/proxy-router/index.js @@ -15,7 +15,7 @@ function createPlugin() { const refreshConnectionsStream = (data) => connectionManager - .getConnectionsStream(data.sellerNodeUrl, data.buyerNodeUrl) + .getConnectionsStream(data.proxyNodeUrl) .on('data', (data) => { eventBus.emit('proxy-router-connections-changed', { connections: data.connections, From 8131ca9589c7ab17fb18761ff942efddd41280e1 Mon Sep 17 00:00:00 2001 From: Aleksandr Kukharenko Date: Thu, 14 Sep 2023 12:44:23 +0300 Subject: [PATCH 03/42] update smart-contracts --- package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 59f61fc..c50deea 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@lumerin/wallet-core", - "version": "1.0.66", + "version": "1.0.67", "author": { "name": "Lumerin", "email": "developer@lumerin.io", @@ -36,7 +36,7 @@ "axios-cookiejar-support": "1.0.1", "chalk": "^2.4.2", "cheerio": "^1.0.0-rc.12", - "contracts-js": "github:Lumerin-protocol/contracts-js#v0.0.24", + "contracts-js": "github:Lumerin-protocol/contracts-js#v0.0.26", "cross-port-killer": "^1.4.0", "debug": "4.1.1", "ecies-geth": "^1.6.2", From d42cd03cb4dd1e0699a3f9b848e4177269a0d3c8 Mon Sep 17 00:00:00 2001 From: bohdan Date: Thu, 14 Sep 2023 15:51:38 +0300 Subject: [PATCH 04/42] Fix gas fee --- src/plugins/contracts/api.js | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/src/plugins/contracts/api.js b/src/plugins/contracts/api.js index f234fd6..bd7913a 100644 --- a/src/plugins/contracts/api.js +++ b/src/plugins/contracts/api.js @@ -198,6 +198,7 @@ function createContract(web3, cloneFactory) { const account = web3.eth.accounts.privateKeyToAccount(privateKey) web3.eth.accounts.wallet.create(0).add(account) + const marketplaceFee = await cloneFactory.methods.marketplaceFee().call(); const gas = await cloneFactory.methods .setCreateNewRentalContract( @@ -210,10 +211,9 @@ function createContract(web3, cloneFactory) { ) .estimateGas({ from: sellerAddress, + value: marketplaceFee }) - const marketplaceFee = await cloneFactory.methods.marketplaceFee().call(); - return cloneFactory.methods .setCreateNewRentalContract( price, @@ -248,14 +248,15 @@ function cancelContract(web3, cloneFactory) { const account = web3.eth.accounts.privateKeyToAccount(privateKey) web3.eth.accounts.wallet.create(0).add(account) + const marketplaceFee = await cloneFactory.methods.marketplaceFee().call(); + const gas = await Implementation(web3, contractId) .methods.setContractCloseOut(closeOutType) .estimateGas({ from: walletAddress, + value: marketplaceFee }) - const marketplaceFee = await cloneFactory.methods.marketplaceFee().call(); - return await Implementation(web3, contractId) .methods.setContractCloseOut(closeOutType) .send({ @@ -350,13 +351,15 @@ function purchaseContract(web3, cloneFactory, lumerin) { await lumerin.methods .increaseAllowance(cloneFactory.options.address, price) .send(sendOptions) + + const marketplaceFee = await cloneFactory.methods.marketplaceFee().call(); const purchaseGas = await cloneFactory.methods .setPurchaseRentalContract(contractId, ciphertext.toString('hex')) .estimateGas({ from: sendOptions.from, + value: marketplaceFee }) - const marketplaceFee = await cloneFactory.methods.marketplaceFee().call(); const purchaseResult = await cloneFactory.methods .setPurchaseRentalContract(contractId, ciphertext.toString('hex')) From 4b9e3fde1438d8f28b2e64268a715485cafc9430 Mon Sep 17 00:00:00 2001 From: bohdan Date: Thu, 14 Sep 2023 15:54:00 +0300 Subject: [PATCH 05/42] Update version --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index c50deea..f3928a1 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@lumerin/wallet-core", - "version": "1.0.67", + "version": "1.0.68", "author": { "name": "Lumerin", "email": "developer@lumerin.io", From beea422f84196472faaf42c50ff3f0d5c8a1c4fb Mon Sep 17 00:00:00 2001 From: Aleksandr Kukharenko Date: Thu, 21 Sep 2023 14:23:57 +0300 Subject: [PATCH 06/42] feat: contract version --- package.json | 4 ++-- src/plugins/contracts/api.js | 13 ++++++++++--- 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index f3928a1..a36258d 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@lumerin/wallet-core", - "version": "1.0.68", + "version": "1.0.69", "author": { "name": "Lumerin", "email": "developer@lumerin.io", @@ -36,7 +36,7 @@ "axios-cookiejar-support": "1.0.1", "chalk": "^2.4.2", "cheerio": "^1.0.0-rc.12", - "contracts-js": "github:Lumerin-protocol/contracts-js#v0.0.26", + "contracts-js": "github:Lumerin-protocol/contracts-js#v0.0.27", "cross-port-killer": "^1.4.0", "debug": "4.1.1", "ecies-geth": "^1.6.2", diff --git a/src/plugins/contracts/api.js b/src/plugins/contracts/api.js index bd7913a..934cf82 100644 --- a/src/plugins/contracts/api.js +++ b/src/plugins/contracts/api.js @@ -50,6 +50,7 @@ async function _loadContractInstance( _isDeleted: isDead, // check if contract is dead _balance: balance, _hasFutureTerms: hasFutureTerms, + _version: version, } = contract let futureTerms = null @@ -60,6 +61,7 @@ async function _loadContractInstance( speed: data._speed, length: data._length, limit: data._limit, + version: data._version, } } @@ -84,6 +86,7 @@ async function _loadContractInstance( hasFutureTerms, futureTerms, history: buyerHistory, + version, }, } } catch (err) { @@ -324,7 +327,7 @@ function setContractDeleteStatus(web3, cloneFactory, onUpdate) { */ function purchaseContract(web3, cloneFactory, lumerin) { return async (params) => { - const { walletId, contractId, url, privateKey, price } = params + const { walletId, contractId, url, privateKey, price, version } = params const sendOptions = { from: walletId, gas: 1_000_000 } //getting pubkey from contract to be purchased @@ -355,14 +358,14 @@ function purchaseContract(web3, cloneFactory, lumerin) { const marketplaceFee = await cloneFactory.methods.marketplaceFee().call(); const purchaseGas = await cloneFactory.methods - .setPurchaseRentalContract(contractId, ciphertext.toString('hex')) + .setPurchaseRentalContract(contractId, ciphertext.toString('hex'), version) .estimateGas({ from: sendOptions.from, value: marketplaceFee }) const purchaseResult = await cloneFactory.methods - .setPurchaseRentalContract(contractId, ciphertext.toString('hex')) + .setPurchaseRentalContract(contractId, ciphertext.toString('hex'), version) .send({ ...sendOptions, gas: purchaseGas, @@ -396,10 +399,13 @@ function editContract(web3, cloneFactory, lumerin) { const account = web3.eth.accounts.privateKeyToAccount(privateKey) web3.eth.accounts.wallet.create(0).add(account) + const marketplaceFee = await cloneFactory.methods.marketplaceFee().call(); + const editGas = await cloneFactory.methods .setUpdateContractInformation(contractId, price, limit, speed, duration) .estimateGas({ from: sendOptions.from, + value: marketplaceFee, }) const editResult = await cloneFactory.methods @@ -407,6 +413,7 @@ function editContract(web3, cloneFactory, lumerin) { .send({ ...sendOptions, gas: editGas, + value: marketplaceFee, }) logger.debug('Finished edit contract transaction', editResult) From b194ab1719825da0c2992853ce6a4aa0a55f7a24 Mon Sep 17 00:00:00 2001 From: srt0422 Date: Fri, 22 Sep 2023 18:19:11 -0700 Subject: [PATCH 07/42] Stg bugfixes (#74) * add support for arbitrum * add missing break * increment version --- package.json | 2 +- src/plugins/explorer/api/arbiscan-factory.js | 3 +++ src/plugins/explorer/api/factory.js | 3 ++- 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index a36258d..d458cfe 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@lumerin/wallet-core", - "version": "1.0.69", + "version": "1.0.70", "author": { "name": "Lumerin", "email": "developer@lumerin.io", diff --git a/src/plugins/explorer/api/arbiscan-factory.js b/src/plugins/explorer/api/arbiscan-factory.js index 973cdbe..d84c59b 100644 --- a/src/plugins/explorer/api/arbiscan-factory.js +++ b/src/plugins/explorer/api/arbiscan-factory.js @@ -7,6 +7,9 @@ const createArbiscanApi = (chainId) => { case '421613': baseURL = 'https://api-goerli.arbiscan.io/api' break + case '42161': + baseURL = 'https://api.arbiscan.io/api' + break default: throw new Error(`Unsupported chain ${chainId}`) } diff --git a/src/plugins/explorer/api/factory.js b/src/plugins/explorer/api/factory.js index 1f2f6c7..6a1e3e1 100644 --- a/src/plugins/explorer/api/factory.js +++ b/src/plugins/explorer/api/factory.js @@ -15,8 +15,9 @@ const createExplorerApis = (chainId) => { const etherscanApi = createEtherscanApi(chainId) const blockscoutApi = createBlockscoutApi(chainId) apis.push(etherscanApi, blockscoutApi) - break; + break case '421613': + case '42161': const arbiscanApi = createArbiscanApi(chainId) apis.push(arbiscanApi) break From 3d681f19281c8fc56902b0a7c3faa52e23efc3b3 Mon Sep 17 00:00:00 2001 From: Aleksandr Kukharenko Date: Tue, 26 Sep 2023 01:02:21 +0300 Subject: [PATCH 08/42] feat: check port --- package.json | 2 +- .../proxy-router/connections-manager.js | 51 ++++++++++++++----- src/plugins/proxy-router/index.js | 3 +- 3 files changed, 40 insertions(+), 16 deletions(-) diff --git a/package.json b/package.json index d458cfe..72954cf 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@lumerin/wallet-core", - "version": "1.0.70", + "version": "1.0.71", "author": { "name": "Lumerin", "email": "developer@lumerin.io", diff --git a/src/plugins/proxy-router/connections-manager.js b/src/plugins/proxy-router/connections-manager.js index 6225fcd..572b613 100644 --- a/src/plugins/proxy-router/connections-manager.js +++ b/src/plugins/proxy-router/connections-manager.js @@ -1,9 +1,10 @@ 'use strict' const { create: createAxios } = require('axios') -const logger = require('../../logger'); +const axios = require('axios') +const logger = require('../../logger') const EventEmitter = require('events') -const killer = require('cross-port-killer'); +const killer = require('cross-port-killer') /** * Create an object to interact with the Lumerin indexer. @@ -13,7 +14,12 @@ const killer = require('cross-port-killer'); * @returns {object} The exposed indexer API. */ function createConnectionsManager(config, eventBus) { - const { debug: enableDebug, proxyRouterUrl, ipLookupUrl } = config + const { + debug: enableDebug, + proxyRouterUrl, + ipLookupUrl, + portCheckerUrl, + } = config const pollingInterval = 5000 // debug.enabled = enableDebug @@ -26,19 +32,19 @@ function createConnectionsManager(config, eventBus) { } if (proxyUrl) { - const sellerMiners = await getMiners(proxyUrl); - return [...sellerMiners]; + const sellerMiners = await getMiners(proxyUrl) + return [...sellerMiners] } return await getMiners(proxyRouterUrl) } const healthCheck = async (url) => { - return createAxios({ baseURL: url })('/healthcheck'); + return createAxios({ baseURL: url })('/healthcheck') } const kill = (port) => { - return killer.kill(port); + return killer.kill(port) } /** @@ -97,17 +103,33 @@ function createConnectionsManager(config, eventBus) { } /** - * + * * @returns {string|null} */ const getLocalIp = async () => { - const baseURL = ipLookupUrl || 'https://ifconfig.io/ip'; - const { data } = await createAxios({ baseURL })(); - const stringData = typeof data === 'string' ? data : JSON.stringify(data); + const baseURL = ipLookupUrl || 'https://ifconfig.io/ip' + const { data } = await createAxios({ baseURL })() + const stringData = typeof data === 'string' ? data : JSON.stringify(data) + + const ipRegex = + /\b(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\b/ + const [ip] = stringData.match(ipRegex) + return ip + } - const ipRegex = /\b(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\b/; - const [ip] = stringData.match(ipRegex); - return ip; + /** + * + * @returns {Promise} + */ + const isProxyPortPublic = async (payload) => { + const { ip, port } = payload; + const baseURL = portCheckerUrl || 'https://portchecker.io/api/v1/query' + const { data } = await axios.post(baseURL, { + host: ip, + ports: [port], + }) + + return !!data.check?.find((c) => c.port === port && c.status === true) } return { @@ -116,6 +138,7 @@ function createConnectionsManager(config, eventBus) { getLocalIp, healthCheck, kill, + isProxyPortPublic, } } diff --git a/src/plugins/proxy-router/index.js b/src/plugins/proxy-router/index.js index c1a672f..0fe2820 100644 --- a/src/plugins/proxy-router/index.js +++ b/src/plugins/proxy-router/index.js @@ -34,7 +34,8 @@ function createPlugin() { refreshConnectionsStream: refreshConnectionsStream, getLocalIp: connectionManager.getLocalIp, healthCheck: connectionManager.healthCheck, - kill: connectionManager.kill + kill: connectionManager.kill, + isProxyPortPublic: connectionManager.isProxyPortPublic, }, events: [ 'proxy-router-connections-changed', From 23cc390186e4d85c9af4b4e565c7572bd57d088e Mon Sep 17 00:00:00 2001 From: srt0422 Date: Thu, 28 Sep 2023 00:45:37 +0000 Subject: [PATCH 09/42] =?UTF-8?q?use=20overloaded=20web3=20instance=20inst?= =?UTF-8?q?ead=20of=20single=20provider.=20=20required=20fo=E2=80=A6=20(#7?= =?UTF-8?q?6)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * use overloaded web3 instance instead of single provider. required for cycling through multiple public nodes * increment version --- package.json | 2 +- src/plugins/contracts/index.js | 2 +- src/plugins/eth/index.js | 1 + src/plugins/eth/web3.js | 23 +++++++++++++++++------ 4 files changed, 20 insertions(+), 8 deletions(-) diff --git a/package.json b/package.json index 72954cf..39bd8e2 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@lumerin/wallet-core", - "version": "1.0.71", + "version": "1.0.72", "author": { "name": "Lumerin", "email": "developer@lumerin.io", diff --git a/src/plugins/contracts/index.js b/src/plugins/contracts/index.js index f58222d..bb6f012 100644 --- a/src/plugins/contracts/index.js +++ b/src/plugins/contracts/index.js @@ -38,7 +38,7 @@ function createPlugin() { const { lmrTokenAddress, cloneFactoryAddress } = config const { eth } = plugins - const web3 = new Web3(eth.web3Provider) + const web3 = eth.web3 const web3Subscriptionable = new Web3(plugins.eth.web3SubscriptionProvider) const lumerin = Lumerin(web3, lmrTokenAddress) diff --git a/src/plugins/eth/index.js b/src/plugins/eth/index.js index e496ffa..e5412aa 100644 --- a/src/plugins/eth/index.js +++ b/src/plugins/eth/index.js @@ -29,6 +29,7 @@ function createPlugin () { return { api: { + web3, web3Provider: web3.currentProvider, web3SubscriptionProvider: web3SubscribAble.currentProvider, }, diff --git a/src/plugins/eth/web3.js b/src/plugins/eth/web3.js index 2789540..a34cddc 100644 --- a/src/plugins/eth/web3.js +++ b/src/plugins/eth/web3.js @@ -87,8 +87,8 @@ const overrideFunctions = function (object, providers) { !key.startsWith('set') ) { object[key] = function () { - const isAsync = - originalFunctions[key][Symbol.toStringTag] === 'AsyncFunction' + const originalFunction = originalFunctions[key] + const isAsync = originalFunction[Symbol.toStringTag] === 'AsyncFunction' const args = arguments let providerIndex = lastUsedProviderIndex let result @@ -97,7 +97,7 @@ const overrideFunctions = function (object, providers) { const provider = providers[providerIndex] originalSetProvider(provider) if (isAsync) { - result = originalFunctions[key] + result = originalFunction .apply(this, args) .then((res) => { if (res !== undefined) { @@ -111,8 +111,19 @@ const overrideFunctions = function (object, providers) { }) } else { try { - result = originalFunctions[key].apply(this, args) - if (result !== undefined) { + if (new.target) { + function F(args) { + return originalFunction.apply(this, args) + } + + F.prototype = originalFunction.prototype + + return new F(args) + } else { + result = originalFunctions[key].apply(this, args) + } + + if (typeof result !== "undefined") { break } } catch (error) { @@ -121,7 +132,7 @@ const overrideFunctions = function (object, providers) { } } while (providerIndex !== lastUsedProviderIndex) lastUsedProviderIndex = providerIndex - if (result === undefined) { + if (typeof result === "undefined") { throw new Error('All providers failed to execute the function') } return result From 6d77bc7da05e9318a9079aed7426829bfd4f5911 Mon Sep 17 00:00:00 2001 From: Aleksandr Kukharenko Date: Tue, 3 Oct 2023 14:17:29 +0300 Subject: [PATCH 10/42] polling for new blocks --- package.json | 2 +- src/plugins/explorer/blocks-stream.js | 23 ++++++++++++++--------- src/plugins/explorer/index.js | 11 ++++++++--- 3 files changed, 23 insertions(+), 13 deletions(-) diff --git a/package.json b/package.json index 39bd8e2..bf949db 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@lumerin/wallet-core", - "version": "1.0.72", + "version": "1.0.73", "author": { "name": "Lumerin", "email": "developer@lumerin.io", diff --git a/src/plugins/explorer/blocks-stream.js b/src/plugins/explorer/blocks-stream.js index 996a597..dbbc8db 100644 --- a/src/plugins/explorer/blocks-stream.js +++ b/src/plugins/explorer/blocks-stream.js @@ -1,24 +1,29 @@ 'use strict'; const logger = require('../../logger'); +const EventEmitter = require('events'); -function createStream (web3) { - const subscription = web3.eth.subscribe('newBlockHeaders'); +function createStream (web3, updateInterval = 10000) { + const ee = new EventEmitter(); web3.eth.getBlock('latest') .then(function (block) { - subscription.emit('data', block); + ee.emit('data', block); }) .catch(function (err) { - subscription.emit('error', err); + ee.emit('error', err); }) - // subscription.destroy = subscription.unsubscribe; - subscription.unsubscribe(function(error, success) { - success || logger.error('Could not successfully unsubscribe from web3 block-stream'); - }); + const interval = setInterval(async () => { + try { + const block = await web3.eth.getBlock('latest'); + ee.emit('data', block); + } catch (err) { + ee.emit('error', err); + } + }, updateInterval); - return subscription; + return { interval, stream: ee }; } module.exports = createStream; diff --git a/src/plugins/explorer/index.js b/src/plugins/explorer/index.js index ed50825..cb6be8b 100644 --- a/src/plugins/explorer/index.js +++ b/src/plugins/explorer/index.js @@ -16,6 +16,7 @@ const createExplorer = require('./explorer'); function createPlugin () { let blocksStream; let syncer; + let interval; function start ({ config, eventBus, plugins }) { // debug.enabled = config.debug; @@ -39,9 +40,12 @@ function createPlugin () { eventsRegistry, explorer ); - + logger.debug('Initiating blocks stream'); - blocksStream = createStream(web3Subscribable); + const streamData = createStream(web3, config.blocksUpdateMs); + blocksStream = streamData.stream; + interval = streamData.interval; + blocksStream.on('data', function ({ hash, number, timestamp }) { logger.debug('New block', hash, number); eventBus.emit('coin-block', { hash, number, timestamp }); @@ -78,7 +82,8 @@ function createPlugin () { function stop () { // blocksStream.destroy(); - blocksStream.unsubscribe(); + blocksStream.removeAllListeners(); + clearInterval(interval); syncer.stop(); } From 16795ff6f19bab6e7e0924881f4d611de010c3f9 Mon Sep 17 00:00:00 2001 From: Aleksandr Kukharenko Date: Mon, 9 Oct 2023 12:25:28 +0300 Subject: [PATCH 11/42] new retry implementation web3 --- package.json | 2 +- src/plugins/eth/web3.js | 98 +----------------------- src/plugins/eth/web3Http.js | 59 ++++++++++++++ src/plugins/eth/web3Http.spec.js | 127 +++++++++++++++++++++++++++++++ 4 files changed, 189 insertions(+), 97 deletions(-) create mode 100644 src/plugins/eth/web3Http.js create mode 100644 src/plugins/eth/web3Http.spec.js diff --git a/package.json b/package.json index bf949db..ecd53cf 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@lumerin/wallet-core", - "version": "1.0.73", + "version": "1.0.74", "author": { "name": "Lumerin", "email": "developer@lumerin.io", diff --git a/src/plugins/eth/web3.js b/src/plugins/eth/web3.js index a34cddc..ad94dec 100644 --- a/src/plugins/eth/web3.js +++ b/src/plugins/eth/web3.js @@ -3,31 +3,13 @@ const logger = require('../../logger'); const Web3 = require('web3') -const https = require('https') +const { Web3Http } = require('./web3Http'); -let providers = []; function createWeb3(config) { // debug.enabled = config.debug - providers = config.httpApiUrls.map((url) => { - return new Web3.providers.HttpProvider(url, { - agent: new https.Agent({ - rejectUnauthorized: false, // Set to false if your HTTPS node endpoint uses a self-signed certificate - }), - }) - }) - - const web3 = new Web3(providers[0], { - agent: new https.Agent({ - rejectUnauthorized: false, // Set to false if your HTTPS node endpoint uses a self-signed certificate - }), - }) - - overrideFunctions(web3, providers) - overrideFunctions(web3.eth, providers) - web3.subscriptionProvider = subscriptionProvider - + const web3 = new Web3Http(config.httpApiUrls) return web3 } @@ -69,82 +51,6 @@ function destroyWeb3(web3) { web3.currentProvider.disconnect() } -const urls = [ - process.env.HTTP_ETH_NODE_ADDRESS, - process.env.HTTP_ETH_NODE_ADDRESS2, - process.env.HTTP_ETH_NODE_ADDRESS3, -] - -let lastUsedProviderIndex = -1 - -const overrideFunctions = function (object, providers) { - const originalSetProvider = object.setProvider - - const originalFunctions = Object.assign({}, object) - Object.keys(originalFunctions).forEach((key) => { - if ( - typeof originalFunctions[key] === 'function' && - !key.startsWith('set') - ) { - object[key] = function () { - const originalFunction = originalFunctions[key] - const isAsync = originalFunction[Symbol.toStringTag] === 'AsyncFunction' - const args = arguments - let providerIndex = lastUsedProviderIndex - let result - do { - providerIndex = (providerIndex + 1) % providers.length - const provider = providers[providerIndex] - originalSetProvider(provider) - if (isAsync) { - result = originalFunction - .apply(this, args) - .then((res) => { - if (res !== undefined) { - return res - } - throw new Error('Result is undefined') - }) - .catch((error) => { - console.error(`Error with provider ${provider.host}:`, error) - throw error - }) - } else { - try { - if (new.target) { - function F(args) { - return originalFunction.apply(this, args) - } - - F.prototype = originalFunction.prototype - - return new F(args) - } else { - result = originalFunctions[key].apply(this, args) - } - - if (typeof result !== "undefined") { - break - } - } catch (error) { - console.error(`Error with provider ${provider.host}:`, error) - } - } - } while (providerIndex !== lastUsedProviderIndex) - lastUsedProviderIndex = providerIndex - if (typeof result === "undefined") { - throw new Error('All providers failed to execute the function') - } - return result - } - } - }) - - object.setProvider = originalSetProvider -} - -let subscriptionProvider - module.exports = { createWeb3, destroyWeb3, diff --git a/src/plugins/eth/web3Http.js b/src/plugins/eth/web3Http.js new file mode 100644 index 0000000..e0f991b --- /dev/null +++ b/src/plugins/eth/web3Http.js @@ -0,0 +1,59 @@ +const Web3 = require('web3') +const https = require('https') +const logger = require('../../logger') + +class Web3Http extends Web3 { + constructor(providers, options) { + super() + + this.providers = providers.map( + (provider) => + new Web3.providers.HttpProvider(provider, { + agent: new https.Agent({ + rejectUnauthorized: false, // Set to false if your HTTPS node endpoint uses a self-signed certificate + }), + }) + ) + this.currentIndex = 0 + + // Initialize Web3 with the first provider from the list + this.setCustomProvider(this.providers[this.currentIndex]) + + // Set options if provided + if (options) { + this.setProviderOptions(options) + } + } + + setCustomProvider(provider) { + // Override the setProvider method to handle switching providers on failure + this.setProvider(provider) + + // Hook into provider's request and response handling + const originalSend = this.currentProvider.send.bind(this.currentProvider) + this.currentProvider.send = (payload, callback) => { + originalSend(payload, (error, response) => { + if (error) { + // If the request fails, switch to the next provider and try again + this.currentIndex = (this.currentIndex + 1) % this.providers.length + this.setCustomProvider(this.providers[this.currentIndex]) + logger.error( + `Switched to provider: ${this.providers[this.currentIndex].host}` + ) + this.currentProvider.send(payload, callback) // Retry the request + } else { + callback(null, response) + } + }) + } + return true + } + + setProviderOptions(options) { + this.currentProvider.host = options.host || this.currentProvider.host + this.currentProvider.timeout = + options.timeout || this.currentProvider.timeout + } +} + +module.exports = { Web3Http }; diff --git a/src/plugins/eth/web3Http.spec.js b/src/plugins/eth/web3Http.spec.js new file mode 100644 index 0000000..1ba7b2d --- /dev/null +++ b/src/plugins/eth/web3Http.spec.js @@ -0,0 +1,127 @@ +//@ts-check + +const { expect } = require('chai') +const { Web3Http } = require('./web3Http') +const { Lumerin, CloneFactory } = require('contracts-js') + +const invalidNode = 'https://arbitrum.llamarpc.com_INVALID' +const validNode = 'https://arbitrum.blockpi.network/v1/rpc/public' +const providerList = [ + invalidNode, + validNode, + 'https://rpc.ankr.com/arbitrum', + 'https://arbitrum.api.onfinality.io/public', + 'https://arb1-mainnet-public.unifra.io', + 'https://arbitrum-one.public.blastapi.io', + 'https://endpoints.omniatech.io/v1/arbitrum/one/public', + 'https://1rpc.io/arb', +] + +describe('Web3 multiple nodes integration tests', () => { + const lumerinAddress = '0x0FC0c323Cf76E188654D63D62e668caBeC7a525b' + const cloneFactoryAddress = '0x05C9F9E9041EeBCD060df2aee107C66516E2b9bA' + + it('should work with simple blockchain query', async () => { + const web3 = new Web3Http(providerList) + + const result = await web3.eth.getBlockNumber() + expect(typeof result).eq('number') + expect(web3.currentIndex).eq(1) + }) + + it('should iterate all nodes', async () => { + const web3 = new Web3Http([ + invalidNode, + invalidNode, + invalidNode, + invalidNode, + validNode, + ]) + + const result = await web3.eth.getBlockNumber() + expect(typeof result).eq('number') + expect(web3.currentIndex).eq(4) + }) + + it('should work with Contract.call()', async () => { + const web3 = new Web3Http(providerList) + const lumerin = Lumerin(web3, lumerinAddress) + + const result = await lumerin.methods + .balanceOf('0x0000000000000000000000000000000000000000') + .call() + expect(typeof result).eq('string') + expect(web3.currentIndex).eq(1) + }) + + it('should work with Contract.send()', async () => { + const web3 = new Web3Http(providerList) + const cf = CloneFactory(web3, cloneFactoryAddress) + + try { + await cf.methods + .setCreateNewRentalContract( + '0', + '0', + '0', + '0', + '0x0000000000000000000000000000000000000000', + '0' + ) + .send({ + from: '0x0000000000000000000000000000000000000000', + }) + expect(1).eq(0) + } catch (err) { + expect(err.message.includes('unknown account')).eq(true) + } + }) + + it('should work with Contract.estimateGas()', async () => { + const web3 = new Web3Http(providerList) + const cf = CloneFactory(web3, cloneFactoryAddress) + + try { + await cf.methods + .setCreateNewRentalContract( + '0', + '0', + '0', + '0', + '0x0000000000000000000000000000000000000000', + '0' + ) + .estimateGas({ + from: '0x0000000000000000000000000000000000000000', + }) + expect(1).eq(0) + } catch (err) { + expect(err.message.includes('execution reverted')).eq(true) + expect(web3.currentIndex).eq(1) + } + }) + + it('should not iterate if request if invalid/reverted', async () => { + const web3 = new Web3Http([validNode, validNode, validNode]) + const cf = CloneFactory(web3, cloneFactoryAddress) + + try { + await cf.methods + .setCreateNewRentalContract( + '0', + '0', + '0', + '0', + '0x0000000000000000000000000000000000000000', + '0' + ) + .send({ + from: '0x0000000000000000000000000000000000000000', + }) + expect(1).eq(0) + } catch (err) { + expect(err.message.includes('unknown account')).eq(true) + expect(web3.currentIndex).eq(0) + } + }) +}) From a00b29bb9a30947011081fddafb72ae16265b739 Mon Sep 17 00:00:00 2001 From: Aleksandr Kukharenko Date: Mon, 9 Oct 2023 12:33:09 +0300 Subject: [PATCH 12/42] avoid infinite loop --- src/plugins/eth/web3Http.js | 11 ++++++++++- src/plugins/eth/web3Http.spec.js | 12 ++++++++++++ 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/src/plugins/eth/web3Http.js b/src/plugins/eth/web3Http.js index e0f991b..a4117c7 100644 --- a/src/plugins/eth/web3Http.js +++ b/src/plugins/eth/web3Http.js @@ -15,6 +15,7 @@ class Web3Http extends Web3 { }) ) this.currentIndex = 0 + this.retryCount = 0 // Initialize Web3 with the first provider from the list this.setCustomProvider(this.providers[this.currentIndex]) @@ -34,14 +35,22 @@ class Web3Http extends Web3 { this.currentProvider.send = (payload, callback) => { originalSend(payload, (error, response) => { if (error) { + // Avoid infinite loop + if (this.retryCount >= this.providers.length) { + callback(error, null) + this.retryCount = 0 + return; + } // If the request fails, switch to the next provider and try again this.currentIndex = (this.currentIndex + 1) % this.providers.length this.setCustomProvider(this.providers[this.currentIndex]) logger.error( `Switched to provider: ${this.providers[this.currentIndex].host}` ) + this.retryCount += 1 this.currentProvider.send(payload, callback) // Retry the request } else { + this.retryCount = 0 callback(null, response) } }) @@ -56,4 +65,4 @@ class Web3Http extends Web3 { } } -module.exports = { Web3Http }; +module.exports = { Web3Http } diff --git a/src/plugins/eth/web3Http.spec.js b/src/plugins/eth/web3Http.spec.js index 1ba7b2d..c5f4ee8 100644 --- a/src/plugins/eth/web3Http.spec.js +++ b/src/plugins/eth/web3Http.spec.js @@ -124,4 +124,16 @@ describe('Web3 multiple nodes integration tests', () => { expect(web3.currentIndex).eq(0) } }) + + it('should not loop forever', async () => { + const web3 = new Web3Http([invalidNode, invalidNode, invalidNode]) + + try { + await web3.eth.getBlockNumber() + expect(1).eq(0) + } catch (err) { + expect(web3.retryCount).eq(0) + expect(web3.currentIndex).eq(0) + } + }) }) From 928b7e451f8c06ce0ff06080477ae439ef2e4afe Mon Sep 17 00:00:00 2001 From: Aleksandr Kukharenko Date: Mon, 9 Oct 2023 12:34:27 +0300 Subject: [PATCH 13/42] check nullable --- src/plugins/eth/web3.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/plugins/eth/web3.js b/src/plugins/eth/web3.js index ad94dec..8ce768c 100644 --- a/src/plugins/eth/web3.js +++ b/src/plugins/eth/web3.js @@ -48,7 +48,7 @@ function createWeb3Subscribable(config, eventBus) { } function destroyWeb3(web3) { - web3.currentProvider.disconnect() + web3.currentProvider?.disconnect() } module.exports = { From e40769b21d0563d4eedf7c2c4d6e65a7b24ae454 Mon Sep 17 00:00:00 2001 From: Aleksandr Kukharenko Date: Mon, 6 Nov 2023 11:37:08 +0200 Subject: [PATCH 14/42] update ecies-geth lib --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index ecd53cf..2598cb6 100644 --- a/package.json +++ b/package.json @@ -39,7 +39,7 @@ "contracts-js": "github:Lumerin-protocol/contracts-js#v0.0.27", "cross-port-killer": "^1.4.0", "debug": "4.1.1", - "ecies-geth": "^1.6.2", + "ecies-geth": "^1.7.0", "electron-log": "^4.4.8", "ethereumjs-wallet": "1.0.1", "ip": "^1.1.8", From cf02876e3ce18e52df14df3ceaac49ef92562c32 Mon Sep 17 00:00:00 2001 From: Aleksandr Kukharenko Date: Mon, 6 Nov 2023 11:37:28 +0200 Subject: [PATCH 15/42] release 1.0.75 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 2598cb6..c54780a 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@lumerin/wallet-core", - "version": "1.0.74", + "version": "1.0.75", "author": { "name": "Lumerin", "email": "developer@lumerin.io", From 2607918455422cbf79571e704b4ff2729db9798c Mon Sep 17 00:00:00 2001 From: Aleksandr Kukharenko Date: Tue, 14 Nov 2023 16:10:43 +0200 Subject: [PATCH 16/42] fix: cycling on too many requests --- package.json | 2 +- src/plugins/eth/web3Http.js | 7 ++++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/package.json b/package.json index c54780a..e9d7c38 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@lumerin/wallet-core", - "version": "1.0.75", + "version": "1.0.76", "author": { "name": "Lumerin", "email": "developer@lumerin.io", diff --git a/src/plugins/eth/web3Http.js b/src/plugins/eth/web3Http.js index a4117c7..4c02485 100644 --- a/src/plugins/eth/web3Http.js +++ b/src/plugins/eth/web3Http.js @@ -33,11 +33,11 @@ class Web3Http extends Web3 { // Hook into provider's request and response handling const originalSend = this.currentProvider.send.bind(this.currentProvider) this.currentProvider.send = (payload, callback) => { - originalSend(payload, (error, response) => { - if (error) { + originalSend(payload, async (error, response) => { + if (error || response.error) { // Avoid infinite loop if (this.retryCount >= this.providers.length) { - callback(error, null) + callback(error || response.error, null) this.retryCount = 0 return; } @@ -47,6 +47,7 @@ class Web3Http extends Web3 { logger.error( `Switched to provider: ${this.providers[this.currentIndex].host}` ) + await new Promise((resolve) => setTimeout(resolve, 500)); this.retryCount += 1 this.currentProvider.send(payload, callback) // Retry the request } else { From b59b6e8b3a2c73245ebbb0fcbbf1ebae85f96bd3 Mon Sep 17 00:00:00 2001 From: Aleksandr Kukharenko Date: Tue, 14 Nov 2023 16:34:59 +0200 Subject: [PATCH 17/42] 1.0.77 --- package.json | 2 +- src/plugins/eth/web3Http.js | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package.json b/package.json index e9d7c38..bbe8dde 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@lumerin/wallet-core", - "version": "1.0.76", + "version": "1.0.77", "author": { "name": "Lumerin", "email": "developer@lumerin.io", diff --git a/src/plugins/eth/web3Http.js b/src/plugins/eth/web3Http.js index 4c02485..031253f 100644 --- a/src/plugins/eth/web3Http.js +++ b/src/plugins/eth/web3Http.js @@ -34,10 +34,10 @@ class Web3Http extends Web3 { const originalSend = this.currentProvider.send.bind(this.currentProvider) this.currentProvider.send = (payload, callback) => { originalSend(payload, async (error, response) => { - if (error || response.error) { + if (error || response.error?.code === 429) { // Avoid infinite loop if (this.retryCount >= this.providers.length) { - callback(error || response.error, null) + callback(error, response) this.retryCount = 0 return; } From f2b10f89de17e5f5e2234b8706635b5f3cbefff8 Mon Sep 17 00:00:00 2001 From: Aleksandr Kukharenko Date: Tue, 14 Nov 2023 17:52:24 +0200 Subject: [PATCH 18/42] 1.0.78 --- src/plugins/contracts/api.js | 72 ++++++++++++++++++++++------------ src/plugins/contracts/index.js | 3 +- src/plugins/eth/web3Http.js | 2 +- 3 files changed, 49 insertions(+), 28 deletions(-) diff --git a/src/plugins/contracts/api.js b/src/plugins/contracts/api.js index 934cf82..08b4076 100644 --- a/src/plugins/contracts/api.js +++ b/src/plugins/contracts/api.js @@ -112,20 +112,32 @@ async function getContracts( lumerin, cloneFactory, addresses, - walletAddress + walletAddress, + eventBus, ) { - return Promise.all( - addresses.map((address) => - getContract( - web3, - web3Subscriptionable, - lumerin, - cloneFactory, - address, - walletAddress - ) + const chunkSize = 5 + const result = [] + for (let i = 0; i < addresses.length; i += chunkSize) { + const contracts = await Promise.all( + addresses + .slice(i, i + chunkSize) + .map((address) => + getContract( + web3, + web3Subscriptionable, + lumerin, + cloneFactory, + address, + walletAddress + ) + ) ) - ) + eventBus.emit('contract-updated', { + actives: contracts, + }) + result.push(...contracts); + } + return result; } /** @@ -162,7 +174,7 @@ async function getContract( * @param {import('contracts-js').CloneFactoryContext} cloneFactory */ const getMarketplaceFee = (cloneFactory) => async () => { - return await cloneFactory.methods.marketplaceFee().call(); + return await cloneFactory.methods.marketplaceFee().call() } /** @@ -201,7 +213,7 @@ function createContract(web3, cloneFactory) { const account = web3.eth.accounts.privateKeyToAccount(privateKey) web3.eth.accounts.wallet.create(0).add(account) - const marketplaceFee = await cloneFactory.methods.marketplaceFee().call(); + const marketplaceFee = await cloneFactory.methods.marketplaceFee().call() const gas = await cloneFactory.methods .setCreateNewRentalContract( @@ -214,7 +226,7 @@ function createContract(web3, cloneFactory) { ) .estimateGas({ from: sellerAddress, - value: marketplaceFee + value: marketplaceFee, }) return cloneFactory.methods @@ -251,13 +263,13 @@ function cancelContract(web3, cloneFactory) { const account = web3.eth.accounts.privateKeyToAccount(privateKey) web3.eth.accounts.wallet.create(0).add(account) - const marketplaceFee = await cloneFactory.methods.marketplaceFee().call(); + const marketplaceFee = await cloneFactory.methods.marketplaceFee().call() const gas = await Implementation(web3, contractId) .methods.setContractCloseOut(closeOutType) .estimateGas({ from: walletAddress, - value: marketplaceFee + value: marketplaceFee, }) return await Implementation(web3, contractId) @@ -265,7 +277,7 @@ function cancelContract(web3, cloneFactory) { .send({ from: walletAddress, gas, - value: marketplaceFee + value: marketplaceFee, }) } } @@ -354,22 +366,30 @@ function purchaseContract(web3, cloneFactory, lumerin) { await lumerin.methods .increaseAllowance(cloneFactory.options.address, price) .send(sendOptions) - - const marketplaceFee = await cloneFactory.methods.marketplaceFee().call(); + + const marketplaceFee = await cloneFactory.methods.marketplaceFee().call() const purchaseGas = await cloneFactory.methods - .setPurchaseRentalContract(contractId, ciphertext.toString('hex'), version) + .setPurchaseRentalContract( + contractId, + ciphertext.toString('hex'), + version + ) .estimateGas({ from: sendOptions.from, - value: marketplaceFee + value: marketplaceFee, }) const purchaseResult = await cloneFactory.methods - .setPurchaseRentalContract(contractId, ciphertext.toString('hex'), version) + .setPurchaseRentalContract( + contractId, + ciphertext.toString('hex'), + version + ) .send({ ...sendOptions, gas: purchaseGas, - value: marketplaceFee + value: marketplaceFee, }) logger.debug('Finished puchase transaction', purchaseResult) @@ -399,7 +419,7 @@ function editContract(web3, cloneFactory, lumerin) { const account = web3.eth.accounts.privateKeyToAccount(privateKey) web3.eth.accounts.wallet.create(0).add(account) - const marketplaceFee = await cloneFactory.methods.marketplaceFee().call(); + const marketplaceFee = await cloneFactory.methods.marketplaceFee().call() const editGas = await cloneFactory.methods .setUpdateContractInformation(contractId, price, limit, speed, duration) @@ -428,5 +448,5 @@ module.exports = { purchaseContract, setContractDeleteStatus, editContract, - getMarketplaceFee + getMarketplaceFee, } diff --git a/src/plugins/contracts/index.js b/src/plugins/contracts/index.js index bb6f012..8d633cc 100644 --- a/src/plugins/contracts/index.js +++ b/src/plugins/contracts/index.js @@ -68,7 +68,8 @@ function createPlugin() { lumerin, cloneFactory, addresses, - walletAddress + walletAddress, + eventBus, ) .then((contracts) => { eventBus.emit('contracts-scan-finished', { diff --git a/src/plugins/eth/web3Http.js b/src/plugins/eth/web3Http.js index 031253f..3d94594 100644 --- a/src/plugins/eth/web3Http.js +++ b/src/plugins/eth/web3Http.js @@ -47,7 +47,7 @@ class Web3Http extends Web3 { logger.error( `Switched to provider: ${this.providers[this.currentIndex].host}` ) - await new Promise((resolve) => setTimeout(resolve, 500)); + await new Promise((resolve) => setTimeout(resolve, 750)); this.retryCount += 1 this.currentProvider.send(payload, callback) // Retry the request } else { From 6265c853c6ca15280c91e37b340e308cbe6b92f7 Mon Sep 17 00:00:00 2001 From: Aleksandr Kukharenko Date: Tue, 14 Nov 2023 17:56:12 +0200 Subject: [PATCH 19/42] 1.0.78 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index bbe8dde..6e0e8eb 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@lumerin/wallet-core", - "version": "1.0.77", + "version": "1.0.78", "author": { "name": "Lumerin", "email": "developer@lumerin.io", From 0a3f0466d5de935811501a13eb84c4fe1286e0b7 Mon Sep 17 00:00:00 2001 From: Aleksandr Kukharenko Date: Tue, 14 Nov 2023 19:54:12 +0200 Subject: [PATCH 20/42] feat: 1.0.79 --- package.json | 2 +- src/plugins/contracts/api.js | 23 +++++++++++++++-------- 2 files changed, 16 insertions(+), 9 deletions(-) diff --git a/package.json b/package.json index 6e0e8eb..6fc3e9b 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@lumerin/wallet-core", - "version": "1.0.78", + "version": "1.0.79", "author": { "name": "Lumerin", "email": "developer@lumerin.io", diff --git a/src/plugins/contracts/api.js b/src/plugins/contracts/api.js index 08b4076..2f869b6 100644 --- a/src/plugins/contracts/api.js +++ b/src/plugins/contracts/api.js @@ -113,7 +113,7 @@ async function getContracts( cloneFactory, addresses, walletAddress, - eventBus, + eventBus ) { const chunkSize = 5 const result = [] @@ -135,9 +135,9 @@ async function getContracts( eventBus.emit('contract-updated', { actives: contracts, }) - result.push(...contracts); + result.push(...contracts) } - return result; + return result } /** @@ -254,7 +254,6 @@ function cancelContract(web3, cloneFactory) { return async function (params) { const { walletAddress, - gasLimit = 1000000, contractId, privateKey, closeOutType, @@ -295,7 +294,6 @@ function setContractDeleteStatus(web3, cloneFactory, onUpdate) { return async function (params) { const { walletAddress, - gasLimit = 3000000, contractId, privateKey, deleteContract, @@ -340,7 +338,7 @@ function setContractDeleteStatus(web3, cloneFactory, onUpdate) { function purchaseContract(web3, cloneFactory, lumerin) { return async (params) => { const { walletId, contractId, url, privateKey, price, version } = params - const sendOptions = { from: walletId, gas: 1_000_000 } + const sendOptions = { from: walletId } //getting pubkey from contract to be purchased const implementationContract = Implementation(web3, contractId) @@ -363,9 +361,18 @@ function purchaseContract(web3, cloneFactory, lumerin) { throw new Error('Contract is deleted already') } + const increaseAllowanceEstimate = await lumerin.methods + .increaseAllowance(cloneFactory.options.address, price) + .estimateGas({ + from: walletId, + }) + await lumerin.methods .increaseAllowance(cloneFactory.options.address, price) - .send(sendOptions) + .send({ + from: walletId, + gas: increaseAllowanceEstimate, + }) const marketplaceFee = await cloneFactory.methods.marketplaceFee().call() @@ -414,7 +421,7 @@ function editContract(web3, cloneFactory, lumerin) { speed, duration, } = params - const sendOptions = { from: walletId, gas: 1_000_000 } + const sendOptions = { from: walletId } const account = web3.eth.accounts.privateKeyToAccount(privateKey) web3.eth.accounts.wallet.create(0).add(account) From 8ba706cf6a007932247c66f64ea099c87140b17b Mon Sep 17 00:00:00 2001 From: Aleksandr Kukharenko Date: Tue, 14 Nov 2023 20:11:26 +0200 Subject: [PATCH 21/42] handle specific error --- package.json | 2 +- src/plugins/eth/web3Http.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 6fc3e9b..6cd8d76 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@lumerin/wallet-core", - "version": "1.0.79", + "version": "1.0.80", "author": { "name": "Lumerin", "email": "developer@lumerin.io", diff --git a/src/plugins/eth/web3Http.js b/src/plugins/eth/web3Http.js index 3d94594..a2fe1df 100644 --- a/src/plugins/eth/web3Http.js +++ b/src/plugins/eth/web3Http.js @@ -34,7 +34,7 @@ class Web3Http extends Web3 { const originalSend = this.currentProvider.send.bind(this.currentProvider) this.currentProvider.send = (payload, callback) => { originalSend(payload, async (error, response) => { - if (error || response.error?.code === 429) { + if (error || response.error?.code === 429 || response.error?.message?.includes('rate limit exceeded')) { // Avoid infinite loop if (this.retryCount >= this.providers.length) { callback(error, response) From 32dd3a95b30ea6c3bd64eb1144fcee0829aba356 Mon Sep 17 00:00:00 2001 From: Aleksandr Kukharenko Date: Thu, 16 Nov 2023 14:13:52 +0200 Subject: [PATCH 22/42] feat: handle all nodes 429 --- package.json | 2 +- src/plugins/eth/web3Http.js | 25 ++++++++++++++++++++++--- 2 files changed, 23 insertions(+), 4 deletions(-) diff --git a/package.json b/package.json index 6cd8d76..6f4f275 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@lumerin/wallet-core", - "version": "1.0.80", + "version": "1.0.81", "author": { "name": "Lumerin", "email": "developer@lumerin.io", diff --git a/src/plugins/eth/web3Http.js b/src/plugins/eth/web3Http.js index a2fe1df..b97b53a 100644 --- a/src/plugins/eth/web3Http.js +++ b/src/plugins/eth/web3Http.js @@ -2,6 +2,25 @@ const Web3 = require('web3') const https = require('https') const logger = require('../../logger') +const isRateLimitError = (response) => { + const { result, ...data } = response + const code = response.error?.code + if (code === 429 || code === -32029 || code === -32097) { + return true + } + + const message = response.error?.message?.toLowerCase() + if (!message) { + return false + } + return ( + message.includes('too many requests') || + message.includes('rate limit exceeded') || + message.includes('reached maximum qps limit') || + message.includes('rate limit reached') + ) +} + class Web3Http extends Web3 { constructor(providers, options) { super() @@ -34,12 +53,12 @@ class Web3Http extends Web3 { const originalSend = this.currentProvider.send.bind(this.currentProvider) this.currentProvider.send = (payload, callback) => { originalSend(payload, async (error, response) => { - if (error || response.error?.code === 429 || response.error?.message?.includes('rate limit exceeded')) { + if (error || isRateLimitError(response)) { // Avoid infinite loop if (this.retryCount >= this.providers.length) { callback(error, response) this.retryCount = 0 - return; + return } // If the request fails, switch to the next provider and try again this.currentIndex = (this.currentIndex + 1) % this.providers.length @@ -47,7 +66,7 @@ class Web3Http extends Web3 { logger.error( `Switched to provider: ${this.providers[this.currentIndex].host}` ) - await new Promise((resolve) => setTimeout(resolve, 750)); + await new Promise((resolve) => setTimeout(resolve, 750)) this.retryCount += 1 this.currentProvider.send(payload, callback) // Retry the request } else { From 9010bc42af4b9b853271bfb1ffd88899e7324576 Mon Sep 17 00:00:00 2001 From: Aleksandr Kukharenko Date: Fri, 17 Nov 2023 12:33:21 +0200 Subject: [PATCH 23/42] 1.0.82 --- package.json | 3 ++- src/plugins/eth/web3Http.js | 27 ++++++++++++++++++++------- 2 files changed, 22 insertions(+), 8 deletions(-) diff --git a/package.json b/package.json index 6f4f275..04f4f73 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@lumerin/wallet-core", - "version": "1.0.81", + "version": "1.0.82", "author": { "name": "Lumerin", "email": "developer@lumerin.io", @@ -34,6 +34,7 @@ "abi-decoder": "^2.4.0", "axios": "0.21.1", "axios-cookiejar-support": "1.0.1", + "bottleneck": "^2.19.5", "chalk": "^2.4.2", "cheerio": "^1.0.0-rc.12", "contracts-js": "github:Lumerin-protocol/contracts-js#v0.0.27", diff --git a/src/plugins/eth/web3Http.js b/src/plugins/eth/web3Http.js index b97b53a..895c439 100644 --- a/src/plugins/eth/web3Http.js +++ b/src/plugins/eth/web3Http.js @@ -17,8 +17,18 @@ const isRateLimitError = (response) => { message.includes('too many requests') || message.includes('rate limit exceeded') || message.includes('reached maximum qps limit') || - message.includes('rate limit reached') - ) + message.includes('rate limit reached') || + message.includes("we can't execute this request") + ); +} + +const timeouts = { + 0: 500, + 1: 750, + 2: 1000, + 3: 1500, + 4: 2000, + 5: 2000, } class Web3Http extends Web3 { @@ -55,7 +65,7 @@ class Web3Http extends Web3 { originalSend(payload, async (error, response) => { if (error || isRateLimitError(response)) { // Avoid infinite loop - if (this.retryCount >= this.providers.length) { + if (this.retryCount >= this.providers.length * 2) { callback(error, response) this.retryCount = 0 return @@ -63,12 +73,15 @@ class Web3Http extends Web3 { // If the request fails, switch to the next provider and try again this.currentIndex = (this.currentIndex + 1) % this.providers.length this.setCustomProvider(this.providers[this.currentIndex]) + logger.error(error || JSON.stringify(response.error)); + this.retryCount += 1 + const timeout = timeouts[this.retryCount] || 1000; logger.error( - `Switched to provider: ${this.providers[this.currentIndex].host}` + `Switched to provider: ${this.providers[this.currentIndex].host}, timeout: ${timeout}` ) - await new Promise((resolve) => setTimeout(resolve, 750)) - this.retryCount += 1 - this.currentProvider.send(payload, callback) // Retry the request + await new Promise((resolve) => setTimeout(resolve, timeout)) + + this.currentProvider.send(payload, callback) } else { this.retryCount = 0 callback(null, response) From 3027f842773dcc70c58bcb0c92243293a6818aa1 Mon Sep 17 00:00:00 2001 From: Oleksandr Shevchuk Date: Tue, 21 Nov 2023 16:07:51 +0100 Subject: [PATCH 24/42] fix: use explorerApiURLs instead of mapping chainID to explorer api url --- package.json | 4 +- src/plugins/explorer/api/arbiscan-api.js | 72 ------------------- src/plugins/explorer/api/arbiscan-factory.js | 20 ------ .../explorer/api/blockscout-factory.js | 26 ------- src/plugins/explorer/api/etherscan-factory.js | 26 ------- src/plugins/explorer/api/factory.js | 39 +++------- src/plugins/explorer/explorer.js | 18 +++-- src/plugins/explorer/index.js | 10 +-- 8 files changed, 31 insertions(+), 184 deletions(-) delete mode 100644 src/plugins/explorer/api/arbiscan-api.js delete mode 100644 src/plugins/explorer/api/arbiscan-factory.js delete mode 100644 src/plugins/explorer/api/blockscout-factory.js delete mode 100644 src/plugins/explorer/api/etherscan-factory.js diff --git a/package.json b/package.json index 04f4f73..8a710b1 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@lumerin/wallet-core", - "version": "1.0.82", + "version": "1.0.83", "author": { "name": "Lumerin", "email": "developer@lumerin.io", @@ -84,4 +84,4 @@ "engines": { "node": ">=12" } -} +} \ No newline at end of file diff --git a/src/plugins/explorer/api/arbiscan-api.js b/src/plugins/explorer/api/arbiscan-api.js deleted file mode 100644 index d197178..0000000 --- a/src/plugins/explorer/api/arbiscan-api.js +++ /dev/null @@ -1,72 +0,0 @@ -'use strict'; -const axios = require('axios').default; - -class ArbiscanApi { - constructor({ baseURL }) { - this.api = axios.create({baseURL}) - } - - /** - * Returns a list of ERC20 Token Transfer Events hashes by Address - * @param {string} from start block - * @param {string} to end block - * @param {string} address wallet address - * @param {string} tokenAddress address - * @param {number} [page] page number - * @param {number} [pageSize] page size - * @returns {Promise} array of transaction hashes - */ - async getTokenTransactions(from, to, address, tokenAddress, page = 1, pageSize = 10) { - const params = { - module: 'account', - action: 'tokentx', - sort: 'desc', - contractaddress: tokenAddress, - startblock: from, - endblock: to, - address, - page, - offset: pageSize - } - const { data } = await this.api.get('', { params }) - const { status, message, result } = data - if (status !== '1' && message !== 'No transactions found') { - throw new Error(result) - } - - return result - } - - /** - * Returns a list of transactions for a specific address - * @param {string} from start block - * @param {string} to end block - * @param {string} address wallet address - * @param {number} [page] page number - * @param {number} [pageSize] page size - * @returns {Promise} array of transaction hashes - */ - async getEthTransactions(from, to, address, page = 1, pageSize = 1000) { - const params = { - module: 'account', - action: 'txlist', - sort: 'desc', - startblock: from, - endblock: to, - address, - page, - offset: pageSize - } - - const { data } = await this.api.get('', { params }) - - const { status, message, result } = data - if (status !== '1' && message !== 'No transactions found') { - throw new Error(result) - } - - return result - } -} - -module.exports = { ArbiscanApi }; diff --git a/src/plugins/explorer/api/arbiscan-factory.js b/src/plugins/explorer/api/arbiscan-factory.js deleted file mode 100644 index d84c59b..0000000 --- a/src/plugins/explorer/api/arbiscan-factory.js +++ /dev/null @@ -1,20 +0,0 @@ -const { EtherscanApi } = require('./etherscan-api') - -const createArbiscanApi = (chainId) => { - let baseURL - - switch (chainId.toString()) { - case '421613': - baseURL = 'https://api-goerli.arbiscan.io/api' - break - case '42161': - baseURL = 'https://api.arbiscan.io/api' - break - default: - throw new Error(`Unsupported chain ${chainId}`) - } - - return new EtherscanApi({ baseURL }) -} - -module.exports = { createArbiscanApi } diff --git a/src/plugins/explorer/api/blockscout-factory.js b/src/plugins/explorer/api/blockscout-factory.js deleted file mode 100644 index f5b87e0..0000000 --- a/src/plugins/explorer/api/blockscout-factory.js +++ /dev/null @@ -1,26 +0,0 @@ -const { BlockscoutApi } = require('./blockscout-api') - -const createBlockscoutApi = (chainId) => { - let baseURL - - switch (chainId.toString()) { - case 'mainnet': - case '1': - baseURL = 'https://blockscout.com/eth/mainnet/api' - break - case 'goerli': - case '5': - baseURL = 'https://eth-goerli.blockscout.com/api' - break - case 'sepolia': - case '11155111': - baseURL = 'https://eth-sepolia.blockscout.com/api' - break - default: - throw new Error(`Unsupported chain ${chainId}`) - } - - return new BlockscoutApi({ baseURL }) -} - -module.exports = { createBlockscoutApi } diff --git a/src/plugins/explorer/api/etherscan-factory.js b/src/plugins/explorer/api/etherscan-factory.js deleted file mode 100644 index 2e649dd..0000000 --- a/src/plugins/explorer/api/etherscan-factory.js +++ /dev/null @@ -1,26 +0,0 @@ -const { EtherscanApi } = require('./etherscan-api') - -const createEtherscanApi = (chainId) => { - let baseURL - - switch (chainId.toString()) { - case 'mainnet': - case '1': - baseURL = 'https://api.etherscan.io/api' - break - case 'goerli': - case '5': - baseURL = 'https://api-goerli.etherscan.io/api' - break - case 'sepolia': - case '11155111': - baseURL = 'https://api-sepolia.etherscan.io/api' - break - default: - throw new Error(`Unsupported chain ${chainId}`) - } - - return new EtherscanApi({ baseURL }) -} - -module.exports = { createEtherscanApi } diff --git a/src/plugins/explorer/api/factory.js b/src/plugins/explorer/api/factory.js index 6a1e3e1..6afa373 100644 --- a/src/plugins/explorer/api/factory.js +++ b/src/plugins/explorer/api/factory.js @@ -1,31 +1,14 @@ -const { createArbiscanApi } = require('./arbiscan-factory') -const { createBlockscoutApi } = require('./blockscout-factory') -const { createEtherscanApi } = require('./etherscan-factory') - -const createExplorerApis = (chainId) => { - const apis = [] - - switch (chainId.toString()) { - case 'mainnet': - case '1': - case 'goerli': - case '5': - case 'sepolia': - case '11155111': - const etherscanApi = createEtherscanApi(chainId) - const blockscoutApi = createBlockscoutApi(chainId) - apis.push(etherscanApi, blockscoutApi) - break - case '421613': - case '42161': - const arbiscanApi = createArbiscanApi(chainId) - apis.push(arbiscanApi) - break - default: - throw new Error(`Unsupported chain ${chainId}`) - } - - return apis; +const { BlockscoutApi } = require('./blockscout-api') +const { EtherscanApi } = require('./etherscan-api') + +/** + * @param {string[]} explorerApiURLs + */ +const createExplorerApis = (explorerApiURLs) => { + return explorerApiURLs.map(baseURL => { + const isBlockscoutApi = baseURL.includes('blockscout') + return isBlockscoutApi ? new BlockscoutApi({ baseURL }) : new EtherscanApi({ baseURL }) + }) } module.exports = { createExplorerApis } diff --git a/src/plugins/explorer/explorer.js b/src/plugins/explorer/explorer.js index ae8b1fb..539e5a3 100644 --- a/src/plugins/explorer/explorer.js +++ b/src/plugins/explorer/explorer.js @@ -4,8 +4,16 @@ const EventEmitter = require('events') const pRetry = require('p-retry'); const { createExplorerApis } = require('./api/factory'); -const createExplorer = (chainId, web3, lumerin, eventBus) => { - const apis = createExplorerApis(chainId); +/** + * + * @param {string[]} explorerApiURLs + * @param {*} web3 + * @param {*} lumerin + * @param {*} eventBus + * @returns + */ +const createExplorer = (explorerApiURLs, web3, lumerin, eventBus) => { + const apis = createExplorerApis(explorerApiURLs); return new Explorer({ apis, lumerin, web3, eventBus }) } @@ -114,14 +122,14 @@ class Explorer { * @param {...any} args * @returns {Promise} */ - async invoke(methodName, ...args){ + async invoke(methodName, ...args) { return await pRetry(async () => { let lastErr - for (const api of this.apis){ + for (const api of this.apis) { try { return await api[methodName](...args) - } catch(err){ + } catch (err) { lastErr = err } } diff --git a/src/plugins/explorer/index.js b/src/plugins/explorer/index.js index cb6be8b..1ee5fc3 100644 --- a/src/plugins/explorer/index.js +++ b/src/plugins/explorer/index.js @@ -13,12 +13,12 @@ const createTransactionSyncer = require('./sync-transactions'); const tryParseEventLog = require('./parse-log'); const createExplorer = require('./explorer'); -function createPlugin () { +function createPlugin() { let blocksStream; let syncer; let interval; - function start ({ config, eventBus, plugins }) { + function start({ config, eventBus, plugins }) { // debug.enabled = config.debug; const { lmrTokenAddress } = config; @@ -30,7 +30,7 @@ function createPlugin () { const queue = createQueue(config, eventBus, web3); const lumerin = Lumerin(web3Subscribable, lmrTokenAddress); - const explorer = createExplorer(config.chainId, web3, lumerin, eventBus); + const explorer = createExplorer(config.explorerApiURLs, web3, lumerin, eventBus); syncer = createTransactionSyncer( config, @@ -40,7 +40,7 @@ function createPlugin () { eventsRegistry, explorer ); - + logger.debug('Initiating blocks stream'); const streamData = createStream(web3, config.blocksUpdateMs); blocksStream = streamData.stream; @@ -80,7 +80,7 @@ function createPlugin () { }; } - function stop () { + function stop() { // blocksStream.destroy(); blocksStream.removeAllListeners(); clearInterval(interval); From 20b933c25e2822f8d2d8bda4f43a49521c2c6093 Mon Sep 17 00:00:00 2001 From: Aleksandr Kukharenko Date: Thu, 7 Dec 2023 18:53:06 +0200 Subject: [PATCH 25/42] feat: additional error handling --- package.json | 2 +- src/plugins/eth/web3Http.js | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 8a710b1..1bbed1d 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@lumerin/wallet-core", - "version": "1.0.83", + "version": "1.0.84", "author": { "name": "Lumerin", "email": "developer@lumerin.io", diff --git a/src/plugins/eth/web3Http.js b/src/plugins/eth/web3Http.js index 895c439..88c8841 100644 --- a/src/plugins/eth/web3Http.js +++ b/src/plugins/eth/web3Http.js @@ -18,7 +18,9 @@ const isRateLimitError = (response) => { message.includes('rate limit exceeded') || message.includes('reached maximum qps limit') || message.includes('rate limit reached') || - message.includes("we can't execute this request") + message.includes("we can't execute this request") || + message.includes("max message response size exceed") || + message.includes("upgrade your plan") ); } From 12e12cf01a3124d83571aa71f64d52a8f7ba70ea Mon Sep 17 00:00:00 2001 From: alex-sandrk <113540941+alex-sandrk@users.noreply.github.com> Date: Thu, 7 Dec 2023 19:08:24 +0200 Subject: [PATCH 26/42] Update auto-tag.yml --- .github/workflows/auto-tag.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/auto-tag.yml b/.github/workflows/auto-tag.yml index a2b90ac..b132dde 100644 --- a/.github/workflows/auto-tag.yml +++ b/.github/workflows/auto-tag.yml @@ -10,8 +10,8 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - - uses: butlerlogic/action-autotag@stable + - uses: butlerlogic/action-autotag@1.1.2 env: GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}" with: - strategy: package \ No newline at end of file + strategy: package From 56d073dbdd4a07cb94a76c8817a6007aa87994d0 Mon Sep 17 00:00:00 2001 From: bohdan Date: Tue, 2 Jan 2024 17:38:00 +0200 Subject: [PATCH 27/42] Profit target change --- src/plugins/contracts/api.js | 25 ++++++++++++++++++------- 1 file changed, 18 insertions(+), 7 deletions(-) diff --git a/src/plugins/contracts/api.js b/src/plugins/contracts/api.js index 2f869b6..b9a4b8c 100644 --- a/src/plugins/contracts/api.js +++ b/src/plugins/contracts/api.js @@ -39,10 +39,14 @@ async function _loadContractInstance( const { _state: state, - _price: price, // cost to purchase the contract - _limit: limit, // max th provided - _speed: speed, // th/s of contract - _length: length, // duration of the contract in seconds + _terms: { + _price: price, // cost to purchase the contract + _limit: limit, // max th provided + _speed: speed, // th/s of contract + _length: length, // duration of the contract in seconds + _version: version, + _profitTarget: profitTarget + }, _startingBlockTimestamp: timestamp, // timestamp of the block at moment of purchase _buyer: buyer, // wallet address of the purchasing party _seller: seller, // wallet address of the selling party @@ -50,8 +54,8 @@ async function _loadContractInstance( _isDeleted: isDead, // check if contract is dead _balance: balance, _hasFutureTerms: hasFutureTerms, - _version: version, } = contract + console.log("🚀 ~ file: api.js:56 ~ terms:", contract) let futureTerms = null if (walletAddress && hasFutureTerms && seller === walletAddress) { @@ -62,6 +66,7 @@ async function _loadContractInstance( length: data._length, limit: data._limit, version: data._version, + profitTarget: data._profitTarget } } @@ -87,6 +92,7 @@ async function _loadContractInstance( futureTerms, history: buyerHistory, version, + profitTarget }, } } catch (err) { @@ -188,6 +194,7 @@ function createContract(web3, cloneFactory) { } return async function (params) { + console.log("🚀 ~ file: api.js:197 ~ params:", params) // const { gasPrice } = await plugins.wallet.getGasPrice() let { price, @@ -195,6 +202,7 @@ function createContract(web3, cloneFactory) { speed, duration, sellerAddress, + profit = 0, validatorAddress = '0x0000000000000000000000000000000000000000', privateKey, } = params @@ -221,6 +229,7 @@ function createContract(web3, cloneFactory) { limit, speed, duration, + profit, validatorAddress, pubKey.toString('hex') ) @@ -235,6 +244,7 @@ function createContract(web3, cloneFactory) { limit, speed, duration, + profit, validatorAddress, pubKey.toString('hex') ) @@ -420,6 +430,7 @@ function editContract(web3, cloneFactory, lumerin) { limit = 0, speed, duration, + profit = 0 } = params const sendOptions = { from: walletId } @@ -429,14 +440,14 @@ function editContract(web3, cloneFactory, lumerin) { const marketplaceFee = await cloneFactory.methods.marketplaceFee().call() const editGas = await cloneFactory.methods - .setUpdateContractInformation(contractId, price, limit, speed, duration) + .setUpdateContractInformation(contractId, price, limit, speed, duration, profit) .estimateGas({ from: sendOptions.from, value: marketplaceFee, }) const editResult = await cloneFactory.methods - .setUpdateContractInformation(contractId, price, limit, speed, duration) + .setUpdateContractInformation(contractId, price, limit, speed, duration, profit) .send({ ...sendOptions, gas: editGas, From fc2b42732d8510552abcce304c6cbd9fe0146760 Mon Sep 17 00:00:00 2001 From: bohdan Date: Tue, 9 Jan 2024 14:46:32 +0200 Subject: [PATCH 28/42] Added v2 --- package.json | 4 ++-- src/plugins/contracts/api.js | 10 +++++----- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/package.json b/package.json index 8a710b1..498416a 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@lumerin/wallet-core", - "version": "1.0.83", + "version": "1.0.84", "author": { "name": "Lumerin", "email": "developer@lumerin.io", @@ -37,7 +37,7 @@ "bottleneck": "^2.19.5", "chalk": "^2.4.2", "cheerio": "^1.0.0-rc.12", - "contracts-js": "github:Lumerin-protocol/contracts-js#v0.0.27", + "contracts-js": "github:Lumerin-protocol/contracts-js#v0.1.0", "cross-port-killer": "^1.4.0", "debug": "4.1.1", "ecies-geth": "^1.7.0", diff --git a/src/plugins/contracts/api.js b/src/plugins/contracts/api.js index b9a4b8c..9294c8d 100644 --- a/src/plugins/contracts/api.js +++ b/src/plugins/contracts/api.js @@ -19,7 +19,7 @@ async function _loadContractInstance( try { const implementationContract = Implementation(web3, implementationAddress) const contract = await implementationContract.methods - .getPublicVariables() + .getPublicVariablesV2() .call() const stats = await implementationContract.methods.getStats().call() @@ -224,7 +224,7 @@ function createContract(web3, cloneFactory) { const marketplaceFee = await cloneFactory.methods.marketplaceFee().call() const gas = await cloneFactory.methods - .setCreateNewRentalContract( + .setCreateNewRentalContractV2( price, limit, speed, @@ -239,7 +239,7 @@ function createContract(web3, cloneFactory) { }) return cloneFactory.methods - .setCreateNewRentalContract( + .setCreateNewRentalContractV2( price, limit, speed, @@ -440,14 +440,14 @@ function editContract(web3, cloneFactory, lumerin) { const marketplaceFee = await cloneFactory.methods.marketplaceFee().call() const editGas = await cloneFactory.methods - .setUpdateContractInformation(contractId, price, limit, speed, duration, profit) + .setUpdateContractInformationV2(contractId, price, limit, speed, duration, profit) .estimateGas({ from: sendOptions.from, value: marketplaceFee, }) const editResult = await cloneFactory.methods - .setUpdateContractInformation(contractId, price, limit, speed, duration, profit) + .setUpdateContractInformationV2(contractId, price, limit, speed, duration, profit) .send({ ...sendOptions, gas: editGas, From 21ff51e50a2144387891a0826b4a8cb9c1e42a58 Mon Sep 17 00:00:00 2001 From: Aleksandr Kukharenko Date: Fri, 12 Jan 2024 15:38:11 +0200 Subject: [PATCH 29/42] fix: do not pay for contract update --- package.json | 4 ++-- src/plugins/contracts/api.js | 9 ++------- 2 files changed, 4 insertions(+), 9 deletions(-) diff --git a/package.json b/package.json index 498416a..1d72cd8 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@lumerin/wallet-core", - "version": "1.0.84", + "version": "1.0.85", "author": { "name": "Lumerin", "email": "developer@lumerin.io", @@ -84,4 +84,4 @@ "engines": { "node": ">=12" } -} \ No newline at end of file +} diff --git a/src/plugins/contracts/api.js b/src/plugins/contracts/api.js index 9294c8d..ca5e445 100644 --- a/src/plugins/contracts/api.js +++ b/src/plugins/contracts/api.js @@ -55,7 +55,6 @@ async function _loadContractInstance( _balance: balance, _hasFutureTerms: hasFutureTerms, } = contract - console.log("🚀 ~ file: api.js:56 ~ terms:", contract) let futureTerms = null if (walletAddress && hasFutureTerms && seller === walletAddress) { @@ -437,21 +436,17 @@ function editContract(web3, cloneFactory, lumerin) { const account = web3.eth.accounts.privateKeyToAccount(privateKey) web3.eth.accounts.wallet.create(0).add(account) - const marketplaceFee = await cloneFactory.methods.marketplaceFee().call() - const editGas = await cloneFactory.methods .setUpdateContractInformationV2(contractId, price, limit, speed, duration, profit) .estimateGas({ from: sendOptions.from, - value: marketplaceFee, - }) - + }); + const editResult = await cloneFactory.methods .setUpdateContractInformationV2(contractId, price, limit, speed, duration, profit) .send({ ...sendOptions, gas: editGas, - value: marketplaceFee, }) logger.debug('Finished edit contract transaction', editResult) From 4f2686c30fdff52cae67205e80bc1cb25e60ae05 Mon Sep 17 00:00:00 2001 From: Aleksandr Kukharenko Date: Fri, 12 Jan 2024 15:39:49 +0200 Subject: [PATCH 30/42] remove comments --- src/plugins/contracts/api.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/plugins/contracts/api.js b/src/plugins/contracts/api.js index ca5e445..5f5a297 100644 --- a/src/plugins/contracts/api.js +++ b/src/plugins/contracts/api.js @@ -193,8 +193,6 @@ function createContract(web3, cloneFactory) { } return async function (params) { - console.log("🚀 ~ file: api.js:197 ~ params:", params) - // const { gasPrice } = await plugins.wallet.getGasPrice() let { price, limit = 0, From 319b61a95b105448db76dd55797dc31fe082aaa0 Mon Sep 17 00:00:00 2001 From: Aleksandr Kukharenko Date: Wed, 17 Jan 2024 17:33:38 +0200 Subject: [PATCH 31/42] allow negative profit target --- package.json | 2 +- src/plugins/contracts/api.js | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index 1d72cd8..27d7065 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@lumerin/wallet-core", - "version": "1.0.85", + "version": "1.0.86", "author": { "name": "Lumerin", "email": "developer@lumerin.io", diff --git a/src/plugins/contracts/api.js b/src/plugins/contracts/api.js index 5f5a297..d8c5b45 100644 --- a/src/plugins/contracts/api.js +++ b/src/plugins/contracts/api.js @@ -226,7 +226,7 @@ function createContract(web3, cloneFactory) { limit, speed, duration, - profit, + +profit, validatorAddress, pubKey.toString('hex') ) @@ -241,7 +241,7 @@ function createContract(web3, cloneFactory) { limit, speed, duration, - profit, + +profit, validatorAddress, pubKey.toString('hex') ) @@ -435,13 +435,13 @@ function editContract(web3, cloneFactory, lumerin) { web3.eth.accounts.wallet.create(0).add(account) const editGas = await cloneFactory.methods - .setUpdateContractInformationV2(contractId, price, limit, speed, duration, profit) + .setUpdateContractInformationV2(contractId, price, limit, speed, duration, +profit) .estimateGas({ from: sendOptions.from, }); const editResult = await cloneFactory.methods - .setUpdateContractInformationV2(contractId, price, limit, speed, duration, profit) + .setUpdateContractInformationV2(contractId, price, limit, speed, duration, +profit) .send({ ...sendOptions, gas: editGas, From f007876b14f9aaed02fdde876f183ebb8b4e3a2e Mon Sep 17 00:00:00 2001 From: Aleksandr Kukharenko Date: Thu, 25 Jan 2024 00:35:10 +0200 Subject: [PATCH 32/42] refresh contract data after purchase --- package.json | 2 +- src/plugins/contracts/index.js | 6 ++++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/package.json b/package.json index 27d7065..007dbdb 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@lumerin/wallet-core", - "version": "1.0.86", + "version": "1.0.87", "author": { "name": "Lumerin", "email": "developer@lumerin.io", diff --git a/src/plugins/contracts/index.js b/src/plugins/contracts/index.js index 8d633cc..761b64e 100644 --- a/src/plugins/contracts/index.js +++ b/src/plugins/contracts/index.js @@ -90,12 +90,14 @@ function createPlugin() { const onUpdate = refreshContracts(web3, lumerin, cloneFactory) contractEventsListener.setOnUpdate(onUpdate) + const refreshContractsFn = refreshContracts(web3, lumerin, cloneFactory) + const purchaseContractFn = purchaseContract(web3, cloneFactory, lumerin) return { api: { - refreshContracts: refreshContracts(web3, lumerin, cloneFactory), + refreshContracts: refreshContractsFn, createContract: createContract(web3, cloneFactory), cancelContract: cancelContract(web3, cloneFactory), - purchaseContract: purchaseContract(web3, cloneFactory, lumerin), + purchaseContract: (params) => purchaseContractFn(params).then(() => refreshContractsFn(params.contractId, params.walletId)), editContract: editContract(web3, cloneFactory, lumerin), getMarketplaceFee: getMarketplaceFee(cloneFactory), setContractDeleteStatus: setContractDeleteStatus( From b200545f8b1a2058baa820b121a556add2508992 Mon Sep 17 00:00:00 2001 From: Aleksandr Kukharenko Date: Fri, 2 Feb 2024 14:32:01 +0200 Subject: [PATCH 33/42] fix: slow ui updates --- package.json | 2 +- src/plugins/contracts/events-listener.js | 6 ++++-- src/plugins/contracts/index.js | 5 +++-- 3 files changed, 8 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index 007dbdb..a12fd9c 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@lumerin/wallet-core", - "version": "1.0.87", + "version": "1.0.88", "author": { "name": "Lumerin", "email": "developer@lumerin.io", diff --git a/src/plugins/contracts/events-listener.js b/src/plugins/contracts/events-listener.js index 3929472..52b2e45 100644 --- a/src/plugins/contracts/events-listener.js +++ b/src/plugins/contracts/events-listener.js @@ -33,9 +33,10 @@ class ContractEventsListener { .on('connected', () => { logger.debug(`Start listen contract (${id}) events`) }) - .on('data', () => { + .on('data', async () => { logger.debug(`Contract (${id}) updated`) if (this.onUpdate){ + await new Promise((resolve) => setTimeout(resolve, 1000)) this.onUpdate(id, this.walletAddress || walletAddress) } }) @@ -49,9 +50,10 @@ class ContractEventsListener { .on('connected', () => { logger.debug('Start listen clone factory events') }) - .on('data', (event) => { + .on('data', async (event) => { const contractId = event.returnValues._address logger.debug('New contract created', contractId) + await new Promise((resolve) => setTimeout(resolve, 1000)) this.onUpdate(contractId, this.walletAddress) }) } diff --git a/src/plugins/contracts/index.js b/src/plugins/contracts/index.js index 761b64e..4e1c4f1 100644 --- a/src/plugins/contracts/index.js +++ b/src/plugins/contracts/index.js @@ -92,12 +92,13 @@ function createPlugin() { const refreshContractsFn = refreshContracts(web3, lumerin, cloneFactory) const purchaseContractFn = purchaseContract(web3, cloneFactory, lumerin) + const cancelContractFn = cancelContract(web3, cloneFactory) return { api: { refreshContracts: refreshContractsFn, createContract: createContract(web3, cloneFactory), - cancelContract: cancelContract(web3, cloneFactory), - purchaseContract: (params) => purchaseContractFn(params).then(() => refreshContractsFn(params.contractId, params.walletId)), + cancelContract: cancelContractFn, + purchaseContract: purchaseContractFn, editContract: editContract(web3, cloneFactory, lumerin), getMarketplaceFee: getMarketplaceFee(cloneFactory), setContractDeleteStatus: setContractDeleteStatus( From 418bfb6b730f4657d64a872c643e36ab49491a65 Mon Sep 17 00:00:00 2001 From: Aleksandr Kukharenko Date: Thu, 8 Feb 2024 15:23:55 +0200 Subject: [PATCH 34/42] fix: additional error handling, more logs, more retries --- package.json | 2 +- src/plugins/eth/web3Http.js | 28 +++++++++++++++++++--------- 2 files changed, 20 insertions(+), 10 deletions(-) diff --git a/package.json b/package.json index a12fd9c..9308f77 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@lumerin/wallet-core", - "version": "1.0.88", + "version": "1.0.89", "author": { "name": "Lumerin", "email": "developer@lumerin.io", diff --git a/src/plugins/eth/web3Http.js b/src/plugins/eth/web3Http.js index 88c8841..8a2731d 100644 --- a/src/plugins/eth/web3Http.js +++ b/src/plugins/eth/web3Http.js @@ -2,13 +2,17 @@ const Web3 = require('web3') const https = require('https') const logger = require('../../logger') -const isRateLimitError = (response) => { +const isRateLimitError = (response, payload) => { const { result, ...data } = response const code = response.error?.code if (code === 429 || code === -32029 || code === -32097) { return true } + if (payload?.method === 'eth_call' && response.error?.message?.includes('execution reverted') || response.error?.code === -32000) { + return true + } + const message = response.error?.message?.toLowerCase() if (!message) { return false @@ -24,13 +28,19 @@ const isRateLimitError = (response) => { ); } +const MAX_RETRIES = 10; const timeouts = { - 0: 500, - 1: 750, - 2: 1000, - 3: 1500, - 4: 2000, + 0: 1000, + 1: 1200, + 2: 1400, + 3: 1600, + 4: 1800, 5: 2000, + 6: 2200, + 7: 2400, + 8: 2600, + 9: 2800, + 10: 3000, } class Web3Http extends Web3 { @@ -65,9 +75,9 @@ class Web3Http extends Web3 { const originalSend = this.currentProvider.send.bind(this.currentProvider) this.currentProvider.send = (payload, callback) => { originalSend(payload, async (error, response) => { - if (error || isRateLimitError(response)) { + if (error || isRateLimitError(response, payload)) { // Avoid infinite loop - if (this.retryCount >= this.providers.length * 2) { + if (this.retryCount >= MAX_RETRIES) { callback(error, response) this.retryCount = 0 return @@ -79,7 +89,7 @@ class Web3Http extends Web3 { this.retryCount += 1 const timeout = timeouts[this.retryCount] || 1000; logger.error( - `Switched to provider: ${this.providers[this.currentIndex].host}, timeout: ${timeout}` + `Switched to provider: ${this.providers[this.currentIndex].host}, timeout: ${timeout}, retry count: ${this.retryCount}, request payload: ${JSON.stringify(payload)}`, ) await new Promise((resolve) => setTimeout(resolve, timeout)) From ba596f87ca3e5c8cbaa07c48607281f2bf79c36c Mon Sep 17 00:00:00 2001 From: Aleksandr Kukharenko Date: Thu, 8 Feb 2024 15:28:02 +0200 Subject: [PATCH 35/42] group if condition --- src/plugins/eth/web3Http.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/plugins/eth/web3Http.js b/src/plugins/eth/web3Http.js index 8a2731d..820f3d4 100644 --- a/src/plugins/eth/web3Http.js +++ b/src/plugins/eth/web3Http.js @@ -9,7 +9,7 @@ const isRateLimitError = (response, payload) => { return true } - if (payload?.method === 'eth_call' && response.error?.message?.includes('execution reverted') || response.error?.code === -32000) { + if (payload?.method === 'eth_call' && (response.error?.message?.includes('execution reverted') || response.error?.code === -32000)) { return true } From 7fccd3d33942afbd209fa7e0a0c075c64aee92a2 Mon Sep 17 00:00:00 2001 From: Aleksandr Kukharenko Date: Mon, 4 Mar 2024 20:43:01 +0200 Subject: [PATCH 36/42] fix: handle additional public node error --- package.json | 2 +- src/plugins/contracts/api.js | 1 + src/plugins/eth/web3Http.js | 3 ++- 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 9308f77..2068d3d 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@lumerin/wallet-core", - "version": "1.0.89", + "version": "1.0.90", "author": { "name": "Lumerin", "email": "developer@lumerin.io", diff --git a/src/plugins/contracts/api.js b/src/plugins/contracts/api.js index d8c5b45..77f8dd9 100644 --- a/src/plugins/contracts/api.js +++ b/src/plugins/contracts/api.js @@ -95,6 +95,7 @@ async function _loadContractInstance( }, } } catch (err) { + logger.error(err) logger.error( 'Error when trying to load Contracts by address in the Implementation contract: ', err diff --git a/src/plugins/eth/web3Http.js b/src/plugins/eth/web3Http.js index 820f3d4..4860d19 100644 --- a/src/plugins/eth/web3Http.js +++ b/src/plugins/eth/web3Http.js @@ -24,7 +24,8 @@ const isRateLimitError = (response, payload) => { message.includes('rate limit reached') || message.includes("we can't execute this request") || message.includes("max message response size exceed") || - message.includes("upgrade your plan") + message.includes("upgrade your plan") || + message.includes("Failed to validate quota usage") ); } From ecc41bac8d0471ad623d6236e5a17de218487503 Mon Sep 17 00:00:00 2001 From: Aleksandr Kukharenko Date: Tue, 26 Mar 2024 14:33:50 +0200 Subject: [PATCH 37/42] feat: auto-close + indexer --- package.json | 2 +- src/plugins/contracts/api.js | 78 ---------------- src/plugins/contracts/events-listener.js | 98 -------------------- src/plugins/contracts/index.js | 112 ++++++++++------------- src/plugins/contracts/indexer.js | 70 ++++++++++++++ 5 files changed, 120 insertions(+), 240 deletions(-) delete mode 100644 src/plugins/contracts/events-listener.js create mode 100644 src/plugins/contracts/indexer.js diff --git a/package.json b/package.json index 2068d3d..a5b17af 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@lumerin/wallet-core", - "version": "1.0.90", + "version": "1.1.0", "author": { "name": "Lumerin", "email": "developer@lumerin.io", diff --git a/src/plugins/contracts/api.js b/src/plugins/contracts/api.js index 77f8dd9..0c4ce70 100644 --- a/src/plugins/contracts/api.js +++ b/src/plugins/contracts/api.js @@ -3,7 +3,6 @@ const logger = require('../../logger') const { encrypt } = require('ecies-geth') const { Implementation } = require('contracts-js') const { remove0xPrefix, add65BytesPrefix } = require('./helpers') -const { ContractEventsListener } = require('./events-listener') const ethereumWallet = require('ethereumjs-wallet').default /** @@ -104,78 +103,6 @@ async function _loadContractInstance( } } -/** - * @param {import('web3').default} web3 - * @param {import('web3').default} web3Subscriptionable - * @param {import('contracts-js').LumerinContext} lumerin - * @param {import('contracts-js').CloneFactoryContext} cloneFactory - * @param {string[]} addresses - * @param {string} walletAddress - */ -async function getContracts( - web3, - web3Subscriptionable, - lumerin, - cloneFactory, - addresses, - walletAddress, - eventBus -) { - const chunkSize = 5 - const result = [] - for (let i = 0; i < addresses.length; i += chunkSize) { - const contracts = await Promise.all( - addresses - .slice(i, i + chunkSize) - .map((address) => - getContract( - web3, - web3Subscriptionable, - lumerin, - cloneFactory, - address, - walletAddress - ) - ) - ) - eventBus.emit('contract-updated', { - actives: contracts, - }) - result.push(...contracts) - } - return result -} - -/** - * @param {import('web3').default} web3 - * @param {import('web3').default} web3Subscriptionable - * @param {import('contracts-js').LumerinContext} lumerin - * @param {string} contractId - * @param {string} walletAddress - */ -async function getContract( - web3, - web3Subscriptionable, - lumerin, - cloneFactory, - contractId, - walletAddress -) { - const contractEventsListener = ContractEventsListener.getInstance() - const contractInfo = await _loadContractInstance( - web3, - contractId, - walletAddress - ) - - contractEventsListener.addContract( - contractInfo.data.id, - Implementation(web3Subscriptionable, contractId), - walletAddress - ) - return contractInfo.data -} - /** * @param {import('contracts-js').CloneFactoryContext} cloneFactory */ @@ -329,9 +256,6 @@ function setContractDeleteStatus(web3, cloneFactory, onUpdate) { from: walletAddress, gas, }) - onUpdate(contractId, walletAddress).catch((err) => - logger.error(`Failed to refresh after setContractDeadStatus: ${err}`) - ) return result } } @@ -453,8 +377,6 @@ function editContract(web3, cloneFactory, lumerin) { } module.exports = { - getContracts, - getContract, createContract, cancelContract, purchaseContract, diff --git a/src/plugins/contracts/events-listener.js b/src/plugins/contracts/events-listener.js deleted file mode 100644 index 52b2e45..0000000 --- a/src/plugins/contracts/events-listener.js +++ /dev/null @@ -1,98 +0,0 @@ -//@ts-check -// const debug = require('debug')('lmr-wallet:core:contracts:event-listener') -const logger = require('../../logger'); - -class ContractEventsListener { - /** - * @param {import('contracts-js').CloneFactoryContext} cloneFactory - */ - constructor(cloneFactory) { - this.cloneFactory = cloneFactory - this.cloneFactoryListener = null - this.contracts = {} - this.walletAddress = null; - } - - /** - * @param {(contractId?: string, walletAddress?: string) => void} onUpdate - */ - setOnUpdate(onUpdate) { - this.onUpdate = onUpdate - } - - /** - * - * @param {string} id - * @param {import('contracts-js').ImplementationContext} instance - * @param {string} walletAddress - */ - addContract(id, instance, walletAddress) { - if (!this.contracts[id]) { - this.contracts[id] = instance.events.allEvents() - this.contracts[id] - .on('connected', () => { - logger.debug(`Start listen contract (${id}) events`) - }) - .on('data', async () => { - logger.debug(`Contract (${id}) updated`) - if (this.onUpdate){ - await new Promise((resolve) => setTimeout(resolve, 1000)) - this.onUpdate(id, this.walletAddress || walletAddress) - } - }) - } - } - - listenCloneFactory() { - if (!this.cloneFactoryListener) { - this.cloneFactoryListener = this.cloneFactory.events.contractCreated() - this.cloneFactoryListener - .on('connected', () => { - logger.debug('Start listen clone factory events') - }) - .on('data', async (event) => { - const contractId = event.returnValues._address - logger.debug('New contract created', contractId) - await new Promise((resolve) => setTimeout(resolve, 1000)) - this.onUpdate(contractId, this.walletAddress) - }) - } - } - - /** - * @static - * @param {import('contracts-js').CloneFactoryContext} cloneFactory - * @param {boolean} [debugEnabled=false] - * @returns {ContractEventsListener} - */ - static create(cloneFactory, debugEnabled = false) { - if (ContractEventsListener.instance) { - return ContractEventsListener.instance - } - - const instance = new ContractEventsListener(cloneFactory) - ContractEventsListener.instance = instance - instance.listenCloneFactory() - return instance - } - - /** - * @returns {ContractEventsListener} - */ - static getInstance() { - if (!ContractEventsListener.instance) { - throw new Error("ContractEventsListener instance not created") - } - return ContractEventsListener.instance - } - - /** - * @static - * @param {(contractId?: string) => void} onUpdate - */ - static setOnUpdate(onUpdate) { - ContractEventsListener.getInstance().onUpdate = onUpdate - } -} - -module.exports = { ContractEventsListener } diff --git a/src/plugins/contracts/index.js b/src/plugins/contracts/index.js index 4e1c4f1..c097a7e 100644 --- a/src/plugins/contracts/index.js +++ b/src/plugins/contracts/index.js @@ -1,26 +1,18 @@ //@ts-check 'use strict' -// const debug = require('debug')('lmr-wallet:core:contracts') -const logger = require('../../logger'); +const logger = require('../../logger') const { Lumerin, CloneFactory } = require('contracts-js') -/** - * @type {typeof import('web3').default} - */ -//@ts-ignore -const Web3 = require('web3') - const { - getContracts, createContract, cancelContract, purchaseContract, setContractDeleteStatus, editContract, - getMarketplaceFee + getMarketplaceFee, } = require('./api') -const { ContractEventsListener } = require('./events-listener') +const { Indexer } = require('./indexer') /** * Create a plugin instance. @@ -35,76 +27,70 @@ function createPlugin() { * @returns {{ api: {[key: string]:any}, events: string[], name: string }} The instance details. */ function start({ config, eventBus, plugins }) { - const { lmrTokenAddress, cloneFactoryAddress } = config + const { + lmrTokenAddress, + cloneFactoryAddress, + indexerUrl, + pollingInterval, + } = config const { eth } = plugins const web3 = eth.web3 - const web3Subscriptionable = new Web3(plugins.eth.web3SubscriptionProvider) const lumerin = Lumerin(web3, lmrTokenAddress) const cloneFactory = CloneFactory(web3, cloneFactoryAddress) - const cloneFactorySubscriptionable = CloneFactory( - web3Subscriptionable, - cloneFactoryAddress - ) - const refreshContracts = - (web3, lumerin, cloneFactory) => async (contractId, walletAddress) => { - eventBus.emit('contracts-scan-started', {}) - ContractEventsListener.getInstance().walletAddress = walletAddress; - const addresses = contractId - ? [contractId] - : await cloneFactory.methods - .getContractList() - .call() - .catch((error) => { - logger.error('cannot get list of contract addresses:', error) - throw error - }) + const indexer = new Indexer(indexerUrl) - return getContracts( - web3, - web3Subscriptionable, - lumerin, - cloneFactory, - addresses, - walletAddress, - eventBus, - ) - .then((contracts) => { - eventBus.emit('contracts-scan-finished', { - actives: contracts, - }) - }) - .catch(function (error) { - logger.error('Could not sync contracts/events', error) - throw error + const refreshContracts = async (contractId, walletAddress) => { + if (walletAddress) { + Indexer.walletAddr = walletAddress + } + eventBus.emit('contracts-scan-started', {}) + + try { + const contracts = contractId + ? await indexer.getContract(contractId) + : await indexer.getContracts() + + eventBus.emit('contracts-scan-finished', { + actives: contracts, }) + } catch (error) { + logger.error( + `Could not sync contracts/events, params: ${contractId}, error:`, + error + ) + throw error + } } - const contractEventsListener = ContractEventsListener.create( - cloneFactorySubscriptionable, - config.debug - ) + setInterval(() => { + refreshContracts() + }, pollingInterval) - const onUpdate = refreshContracts(web3, lumerin, cloneFactory) - contractEventsListener.setOnUpdate(onUpdate) + const wrapAction = (fn) => async (params) => { + const contractId = params?.contractId + const result = await fn(params) + await new Promise((resolve) => setTimeout(resolve, 1000)) + await refreshContracts(contractId).catch((error) => { + logger.error('Error refreshing contracts', error) + }) + return result + } - const refreshContractsFn = refreshContracts(web3, lumerin, cloneFactory) const purchaseContractFn = purchaseContract(web3, cloneFactory, lumerin) const cancelContractFn = cancelContract(web3, cloneFactory) return { api: { - refreshContracts: refreshContractsFn, - createContract: createContract(web3, cloneFactory), - cancelContract: cancelContractFn, - purchaseContract: purchaseContractFn, - editContract: editContract(web3, cloneFactory, lumerin), + refreshContracts, + createContract: wrapAction(createContract(web3, cloneFactory)), + cancelContract: wrapAction(cancelContractFn), + purchaseContract: wrapAction(purchaseContractFn), + editContract: wrapAction(editContract(web3, cloneFactory, lumerin)), getMarketplaceFee: getMarketplaceFee(cloneFactory), - setContractDeleteStatus: setContractDeleteStatus( - web3, - cloneFactory, - onUpdate, + setContractDeleteStatus: wrapAction( + setContractDeleteStatus(web3, cloneFactory) ), }, events: [ diff --git a/src/plugins/contracts/indexer.js b/src/plugins/contracts/indexer.js new file mode 100644 index 0000000..26f0430 --- /dev/null +++ b/src/plugins/contracts/indexer.js @@ -0,0 +1,70 @@ +const axios = require('axios').default + +class Indexer { + /** + * @type {string} + */ + static walletAddr = null + + /** + * + * @param {string} url + */ + constructor(url) { + this.url = url + + this.headers = { + 'Content-Type': 'application/json', + 'User-Agent': `Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36`, + } + } + + /** + * + * @returns {Promise} + */ + getContracts = async () => { + const params = Indexer.walletAddr ? { walletAddr: Indexer.walletAddr } : {} + + const response = await axios.get(`${this.url}/api/contracts`, { + params, + headers: this.headers, + }) + return this.mapIndexerContracts(response.data) + } + + /** + * + * @param {string} id + * @returns {Promise} + */ + getContract = async (id) => { + const params = Indexer.walletAddr ? { walletAddr: Indexer.walletAddr } : {} + + const response = await axios.get(`${this.url}/api/contracts/${id}`, { + params, + headers: this.headers, + }) + return this.mapIndexerContracts([response.data]) + } + + /** + * @param {object[]} contracts + */ + mapIndexerContracts(contracts) { + return contracts.map((c) => { + return { + ...c, + isDead: c.isDeleted, + encryptedPoolData: c.encrValidatorUrl, + timestamp: c.startingBlockTimestamp, + history: c.history.map((h) => ({ + ...h, + id: c.id, + })), + } + }) + } +} + +module.exports = { Indexer } From 0207e1c9c445df1218523de86952338334a3ddac Mon Sep 17 00:00:00 2001 From: Aleksandr Kukharenko Date: Tue, 16 Apr 2024 17:01:58 +0300 Subject: [PATCH 38/42] feat: dynamic block reward --- package.json | 2 +- src/plugins/rates/index.js | 15 ++++++++++++--- src/plugins/rates/network-difficulty.js | 12 +++++++++++- 3 files changed, 24 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index a5b17af..a29ad7b 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@lumerin/wallet-core", - "version": "1.1.0", + "version": "1.1.1", "author": { "name": "Lumerin", "email": "developer@lumerin.io", diff --git a/src/plugins/rates/index.js b/src/plugins/rates/index.js index bdf4432..69cf6f8 100644 --- a/src/plugins/rates/index.js +++ b/src/plugins/rates/index.js @@ -2,7 +2,7 @@ const logger = require('../../logger'); -const { getNetworkDifficulty } = require('./network-difficulty') +const { getNetworkDifficulty, getBlockReward } = require('./network-difficulty') const { getRate } = require('./rate') const createStream = require('./stream') @@ -53,11 +53,20 @@ function createPlugin() { }) }) - networkDifficultyStream = createStream(getNetworkDifficulty, ratesUpdateMs) + const streamFn = async () => { + return { + difficulty: await getNetworkDifficulty(), + reward: await getBlockReward(), + } + } + + networkDifficultyStream = createStream(streamFn, ratesUpdateMs) - networkDifficultyStream.on('data', function (difficulty) { + networkDifficultyStream.on('data', function (data) { + const { difficulty, reward } = data; eventBus.emit('network-difficulty-updated', { difficulty, + reward, }) }) diff --git a/src/plugins/rates/network-difficulty.js b/src/plugins/rates/network-difficulty.js index d235b3f..77713e8 100644 --- a/src/plugins/rates/network-difficulty.js +++ b/src/plugins/rates/network-difficulty.js @@ -16,4 +16,14 @@ const getNetworkDifficulty = async () => { } } -module.exports = { getNetworkDifficulty } +const getBlockReward = async () => { + try { + const baseUrl = 'https://blockchain.info' + const res = await axios.get(`${baseUrl}/q/bcperblock`) + return res?.data + } catch (err) { + logger.error('Failed to get block reward:', err) + } +} + +module.exports = { getNetworkDifficulty, getBlockReward } From ec7224611c2efe48c730f78768bb9af53f9aa5d3 Mon Sep 17 00:00:00 2001 From: bohdan-titan Date: Thu, 16 Jan 2025 01:38:53 +0100 Subject: [PATCH 39/42] Removed whitelist check --- package.json | 2 +- src/plugins/contracts/api.js | 7 ------- 2 files changed, 1 insertion(+), 8 deletions(-) diff --git a/package.json b/package.json index a29ad7b..03def86 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@lumerin/wallet-core", - "version": "1.1.1", + "version": "1.1.2", "author": { "name": "Lumerin", "email": "developer@lumerin.io", diff --git a/src/plugins/contracts/api.js b/src/plugins/contracts/api.js index 0c4ce70..05f94a0 100644 --- a/src/plugins/contracts/api.js +++ b/src/plugins/contracts/api.js @@ -132,13 +132,6 @@ function createContract(web3, cloneFactory) { privateKey, } = params - const isWhitelisted = await cloneFactory.methods - .checkWhitelist(sellerAddress) - .call() - if (!isWhitelisted) { - throw new Error('seller is not whitelisted') - } - const tempWallet = ethereumWallet.fromPrivateKey( Buffer.from(remove0xPrefix(privateKey), 'hex') ) From b2e85eb6102db30eea90e424e57c1da7d3152422 Mon Sep 17 00:00:00 2001 From: bohdan-titan Date: Fri, 24 Jan 2025 10:37:36 +0100 Subject: [PATCH 40/42] Added new plugin --- package.json | 4 +- src/index.js | 1 + src/plugins/contracts/api.js | 79 ++++++++++++++ .../configuration-strategies/factory.js | 4 +- src/plugins/validator-registry/api.js | 101 ++++++++++++++++++ src/plugins/validator-registry/index.js | 50 +++++++++ 6 files changed, 235 insertions(+), 4 deletions(-) create mode 100644 src/plugins/validator-registry/api.js create mode 100644 src/plugins/validator-registry/index.js diff --git a/package.json b/package.json index 03def86..366d54c 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@lumerin/wallet-core", - "version": "1.1.2", + "version": "1.1.3", "author": { "name": "Lumerin", "email": "developer@lumerin.io", @@ -37,7 +37,7 @@ "bottleneck": "^2.19.5", "chalk": "^2.4.2", "cheerio": "^1.0.0-rc.12", - "contracts-js": "github:Lumerin-protocol/contracts-js#v0.1.0", + "contracts-js": "github:Lumerin-protocol/contracts-js#v1.2.1", "cross-port-killer": "^1.4.0", "debug": "4.1.1", "ecies-geth": "^1.7.0", diff --git a/src/index.js b/src/index.js index 1b2de0d..e1a85d7 100644 --- a/src/index.js +++ b/src/index.js @@ -14,6 +14,7 @@ const pluginCreators = [ require('./plugins/proxy-router'), require('./plugins/contracts'), require('./plugins/devices'), + require('./plugins/validator-registry') ] function createCore() { diff --git a/src/plugins/contracts/api.js b/src/plugins/contracts/api.js index 05f94a0..5cf2117 100644 --- a/src/plugins/contracts/api.js +++ b/src/plugins/contracts/api.js @@ -328,6 +328,85 @@ function purchaseContract(web3, cloneFactory, lumerin) { } } +/** + * + * @param {import('web3').default} web3 + * @param {import('contracts-js').CloneFactoryContext} cloneFactory + * @param {import('contracts-js').LumerinContext} lumerin + * @returns + */ +function purchaseContractV2(web3, cloneFactory, lumerin) { + return async (params) => { + const { walletId, contractId, url, privateKey, price, version, validator, validatorUrl } = params + const sendOptions = { from: walletId } + + //getting pubkey from contract to be purchased + const implementationContract = Implementation(web3, contractId) + + const pubKey = await implementationContract.methods.pubKey().call() + + //encrypting plaintext url parameter + const ciphertext = await encrypt( + Buffer.from(add65BytesPrefix(pubKey), 'hex'), + Buffer.from(url) + ) + + const account = web3.eth.accounts.privateKeyToAccount(privateKey) + web3.eth.accounts.wallet.create(0).add(account) + + const { + data: { isDead, price: p }, + } = await _loadContractInstance(web3, contractId) + if (isDead) { + throw new Error('Contract is deleted already') + } + + const increaseAllowanceEstimate = await lumerin.methods + .increaseAllowance(cloneFactory.options.address, price) + .estimateGas({ + from: walletId, + }) + + await lumerin.methods + .increaseAllowance(cloneFactory.options.address, price) + .send({ + from: walletId, + gas: increaseAllowanceEstimate, + }) + + const marketplaceFee = await cloneFactory.methods.marketplaceFee().call() + + const purchaseGas = await cloneFactory.methods + .setPurchaseRentalContractV2( + contractId, + validator, + validatorUrl, + ciphertext.toString('hex'), + version + ) + .estimateGas({ + from: sendOptions.from, + value: marketplaceFee, + }) + + const purchaseResult = await cloneFactory.methods + .setPurchaseRentalContractV2( + contractId, + validator, + validatorUrl, + ciphertext.toString('hex'), + version + ) + .send({ + ...sendOptions, + gas: purchaseGas, + value: marketplaceFee, + }) + + logger.debug('Finished puchase transaction', purchaseResult) + } +} + /** * * @param {import('web3').default} web3 diff --git a/src/plugins/devices/configuration-strategies/factory.js b/src/plugins/devices/configuration-strategies/factory.js index 15e1a70..9fb2b44 100644 --- a/src/plugins/devices/configuration-strategies/factory.js +++ b/src/plugins/devices/configuration-strategies/factory.js @@ -1,6 +1,6 @@ const { AbortSignal } = require('@azure/abort-controller') -const { AntMinerStrategy } = require('./ant-miner-strategy') +// const { AntMinerStrategy } = require('./ant-miner-strategy') const { TcpConfigurationStrategy } = require('./tcp-strategy') const { ConfigurationStrategyInterface } = require('./strategy.interface'); @@ -11,7 +11,7 @@ class ConfigurationStrategyFactory { * @returns {Promise} */ static async createStrategy(host, abort) { - const strategies = [TcpConfigurationStrategy, AntMinerStrategy] + const strategies = [TcpConfigurationStrategy] for (const Strategy of strategies) { try { const strategy = new Strategy(host, abort) diff --git a/src/plugins/validator-registry/api.js b/src/plugins/validator-registry/api.js new file mode 100644 index 0000000..1c28cf6 --- /dev/null +++ b/src/plugins/validator-registry/api.js @@ -0,0 +1,101 @@ +const ethereumWallet = require('ethereumjs-wallet').default +const { remove0xPrefix, add65BytesPrefix } = require('../contracts/helpers') +const { secp256k1 } = require('@noble/curves/secp256k1'); +const { hexToBytes, bytesToHex } = require('@noble/curves/abstract/utils'); + +/** + * @param {import('contracts-js').ValidatorRegistryContext} registry + */ +const getValidators = (registry) => async () => { + const ids = await registry.methods.getActiveValidators("0", 100).call() + + const result = await Promise.all(ids.map(id => { + return _loadValidator(registry, id); + })) + return result; +} + +/** + * @param {import('contracts-js').ValidatorRegistryContext} registry + */ +const getStake = (registry) => async () => { + return await registry.methods.stakeMinimum().call() +} + +const _loadValidator = async (registry, id) => { + const data = await registry.methods.getValidator(id).call(); + return { + stake: data[0], + addr: data[1], + pubKeyYparity: data[2], + lastComplainer: data[3], + complains: data[4], + host: data[5], + pubKeyX: data[6] + } +} + +/** + * @param {import('contracts-js').ValidatorRegistryContext} registry + */ +const deregisterValidator = (registry) => async (walletId) => { + await registry.methods.validatorDeregister().send({ from: walletId }); +} + +/** + * @param {import('contracts-js').ValidatorRegistryContext} registry + * @param {import('web3').default} web3 + * @param {import('contracts-js').LumerinContext} lumerin + * @param {import('contracts-js').CloneFactoryContext} cloneFactory + */ +const registerValidator = (registry, web3, lumerin, cloneFactory) => async (request) => { + + const privateKey = request.privateKey; + const tempWallet = ethereumWallet.fromPrivateKey( + Buffer.from(remove0xPrefix(privateKey), 'hex') + ) + const pubKey = tempWallet.getPublicKey(); + const { yParity, x } = compressPublicKey(pubKey); + + const account = web3.eth.accounts.privateKeyToAccount(privateKey) + web3.eth.accounts.wallet.create(0).add(account) + + const stake = await getStake(registry)(); + + const increaseAllowanceEstimate = await lumerin.methods + .increaseAllowance(cloneFactory.options.address, stake) + .estimateGas({ + from: request.walletId, + }) + + await lumerin.methods + .increaseAllowance(cloneFactory.options.address, stake) + .send({ + from: request.walletId, + gas: increaseAllowanceEstimate, + }) + + const estimatedGas = await registry.methods + .validatorRegister(request.stake, yParity, x, request.host) + .estimateGas({ from: request.walletId }); + + await registry.methods.validatorRegister( + request.stake, yParity, x, request.host) + .send({ from: request.walletId, gas: estimatedGas }); +} + +const compressPublicKey = (pubKey) => { + const point = secp256k1.ProjectivePoint.fromHex(pubKey); + const compressed = point.toRawBytes(true); + + return { + yParity: compressed[0] === hexToBytes("03")[0], + x: bytesToHex(compressed.slice(1)), + }; +} + +module.exports = { + getValidators, + registerValidator, + deregisterValidator +} diff --git a/src/plugins/validator-registry/index.js b/src/plugins/validator-registry/index.js new file mode 100644 index 0000000..26ce4d8 --- /dev/null +++ b/src/plugins/validator-registry/index.js @@ -0,0 +1,50 @@ +'use strict' + +const logger = require('../../logger'); +const { + getValidators, + registerValidator, + deregisterValidator +} = require('./api') +const { ValidatorRegistry, Lumerin, CloneFactory } = require('contracts-js') + +function createPlugin() { + + function start({ config, plugins }) { + const { + lmrTokenAddress, + cloneFactoryAddress, + validatorRegistryAddress + } = config + const { eth } = plugins + + const web3 = eth.web3 + + const lumerin = Lumerin(web3, lmrTokenAddress) + const cloneFactory = CloneFactory(web3, cloneFactoryAddress) + + const registry = ValidatorRegistry(web3, validatorRegistryAddress) + + return { + api: { + getValidators: getValidators(registry), + registerValidator: registerValidator(registry, web3, lumerin, cloneFactory), + deregisterValidator: deregisterValidator(registry) + }, + events: [ + ], + name: 'validator-registry', + } + } + + function stop() { + logger.debug('Plugin stopping') + } + + return { + start, + stop, + } +} + +module.exports = createPlugin From ec9215617d15b0f8c9bdb54abbb53dd44f3f70e0 Mon Sep 17 00:00:00 2001 From: Oleksandr Shevchuk Date: Sun, 2 Feb 2025 23:04:34 +0100 Subject: [PATCH 41/42] feat: validator registry contract integration --- package.json | 6 +- src/plugins/contracts/api.js | 137 +++--------- src/plugins/contracts/helpers.js | 12 +- .../ant-miner-strategy.js | 2 +- src/plugins/eth/web3Http.js | 12 +- src/plugins/validator-registry/api.js | 201 ++++++++++++++---- src/plugins/validator-registry/index.js | 23 +- 7 files changed, 220 insertions(+), 173 deletions(-) diff --git a/package.json b/package.json index 366d54c..4f9bbfc 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@lumerin/wallet-core", - "version": "1.1.3", + "version": "1.1.4", "author": { "name": "Lumerin", "email": "developer@lumerin.io", @@ -36,7 +36,6 @@ "axios-cookiejar-support": "1.0.1", "bottleneck": "^2.19.5", "chalk": "^2.4.2", - "cheerio": "^1.0.0-rc.12", "contracts-js": "github:Lumerin-protocol/contracts-js#v1.2.1", "cross-port-killer": "^1.4.0", "debug": "4.1.1", @@ -58,6 +57,7 @@ "websocket-reconnector": "1.1.1" }, "devDependencies": { + "@dopex-io/web3-multicall": "^0.1.10", "chai": "4.3.4", "chai-as-promised": "7.1.1", "check-tag-matches": "1.0.0", @@ -84,4 +84,4 @@ "engines": { "node": ">=12" } -} +} \ No newline at end of file diff --git a/src/plugins/contracts/api.js b/src/plugins/contracts/api.js index 5cf2117..7de605d 100644 --- a/src/plugins/contracts/api.js +++ b/src/plugins/contracts/api.js @@ -3,8 +3,11 @@ const logger = require('../../logger') const { encrypt } = require('ecies-geth') const { Implementation } = require('contracts-js') const { remove0xPrefix, add65BytesPrefix } = require('./helpers') +const { decompressPublicKey } = require('../validator-registry/api') const ethereumWallet = require('ethereumjs-wallet').default +const ZERO_ADDRESS = "0x0000000000000000000000000000000000000000" + /** * @param {import('web3').default} web3 * @param {string} implementationAddress @@ -45,7 +48,7 @@ async function _loadContractInstance( _length: length, // duration of the contract in seconds _version: version, _profitTarget: profitTarget - }, + }, _startingBlockTimestamp: timestamp, // timestamp of the block at moment of purchase _buyer: buyer, // wallet address of the purchasing party _seller: seller, // wallet address of the selling party @@ -253,124 +256,48 @@ function setContractDeleteStatus(web3, cloneFactory, onUpdate) { } } + /** * * @param {import('web3').default} web3 * @param {import('contracts-js').CloneFactoryContext} cloneFactory * @param {import('contracts-js').LumerinContext} lumerin - * @returns + * @returns {(p: {validatorUrl: string, validatorAddr: string, destUrl: string, validatorPubKeyYparity: boolean, validatorPubKeyX: `0x${string}`, contractAddr: string, price: string,version: number, privateKey: string}) => Promise} */ function purchaseContract(web3, cloneFactory, lumerin) { return async (params) => { - const { walletId, contractId, url, privateKey, price, version } = params - const sendOptions = { from: walletId } + const { validatorUrl, destUrl, validatorAddr, validatorPubKeyYparity, validatorPubKeyX, privateKey, contractAddr, price, version } = params; - //getting pubkey from contract to be purchased - const implementationContract = Implementation(web3, contractId) - - const pubKey = await implementationContract.methods.pubKey().call() - - //encrypting plaintext url parameter - const ciphertext = await encrypt( - Buffer.from(add65BytesPrefix(pubKey), 'hex'), - Buffer.from(url) - ) const account = web3.eth.accounts.privateKeyToAccount(privateKey) web3.eth.accounts.wallet.create(0).add(account) - const { - data: { isDead, price: p }, - } = await _loadContractInstance(web3, contractId) - if (isDead) { - throw new Error('Contract is deleted already') - } - - const increaseAllowanceEstimate = await lumerin.methods - .increaseAllowance(cloneFactory.options.address, price) - .estimateGas({ - from: walletId, - }) - - await lumerin.methods - .increaseAllowance(cloneFactory.options.address, price) - .send({ - from: walletId, - gas: increaseAllowanceEstimate, - }) - - const marketplaceFee = await cloneFactory.methods.marketplaceFee().call() - - const purchaseGas = await cloneFactory.methods - .setPurchaseRentalContract( - contractId, - ciphertext.toString('hex'), - version - ) - .estimateGas({ - from: sendOptions.from, - value: marketplaceFee, - }) - - const purchaseResult = await cloneFactory.methods - .setPurchaseRentalContract( - contractId, - ciphertext.toString('hex'), - version - ) - .send({ - ...sendOptions, - gas: purchaseGas, - value: marketplaceFee, - }) + const implementationContract = Implementation(web3, contractAddr) + const sellerPubKey = await implementationContract.methods.pubKey().call() - logger.debug('Finished puchase transaction', purchaseResult) - } -} + const isThirdPartyValidator = validatorAddr !== ZERO_ADDRESS; + const encrDestPubKey = isThirdPartyValidator ? decompressPublicKey(validatorPubKeyYparity, validatorPubKeyX) : sellerPubKey; -/** - * - * @param {import('web3').default} web3 - * @param {import('contracts-js').CloneFactoryContext} cloneFactory - * @param {import('contracts-js').LumerinContext} lumerin - * @returns - */ -function purchaseContractV2(web3, cloneFactory, lumerin) { - return async (params) => { - const { walletId, contractId, url, privateKey, price, version, validator, validatorUrl } = params - const sendOptions = { from: walletId } - - //getting pubkey from contract to be purchased - const implementationContract = Implementation(web3, contractId) - - const pubKey = await implementationContract.methods.pubKey().call() - - //encrypting plaintext url parameter - const ciphertext = await encrypt( - Buffer.from(add65BytesPrefix(pubKey), 'hex'), - Buffer.from(url) - ) - - const account = web3.eth.accounts.privateKeyToAccount(privateKey) - web3.eth.accounts.wallet.create(0).add(account) + const encrValidatorUrl = await encrypt( + Buffer.from(add65BytesPrefix(sellerPubKey), 'hex'), + Buffer.from(validatorUrl) + ).then(res => res.toString('hex')) - const { - data: { isDead, price: p }, - } = await _loadContractInstance(web3, contractId) - if (isDead) { - throw new Error('Contract is deleted already') - } + const encrDestUrl = await encrypt( + Buffer.from(add65BytesPrefix(encrDestPubKey), 'hex'), + Buffer.from(destUrl) + ).then(res => res.toString('hex')) const increaseAllowanceEstimate = await lumerin.methods .increaseAllowance(cloneFactory.options.address, price) .estimateGas({ - from: walletId, + from: account.address, }) await lumerin.methods .increaseAllowance(cloneFactory.options.address, price) .send({ - from: walletId, + from: account.address, gas: increaseAllowanceEstimate, }) @@ -378,27 +305,27 @@ function purchaseContractV2(web3, cloneFactory, lumerin) { const purchaseGas = await cloneFactory.methods .setPurchaseRentalContractV2( - contractId, - validator, - validatorUrl, - ciphertext.toString('hex'), + contractAddr, + validatorAddr, + encrValidatorUrl, + encrDestUrl, version ) .estimateGas({ - from: sendOptions.from, + from: account.address, value: marketplaceFee, }) const purchaseResult = await cloneFactory.methods .setPurchaseRentalContractV2( - contractId, - validator, - validatorUrl, - ciphertext.toString('hex'), + contractAddr, + validatorAddr, + encrValidatorUrl, + encrDestUrl, version ) .send({ - ...sendOptions, + from: account.address, gas: purchaseGas, value: marketplaceFee, }) @@ -436,7 +363,7 @@ function editContract(web3, cloneFactory, lumerin) { .estimateGas({ from: sendOptions.from, }); - + const editResult = await cloneFactory.methods .setUpdateContractInformationV2(contractId, price, limit, speed, duration, +profit) .send({ diff --git a/src/plugins/contracts/helpers.js b/src/plugins/contracts/helpers.js index a30e43a..7c08337 100644 --- a/src/plugins/contracts/helpers.js +++ b/src/plugins/contracts/helpers.js @@ -3,7 +3,17 @@ const remove0xPrefix = privateKey => privateKey.replace('0x', ''); // https://superuser.com/a/1465498 -const add65BytesPrefix = key => `04${key}`; +/** @param {string} key */ +const add65BytesPrefix = key => { + key = key.replace("0x", "") + + // 64 bytes hex string (2 char per byte) + if (key.length === 64 * 2) { + return `04${key}` + } + + return key +} module.exports = { remove0xPrefix, diff --git a/src/plugins/devices/configuration-strategies/ant-miner-strategy.js b/src/plugins/devices/configuration-strategies/ant-miner-strategy.js index 256ab2b..bbdbcc1 100644 --- a/src/plugins/devices/configuration-strategies/ant-miner-strategy.js +++ b/src/plugins/devices/configuration-strategies/ant-miner-strategy.js @@ -1,5 +1,5 @@ const AxiosDigestAuth = require('@mhoc/axios-digest-auth') -const cheerio = require('cheerio') +// const cheerio = require('cheerio') const { ConfigurationStrategyInterface } = require('./strategy.interface') diff --git a/src/plugins/eth/web3Http.js b/src/plugins/eth/web3Http.js index 4860d19..046b0d2 100644 --- a/src/plugins/eth/web3Http.js +++ b/src/plugins/eth/web3Http.js @@ -9,10 +9,16 @@ const isRateLimitError = (response, payload) => { return true } - if (payload?.method === 'eth_call' && (response.error?.message?.includes('execution reverted') || response.error?.code === -32000)) { + // Some providers return execution reverted error for eth_call when rate limiting + if ( + payload?.method === 'eth_call' && + response.error?.message?.includes('execution reverted') && + (response.error?.data === '' || response.error?.data === '0x' || response.error?.data === null || response.error?.data === undefined) + ) { return true } + const message = response.error?.message?.toLowerCase() if (!message) { return false @@ -21,9 +27,9 @@ const isRateLimitError = (response, payload) => { message.includes('too many requests') || message.includes('rate limit exceeded') || message.includes('reached maximum qps limit') || - message.includes('rate limit reached') || + message.includes('rate limit reached') || message.includes("we can't execute this request") || - message.includes("max message response size exceed") || + message.includes("max message response size exceed") || message.includes("upgrade your plan") || message.includes("Failed to validate quota usage") ); diff --git a/src/plugins/validator-registry/api.js b/src/plugins/validator-registry/api.js index 1c28cf6..c3248f7 100644 --- a/src/plugins/validator-registry/api.js +++ b/src/plugins/validator-registry/api.js @@ -1,101 +1,210 @@ +//@ts-check + const ethereumWallet = require('ethereumjs-wallet').default const { remove0xPrefix, add65BytesPrefix } = require('../contracts/helpers') const { secp256k1 } = require('@noble/curves/secp256k1'); const { hexToBytes, bytesToHex } = require('@noble/curves/abstract/utils'); +const { keccak256 } = require('web3-utils'); +/** @type {typeof import("@dopex-io/web3-multicall").default} */ +const Multicall = require("@dopex-io/web3-multicall"); + +// Cross-chain multicall address +const MulticallAddress = "0xcA11bde05977b3631167028862bE2a173976CA11" + +/** + * @typedef {Object} Validator + * @property {string} stake + * @property {string} addr + * @property {string} pubKeyYparity + * @property {string} lastComplainer + * @property {string} complains + * @property {string} host + * @property {string} pubKeyX + */ /** * @param {import('contracts-js').ValidatorRegistryContext} registry + * @param {import('web3').default} web3 + * @returns {function(number, number): Promise>} Function that returns all active validators */ -const getValidators = (registry) => async () => { - const ids = await registry.methods.getActiveValidators("0", 100).call() +const getValidators = (registry, web3, chainId) => async (offset = 0, limit = 100) => { + const addresses = await registry.methods.getActiveValidators(String(offset), limit).call() - const result = await Promise.all(ids.map(id => { - return _loadValidator(registry, id); - })) - return result; + const multicall = new Multicall({ provider: web3.currentProvider, multicallAddress: MulticallAddress }) + const result = await multicall.aggregate( + addresses.map(addr => registry.methods.getValidator(addr)) + ) + + return mapValidators(result); } /** * @param {import('contracts-js').ValidatorRegistryContext} registry + * @returns {function(string): Promise} Returns null if validator is not found */ -const getStake = (registry) => async () => { - return await registry.methods.stakeMinimum().call() +const getValidator = (registry) => async (walletAddr) => { + return await _loadValidator(registry, walletAddr) +} + +/** @param {import('contracts-js').ValidatorRegistryContext} registry */ +const getValidatorsMinimalStake = (registry) => async () => { + return await registry.methods.stakeMinimum().call() +} + +/** @param {import('contracts-js').ValidatorRegistryContext} registry */ +const getValidatorsRegisterStake = (registry) => async () => { + return await registry.methods.stakeRegister().call() } +/** + * @param {import('contracts-js').ValidatorRegistryContext} registry + * @param {string} id + * @returns {Promise} + */ const _loadValidator = async (registry, id) => { - const data = await registry.methods.getValidator(id).call(); - return { - stake: data[0], - addr: data[1], - pubKeyYparity: data[2], - lastComplainer: data[3], - complains: data[4], - host: data[5], - pubKeyX: data[6] + const data = await registry.methods.getValidator(id).call().catch(err => { + if (err.data === getHash("ValidatorNotFound()")) { + return null } + throw err + }); + + if (!data) { + return null + } + + return mapValidator(data) +} + +/** + * @param {import('contracts-js/dist/generated-types/ValidatorRegistry').ValidatorResponse[]} validators + * @returns {Validator[]} + */ +const mapValidators = (validators) => { + return validators.map(mapValidator) +} + +/** + * @param {import('contracts-js/dist/generated-types/ValidatorRegistry').ValidatorResponse} validator + * @returns {Validator} + */ +const mapValidator = (validator) => { + return { + stake: validator[0], + addr: validator[1], + pubKeyYparity: validator[2], + lastComplainer: validator[3], + complains: validator[4], + host: validator[5], + pubKeyX: validator[6] + } } /** * @param {import('contracts-js').ValidatorRegistryContext} registry + * @param {import('web3').default} web3 */ -const deregisterValidator = (registry) => async (walletId) => { - await registry.methods.validatorDeregister().send({ from: walletId }); +const deregisterValidator = (registry, web3) => async ({ walletId, privateKey }) => { + const account = web3.eth.accounts.privateKeyToAccount(privateKey) + web3.eth.accounts.wallet.create(0).add(account) + + const estimatedGas = await registry.methods + .validatorDeregister() + .estimateGas({ from: walletId }); + + await registry.methods.validatorDeregister().send({ from: walletId, gas: estimatedGas }); } +/** + * @typedef {Object} RegisterValidatorRequest + * @property {string} privateKey + * @property {string} stake + * @property {string} host + * @property {string} walletId + */ + /** * @param {import('contracts-js').ValidatorRegistryContext} registry * @param {import('web3').default} web3 * @param {import('contracts-js').LumerinContext} lumerin - * @param {import('contracts-js').CloneFactoryContext} cloneFactory + * @returns {function(RegisterValidatorRequest): Promise} Function that takes a request object and registers a validator */ -const registerValidator = (registry, web3, lumerin, cloneFactory) => async (request) => { - - const privateKey = request.privateKey; - const tempWallet = ethereumWallet.fromPrivateKey( - Buffer.from(remove0xPrefix(privateKey), 'hex') - ) - const pubKey = tempWallet.getPublicKey(); - const { yParity, x } = compressPublicKey(pubKey); - - const account = web3.eth.accounts.privateKeyToAccount(privateKey) - web3.eth.accounts.wallet.create(0).add(account) - - const stake = await getStake(registry)(); - - const increaseAllowanceEstimate = await lumerin.methods - .increaseAllowance(cloneFactory.options.address, stake) +const registerValidator = (registry, web3, lumerin) => async (request) => { + const privateKey = request.privateKey; + const tempWallet = ethereumWallet.fromPrivateKey( + Buffer.from(remove0xPrefix(privateKey), 'hex') + ) + const pubKey = add65BytesPrefix(tempWallet.getPublicKey().toString('hex')); + const { yParity, x } = compressPublicKey(pubKey); + + const account = web3.eth.accounts.privateKeyToAccount(privateKey) + web3.eth.accounts.wallet.create(0).add(account) + + const increaseAllowanceEstimate = await lumerin.methods + .increaseAllowance(registry.options.address, request.stake) .estimateGas({ from: request.walletId, }) await lumerin.methods - .increaseAllowance(cloneFactory.options.address, stake) + .increaseAllowance(registry.options.address, request.stake) .send({ from: request.walletId, gas: increaseAllowanceEstimate, }) - const estimatedGas = await registry.methods - .validatorRegister(request.stake, yParity, x, request.host) - .estimateGas({ from: request.walletId }); + try { + const estimatedGas = await registry.methods + .validatorRegister(request.stake, yParity, x, request.host) + .estimateGas({ from: request.walletId }); await registry.methods.validatorRegister( - request.stake, yParity, x, request.host) - .send({ from: request.walletId, gas: estimatedGas }); + request.stake, yParity, x, request.host) + .send({ from: request.walletId, gas: estimatedGas }); + } catch (err) { + console.log("validator register error", err) + throw err + } } +/** @param {Uint8Array|string} pubKey */ const compressPublicKey = (pubKey) => { const point = secp256k1.ProjectivePoint.fromHex(pubKey); const compressed = point.toRawBytes(true); return { yParity: compressed[0] === hexToBytes("03")[0], - x: bytesToHex(compressed.slice(1)), + x: "0x" + bytesToHex(compressed.slice(1)), }; } +/** @param {boolean} yParity @param {`0x${string}`} x */ +const decompressPublicKey = (yParity, x) => { + const xBytes = hexToBytes(x.replace("0x", "")) + + const rec = new Uint8Array(33); + rec.set(hexToBytes(yParity ? "03" : "02")); + rec.set(xBytes, 1); + + const decompressed = secp256k1.ProjectivePoint.fromHex(bytesToHex(rec)); + + return "0x" + bytesToHex(decompressed.toRawBytes(false)); +} + +/** + * Returns solidity function (error, event) selector hash of the given signature + * @param {string} signature + * */ +const getHash = (signature) => { + return keccak256(signature).slice(0, 10) +} + module.exports = { - getValidators, - registerValidator, - deregisterValidator + getValidator, + getValidators, + getValidatorsMinimalStake, + getValidatorsRegisterStake, + registerValidator, + decompressPublicKey, + deregisterValidator } diff --git a/src/plugins/validator-registry/index.js b/src/plugins/validator-registry/index.js index 26ce4d8..a351af0 100644 --- a/src/plugins/validator-registry/index.js +++ b/src/plugins/validator-registry/index.js @@ -1,19 +1,13 @@ -'use strict' - +// @ts-check const logger = require('../../logger'); -const { - getValidators, - registerValidator, - deregisterValidator -} = require('./api') -const { ValidatorRegistry, Lumerin, CloneFactory } = require('contracts-js') +const api = require('./api') +const { ValidatorRegistry, Lumerin } = require('contracts-js') function createPlugin() { function start({ config, plugins }) { const { lmrTokenAddress, - cloneFactoryAddress, validatorRegistryAddress } = config const { eth } = plugins @@ -21,15 +15,16 @@ function createPlugin() { const web3 = eth.web3 const lumerin = Lumerin(web3, lmrTokenAddress) - const cloneFactory = CloneFactory(web3, cloneFactoryAddress) - const registry = ValidatorRegistry(web3, validatorRegistryAddress) return { api: { - getValidators: getValidators(registry), - registerValidator: registerValidator(registry, web3, lumerin, cloneFactory), - deregisterValidator: deregisterValidator(registry) + getValidator: api.getValidator(registry), + getValidators: api.getValidators(registry, web3), + registerValidator: api.registerValidator(registry, web3, lumerin), + deregisterValidator: api.deregisterValidator(registry, web3), + getValidatorsMinimalStake: api.getValidatorsMinimalStake(registry), + getValidatorsRegisterStake: api.getValidatorsRegisterStake(registry) }, events: [ ], From e9dc82881fff144f45419d7d0268f9a16adbbf34 Mon Sep 17 00:00:00 2001 From: Oleksandr Shevchuk Date: Sun, 2 Feb 2025 23:28:28 +0100 Subject: [PATCH 42/42] fix: move multicall to deps --- package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 4f9bbfc..09ff43a 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@lumerin/wallet-core", - "version": "1.1.4", + "version": "1.1.5", "author": { "name": "Lumerin", "email": "developer@lumerin.io", @@ -31,6 +31,7 @@ "dependencies": { "@azure/abort-controller": "^1.1.0", "@mhoc/axios-digest-auth": "^0.8.0", + "@dopex-io/web3-multicall": "^0.1.10", "abi-decoder": "^2.4.0", "axios": "0.21.1", "axios-cookiejar-support": "1.0.1", @@ -57,7 +58,6 @@ "websocket-reconnector": "1.1.1" }, "devDependencies": { - "@dopex-io/web3-multicall": "^0.1.10", "chai": "4.3.4", "chai-as-promised": "7.1.1", "check-tag-matches": "1.0.0",