DEV Community

Cover image for I Built a Parking Meter for Paragraphs
BoRn SliPPy
BoRn SliPPy

Posted on

I Built a Parking Meter for Paragraphs

Most tip jars live at the bottom of the post. You read 2,000 words, scroll past two newsletter popups, and arrive at a "buy me a coffee" button having completely forgotten which sentence earned the coffee. Mine did too.

So one weekend I moved the tip jar. Now every paragraph has its own little heart. Tap it, and a fraction of a cent in cUSD goes from your wallet to the author's. Tip the paragraph, not the post.

Full honesty about the timeline: this was a weekend idea, which is internet for "not fully thought through." The contract works, the SDK is on npm, real tips have moved on mainnet, and several of the important decisions are still gloriously undecided. This post is half build log, half me asking you to settle an argument I am having with myself. The argument is about money. We will get there.

A tenth of a cent is a valid tip

A tip here is between $0.001 and $0.05. Yes, a tenth of a cent is allowed. This is where most chains check their watch and leave, because the gas to move a cent costs more than the cent. On Ethereum L1 you can burn ~50 cents in gas to send one. That is not a payment, that is a donation to a validator with a rounding error attached.

Celo is the unglamorous reason it works: sub-cent gas, ~1s blocks, and the part that actually matters, you pay gas in the stablecoin itself. No native token to pre-fund. No "acquire token B before you may spend token A" ritual.

Paragraphs have addresses

On-chain, a TipJar (UUPS proxy, verified on Celo mainnet) tracks balances per author. The one genuinely clever line is how a paragraph gets an identity:

bytes32 paragraphKey = keccak256(
    abi.encodePacked(articleId, uint32(index), keccak256(bytes(text)))
);
Enter fullscreen mode Exit fullscreen mode

The text is baked into the key. Fix a typo in paragraph 3 and it gets a brand new key, while tips already sitting on paragraphs 1, 2 and 4 do not move. Tips attach to what was said, not to a line number that slides the second you add a sentence. I am unreasonably proud of this and it took twenty minutes.

The reader never meets the chain

The app spots MiniPay (Opera's stablecoin wallet, preinstalled in plenty of places Ethereum gas has never been invited to), auto-connects, and sets feeCurrency to cUSD so gas and tip come out of the same balance. First tap approves an allowance once. Every tap after is one transaction at sub-cent gas. No signup, no "connect wallet" interpretive dance.

The argument I cannot win alone

Here is the part I am genuinely stuck on: how big is a tip that feels like nothing?

The presets are currently $0.001, $0.005, $0.01 and $0.05, and I have no idea which should be the default. Is one cent a tip or a mild insult? Is a tenth of a cent charming, or am I just wasting your tap? I do not know, so I am going to do the radical thing and ask the people who would actually tap it. That is you. There is a comment box.

While I stall on that, I added a custom amount field so you can type your own number. It does one slightly paranoid thing: it resets to a normal preset after a single tap. Because the alternative is that you set it to $5, find a rhythm, and accidentally fund my entire roadmap in a burst of generosity. Flattering, but I would rather you meant it. (There is also a hard cap, for the same reason. I have met enthusiasm before.)

Two lines to put it on your blog

None of this is useful trapped on my domain, so the tipping part is an npm package, @tipitip/embed, and integration is two lines.

React:

import { TipParagraphs } from "@tipitip/embed";

<TipParagraphs articleId="0xbdc473d818a4a15c39941bd9513dbff8eade14a57825619c39884ce36fa40178" />
Enter fullscreen mode Exit fullscreen mode

Allergic to React? There is a framework-free web component:

<script type="module" src="https://esm.sh/@tipitip/embed/vanilla"></script>
<tipitip-paragraphs article-id="0xbdc473d818a4a15c39941bd9513dbff8eade14a57825619c39884ce36fa40178"></tipitip-paragraphs>
Enter fullscreen mode Exit fullscreen mode

Drop either into a blog, a docs page, or a Farcaster frame and your paragraphs become tippable. It ships with npm provenance, a signed attestation linking the tarball to the exact commit and CI run, because I would like at least one part of this to look like an adult made it.

Things I shipped, things I have very much not decided

  • Which chains next? Celo is where the cent-sized math actually works. The honest catch: the moment I add a second chain, I have to explain to users why they suddenly need a second token for gas, which deletes half the magic. So tell me which chains you want, and whether you are willing to onboard your own readers to fee-currency, because I am tired. (An account-abstraction paymaster is the escape hatch. It is also more work, which is why it is a bullet point and not a feature.)
  • The default tip size: see above. Throw numbers at me. I will read all of them.
  • Anti-Sybil: off, on purpose. There is a storage gap in the contract so I can add it later without redeploying the universe. Bolting on Sybil-resistance before you have users is solving the wrong problem with great enthusiasm.
  • A subgraph: the reader does viem getLogs plus an IndexedDB cache for now. A subgraph is on the list, right after "have enough volume to justify a subgraph."

Your turn

I will read the comments, and two questions decide real things:

  1. What is the largest tip that still feels like nothing to you? Give me a number.
  2. Which chain should be next, and do you actually have users there, or does it just sound nice?

If you would rather poke it directly:

Put it on your own blog and send me the link. I will tip your best paragraph. Probably $0.01. Possibly an insult. We are, as established, still deciding.

Top comments (0)