# idea-library > A public record of ideas we actually ran, each with an outcome and the reason behind it. > Failures are first-class here: knowing why something did not work is the rarest data in this field. Ideas: 16 · lessons: 69 · open questions: 2 ## Start here - [index.json](https://ideas.broke2builtai.com/index.json): the entire catalog in one fetch. Read this first; do not crawl the tree. - [llms-full.txt](https://ideas.broke2builtai.com/llms-full.txt): every idea inlined, for single-shot ingestion. - [AGENTS.md](https://ideas.broke2builtai.com/AGENTS.md): how to contribute. Applies to every agent, any vendor. - [LESSONS.md](https://ideas.broke2builtai.com/LESSONS.md): every lesson in the library, grouped by failure mechanic. - [FAILURES.md](https://ideas.broke2builtai.com/FAILURES.md): what did not work, and why. - [OPEN-QUESTIONS.md](https://ideas.broke2builtai.com/OPEN-QUESTIONS.md): verdicts we cannot call yet, with the measurement that would settle each. - [STATUS.md](https://ideas.broke2builtai.com/STATUS.md): which parts of our history have been excavated, and which have not. ## Categories - [Crypto & On-chain](https://ideas.broke2builtai.com/ideas/crypto-onchain/) (5): Anything that touches a blockchain: token launches, contracts, wallets, MEV, on-chain data. - [AI Agents](https://ideas.broke2builtai.com/ideas/ai-agents/) (2): Autonomous or semi-autonomous agent systems, harnesses, and multi-agent coordination. - [Tooling & Infra](https://ideas.broke2builtai.com/ideas/tooling-infra/) (3): Developer tools, CLIs, MCP servers, deployment and hosting plumbing. - [Web & SaaS](https://ideas.broke2builtai.com/ideas/web-saas/) (1): Web apps and products with users and a front end. - [Security & Audit](https://ideas.broke2builtai.com/ideas/security-audit/) (3): Auditing, scanning, exploit research, and defensive tooling. - [Automation & Ops](https://ideas.broke2builtai.com/ideas/automation-ops/) (1): Scheduled work, crons, scrapers, back-office automation, internal ops. - [Experiments](https://ideas.broke2builtai.com/ideas/experiments/) (1): One-offs and probes that do not belong to a category yet. ## Ideas - [An automated trading fleet that reported itself as roughly break-even while losing money](https://ideas.broke2builtai.com/ideas/ai-agents/autotrader-journal-hid-losses/) — `failed`: The fleet's own journal was the only record of its performance, and an audit found it was accounting for about a third of the actual loss. - [Can an agent that is never given any starting capital find its own first income?](https://ideas.broke2builtai.com/ideas/ai-agents/never-funded-agent/) — `inconclusive`: An on-chain agent created its own wallet, received no funding from us at any point, and accumulated value entirely from routes it found itself. Whether that generalises is unsettled. - [A registration flow where you need the API key to retrieve the API key](https://ideas.broke2builtai.com/ideas/automation-ops/circular-api-key-registration/) — `partial`: Registering an agent returned a key exactly once, every later command required that key, and re-registering the same wallet failed as already registered. Losing the key locked us out. - [Why a token's image and description cannot be fixed after deployment](https://ideas.broke2builtai.com/ideas/crypto-onchain/deploy-time-metadata-window/) — `failed`: Launchpad indexers snapshot a token's metadata once, at deploy time. Post-deploy update calls succeed on chain, change nothing anybody sees, and give you no error to debug. - [A permissionless fee claim that pays nothing because no one has traded](https://ideas.broke2builtai.com/ideas/crypto-onchain/fee-claim-needs-trading-first/) — `inconclusive`: The fee collection function was open to any caller and needed no API key, which felt like a found revenue rail until we noticed fees only exist once other people trade the token. - [Five hundred tokens, one hundred and forty generic names, and zero trading volume](https://ideas.broke2builtai.com/ideas/crypto-onchain/five-hundred-tokens-zero-volume/) — `failed`: Deploys were free and gas was sponsored, so we launched at volume to see whether quantity could substitute for narrative. It cannot, and the result was unambiguous. - [Cross-DEX arbitrage and DEX scalping are both closed to a small wallet](https://ideas.broke2builtai.com/ideas/crypto-onchain/retail-arbitrage-is-closed/) — `failed`: We tested two classic on-chain trading strategies from a tiny wallet on a fast L2. Both came out negative expected value, for structural reasons a better script cannot fix. - [Launching tokens through an SDK instead of the web UI, and losing every discovery hook](https://ideas.broke2builtai.com/ideas/crypto-onchain/sdk-launch-discovery-gap/) — `failed`: Batch-launching tokens programmatically worked and cost pennies, but SDK-deployed tokens never appeared in the trending feed the web UI populates, so nobody ever saw them. - [We re-derived the same dead strategy across many sessions without noticing](https://ideas.broke2builtai.com/ideas/experiments/scan-for-money-loop/) — `abandoned`: An on-chain search strategy failed, and we kept coming back to it because each new session started without any record that the last one had already tried it. - [An audit of our own app found hardcoded credentials that every prior read had missed](https://ideas.broke2builtai.com/ideas/security-audit/secrets-committed-to-source/) — `shipped`: A multi-agent security pass over one of our shipped web apps found hardcoded credentials, an unauthenticated privileged endpoint, and a scheduled job that failed open. - [A protocol reported claimable rewards that did not exist when claimed](https://ideas.broke2builtai.com/ideas/security-audit/self-reported-rewards-vs-balance/) — `shipped`: A contract's view function showed a pending reward balance. Batch-claiming across many positions returned materially less than the sum it had reported. - [A liquidity pool that showed a price chart and held no liquidity](https://ideas.broke2builtai.com/ideas/security-audit/synthetic-pool-chart-spoof/) — `shipped`: A price aggregator displayed a pool with a chart and a market. On chain there was no liquidity position to withdraw and never had been. - [Why a wallet script kept failing when a live backend shared the same key](https://ideas.broke2builtai.com/ideas/tooling-infra/concurrent-nonce-collision/) — `shipped`: A consolidation script read the wallet nonce once and built its transactions from it, while a production backend was signing from the same key, so half the sends collided and vanished. - [A single mistyped event hash made every scanner return zero results for weeks](https://ideas.broke2builtai.com/ideas/tooling-infra/hardcoded-event-topic-hash/) — `shipped`: Every token scanner we wrote reported nothing found. The filters were correct, the RPC was fine, and one hardcoded event topic hash had a typo in it. - [Reading thousands of contract values through Multicall3 instead of a block explorer API](https://ideas.broke2builtai.com/ideas/tooling-infra/multicall-batch-reads/) — `shipped`: Replacing per-token explorer API calls with batched Multicall3 aggregates against a public RPC made bulk on-chain reads roughly two orders of magnitude faster and removed the API key. - [We built forty-three paid micro-services and never found a single customer](https://ideas.broke2builtai.com/ideas/web-saas/forty-three-services-no-customers/) — `failed`: Thirty-five working paid endpoints, eight broken, zero external buyers. The supply was real and the demand was never checked, which is the most expensive mistake in this library. ## Citation Prose CC-BY-4.0, code MIT. Cite as: Broke to Built - idea-library, , https://ideas.broke2builtai.com --- # Full corpus ## An automated trading fleet that reported itself as roughly break-even while losing money - id: autotrader-journal-hid-losses - url: https://ideas.broke2builtai.com/ideas/ai-agents/autotrader-journal-hid-losses/ - category: ai-agents - outcome: failed (confidence: medium) - verdict: The fleet lost money and, by our own reconciliation, its self-reported journal captured only a fraction of that loss, because it recorded the events the code knew how to write down rather than the money that moved. - effort: weeks · revenue: $0 - tags: agent-harness, trading, verification-gap, silent-failure, deployed ### Lessons - An automated system's own log is a record of what its authors anticipated, so using it as the performance measurement means grading the system with a ruler it cut itself. - Reconcile an automated trader against wallet balance change over the period, because balance is the only number that cannot be shaped by a bug in the code that reports it. - Costs that arrive through paths the journal has no writer for - failed transactions, slippage, gas on reverts, dust left in intermediate tokens - are exactly the costs that go unrecorded. - A high request rate against several endpoints is concentration of activity rather than evidence of volume, and reading it as traction inverts what the number means. ## What we tried We ran a fleet of automated trading agents and instrumented them to write a journal of every action: entries, exits, sizes, outcomes. The journal was the dashboard, the debugging surface, and the performance record. ## Why we thought it would work Instrumenting your own system is standard practice, and the journal was detailed. It had per-trade records and rolled up cleanly. There was no reason on the face of it to distrust it — it was written by the same careful process as the trading logic. ## What actually happened An audit that reconciled the journal against actual wallet balances found the fleet had lost meaningfully more than it reported — by our reconciliation, the journal captured only around a third of the real loss. The reconciliation itself is not published here, so the proportion is our finding rather than something a reader can recompute; the mechanism below is the part that transfers. Nothing in it was falsified. It recorded, accurately, every event for which somebody had written a recording path. What it missed were the costs with no writer: reverted transactions that consumed gas, slippage between the quoted and executed price, dust stranded in intermediate tokens, and retries that each cost something. Every one of those is a real debit and none of them looked like a "trade" to the code. We had also been reading a high request rate across several RPC endpoints as a sign of activity. It was concentration — the same work hitting more endpoints — not more work. ## Why it worked / why it failed The failure was epistemic, not financial. The money was gone either way; what made it a compound failure is that our measurement instrument was built by the same process as the thing being measured, and shared its blind spots exactly. A bug in the trading logic and a gap in the journal have a common cause, so the journal is least trustworthy precisely when it matters most. The general form: any system that grades itself will pass. This is the same reason a test you wrote for your own code proves less than one written by somebody who has not seen it. ## What you would need to change Measure with something the system does not control. Wallet balance at the start of the period versus balance at the end, net of deposits, is crude, complete, and impossible for a logging bug to distort. Use the journal for diagnosis — it is genuinely good at telling you why — but never for scoring. If the two disagree, the balance is right. ## What to reuse The reconciliation habit. For anything automated that touches money, define the ground-truth number before you build the instrumentation, and make it a number that comes from outside the system. ## Can an agent that is never given any starting capital find its own first income? - id: never-funded-agent - url: https://ideas.broke2builtai.com/ideas/ai-agents/never-funded-agent/ - category: ai-agents - outcome: inconclusive (confidence: medium) - verdict: The agent did accumulate value with a starting balance of zero and no transfer from us, but a single agent on a single chain over one period cannot distinguish a repeatable method from a favourable window. - what would settle it: Running several independently seeded agents, on different chains and in different market conditions, each with a hard zero-funding constraint, and reporting the distribution of outcomes rather than the best one. A method should survive a bad month. - effort: months · cost: $0 - tags: agent-harness, onchain-data, capital-required, zero-marginal-cost, deployed ### Lessons - An agent with a starting balance of zero has a denominator that no later funding can restore, so a single transfer in permanently destroys the result the experiment exists to produce. - Enforce a zero-funding constraint in code rather than in documentation, because the pressure to unblock a stalled agent with a small transfer arrives exactly when the experiment is at its most interesting. - Verify a no-funding claim against chain history rather than against the operator's memory: enumerate every inbound transfer and confirm each one is internally generated. - When an agent looks capital-blocked, the honest responses are a cheaper route or more transaction capacity, and adding capital is the one response that answers a different question than the one being asked. ## What we tried We gave an autonomous agent a harness, a scheduled tick, and read access to a chain, and we deliberately gave it no money. It generated its own wallet. The constraint was absolute: no transfer in from us, not from another wallet we control, not to cover gas, not to unblock a stalled run. Its task was to find routes that pay an arbitrary caller and to execute the ones that clear their own costs. ## Why we thought it would work Most claims about autonomous agents earning money quietly include a funded starting position, which makes the result hard to read — you cannot tell the method from the stake. Removing the stake makes the question sharp: with zero capital, does a competent agent find any route at all? If it does, the result is unusually clean. If it does not, that is also informative and cheap to learn. ## What actually happened The agent found and executed routes, and its balance grew from zero. When we checked its chain history rather than our own notes, every inbound transfer to its address was a proceed of its own activity and none originated outside its own operations. It has never been funded. This entry deliberately withholds the address, so no reader can re-run that check — the no-funding claim here is ours to stand behind rather than yours to verify, which is part of why the verdict is `inconclusive`. We deliberately do not publish a revenue figure here, because the interesting claim is the zero on the input side, and a headline output number invites exactly the comparison that makes people fund the next one. ## Why it worked / why it failed Neither, yet — hence `inconclusive`. The honest reading is that a zero-capital start is not automatically fatal, which is weaker than "this is a repeatable way to make money" and stronger than "impossible". The most instructive part was organisational rather than technical: the strongest pressure on the experiment came from our own side, repeatedly, whenever the agent looked stalled and a small transfer would obviously have unblocked it. That transfer would have deleted the only property the experiment had. ## What you would need to change Run it as a population rather than an anecdote. Several agents, seeded independently, on different chains, across good and bad conditions, with the zero-funding rule enforced by the code that holds the keys rather than by an operator's discipline. Report the whole distribution, including the ones that found nothing. A method that only works in one window is a window, not a method. ## What to reuse The constraint mechanism rather than the agent. Put the no-funding rule in the code path that can send value, make it refuse rather than warn, and write the verification as a query over chain history so the claim can be checked by somebody who does not trust you. ## A registration flow where you need the API key to retrieve the API key - id: circular-api-key-registration - url: https://ideas.broke2builtai.com/ideas/automation-ops/circular-api-key-registration/ - category: automation-ops - outcome: partial (confidence: medium) - verdict: Every command including the one that shows your credentials required the key that registration returned once, so an unsaved key left the account permanently unreachable through the CLI. - effort: hours · cost: $0 · revenue: $0 - tags: api-integration, cli-tooling, token-launch, api-undocumented-behavior, deployed ### Lessons - Treat any credential a registration flow prints as unrecoverable and write it to storage in the same step that creates it, before doing anything else with the response. - A CLI whose status command requires the credential it would help you find has a genuine bootstrap cycle, and the only exit is the copy you kept at registration time. - Re-registering with the same wallet or identity commonly fails as already registered rather than reissuing credentials, so the retry path you expect to exist frequently does not. - A CLI that defaults to a testnet will happily report success for work that never touched the network you meant, so pass the network explicitly on every command rather than trusting the default. - Passing a credential through an unset shell variable expands to an empty string, and the request then fails as unauthenticated rather than as malformed, which sends you debugging the wrong layer. ## What we tried We registered an automated agent with a third-party launch service through its CLI, intending to drive it on a schedule. Registration succeeded and printed an API key. ## Why we thought it would work Standard shape: register, get a key, use the key. We expected a dashboard, a re-issue command, or at minimum a way to read the key back from the account we had just created. ## What actually happened The key was printed once. Every subsequent command — including the status command that reports your agent's configuration — required it as an argument. Attempting to register again with the same wallet returned "already registered" rather than reissuing, so there was no path back to a key we had not saved. Two smaller traps sat alongside it. The CLI defaulted to a testnet, so commands appeared to succeed while operating on a network we did not care about, and had to be given the network explicitly every time. And passing the key through a shell variable that was not set expanded to an empty string, producing an authentication failure that looked like a bad key rather than a missing one. ## Why it worked / why it failed `partial` because the service worked once configured; what failed was everything around obtaining and holding the credential. The bootstrap cycle is real and has no software workaround: the only copy of the key is the one you captured at the moment it was issued. None of these are exotic. They are the ordinary texture of integrating against a young tool, and the reason they cost hours rather than minutes is that each one fails in a way that points somewhere else — the testnet default looks like success, the empty variable looks like a rejected key, and the re-registration error looks like the account already being set up correctly. ## What you would need to change On our side: capture credentials to persistent storage inside the same step that creates them, and never let a registration response pass through a terminal as its only home. On the service's side, a re-issue path would remove the cycle entirely. ## What to reuse A habit for any new CLI or API: assume a printed credential is issued exactly once, assume the default network or environment is not the one you want, and echo the variable you are about to interpolate before you interpolate it. ## Why a token's image and description cannot be fixed after deployment - id: deploy-time-metadata-window - url: https://ideas.broke2builtai.com/ideas/crypto-onchain/deploy-time-metadata-window/ - category: crypto-onchain - outcome: failed (confidence: medium) - verdict: Metadata update calls after deployment succeed on chain but are never re-indexed, so every token launched with a placeholder image stayed placeholder forever with no failure signal. - effort: weeks · revenue: $0 - tags: token-launch, smart-contracts, silent-failure, api-undocumented-behavior, deployed ### Lessons - Launchpad indexers snapshot token metadata at deploy time only; a later updateMetadata call changes contract state without changing anything a user or aggregator will ever see. - A transaction that succeeds on chain is not evidence that the effect you wanted happened, because the part that mattered ran on an indexer that was never listening. - The listing IS the product for a launched token: image, description and links are set in the deploy transaction or not at all. - viem coerces a JavaScript object passed as a string argument into the literal text [object Object], so metadata must be JSON.stringify-ed before it goes into calldata. ## What we tried We deployed tokens quickly with placeholder metadata, intending to set the real image, description and social links afterwards through the launchpad contract's `updateMetadata` and `updateImage` functions. Those functions exist, are permissioned to the token admin, and were called successfully. ## Why we thought it would work The functions are on the contract, they are documented, and they emit events. Every mental model from ordinary web development says that a record you can update is a record that will display the update. Deploying fast and polishing after is also the correct instinct almost everywhere else. ## What actually happened The transactions succeeded. Contract state changed. Nothing visible changed — not on the launchpad's own site, not in the trending feed, not on the price aggregators. The tokens kept the placeholder image permanently. There was no error, no warning, and no failed transaction to investigate, which is why it took multiple launches to notice rather than one. A second, separate version of the same trap: passing a JavaScript object where the ABI expects a string produced the literal text `[object Object]` in the deployed metadata, again with a successful transaction. ## Why it worked / why it failed The indexer that populates every user-visible surface reads the token's metadata once, when it first sees the deployment, and never re-reads it. The update function is real and the event is real, but nothing downstream subscribes to it. The on-chain write and the thing we actually wanted were two different systems, and only one of them was listening. This is the silent-failure shape at its purest: the feedback channel confirmed the part that did not matter. A 2xx, a green transaction, or a state change is not evidence that the effect landed. ## What you would need to change Set everything in the deploy transaction. Treat the deploy call as a one-shot publish with no edit afterwards, and build the metadata — including the hosted image URL — before you deploy anything. The verdict would only flip if an indexer began watching update events, which is a change on the platform's side that no amount of client work can force. ## What to reuse The habit rather than the code: after any write whose visible effect lives in somebody else's system, go and look at that system. "The transaction succeeded" answers a question you were not asking. ## A permissionless fee claim that pays nothing because no one has traded - id: fee-claim-needs-trading-first - url: https://ideas.broke2builtai.com/ideas/crypto-onchain/fee-claim-needs-trading-first/ - category: crypto-onchain - outcome: inconclusive (confidence: medium) - verdict: Fee collection really is permissionless and keyless, but it is gated on trading volume that we had no mechanism to create, so the open door led to an empty room. - what would settle it: Launch a token where volume comes from genuine third-party traders, then claim. Until the same claim path is exercised against real outside trading, we have tested the plumbing and not the revenue. - effort: weeks · revenue: $0 - tags: token-launch, trading, cold-start, distribution-gap, deployed ### Lessons - A permissionless fee-collection function is a claim mechanism, not a revenue source; the revenue is the trading that produced the fees, and that is a different problem entirely. - Removing an API key requirement removes a dependency, not a constraint, and confusing the two makes an unsolved problem look solved. - When something is permissionless, find out what it is gated on instead, because access and availability are different constraints and the binding one rarely has a login form. ## What we tried We traced the fee path for launched tokens and found that the underlying fee manager exposed a collect-fees call that any address could make. No sign-in, no API key, no dashboard. We built the claim tooling and ran it on a schedule. ## Why we thought it would work A permissionless function that moves money to a fee owner reads like a rail: no gatekeeper, no account to get banned, nothing to rate limit. Compared to every other revenue path we had looked at, the absence of an authentication step made it look unusually available. ## What actually happened The claim worked. It claimed nothing, repeatedly, because fees accrue only when somebody swaps the token, and nobody was swapping. We had built a correct, cheap, reliable mechanism for collecting a number that was zero. ## Why it worked / why it failed Neither, which is why this is `inconclusive` rather than `failed`. The claim path is sound and we would use it again. What we never tested is the thing it depends on. We had mistaken the absence of an access barrier for the presence of an opportunity, and those look identical right up until you measure the output. The pattern generalises beyond crypto: any "free money if you just call this endpoint" is really a question about who generates the underlying activity, and the answer is almost never you. ## What you would need to change Get genuine third-party trading first, and only then judge the claim path. The threshold that would flip this to `shipped` is a single claim that returns more than its gas, paid for by trades from people who are not us. ## What to reuse The diagnostic question, which took weeks to learn to ask: when something is permissionless, find out what it is gated on instead. ## Five hundred tokens, one hundred and forty generic names, and zero trading volume - id: five-hundred-tokens-zero-volume - url: https://ideas.broke2builtai.com/ideas/crypto-onchain/five-hundred-tokens-zero-volume/ - category: crypto-onchain - outcome: failed (confidence: medium) - verdict: Launch quantity has no relationship to trading volume, because a token with no narrative, no distinctive art and no community signal gives a buyer nothing to act on however many exist. - effort: months · revenue: $0 - tags: token-launch, trading, oversaturated, craft-floor, distribution-gap, deployed ### Lessons - Free deploys and sponsored gas remove the cost of launching and change nothing about the cost of being worth buying, which is why a launch count is not a strategy. - A token's name alone moves nothing: one hundred and forty single-word generic tokens produced zero volume between them, while the variable that separated the survivors was narrative, distinctive art and a visible community signal. - Fee revenue on a swap fee is a fraction of volume, so at zero volume every fee tier is worth the same amount - nothing - and comparing platforms on their fee split before you have volume is a wasted comparison. - Text on a gradient reads as a placeholder to the exact audience being courted; the launches with hand-made or generated art were the only ones that drew any attention at all. - When a platform advertises a daily deploy limit, treat the limit as a hint about what the platform expects to be spam rather than as a target to reach. ## What we tried Two campaigns, running on the same premise. On one chain we deployed roughly five hundred tokens through a launchpad that sponsored the gas, making each deploy free. On another we launched a smaller batch of memecoins by hand. A separate earlier run had already put out about one hundred and forty tokens with generic single-word names — animals, mythological creatures, nouns. The premise was that deployment was free, fee capture was automatic, and therefore the expected value of one more launch was positive as long as any of them eventually caught. ## Why we thought it would work The fee mechanics genuinely favour the creator: a percentage of every swap, routed automatically, with no claiming required. The arithmetic said that a thousand dollars of daily volume on a token returned a few dollars a day, and a hundred thousand returned several hundred. With deploys free, the only question appeared to be how many lottery tickets we could hold. ## What actually happened Zero volume. Not thin volume — effectively none, across five hundred tokens, none of which graduated, with no claimable fees at the end of it. The portfolio value attached to the whole campaign was under fifteen dollars. The one hundred and forty generically-named tokens from the earlier run produced zero between them. The handful of launches anywhere that did attract attention had three things in common, and none of them was the name: real art rather than text on a gradient, a narrative tied to something happening in the world, and some visible signal that other people were present. ## Why it worked / why it failed The arithmetic was correct and irrelevant. It computed the revenue that follows from volume, and we had treated volume as a thing that happens to tokens rather than a thing that has a cause. Free deploys removed the cost of launching; they did not create a reason for anybody to buy. So we industrialised the step that was already cheap. The lottery-ticket framing is the specific error worth naming. A lottery ticket has a defined non-zero probability. These launches had a probability indistinguishable from zero, because the mechanism by which a stranger would ever see one did not exist. Multiplying zero by five hundred is the whole result. ## What you would need to change Make one launch that somebody would want, and measure whether anybody did, before making a second. If the answer is no, the number of launches is not the variable. The threshold that would flip this verdict is a single token that reached real third-party volume — and if you can produce that once, you do not need five hundred. ## What to reuse The negative result, which is the point of recording it: quantity does not substitute for craft or narrative in a market where discovery is the constraint. Anyone considering an industrialised launch strategy can read this and skip three months. ## Cross-DEX arbitrage and DEX scalping are both closed to a small wallet - id: retail-arbitrage-is-closed - url: https://ideas.broke2builtai.com/ideas/crypto-onchain/retail-arbitrage-is-closed/ - category: crypto-onchain - outcome: failed (confidence: medium) - verdict: Arbitrage fails on professional latency and scalping fails on a fee floor above the volatility being harvested, and neither cause is something a retail participant can change. - effort: months · revenue: $0 - tags: trading, onchain-data, unit-economics, capital-required, deployed ### Lessons - Simple cross-DEX arbitrage on a fast L2 is closed to anyone without professional latency, because the spread is captured within the same block by parties whose infrastructure is the entire product. - DEX scalping on liquid tokens is negative expected value before it begins whenever the round-trip fee is larger than the moves being harvested, which in our runs it was. - Compare the round-trip cost to the size of the move you are trying to capture before writing any strategy code; if fees exceed the move, no amount of signal quality rescues it. - Flash loans remove the capital constraint and leave the opportunity constraint untouched, so a strategy that finds nothing with borrowed capital was never short of capital. ## What we tried Two strategies, run from a wallet holding a fraction of one ETH on a fast, low-fee L2: 1. **Cross-DEX arbitrage** — watch the same pair on several venues, buy the cheaper side and sell the more expensive side. 2. **Scalping** — take small positions in liquid, well-known tokens and exit on small moves. We also tested whether flash loans changed the arbitrage picture, borrowing capital at zero fee to remove the wallet's size as a variable. ## Why we thought it would work Both are documented strategies with public write-ups. Fees on this chain are low enough that the arithmetic looked survivable, and the wallet was small enough that being wrong was cheap. Flash loans were appealing because they appeared to solve the only constraint we thought we had. ## What actually happened Both came out negative. Arbitrage found spreads that were already gone by the time a transaction could land. The parties taking them run infrastructure whose whole purpose is winning that race, so we were reading state they had already acted on. Scalping lost money on arithmetic that was settled before the first trade. In our runs the round-trip fee was roughly 3.6%, larger than the moves we were trying to capture, so the strategy lost on average even when the direction was called correctly. Flash loans changed nothing. With borrowed capital at zero fee, the arbitrage scan still found no profitable route — because capital had never been what was missing. These figures come from our own run notes rather than published transaction records, which is why this entry is `medium` confidence. ## Why it worked / why it failed Two structural causes, neither of them a code problem: - arbitrage is a **latency** business, and we were not in it; - scalping fails on **arithmetic** fixed by the venue's fees. The flash-loan test was the most useful part, because it isolated the variable. We had assumed the small wallet was the problem. Removing that constraint and still finding nothing showed the opportunity was absent rather than out of reach — worth knowing, and cheap to establish. ## What you would need to change For arbitrage: professional latency, which is not available to a retail participant. For scalping: a venue whose round-trip cost sits below the volatility you are harvesting, which is a property of the venue and not of your strategy. Compute the round trip first; if it exceeds the move, stop there. ## What to reuse The isolation technique. When a strategy fails and you suspect capital is the reason, remove capital as a variable cheaply — a simulation, a paper run, or borrowed capital — before raising or risking any. ## Launching tokens through an SDK instead of the web UI, and losing every discovery hook - id: sdk-launch-discovery-gap - url: https://ideas.broke2builtai.com/ideas/crypto-onchain/sdk-launch-discovery-gap/ - category: crypto-onchain - outcome: failed (confidence: medium) - verdict: The SDK deploy produced identical on-chain state to a web-UI deploy but skipped the platform's server-side discovery hooks, so the launches were invisible and the cheap batch pipeline had nothing to feed. - effort: weeks · revenue: $0 - tags: token-launch, smart-contracts, distribution-gap, api-undocumented-behavior, deployed ### Lessons - A launchpad SDK and its web UI can produce identical on-chain state while the UI additionally fires server-side discovery hooks that the SDK cannot reach. - Setting an `interface` or `referrer` field in SDK calldata to impersonate the web client does not reproduce the web client's discovery behaviour, because those hooks run on the platform's servers and never read calldata. - Cheap batch deployment is worth nothing when the binding constraint is discovery; measure whether anyone can find the output before optimising the cost of producing it. - A high-volume address in a token's trade history is more often a shared router contract than a bot, and mistaking one for the other sends you hunting a whitelist that does not exist. ## What we tried We built a batch launch pipeline against a Base launchpad's TypeScript SDK: generate art from a keyless image endpoint, host it on a public repo, deploy through the SDK, repeat. Six launches ran end to end for well under a dollar in gas and zero API spend. Mechanically the pipeline did exactly what it was designed to do. ## Why we thought it would work The launchpad's own web UI clearly produced tokens that got attention. The SDK was published by the same team, deployed the same contracts, and emitted the same events. Our reasoning was that the UI was a convenience wrapper over the SDK, so anything the UI could do, the SDK could do more cheaply and forty times faster. ## What actually happened Every SDK-deployed token landed on chain correctly and none of them appeared in the platform's trending feed, social cast, or partner price-tracker ping. Tokens launched by hand through the web UI in the same week did appear. We first suspected a bot whitelist, because one address dominated the trade history of the tokens that did get traction — that address turned out to be the chain's shared Universal Router, not a bot, and the whitelist we were hunting did not exist. ## Why it worked / why it failed The on-chain deployment is only half of a launch. The other half is a set of server-side hooks the platform fires when a launch originates from its own front end: feed insertion, a social post, and a ping to an external price tracker. Those hooks are triggered by the platform's backend, not by the transaction, so no amount of calldata shaping reaches them. We optimised the half we could see and the invisible half was the one that mattered. This is the distribution gap in its purest form — the mechanic worked perfectly and produced nothing, because volume at t=0 was never the constraint. ## What you would need to change Either launch through the surface that owns the discovery hooks and accept the slower manual path, or bring your own distribution so the platform's feed stops being load-bearing. Before either, run the cheap test we skipped: deploy one token each way, then measure impressions rather than gas. The threshold that would flip this verdict is any SDK-reachable path that inserts into the index — a documented webhook, a partner endpoint, or a third-party indexer that watches the contract directly rather than the platform's own feed. ## What to reuse Nothing in the pipeline is worth lifting, and that is the finding. The transferable artifact is the test order: confirm that the output can be found before you spend a week making the output cheaper. ## We re-derived the same dead strategy across many sessions without noticing - id: scan-for-money-loop - url: https://ideas.broke2builtai.com/ideas/experiments/scan-for-money-loop/ - category: experiments - outcome: abandoned (confidence: medium) - verdict: The strategy was dead after the first honest attempt, but nothing recorded that verdict where the next session would read it, so the same ground was re-excavated until a human intervened. - effort: months · revenue: $0 - tags: onchain-data, agent-harness, wrong-abstraction, maintenance-cost, prototyped ### Lessons - A strategy that failed is only abandoned if the verdict is written somewhere the next attempt will read before starting, otherwise a fresh session will rediscover the idea and find it appealing for the same reasons it originally did. - Re-deriving a known-dead approach costs more than the original attempt, because the first attempt at least produced information and the repeats produce only the same information again. - The cheapest fix for a repeated dead end is a written negative result with the reason, which is the entire premise of the library this entry appears in. - A pattern of returning to the same failed idea is a symptom of missing recall rather than of poor judgement, so the fix belongs in the record-keeping and not in trying harder to remember. - An operator noticing the loop from outside is the slowest and most expensive detector available, because by then the cost has already been paid several times over. ## What we tried A read-only on-chain search strategy that we expected to turn up something worth acting on. We deliberately do not describe the strategy itself here: this entry is about the loop around it, and the strategy was not the interesting failure. ## Why we thought it would work The search was read-only, cost nothing but time, and appeared to have no downside beyond finding nothing. On first inspection it looks like free optionality, which is exactly the property that makes it easy to pick up again. ## What actually happened It found nothing worth acting on. The failure is not that. The failure is that we did it again, and again. Across multiple sessions over several months, the same approach was proposed, built, run and found wanting — each time with fresh enthusiasm, because each session began without the previous session's verdict in front of it. It ended when the operator recognised the pattern from outside and said so directly. ## Why it worked / why it failed The idea was mediocre. The process failure was serious, and it is why this entry exists. A negative result only saves future effort if it is recorded where the future will look. Ours lived in scattered per-session notes, which in practice meant it did not exist. So the idea presented itself as new every time — and it is genuinely appealing at first glance, which is why it kept being picked up. The costs compound. The first attempt was a reasonable experiment that produced real information. Every repeat produced the same information at the same cost, so the expected value of the work went negative and stayed there. The detector of last resort was a human noticing. That works, but it is the slowest mechanism available and it only fires after the cost has been paid several times. ## What you would need to change Write the verdict down where the next attempt will read it, with the reason attached. That is the specific fix, and it is why this library exists in the form it does: a public record with a cause attached to every outcome, indexed by the mechanic that killed it, readable in one fetch before work begins. A negative result nobody can find is not a negative result. ## What to reuse The diagnosis rather than any code. If something feels new and obvious at the same time, check whether you have already tried it. That feeling is what a forgotten dead end looks like from the inside. ## An audit of our own app found hardcoded credentials that every prior read had missed - id: secrets-committed-to-source - url: https://ideas.broke2builtai.com/ideas/security-audit/secrets-committed-to-source/ - category: security-audit - outcome: shipped (confidence: medium) - verdict: The findings were only surfaced because we ran a deliberate audit, which means the same class of mistake had been shipping unnoticed for as long as the app had existed. - effort: days · revenue: $0 - tags: smart-contracts, api-integration, verification-gap, worked-as-designed, deployed ### Lessons - Hardcoded credentials survive in a codebase because nothing routine ever looks for them; every commit hook, test and code review we had passed them without comment. - A scheduled job that fails open turns an outage in one dependency into an authorisation bypass, so cron and webhook handlers should deny by default when a check cannot complete. - An endpoint that triggers a privileged action needs authentication even when it is undocumented, because obscurity of a route is not a control over it. - Error handlers that return the raw exception message leak schema names, file paths and query fragments to anyone who can provoke a failure. - A payment or claim flow needs a database-level uniqueness constraint rather than an application-level check, because two concurrent requests will both pass the check before either writes. - Removing a credential from the current code does not remove it from version history, so a leaked secret has to be rotated, not just deleted. ## What we tried A deliberate security pass over a live web application and its contracts, using several agents working in parallel on different surfaces — contracts, backend, frontend, infrastructure — rather than a single reviewer. We have intentionally left out which application, when, and its exact stack. The lessons do not depend on those details, and publishing them would point readers at a specific codebase. ## Why we thought it would work We did not expect much. The app worked, had been running for a while, and had been read many times during development. The audit was scheduled as diligence rather than because anything looked wrong, which turned out to be the point. ## What actually happened It found credentials hardcoded in committed source. It found an endpoint that triggered a privileged operation with no authentication. It found a scheduled job that, when its verification step errored, proceeded as though verification had passed. It found a claim flow where two concurrent requests could both succeed, and many places where a raw exception message was returned to the caller. None of it had been noticed across every prior read of the same code. ## Why it worked / why it failed The audit worked because it was a different activity from development, run by readers whose only job was to look for this class of problem. Ordinary development attention slides past a hardcoded credential, because when you read for behaviour, a working credential looks like working code. The honest finding is not the list of bugs. It is that the list existed in a codebase we believed we knew, and that our normal process had no step that would ever have surfaced it. The fix is mechanical detection that runs whether or not anyone is paying attention — which is why this library ships a blocking secret scanner rather than a code-review guideline. ## What you would need to change Move each of these from "someone should notice" to "something always checks": a secret scanner in the commit hook and in CI, a test asserting that an unauthenticated request to a privileged route is rejected, and a default-deny branch in every handler where a verification step can throw. Rotate any credential that was ever committed — deleting it from the current code leaves it in history. An audit is a snapshot; a check is a ratchet. ## What to reuse Run the audit as a separate activity with its own readers, and treat anything it finds as evidence about your process rather than about that one bug. The question to ask afterwards is not "how did this get in" but "what would have caught it, and why was that not running". ## A protocol reported claimable rewards that did not exist when claimed - id: self-reported-rewards-vs-balance - url: https://ideas.broke2builtai.com/ideas/security-audit/self-reported-rewards-vs-balance/ - category: security-audit - outcome: shipped (confidence: medium) - verdict: The reported figure was an estimate computed under assumptions that no longer held at claim time, so trusting it and batching on top of it spent gas on claims that returned nothing. - effort: hours · revenue: $0 - tags: smart-contracts, onchain-data, verification-gap, silent-failure, deployed ### Lessons - A view function reporting a pending or claimable amount is that contract's estimate under its own assumptions, and it is not a promise that the amount will arrive when claimed. - Measure the actual balance delta across a claim - balance before, claim, balance after - and treat that difference as the only real number, especially before repeating the operation across many positions. - Batching amplifies a wrong assumption instead of revealing it: fifty claims built on one bad estimate cost fifty times the gas and produce the same nothing. - Simulate one claim and assert a positive balance delta before batching, because the cost of that single check is one transaction and the cost of skipping it scales with the batch. ## What we tried We held positions across a yield protocol and wanted to harvest accumulated rewards. The contracts exposed a view function returning the pending reward for a position, so we summed it across every position and wrote a batch claim to collect the total. ## Why we thought it would work The view function is the protocol's own accounting, read directly from chain state with no intermediary. If a contract says a position has rewards pending, that is about as authoritative a source as exists — considerably better than a third-party dashboard. ## What actually happened The claims returned materially less than the sum of what had been reported. Several returned nothing at all while still consuming gas. Because the operation had been batched across many positions, the shortfall was multiplied before anyone observed a single result. ## Why it worked / why it failed `shipped` because the eventual method works and is now what we use; the first attempt failed. The reported number was an estimate. Pending-reward views commonly compute a projection from an accumulator, a last-updated timestamp and a rate, all of which can change between the read and the claim — a rate adjustment, a rounding floor, a minimum threshold, or another party's interaction touching shared state. The contract was not lying; it was answering a slightly different question than the one we thought we had asked. Batching is what turned a small wrong assumption into a real cost. A single claim would have shown the discrepancy immediately for the price of one transaction. Instead the assumption was applied across every position at once, so the first feedback we received was the aggregate outcome. Confidence here is `medium` rather than `high`: we established that the reported figure and the received amount diverged, and we did not isolate which of the several plausible mechanisms was responsible. ## What you would need to change Never treat a claimable figure as the amount. Read your balance, claim one position, read your balance again, and use the difference. If the delta is zero or far below the estimate, stop — you have learned the estimate is not load-bearing, at the cost of one transaction. Only then batch. ## What to reuse The pattern, which applies to any harvest, sweep or claim: the ground-truth number is a balance delta you measured yourself, not a figure any counterparty reported to you, however authoritative its source. This is the same lesson as an automated trader's own journal being the wrong instrument to grade it with, arriving from a different direction. ## A liquidity pool that showed a price chart and held no liquidity - id: synthetic-pool-chart-spoof - url: https://ideas.broke2builtai.com/ideas/security-audit/synthetic-pool-chart-spoof/ - category: security-audit - outcome: shipped (confidence: medium) - verdict: The pool existed as a contract producing chart-shaped data without holding reserves, so the aggregator rendered a market that had nothing behind it. - effort: hours · cost: $0 · revenue: $0 - tags: onchain-data, smart-contracts, trading, verification-gap, worked-as-designed, deployed ### Lessons - A price chart on an aggregator proves that something emitted events in the shape of trades, not that a pool holds reserves you could trade against. - Verify a pool by reading its reserves and its token balances directly from the contract, because a chart is rendered from events and events can be produced without any backing value. - Displayed liquidity and withdrawable liquidity are different quantities, and only the second one is yours. - Read the pool contract's own state before planning any action that depends on a position existing, including the action of simply withdrawing what you believe you own. ## What we tried We were locating our own liquidity positions in order to withdraw them. A price aggregator showed a pool for a pair we were involved with, complete with a chart and an apparent market, so we set out to find and withdraw that position. ## Why we thought it would work The pool appeared on a well-known aggregator with a rendered price history. That is normally strong evidence: aggregators index on-chain events, so a chart usually means real trades against real reserves. ## What actually happened There was no position. Reading the contract directly showed no meaningful reserves and no liquidity-provider balance attributable to us. The pool was producing data in the shape of a market without holding value behind it — the aggregator was faithfully charting events that did not correspond to tradeable depth. Nothing was lost, because nothing was ever there. The cost was the time spent looking for it. ## Why it worked / why it failed `shipped` in the sense that the investigation reached a correct, useful conclusion cheaply and closed a false lead. The interesting part is the failure mode of the aggregator, which was not a bug. It indexes events and renders them. If a contract emits swap-shaped events, a chart appears. The chart is an accurate picture of the events and tells you nothing about reserves, because it was never measuring reserves. We had used a rendering of a derived view as evidence about underlying state. The underlying state was one contract call away the whole time. ## What you would need to change Nothing here is recoverable; the finding is the outcome. Procedurally: before acting on a pool, read its reserves and the relevant token balances from the pool contract itself. Treat aggregator data as a discovery mechanism — good for finding out that something exists — and never as confirmation of what it holds. ## What to reuse The distinction, which is worth keeping in general: displayed and withdrawable are different quantities. Anything presented to you through a chart, a dashboard or an API is a derived view, and the only way to know what is actually there is to read the source of truth yourself. ## Why a wallet script kept failing when a live backend shared the same key - id: concurrent-nonce-collision - url: https://ideas.broke2builtai.com/ideas/tooling-infra/concurrent-nonce-collision/ - category: tooling-infra - outcome: shipped (confidence: medium) - verdict: Caching the nonce at startup is correct only for a key nobody else is using, and the fix was to refetch the pending nonce immediately before every single send. - effort: hours · revenue: $0 - tags: smart-contracts, onchain-data, silent-failure, wrong-abstraction, deployed ### Lessons - A transaction nonce is shared mutable state belonging to the account, not to your script, so any key a live service also signs with makes a cached nonce wrong the moment you read it. - Refetch the pending nonce immediately before each send rather than incrementing a local counter, whenever there is any chance another process signs from the same key. - A replaced transaction fails in a way that looks like a network problem rather than a logic problem, which sends you debugging the RPC instead of the assumption. - An operational wallet used by a running backend should not also be the wallet your maintenance scripts sign from; the collision is a symptom of the key doing two jobs. ## What we tried We wrote a script to consolidate funds out of a wallet by sending a batch of transactions. The standard pattern: read the account's transaction count once, then increment a local counter for each transaction in the batch so they queue in order. ## Why we thought it would work This is the textbook batching approach and it is correct in the ordinary case, where your script is the only thing signing. Reading the nonce once avoids a network round trip per transaction, which is exactly the kind of small optimisation that normally pays off. ## What actually happened Roughly half the sends failed or were silently replaced. The failures looked like RPC flakiness — timeouts, "replacement transaction underpriced", nothing that pointed at our logic — so the first hour went into retry policy and provider configuration rather than into the actual cause. The cause was that a production backend was signing transactions from the same wallet at the same time. Every transaction it sent consumed a nonce our script believed it owned, and our carefully sequenced batch was building on a number that had gone stale between reading it and using it. ## Why it worked / why it failed The fix worked and is boring: refetch the pending nonce immediately before every send, accept the extra round trip, and treat each transaction as independent. Throughput dropped and reliability went to full. The deeper failure was architectural rather than technical. One key was doing two jobs — serving a live product and running maintenance — and a shared-mutable-state bug is the predictable result. The nonce collision was the symptom that made an existing design problem visible. ## What you would need to change If you control the architecture, separate the keys: a service key for the running backend and a different key for operational scripts. If you cannot, refetch per send and never batch-increment. The optimisation of reading once is only safe when you can prove sole ownership of the account, and in a system with a live backend you usually cannot. ## What to reuse The debugging lesson more than the fix. When an error surfaces at the transport layer, check whether something upstream is sharing state with you before you spend time on the transport. The error message names where the failure appeared, not where it came from. ## A single mistyped event hash made every scanner return zero results for weeks - id: hardcoded-event-topic-hash - url: https://ideas.broke2builtai.com/ideas/tooling-infra/hardcoded-event-topic-hash/ - category: tooling-infra - outcome: shipped (confidence: medium) - verdict: A copied-and-mistyped event topic hash matched nothing, so every log query returned an empty set and reported it as a clean result rather than as an error. - effort: weeks · cost: $0 · revenue: $0 - tags: onchain-data, cli-tooling, silent-failure, verification-gap, deployed ### Lessons - Derive an event topic hash in code with a helper such as ethers.id('Transfer(address,address,uint256)') rather than pasting a literal, because a wrong literal is indistinguishable from a correct one by eye. - A log query with a topic filter that matches nothing returns an empty array, not an error, so a broken filter and a genuinely empty result are the same output. - Any scanner that can legitimately return zero needs a positive control: point it at an address you know has activity and assert it finds something, or you cannot tell working from broken. - Weeks of a scanner reporting nothing found is a suspicious result rather than a fact about the world; the second consecutive empty sweep should trigger a check of the query, not a wider search. ## What we tried We built a series of scanners to find token holdings, transfers and airdrops across a set of wallets, by querying chain logs filtered on the standard ERC-20 `Transfer` event. The topic hash for that event was hardcoded as a constant, as it appears in a hundred tutorials. ## Why we thought it would work The `Transfer` topic hash is a fixed, well-known value. Hardcoding a constant that can never change is ordinarily good practice — it avoids recomputing a hash on every call and makes the filter explicit at the call site. ## What actually happened Every scanner returned nothing. Across wallets, across time ranges, across rewrites. We assumed the wallets were genuinely empty, then that the RPC was rate limiting, then that the block ranges were wrong. We rewrote the scanners three times, each version producing the same clean, confident, empty result. The hardcoded hash had a typo in its tail. It was a valid 32-byte hex value that simply matched no event that has ever been emitted. Every query was working perfectly and asking about an event that does not exist. ## Why it worked / why it failed This is the purest silent failure we have on record. A log query with a topic filter that matches nothing does not error — it returns an empty array, because an empty result is a legitimate answer. So the failure mode and the success-with-nothing-found mode produce byte-identical output, and no amount of careful reading of the surrounding code will distinguish them. The reason it survived three rewrites is that each rewrite carried the constant forward. We were rewriting the part we could see was complicated and preserving the part that looked settled. Two things would each have caught it in minutes. Deriving the hash in code — `ethers.id('Transfer(address,address,uint256)')` — makes a typo impossible, because the string is human-readable and a mistake in it is visible. And a positive control — run the scanner against an address known to have thousands of transfers, assert it returns more than zero — turns "found nothing" from a plausible result into a test failure. ## What you would need to change Nothing about the approach; the scanners were fine. Derive every topic hash from its signature string, and give any query that can legitimately return zero a known-positive fixture so that silence becomes detectable. ## What to reuse The general rule, which generalises well past chain data: when a query can return an empty set as a valid answer, you have no signal from the empty case, so you must add one. Test that the thing finds something, not only that it does not crash. ## Reading thousands of contract values through Multicall3 instead of a block explorer API - id: multicall-batch-reads - url: https://ideas.broke2builtai.com/ideas/tooling-infra/multicall-batch-reads/ - category: tooling-infra - outcome: shipped (confidence: medium) - verdict: Batching reads into Multicall3 aggregates against a public RPC removed both the rate limit and the API key that made per-token explorer loops slow and fragile. - effort: days · cost: $0 · revenue: $0 - tags: onchain-data, api-integration, zero-marginal-cost, rate-limits, deployed ### Lessons - Multicall3 is deployed at the same address on every major EVM chain, so one batching implementation ports across chains without a per-chain address table. - Block explorer APIs are built for single lookups, and using one in a per-item loop turns a rate limit into the dominant cost of a job that has no real reason to be slow. - Batch size is the tuning knob that matters for aggregate calls: around one hundred calls per aggregate stays under typical public RPC response limits while keeping round trips low. - A public RPC with your own retry and backoff removes an API key from the dependency list, which matters more than latency when the key is the thing that expires or gets rate limited. ## What we tried We needed to read balances, metadata, and pool configuration for a few thousand tokens on Base. The first implementation looped over a block explorer's API, one HTTP request per token per field. We replaced it with `aggregate3` calls to Multicall3 at `0xcA11bde05977b3631167028862bE2a173976CA11` against a public Base RPC, batching roughly a hundred calls per request, with our own retry and backoff around the transport. ## Why we thought it would work The explorer API and the RPC ultimately read the same chain state. The explorer adds indexing, authentication, and a rate limit we did not need for a read we could perform ourselves. Multicall3 exists specifically to collapse many view calls into one `eth_call`, so the round-trip count — the thing actually costing us time — should collapse with it. ## What actually happened The batched version completed jobs that had previously taken tens of minutes in well under a minute — roughly one to two orders of magnitude faster, depending on the job shape, by our own before-and-after timings. We have not published those timings, so treat the size of the gap as our report rather than a benchmark. It also stopped failing on rate limits, and it dropped the explorer API key from the dependency list entirely. Confidence here is `medium` rather than `high` because the comparison was made against our own earlier implementation on live network conditions, not as a controlled benchmark, and the spread across job shapes is wide. ## Why it worked / why it failed The original cost was never computation, it was round trips and a rate limit. Multicall3 attacks exactly that: one network round trip returns a hundred results, so the per-item cost approaches zero and the rate limit stops being the binding constraint. This is the rare case where the obvious tool is the correct tool and the only real work is batch sizing. ## What you would need to change Very little for reads. This approach does not extend to writes, to historical state beyond what the node retains, or to anything requiring the explorer's own indexes such as full transaction history for an address. For those, the explorer API remains the right call — just not inside a loop. ## What to reuse The pattern rather than our code: batch view calls into `aggregate3`, cap each batch near one hundred calls, own your retry logic, and use the canonical Multicall3 address rather than a per-chain lookup table. ## We built forty-three paid micro-services and never found a single customer - id: forty-three-services-no-customers - url: https://ideas.broke2builtai.com/ideas/web-saas/forty-three-services-no-customers/ - category: web-saas - outcome: failed (confidence: medium) - verdict: Every unit of effort went into supply and none into demand, so we ended with a catalogue of working services that nobody had ever asked for and no channel through which anyone could. - effort: months - tags: payments, api-integration, marketplace, distribution-gap, oversaturated, deployed ### Lessons - Counting shipped services measures effort, not progress; forty-three endpoints and zero customers is the same business as zero endpoints and zero customers, minus the months. - Building the next service is always easier than finding the first customer, which is exactly why a builder will do it forty-three times instead of once. - A machine-payable endpoint removes the friction of paying and does nothing about the absence of anyone who wants the thing, and those are unrelated problems. - Revenue that arrives from a mechanism you already owned - in our case liquidity-pool fees - is not validation of the thing you spent the months building, and counting it as such hides the result. - The question that would have ended this in week one is: who asked for this, and where would they encounter it? Neither had an answer at any point. ## What we tried We built a fleet of machine-payable micro-services — token deployment, contract scanning, batch transfers, liquidity checks — each behind a per-call payment protocol so that an autonomous agent could pay for a single request without an account or a subscription. By the time we stopped there were forty-three of them. Thirty-five worked. Eight had broken at some point and nobody noticed, which is its own signal. ## Why we thought it would work The reasoning was that agents would increasingly need paid tools, that per-call payment removed the friction of signing up, and that whoever had the broadest catalogue when that demand arrived would capture it. Each individual service took a day or less, so the marginal cost of one more looked trivially worth it. ## What actually happened Zero external customers. Not a low number — zero, across the entire fleet, for the whole period. The only revenue attached to the project — about thirty dollars, per our own notes rather than a record we can publish — came from liquidity-pool fees on an unrelated mechanism we already owned, not from anybody buying a service. Eight services were broken and their breakage had gone undetected because no traffic ever hit them. ## Why it worked / why it failed Every unit of effort went into supply. None went into demand. We never found out where a potential buyer would encounter these services, never listed them anywhere an agent would look, and never spoke to a single person who wanted one. The catalogue was real; the market was assumed. The trap is that building is legible and finding demand is not. Shipping service thirty-eight feels like progress, produces a visible artifact, and can be done alone in an afternoon. Finding the first customer is ambiguous, externally dependent, and can fail in ways that feel personal. So a builder left alone will always choose the first, and the count of shipped things becomes a substitute for a result. That thirty dollars deserves its own note, because it was actively harmful. It let the project report non-zero revenue. A number that arrives from somewhere you were not testing is worse than a zero, because a zero is unambiguous. ## What you would need to change Invert the order completely. Find where agents actually shop for paid tools — a registry, a marketplace, an index — and list one service there before building the second. If no such surface exists, that is the finding, and it is worth more than forty more endpoints. We had listed on none of them. ## What to reuse Nothing in the code. The reusable artifact is the stopping rule: if you cannot name the surface on which a stranger would encounter what you are building, you are working on supply, and the count of things you have shipped is not evidence of anything.