Attribution
Every swap can carry a sourceId and referrer field via hookData. The official hook emits a SwapAttribution event for every attributed swap. Permissionless, live from day one.
The official 111 pool is a composable host. Anyone can route swaps through it, get credit on-chain, and earn a small builder fee — without forking liquidity, without weakening the core, without permission.
Every swap can carry a sourceId and referrer field via hookData. The official hook emits a SwapAttribution event for every attributed swap. Permissionless, live from day one.
Up to 0.25% of swap volume can flow to the referrer on every attributed swap. The payment comes exclusively from the protocol slice; live-bid funding stays structurally untouched.
Every swap takes a 6% baseline skim. The hook splits it at swap time into three legs. The math is enforced on-chain: no path exists for a referral payment to reduce live-bid funding.
Pass attribution data as the hookData argument of any Uniswap V4 swap on the 111 pool. Bad encoding falls through — the swap never reverts on malformed hookData.
import { encodeAbiParameters } from "viem";
// Both envelopes are encoded as 1-tuple structs (not 2-tuples of
// bytes). The hook decodes the OUTER bytes as
// `abi.decode(swapData, (PoolSwapData))` — a single struct argument.
// Encoding as a 2-tuple silently fails to decode and attribution is
// treated as empty.
const pcSwapData = encodeAbiParameters(
[
{
type: "tuple",
components: [
{
name: "attribution",
type: "tuple",
components: [
{ name: "sourceId", type: "bytes32" },
{ name: "referrer", type: "address" },
{ name: "campaignId", type: "bytes16" },
{ name: "referralBps", type: "uint24" },
],
},
{ name: "extensionPayload", type: "bytes" },
],
},
],
[
{
attribution: {
sourceId: "0x...32-byte builder id...",
referrer: "0xYourPayoutAddress",
campaignId: "0x...16-byte campaign id (or zeros)...",
referralBps: 250, // 0.25% of volume — clamped at the hook
},
extensionPayload: "0x", // reserved for future use
},
],
);
const hookData = encodeAbiParameters(
[
{
type: "tuple",
components: [
{ name: "mevModuleSwapData", type: "bytes" },
{ name: "poolExtensionSwapData", type: "bytes" },
],
},
],
[{ mevModuleSwapData: "0x", poolExtensionSwapData: pcSwapData }],
);
// Pass `hookData` as the final argument of your V4 swap call.Referrals land in ReferralPayout within the same tx as the attributed swap — the hook flushes the credited referrer's accrual at the end of _afterSwap. No separate flush call is needed in normal operation; the balance is immediately claimable.
// View balance any time — populated by the credited swap
const owed = await referralPayout.balances(referrerAddress);
// Claim — by the referrer, or by anyone on the referrer's behalf
referralPayout.claim(); // pulls msg.sender's balance
referralPayout.claimFor(referrerAddr); // sends balance to referrerAddr
// There are no hook-side retry hatches: the hook flushes each swap's
// referral fresh inside _afterSwap and holds no balance between swaps.
// If a payout ever fails (reverting recipient / out of gas) the hook
// folds that swap's slice back into the protocol leg, nothing to retry.event SwapAttribution(
PoolId indexed poolId,
address indexed swapper,
address indexed referrer,
bytes32 sourceId,
bytes16 campaignId,
uint256 quoteVolume,
uint256 referralPaid
);
event SkimSplit(
PoolId indexed poolId,
uint256 quoteVolume,
uint256 bountyAmount,
uint256 protocolNet,
uint256 referralPaid
);
event ReferralCredited (address indexed referrer, uint256 amount);
event ReferralClaimed (address indexed referrer, uint256 amount);For every swap S on the 111 pool:
bountyInflow(S) == volume(S) × 5% + antiSniperExtra(S)
protocolInflow(S)
+ referralPaid(S) == volume(S) × 1%
referralPaid(S) <= volume(S) × min(hookData.referralBps, 250) / 100_000
referralPaid(S) == 0 if no referrer is attributed (stays in protocol leg)