Skip to content

Building Event-Driven Workers

Now that you’ve created your first worker, let’s build something more powerful: workers that automatically respond to blockchain events.

A worker that:

  • Listens for token transfers on Ethereum
  • Automatically logs the transaction details
  • Can trigger actions on other blockchains
  • Complete the Getting Started guide
  • nXCC node running locally (./run.sh)
  • Basic understanding of blockchain events

Open workers/my-worker.ts in the project you scaffolded during Getting Started. The template already wires up HTTP, timer, and transfer handlers so you can jump straight to reacting to events. Focus on the generated handleTransfer implementation:

workers/my-worker.ts
async handleTransfer(eventPayload: Record<string, unknown>, { userdata }: WorkerContext) {
try {
const {
args: { from, to, value },
} = decodeEventLog({
abi: [transferEvent],
topics: eventPayload.topics as [signature: Hex, ...args: Hex[]],
data: eventPayload.data as Hex,
});
const transactionHash = eventPayload.transaction_hash as Hex;
const blockNumber = eventPayload.block_number as number;
console.log(`➡️ Transfer detected in block ${blockNumber}:`);
console.log(` From: ${from}`);
console.log(` To: ${to}`);
console.log(` Amount: ${formatUnits(value, 6)} USDC`);
console.log(` Tx: ${transactionHash}`);
} catch (error) {
console.error("Failed to decode transfer event", error, eventPayload);
}
},

handleTransfer receives a decoded payload (including args, the transaction hash, and block number), so you can log details or trigger downstream actions immediately.

If you don’t already have Foundry installed, set it up first:

curl -L https://foundry.paradigm.xyz | bash && foundryup

Run a local Ethereum chain in a separate terminal:

anvil

Then create and deploy a simple ERC-20 token that emits Transfer events:

cat > TestToken.sol <<'EOF'
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
contract TestToken {
mapping(address => uint256) public balanceOf;
event Transfer(address indexed from, address indexed to, uint256 value);
constructor() {
balanceOf[msg.sender] = 1000000 * 10**18;
}
function transfer(address to, uint256 amount) public {
require(balanceOf[msg.sender] >= amount, "Insufficient balance");
balanceOf[msg.sender] -= amount;
balanceOf[to] += amount;
emit Transfer(msg.sender, to, amount);
}
}
EOF
forge create TestToken.sol:TestToken \
--private-key 0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80 \
--broadcast

The output should be similar to the following. The deployed contract address is in the highlighted line.

Deployer: 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266
Deployed to: 0x5FbDB2315678afecb367f032d93F642f64180aa3
Transaction hash: 0x2f241f20b76090179012a16f9060a223d793fd97939a31d03bbfd19d144534fc

Record the deployed contract address and replace YOUR_TOKEN_CONTRACT_ADDRESS in workers/manifest.template.json.

Update workers/manifest.template.json to watch your local development chain. We override the gateways so that the nXCC node running in docker can access the anvil node running on the host. Replace the contract address 0x5FbDB2315678afecb367f032d93F642f64180aa3 with the one you just got, if it differs.

{
"handler": "handleTransfer",
"kind": "web3_event",
"chain": 31337,
"address": ["0x5FbDB2315678afecb367f032d93F642f64180aa3"],
"gateways": ["ws://host.docker.internal:8545"],
"topics": [
["0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef"]
]
}

With Anvil running and the manifest pointing at your test token, build and deploy the worker. Choose whichever method you prefer to capture the worker ID so you can reference it later without copy/paste hunting:

npm run build
export WORKER_ID=$(nxcc worker deploy --bundle workers/manifest.template.json | jq -r '.workOrderId')
echo "Worker ID: $WORKER_ID"

Trigger a transfer (swap in the address you recorded, if different from 0x5FbDB2315678afecb367f032d93F642f64180aa3) to watch the worker run:

cast send 0x5FbDB2315678afecb367f032d93F642f64180aa3 \
"transfer(address,uint256)" \
0x70997970C51812dc3A010C7d01b50e0d17dc79C8 \
1000000000000000000 \
--private-key 0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80

Then inspect the worker’s logs directly from the CLI:

nxcc worker logs "$WORKER_ID" --tail 50

Tip: Use --follow to stream logs.

When you’re ready to monitor mainnet activity, point the manifest at a contract such as USDC on Ethereum and redeploy your nXCC worker:

"chain": 31337,
"address": ["0x5FbDB2315678afecb367f032d93F642f64180aa3"],
"gateways": ["ws://host.docker.internal:8545"],
"chain": 1,
"address": ["0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"],

Rebuild and run the deploy command again and watch mainnet transfers stream into the same handler.

One of nXCC’s superpowers is handling events from multiple blockchains in a single worker:

{
"events": [
{
"handler": "handleTransfer",
"kind": "web3_event",
"chain": 1,
"address": ["0x..."],
"topics": [["0x..."]]
},
{
"handler": "handleTransfer",
"kind": "web3_event",
"chain": 137,
"address": ["0x..."],
"topics": [["0x..."]]
},
{
"handler": "handleTransfer",
"kind": "web3_event",
"chain": 42161,
"address": ["0x..."],
"topics": [["0x..."]]
}
]
}

Your worker will receive events from Ethereum, Polygon, and Arbitrum in the same handleTransfer function!

You’re now ready to build sophisticated event-driven applications that span multiple blockchains! 🚀