diff --git a/README.md b/README.md index f4107de3..c98ad6e4 100644 --- a/README.md +++ b/README.md @@ -274,3 +274,16 @@ let account(1) fulfill the listing grant read access to another party `cast send --private-key $PRIVATE_KEY -i $IPNFT_ADDRESS "grantReadAccess(address,uint256,uint256)" 0x70997970C51812dc3A010C7d01b50e0d17dc79C8 1 1680265071` + +## Actions + +We are using Tenderly Web3Actions to trigger actions based on emitted Events from our deployed Contracts. + +These are setup under the moleculeprotocol organization on Tenderly. +The QueryIds and API-KEY are stored in the Tenderly context and can be accessed via the Tenderly Frontend. +To update these actions you need the Tenderly login credentials. + +- StakedLockingCrowdSale (Mainnet & Goerli): BidEvent => Triggers a POST request that executes Dune Queries to update the Dune Visualizations. + +You can find out more about Web3Actions on Tenderly here: +How to init & deploy new Web3Actions: diff --git a/package.json b/package.json index 27f128f8..d8322dad 100644 --- a/package.json +++ b/package.json @@ -1,5 +1,5 @@ { - "license": "UNLICENSED", + "license": "MIT", "scripts": {}, "devDependencies": { "@nomicfoundation/hardhat-foundry": "^1.0.0", @@ -10,6 +10,5 @@ "chai": "^4.3.7", "ethers": "^5.7.2", "hardhat": "^2.14.0" - }, - "dependencies": {} + } } diff --git a/periphery/dune/crowdsale_bids.sql b/periphery/dune/crowdsale_bids.sql new file mode 100644 index 00000000..9cccc2fe --- /dev/null +++ b/periphery/dune/crowdsale_bids.sql @@ -0,0 +1,31 @@ +with bids as ( + SELECT + Bidder as bidder, + sum(amount) as bid_size + FROM + ipnft_{{chain}}.StakedLockingCrowdSale_evt_Bid + WHERE + saleId = cast('{{saleId}}' as uint256) + GROUP BY + Bidder, + amount + ORDER BY + bid_size DESC +), +decimals as ( + SELECT erc20.decimals + FROM + (SELECT from_hex(json_extract_scalar(sale, '$.biddingToken')) as biddingTokenContract + FROM + ipnft_{{chain}}.StakedLockingCrowdSale_evt_Started + WHERE + saleId = cast('{{saleId}}' as uint256) + ) as sale + LEFT JOIN tokens.erc20 as erc20 + ON erc20.blockchain = '{{chain}}' + AND erc20.contract_address = sale.biddingTokenContract + ) + select + Bidder, + bid_size / pow(10, COALESCE(decimals,18)) as bid_amount + FROM bids, decimals \ No newline at end of file diff --git a/periphery/dune/cumulative_bids.sql b/periphery/dune/cumulative_bids.sql new file mode 100644 index 00000000..8933c4c6 --- /dev/null +++ b/periphery/dune/cumulative_bids.sql @@ -0,0 +1,15 @@ +SELECT + bids.evt_block_time, + SUM(previous_bids.amount) / 1e18 AS cumulative_growth +FROM + ipnft_{{chain}}.StakedLockingCrowdSale_evt_Bid as bids +LEFT JOIN ipnft_{{chain}}.StakedLockingCrowdSale_evt_Bid + AS previous_bids + ON previous_bids.evt_block_time <= bids.evt_block_time +WHERE + bids.saleId = cast('{{saleId}}' as uint256) +GROUP BY + bids.evt_block_time, + bids.amount +ORDER BY + bids.evt_block_time; \ No newline at end of file diff --git a/periphery/tenderly.yaml b/periphery/tenderly.yaml new file mode 100644 index 00000000..719c549a --- /dev/null +++ b/periphery/tenderly.yaml @@ -0,0 +1,38 @@ +account_id: '' +project_slug: '' +actions: + moleculeprotocol/project: + runtime: v2 + sources: tenderly + specs: + BidEventGoerli: + description: BidEvent on Goerli StakedLockingCrowdSale contract + function: dune:triggerDuneQuery + trigger: + type: transaction + transaction: + filters: + - eventEmitted: + contract: + address: 0x46c3369dEce07176Ad7164906D3593AA4C126d35 + name: Bid + network: 5 + status: + - mined + execution_type: '' + BidEventMainnet: + description: BidEvent on Mainnet StakedLockingCrowdSale contract + function: dune:triggerDuneQuery + trigger: + type: transaction + transaction: + filters: + - eventEmitted: + contract: + address: 0x35Bce29F52f51f547998717CD598068Afa2B29B7 + name: Bid + network: 1 + status: + - mined + execution_type: '' + diff --git a/periphery/tenderly/.gitignore b/periphery/tenderly/.gitignore new file mode 100755 index 00000000..2f5b2a0e --- /dev/null +++ b/periphery/tenderly/.gitignore @@ -0,0 +1,5 @@ +# Dependency directories +node_modules/ + +# Ignore tsc output +out/**/* diff --git a/periphery/tenderly/dune.js b/periphery/tenderly/dune.js new file mode 100755 index 00000000..b5a3576b --- /dev/null +++ b/periphery/tenderly/dune.js @@ -0,0 +1,43 @@ +import axios from 'axios' + +const triggerDuneQuery = async (context, event) => { + // keccak256(Bid(uint256 saleId,address bidder,uint256 amount)); + const BID_EVENT_SIG = + '0xdcd726e11f8b5e160f00290f0fe3a1abb547474e53a8e7a8f49a85e7b1ca3199' + + const placeBidLog = event.logs.find((log) => log.topics[0] === BID_EVENT_SIG) + if (!placeBidLog) return + + const saleId = BigInt(placeBidLog.topics[1]).toString() + const DUNE_API_KEY = await context.secrets.get('DUNE_API_KEY') + + //[Cumulative Bids, CrowdSale Bids] + const duneQueryIds = [2709374, 2709364] + + const query_parameters = { + saleId: saleId, + chain: event.network === '5' ? 'goerli' : 'ethereum' + } + + for (const queryId of duneQueryIds) { + try { + const res = await axios.post( + `https://api.dune.com/api/v1/query/${queryId}/execute`, + { + query_parameters + }, + { + headers: { + 'Content-Type': 'application/json', + 'X-Dune-API-Key': DUNE_API_KEY + } + } + ) + console.log('response >> ', res.data) + } catch (e) { + console.log('error >> ', e) + } + } +} + +module.exports = { triggerDuneQuery } diff --git a/periphery/tenderly/package-lock.json b/periphery/tenderly/package-lock.json new file mode 100644 index 00000000..e789e1c2 --- /dev/null +++ b/periphery/tenderly/package-lock.json @@ -0,0 +1,109 @@ +{ + "name": "@moleculeprotocol/web3-actions", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "@moleculeprotocol/web3-actions", + "dependencies": { + "@tenderly/actions": "^0.2.0", + "axios": "^1.4.0" + } + }, + "node_modules/@tenderly/actions": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/@tenderly/actions/-/actions-0.2.4.tgz", + "integrity": "sha512-3shZek2f8D9qBAGYO1ZT/N5y9HSMNZdGh9R+FS7gv48xm53X3SYqHpqituTda0I/AUABaeD1ZQyFEBOTbsHt0Q==" + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" + }, + "node_modules/axios": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.4.0.tgz", + "integrity": "sha512-S4XCWMEmzvo64T9GfvQDOXgYRDJ/wsSZc7Jvdgx5u1sd0JwsuPLqb3SYmusag+edF6ziyMensPVqLTSc1PiSEA==", + "dependencies": { + "follow-redirects": "^1.15.0", + "form-data": "^4.0.0", + "proxy-from-env": "^1.1.0" + } + }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/follow-redirects": { + "version": "1.15.2", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.2.tgz", + "integrity": "sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, + "node_modules/form-data": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", + "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==" + } + } +} diff --git a/periphery/tenderly/package.json b/periphery/tenderly/package.json new file mode 100755 index 00000000..6922e3ca --- /dev/null +++ b/periphery/tenderly/package.json @@ -0,0 +1,9 @@ +{ + "name": "@moleculeprotocol/web3-actions", + "private": true, + "scripts": {}, + "dependencies": { + "@tenderly/actions": "^0.2.0", + "axios": "^1.4.0" + } +} diff --git a/periphery/tenderly/tsconfig.json b/periphery/tenderly/tsconfig.json new file mode 100755 index 00000000..cb5301c2 --- /dev/null +++ b/periphery/tenderly/tsconfig.json @@ -0,0 +1,18 @@ +{ + "compileOnSave": true, + "compilerOptions": { + "module": "commonjs", + "noImplicitReturns": true, + "noUnusedLocals": true, + "outDir": "out", + "sourceMap": true, + "strict": true, + "target": "es2020" + }, + "exclude": [ + "**/*.spec.ts" + ], + "include": [ + "**/*" + ] +} \ No newline at end of file