├── src ├── utils.ts ├── api │ ├── wallet-access.ts │ └── voting.ts ├── index.ts ├── types.ts ├── eventprocessor.ts ├── test-transaction.ts ├── eventlistener.ts ├── hmesh-client.ts ├── db.ts └── contract_abi │ └── presale.json ├── package.json ├── LICENSE ├── .gitignore ├── README.md └── tsconfig.json /src/utils.ts: -------------------------------------------------------------------------------- 1 | import crypto from 'crypto'; 2 | 3 | const ENCRYPTION_KEY = process.env.ENCRYPTION_KEY!; 4 | const IV_LENGTH = 16; // For AES, this is always 16 5 | 6 | export function encrypt(text: string): string { 7 | const iv = crypto.randomBytes(IV_LENGTH); 8 | const cipher = crypto.createCipheriv('aes-256-cbc', Buffer.from(ENCRYPTION_KEY), iv); 9 | let encrypted = cipher.update(text); 10 | encrypted = Buffer.concat([encrypted, cipher.final()]); 11 | return iv.toString('hex') + ':' + encrypted.toString('hex'); 12 | } 13 | 14 | export function decrypt(text: string): string { 15 | const textParts = text.split(':'); 16 | const iv = Buffer.from(textParts.shift()!, 'hex'); 17 | const encryptedText = Buffer.from(textParts.join(':'), 'hex'); 18 | const decipher = crypto.createDecipheriv('aes-256-cbc', Buffer.from(ENCRYPTION_KEY), iv); 19 | let decrypted = decipher.update(encryptedText); 20 | decrypted = Buffer.concat([decrypted, decipher.final()]); 21 | return decrypted.toString(); 22 | } 23 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "eventlistener", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1", 8 | "start": "nodemon --watch 'src/**/*.ts' --exec 'ts-node' src/index.ts" 9 | }, 10 | "keywords": [], 11 | "author": "", 12 | "license": "ISC", 13 | "type": "commonjs", 14 | "devDependencies": { 15 | "@types/cors": "^2.8.17", 16 | "@types/express": "^5.0.1", 17 | "@types/node": "^22.13.10", 18 | "ts-node": "^10.9.2", 19 | "typescript": "^5.8.2" 20 | }, 21 | "dependencies": { 22 | "@arkecosystem/client": "^3.0.0", 23 | "@arkecosystem/crypto": "^3.9.1", 24 | "@types/pg": "^8.11.11", 25 | "axios": "^1.8.3", 26 | "axios-retry": "^4.5.0", 27 | "cors": "^2.8.5", 28 | "dotenv": "^16.4.7", 29 | "ethers": "^6.13.5", 30 | "express": "^4.21.2", 31 | "express-rate-limit": "^7.5.0", 32 | "helmet": "^8.1.0", 33 | "nodemon": "^3.1.9", 34 | "pg": "^8.14.1" 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2025 MARK 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/api/wallet-access.ts: -------------------------------------------------------------------------------- 1 | import express, {Request, Response} from 'express'; 2 | import { ethers } from 'ethers'; 3 | import { getUserInfoByEthAddress } from '../db'; 4 | 5 | const router = express.Router(); 6 | 7 | router.post('/get-hmesh-wallet', async function(req: Request, res: Response) { 8 | try { 9 | const { arbitrumAddress, signature, message } = req.body; 10 | 11 | const recoveredAddress = ethers.verifyMessage(message, signature); 12 | 13 | if (recoveredAddress.toLowerCase() !== arbitrumAddress.toLowerCase()) { 14 | res.status(401).json({ error: 'Invalid signature' }); 15 | return; 16 | } 17 | 18 | const userInfo = await getUserInfoByEthAddress(arbitrumAddress); 19 | 20 | if (!userInfo || !userInfo.hmeshInfo) { 21 | res.status(404).json({ error: 'HMESH wallet not found' }); 22 | return; 23 | } 24 | 25 | res.json({ 26 | success: true, 27 | hmeshAddress: userInfo.hmeshInfo.hmeshAddress, 28 | message: 'HMESH wallet retrieved successfully' 29 | }); 30 | } catch(error) { 31 | console.error('Error retrieving HMESH wallet:', error); 32 | res.status(500).json({ error: 'Server error' }); 33 | } 34 | }); 35 | 36 | export default router; -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import express from 'express'; 2 | import cors from 'cors'; 3 | import { testPastEvents, startEventListener } from "./eventlistener"; 4 | import { resetDatabase } from "./db"; 5 | 6 | import walletAccessRouter from './api/wallet-access'; 7 | import votingRouter from './api/voting'; 8 | 9 | const app = express(); 10 | const PORT = process.env.PORT || 5000; 11 | 12 | app.use(cors()); 13 | app.use(express.json()); 14 | 15 | app.use('/api/wallet', walletAccessRouter); 16 | app.use('/api/voting', votingRouter); 17 | 18 | app.listen(PORT, () => { 19 | console.log(`Server running on port ${PORT}`); 20 | }); 21 | 22 | async function main() { 23 | try { 24 | const foundPastEvents = await testPastEvents(); 25 | 26 | if (!foundPastEvents) { 27 | console.warn('No past RoundCreated events found. This could be normal if no events have been emitted,'); 28 | console.warn('but could also indicate an issue with the contract address, ABI, or event name.'); 29 | } 30 | 31 | await startEventListener(); 32 | // await resetDatabase(); 33 | } catch (error) { 34 | console.error('Fatal error:', error); 35 | process.exit(1); 36 | } 37 | } 38 | 39 | main().catch(error => { 40 | console.error('Unhandled error:', error); 41 | process.exit(1); 42 | }); 43 | -------------------------------------------------------------------------------- /src/types.ts: -------------------------------------------------------------------------------- 1 | export interface HmeshTransaction { 2 | id?: string; 3 | blockId?: string; 4 | version?: number; 5 | type?: number; 6 | typeGroup?: number; 7 | amount?: string; 8 | fee?: string; 9 | sender?: string; 10 | senderPublicKey?: string; 11 | recipient?: string; 12 | recipientId?: string; 13 | signature?: string; 14 | signatures?: string[]; 15 | vendorField?: string; 16 | asset?: any; 17 | confirmations?: number; 18 | timestamp?: any; 19 | nonce?: string; 20 | [key: string]: any; 21 | } 22 | 23 | export interface TransactionResponse { 24 | id: string; 25 | } 26 | 27 | export interface CreateTransactionApiResponse { 28 | data: TransactionResponse[]; 29 | } 30 | 31 | export interface ArbitrumEventData { 32 | eventId: string; 33 | transactionHash: string; 34 | blockNumber: number; 35 | event: 'RoundCreated' | 'TokensBought' | 'TokensClaimed'; 36 | args: any[]; 37 | userInfo?: { 38 | address: string; 39 | rounds: number[]; 40 | purchaseDetails: { 41 | [roundId: string]: { 42 | amountBought: string; 43 | amountClaimed: string; 44 | totalClaimable: string; 45 | cliffCompleted: boolean; 46 | lastClaimTime: string; 47 | unclaimedPeriodsPassed: string; 48 | } 49 | } 50 | }; 51 | 52 | processed: boolean; 53 | createdAt: Date; 54 | processedAt?: Date; 55 | } 56 | 57 | export interface EventQueueRow { 58 | eventId: string; 59 | transactionHash: string; 60 | blockNumber: string | number; 61 | event: 'TokensBought' | 'TokensClaimed'; 62 | args: string; 63 | userInfo: string; 64 | userAddress: string | null; 65 | processed: boolean; 66 | createdAt: Date; 67 | processedAt: Date | null; 68 | } 69 | 70 | export interface UserInfoRow { 71 | ethAddress: string; 72 | hmeshInfo: { 73 | hmeshMnemonic: string; 74 | hmeshPublicKey: string; 75 | hmeshPrivateKey: string; 76 | hmeshAddress: string; 77 | } 78 | rounds: string | any[]; 79 | purchaseDetails: string | Record; 80 | lastUpdated: Date; 81 | createdAt: Date; 82 | } 83 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | lerna-debug.log* 8 | .pnpm-debug.log* 9 | 10 | # Diagnostic reports (https://nodejs.org/api/report.html) 11 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 12 | 13 | # Runtime data 14 | pids 15 | *.pid 16 | *.seed 17 | *.pid.lock 18 | 19 | # Directory for instrumented libs generated by jscoverage/JSCover 20 | lib-cov 21 | 22 | # Coverage directory used by tools like istanbul 23 | coverage 24 | *.lcov 25 | 26 | # nyc test coverage 27 | .nyc_output 28 | 29 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 30 | .grunt 31 | 32 | # Bower dependency directory (https://bower.io/) 33 | bower_components 34 | 35 | # node-waf configuration 36 | .lock-wscript 37 | 38 | # Compiled binary addons (https://nodejs.org/api/addons.html) 39 | build/Release 40 | 41 | # Dependency directories 42 | node_modules/ 43 | jspm_packages/ 44 | 45 | # Snowpack dependency directory (https://snowpack.dev/) 46 | web_modules/ 47 | 48 | # TypeScript cache 49 | *.tsbuildinfo 50 | 51 | # Optional npm cache directory 52 | .npm 53 | 54 | # Optional eslint cache 55 | .eslintcache 56 | 57 | # Optional stylelint cache 58 | .stylelintcache 59 | 60 | # Microbundle cache 61 | .rpt2_cache/ 62 | .rts2_cache_cjs/ 63 | .rts2_cache_es/ 64 | .rts2_cache_umd/ 65 | 66 | # Optional REPL history 67 | .node_repl_history 68 | 69 | # Output of 'npm pack' 70 | *.tgz 71 | 72 | # Yarn Integrity file 73 | .yarn-integrity 74 | 75 | # dotenv environment variable files 76 | .env 77 | .env.development.local 78 | .env.test.local 79 | .env.production.local 80 | .env.local 81 | 82 | # parcel-bundler cache (https://parceljs.org/) 83 | .cache 84 | .parcel-cache 85 | 86 | # Next.js build output 87 | .next 88 | out 89 | 90 | # Nuxt.js build / generate output 91 | .nuxt 92 | dist 93 | 94 | # Gatsby files 95 | .cache/ 96 | # Comment in the public line in if your project uses Gatsby and not Next.js 97 | # https://nextjs.org/blog/next-9-1#public-directory-support 98 | # public 99 | 100 | # vuepress build output 101 | .vuepress/dist 102 | 103 | # vuepress v2.x temp and cache directory 104 | .temp 105 | .cache 106 | 107 | # vitepress build output 108 | **/.vitepress/dist 109 | 110 | # vitepress cache directory 111 | **/.vitepress/cache 112 | 113 | # Docusaurus cache and generated files 114 | .docusaurus 115 | 116 | # Serverless directories 117 | .serverless/ 118 | 119 | # FuseBox cache 120 | .fusebox/ 121 | 122 | # DynamoDB Local files 123 | .dynamodb/ 124 | 125 | # TernJS port file 126 | .tern-port 127 | 128 | # Stores VSCode versions used for testing VSCode extensions 129 | .vscode-test 130 | 131 | # yarn v2 132 | .yarn/cache 133 | .yarn/unplugged 134 | .yarn/build-state.yml 135 | .yarn/install-state.gz 136 | .pnp.* 137 | -------------------------------------------------------------------------------- /src/eventprocessor.ts: -------------------------------------------------------------------------------- 1 | import { createHmeshBridgeClient } from './hmesh-client'; 2 | import { initializeDatabase, saveEventToQueue, getUnprocessedEvents, getUserInfoByEthAddress, markEventAsProcessed } from './db'; 3 | import { ArbitrumEventData } from './types'; 4 | import { decrypt } from './utils'; 5 | 6 | export async function processEvent(eventData: ArbitrumEventData): Promise { 7 | try { 8 | console.log("processing event data") 9 | await initializeDatabase(); 10 | 11 | console.log("save events to queue") 12 | await saveEventToQueue(eventData); 13 | 14 | const unprocessedEvents = await getUnprocessedEvents(); 15 | console.log(`Found ${unprocessedEvents.length} unprocessed events`); 16 | 17 | if (unprocessedEvents.length === 0) { 18 | console.log('No unprocessed events found.'); 19 | return; 20 | } 21 | 22 | for (const event of unprocessedEvents) { 23 | await processEventData(event); 24 | 25 | if (event.eventId) { 26 | await markEventAsProcessed(event.eventId); 27 | } 28 | } 29 | } catch (error) { 30 | console.error('Error processing event:', error); 31 | } 32 | } 33 | 34 | // Function to process the event data 35 | async function processEventData(event: ArbitrumEventData): Promise { 36 | console.log("processing event data") 37 | try { 38 | if (!event.userInfo?.address) { 39 | console.error('No user address found in event:', event); 40 | return; 41 | } 42 | 43 | const userInfoRow = await getUserInfoByEthAddress(event.userInfo.address); 44 | 45 | if (!userInfoRow) { 46 | console.error(`User info not found for address: ${event.userInfo.address}`); 47 | return; 48 | } 49 | if (!userInfoRow.hmeshInfo || !userInfoRow.hmeshInfo.hmeshMnemonic) { 50 | console.error(`Missing HMESH mnemonic for user: ${event.userInfo.address}`); 51 | return; 52 | } 53 | 54 | if (!userInfoRow) { 55 | console.error(`User info not found for address: ${event.userInfo.address}`); 56 | return; 57 | } 58 | 59 | switch (event.event) { 60 | case 'RoundCreated': 61 | console.log('Processing RoundCreated event:', event); 62 | break; 63 | case 'TokensBought': 64 | if (!event.args || event.args.length === 0) { 65 | console.error('Missing args for TokensBought event:', event); 66 | return; 67 | } 68 | console.log('Processing TokensBought event:', event); 69 | console.log(`mintToken amount ---> ${BigInt(event.args[2])}`); 70 | 71 | await createHmeshBridgeClient().mintTokens(userInfoRow?.hmeshInfo.hmeshAddress!, BigInt(event.args[2])); 72 | break; 73 | case 'TokensClaimed': 74 | const decryptedHmeshMnemonic = decrypt(userInfoRow?.hmeshInfo.hmeshMnemonic!) 75 | await createHmeshBridgeClient().burnTokens(decryptedHmeshMnemonic, BigInt(event.args[2])); 76 | console.log('Processing TokensClaimed event:', event); 77 | break; 78 | default: 79 | console.error('Unknown event type:', event.event); 80 | } 81 | 82 | console.log('Processing event:', event); 83 | } catch (error) { 84 | console.error('Error processing event:', error); 85 | } 86 | } -------------------------------------------------------------------------------- /src/api/voting.ts: -------------------------------------------------------------------------------- 1 | import express, { Request, Response } from 'express'; 2 | import { ethers } from 'ethers'; 3 | import { getUserInfoByEthAddress } from '../db'; 4 | import { createHmeshBridgeClient, getCurrentVotes } from '../hmesh-client'; 5 | 6 | const router = express.Router(); 7 | const hmeshClient = createHmeshBridgeClient(); 8 | 9 | // Endpoint to cast votes on behalf of the user 10 | router.post('/vote', async function(req: Request, res: Response) { 11 | try { 12 | const { arbitrumAddress, signature, message, delegatePublicKey } = req.body; 13 | 14 | const recoveredAddress = ethers.verifyMessage(message, signature); 15 | 16 | if (recoveredAddress.toLowerCase() !== arbitrumAddress.toLowerCase()) { 17 | res.status(401).json({ error: 'Invalid signature' }); 18 | return; 19 | } 20 | 21 | const userInfo = await getUserInfoByEthAddress(arbitrumAddress); 22 | 23 | if (!userInfo || !userInfo.hmeshInfo) { 24 | res.status(404).json({ error: 'HMESH wallet not found' }); 25 | return; 26 | } 27 | 28 | const transaction = await hmeshClient.submitVote(userInfo.hmeshInfo.hmeshAddress, delegatePublicKey); 29 | 30 | res.json({ 31 | success: true, 32 | transactionId: transaction, 33 | message: 'Vote submitted successfully' 34 | }); 35 | } catch (error) { 36 | console.error('Error submitting vote:', error); 37 | res.status(500).json({ error: 'Server error' }); 38 | } 39 | }); 40 | 41 | // Endpoint to unvote a delegate 42 | router.post('/unvote', async function(req: Request, res: Response) { 43 | try { 44 | const { arbitrumAddress, signature, message, delegatePublicKey } = req.body; 45 | 46 | const recoveredAddress = ethers.verifyMessage(message, signature); 47 | 48 | if (recoveredAddress.toLowerCase() !== arbitrumAddress.toLowerCase()) { 49 | res.status(401).json({ error: 'Invalid signature' }); 50 | return; 51 | } 52 | 53 | const userInfo = await getUserInfoByEthAddress(arbitrumAddress); 54 | 55 | if (!userInfo || !userInfo.hmeshInfo) { 56 | res.status(404).json({ error: 'HMESH wallet not found' }); 57 | return; 58 | } 59 | 60 | const transaction = await hmeshClient.submitUnvote(userInfo.hmeshInfo.hmeshAddress, delegatePublicKey); 61 | 62 | res.json({ 63 | success: true, 64 | transactionId: transaction, 65 | message: 'Unvote submitted successfully' 66 | }); 67 | } catch (error) { 68 | console.error('Error submitting unvote:', error); 69 | res.status(500).json({ error: 'Server error' }); 70 | } 71 | }); 72 | 73 | // Endpoint to get wallet balance and voting status 74 | router.post('/status', async function(req: Request, res: Response) { 75 | try { 76 | const { arbitrumAddress, signature, message } = req.body; 77 | 78 | const recoveredAddress = ethers.verifyMessage(message, signature); 79 | 80 | if (recoveredAddress.toLowerCase() !== arbitrumAddress.toLowerCase()) { 81 | res.status(401).json({ error: 'Invalid signature' }); 82 | return; 83 | } 84 | 85 | const userInfo = await getUserInfoByEthAddress(arbitrumAddress); 86 | 87 | if (!userInfo || !userInfo.hmeshInfo) { 88 | res.status(404).json({ error: 'HMESH wallet not found' }); 89 | return; 90 | } 91 | 92 | const balance = await hmeshClient.getBalance(userInfo.hmeshInfo.hmeshAddress); 93 | 94 | const currentVotes = await getCurrentVotes(userInfo.hmeshInfo.hmeshAddress); 95 | 96 | res.json({ 97 | hmeshAddress: userInfo.hmeshInfo.hmeshAddress, 98 | balance, 99 | currentVotes, 100 | message: 'Wallet status retrieved successfully' 101 | }); 102 | } catch (error) { 103 | console.error('Error retrieving wallet status:', error); 104 | res.status(500).json({ error: 'Server error' }); 105 | } 106 | }); 107 | 108 | export default router; 109 | -------------------------------------------------------------------------------- /src/test-transaction.ts: -------------------------------------------------------------------------------- 1 | // import axios from 'axios'; 2 | // import dotenv from 'dotenv' 3 | // const { Connection } = require("@arkecosystem/client"); 4 | // const {Transactions, Managers, Utils} = require("@arkecosystem/crypto"); 5 | 6 | // dotenv.config(); 7 | // const API_URL = process.env.HMESH_DEVNET_NODE_URL; 8 | 9 | 10 | // const client = new Connection(`${API_URL}/api`); 11 | 12 | // Managers.configManager.setFromPreset("devnet"); 13 | // Managers.configManager.setHeight(4006000); 14 | 15 | // const response = client.api("transactions").all(); 16 | // console.log(response); 17 | 18 | // // Configuration 19 | 20 | // /** 21 | // * Test GET request - Fetch latest transactions 22 | // */ 23 | // async function testGetTransactions() { 24 | // try { 25 | // console.log('Testing GET transactions...'); 26 | 27 | // const response = await axios.get(`${API_URL}/api/transactions`, { 28 | // params: { 29 | // limit: 5, 30 | // page: 1, 31 | // orderBy: 'timestamp:desc' 32 | // } 33 | // }); 34 | 35 | // console.log('GET Success! Latest 5 transactions:'); 36 | // response.data.data.forEach((tx: any) => { 37 | // console.log(`ID: ${tx.id}, Amount: ${tx.amount}, Sender: ${tx.sender}`); 38 | // }); 39 | 40 | // return response.data; 41 | // } catch (error:any) { 42 | // console.error('GET Error:', error.message); 43 | // if (error.response) { 44 | // console.error('Response data:', error.response.data); 45 | // } 46 | // } 47 | // } 48 | 49 | // /** 50 | // * Test POST request - Broadcast a transaction 51 | // * Note: This is a sample transaction and will fail validation 52 | // * Replace with a properly signed transaction for actual testing 53 | // */ 54 | // async function testPostTransaction() { 55 | // try { 56 | // console.log('\nTesting POST transaction...'); 57 | 58 | // // This is a sample transaction structure 59 | 60 | // const sampleTransaction = { 61 | // version: 2, 62 | // network: 23, // devnet 63 | // type: 0, 64 | // nonce: "1", 65 | // senderPublicKey: "03287bfebba4c7881a0509717e71b34b63f31e40021c321f89ae04f84be6d6ac37", 66 | // fee: "10000000", 67 | // amount: "100000000", 68 | // recipientId: "D8rr7B1d6TL6pf14LgMz4sKp1VBMs6YUYD", 69 | // signature: "sample_signature_replace_with_real_one", 70 | // id: "sample_id_replace_with_real_one" 71 | // }; 72 | 73 | // const response = await axios.post(`${API_URL}/transactions`, { 74 | // transactions: [sampleTransaction] 75 | // }); 76 | 77 | // console.log('POST Success! Response:', response.data); 78 | // return response.data; 79 | // } catch (error:any) { 80 | // console.error('POST Error:', error.message); 81 | // if (error.response) { 82 | // console.error('Response data:', error.response.data); 83 | // } 84 | // } 85 | // } 86 | 87 | // /** 88 | // * Test GET request - Fetch transaction fees 89 | // */ 90 | // async function testGetTransactionFees() { 91 | // try { 92 | // console.log('\nTesting GET transaction fees...'); 93 | 94 | // const response = await axios.get(`${API_URL}/transactions/fees`); 95 | 96 | // console.log('GET Success! Transaction fees:'); 97 | // console.log(JSON.stringify(response.data.data, null, 2)); 98 | 99 | // return response.data; 100 | // } catch (error:any) { 101 | // console.error('GET Error:', error.message); 102 | // if (error.response) { 103 | // console.error('Response data:', error.response.data); 104 | // } 105 | // } 106 | // } 107 | 108 | // /** 109 | // * Run all tests 110 | // */ 111 | // async function runTests() { 112 | // try { 113 | // // Test GET endpoints 114 | // await testGetTransactions(); 115 | // await testGetTransactionFees(); 116 | 117 | // // Test POST endpoint (will likely fail without a proper signature) 118 | // await testPostTransaction(); 119 | 120 | // console.log('\nAll tests completed!'); 121 | // } catch (error) { 122 | // console.error('Test execution error:', error); 123 | // } 124 | // } 125 | 126 | // // Run the tests 127 | // // runTests(); 128 | 129 | 130 | import axios from 'axios'; 131 | import dotenv from 'dotenv'; 132 | const { Connection } = require("@arkecosystem/client"); 133 | const { Transactions, Managers, Utils } = require("@arkecosystem/crypto"); 134 | 135 | dotenv.config(); 136 | 137 | const API_URL = process.env.HMESH_DEVNET_NODE_URL; 138 | console.log("Using API URL:", API_URL); // Log the URL to verify it's correct 139 | 140 | // Create a client with a custom timeout 141 | const client = new Connection(`${API_URL}/api`, { 142 | timeout: 30000, // 10 seconds timeout 143 | }); 144 | 145 | // Configure the network 146 | Managers.configManager.setFromPreset("devnet"); 147 | Managers.configManager.setHeight(4006000); 148 | 149 | // First test basic connectivity with axios 150 | async function testConnectivity() { 151 | try { 152 | console.log("Testing basic connectivity to the API..."); 153 | const response = await axios.get(`${API_URL}/api/node/status`, { 154 | timeout: 5000 // 5 seconds timeout 155 | }); 156 | console.log("API is reachable. Node status:", response.data); 157 | return true; 158 | } catch (error:any) { 159 | console.error("Error connecting to API:", error.message); 160 | if (error.code === 'ECONNREFUSED') { 161 | console.error("Connection refused. The server might be down or the URL might be incorrect."); 162 | } else if (error.code === 'ETIMEDOUT') { 163 | console.error("Connection timed out. The server might be slow or unreachable."); 164 | } 165 | return false; 166 | } 167 | } 168 | 169 | // Then try to get transactions 170 | async function getTransactions() { 171 | try { 172 | console.log("Fetching transactions with axios..."); 173 | const response = await axios.get(`${API_URL}/api/transactions`, { 174 | params: { 175 | page: 1, 176 | limit: 10 177 | }, 178 | timeout: 15000 179 | }); 180 | console.log("Transactions:", response.data); 181 | return response.data; 182 | } catch (error:any) { 183 | console.error("Error fetching transactions:", error.message); 184 | throw error; 185 | } 186 | } 187 | 188 | // Run the tests 189 | async function runTests() { 190 | const isConnected = await testConnectivity(); 191 | if (isConnected) { 192 | try { 193 | await getTransactions(); 194 | } catch (error) { 195 | console.error("Failed to get transactions."); 196 | } 197 | } else { 198 | console.error("Skipping transaction fetch due to connectivity issues."); 199 | } 200 | } 201 | 202 | runTests(); -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Arbitrum-Smart-Bridge-with-ARK 2 | 3 | A bridge implementation that enables asset transfers between Arbitrum and the ARK V3 based custom DPoS blockchain - HMESH. 4 | 5 | ## Overview 6 | 7 | This project implements a cross-chain bridge that allows users to transfer tokens between Arbitrum (an Ethereum Layer 2 scaling solution) and HMESH blockchain. The bridge facilitates the minting and burning of tokens on the HMESH blockchain, which corresponds to locking and unlocking of tokens on the Arbitrum side. 8 | 9 | ## Features 10 | 11 | - **Token Bridging**: Transfer tokens between Arbitrum and HMESH networks 12 | - **Minting Operations**: Create new HMESH tokens when assets are locked on Arbitrum 13 | - **Burning Operations**: Burn HMESH tokens to release assets on Arbitrum 14 | - **Wallet Management**: Create and manage HMESH wallets 15 | - **Transaction Handling**: Reliable transaction submission with retry mechanisms 16 | 17 | ## Prerequisites 18 | 19 | - Node.js (v14 or higher) 20 | - npm or yarn 21 | - Access to Arbitrum and HMESH networks 22 | 23 | ## Installation 24 | 25 | 1. Clone the repository: 26 | ```bash 27 | git clone https://github.com//Arbitrum-Smart-Bridge-with-ARK.git 28 | cd Arbitrum-Smart-Bridge-with-ARK 29 | ``` 30 | 31 | 2. Install dependencies: 32 | ```bash 33 | npm install 34 | ``` 35 | 36 | 3. Configure environment variables by creating a `.env` file: 37 | ``` 38 | HMESH_DEVNET_NODE_URL=https://your-hmesh-node-url 39 | HMESH_BRIDGE_MNEMONIC=your-bridge-wallet-mnemonic 40 | HMESH_NETWORK=devnet 41 | ``` 42 | 43 | ## Usage 44 | 45 | ### Bridging Tokens 46 | 47 | First it catches the event of a new transaction on the Arbitrum network when user buys or claims ERC20 token on presale using RPC node and then it sends the transaction to HMESH network. 48 | 49 | ```typescript 50 | async function catchEvent(event: any, source: string, args?: any[], eventName?: 'RoundCreated' | 'TokensBought' | 'TokensClaimed') { 51 | try { 52 | const eventId = `${event.transactionHash}-${event.index || 0}`; 53 | 54 | if (processedEvents.has(eventId)) { 55 | return; 56 | } 57 | 58 | processedEvents.add(eventId); 59 | 60 | console.log(`${source}: ${eventName || 'Unknown'} event detected at block ${event.blockNumber}`); 61 | 62 | const isEventLog = (log: Log | EventLog): log is EventLog => { 63 | return 'args' in log; 64 | }; 65 | 66 | let actualEventName: 'RoundCreated' | 'TokensBought' | 'TokensClaimed'; 67 | 68 | if (eventName) { 69 | actualEventName = eventName; 70 | } else if (isEventLog(event) && event.fragment && event.fragment.name) { 71 | actualEventName = event.fragment.name as 'RoundCreated' | 'TokensBought' | 'TokensClaimed'; 72 | } else { 73 | console.warn('Could not determine event type, defaulting to RoundCreated'); 74 | actualEventName = 'RoundCreated'; 75 | } 76 | 77 | const eventArgs = args ? args : (isEventLog(event) ? Array.from(event.args || []) : []); 78 | 79 | const eventData: ArbitrumEventData = { 80 | eventId: eventId, 81 | transactionHash: event.transactionHash, 82 | blockNumber: event.blockNumber, 83 | event: actualEventName, 84 | args: eventArgs, 85 | processed: false, 86 | createdAt: new Date(), 87 | }; 88 | 89 | if (actualEventName === 'TokensBought' || actualEventName === 'TokensClaimed') { 90 | try { 91 | const userAddress = eventArgs[0]; 92 | 93 | if (userAddress) { 94 | const userRounds = await httpContract.getUserRounds(userAddress); 95 | const userPurchaseDetails: any = {}; 96 | 97 | for (const roundId of userRounds) { 98 | const purchaseDetails = await httpContract.getUserRoundPurchase(userAddress, roundId); 99 | 100 | userPurchaseDetails[roundId] = { 101 | amountBought: purchaseDetails[0].toString(), 102 | amountClaimed: purchaseDetails[1].toString(), 103 | totalClaimable: purchaseDetails[2].toString(), 104 | cliffCompleted: purchaseDetails[3], 105 | lastClaimTime: purchaseDetails[4].toString() 106 | }; 107 | } 108 | 109 | eventData.userInfo = { 110 | address: userAddress, 111 | rounds: userRounds, 112 | purchaseDetails: userPurchaseDetails 113 | }; 114 | } 115 | } catch (userInfoError) { 116 | console.error('Error fetching user information:', userInfoError); 117 | } 118 | } 119 | 120 | console.log(`${actualEventName} event data:`, eventData); 121 | 122 | await processEvent(eventData) 123 | 124 | if (processedEvents.size > 1000) { 125 | const toRemove = Array.from(processedEvents).slice(0, 500); 126 | toRemove.forEach(id => processedEvents.delete(id)); 127 | } 128 | } catch (error) { 129 | console.error(`Error processing ${source} event:`, error); 130 | } 131 | } 132 | ``` 133 | 134 | ### Creating a HMESH Wallet 135 | 136 | ```typescript 137 | export function createHMESHWallet() { 138 | const mnemonic: string = generateMnemonic(256); 139 | 140 | const publicKey = Identities.PublicKey.fromPassphrase(mnemonic); 141 | const privateKey = Identities.PrivateKey.fromPassphrase(mnemonic); 142 | const address = Identities.Address.fromPassphrase(mnemonic); 143 | 144 | return { mnemonic, publicKey, privateKey, address } 145 | } 146 | 147 | ``` 148 | 149 | ### Minting Tokens on HMESH 150 | 151 | ```typescript 152 | async function mintTokens(hmeshClientAddress: string, amount: bigint): Promise { 153 | try { 154 | console.log(`Minting ${amount} tokens for ${hmeshClientAddress}`); 155 | const nonce = await getNextNonce(bridgeAddress); 156 | 157 | const amountWithDecimals = (amount / BigInt(10000000000)).toString(); 158 | console.log(`Converted native HMESH coinst with 8 decimals: ${amountWithDecimals}`); 159 | 160 | const transaction:HmeshTransaction = Transactions.BuilderFactory 161 | .transfer() 162 | .version(2) 163 | .nonce(nonce) 164 | .recipientId(hmeshClientAddress) 165 | .amount(amountWithDecimals) 166 | .vendorField(JSON.stringify({ 167 | action: 'mint', 168 | token: 'HMESH' 169 | })) 170 | .fee('10000000') 171 | .typeGroup(1) 172 | .sign(HMESH_BRIDGE_MNEMONIC) 173 | .build(); 174 | 175 | console.log('Transaction details:', transaction.data); 176 | 177 | const txId = await sendTransaction(transaction.data); 178 | console.log(`Successfully minted ${amount} HMESH tokens. Transaction ID: ${txId}`); 179 | return txId; 180 | } catch (error:any) { 181 | if (error.message?.includes('nonce')) { 182 | console.error('Nonce error detected, retrying with updated nonce...'); 183 | } 184 | console.error('Error minting tokens:', error); 185 | throw new Error(`Failed to mint tokens: ${error.message}`); 186 | } 187 | } 188 | ``` 189 | 190 | ### Burning Tokens on HMESH 191 | 192 | ```typescript 193 | async function burnTokens(HMESH_CLIENT_MNEMONIC: string, amount: bigint): Promise { 194 | try { 195 | const hmeshClientAddress = Identities.Address.fromPassphrase(HMESH_CLIENT_MNEMONIC); 196 | console.log(`Burning ${amount} tokens for ${hmeshClientAddress}`); 197 | 198 | const nonce = await getNextNonce(hmeshClientAddress); 199 | 200 | const amountWithDecimals = (amount / BigInt(10000000000)).toString(); 201 | console.log(`Converted native HMESH coinst with 8 decimals: ${amountWithDecimals}`); 202 | 203 | const transaction: HmeshTransaction = Transactions.BuilderFactory 204 | .transfer() 205 | .version(2) 206 | .nonce(nonce) 207 | .recipientId(bridgeAddress) 208 | .amount(amountWithDecimals.toString()) 209 | .vendorField(JSON.stringify({ 210 | action: 'burn', 211 | token: 'HMESH', 212 | })) 213 | .fee('10000000') 214 | .typeGroup(1) 215 | .sign(HMESH_CLIENT_MNEMONIC) 216 | .build(); 217 | 218 | console.log('Transaction details:', transaction.data); 219 | 220 | const txId = await sendTransaction(transaction.data); 221 | console.log(`Successfully burned ${amount} HMESH tokens. Transaction ID: ${txId}`) 222 | return txId; 223 | } catch (error) { 224 | console.error('Error burning tokens:', error); 225 | throw error; 226 | } 227 | } 228 | ``` 229 | 230 | ## Technical Details 231 | 232 | - The HMESH blockchain uses 8 decimal places for its native token 233 | - Transactions on HMESH include metadata in the `vendorField` to specify actions (mint/burn) 234 | - The bridge uses a dedicated wallet (specified by `HMESH_BRIDGE_MNEMONIC`) to handle token minting 235 | - Transaction submission includes retry logic to handle network issues 236 | 237 | ## Security Considerations 238 | 239 | - The bridge wallet mnemonic should be kept secure and not exposed 240 | - Consider implementing multi-signature requirements for bridge operations 241 | - Implement proper monitoring and alerting for bridge activities 242 | - Regular audits of the bridge code and operations are recommended 243 | 244 | ## Development 245 | 246 | ### Running Tests 247 | 248 | ```bash 249 | npm test 250 | ``` 251 | 252 | ### Building the Project 253 | 254 | ```bash 255 | npm run build 256 | ``` 257 | 258 | ## License 259 | 260 | [MIT License](LICENSE) 261 | 262 | ## Contributing 263 | 264 | Contributions are welcome! Please feel free to submit a Pull Request. 265 | -------------------------------------------------------------------------------- /src/eventlistener.ts: -------------------------------------------------------------------------------- 1 | import { ethers } from 'ethers'; 2 | import { Log, EventLog } from 'ethers'; 3 | import dotenv from 'dotenv'; 4 | import { processEvent } from './eventprocessor'; 5 | import { ArbitrumEventData } from './types'; 6 | const CONTRACT_ABI = require('./contract_abi/presale.json'); 7 | 8 | dotenv.config(); 9 | 10 | // Arbitrum RPC URLs 11 | const ARBITRUM_RPC_URL = process.env.ARBITRUM_SEPOLIA_RPC_URL || 'https://sepolia-rollup.arbitrum.io/rpc'; 12 | const ARBITRUM_WS_URL = process.env.ARBITRUM_SEPOLIA_WSS_URL || 'wss://sepolia-rollup.arbitrum.io/ws'; 13 | 14 | const CONTRACT_ADDRESS = process.env.CONTRACT_ADDRESS_SEPOLIA!; 15 | 16 | const httpProvider = new ethers.JsonRpcProvider(ARBITRUM_RPC_URL, undefined, { 17 | polling: true, 18 | pollingInterval: 4000, 19 | staticNetwork: true, 20 | batchStallTime: 50, 21 | }); 22 | 23 | let wsProvider: ethers.WebSocketProvider | null = null; 24 | let wsReconnectInterval: NodeJS.Timeout | null = null; 25 | 26 | try { 27 | wsProvider = new ethers.WebSocketProvider(ARBITRUM_WS_URL); 28 | } catch (error) { 29 | console.warn('Failed to initialize WebSocket provider:', error); 30 | console.warn('Falling back to HTTP polling only'); 31 | } 32 | 33 | // Initialize contracts 34 | const httpContract = new ethers.Contract(CONTRACT_ADDRESS, CONTRACT_ABI, httpProvider); 35 | let wsContract: ethers.Contract | null = null; 36 | if (wsProvider) { 37 | wsContract = new ethers.Contract(CONTRACT_ADDRESS, CONTRACT_ABI, wsProvider); 38 | } 39 | 40 | const processedEvents = new Set(); 41 | 42 | function setupWebSocketProvider() { 43 | try { 44 | if (wsProvider) { 45 | // Clean up existing provider if any 46 | try { 47 | wsProvider.destroy(); 48 | } catch (e) { 49 | console.warn('Error destroying previous WebSocket provider:', e); 50 | } 51 | } 52 | 53 | console.log('Setting up WebSocket provider...'); 54 | wsProvider = new ethers.WebSocketProvider(ARBITRUM_WS_URL); 55 | 56 | // Access the underlying WebSocket connection 57 | const websocket = (wsProvider as any)._websocket; 58 | 59 | if (websocket) { 60 | websocket.onclose = () => { 61 | console.warn('WebSocket connection closed'); 62 | wsProvider = null; 63 | wsContract = null; 64 | 65 | if (!wsReconnectInterval) { 66 | console.log('Scheduling WebSocket reconnection...'); 67 | wsReconnectInterval = setTimeout(() => { 68 | wsReconnectInterval = null; 69 | setupWebSocketProvider(); 70 | }, 10000); 71 | } 72 | }; 73 | 74 | websocket.onerror = (error: any) => { 75 | console.error('WebSocket error:', error); 76 | }; 77 | } 78 | 79 | wsContract = new ethers.Contract(CONTRACT_ADDRESS, CONTRACT_ABI, wsProvider); 80 | 81 | wsContract.on('RoundCreated', async (...args: any[]) => { 82 | const event = args[args.length - 1]; 83 | await catchEvent(event, 'WebSocket', args.slice(0, -1), 'RoundCreated'); 84 | }); 85 | 86 | wsContract.on('TokensBought', async (...args: any[]) => { 87 | const event = args[args.length - 1]; 88 | await catchEvent(event, 'WebSocket', args.slice(0, -1), 'TokensBought'); 89 | }) 90 | 91 | wsContract.on('TokensClaimed', async (...args: any[]) => { 92 | const event = args[args.length - 1]; 93 | await catchEvent(event, 'WebSocket', args.slice(0, -1), 'TokensClaimed'); 94 | }) 95 | 96 | console.log('WebSocket provider and contract set up successfully'); 97 | return true; 98 | } catch (error) { 99 | console.error('Failed to set up WebSocket provider:', error); 100 | wsProvider = null; 101 | wsContract = null; 102 | return false; 103 | } 104 | } 105 | 106 | 107 | // Process an event regardless of source 108 | async function catchEvent(event: any, source: string, args?: any[], eventName?: 'RoundCreated' | 'TokensBought' | 'TokensClaimed') { 109 | try { 110 | const eventId = `${event.transactionHash}-${event.index || 0}`; 111 | 112 | if (processedEvents.has(eventId)) { 113 | return; 114 | } 115 | 116 | // Mark as processed 117 | processedEvents.add(eventId); 118 | 119 | console.log(`${source}: ${eventName || 'Unknown'} event detected at block ${event.blockNumber}`); 120 | 121 | const isEventLog = (log: Log | EventLog): log is EventLog => { 122 | return 'args' in log; 123 | }; 124 | 125 | let actualEventName: 'RoundCreated' | 'TokensBought' | 'TokensClaimed'; 126 | 127 | if (eventName) { 128 | actualEventName = eventName; 129 | } else if (isEventLog(event) && event.fragment && event.fragment.name) { 130 | actualEventName = event.fragment.name as 'RoundCreated' | 'TokensBought' | 'TokensClaimed'; 131 | } else { 132 | console.warn('Could not determine event type, defaulting to RoundCreated'); 133 | actualEventName = 'RoundCreated'; 134 | } 135 | 136 | const eventArgs = args ? args : (isEventLog(event) ? Array.from(event.args || []) : []); 137 | 138 | const eventData: ArbitrumEventData = { 139 | eventId: eventId, 140 | transactionHash: event.transactionHash, 141 | blockNumber: event.blockNumber, 142 | event: actualEventName, 143 | args: eventArgs, 144 | processed: false, 145 | createdAt: new Date(), 146 | }; 147 | 148 | if (actualEventName === 'TokensBought' || actualEventName === 'TokensClaimed') { 149 | try { 150 | const userAddress = eventArgs[0]; 151 | 152 | if (userAddress) { 153 | const userRounds = await httpContract.getUserRounds(userAddress); 154 | 155 | const userPurchaseDetails: any = {}; 156 | 157 | for (const roundId of userRounds) { 158 | const purchaseDetails = await httpContract.getUserRoundPurchase(userAddress, roundId); 159 | 160 | userPurchaseDetails[roundId] = { 161 | amountBought: purchaseDetails[0].toString(), 162 | amountClaimed: purchaseDetails[1].toString(), 163 | totalClaimable: purchaseDetails[2].toString(), 164 | cliffCompleted: purchaseDetails[3], 165 | lastClaimTime: purchaseDetails[4].toString() 166 | }; 167 | } 168 | 169 | eventData.userInfo = { 170 | address: userAddress, 171 | rounds: userRounds, 172 | purchaseDetails: userPurchaseDetails 173 | }; 174 | } 175 | } catch (userInfoError) { 176 | console.error('Error fetching user information:', userInfoError); 177 | } 178 | } 179 | 180 | console.log(`${actualEventName} event data:`, eventData); 181 | 182 | await processEvent(eventData) 183 | 184 | if (processedEvents.size > 1000) { 185 | const toRemove = Array.from(processedEvents).slice(0, 500); 186 | toRemove.forEach(id => processedEvents.delete(id)); 187 | } 188 | } catch (error) { 189 | console.error(`Error processing ${source} event:`, error); 190 | } 191 | } 192 | 193 | export async function startEventListener() { 194 | console.log('Starting Arbitrum event listener...'); 195 | 196 | try { 197 | const network = await httpProvider.getNetwork(); 198 | console.log('Connected to network:', network.name); 199 | 200 | const code = await httpProvider.getCode(CONTRACT_ADDRESS); 201 | if (code === '0x') { 202 | console.error('Contract not deployed on network:', network.name); 203 | process.exit(1); 204 | } 205 | 206 | console.log(`Contract found at ${CONTRACT_ADDRESS}`); 207 | 208 | let lastCheckedBlock = await httpProvider.getBlockNumber(); 209 | console.log(`Starting from block ${lastCheckedBlock}`); 210 | 211 | const wsSetupSuccess = setupWebSocketProvider(); 212 | 213 | if (!wsSetupSuccess) { 214 | console.warn('WebSocket setup failed, continuing with HTTP polling only'); 215 | } 216 | 217 | console.log('Setting up polling backup for events...'); 218 | 219 | let consecutiveErrors = 0; 220 | 221 | async function pollForEvents() { 222 | let currentBlock; 223 | try { 224 | currentBlock = await httpProvider.getBlockNumber(); 225 | 226 | if (currentBlock <= lastCheckedBlock) { 227 | return; 228 | } 229 | 230 | const batchSize = 2000; 231 | const fromBlock = lastCheckedBlock + 1; 232 | const toBlock = Math.min(currentBlock, fromBlock + batchSize - 1); 233 | 234 | const eventTypes = ['RoundCreated', 'TokensBought', 'TokensClaimed']; 235 | 236 | for (const eventType of eventTypes) { 237 | try { 238 | const filter = httpContract.filters[eventType](); 239 | 240 | const events = await httpContract.queryFilter(filter, fromBlock, toBlock); 241 | 242 | if (events.length > 0) { 243 | console.log(`Polling: Found ${events.length} ${eventType} events`); 244 | 245 | for (const event of events) { 246 | await catchEvent(event, 'Polling', undefined, eventType as any); 247 | } 248 | } 249 | } catch (eventError) { 250 | console.error(`Error querying for ${eventType} events:`, eventError); 251 | } 252 | } 253 | 254 | lastCheckedBlock = toBlock; 255 | 256 | if (toBlock < currentBlock) { 257 | console.log(`More blocks to process: ${toBlock + 1} to ${currentBlock}`); 258 | setTimeout(pollForEvents, 100); 259 | } 260 | } catch (error) { 261 | console.error('Error polling for events:', error); 262 | 263 | if (consecutiveErrors > 3) { 264 | console.warn(`Too many consecutive errors, skipping ahead from block ${lastCheckedBlock}`); 265 | if (currentBlock !== undefined) { 266 | lastCheckedBlock = Math.min(currentBlock, lastCheckedBlock + 100); 267 | } else { 268 | lastCheckedBlock += 100; 269 | } 270 | consecutiveErrors = 0; 271 | } else { 272 | consecutiveErrors++; 273 | } 274 | } 275 | } 276 | 277 | const pollingInterval = setInterval(pollForEvents, 10000); 278 | 279 | // Handle process termination 280 | process.on('SIGINT', () => { 281 | console.log('Shutting down event listener...'); 282 | clearInterval(pollingInterval); 283 | 284 | if (wsReconnectInterval) { 285 | clearTimeout(wsReconnectInterval); 286 | } 287 | 288 | if (wsProvider) { 289 | try { 290 | wsProvider.destroy(); 291 | } catch (e) { 292 | console.warn('Error destroying WebSocket provider:', e); 293 | } 294 | } 295 | 296 | process.exit(0); 297 | }); 298 | 299 | console.log(`Listening for events on contract ${CONTRACT_ADDRESS}...`); 300 | } catch (error: any) { 301 | console.error('Error starting event listener:', error); 302 | process.exit(1); 303 | } 304 | } 305 | 306 | // Function to test for past events 307 | export async function testPastEvents() { 308 | try { 309 | console.log('Testing for past RoundCreated events...'); 310 | 311 | const currentBlock = await httpProvider.getBlockNumber(); 312 | 313 | const fromBlock = Math.max(0, currentBlock - 10000); 314 | 315 | console.log(`Checking for events from block ${fromBlock} to ${currentBlock}`); 316 | 317 | const filter = httpContract.filters.RoundCreated(); 318 | 319 | const events = await httpContract.queryFilter(filter, fromBlock, currentBlock); 320 | 321 | console.log(`Found ${events.length} past RoundCreated events`); 322 | 323 | events.forEach((event, index) => { 324 | console.log(`Event ${index + 1}:`, { 325 | blockNumber: event.blockNumber, 326 | transactionHash: event.transactionHash, 327 | args: 'args' in event ? Array.from(event.args || []) : [] 328 | }); 329 | }); 330 | 331 | return events.length > 0; 332 | } catch (error) { 333 | console.error('Error testing for past events:', error); 334 | return false; 335 | } 336 | } 337 | 338 | 339 | -------------------------------------------------------------------------------- /src/hmesh-client.ts: -------------------------------------------------------------------------------- 1 | import { Transactions, Managers, Interfaces, Identities } from '@arkecosystem/crypto'; 2 | import { generateMnemonic } from 'bip39'; 3 | import axios from 'axios'; 4 | import axiosRetry from 'axios-retry'; 5 | import dotenv from 'dotenv'; 6 | import { decrypt, encrypt } from './utils'; 7 | import { HmeshTransaction } from './types' 8 | import { getUserInfoByHmeshAddress } from './db'; 9 | 10 | dotenv.config(); 11 | 12 | // HMESH node details 13 | const HMESH_DEVNET_NODE_URL = process.env.HMESH_DEVNET_NODE_URL!; 14 | const HMESH_BRIDGE_MNEMONIC = process.env.HMESH_BRIDGE_MNEMONIC!; 15 | const HMESH_NETWORK = process.env.HMESH_NETWORK || 'devnet'; 16 | 17 | // Configure the network 18 | Managers.configManager.setFromPreset(HMESH_NETWORK as any); 19 | Managers.configManager.setHeight(2); 20 | 21 | const hmeshClient = axios.create({ 22 | timeout:30000 23 | }); 24 | 25 | axiosRetry(hmeshClient, { 26 | retries: 3, 27 | retryDelay: (retryCount) => { 28 | console.log(`Retry attempt ${retryCount}`); 29 | return retryCount * 2000; 30 | }, 31 | retryCondition: (error) => { 32 | return axiosRetry.isNetworkOrIdempotentRequestError(error) || 33 | (error.response && error.response.status >= 500) || false; 34 | }, 35 | }); 36 | 37 | export function createHmeshBridgeClient() { 38 | // Create wallet from passphrase 39 | const bridgeAddress = Identities.Address.fromPassphrase(HMESH_BRIDGE_MNEMONIC); 40 | 41 | console.log(`HMESH Bridge wallet address: ${bridgeAddress}`); 42 | 43 | // Get the next nonce for the wallet 44 | async function getNextNonce(address: string): Promise { 45 | try { 46 | const response = await axios.get(`${HMESH_DEVNET_NODE_URL}/api/wallets/${address}`); 47 | return (parseInt(response.data.data.nonce) + 1).toString(); 48 | } catch (error) { 49 | console.error('Error fetching nonce:', error); 50 | throw new Error('Failed to get nonce'); 51 | } 52 | } 53 | 54 | // Send a transaction to the HMESH network 55 | async function sendTransaction(transaction: HmeshTransaction): Promise { 56 | try { 57 | console.log('Sending transaction...') 58 | const response = await hmeshClient.post(`${HMESH_DEVNET_NODE_URL}/api/transactions`, { 59 | transactions: [transaction], 60 | }); 61 | 62 | if (response.data.errors) { 63 | console.error('Transaction errors:', response.data.errors); 64 | throw new Error(`Transaction failed: ${JSON.stringify(response.data.errors)}`); 65 | } 66 | 67 | if (response.data.data && response.data.data.accept && response.data.data.accept.length > 0) { 68 | const txId = response.data.data.accept[0]; 69 | console.log(`Transaction ${txId} accepted by the network`); 70 | return txId; 71 | } 72 | 73 | const txId = transaction.id!; 74 | console.log(`Transaction ${txId} sent successfully`); 75 | return txId; 76 | } catch (error:any) { 77 | console.error('Error sending transaction:', error); 78 | 79 | if (error.response) { 80 | console.error('Response status:', error.response.status); 81 | console.error('Response data:', JSON.stringify(error.response.data, null, 2)); 82 | } 83 | throw error; 84 | } 85 | } 86 | 87 | // Mint tokens on HMESH blockchain 88 | async function mintTokens(hmeshClientAddress: string, amount: bigint): Promise { 89 | try { 90 | console.log(`Minting ${amount} tokens for ${hmeshClientAddress}`); 91 | const nonce = await getNextNonce(bridgeAddress); 92 | 93 | const amountWithDecimals = (amount / BigInt(10000000000)).toString(); 94 | console.log(`Converted native HMESH coinst with 8 decimals: ${amountWithDecimals}`); 95 | 96 | const transaction:HmeshTransaction = Transactions.BuilderFactory 97 | .transfer() 98 | .version(2) 99 | .nonce(nonce) 100 | .recipientId(hmeshClientAddress) 101 | .amount(amountWithDecimals) 102 | // .amount("100000000") 103 | .vendorField(JSON.stringify({ 104 | action: 'mint', 105 | token: 'HMESH' 106 | })) 107 | .fee('10000000') 108 | .typeGroup(1) 109 | .sign(HMESH_BRIDGE_MNEMONIC) 110 | .build(); 111 | 112 | console.log('Transaction details:', transaction.data); 113 | 114 | const txId = await sendTransaction(transaction.data); 115 | console.log(`Successfully minted ${amount} HMESH tokens. Transaction ID: ${txId}`); 116 | return txId; 117 | } catch (error:any) { 118 | if (error.message?.includes('nonce')) { 119 | console.error('Nonce error detected, retrying with updated nonce...'); 120 | } 121 | console.error('Error minting tokens:', error); 122 | throw new Error(`Failed to mint tokens: ${error.message}`); 123 | } 124 | } 125 | 126 | // Burn tokens on HMESH blockchain 127 | async function burnTokens(HMESH_CLIENT_MNEMONIC: string, amount: bigint): Promise { 128 | try { 129 | const hmeshClientAddress = Identities.Address.fromPassphrase(HMESH_CLIENT_MNEMONIC); 130 | console.log(`Burning ${amount} tokens for ${hmeshClientAddress}`); 131 | 132 | const nonce = await getNextNonce(hmeshClientAddress); 133 | 134 | const amountWithDecimals = (amount / BigInt(10000000000)).toString(); 135 | console.log(`Converted native HMESH coinst with 8 decimals: ${amountWithDecimals}`); 136 | 137 | const transaction: HmeshTransaction = Transactions.BuilderFactory 138 | .transfer() 139 | .version(2) 140 | .nonce(nonce) 141 | .recipientId(bridgeAddress) 142 | .amount(amountWithDecimals.toString()) 143 | .vendorField(JSON.stringify({ 144 | action: 'burn', 145 | token: 'HMESH', 146 | })) 147 | .fee('10000000') 148 | .typeGroup(1) 149 | .sign(HMESH_CLIENT_MNEMONIC) 150 | .build(); 151 | 152 | console.log('Transaction details:', transaction.data); 153 | 154 | const txId = await sendTransaction(transaction.data); 155 | console.log(`Successfully burned ${amount} HMESH tokens. Transaction ID: ${txId}`) 156 | return txId; 157 | } catch (error) { 158 | console.error('Error burning tokens:', error); 159 | throw error; 160 | } 161 | } 162 | 163 | // Submit a vote on HMESH blockchain 164 | async function submitVote(hmeshClientAddress:string, delegatePublicKey:string):Promise { 165 | try { 166 | console.log(`Submitting vote for ${delegatePublicKey} from ${hmeshClientAddress}`); 167 | 168 | const hmesh_client_walletInfo = await getUserInfoByHmeshAddress(hmeshClientAddress); 169 | const hmesh_client_mnemonic = hmesh_client_walletInfo?.hmeshInfo.hmeshMnemonic!; 170 | 171 | const nonce = await getNextNonce(decrypt(hmesh_client_mnemonic)); 172 | 173 | const transaction:HmeshTransaction = Transactions.BuilderFactory 174 | .vote() 175 | .version(2) 176 | .nonce(nonce) 177 | .votesAsset([`+${delegatePublicKey}`]) 178 | .fee('10000000') 179 | .typeGroup(1) 180 | .sign(hmesh_client_mnemonic) 181 | .build(); 182 | 183 | console.log('Transaction details:', transaction.data); 184 | const txId = await sendTransaction(transaction.data); 185 | console.log(`Successfully submitted vote for ${delegatePublicKey}. Transaction ID: ${txId}`); 186 | return txId; 187 | 188 | } catch(error) { 189 | console.error('Error submitting vote transactions:', error); 190 | throw error; 191 | } 192 | } 193 | 194 | // Submit an unvote on HMESH blockchain 195 | async function submitUnvote(hmeshClientAddress:string, delegatePublicKey:string):Promise { 196 | try { 197 | console.log(`Submitting unvote for ${delegatePublicKey} from ${hmeshClientAddress}`); 198 | 199 | const hmesh_client_walletInfo = await getUserInfoByHmeshAddress(hmeshClientAddress); 200 | const hmesh_client_mnemonic = hmesh_client_walletInfo?.hmeshInfo.hmeshMnemonic!; 201 | 202 | const nonce = await getNextNonce(decrypt(hmesh_client_mnemonic)); 203 | 204 | const transaction:HmeshTransaction = Transactions.BuilderFactory 205 | .vote() 206 | .version(2) 207 | .nonce(nonce) 208 | .votesAsset([`-${delegatePublicKey}`]) 209 | .fee('10000000') 210 | .typeGroup(1) 211 | .sign(hmesh_client_mnemonic) 212 | .build(); 213 | 214 | console.log('Transaction details:', transaction.data); 215 | const txId = await sendTransaction(transaction.data); 216 | console.log(`Successfully submitted unvote for ${delegatePublicKey}. Transaction ID: ${txId}`); 217 | return txId; 218 | } catch(error) { 219 | console.error('Error submitting unvote transactions:', error); 220 | throw error; 221 | } 222 | } 223 | 224 | // Get the balance of a HMESH address 225 | async function getBalance(hmeshClientAddress:string):Promise { 226 | try { 227 | try{ 228 | const response = await axios.get(`${HMESH_DEVNET_NODE_URL}/api/wallets/${hmeshClientAddress}`); 229 | return response.data.balance; 230 | } catch(apiError) { 231 | console.error('Error fetching balance from HMESH API:', apiError); 232 | throw apiError; 233 | } 234 | } catch(error:any) { 235 | console.error('Error getting balance:', error); 236 | throw new Error(`Failed to get balance for ${hmeshClientAddress}: ${error.message}`); 237 | } 238 | } 239 | 240 | return { 241 | mintTokens, 242 | burnTokens, 243 | submitVote, 244 | submitUnvote, 245 | getBalance 246 | }; 247 | } 248 | 249 | export function createHMESHWallet() { 250 | const mnemonic: string = generateMnemonic(256); 251 | 252 | const publicKey = Identities.PublicKey.fromPassphrase(mnemonic); 253 | const privateKey = Identities.PrivateKey.fromPassphrase(mnemonic); 254 | const address = Identities.Address.fromPassphrase(mnemonic); 255 | 256 | const encryptedMnemonic = encrypt(mnemonic); 257 | const encryptedPrivateKey = encrypt(privateKey); 258 | 259 | return { encryptedMnemonic, publicKey, encryptedPrivateKey, address } 260 | } 261 | 262 | export async function getCurrentVotes(hmeshClientAddress: string): Promise> { 268 | try { 269 | const response = await axios.get(`${HMESH_DEVNET_NODE_URL}/api/wallets/${hmeshClientAddress}/votes`); 270 | 271 | if (!response.data || !response.data.data) { 272 | return []; 273 | } 274 | 275 | return response.data.data.map((vote: any) => ({ 276 | delegateAddress: vote.delegate.address, 277 | delegatePublicKey: vote.delegate.publicKey, 278 | delegateName: vote.delegate.username || 'Unknown', 279 | voteWeight: vote.balance || '0' 280 | })); 281 | } catch (error) { 282 | console.error('Error fetching votes:', error); 283 | return []; 284 | } 285 | } 286 | 287 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | /* Visit https://aka.ms/tsconfig to read more about this file */ 4 | 5 | /* Projects */ 6 | // "incremental": true, /* Save .tsbuildinfo files to allow for incremental compilation of projects. */ 7 | // "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */ 8 | // "tsBuildInfoFile": "./.tsbuildinfo", /* Specify the path to .tsbuildinfo incremental compilation file. */ 9 | // "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects. */ 10 | // "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */ 11 | // "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */ 12 | 13 | /* Language and Environment */ 14 | "target": "es2016", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */ 15 | // "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */ 16 | // "jsx": "preserve", /* Specify what JSX code is generated. */ 17 | // "libReplacement": true, /* Enable lib replacement. */ 18 | // "experimentalDecorators": true, /* Enable experimental support for legacy experimental decorators. */ 19 | // "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */ 20 | // "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h'. */ 21 | // "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */ 22 | // "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using 'jsx: react-jsx*'. */ 23 | // "reactNamespace": "", /* Specify the object invoked for 'createElement'. This only applies when targeting 'react' JSX emit. */ 24 | // "noLib": true, /* Disable including any library files, including the default lib.d.ts. */ 25 | // "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */ 26 | // "moduleDetection": "auto", /* Control what method is used to detect module-format JS files. */ 27 | 28 | /* Modules */ 29 | "module": "NodeNext", /* Specify what module code is generated. */ 30 | // "rootDir": "./", /* Specify the root folder within your source files. */ 31 | "moduleResolution": "nodenext", /* Specify how TypeScript looks up a file from a given module specifier. */ 32 | // "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */ 33 | // "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */ 34 | // "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */ 35 | // "typeRoots": [], /* Specify multiple folders that act like './node_modules/@types'. */ 36 | // "types": [], /* Specify type package names to be included without being referenced in a source file. */ 37 | // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ 38 | // "moduleSuffixes": [], /* List of file name suffixes to search when resolving a module. */ 39 | // "allowImportingTsExtensions": true, /* Allow imports to include TypeScript file extensions. Requires '--moduleResolution bundler' and either '--noEmit' or '--emitDeclarationOnly' to be set. */ 40 | // "rewriteRelativeImportExtensions": true, /* Rewrite '.ts', '.tsx', '.mts', and '.cts' file extensions in relative import paths to their JavaScript equivalent in output files. */ 41 | // "resolvePackageJsonExports": true, /* Use the package.json 'exports' field when resolving package imports. */ 42 | // "resolvePackageJsonImports": true, /* Use the package.json 'imports' field when resolving imports. */ 43 | // "customConditions": [], /* Conditions to set in addition to the resolver-specific defaults when resolving imports. */ 44 | // "noUncheckedSideEffectImports": true, /* Check side effect imports. */ 45 | // "resolveJsonModule": true, /* Enable importing .json files. */ 46 | // "allowArbitraryExtensions": true, /* Enable importing files with any extension, provided a declaration file is present. */ 47 | // "noResolve": true, /* Disallow 'import's, 'require's or ''s from expanding the number of files TypeScript should add to a project. */ 48 | 49 | /* JavaScript Support */ 50 | // "allowJs": true, /* Allow JavaScript files to be a part of your program. Use the 'checkJS' option to get errors from these files. */ 51 | // "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */ 52 | // "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from 'node_modules'. Only applicable with 'allowJs'. */ 53 | 54 | /* Emit */ 55 | // "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */ 56 | // "declarationMap": true, /* Create sourcemaps for d.ts files. */ 57 | // "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */ 58 | // "sourceMap": true, /* Create source map files for emitted JavaScript files. */ 59 | // "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */ 60 | // "noEmit": true, /* Disable emitting files from a compilation. */ 61 | // "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If 'declaration' is true, also designates a file that bundles all .d.ts output. */ 62 | // "outDir": "./", /* Specify an output folder for all emitted files. */ 63 | // "removeComments": true, /* Disable emitting comments. */ 64 | // "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */ 65 | // "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */ 66 | // "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */ 67 | // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ 68 | // "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */ 69 | // "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */ 70 | // "newLine": "crlf", /* Set the newline character for emitting files. */ 71 | // "stripInternal": true, /* Disable emitting declarations that have '@internal' in their JSDoc comments. */ 72 | // "noEmitHelpers": true, /* Disable generating custom helper functions like '__extends' in compiled output. */ 73 | // "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */ 74 | // "preserveConstEnums": true, /* Disable erasing 'const enum' declarations in generated code. */ 75 | // "declarationDir": "./", /* Specify the output directory for generated declaration files. */ 76 | 77 | /* Interop Constraints */ 78 | // "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */ 79 | // "verbatimModuleSyntax": true, /* Do not transform or elide any imports or exports not marked as type-only, ensuring they are written in the output file's format based on the 'module' setting. */ 80 | // "isolatedDeclarations": true, /* Require sufficient annotation on exports so other tools can trivially generate declaration files. */ 81 | // "erasableSyntaxOnly": true, /* Do not allow runtime constructs that are not part of ECMAScript. */ 82 | // "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */ 83 | "esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */ 84 | // "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */ 85 | "forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */ 86 | 87 | /* Type Checking */ 88 | "strict": true, /* Enable all strict type-checking options. */ 89 | // "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied 'any' type. */ 90 | // "strictNullChecks": true, /* When type checking, take into account 'null' and 'undefined'. */ 91 | // "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */ 92 | // "strictBindCallApply": true, /* Check that the arguments for 'bind', 'call', and 'apply' methods match the original function. */ 93 | // "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */ 94 | // "strictBuiltinIteratorReturn": true, /* Built-in iterators are instantiated with a 'TReturn' type of 'undefined' instead of 'any'. */ 95 | // "noImplicitThis": true, /* Enable error reporting when 'this' is given the type 'any'. */ 96 | // "useUnknownInCatchVariables": true, /* Default catch clause variables as 'unknown' instead of 'any'. */ 97 | // "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */ 98 | // "noUnusedLocals": true, /* Enable error reporting when local variables aren't read. */ 99 | // "noUnusedParameters": true, /* Raise an error when a function parameter isn't read. */ 100 | // "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */ 101 | // "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */ 102 | // "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */ 103 | // "noUncheckedIndexedAccess": true, /* Add 'undefined' to a type when accessed using an index. */ 104 | // "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */ 105 | // "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type. */ 106 | // "allowUnusedLabels": true, /* Disable error reporting for unused labels. */ 107 | // "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */ 108 | 109 | /* Completeness */ 110 | // "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */ 111 | "skipLibCheck": true /* Skip type checking all .d.ts files. */ 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /src/db.ts: -------------------------------------------------------------------------------- 1 | import { Pool } from 'pg'; 2 | import dotenv from 'dotenv'; 3 | import { createHMESHWallet } from './hmesh-client'; 4 | import { ArbitrumEventData, UserInfoRow, EventQueueRow } from './types'; 5 | 6 | dotenv.config(); 7 | 8 | const pool = new Pool({ 9 | connectionString: process.env.DB_URL, 10 | }); 11 | 12 | /** 13 | * Create database tables if they don't exist 14 | */ 15 | export async function initializeDatabase(): Promise { 16 | const client = await pool.connect(); 17 | try { 18 | // Create the event_queue table 19 | await client.query(` 20 | CREATE TABLE IF NOT EXISTS event_queue ( 21 | event_id VARCHAR(255) PRIMARY KEY, 22 | transaction_hash VARCHAR(255) NOT NULL, 23 | block_number BIGINT NOT NULL, 24 | event_type VARCHAR(50) NOT NULL, 25 | args JSONB NOT NULL, 26 | user_info JSONB, 27 | user_address VARCHAR(255), 28 | processed BOOLEAN DEFAULT FALSE, 29 | created_at TIMESTAMP NOT NULL DEFAULT NOW(), 30 | processed_at TIMESTAMP 31 | ); 32 | 33 | CREATE INDEX IF NOT EXISTS idx_event_queue_processed ON event_queue(processed); 34 | CREATE INDEX IF NOT EXISTS idx_event_queue_created_at ON event_queue(created_at); 35 | CREATE INDEX IF NOT EXISTS idx_event_queue_user_address ON event_queue(user_address); 36 | `); 37 | 38 | // Create the user_info table with JSONB fields to store pre-fetched data 39 | await client.query(` 40 | CREATE TABLE IF NOT EXISTS user_info ( 41 | eth_address VARCHAR(255) PRIMARY KEY, 42 | hmesh_info JSONB NOT NULL, 43 | rounds JSONB NOT NULL DEFAULT '[]'::jsonb, 44 | purchase_details JSONB NOT NULL DEFAULT '{}'::jsonb, 45 | last_updated TIMESTAMP NOT NULL, 46 | created_at TIMESTAMP NOT NULL 47 | ); 48 | 49 | CREATE INDEX IF NOT EXISTS idx_user_info_hmesh_address ON user_info((hmesh_info->>'hmeshAddress')); 50 | `); 51 | 52 | console.log('Database tables initialized successfully'); 53 | } catch (error) { 54 | console.error('Error initializing database tables:', error); 55 | throw error; 56 | } finally { 57 | client.release(); 58 | } 59 | } 60 | 61 | /** 62 | * Save an event to the queue 63 | */ 64 | export async function saveEventToQueue(eventData: ArbitrumEventData): Promise { 65 | const client = await pool.connect(); 66 | try { 67 | await client.query('BEGIN'); 68 | 69 | let userAddress = eventData.userInfo?.address || (eventData.args && eventData.args.length > 0 ? eventData.args[0] : null); 70 | 71 | const processedArgs = replaceBigInts(eventData.args); 72 | const processedUserInfo = replaceBigInts(eventData.userInfo || {}); 73 | 74 | const insertEventQuery = ` 75 | INSERT INTO event_queue ( 76 | event_id, 77 | transaction_hash, 78 | block_number, 79 | event_type, 80 | args, 81 | user_info, 82 | user_address, 83 | processed, 84 | created_at 85 | ) VALUES ($1, $2, $3, $4, $5, $6, $7, FALSE, NOW()) 86 | ON CONFLICT (event_id) DO NOTHING 87 | `; 88 | 89 | const eventResult = await client.query(insertEventQuery, [ 90 | eventData.eventId, 91 | eventData.transactionHash, 92 | eventData.blockNumber, 93 | eventData.event, 94 | JSON.stringify(processedArgs), 95 | JSON.stringify(processedUserInfo), 96 | userAddress 97 | ]); 98 | 99 | if (eventResult.rowCount! > 0 && userAddress) { 100 | const userCheckQuery = `SELECT * FROM user_info WHERE eth_address = $1`; 101 | const userResult = await client.query(userCheckQuery, [userAddress]); 102 | 103 | if (userResult.rowCount === 0) { 104 | const hmeshWallet = createHMESHWallet(); 105 | const hmeshInfo = { 106 | hmeshMnemonic: hmeshWallet.encryptedMnemonic, 107 | hmeshPublicKey: hmeshWallet.publicKey, 108 | hmeshPrivateKey: hmeshWallet.encryptedPrivateKey, 109 | hmeshAddress: hmeshWallet.address 110 | }; 111 | 112 | const insertUserQuery = ` 113 | INSERT INTO user_info ( 114 | eth_address, 115 | hmesh_info, 116 | rounds, 117 | purchase_details, 118 | last_updated, 119 | created_at 120 | ) VALUES ($1, $2, $3, $4, NOW(), NOW()) 121 | `; 122 | 123 | await client.query(insertUserQuery, [ 124 | userAddress, 125 | JSON.stringify(hmeshInfo), 126 | JSON.stringify(replaceBigInts(eventData.userInfo!.rounds || [])), 127 | JSON.stringify(replaceBigInts(eventData.userInfo!.purchaseDetails || {})) 128 | ]); 129 | 130 | console.log(`Created new user with ETH address ${userAddress} and HMESH address ${hmeshWallet.address}`); 131 | } else { 132 | const updateUserQuery = ` 133 | UPDATE user_info 134 | SET 135 | rounds = $2, 136 | purchase_details = $3, 137 | last_updated = NOW() 138 | WHERE eth_address = $1 139 | `; 140 | 141 | await client.query(updateUserQuery, [ 142 | userAddress, 143 | JSON.stringify(replaceBigInts(eventData.userInfo!.rounds || [])), 144 | JSON.stringify(replaceBigInts(eventData.userInfo!.purchaseDetails || {})) 145 | ]); 146 | 147 | console.log(`Updated user info for ETH address ${userAddress}`); 148 | } 149 | } 150 | 151 | await client.query('COMMIT'); 152 | console.log(`Event ${eventData.eventId} saved to queue`); 153 | } catch (error) { 154 | await client.query('ROLLBACK'); 155 | console.error('Error saving event to queue:', error); 156 | throw error; 157 | } finally { 158 | client.release(); 159 | } 160 | } 161 | 162 | /** 163 | * mark event as processed 164 | */ 165 | export async function markEventAsProcessed(eventId: string): Promise { 166 | const client = await pool.connect(); 167 | try { 168 | const updateQuery = ` 169 | UPDATE event_queue 170 | SET processed = true, processed_at = NOW() 171 | WHERE event_id = $1 172 | `; 173 | 174 | await client.query(updateQuery, [eventId]); 175 | console.log(`Event ${eventId} marked as processed`); 176 | } catch (error) { 177 | console.error('Error marking event as processed:', error); 178 | throw error; 179 | } finally { 180 | client.release(); 181 | } 182 | } 183 | 184 | /** 185 | * Get unprocessed events from the queue 186 | */ 187 | export async function getUnprocessedEvents(limit: number = 10): Promise { 188 | const client = await pool.connect(); 189 | try { 190 | const query = ` 191 | SELECT 192 | event_id as "eventId", 193 | transaction_hash as "transactionHash", 194 | block_number as "blockNumber", 195 | event_type as "event", 196 | args, 197 | user_info as "userInfo", 198 | user_address as "userAddress", 199 | processed, 200 | created_at as "createdAt" 201 | FROM event_queue 202 | WHERE processed = false 203 | ORDER BY created_at ASC 204 | LIMIT $1 205 | `; 206 | 207 | const result = await client.query(query, [limit]); 208 | 209 | return result.rows.map(row => { 210 | let parsedArgs = []; 211 | let parsedUserInfo: any = { 212 | address: row.userAddress || '', 213 | rounds: [], 214 | purchaseDetails: {} 215 | }; 216 | 217 | try { 218 | parsedArgs = typeof row.args === 'string' ? JSON.parse(row.args) : row.args; 219 | } catch (e: any) { 220 | console.warn(`Failed to parse args for event ${row.eventId}: ${e.message}`); 221 | parsedArgs = typeof row.args === 'object' ? row.args : []; 222 | } 223 | 224 | try { 225 | const tempUserInfo = typeof row.userInfo === 'string' ? JSON.parse(row.userInfo) : row.userInfo; 226 | 227 | parsedUserInfo = { 228 | address: tempUserInfo.address || row.userAddress || '', 229 | rounds: tempUserInfo.rounds || [], 230 | purchaseDetails: tempUserInfo.purchaseDetails || {} 231 | }; 232 | } catch (e: any) { 233 | console.warn(`Failed to parse userInfo for event ${row.eventId}: ${e.message}`); 234 | } 235 | 236 | return { 237 | eventId: row.eventId, 238 | transactionHash: row.transactionHash, 239 | blockNumber: typeof row.blockNumber === 'string' ? parseInt(row.blockNumber, 10) : row.blockNumber, 240 | event: row.event as 'RoundCreated' | 'TokensBought' | 'TokensClaimed', 241 | args: parsedArgs, 242 | userInfo: parsedUserInfo, 243 | processed: row.processed, 244 | createdAt: row.createdAt 245 | }; 246 | }); 247 | } catch (error) { 248 | console.error('Error getting unprocessed events:', error); 249 | throw error; 250 | } finally { 251 | client.release(); 252 | } 253 | } 254 | 255 | /** 256 | * Get user information by ETH address 257 | */ 258 | export async function getUserInfoByEthAddress(ethAddress: string): Promise { 259 | const client = await pool.connect(); 260 | try { 261 | const query = ` 262 | SELECT 263 | eth_address as "ethAddress", 264 | hmesh_info as "hmeshInfo", 265 | rounds, 266 | purchase_details as "purchaseDetails", 267 | last_updated as "lastUpdated", 268 | created_at as "createdAt" 269 | FROM user_info 270 | WHERE eth_address = $1 271 | `; 272 | 273 | const result = await client.query(query, [ethAddress]); 274 | 275 | if (result.rowCount === 0) { 276 | return null; 277 | } 278 | 279 | const user = result.rows[0]; 280 | 281 | let hmeshInfo: any = { 282 | hmeshMnemonic: '', 283 | hmeshPublicKey: '', 284 | hmeshPrivateKey: '', 285 | hmeshAddress: '' 286 | }; 287 | 288 | let rounds = []; 289 | let purchaseDetails = {}; 290 | 291 | try { 292 | const parsedHmeshInfo = typeof user.hmeshInfo === 'string' ? JSON.parse(user.hmeshInfo) : user.hmeshInfo; 293 | hmeshInfo = { 294 | hmeshMnemonic: parsedHmeshInfo.hmeshMnemonic || '', 295 | hmeshPublicKey: parsedHmeshInfo.hmeshPublicKey || '', 296 | hmeshPrivateKey: parsedHmeshInfo.hmeshPrivateKey || '', 297 | hmeshAddress: parsedHmeshInfo.hmeshAddress || '' 298 | }; 299 | } catch (e: any) { 300 | console.warn(`Failed to parse hmeshInfo for user ${ethAddress}: ${e.message}`); 301 | } 302 | 303 | try { 304 | rounds = typeof user.rounds === 'string' ? JSON.parse(user.rounds) : user.rounds; 305 | } catch (e: any) { 306 | console.warn(`Failed to parse rounds for user ${ethAddress}: ${e.message}`); 307 | rounds = typeof user.rounds === 'object' ? user.rounds : []; 308 | } 309 | 310 | try { 311 | purchaseDetails = typeof user.purchaseDetails === 'string' 312 | ? JSON.parse(user.purchaseDetails) 313 | : user.purchaseDetails; 314 | } catch (e: any) { 315 | console.warn(`Failed to parse purchaseDetails for user ${ethAddress}: ${e.message}`); 316 | purchaseDetails = typeof user.purchaseDetails === 'object' ? user.purchaseDetails : {}; 317 | } 318 | 319 | return { 320 | ...user, 321 | hmeshInfo, 322 | rounds, 323 | purchaseDetails 324 | }; 325 | } catch (error) { 326 | console.error(`Error getting user info for ${ethAddress}:`, error); 327 | throw error; 328 | } finally { 329 | client.release(); 330 | } 331 | } 332 | 333 | /** 334 | * Get user information by HMESH address 335 | */ 336 | export async function getUserInfoByHmeshAddress(hmeshAddress: string): Promise { 337 | const client = await pool.connect(); 338 | try { 339 | const query = ` 340 | SELECT 341 | eth_address as "ethAddress", 342 | hmesh_info as "hmeshInfo", 343 | rounds, 344 | purchase_details as "purchaseDetails", 345 | last_updated as "lastUpdated", 346 | created_at as "createdAt" 347 | FROM user_info 348 | WHERE (hmesh_info->>'hmeshAddress') = $1 349 | `; 350 | 351 | const result = await client.query(query, [hmeshAddress]); 352 | 353 | if (result.rowCount === 0) { 354 | return null; 355 | } 356 | 357 | const user = result.rows[0]; 358 | 359 | let hmeshInfo: any = { 360 | hmeshMnemonic: '', 361 | hmeshPublicKey: '', 362 | hmeshPrivateKey: '', 363 | hmeshAddress: hmeshAddress || '' 364 | }; 365 | 366 | let rounds = []; 367 | let purchaseDetails = {}; 368 | 369 | try { 370 | const parsedHmeshInfo = typeof user.hmeshInfo === 'string' ? JSON.parse(user.hmeshInfo) : user.hmeshInfo; 371 | hmeshInfo = { 372 | hmeshMnemonic: parsedHmeshInfo.hmeshMnemonic || '', 373 | hmeshPublicKey: parsedHmeshInfo.hmeshPublicKey || '', 374 | hmeshPrivateKey: parsedHmeshInfo.hmeshPrivateKey || '', 375 | hmeshAddress: parsedHmeshInfo.hmeshAddress || hmeshAddress || '' 376 | }; 377 | } catch (e: any) { 378 | console.warn(`Failed to parse hmeshInfo for HMESH address ${hmeshAddress}: ${e.message}`); 379 | } 380 | 381 | try { 382 | rounds = typeof user.rounds === 'string' ? JSON.parse(user.rounds) : user.rounds; 383 | } catch (e: any) { 384 | console.warn(`Failed to parse rounds for HMESH address ${hmeshAddress}: ${e.message}`); 385 | rounds = typeof user.rounds === 'object' ? user.rounds : []; 386 | } 387 | 388 | try { 389 | purchaseDetails = typeof user.purchaseDetails === 'string' 390 | ? JSON.parse(user.purchaseDetails) 391 | : user.purchaseDetails; 392 | } catch (e: any) { 393 | console.warn(`Failed to parse purchaseDetails for HMESH address ${hmeshAddress}: ${e.message}`); 394 | purchaseDetails = typeof user.purchaseDetails === 'object' ? user.purchaseDetails : {}; 395 | } 396 | 397 | return { 398 | ...user, 399 | hmeshInfo, 400 | rounds, 401 | purchaseDetails 402 | }; 403 | } catch (error) { 404 | console.error(`Error getting user info for HMESH address ${hmeshAddress}:`, error); 405 | throw error; 406 | } finally { 407 | client.release(); 408 | } 409 | } 410 | 411 | /** 412 | * Get events for a specific user by ETH address 413 | */ 414 | export async function getEventsByUserAddress( 415 | userAddress: string, 416 | limit: number = 100 417 | ): Promise { 418 | const client = await pool.connect(); 419 | try { 420 | const query = ` 421 | SELECT 422 | event_id as "eventId", 423 | transaction_hash as "transactionHash", 424 | block_number as "blockNumber", 425 | event_type as "event", 426 | args, 427 | user_info as "userInfo", 428 | user_address as "userAddress", 429 | processed, 430 | created_at as "createdAt", 431 | processed_at as "processedAt" 432 | FROM event_queue 433 | WHERE user_address = $1 434 | ORDER BY created_at DESC 435 | LIMIT $2 436 | `; 437 | 438 | const result = await client.query(query, [userAddress, limit]); 439 | 440 | return result.rows.map(row => { 441 | let parsedArgs = []; 442 | let parsedUserInfo: any = { 443 | address: row.userAddress || '', 444 | rounds: [], 445 | purchaseDetails: {} 446 | }; 447 | 448 | try { 449 | parsedArgs = typeof row.args === 'string' ? JSON.parse(row.args) : row.args; 450 | } catch (e: any) { 451 | console.warn(`Failed to parse args for event ${row.eventId}: ${e.message}`); 452 | parsedArgs = typeof row.args === 'object' ? row.args : []; 453 | } 454 | 455 | try { 456 | const tempUserInfo = typeof row.userInfo === 'string' ? JSON.parse(row.userInfo) : row.userInfo; 457 | 458 | parsedUserInfo = { 459 | address: tempUserInfo.address || row.userAddress || '', 460 | rounds: tempUserInfo.rounds || [], 461 | purchaseDetails: tempUserInfo.purchaseDetails || {} 462 | }; 463 | } catch (e: any) { 464 | console.warn(`Failed to parse userInfo for event ${row.eventId}: ${e.message}`); 465 | } 466 | 467 | return { 468 | eventId: row.eventId, 469 | transactionHash: row.transactionHash, 470 | blockNumber: typeof row.blockNumber === 'string' ? parseInt(row.blockNumber, 10) : row.blockNumber, 471 | event: row.event as 'RoundCreated' | 'TokensBought' | 'TokensClaimed', 472 | args: parsedArgs, 473 | userInfo: parsedUserInfo, 474 | processed: row.processed, 475 | createdAt: row.createdAt, 476 | processedAt: row.processedAt! 477 | }; 478 | }); 479 | } catch (error) { 480 | console.error(`Error getting events for user ${userAddress}:`, error); 481 | throw error; 482 | } finally { 483 | client.release(); 484 | } 485 | } 486 | 487 | function replaceBigInts(obj: any): any { 488 | if (obj === null || obj === undefined) { 489 | return obj; 490 | } 491 | 492 | if (typeof obj === 'bigint') { 493 | return obj.toString(); 494 | } 495 | 496 | if (Array.isArray(obj)) { 497 | return obj.map(replaceBigInts); 498 | } 499 | 500 | if (typeof obj === 'object') { 501 | const result: any = {}; 502 | for (const key in obj) { 503 | result[key] = replaceBigInts(obj[key]); 504 | } 505 | return result; 506 | } 507 | 508 | return obj; 509 | } 510 | 511 | /** 512 | * Reset database by dropping and recreating tables 513 | */ 514 | export async function resetDatabase(): Promise { 515 | const client = await pool.connect(); 516 | try { 517 | await client.query('BEGIN'); 518 | 519 | await client.query(` 520 | DROP TABLE IF EXISTS event_queue CASCADE; 521 | DROP TABLE IF EXISTS user_info CASCADE; 522 | `); 523 | 524 | console.log('Existing tables dropped successfully'); 525 | 526 | await client.query('COMMIT'); 527 | console.log('Database reset completed successfully'); 528 | } catch (error) { 529 | console.error('Error in resetDatabase(), rolling back:', error); 530 | try { 531 | await client.query('ROLLBACK'); 532 | console.log('Rollback completed'); 533 | } catch (rollbackError) { 534 | console.error('Error during rollback:', rollbackError); 535 | } 536 | throw error; 537 | } finally { 538 | client.release(); 539 | } 540 | } 541 | -------------------------------------------------------------------------------- /src/contract_abi/presale.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "inputs": [ 4 | { 5 | "internalType": "address", 6 | "name": "tokenAddress_", 7 | "type": "address" 8 | } 9 | ], 10 | "stateMutability": "nonpayable", 11 | "type": "constructor" 12 | }, 13 | { 14 | "inputs": [], 15 | "name": "EnforcedPause", 16 | "type": "error" 17 | }, 18 | { 19 | "inputs": [], 20 | "name": "ExpectedPause", 21 | "type": "error" 22 | }, 23 | { 24 | "inputs": [], 25 | "name": "NotOwner", 26 | "type": "error" 27 | }, 28 | { 29 | "inputs": [], 30 | "name": "ReentrancyGuardReentrantCall", 31 | "type": "error" 32 | }, 33 | { 34 | "inputs": [ 35 | { 36 | "internalType": "address", 37 | "name": "token", 38 | "type": "address" 39 | } 40 | ], 41 | "name": "SafeERC20FailedOperation", 42 | "type": "error" 43 | }, 44 | { 45 | "anonymous": false, 46 | "inputs": [ 47 | { 48 | "indexed": true, 49 | "internalType": "address", 50 | "name": "caller", 51 | "type": "address" 52 | }, 53 | { 54 | "indexed": true, 55 | "internalType": "uint256", 56 | "name": "fundsAmount", 57 | "type": "uint256" 58 | }, 59 | { 60 | "indexed": false, 61 | "internalType": "uint256", 62 | "name": "timestamp", 63 | "type": "uint256" 64 | } 65 | ], 66 | "name": "FundsRefunded", 67 | "type": "event" 68 | }, 69 | { 70 | "anonymous": false, 71 | "inputs": [ 72 | { 73 | "indexed": true, 74 | "internalType": "address", 75 | "name": "wallet", 76 | "type": "address" 77 | }, 78 | { 79 | "indexed": false, 80 | "internalType": "uint256", 81 | "name": "usdtBalance", 82 | "type": "uint256" 83 | }, 84 | { 85 | "indexed": false, 86 | "internalType": "uint256", 87 | "name": "usdcBalance", 88 | "type": "uint256" 89 | }, 90 | { 91 | "indexed": false, 92 | "internalType": "uint256", 93 | "name": "daiBalance", 94 | "type": "uint256" 95 | }, 96 | { 97 | "indexed": false, 98 | "internalType": "uint256", 99 | "name": "timestamp", 100 | "type": "uint256" 101 | } 102 | ], 103 | "name": "FundsWithdrawn", 104 | "type": "event" 105 | }, 106 | { 107 | "anonymous": false, 108 | "inputs": [ 109 | { 110 | "indexed": true, 111 | "internalType": "address", 112 | "name": "investor", 113 | "type": "address" 114 | }, 115 | { 116 | "indexed": true, 117 | "internalType": "uint8", 118 | "name": "roundId", 119 | "type": "uint8" 120 | }, 121 | { 122 | "indexed": false, 123 | "internalType": "uint256", 124 | "name": "tokenAmount", 125 | "type": "uint256" 126 | }, 127 | { 128 | "indexed": false, 129 | "internalType": "uint256", 130 | "name": "coinAmount", 131 | "type": "uint256" 132 | } 133 | ], 134 | "name": "InvestorRecordUpdated", 135 | "type": "event" 136 | }, 137 | { 138 | "anonymous": false, 139 | "inputs": [ 140 | { 141 | "indexed": true, 142 | "internalType": "address", 143 | "name": "previousOwner", 144 | "type": "address" 145 | }, 146 | { 147 | "indexed": true, 148 | "internalType": "address", 149 | "name": "newOwner", 150 | "type": "address" 151 | } 152 | ], 153 | "name": "OwnershipTransferred", 154 | "type": "event" 155 | }, 156 | { 157 | "anonymous": false, 158 | "inputs": [ 159 | { 160 | "indexed": false, 161 | "internalType": "address", 162 | "name": "account", 163 | "type": "address" 164 | } 165 | ], 166 | "name": "Paused", 167 | "type": "event" 168 | }, 169 | { 170 | "anonymous": false, 171 | "inputs": [ 172 | { 173 | "indexed": true, 174 | "internalType": "uint8", 175 | "name": "roundId", 176 | "type": "uint8" 177 | } 178 | ], 179 | "name": "RefundComplete", 180 | "type": "event" 181 | }, 182 | { 183 | "anonymous": false, 184 | "inputs": [ 185 | { 186 | "indexed": false, 187 | "internalType": "uint8", 188 | "name": "roundId", 189 | "type": "uint8" 190 | }, 191 | { 192 | "indexed": false, 193 | "internalType": "uint256", 194 | "name": "tokenPrice", 195 | "type": "uint256" 196 | }, 197 | { 198 | "indexed": false, 199 | "internalType": "uint256", 200 | "name": "tokenAmount", 201 | "type": "uint256" 202 | }, 203 | { 204 | "indexed": false, 205 | "internalType": "uint256", 206 | "name": "startTime", 207 | "type": "uint256" 208 | }, 209 | { 210 | "indexed": false, 211 | "internalType": "uint256", 212 | "name": "endTime", 213 | "type": "uint256" 214 | } 215 | ], 216 | "name": "RoundCreated", 217 | "type": "event" 218 | }, 219 | { 220 | "anonymous": false, 221 | "inputs": [ 222 | { 223 | "indexed": true, 224 | "internalType": "address", 225 | "name": "buyer", 226 | "type": "address" 227 | }, 228 | { 229 | "indexed": false, 230 | "internalType": "uint8", 231 | "name": "roundId", 232 | "type": "uint8" 233 | }, 234 | { 235 | "indexed": true, 236 | "internalType": "uint256", 237 | "name": "tokensBought", 238 | "type": "uint256" 239 | }, 240 | { 241 | "indexed": true, 242 | "internalType": "uint256", 243 | "name": "amountPaid", 244 | "type": "uint256" 245 | }, 246 | { 247 | "indexed": false, 248 | "internalType": "uint256", 249 | "name": "timestamp", 250 | "type": "uint256" 251 | } 252 | ], 253 | "name": "TokensBought", 254 | "type": "event" 255 | }, 256 | { 257 | "anonymous": false, 258 | "inputs": [ 259 | { 260 | "indexed": true, 261 | "internalType": "address", 262 | "name": "caller", 263 | "type": "address" 264 | }, 265 | { 266 | "indexed": true, 267 | "internalType": "uint256", 268 | "name": "tokenAmount", 269 | "type": "uint256" 270 | }, 271 | { 272 | "indexed": false, 273 | "internalType": "uint256", 274 | "name": "timestamp", 275 | "type": "uint256" 276 | } 277 | ], 278 | "name": "TokensClaimed", 279 | "type": "event" 280 | }, 281 | { 282 | "anonymous": false, 283 | "inputs": [ 284 | { 285 | "indexed": false, 286 | "internalType": "uint256", 287 | "name": "amount", 288 | "type": "uint256" 289 | }, 290 | { 291 | "indexed": false, 292 | "internalType": "uint8", 293 | "name": "roundId", 294 | "type": "uint8" 295 | } 296 | ], 297 | "name": "TokensTransferredForRound", 298 | "type": "event" 299 | }, 300 | { 301 | "anonymous": false, 302 | "inputs": [ 303 | { 304 | "indexed": false, 305 | "internalType": "address", 306 | "name": "account", 307 | "type": "address" 308 | } 309 | ], 310 | "name": "Unpaused", 311 | "type": "event" 312 | }, 313 | { 314 | "anonymous": false, 315 | "inputs": [ 316 | { 317 | "indexed": true, 318 | "internalType": "uint8", 319 | "name": "roundId", 320 | "type": "uint8" 321 | }, 322 | { 323 | "indexed": false, 324 | "internalType": "uint256", 325 | "name": "amount", 326 | "type": "uint256" 327 | }, 328 | { 329 | "indexed": false, 330 | "internalType": "uint256", 331 | "name": "timestamp", 332 | "type": "uint256" 333 | } 334 | ], 335 | "name": "UnsoldTokensRefunded", 336 | "type": "event" 337 | }, 338 | { 339 | "anonymous": false, 340 | "inputs": [ 341 | { 342 | "indexed": true, 343 | "internalType": "address", 344 | "name": "oldWallet", 345 | "type": "address" 346 | }, 347 | { 348 | "indexed": true, 349 | "internalType": "address", 350 | "name": "newWallet", 351 | "type": "address" 352 | } 353 | ], 354 | "name": "WalletUpdated", 355 | "type": "event" 356 | }, 357 | { 358 | "anonymous": false, 359 | "inputs": [ 360 | { 361 | "indexed": true, 362 | "internalType": "uint8", 363 | "name": "roundId", 364 | "type": "uint8" 365 | } 366 | ], 367 | "name": "WithdrawalCancelled", 368 | "type": "event" 369 | }, 370 | { 371 | "anonymous": false, 372 | "inputs": [ 373 | { 374 | "indexed": true, 375 | "internalType": "uint8", 376 | "name": "roundId", 377 | "type": "uint8" 378 | }, 379 | { 380 | "indexed": false, 381 | "internalType": "uint256", 382 | "name": "unlockTime", 383 | "type": "uint256" 384 | } 385 | ], 386 | "name": "WithdrawalInitiated", 387 | "type": "event" 388 | }, 389 | { 390 | "stateMutability": "payable", 391 | "type": "fallback" 392 | }, 393 | { 394 | "inputs": [], 395 | "name": "DAIInterface", 396 | "outputs": [ 397 | { 398 | "internalType": "contract IERC20", 399 | "name": "", 400 | "type": "address" 401 | } 402 | ], 403 | "stateMutability": "view", 404 | "type": "function" 405 | }, 406 | { 407 | "inputs": [], 408 | "name": "USDCInterface", 409 | "outputs": [ 410 | { 411 | "internalType": "contract IERC20", 412 | "name": "", 413 | "type": "address" 414 | } 415 | ], 416 | "stateMutability": "view", 417 | "type": "function" 418 | }, 419 | { 420 | "inputs": [], 421 | "name": "USDTInterface", 422 | "outputs": [ 423 | { 424 | "internalType": "contract IERC20", 425 | "name": "", 426 | "type": "address" 427 | } 428 | ], 429 | "stateMutability": "view", 430 | "type": "function" 431 | }, 432 | { 433 | "inputs": [], 434 | "name": "VESTING_PERIOD", 435 | "outputs": [ 436 | { 437 | "internalType": "uint256", 438 | "name": "", 439 | "type": "uint256" 440 | } 441 | ], 442 | "stateMutability": "view", 443 | "type": "function" 444 | }, 445 | { 446 | "inputs": [], 447 | "name": "WITHDRAWAL_DELAY", 448 | "outputs": [ 449 | { 450 | "internalType": "uint256", 451 | "name": "", 452 | "type": "uint256" 453 | } 454 | ], 455 | "stateMutability": "view", 456 | "type": "function" 457 | }, 458 | { 459 | "inputs": [ 460 | { 461 | "internalType": "uint256", 462 | "name": "tokenAmount_", 463 | "type": "uint256" 464 | }, 465 | { 466 | "internalType": "uint8", 467 | "name": "roundId_", 468 | "type": "uint8" 469 | } 470 | ], 471 | "name": "buyWithDAI", 472 | "outputs": [], 473 | "stateMutability": "nonpayable", 474 | "type": "function" 475 | }, 476 | { 477 | "inputs": [ 478 | { 479 | "internalType": "uint8", 480 | "name": "roundId_", 481 | "type": "uint8" 482 | } 483 | ], 484 | "name": "buyWithETH", 485 | "outputs": [], 486 | "stateMutability": "payable", 487 | "type": "function" 488 | }, 489 | { 490 | "inputs": [ 491 | { 492 | "internalType": "uint256", 493 | "name": "tokenAmount_", 494 | "type": "uint256" 495 | }, 496 | { 497 | "internalType": "uint8", 498 | "name": "roundId_", 499 | "type": "uint8" 500 | } 501 | ], 502 | "name": "buyWithUSDC", 503 | "outputs": [], 504 | "stateMutability": "nonpayable", 505 | "type": "function" 506 | }, 507 | { 508 | "inputs": [ 509 | { 510 | "internalType": "uint256", 511 | "name": "tokenAmount_", 512 | "type": "uint256" 513 | }, 514 | { 515 | "internalType": "uint8", 516 | "name": "roundId_", 517 | "type": "uint8" 518 | } 519 | ], 520 | "name": "buyWithUSDT", 521 | "outputs": [], 522 | "stateMutability": "nonpayable", 523 | "type": "function" 524 | }, 525 | { 526 | "inputs": [], 527 | "name": "cancelWithdrawal", 528 | "outputs": [], 529 | "stateMutability": "nonpayable", 530 | "type": "function" 531 | }, 532 | { 533 | "inputs": [], 534 | "name": "claimTokens", 535 | "outputs": [], 536 | "stateMutability": "nonpayable", 537 | "type": "function" 538 | }, 539 | { 540 | "inputs": [ 541 | { 542 | "internalType": "uint8", 543 | "name": "roundId_", 544 | "type": "uint8" 545 | }, 546 | { 547 | "internalType": "uint256", 548 | "name": "tokenPrice_", 549 | "type": "uint256" 550 | }, 551 | { 552 | "internalType": "uint256", 553 | "name": "tokenAmount_", 554 | "type": "uint256" 555 | }, 556 | { 557 | "internalType": "uint256", 558 | "name": "startTime_", 559 | "type": "uint256" 560 | }, 561 | { 562 | "internalType": "uint256", 563 | "name": "endTime_", 564 | "type": "uint256" 565 | }, 566 | { 567 | "internalType": "uint256", 568 | "name": "cliffDuration_", 569 | "type": "uint256" 570 | }, 571 | { 572 | "internalType": "uint256", 573 | "name": "vestingDuration_", 574 | "type": "uint256" 575 | }, 576 | { 577 | "internalType": "uint8", 578 | "name": "releasePercentageAfterCliff_", 579 | "type": "uint8" 580 | }, 581 | { 582 | "internalType": "uint8", 583 | "name": "releasePercentageInVestingPerMonth_", 584 | "type": "uint8" 585 | } 586 | ], 587 | "name": "createRound", 588 | "outputs": [], 589 | "stateMutability": "nonpayable", 590 | "type": "function" 591 | }, 592 | { 593 | "inputs": [ 594 | { 595 | "internalType": "uint256", 596 | "name": "tokenAmount_", 597 | "type": "uint256" 598 | }, 599 | { 600 | "internalType": "contract IERC20", 601 | "name": "coin_", 602 | "type": "address" 603 | }, 604 | { 605 | "internalType": "uint8", 606 | "name": "roundId_", 607 | "type": "uint8" 608 | } 609 | ], 610 | "name": "estimatedCoinAmountForTokenAmount", 611 | "outputs": [ 612 | { 613 | "internalType": "uint256", 614 | "name": "", 615 | "type": "uint256" 616 | } 617 | ], 618 | "stateMutability": "view", 619 | "type": "function" 620 | }, 621 | { 622 | "inputs": [ 623 | { 624 | "internalType": "uint256", 625 | "name": "tokenAmount_", 626 | "type": "uint256" 627 | }, 628 | { 629 | "internalType": "uint8", 630 | "name": "roundId_", 631 | "type": "uint8" 632 | } 633 | ], 634 | "name": "estimatedEthAmountForTokenAmount", 635 | "outputs": [ 636 | { 637 | "internalType": "uint256", 638 | "name": "", 639 | "type": "uint256" 640 | } 641 | ], 642 | "stateMutability": "view", 643 | "type": "function" 644 | }, 645 | { 646 | "inputs": [ 647 | { 648 | "internalType": "uint256", 649 | "name": "coinAmount_", 650 | "type": "uint256" 651 | }, 652 | { 653 | "internalType": "contract IERC20", 654 | "name": "coin_", 655 | "type": "address" 656 | }, 657 | { 658 | "internalType": "uint8", 659 | "name": "roundId_", 660 | "type": "uint8" 661 | } 662 | ], 663 | "name": "estimatedTokenAmountAvailableWithCoin", 664 | "outputs": [ 665 | { 666 | "internalType": "uint256", 667 | "name": "", 668 | "type": "uint256" 669 | } 670 | ], 671 | "stateMutability": "view", 672 | "type": "function" 673 | }, 674 | { 675 | "inputs": [ 676 | { 677 | "internalType": "uint256", 678 | "name": "ethAmount_", 679 | "type": "uint256" 680 | }, 681 | { 682 | "internalType": "uint8", 683 | "name": "roundId_", 684 | "type": "uint8" 685 | } 686 | ], 687 | "name": "estimatedTokenAmountAvailableWithETH", 688 | "outputs": [ 689 | { 690 | "internalType": "uint256", 691 | "name": "", 692 | "type": "uint256" 693 | } 694 | ], 695 | "stateMutability": "view", 696 | "type": "function" 697 | }, 698 | { 699 | "inputs": [], 700 | "name": "fundsRaised", 701 | "outputs": [ 702 | { 703 | "internalType": "uint256", 704 | "name": "", 705 | "type": "uint256" 706 | } 707 | ], 708 | "stateMutability": "view", 709 | "type": "function" 710 | }, 711 | { 712 | "inputs": [ 713 | { 714 | "internalType": "address", 715 | "name": "user_", 716 | "type": "address" 717 | }, 718 | { 719 | "internalType": "uint8", 720 | "name": "roundId_", 721 | "type": "uint8" 722 | } 723 | ], 724 | "name": "getClaimableAmount", 725 | "outputs": [ 726 | { 727 | "internalType": "uint256", 728 | "name": "", 729 | "type": "uint256" 730 | }, 731 | { 732 | "internalType": "uint8", 733 | "name": "", 734 | "type": "uint8" 735 | } 736 | ], 737 | "stateMutability": "view", 738 | "type": "function" 739 | }, 740 | { 741 | "inputs": [], 742 | "name": "getCurrentBlockTimestamp", 743 | "outputs": [ 744 | { 745 | "internalType": "uint256", 746 | "name": "", 747 | "type": "uint256" 748 | } 749 | ], 750 | "stateMutability": "view", 751 | "type": "function" 752 | }, 753 | { 754 | "inputs": [], 755 | "name": "getFundsRaised", 756 | "outputs": [ 757 | { 758 | "internalType": "uint256", 759 | "name": "", 760 | "type": "uint256" 761 | } 762 | ], 763 | "stateMutability": "view", 764 | "type": "function" 765 | }, 766 | { 767 | "inputs": [ 768 | { 769 | "internalType": "address", 770 | "name": "investor_", 771 | "type": "address" 772 | }, 773 | { 774 | "internalType": "contract IERC20", 775 | "name": "coin_", 776 | "type": "address" 777 | } 778 | ], 779 | "name": "getInvestments", 780 | "outputs": [ 781 | { 782 | "internalType": "uint256", 783 | "name": "", 784 | "type": "uint256" 785 | } 786 | ], 787 | "stateMutability": "view", 788 | "type": "function" 789 | }, 790 | { 791 | "inputs": [], 792 | "name": "getInvestors", 793 | "outputs": [ 794 | { 795 | "internalType": "address[]", 796 | "name": "", 797 | "type": "address[]" 798 | } 799 | ], 800 | "stateMutability": "view", 801 | "type": "function" 802 | }, 803 | { 804 | "inputs": [], 805 | "name": "getOwner", 806 | "outputs": [ 807 | { 808 | "internalType": "address", 809 | "name": "", 810 | "type": "address" 811 | } 812 | ], 813 | "stateMutability": "view", 814 | "type": "function" 815 | }, 816 | { 817 | "inputs": [], 818 | "name": "getOwnerTokenBalance", 819 | "outputs": [ 820 | { 821 | "internalType": "uint256", 822 | "name": "", 823 | "type": "uint256" 824 | } 825 | ], 826 | "stateMutability": "view", 827 | "type": "function" 828 | }, 829 | { 830 | "inputs": [ 831 | { 832 | "internalType": "uint8", 833 | "name": "roundId_", 834 | "type": "uint8" 835 | } 836 | ], 837 | "name": "getRemainingTimeForCliffEnd", 838 | "outputs": [ 839 | { 840 | "internalType": "uint256", 841 | "name": "", 842 | "type": "uint256" 843 | } 844 | ], 845 | "stateMutability": "view", 846 | "type": "function" 847 | }, 848 | { 849 | "inputs": [ 850 | { 851 | "internalType": "uint8", 852 | "name": "roundId_", 853 | "type": "uint8" 854 | } 855 | ], 856 | "name": "getRemainingTimeForPresaleEnd", 857 | "outputs": [ 858 | { 859 | "internalType": "uint256", 860 | "name": "", 861 | "type": "uint256" 862 | } 863 | ], 864 | "stateMutability": "view", 865 | "type": "function" 866 | }, 867 | { 868 | "inputs": [ 869 | { 870 | "internalType": "uint8", 871 | "name": "roundId_", 872 | "type": "uint8" 873 | } 874 | ], 875 | "name": "getRemainingTimeForPresaleStart", 876 | "outputs": [ 877 | { 878 | "internalType": "uint256", 879 | "name": "", 880 | "type": "uint256" 881 | } 882 | ], 883 | "stateMutability": "view", 884 | "type": "function" 885 | }, 886 | { 887 | "inputs": [ 888 | { 889 | "internalType": "uint8", 890 | "name": "roundId_", 891 | "type": "uint8" 892 | } 893 | ], 894 | "name": "getRound", 895 | "outputs": [ 896 | { 897 | "components": [ 898 | { 899 | "internalType": "uint8", 900 | "name": "roundId", 901 | "type": "uint8" 902 | }, 903 | { 904 | "internalType": "uint256", 905 | "name": "tokenPrice", 906 | "type": "uint256" 907 | }, 908 | { 909 | "internalType": "uint256", 910 | "name": "tokenAmount", 911 | "type": "uint256" 912 | }, 913 | { 914 | "internalType": "uint256", 915 | "name": "startTime", 916 | "type": "uint256" 917 | }, 918 | { 919 | "internalType": "uint256", 920 | "name": "endTime", 921 | "type": "uint256" 922 | }, 923 | { 924 | "internalType": "uint256", 925 | "name": "soldAmount", 926 | "type": "uint256" 927 | }, 928 | { 929 | "internalType": "bool", 930 | "name": "isActive", 931 | "type": "bool" 932 | }, 933 | { 934 | "internalType": "uint256", 935 | "name": "cliffDuration", 936 | "type": "uint256" 937 | }, 938 | { 939 | "internalType": "uint256", 940 | "name": "vestingDuration", 941 | "type": "uint256" 942 | }, 943 | { 944 | "internalType": "uint8", 945 | "name": "releasePercentageAfterCliff", 946 | "type": "uint8" 947 | }, 948 | { 949 | "internalType": "uint8", 950 | "name": "releasePercentageInVestingPerMonth", 951 | "type": "uint8" 952 | } 953 | ], 954 | "internalType": "struct Presale.Round", 955 | "name": "", 956 | "type": "tuple" 957 | } 958 | ], 959 | "stateMutability": "view", 960 | "type": "function" 961 | }, 962 | { 963 | "inputs": [ 964 | { 965 | "internalType": "address", 966 | "name": "investor_", 967 | "type": "address" 968 | } 969 | ], 970 | "name": "getTokenAmountForInvestor", 971 | "outputs": [ 972 | { 973 | "internalType": "uint256", 974 | "name": "", 975 | "type": "uint256" 976 | } 977 | ], 978 | "stateMutability": "view", 979 | "type": "function" 980 | }, 981 | { 982 | "inputs": [ 983 | { 984 | "internalType": "address", 985 | "name": "user_", 986 | "type": "address" 987 | }, 988 | { 989 | "internalType": "uint8", 990 | "name": "roundId_", 991 | "type": "uint8" 992 | } 993 | ], 994 | "name": "getUserRoundPurchase", 995 | "outputs": [ 996 | { 997 | "internalType": "uint256", 998 | "name": "amountBought_", 999 | "type": "uint256" 1000 | }, 1001 | { 1002 | "internalType": "uint256", 1003 | "name": "amountClaimed_", 1004 | "type": "uint256" 1005 | }, 1006 | { 1007 | "internalType": "uint256", 1008 | "name": "totalClaimable_", 1009 | "type": "uint256" 1010 | }, 1011 | { 1012 | "internalType": "bool", 1013 | "name": "cliffCompleted_", 1014 | "type": "bool" 1015 | }, 1016 | { 1017 | "internalType": "uint256", 1018 | "name": "lastClaimTime_", 1019 | "type": "uint256" 1020 | }, 1021 | { 1022 | "internalType": "uint8", 1023 | "name": "unclaimedPeriodsPassed_", 1024 | "type": "uint8" 1025 | } 1026 | ], 1027 | "stateMutability": "view", 1028 | "type": "function" 1029 | }, 1030 | { 1031 | "inputs": [ 1032 | { 1033 | "internalType": "address", 1034 | "name": "user_", 1035 | "type": "address" 1036 | } 1037 | ], 1038 | "name": "getUserRounds", 1039 | "outputs": [ 1040 | { 1041 | "internalType": "uint8[]", 1042 | "name": "", 1043 | "type": "uint8[]" 1044 | } 1045 | ], 1046 | "stateMutability": "view", 1047 | "type": "function" 1048 | }, 1049 | { 1050 | "inputs": [ 1051 | { 1052 | "internalType": "uint8", 1053 | "name": "roundId_", 1054 | "type": "uint8" 1055 | } 1056 | ], 1057 | "name": "initiateWithdrawal", 1058 | "outputs": [], 1059 | "stateMutability": "nonpayable", 1060 | "type": "function" 1061 | }, 1062 | { 1063 | "inputs": [ 1064 | { 1065 | "internalType": "address", 1066 | "name": "", 1067 | "type": "address" 1068 | }, 1069 | { 1070 | "internalType": "address", 1071 | "name": "", 1072 | "type": "address" 1073 | } 1074 | ], 1075 | "name": "investments", 1076 | "outputs": [ 1077 | { 1078 | "internalType": "uint256", 1079 | "name": "", 1080 | "type": "uint256" 1081 | } 1082 | ], 1083 | "stateMutability": "view", 1084 | "type": "function" 1085 | }, 1086 | { 1087 | "inputs": [ 1088 | { 1089 | "internalType": "address", 1090 | "name": "", 1091 | "type": "address" 1092 | } 1093 | ], 1094 | "name": "investorTokenBalance", 1095 | "outputs": [ 1096 | { 1097 | "internalType": "uint256", 1098 | "name": "", 1099 | "type": "uint256" 1100 | } 1101 | ], 1102 | "stateMutability": "view", 1103 | "type": "function" 1104 | }, 1105 | { 1106 | "inputs": [ 1107 | { 1108 | "internalType": "uint256", 1109 | "name": "", 1110 | "type": "uint256" 1111 | } 1112 | ], 1113 | "name": "investors", 1114 | "outputs": [ 1115 | { 1116 | "internalType": "address", 1117 | "name": "", 1118 | "type": "address" 1119 | } 1120 | ], 1121 | "stateMutability": "view", 1122 | "type": "function" 1123 | }, 1124 | { 1125 | "inputs": [], 1126 | "name": "lastRefundedIndex", 1127 | "outputs": [ 1128 | { 1129 | "internalType": "uint256", 1130 | "name": "", 1131 | "type": "uint256" 1132 | } 1133 | ], 1134 | "stateMutability": "view", 1135 | "type": "function" 1136 | }, 1137 | { 1138 | "inputs": [], 1139 | "name": "pause", 1140 | "outputs": [], 1141 | "stateMutability": "nonpayable", 1142 | "type": "function" 1143 | }, 1144 | { 1145 | "inputs": [], 1146 | "name": "paused", 1147 | "outputs": [ 1148 | { 1149 | "internalType": "bool", 1150 | "name": "", 1151 | "type": "bool" 1152 | } 1153 | ], 1154 | "stateMutability": "view", 1155 | "type": "function" 1156 | }, 1157 | { 1158 | "inputs": [], 1159 | "name": "pendingWithdrawalRound", 1160 | "outputs": [ 1161 | { 1162 | "internalType": "uint8", 1163 | "name": "", 1164 | "type": "uint8" 1165 | } 1166 | ], 1167 | "stateMutability": "view", 1168 | "type": "function" 1169 | }, 1170 | { 1171 | "inputs": [ 1172 | { 1173 | "internalType": "contract IERC20", 1174 | "name": "token_", 1175 | "type": "address" 1176 | }, 1177 | { 1178 | "internalType": "uint256", 1179 | "name": "amount_", 1180 | "type": "uint256" 1181 | } 1182 | ], 1183 | "name": "recoverToken", 1184 | "outputs": [], 1185 | "stateMutability": "nonpayable", 1186 | "type": "function" 1187 | }, 1188 | { 1189 | "inputs": [ 1190 | { 1191 | "internalType": "uint8", 1192 | "name": "roundId_", 1193 | "type": "uint8" 1194 | }, 1195 | { 1196 | "internalType": "uint256", 1197 | "name": "batchSize_", 1198 | "type": "uint256" 1199 | } 1200 | ], 1201 | "name": "refundBatch", 1202 | "outputs": [], 1203 | "stateMutability": "nonpayable", 1204 | "type": "function" 1205 | }, 1206 | { 1207 | "inputs": [ 1208 | { 1209 | "internalType": "uint8", 1210 | "name": "roundId_", 1211 | "type": "uint8" 1212 | } 1213 | ], 1214 | "name": "refundUnsoldToken", 1215 | "outputs": [], 1216 | "stateMutability": "nonpayable", 1217 | "type": "function" 1218 | }, 1219 | { 1220 | "inputs": [ 1221 | { 1222 | "internalType": "uint8", 1223 | "name": "", 1224 | "type": "uint8" 1225 | } 1226 | ], 1227 | "name": "rounds", 1228 | "outputs": [ 1229 | { 1230 | "internalType": "uint8", 1231 | "name": "roundId", 1232 | "type": "uint8" 1233 | }, 1234 | { 1235 | "internalType": "uint256", 1236 | "name": "tokenPrice", 1237 | "type": "uint256" 1238 | }, 1239 | { 1240 | "internalType": "uint256", 1241 | "name": "tokenAmount", 1242 | "type": "uint256" 1243 | }, 1244 | { 1245 | "internalType": "uint256", 1246 | "name": "startTime", 1247 | "type": "uint256" 1248 | }, 1249 | { 1250 | "internalType": "uint256", 1251 | "name": "endTime", 1252 | "type": "uint256" 1253 | }, 1254 | { 1255 | "internalType": "uint256", 1256 | "name": "soldAmount", 1257 | "type": "uint256" 1258 | }, 1259 | { 1260 | "internalType": "bool", 1261 | "name": "isActive", 1262 | "type": "bool" 1263 | }, 1264 | { 1265 | "internalType": "uint256", 1266 | "name": "cliffDuration", 1267 | "type": "uint256" 1268 | }, 1269 | { 1270 | "internalType": "uint256", 1271 | "name": "vestingDuration", 1272 | "type": "uint256" 1273 | }, 1274 | { 1275 | "internalType": "uint8", 1276 | "name": "releasePercentageAfterCliff", 1277 | "type": "uint8" 1278 | }, 1279 | { 1280 | "internalType": "uint8", 1281 | "name": "releasePercentageInVestingPerMonth", 1282 | "type": "uint8" 1283 | } 1284 | ], 1285 | "stateMutability": "view", 1286 | "type": "function" 1287 | }, 1288 | { 1289 | "inputs": [], 1290 | "name": "router", 1291 | "outputs": [ 1292 | { 1293 | "internalType": "contract IUniswapV2Router02", 1294 | "name": "", 1295 | "type": "address" 1296 | } 1297 | ], 1298 | "stateMutability": "view", 1299 | "type": "function" 1300 | }, 1301 | { 1302 | "inputs": [ 1303 | { 1304 | "internalType": "address", 1305 | "name": "wallet_", 1306 | "type": "address" 1307 | } 1308 | ], 1309 | "name": "setWallet", 1310 | "outputs": [], 1311 | "stateMutability": "nonpayable", 1312 | "type": "function" 1313 | }, 1314 | { 1315 | "inputs": [], 1316 | "name": "token", 1317 | "outputs": [ 1318 | { 1319 | "internalType": "contract HMESH", 1320 | "name": "", 1321 | "type": "address" 1322 | } 1323 | ], 1324 | "stateMutability": "view", 1325 | "type": "function" 1326 | }, 1327 | { 1328 | "inputs": [ 1329 | { 1330 | "internalType": "uint8", 1331 | "name": "roundId_", 1332 | "type": "uint8" 1333 | } 1334 | ], 1335 | "name": "tokensAvailable", 1336 | "outputs": [ 1337 | { 1338 | "internalType": "uint256", 1339 | "name": "", 1340 | "type": "uint256" 1341 | } 1342 | ], 1343 | "stateMutability": "view", 1344 | "type": "function" 1345 | }, 1346 | { 1347 | "inputs": [ 1348 | { 1349 | "internalType": "address", 1350 | "name": "newOwner_", 1351 | "type": "address" 1352 | } 1353 | ], 1354 | "name": "transferOwnership", 1355 | "outputs": [], 1356 | "stateMutability": "nonpayable", 1357 | "type": "function" 1358 | }, 1359 | { 1360 | "inputs": [], 1361 | "name": "unpause", 1362 | "outputs": [], 1363 | "stateMutability": "nonpayable", 1364 | "type": "function" 1365 | }, 1366 | { 1367 | "inputs": [ 1368 | { 1369 | "internalType": "address", 1370 | "name": "", 1371 | "type": "address" 1372 | }, 1373 | { 1374 | "internalType": "uint8", 1375 | "name": "", 1376 | "type": "uint8" 1377 | } 1378 | ], 1379 | "name": "userRoundPurchases", 1380 | "outputs": [ 1381 | { 1382 | "internalType": "uint256", 1383 | "name": "amountBought", 1384 | "type": "uint256" 1385 | }, 1386 | { 1387 | "internalType": "uint256", 1388 | "name": "amountClaimed", 1389 | "type": "uint256" 1390 | }, 1391 | { 1392 | "internalType": "uint256", 1393 | "name": "lastClaimTime", 1394 | "type": "uint256" 1395 | }, 1396 | { 1397 | "internalType": "bool", 1398 | "name": "cliffCompleted", 1399 | "type": "bool" 1400 | }, 1401 | { 1402 | "internalType": "uint256", 1403 | "name": "initialClaimAmount", 1404 | "type": "uint256" 1405 | }, 1406 | { 1407 | "internalType": "uint256", 1408 | "name": "monthlyVestingAmount", 1409 | "type": "uint256" 1410 | }, 1411 | { 1412 | "internalType": "uint256", 1413 | "name": "vestingStartTime", 1414 | "type": "uint256" 1415 | }, 1416 | { 1417 | "internalType": "uint256", 1418 | "name": "vestingEndTime", 1419 | "type": "uint256" 1420 | } 1421 | ], 1422 | "stateMutability": "view", 1423 | "type": "function" 1424 | }, 1425 | { 1426 | "inputs": [ 1427 | { 1428 | "internalType": "address", 1429 | "name": "", 1430 | "type": "address" 1431 | }, 1432 | { 1433 | "internalType": "uint256", 1434 | "name": "", 1435 | "type": "uint256" 1436 | } 1437 | ], 1438 | "name": "userRounds", 1439 | "outputs": [ 1440 | { 1441 | "internalType": "uint8", 1442 | "name": "", 1443 | "type": "uint8" 1444 | } 1445 | ], 1446 | "stateMutability": "view", 1447 | "type": "function" 1448 | }, 1449 | { 1450 | "inputs": [], 1451 | "name": "wallet", 1452 | "outputs": [ 1453 | { 1454 | "internalType": "address", 1455 | "name": "", 1456 | "type": "address" 1457 | } 1458 | ], 1459 | "stateMutability": "view", 1460 | "type": "function" 1461 | }, 1462 | { 1463 | "inputs": [ 1464 | { 1465 | "internalType": "uint8", 1466 | "name": "roundId_", 1467 | "type": "uint8" 1468 | } 1469 | ], 1470 | "name": "withdraw", 1471 | "outputs": [], 1472 | "stateMutability": "nonpayable", 1473 | "type": "function" 1474 | }, 1475 | { 1476 | "inputs": [], 1477 | "name": "withdrawStartTime", 1478 | "outputs": [ 1479 | { 1480 | "internalType": "uint256", 1481 | "name": "", 1482 | "type": "uint256" 1483 | } 1484 | ], 1485 | "stateMutability": "view", 1486 | "type": "function" 1487 | }, 1488 | { 1489 | "inputs": [], 1490 | "name": "withdrawalInitiated", 1491 | "outputs": [ 1492 | { 1493 | "internalType": "bool", 1494 | "name": "", 1495 | "type": "bool" 1496 | } 1497 | ], 1498 | "stateMutability": "view", 1499 | "type": "function" 1500 | }, 1501 | { 1502 | "stateMutability": "payable", 1503 | "type": "receive" 1504 | } 1505 | ] --------------------------------------------------------------------------------