<!-- GENERATED by scripts/build.mjs — do not edit by hand. Run `npm run build`. -->

# Lessons

Every lesson in the library — 70 of them — grouped by the mechanic that produced it.
Each line is written to survive being quoted on its own, and each has a stable anchor so a
citation lands on the exact claim.

## rate-limits

*A throughput cap was the binding constraint.*

- <a id="multicall-batch-reads-1"></a>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. — [Reading thousands of contract values through Multicall3 instead of a block explorer API](/ideas/tooling-infra/multicall-batch-reads/) `shipped`
- <a id="multicall-batch-reads-2"></a>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. — [Reading thousands of contract values through Multicall3 instead of a block explorer API](/ideas/tooling-infra/multicall-batch-reads/) `shipped`
- <a id="multicall-batch-reads-3"></a>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. — [Reading thousands of contract values through Multicall3 instead of a block explorer API](/ideas/tooling-infra/multicall-batch-reads/) `shipped`
- <a id="multicall-batch-reads-4"></a>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. — [Reading thousands of contract values through Multicall3 instead of a block explorer API](/ideas/tooling-infra/multicall-batch-reads/) `shipped`

## distribution-gap

*It worked and nobody found it. The classic killer.*

- <a id="fee-claim-needs-trading-first-1"></a>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. — [A permissionless fee claim that pays nothing because no one has traded](/ideas/crypto-onchain/fee-claim-needs-trading-first/) `inconclusive`
- <a id="fee-claim-needs-trading-first-2"></a>Removing an API key requirement removes a dependency, not a constraint, and confusing the two makes an unsolved problem look solved. — [A permissionless fee claim that pays nothing because no one has traded](/ideas/crypto-onchain/fee-claim-needs-trading-first/) `inconclusive`
- <a id="fee-claim-needs-trading-first-3"></a>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. — [A permissionless fee claim that pays nothing because no one has traded](/ideas/crypto-onchain/fee-claim-needs-trading-first/) `inconclusive`
- <a id="five-hundred-tokens-zero-volume-1"></a>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. — [Five hundred tokens, one hundred and forty generic names, and zero trading volume](/ideas/crypto-onchain/five-hundred-tokens-zero-volume/) `failed`
- <a id="five-hundred-tokens-zero-volume-2"></a>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. — [Five hundred tokens, one hundred and forty generic names, and zero trading volume](/ideas/crypto-onchain/five-hundred-tokens-zero-volume/) `failed`
- <a id="five-hundred-tokens-zero-volume-3"></a>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. — [Five hundred tokens, one hundred and forty generic names, and zero trading volume](/ideas/crypto-onchain/five-hundred-tokens-zero-volume/) `failed`
- <a id="five-hundred-tokens-zero-volume-4"></a>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. — [Five hundred tokens, one hundred and forty generic names, and zero trading volume](/ideas/crypto-onchain/five-hundred-tokens-zero-volume/) `failed`
- <a id="five-hundred-tokens-zero-volume-5"></a>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. — [Five hundred tokens, one hundred and forty generic names, and zero trading volume](/ideas/crypto-onchain/five-hundred-tokens-zero-volume/) `failed`
- <a id="sdk-launch-discovery-gap-1"></a>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. — [Launching tokens through an SDK instead of the web UI, and losing every discovery hook](/ideas/crypto-onchain/sdk-launch-discovery-gap/) `failed`
- <a id="sdk-launch-discovery-gap-2"></a>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. — [Launching tokens through an SDK instead of the web UI, and losing every discovery hook](/ideas/crypto-onchain/sdk-launch-discovery-gap/) `failed`
- <a id="sdk-launch-discovery-gap-3"></a>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. — [Launching tokens through an SDK instead of the web UI, and losing every discovery hook](/ideas/crypto-onchain/sdk-launch-discovery-gap/) `failed`
- <a id="sdk-launch-discovery-gap-4"></a>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. — [Launching tokens through an SDK instead of the web UI, and losing every discovery hook](/ideas/crypto-onchain/sdk-launch-discovery-gap/) `failed`
- <a id="forty-three-services-no-customers-1"></a>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. — [We built forty-three paid micro-services and never found a single customer](/ideas/web-saas/forty-three-services-no-customers/) `failed`
- <a id="forty-three-services-no-customers-2"></a>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. — [We built forty-three paid micro-services and never found a single customer](/ideas/web-saas/forty-three-services-no-customers/) `failed`
- <a id="forty-three-services-no-customers-3"></a>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. — [We built forty-three paid micro-services and never found a single customer](/ideas/web-saas/forty-three-services-no-customers/) `failed`
- <a id="forty-three-services-no-customers-4"></a>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. — [We built forty-three paid micro-services and never found a single customer](/ideas/web-saas/forty-three-services-no-customers/) `failed`
- <a id="forty-three-services-no-customers-5"></a>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. — [We built forty-three paid micro-services and never found a single customer](/ideas/web-saas/forty-three-services-no-customers/) `failed`

## cold-start

*Needed users/liquidity/data it could not bootstrap.*

- <a id="fee-claim-needs-trading-first-1"></a>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. — [A permissionless fee claim that pays nothing because no one has traded](/ideas/crypto-onchain/fee-claim-needs-trading-first/) `inconclusive`
- <a id="fee-claim-needs-trading-first-2"></a>Removing an API key requirement removes a dependency, not a constraint, and confusing the two makes an unsolved problem look solved. — [A permissionless fee claim that pays nothing because no one has traded](/ideas/crypto-onchain/fee-claim-needs-trading-first/) `inconclusive`
- <a id="fee-claim-needs-trading-first-3"></a>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. — [A permissionless fee claim that pays nothing because no one has traded](/ideas/crypto-onchain/fee-claim-needs-trading-first/) `inconclusive`

## unit-economics

*Each unit cost more than it earned.*

- <a id="retail-arbitrage-is-closed-1"></a>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. — [Cross-DEX arbitrage and DEX scalping are both closed to a small wallet](/ideas/crypto-onchain/retail-arbitrage-is-closed/) `failed`
- <a id="retail-arbitrage-is-closed-2"></a>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. — [Cross-DEX arbitrage and DEX scalping are both closed to a small wallet](/ideas/crypto-onchain/retail-arbitrage-is-closed/) `failed`
- <a id="retail-arbitrage-is-closed-3"></a>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. — [Cross-DEX arbitrage and DEX scalping are both closed to a small wallet](/ideas/crypto-onchain/retail-arbitrage-is-closed/) `failed`
- <a id="retail-arbitrage-is-closed-4"></a>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. — [Cross-DEX arbitrage and DEX scalping are both closed to a small wallet](/ideas/crypto-onchain/retail-arbitrage-is-closed/) `failed`

## api-undocumented-behavior

*The API did something the docs did not say.*

- <a id="circular-api-key-registration-1"></a>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 registration flow where you need the API key to retrieve the API key](/ideas/automation-ops/circular-api-key-registration/) `partial`
- <a id="circular-api-key-registration-2"></a>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. — [A registration flow where you need the API key to retrieve the API key](/ideas/automation-ops/circular-api-key-registration/) `partial`
- <a id="circular-api-key-registration-3"></a>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 registration flow where you need the API key to retrieve the API key](/ideas/automation-ops/circular-api-key-registration/) `partial`
- <a id="circular-api-key-registration-4"></a>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. — [A registration flow where you need the API key to retrieve the API key](/ideas/automation-ops/circular-api-key-registration/) `partial`
- <a id="circular-api-key-registration-5"></a>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. — [A registration flow where you need the API key to retrieve the API key](/ideas/automation-ops/circular-api-key-registration/) `partial`
- <a id="deploy-time-metadata-window-1"></a>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. — [Why a token's image and description cannot be fixed after deployment](/ideas/crypto-onchain/deploy-time-metadata-window/) `failed`
- <a id="deploy-time-metadata-window-2"></a>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. — [Why a token's image and description cannot be fixed after deployment](/ideas/crypto-onchain/deploy-time-metadata-window/) `failed`
- <a id="deploy-time-metadata-window-3"></a>The listing IS the product for a launched token: image, description and links are set in the deploy transaction or not at all. — [Why a token's image and description cannot be fixed after deployment](/ideas/crypto-onchain/deploy-time-metadata-window/) `failed`
- <a id="deploy-time-metadata-window-4"></a>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. — [Why a token's image and description cannot be fixed after deployment](/ideas/crypto-onchain/deploy-time-metadata-window/) `failed`
- <a id="sdk-launch-discovery-gap-1"></a>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. — [Launching tokens through an SDK instead of the web UI, and losing every discovery hook](/ideas/crypto-onchain/sdk-launch-discovery-gap/) `failed`
- <a id="sdk-launch-discovery-gap-2"></a>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. — [Launching tokens through an SDK instead of the web UI, and losing every discovery hook](/ideas/crypto-onchain/sdk-launch-discovery-gap/) `failed`
- <a id="sdk-launch-discovery-gap-3"></a>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. — [Launching tokens through an SDK instead of the web UI, and losing every discovery hook](/ideas/crypto-onchain/sdk-launch-discovery-gap/) `failed`
- <a id="sdk-launch-discovery-gap-4"></a>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. — [Launching tokens through an SDK instead of the web UI, and losing every discovery hook](/ideas/crypto-onchain/sdk-launch-discovery-gap/) `failed`

## silent-failure

*It reported success while doing nothing.*

- <a id="autotrader-journal-hid-losses-1"></a>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. — [An automated trading fleet that reported itself as roughly break-even while losing money](/ideas/ai-agents/autotrader-journal-hid-losses/) `failed`
- <a id="autotrader-journal-hid-losses-2"></a>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. — [An automated trading fleet that reported itself as roughly break-even while losing money](/ideas/ai-agents/autotrader-journal-hid-losses/) `failed`
- <a id="autotrader-journal-hid-losses-3"></a>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. — [An automated trading fleet that reported itself as roughly break-even while losing money](/ideas/ai-agents/autotrader-journal-hid-losses/) `failed`
- <a id="autotrader-journal-hid-losses-4"></a>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. — [An automated trading fleet that reported itself as roughly break-even while losing money](/ideas/ai-agents/autotrader-journal-hid-losses/) `failed`
- <a id="deploy-time-metadata-window-1"></a>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. — [Why a token's image and description cannot be fixed after deployment](/ideas/crypto-onchain/deploy-time-metadata-window/) `failed`
- <a id="deploy-time-metadata-window-2"></a>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. — [Why a token's image and description cannot be fixed after deployment](/ideas/crypto-onchain/deploy-time-metadata-window/) `failed`
- <a id="deploy-time-metadata-window-3"></a>The listing IS the product for a launched token: image, description and links are set in the deploy transaction or not at all. — [Why a token's image and description cannot be fixed after deployment](/ideas/crypto-onchain/deploy-time-metadata-window/) `failed`
- <a id="deploy-time-metadata-window-4"></a>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. — [Why a token's image and description cannot be fixed after deployment](/ideas/crypto-onchain/deploy-time-metadata-window/) `failed`
- <a id="self-reported-rewards-vs-balance-1"></a>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. — [A protocol reported claimable rewards that did not exist when claimed](/ideas/security-audit/self-reported-rewards-vs-balance/) `shipped`
- <a id="self-reported-rewards-vs-balance-2"></a>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. — [A protocol reported claimable rewards that did not exist when claimed](/ideas/security-audit/self-reported-rewards-vs-balance/) `shipped`
- <a id="self-reported-rewards-vs-balance-3"></a>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. — [A protocol reported claimable rewards that did not exist when claimed](/ideas/security-audit/self-reported-rewards-vs-balance/) `shipped`
- <a id="self-reported-rewards-vs-balance-4"></a>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. — [A protocol reported claimable rewards that did not exist when claimed](/ideas/security-audit/self-reported-rewards-vs-balance/) `shipped`
- <a id="concurrent-nonce-collision-1"></a>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. — [Why a wallet script kept failing when a live backend shared the same key](/ideas/tooling-infra/concurrent-nonce-collision/) `shipped`
- <a id="concurrent-nonce-collision-2"></a>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. — [Why a wallet script kept failing when a live backend shared the same key](/ideas/tooling-infra/concurrent-nonce-collision/) `shipped`
- <a id="concurrent-nonce-collision-3"></a>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. — [Why a wallet script kept failing when a live backend shared the same key](/ideas/tooling-infra/concurrent-nonce-collision/) `shipped`
- <a id="concurrent-nonce-collision-4"></a>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. — [Why a wallet script kept failing when a live backend shared the same key](/ideas/tooling-infra/concurrent-nonce-collision/) `shipped`
- <a id="hardcoded-event-topic-hash-1"></a>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 single mistyped event hash made every scanner return zero results for weeks](/ideas/tooling-infra/hardcoded-event-topic-hash/) `shipped`
- <a id="hardcoded-event-topic-hash-2"></a>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. — [A single mistyped event hash made every scanner return zero results for weeks](/ideas/tooling-infra/hardcoded-event-topic-hash/) `shipped`
- <a id="hardcoded-event-topic-hash-3"></a>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. — [A single mistyped event hash made every scanner return zero results for weeks](/ideas/tooling-infra/hardcoded-event-topic-hash/) `shipped`
- <a id="hardcoded-event-topic-hash-4"></a>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. — [A single mistyped event hash made every scanner return zero results for weeks](/ideas/tooling-infra/hardcoded-event-topic-hash/) `shipped`

## capital-required

*Needed money up front that we would not risk.*

- <a id="retail-arbitrage-is-closed-1"></a>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. — [Cross-DEX arbitrage and DEX scalping are both closed to a small wallet](/ideas/crypto-onchain/retail-arbitrage-is-closed/) `failed`
- <a id="retail-arbitrage-is-closed-2"></a>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. — [Cross-DEX arbitrage and DEX scalping are both closed to a small wallet](/ideas/crypto-onchain/retail-arbitrage-is-closed/) `failed`
- <a id="retail-arbitrage-is-closed-3"></a>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. — [Cross-DEX arbitrage and DEX scalping are both closed to a small wallet](/ideas/crypto-onchain/retail-arbitrage-is-closed/) `failed`
- <a id="retail-arbitrage-is-closed-4"></a>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. — [Cross-DEX arbitrage and DEX scalping are both closed to a small wallet](/ideas/crypto-onchain/retail-arbitrage-is-closed/) `failed`

## oversaturated

*The market already had a hundred of these.*

- <a id="five-hundred-tokens-zero-volume-1"></a>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. — [Five hundred tokens, one hundred and forty generic names, and zero trading volume](/ideas/crypto-onchain/five-hundred-tokens-zero-volume/) `failed`
- <a id="five-hundred-tokens-zero-volume-2"></a>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. — [Five hundred tokens, one hundred and forty generic names, and zero trading volume](/ideas/crypto-onchain/five-hundred-tokens-zero-volume/) `failed`
- <a id="five-hundred-tokens-zero-volume-3"></a>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. — [Five hundred tokens, one hundred and forty generic names, and zero trading volume](/ideas/crypto-onchain/five-hundred-tokens-zero-volume/) `failed`
- <a id="five-hundred-tokens-zero-volume-4"></a>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. — [Five hundred tokens, one hundred and forty generic names, and zero trading volume](/ideas/crypto-onchain/five-hundred-tokens-zero-volume/) `failed`
- <a id="five-hundred-tokens-zero-volume-5"></a>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. — [Five hundred tokens, one hundred and forty generic names, and zero trading volume](/ideas/crypto-onchain/five-hundred-tokens-zero-volume/) `failed`
- <a id="forty-three-services-no-customers-1"></a>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. — [We built forty-three paid micro-services and never found a single customer](/ideas/web-saas/forty-three-services-no-customers/) `failed`
- <a id="forty-three-services-no-customers-2"></a>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. — [We built forty-three paid micro-services and never found a single customer](/ideas/web-saas/forty-three-services-no-customers/) `failed`
- <a id="forty-three-services-no-customers-3"></a>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. — [We built forty-three paid micro-services and never found a single customer](/ideas/web-saas/forty-three-services-no-customers/) `failed`
- <a id="forty-three-services-no-customers-4"></a>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. — [We built forty-three paid micro-services and never found a single customer](/ideas/web-saas/forty-three-services-no-customers/) `failed`
- <a id="forty-three-services-no-customers-5"></a>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. — [We built forty-three paid micro-services and never found a single customer](/ideas/web-saas/forty-three-services-no-customers/) `failed`

## craft-floor

*Output quality was below what anyone would pay for.*

- <a id="five-hundred-tokens-zero-volume-1"></a>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. — [Five hundred tokens, one hundred and forty generic names, and zero trading volume](/ideas/crypto-onchain/five-hundred-tokens-zero-volume/) `failed`
- <a id="five-hundred-tokens-zero-volume-2"></a>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. — [Five hundred tokens, one hundred and forty generic names, and zero trading volume](/ideas/crypto-onchain/five-hundred-tokens-zero-volume/) `failed`
- <a id="five-hundred-tokens-zero-volume-3"></a>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. — [Five hundred tokens, one hundred and forty generic names, and zero trading volume](/ideas/crypto-onchain/five-hundred-tokens-zero-volume/) `failed`
- <a id="five-hundred-tokens-zero-volume-4"></a>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. — [Five hundred tokens, one hundred and forty generic names, and zero trading volume](/ideas/crypto-onchain/five-hundred-tokens-zero-volume/) `failed`
- <a id="five-hundred-tokens-zero-volume-5"></a>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. — [Five hundred tokens, one hundred and forty generic names, and zero trading volume](/ideas/crypto-onchain/five-hundred-tokens-zero-volume/) `failed`

## maintenance-cost

*Kept working but cost too much attention to keep alive.*

- <a id="scan-for-money-loop-1"></a>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. — [We re-derived the same dead strategy across many sessions without noticing](/ideas/experiments/scan-for-money-loop/) `abandoned`
- <a id="scan-for-money-loop-2"></a>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. — [We re-derived the same dead strategy across many sessions without noticing](/ideas/experiments/scan-for-money-loop/) `abandoned`
- <a id="scan-for-money-loop-3"></a>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. — [We re-derived the same dead strategy across many sessions without noticing](/ideas/experiments/scan-for-money-loop/) `abandoned`
- <a id="scan-for-money-loop-4"></a>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. — [We re-derived the same dead strategy across many sessions without noticing](/ideas/experiments/scan-for-money-loop/) `abandoned`
- <a id="scan-for-money-loop-5"></a>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. — [We re-derived the same dead strategy across many sessions without noticing](/ideas/experiments/scan-for-money-loop/) `abandoned`

## verification-gap

*We could not tell whether it was actually working.*

- <a id="autotrader-journal-hid-losses-1"></a>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. — [An automated trading fleet that reported itself as roughly break-even while losing money](/ideas/ai-agents/autotrader-journal-hid-losses/) `failed`
- <a id="autotrader-journal-hid-losses-2"></a>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. — [An automated trading fleet that reported itself as roughly break-even while losing money](/ideas/ai-agents/autotrader-journal-hid-losses/) `failed`
- <a id="autotrader-journal-hid-losses-3"></a>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. — [An automated trading fleet that reported itself as roughly break-even while losing money](/ideas/ai-agents/autotrader-journal-hid-losses/) `failed`
- <a id="autotrader-journal-hid-losses-4"></a>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. — [An automated trading fleet that reported itself as roughly break-even while losing money](/ideas/ai-agents/autotrader-journal-hid-losses/) `failed`
- <a id="secrets-committed-to-source-1"></a>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. — [An audit of our own app found hardcoded credentials that every prior read had missed](/ideas/security-audit/secrets-committed-to-source/) `shipped`
- <a id="secrets-committed-to-source-2"></a>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 audit of our own app found hardcoded credentials that every prior read had missed](/ideas/security-audit/secrets-committed-to-source/) `shipped`
- <a id="secrets-committed-to-source-3"></a>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. — [An audit of our own app found hardcoded credentials that every prior read had missed](/ideas/security-audit/secrets-committed-to-source/) `shipped`
- <a id="secrets-committed-to-source-4"></a>Error handlers that return the raw exception message leak schema names, file paths and query fragments to anyone who can provoke a failure. — [An audit of our own app found hardcoded credentials that every prior read had missed](/ideas/security-audit/secrets-committed-to-source/) `shipped`
- <a id="secrets-committed-to-source-5"></a>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. — [An audit of our own app found hardcoded credentials that every prior read had missed](/ideas/security-audit/secrets-committed-to-source/) `shipped`
- <a id="secrets-committed-to-source-6"></a>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. — [An audit of our own app found hardcoded credentials that every prior read had missed](/ideas/security-audit/secrets-committed-to-source/) `shipped`
- <a id="self-reported-rewards-vs-balance-1"></a>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. — [A protocol reported claimable rewards that did not exist when claimed](/ideas/security-audit/self-reported-rewards-vs-balance/) `shipped`
- <a id="self-reported-rewards-vs-balance-2"></a>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. — [A protocol reported claimable rewards that did not exist when claimed](/ideas/security-audit/self-reported-rewards-vs-balance/) `shipped`
- <a id="self-reported-rewards-vs-balance-3"></a>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. — [A protocol reported claimable rewards that did not exist when claimed](/ideas/security-audit/self-reported-rewards-vs-balance/) `shipped`
- <a id="self-reported-rewards-vs-balance-4"></a>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. — [A protocol reported claimable rewards that did not exist when claimed](/ideas/security-audit/self-reported-rewards-vs-balance/) `shipped`
- <a id="synthetic-pool-chart-spoof-1"></a>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. — [A liquidity pool that showed a price chart and held no liquidity](/ideas/security-audit/synthetic-pool-chart-spoof/) `shipped`
- <a id="synthetic-pool-chart-spoof-2"></a>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. — [A liquidity pool that showed a price chart and held no liquidity](/ideas/security-audit/synthetic-pool-chart-spoof/) `shipped`
- <a id="synthetic-pool-chart-spoof-3"></a>Displayed liquidity and withdrawable liquidity are different quantities, and only the second one is yours. — [A liquidity pool that showed a price chart and held no liquidity](/ideas/security-audit/synthetic-pool-chart-spoof/) `shipped`
- <a id="synthetic-pool-chart-spoof-4"></a>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. — [A liquidity pool that showed a price chart and held no liquidity](/ideas/security-audit/synthetic-pool-chart-spoof/) `shipped`
- <a id="hardcoded-event-topic-hash-1"></a>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 single mistyped event hash made every scanner return zero results for weeks](/ideas/tooling-infra/hardcoded-event-topic-hash/) `shipped`
- <a id="hardcoded-event-topic-hash-2"></a>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. — [A single mistyped event hash made every scanner return zero results for weeks](/ideas/tooling-infra/hardcoded-event-topic-hash/) `shipped`
- <a id="hardcoded-event-topic-hash-3"></a>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. — [A single mistyped event hash made every scanner return zero results for weeks](/ideas/tooling-infra/hardcoded-event-topic-hash/) `shipped`
- <a id="hardcoded-event-topic-hash-4"></a>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. — [A single mistyped event hash made every scanner return zero results for weeks](/ideas/tooling-infra/hardcoded-event-topic-hash/) `shipped`

## wrong-abstraction

*Built the wrong shape and had to restart.*

- <a id="scan-for-money-loop-1"></a>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. — [We re-derived the same dead strategy across many sessions without noticing](/ideas/experiments/scan-for-money-loop/) `abandoned`
- <a id="scan-for-money-loop-2"></a>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. — [We re-derived the same dead strategy across many sessions without noticing](/ideas/experiments/scan-for-money-loop/) `abandoned`
- <a id="scan-for-money-loop-3"></a>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. — [We re-derived the same dead strategy across many sessions without noticing](/ideas/experiments/scan-for-money-loop/) `abandoned`
- <a id="scan-for-money-loop-4"></a>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. — [We re-derived the same dead strategy across many sessions without noticing](/ideas/experiments/scan-for-money-loop/) `abandoned`
- <a id="scan-for-money-loop-5"></a>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. — [We re-derived the same dead strategy across many sessions without noticing](/ideas/experiments/scan-for-money-loop/) `abandoned`
- <a id="concurrent-nonce-collision-1"></a>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. — [Why a wallet script kept failing when a live backend shared the same key](/ideas/tooling-infra/concurrent-nonce-collision/) `shipped`
- <a id="concurrent-nonce-collision-2"></a>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. — [Why a wallet script kept failing when a live backend shared the same key](/ideas/tooling-infra/concurrent-nonce-collision/) `shipped`
- <a id="concurrent-nonce-collision-3"></a>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. — [Why a wallet script kept failing when a live backend shared the same key](/ideas/tooling-infra/concurrent-nonce-collision/) `shipped`
- <a id="concurrent-nonce-collision-4"></a>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. — [Why a wallet script kept failing when a live backend shared the same key](/ideas/tooling-infra/concurrent-nonce-collision/) `shipped`

## worked-as-designed

*The thing did what it said. Rare enough to tag.*

- <a id="never-funded-agent-1"></a>ZERO's positive measured protocol rewards demonstrate that an autonomous agent can earn from an unfunded wallet; estimating how reliably other agents can repeat the result is a separate experiment. — [ZERO earned income without receiving starting capital](/ideas/ai-agents/never-funded-agent/) `revenue`
- <a id="never-funded-agent-2"></a>An agent's unfunded-start experiment loses its funding constraint if the operator supplies external capital; transfers of proceeds the agent earned through its own activity do not break that constraint. — [ZERO earned income without receiving starting capital](/ideas/ai-agents/never-funded-agent/) `revenue`
- <a id="never-funded-agent-3"></a>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. — [ZERO earned income without receiving starting capital](/ideas/ai-agents/never-funded-agent/) `revenue`
- <a id="never-funded-agent-4"></a>Verify a no-funding claim against chain history rather than against the operator's memory: enumerate every inbound transfer and distinguish externally supplied capital from proceeds of the agent's own activity. — [ZERO earned income without receiving starting capital](/ideas/ai-agents/never-funded-agent/) `revenue`
- <a id="never-funded-agent-5"></a>An unfunded wallet does not imply free infrastructure, and measured protocol rewards do not establish profit after all operating costs. — [ZERO earned income without receiving starting capital](/ideas/ai-agents/never-funded-agent/) `revenue`
- <a id="secrets-committed-to-source-1"></a>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. — [An audit of our own app found hardcoded credentials that every prior read had missed](/ideas/security-audit/secrets-committed-to-source/) `shipped`
- <a id="secrets-committed-to-source-2"></a>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 audit of our own app found hardcoded credentials that every prior read had missed](/ideas/security-audit/secrets-committed-to-source/) `shipped`
- <a id="secrets-committed-to-source-3"></a>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. — [An audit of our own app found hardcoded credentials that every prior read had missed](/ideas/security-audit/secrets-committed-to-source/) `shipped`
- <a id="secrets-committed-to-source-4"></a>Error handlers that return the raw exception message leak schema names, file paths and query fragments to anyone who can provoke a failure. — [An audit of our own app found hardcoded credentials that every prior read had missed](/ideas/security-audit/secrets-committed-to-source/) `shipped`
- <a id="secrets-committed-to-source-5"></a>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. — [An audit of our own app found hardcoded credentials that every prior read had missed](/ideas/security-audit/secrets-committed-to-source/) `shipped`
- <a id="secrets-committed-to-source-6"></a>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. — [An audit of our own app found hardcoded credentials that every prior read had missed](/ideas/security-audit/secrets-committed-to-source/) `shipped`
- <a id="synthetic-pool-chart-spoof-1"></a>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. — [A liquidity pool that showed a price chart and held no liquidity](/ideas/security-audit/synthetic-pool-chart-spoof/) `shipped`
- <a id="synthetic-pool-chart-spoof-2"></a>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. — [A liquidity pool that showed a price chart and held no liquidity](/ideas/security-audit/synthetic-pool-chart-spoof/) `shipped`
- <a id="synthetic-pool-chart-spoof-3"></a>Displayed liquidity and withdrawable liquidity are different quantities, and only the second one is yours. — [A liquidity pool that showed a price chart and held no liquidity](/ideas/security-audit/synthetic-pool-chart-spoof/) `shipped`
- <a id="synthetic-pool-chart-spoof-4"></a>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. — [A liquidity pool that showed a price chart and held no liquidity](/ideas/security-audit/synthetic-pool-chart-spoof/) `shipped`

## zero-marginal-cost

*Scaled without the cost scaling.*

- <a id="multicall-batch-reads-1"></a>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. — [Reading thousands of contract values through Multicall3 instead of a block explorer API](/ideas/tooling-infra/multicall-batch-reads/) `shipped`
- <a id="multicall-batch-reads-2"></a>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. — [Reading thousands of contract values through Multicall3 instead of a block explorer API](/ideas/tooling-infra/multicall-batch-reads/) `shipped`
- <a id="multicall-batch-reads-3"></a>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. — [Reading thousands of contract values through Multicall3 instead of a block explorer API](/ideas/tooling-infra/multicall-batch-reads/) `shipped`
- <a id="multicall-batch-reads-4"></a>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. — [Reading thousands of contract values through Multicall3 instead of a block explorer API](/ideas/tooling-infra/multicall-batch-reads/) `shipped`
