← back to blog

How to vary transaction timing across 100 wallets without scripts giving you away

How to vary transaction timing across 100 wallets without scripts giving you away

Running 20 wallets with hand-crafted timing is manageable. Running 100 is where most operators hit a wall, not because the infrastructure breaks, but because the timing patterns betray them. Onchain analysts and protocol risk teams are not looking for duplicate addresses. they are looking for wallets that breathe in sync, that wake up at the same second, that all submit transactions within a 30-second burst window at 02:00 UTC every Tuesday. that signature is visible in block explorers, in Dune dashboards, in the open-source Sybil scripts that protocols now run before snapshot day.

I have been running multi-wallet airdrop operations out of Singapore since 2022. the LayerZero Sybil filter in 2024 was the clearest signal the industry has seen that these analyses are not theoretical. LayerZero’s team published a community-driven Sybil identification process that flagged wallets based on clustering signals, including temporal clustering. wallets that transacted together repeatedly, in the same time windows, with the same protocols, were grouped and excluded. the amounts lost by operators who had hundreds of wallets in those clusters were not small. i watched peers lose five-figure allocations because their Python scheduler had a 10-second jitter range that was far too narrow.

The problem is structural. when you automate wallet operations, you are fighting against the fundamental nature of scripted execution: computers are precise, humans are not. your task is to deliberately introduce enough imprecision that a statistical analysis of your wallet cluster’s timing does not produce a low p-value for “these wallets are coordinated.” this article covers how to actually do that at scale, what has failed in production, and how to think about the tradeoffs between operational convenience and detectable patterns.

background and prior art

The academic literature on timing analysis in financial fraud detection goes back decades. FINRA’s guidance on algorithmic trading surveillance from 2015 laid out how regulators think about burst patterns and coordinated order flow. the same statistical intuitions, cross-correlation of timestamps, clustering coefficients, shared IP fingerprints at the network layer, map directly to onchain Sybil detection. protocols like Optimism, Arbitrum, and LayerZero did not invent this methodology. they borrowed it.

On the crypto side, the open literature is thin but practical. Nansen, Chainalysis, and independent researchers have published wallet clustering methodologies that combine timing with other signals (gas price preferences, contract interaction sequences, funding wallet graphs). Chainalysis’s public writing on entity clustering gives a sense of what a well-resourced team can infer from public chain data alone. the key insight practitioners need to hold: timing is rarely the only signal, but it is often the easiest to compute at scale and the most damning when it clusters tightly. a funding wallet that sends ETH to 80 addresses in one hour is already a yellow flag. those 80 addresses then all transacting within 5-minute windows of each other for the next three months is a red flag that survives even noisy data.

Prior to 2023, most operators used simple time.sleep(random.uniform(a, b)) calls. the distributions being used were almost universally uniform, which is itself non-human. real human activity follows patterns closer to a lognormal or Poisson distribution during active sessions, with long tails of inactivity (sleep, weekends, travel). the gap between what operators were doing and what human activity actually looks like created a detectable signature that risk teams could train on.

the core mechanism

The goal is not true randomness. true randomness would have you submitting transactions at 03:17 on a Wednesday and then again at 19:44 on a Sunday, with no consistency. that is also anomalous. the goal is to approximate the distributional properties of a diverse set of independent human actors, each with their own timezone, work schedule, and browsing habits.

I break this into three layers.

Layer 1: the per-wallet timezone anchor. each wallet in your set should be assigned a notional “home timezone” at setup time. this does not need to correspond to any real person. the point is that wallet activity should cluster in the waking hours of that timezone. a wallet anchored to US/Eastern should transact predominantly between 08:00 and 23:00 ET. a wallet anchored to Asia/Seoul should transact between 09:00 and 00:00 KST. when you have 100 wallets, you want a realistic geographic distribution: maybe 30% US, 25% Europe, 30% East Asia, 15% other. this means your 100 wallets are never all “available” at the same time, because humans in different timezones are not all awake at once.

import pytz
from datetime import datetime
import random

TIMEZONE_POOL = [
    ("America/New_York", 0.15),
    ("America/Chicago", 0.08),
    ("America/Los_Angeles", 0.10),
    ("Europe/London", 0.10),
    ("Europe/Berlin", 0.10),
    ("Europe/Warsaw", 0.05),
    ("Asia/Singapore", 0.12),
    ("Asia/Seoul", 0.10),
    ("Asia/Tokyo", 0.08),
    ("Asia/Shanghai", 0.07),
    ("Australia/Sydney", 0.05),
]

def assign_timezone(wallet_index, seed=None):
    rng = random.Random(seed or wallet_index)
    zones, weights = zip(*TIMEZONE_POOL)
    return rng.choices(zones, weights=weights, k=1)[0]

def is_active_hour(tz_name):
    tz = pytz.timezone(tz_name)
    local_hour = datetime.now(tz).hour
    # active between 08:00 and 23:00 local
    return 8 <= local_hour < 23

Layer 2: session-level jitter with a human-realistic distribution. within a wallet’s active hours, you need to model when it actually transacts. real users do not transact uniformly throughout their waking hours. they transact in sessions: check their portfolio, do a swap, maybe bridge something, then close the tab. sessions might last 5-30 minutes with transactions clustered inside them, followed by hours of inactivity.

the practical implementation i use is a Poisson process for session arrival (so the gap between sessions is exponentially distributed, averaging maybe 2-3 sessions per active day) and then within each session, a small number of transactions with inter-transaction delays drawn from a lognormal distribution with a mean of about 3-8 minutes and a standard deviation that gives a heavy right tail. The ethers.js documentation and similar libraries make it trivial to queue and time transactions, but the timing logic needs to live above the library layer in your scheduler.

import numpy as np

def next_session_gap_seconds(mean_sessions_per_day=2.5):
    # exponential inter-arrival for Poisson process
    mean_gap = 86400 / mean_sessions_per_day
    return np.random.exponential(scale=mean_gap)

def intra_session_delay_seconds(mean_minutes=5, sigma_log=0.8):
    # lognormal: most delays short, occasional long pauses
    mean_sec = mean_minutes * 60
    mu = np.log(mean_sec) - (sigma_log**2) / 2
    return np.random.lognormal(mean=mu, sigma=sigma_log)

Layer 3: macro-level weekly and monthly patterns. human activity has weekly cycles. usage drops on Sundays for some demographics, spikes on Monday morning when people check their portfolios after the weekend. it drops during major holidays. if your 100 wallets all maintain perfectly flat activity 7 days a week, that is itself a signal, because individual humans are not flat. the simplest implementation is a day-of-week multiplier: scale the probability of a session starting by a factor that reflects realistic weekly activity patterns. you can also introduce occasional multi-day gaps, i.e., the wallet “goes silent” for 2-5 days, mimicking a user on holiday or just not paying attention. do this for a random 10-20% of your wallets at any given time.

worked examples

Example 1: 100 wallets over a 90-day farming window on an EVM chain.

setup: 100 wallets assigned timezones as per the distribution above. each wallet has a target of 8-15 transactions per week (a realistic range for an engaged DeFi user). sessions are scheduled using a Poisson arrival model (mean 2.5 sessions/day), each session containing 1-4 transactions.

implementation: a master scheduler runs every 60 seconds. it checks which wallets have an active session due (based on next_session_start timestamps stored in a SQLite database). it then queues transactions for that wallet with intra-session delays drawn from the lognormal model above. wallets in “inactive” timezones (outside 08:00-23:00 local) are skipped even if a session was scheduled; the session is rescheduled to the next active window.

result at day 90: the wallet set shows timing autocorrelation across wallets of less than 0.1 (measured by cross-correlating hourly transaction counts pairwise). no wallet fires within 60 seconds of more than 3 other wallets on more than 5 occasions across the entire period. when visualised on a Dune dashboard, the activity looks like a diverse user base, not a single operator.

Example 2: 50-wallet set caught by a naive implementation.

this was a peer’s operation i reviewed post-mortem. they used time.sleep(random.randint(300, 900)) between each wallet’s transaction, running wallets sequentially. the problem: with 50 wallets firing sequentially with 5-15 minute gaps between each, the total cycle time was roughly 4-12 hours, meaning the pattern repeated predictably. Dune showed a sawtooth wave: a burst of activity every 4-12 hours, cycling through all 50 wallets in sequence. the wallet ordering was also consistent, which is an additional signal (wallet address 1 always fires before wallet address 2).

fix: parallelise wallet operations. run each wallet on its own independent schedule. remove any sequential coupling between wallets. the ordering correlation alone is enough to cluster wallets without even looking at timing windows.

Example 3: partial timezone implementation that still leaked.

another operator used timezone anchoring but made one mistake: all wallets were allowed to transact at any hour if a “high-priority” opportunity appeared (a new protocol launch, a limited-time bonus). when a new farming opportunity opened at 14:00 UTC, all 80 wallets transacted within 90 minutes of each other, regardless of their notional timezone. 40 of those wallets were supposed to be “asleep” based on their US Pacific anchoring. the simultaneous activation on a single stimulus was the giveaway.

fix: enforce the timezone constraint even during high-priority events. stagger the “wake up and respond” time across wallets even when you are manually triggering an operation. if you need all 80 wallets to interact with a new protocol, spread those interactions over 6-12 hours, not 90 minutes. the opportunity cost of missing the first hour is lower than the cost of losing the allocation.

edge cases and failure modes

1. gas price correlation. timing is not the only temporal signal. if 80 wallets submit transactions with identical maxFeePerGas values because your script reads the gas oracle once and applies it to all pending transactions, that is a clustering signal independent of timing. different wallets should read gas prices independently, at the time their transaction is actually submitted, and you should allow for some variation in willingness-to-pay (some wallets are “high gas” users, some are “low gas” users). this is actually a useful secondary dimension of wallet personality to build out.

2. the funding wallet graph. no amount of timing variation saves you if all 100 wallets were funded from the same address in the same block. the funding graph is often analysed first because it is computationally cheap. if you have not already solved wallet funding (using CEX withdrawals to unique addresses, OTC, or privacy tools where legally permitted in your jurisdiction), timing variation is cosmetic. address the funding graph before obsessing over timing. for a deeper treatment of wallet funding methodology, see the guide on managing 100+ wallet operations.

3. contract interaction sequence fingerprinting. if all 100 wallets follow the identical sequence of contract interactions (swap on Uniswap, bridge on Stargate, stake on protocol X, in that exact order), the sequence is a fingerprint regardless of timing. vary the order, vary which protocols are used, and introduce “noise” interactions that are not part of the core farming path. this is related to timing but is a distinct failure mode. see also the discussion at multiaccountops.com/blog/ for further treatment of interaction diversity.

4. RPC endpoint clustering. if all 100 wallets are hitting the same RPC node, your IP address appears in the node’s logs correlated with all 100 wallet addresses. even if your timing is perfect, the RPC provider can see the association. use separate RPC providers per wallet cohort at minimum, or per wallet ideally. Alchemy, Infura, QuickNode, and self-hosted nodes via dRPC all have different tradeoffs at this scale. proxyscraping.org/blog/ covers the network-layer isolation problem in detail, which is the counterpart to the timing problem at the infrastructure layer.

5. weekday/weekend inversion. some operators correctly implement timezone anchoring but forget to vary weekend behaviour. a wallet that transacts at a steady rate 7 days a week is anomalous for most DeFi users. weekday activity should be higher than weekend activity for wallets with “professional” timezone anchors (European and US business hours). if you are randomising session counts per day, apply a 0.6-0.8 multiplier to weekend days for these wallets. failure to do this creates a flat activity histogram that stands out against real user populations.

what we learned in production

The biggest lesson from running this at scale is that timing variation cannot be bolted on at the end. if your core script architecture couples wallet operations (wallet N finishes, then wallet N+1 starts), you cannot patch that with jitter. you need independent scheduling from day one. i moved to a worker queue architecture (Celery with Redis, though BullMQ with Node is equally viable) where each wallet is a separate job with its own schedule, completely decoupled from every other wallet’s state. the scheduler does not know or care what the other wallets are doing. this architectural choice paid for itself within the first month of operation.

The second lesson is that you need observability on your own timing before your adversaries have it. i run a private Dune dashboard that plots my own wallet cluster’s hourly transaction counts, pairwise correlation matrix, and day-of-week distribution. i check it weekly. if i see patterns tightening, i adjust parameters. treating timing hygiene as a one-time setup task rather than an ongoing operational discipline is the mistake most operators make. the farming landscape is not static, and neither is the detection tooling. keeping up with what protocols are publishing about their Sybil filtering methodology, which the more transparent teams document in their governance forums and blog posts, is a continuous research task. the airdrop farming blog aggregates this kind of protocol-specific intelligence as it emerges.

For the tooling side: i have settled on a combination of a timezone-aware Celery beat scheduler for session planning, lognormal jitter at the transaction level via a custom middleware layer sitting above ethers.js, and a SQLite-backed wallet state store that tracks each wallet’s next scheduled session independently. total infrastructure cost for 100 wallets is under $50/month (a t3.medium on EC2 for the scheduler, RDS for state, separate RPC keys per 10-wallet cohort). the cost of getting this wrong, in lost allocations, is orders of magnitude higher. for a broader treatment of the browser fingerprinting and network isolation that complements timing variation, antidetectreview.org/blog/ is the most consistently useful resource i have found in this space.

references and further reading

  1. Ethereum transaction documentation, ethereum.org - the foundation for understanding what data is visible onchain for every transaction, including timestamps relative to block production.

  2. Chainalysis blog on entity clustering and blockchain analytics - Chainalysis publishes methodology notes and industry reports that give a clear picture of how professional analytics teams approach wallet clustering.

  3. FINRA Regulatory Notice 15-09 on algorithmic trading surveillance - the regulatory framing for how burst patterns and coordinated order flow are detected in traditional finance, directly applicable by analogy to onchain Sybil analysis.

  4. ethers.js v6 documentation - the primary reference for implementing transaction submission logic in JavaScript/TypeScript, relevant to anyone building a timing-aware wallet automation layer.

  5. LayerZero network official blog and documentation, layerzero.network - LayerZero has published information about their Sybil detection and community reporting process, which is the clearest public case study of how a major protocol operationalises these filters.

Written by Xavier Fok

disclosure: this article may contain affiliate links. if you buy through them we may earn a commission at no extra cost to you. verdicts are independent of payouts. last reviewed by Xavier Fok on 2026-05-22.

need infra for this today?