How do I subscribe to `log events` and get notifications on ThunderCore?

Viewed 3467

I encounter some issues when I wanna subscribe to log events and get notifications on ThunderCore.

As I know, it seems that I should use websocket and eth_subscribe. Is there any example??

Or, are there other solutions could achieve my goal?

2 Answers

listen_to_thundercore_mainnet.js

/**
 * Connect to Thundercore mainnet and listen for tips to a specific smart contract address.
 */

const Web3 = require('web3');
var provider = 'wss://mainnet-ws.thundercore.com';
var web3 = new Web3(provider);


let listenOnce = () => {
  // Create a connection to Thundercore mainet.

  const smartContractAddress = '0xb7d82e5B73e01bB4c1cFB1448f1215bf165929a2'

  var subscription = web3.eth.subscribe('logs', {
    address: smartContractAddress,
  }, (error, result) => {
    if (error) {
      console.log(error)
      console.log('Reconnecting to Thundercore mainnet.')    
      setTimeout( () => {
        // Re-instantiating web3 is needed in case the connection is dropped.
        web3 = new Web3(provider);
        _listen()
      }, 5000);
    }
  })
  .on('connected', (subscriptionId) => {
    console.log('connected')
  })
  .on('data', (log) => {
    console.log(log);
  })
  .on("changed", (log) => {
    console.log('changed')
  })
  
  return subscription
}

const _listen = async () => {
  let tips = listenOnce();
  console.log('Connected to Thundercore mainnet.')

  // web3 subscription times out at 60 secs. Close and reopen at 50 secs.
  setInterval( () => {
    tips.unsubscribe( (error, success) => {
      if(error) {
        console.log('Failed to disconnect from Thundercore mainnet!');
      }
      if(success) {
        console.log('disconnected');
      }
    });
    tips = listenOnce();
  }, (50 * 1000));
}


_listen()

Run with NodeJS

node listen_to_thundercore_mainnet.js

Thundercore disconnects you after 60secs. So we disconnect on purpose just before that and reconnect immediately. This approach will miss some transactions when following contracts with huge traffic.

Get notifications on new contract events on ThunderCore by:

  1. Using a Websocket connection to the RPC nodes, e.g., https://testnet-rpc.thundercore.com
  2. Calling eth_subscribe RPC method on the logs event

Self Contained Example

SimpleRecord.sol

pragma solidity ^0.4.25;

contract SimpleRecord {
    event Record(
        address indexed _from,
        uint _value
    );

    function write() payable public {
        emit Record(msg.sender, msg.value);
    }
}

To generate Record events, run simple-record-write to see the notifications run simple-record-log-subscribe:


const process = require('process')
const path = require('path')
const fs = require('fs')
const Web3 = require('web3')
const Accounts = require('web3-eth-accounts')
const util = require('util')

// truffle migrate --reset --network thunder-mainnet
const thunderWsUrl = 'wss://mainnet-ws.thundercore.com'
// truffle migrate --reset --network thunder-testnet
//const thunderWsUrl = 'wss://testnet-ws.thundercore.com'

const programName = () => {
    return path.basename(process.argv[1])
}

const web3Url = () => {
    let u = process.env['WEB3_PROVIDER_URI']
    if (u === undefined) {
        u = thunderWsUrl
    }
    return u
}

const signTx = async (fromAccount, tx) => {
    const signedTx = await fromAccount.signTransaction(tx)
    return signedTx.rawTransaction // hex string
}

const setup = async () => {
    const privateKeys = fs.readFileSync(path.join(__dirname, '..', '.private-keys'), {encoding: 'ascii'}).split('\n').filter(x => x.length > 0)
    const accounts = new Accounts()
    const account = accounts.privateKeyToAccount('0x' + privateKeys[0])
    const jsonBuf = fs.readFileSync(path.join(__dirname, '..', 'build', 'contracts', 'SimpleRecord.json'))
    const contractData = JSON.parse(jsonBuf)
    const contractAbi = contractData['abi']
    const web3ProviderUrl = web3Url()
    const web3 = new Web3(web3ProviderUrl)
    const networkId = await web3.eth.net.getId()

    let deployedNetwork, contractAddress
    try {
        deployedNetwork = contractData['networks'][networkId]
        contractAddress = deployedNetwork['address']
    } catch (err) {
        msg = `error getting deployedNetwork: ${err}`
        throw new Error(msg)
    }
    const contract = new web3.eth.Contract(contractAbi, contractAddress)
    return [ web3ProviderUrl, web3, networkId, contractAddress, contract, account ]
}

const prettyPrint = (o) => {
   return util.inspect(o, {showHidden: false, depth: null, colors: true})
}

const recordWrite = async () => {
    const [web3ProviderUrl, web3, chainId, contractAddress, contract, fromAccount] = await setup()
    console.log('web3ProviderUrl:', web3ProviderUrl)
    const txnData = contract.methods.write().encodeABI()
    console.log('account.address:', fromAccount.address)
    const promiseResults = await Promise.all([
        web3.eth.getTransactionCount(fromAccount.address),
        web3.eth.getGasPrice(),
    ])
    const nonce = promiseResults[0]
    const gasPrice = promiseResults[1]
    const tx = {
        'gasLimit': 0,
        'chainId': chainId,
        'gasPrice': gasPrice,
        'nonce': Web3.utils.toHex(nonce),
        'from': fromAccount.address,
        'to': contractAddress,
        'value': 0xbeef,
        'data': txnData,
    }
    const gasMultiple = 2.0
    tx.gasLimit = (await web3.eth.estimateGas(tx)) * gasMultiple
    console.log('tx:', prettyPrint(tx))
    const rawTxStr = await signTx(fromAccount, tx)
    const r = await web3.eth.sendSignedTransaction(rawTxStr)
    console.log('sendTransaction: receipt:', prettyPrint(r))
    return 0
}

const logSubscribe = () => {
    return new Promise((resolve, reject) => {
        setup().then(([web3ProviderUrl, web3, chainId, contractAddress, contract, account]) => {
            let eventCount = 0
            console.log('web3ProviderUrl:', web3ProviderUrl)
            console.log('contractAddress:', contractAddress)
            console.log('contract.options.jsonInterface:', prettyPrint(contract.options.jsonInterface))
            const eventAbis = contract.options.jsonInterface.filter((abiObj) => abiObj.type === 'event')
            web3.eth.subscribe('logs', { address: contractAddress }, (err, log) => {
                console.log('eth.subscribe("logs") callback')
                if (err) {
                    console.log('logs callback, err:', err)
                    reject(err)
                    return
                }
                eventCount++
                console.log(`log[${eventCount}]:`, log)
                const eventSig = log.topics[0]
                for (let abi of eventAbis) {
                    if (eventSig === abi.signature) {
                        const decoded = web3.eth.abi.decodeLog(abi.inputs, log.data, log.topics.slice(1))
                        console.log('Decoded Event:', prettyPrint(abi), '\n', prettyPrint(decoded))
                        resolve(0)
                        return
                    }
                }
            })
        })
    })
}

(async () => {
    if (programName().endsWith('-write')) {
        process.exit(await recordWrite())
    } else if (programName().endsWith('-log-subscribe')) {
        process.exit(await logSubscribe())
    } else {
        console.error(`unsupported program name: "${programName()}"`)
        process.exit(2)
    }
})()

See the complete project here in the subscribe-to-logs branch of the field-support repo.

Related