Search Token Jars
Find Token Jars, price their contents, and release them, including failure modes and the sharp edges worth knowing first.
A Token Jar's contents can be redeemed at a fixed price. A searcher sends the jar's threshold in its resource token and takes the assets they name. This guide covers how to find jars, price what they hold, and release them without reverting.
Everything here describes the shipped ExchangeReleaser. A jar owner can adopt a custom releaser, so read an unfamiliar releaser's code before trading against it.
The Trade
A release is profitable when
value(the named assets) > threshold + gasThe searcher sends the threshold in the jar's resource token. If a jar's threshold is 1,000 USDC and gas costs $2, a release is profitable once the named assets are worth more than $1,002 to the searcher.
The assets arrive as-is, and converting them is up to the searcher.
The threshold does not rise as the jar fills. A jar becomes more profitable to clear the longer it goes unswept. The first searcher who finds it profitable clears it, so searchers compete on the smallest margin they will accept.
A release costs the full threshold no matter how many assets it names. Naming fewer assets does not lower the price. It leaves value for the next searcher. Name the most valuable assets, up to maxReleaseLength and what gas allows.
Finding Jars
Jars are discovered through events. The Factory keeps no registry.
| Event | Contract | Meaning |
|---|---|---|
JarDeployed | Factory | A new jar and its first releaser, already paired. |
ReleaserDeployed | Factory | A standalone releaser for an existing jar, inert until the jar adopts it. |
ReleaserSet | jar | The jar switched releasers. The latest one is live. |
Only ReleaserSet makes a releaser live. Anyone can deploy a releaser that points at any jar, and one the jar never adopted cannot move any asset. A releaser deployed outside the Factory shows up only through ReleaserSet.
The Factory and the Fee Controller sit at the same address on every chain. An operator who deploys from the same account with the same salt gets the same jar address on every chain, so key jars by chain id and address.
For a quick scan, the TJAR Dashboard lists existing jars and their release terms.
Pricing a Jar
A jar does not record what it holds, and no onchain call lists the tokens it has received. Build the asset list from ERC-20 Transfer logs where to is the jar, plus call traces for native token transfers, since those emit no log. Then read each balance with balanceOf.
Read the releaser's live state directly:
cast call $RELEASER "nonce()(uint256)" --rpc-url $RPC
cast call $RELEASER "releaseCost()(uint256,uint256)" --rpc-url $RPC
cast call $RELEASER "resource()(address)" --rpc-url $RPC
cast call $RELEASER "maxReleaseLength()(uint256)" --rpc-url $RPC
cast call $JAR "releaser()(address)" --rpc-url $RPC # still this one?Making a Release
- Read
releaseCost(). It returns(total, fee)at the current block. With no protocol fee set,totalequals thethresholdandfeeis 0. - Approve the releaser to spend
totalof theÂresource. - Call
releasewith the current nonce andmaxFeeset to the fee from step 1.
releaser.release(
uint256 nonce, // must equal releaser.nonce() when the call executes
Currency[] assets, // plain addresses; address(0) means the native token
address recipient, // where the assets land; may not be address(0)
uint256 maxFee // upper bound on the protocol fee
);Currency is a user-defined value type over address. In the ABI it is address, so an address[] encodes correctly.
The Nonce
The nonce locks the release terms between simulation and execution. If the nonce a searcher simulated at still matches when the transaction executes, the resource, resourceRecipient, threshold, and maxReleaseLength have not changed.
Two things advance the nonce:
- every release
- every
setConfigcall, even one that changes nothing
If the nonce moved, the transaction reverts with InvalidNonce. Losing a race costs gas and nothing else. Since config changes also advance it, count Released events to count releases, not the nonce.
The nonce does not cover the protocol fee. No fee is set today. If one is set, the Fee Controller adds it on top of the threshold, capped at 1,000 bps, and every releaser reads it at execution. A release reverts with FeeExceedsMax when the fee at execution is above maxFee.
Failure Modes
| Revert | From | Cause | What to do |
|---|---|---|---|
InvalidNonce() | releaser | Another release or a config change landed first. | Re-read nonce() and simulate again. |
Unauthorized() | jar | The releaser is not the jar's current one. It was rotated out or never adopted. | Read jar.releaser() and use that. |
TooManyAssets(count, max) | releaser | The asset list is longer than maxReleaseLength, 20 by default. | Drop the lowest-value assets. |
FeeExceedsMax(fee, maxFee) | releaser | A protocol fee was set or raised above maxFee after simulation. | Re-read releaseCost() and reprice. |
InvalidRecipient() | releaser | recipient is the zero address. | Use 0xdead to burn released assets. |
TRANSFER_FROM_FAILED | releaser | Pulling the resource from the caller failed, usually from low balance or allowance, or the token's pause or blacklist rules. | Check balance and allowance against total. |
| A token's own revert | jar | A named asset reverted on balanceOf or transfer. | Remove it. See Sharp Edges. |
Sharp Edges
The native token is address(0) in the asset list, and its transfer emits no log. Reconciling releases from logs alone misses it.
One address with no code reverts the whole release. The exception is address(0), which means the native token. The jar calls balanceOfSelf() on every other named asset, and that call reverts for an address with no code. Check that every address in the list other than address(0) has code before submitting. Assets the jar holds none of are fine, and naming them costs only gas.
A rotated-out releaser still looks live. A jar owner can switch to a new releaser at any time. The old one keeps returning a normal config from its view functions and reverts with Unauthorized() on every release. Watch ReleaserSet on the jar instead of trusting the releaser's own state.
Fee-on-transfer resources are send-denominated. The caller is debited exactly the threshold, plus the protocol fee as a second transfer if one is set. Each recipient may receive less than the nominal amount.
Every named token runs code during the release. A token that reverts on transfer reverts the whole release, so drop any asset that fails in simulation.
Where to Go Next
- Deployments for Factory and Fee Controller addresses per chain.
- Releasers for the terms model and rotation.
- Token Jar for what the jar does and does not track.