"Transaction failed" is not an error message. It's a shrug.
Error design is wallet safety, not polish. When something breaks, the user is about to decide something - retry, re-sign, send again, or go ask a stranger. Your copy shapes which path they take.
A failure state owes them three things: what failed, what you actually know, and what's safe next.
โ Not enough for the fee. They may hold the asset fine. What's short is spendable native coin on the selected chain. Say that, and name the chain. A bare "insufficient funds" leaves someone staring at an asset they can see, deciding your wallet is broken.
โ Destination doesn't validate for this chain. A string can be well-formed under another chain's rules and still fail here, so don't stop at "invalid address" - that sounds like the recipient handed over a broken string. Name the chain. If you can't tell which chain it was meant for, say only what you know.
โ Input unavailable - and why matters. Already spent? Refresh and rebuild. Tied up in a pending transaction? A normal retry won't free it; point at that transaction, or let them pick different inputs. Excluded by policy? Say which rule - if you protect inscription-bearing outputs, that's protection working, not stale state. Three causes, three next steps. "Try again" explains none of them.
โ Broadcast failed or rejected - name the layer. A service can fail before the transaction reaches a node. A node can reject on consensus rules, on its own mempool policy, or on a conflict it already sees. Those aren't the same answer. If it fails consensus rules under the current chain state, it can't be accepted in that state - say that plainly. A policy rejection, a transport error, or an empty lookup doesn't prove no peer saw it.
That last distinction is the one that protects people. Rebroadcasting the same signed bytes is not a second payment. Building a fresh transaction can be.
Much of this is catchable earlier, too - inspect the inputs and estimate the fee while you're still constructing, before anything gets signed.
And when you genuinely don't know: "Broadcast failed, here's exactly what came back" beats a confident guess every time.
Why this is safety and not decoration: a dead end sends people looking, and whoever answers fastest may ask for a seed phrase. No legitimate support agent needs one.
"Failed" is what your code experienced. Tell them what happened to their attempt - and what's safe to do now.
An active listing you no longer mean is still executable.
A fixed-price listing isn't a suggestion. It's a standing offer. While the listing remains active and valid, a buyer can take it at the posted price - regardless of whether you'd still accept that price today.
The marketplace has no idea your thinking changed.
It only knows the ask is up.
So if:
โ the market moved and your old number is now well below what you'd take,
โ you changed your mind about selling at all
โ or you set a "whatever, I'll never get it" price months ago and forgot
โฆnone of that reaches the marketplace. A buyer can take that stale ask, and the listing's sale price is the one you posted - not the price you would choose today.
That's why unlisting isn't housekeeping. It's risk management. Pulling an ask that no longer reflects your intent closes the gap between what the market can execute and what you'd actually agree to.
If a listing no longer matches what you mean, the fix isn't remembering it's there. It's taking it down.
An active ask you forgot about is still an ask.
A PFP collection has to solve two problems at once: make each piece feel like an individual, and keep the whole set recognizably from one world.
Four Du$tinal Dogs @builderdogecoin, shown at the same size. Across this sample, the shared visual grammar is easy to see:
โ the pixel scale - the visible grid stays consistent, so the traits feel drawn in the same visual language
โ the framing - a three-quarter bust, facing right, cropped at the chest
โ the base construction - upright triangular ears, the same muzzle wedge, the same eye-and-nose placement
โ the core contour - a black line keeps the silhouette readable, and in the first two examples a white halo around it adds a second layer of separation
Then the traits take over. Inscription #177,936,052 is the least accessorized of the four: an orange coat, a sewer-brick background, no accessories. #177,936,364 reuses that same sewer backdrop - brick for brick - and becomes someone else entirely: brown coat, red laser beams, a jetpack venting flame, pink bubble gum, a ghost cat watching calmly from the corner.
Same wall. Same construction. Different character.
#177,936,544 and #177,936,638 share a coat treatment and still pull opposite ways. One pairs crossing laser beams with gold coins spilling from its mouth, chains behind it, and a startled ghost at its shoulder. The other wears a miner's helmet and lamp, with a vertical rainbow pouring from its mouth in a cobwebbed attic.
But here's the design lesson worth borrowing. Most of these traits read as additions - something placed on the dog, or behind it. The coat trait labeled "lost" in the collection metadata behaves differently in the final image: on the pieces carrying it, the contour treatment changes. The halo doesn't vanish - it goes dim. It is bright white on the first two; on the last two, it shifts to a grey that barely lifts the dog off what's behind it.
So #177,936,544 isn't just washed out. Its second separation layer is weakened, and against the grey warehouse the figure nearly merges with the scene at this size. #177,936,638 stays easier to read under the same treatment, helped by a darker, warmer attic and the brighter helmet and rainbow.
In this sample, traits that read as additions leave the shared grammar intact. Traits that alter that grammar are where a collection's coherence gets tested.
Keep the grammar stable. Then the traits can get as strange as you like.