Improving Results
Once you’re live with Specify, there are a few levers that can meaningfully increase your fill rate, your conversion rate, and ultimately your eCPM. This page covers the three most impactful ones.
Passing multiple wallets
The single biggest lever for most publishers — particularly wallets themselves and any platform that tracks multiple addresses per user — is passing every wallet you know about for a user.
Most publishers allow users to connect one wallet at a time and have no history of others. But if your platform does know about multiple wallets for a single user, you can pass them as an array to our serve function.
Why this matters so much
- Better targeting, better fill. The more active wallets we have for a user, the clearer the picture of what protocols they use — and the more accurately we can match them to a relevant ad. More matches means more ads served
- More conversions caught. We track conversion activity across every wallet you pass us. If a user sees your ad and then tests the advertised product on a separate wallet before moving funds over, we still catch the conversion — and you still earn the revenue share
Example: a wallet publisher
If you’re a wallet, this is especially high-leverage.
A user of your wallet app probably has several accounts set up — a main hot wallet, a hardware wallet imported for size, maybe a separate address for a specific chain. You already know all of those addresses.
Pass the full array to serve and:
- We see the complete onchain footprint of the user, not just whichever address they happen to have selected in your UI
- We can attribute conversions across any of their wallets, not just the active one
- Your inventory becomes dramatically more valuable to advertisers, which feeds into your revenue share tier
This isn’t a privacy compromise either — every address is already known to you through legitimate wallet management, and we use the same publicly available onchain data we’d have access to anyway.
A/B testing placements with ad unit IDs
If you have more than one placement in your app — a header banner, an inline card, a sidebar slot — you’ll want to know which ones are actually performing.
Every call to serve accepts an ad_unit_id, a string you define to label each distinct placement. Specify then tracks impressions and conversions separately per ad unit, so you can see in your dashboard:
- Which placements convert best — and therefore contribute most to your revenue share tier
- Which placements are dragging down your average — and might be better removed, redesigned, or moved
- A/B test results — run two variants of the same placement under different ad unit IDs and compare real conversion data
How to use it
Pick any naming scheme that works for you. Common patterns:
- By location:
header-banner,sidebar-inline,footer-leaderboard - By variant:
swap-card-v1,swap-card-v2 - By page:
dashboard-hero,portfolio-inline,settings-footer
The IDs are opaque to us — they’re purely for your own analysis. Keep them consistent across deploys so your historical data stays comparable.
Why it’s worth doing
Most publishers who A/B test discover that their placements perform very differently from what they expected. A placement that looks great in design review often underperforms a less prominent one that happens to hit users at a more receptive moment. The only way to know is to measure.
Browser caching
Browser caching is particularly valuable for publishers whose users sometimes have an active wallet connection and sometimes don’t — which describes most dapps. A user might connect their wallet to make a swap, then disconnect or have their session time out, and continue browsing your app for a while afterwards.
Without caching, those post-disconnection impressions won’t see ads at all, because we won’t have a wallet address to identify the user.
If you’re calling specify.serve from the client side, enable browser caching (cacheMostRecentAddress) when initialising Specify to greatly increase the percentage of users that see ads.
Note: Don’t enable this if you’re calling Specify from the server side — there’s no local browser storage to use (though it won’t break anything if you do).
How it works
When caching is enabled, we generate a unique random ID on the first serve request where a valid wallet address is found. The next time serve is called — even if no wallet address is currently available — we can retrieve the previously used address from the cache.
Whenever a user connects a new wallet on the same browser, we overwrite the cache so it only ever contains their most recent address.
On privacy
We don’t use third-party cookies. We think they go too far into invading user privacy, and we don’t need them anyway.
That decision extends to browser fingerprinting, which is unregulated and similarly invasive. Our caching uses purely randomly assigned UUIDs to identify returning users. If someone clears their browser’s local storage, we can’t work out who they were before — by design.
You can, of course, implement your own caching if you want to be more aggressive or take a different approach, and pass addresses directly to our SDK.