Documentation
Approvals
Which contract pulls your funds depends on what you are calling and whether the asset is native.
Native HYPE never needs an approval: it travels as msg.value.
The matrix#
| Action | Asset | Approve | Send as value |
|---|---|---|---|
factory.launchToken | launch fee | nothing | launchFee() |
launchAndBuy on a HYPE config | fee + buy | nothing | launchFee() + quoteIn |
launchAndBuy on an ERC-20 config | buy | the quote token, to TartarusLaunchAndBuy, for quoteIn | launchFee() |
| Buy on a HYPE pool | HYPE | nothing | the amount in |
| Buy on an ERC-20 pool | quote | Permit2, then the router | nothing |
| Sell | the token | Permit2, then the router | nothing |
feeEscrow.claim / claimToken | none | nothing | nothing |
sweepPoolFees / sweepPoolQuoteFees | none | nothing | nothing |
distributor.claim / batchClaim | none | nothing | nothing |
The launch fee is always native HYPE, even on an ERC-20 config, and msg.value must match exactly.
Sending more does not refund: it reverts.
Swaps need two grants#
UniversalRouter does not pull your tokens directly. It pulls them through Permit2, so there are two separate allowances and each has its own failure.
// 1. The token lets Permit2 move it. A plain ERC-20 approval.
await wallet.writeContract({
address: token, abi: erc20Abi, functionName: "approve", args: [PERMIT2, amount],
});
// 2. Permit2 lets the router move it. This one expires.
await wallet.writeContract({
address: PERMIT2, abi: permit2Abi, functionName: "approve",
args: [token, ROUTER, amount, expiry], // amount is uint160, expiry is uint48
});| Missing | Revert |
|---|---|
| Step 1 | a transfer error, from the token |
| Step 2 was never made, or has expired | AllowanceExpired (0xd81b2f2e), which reads as if a grant lapsed even when none was ever made |
| Step 2 is smaller than the swap, or an earlier swap used it up | InsufficientAllowance (0xf96fb071) |
Check both before asking the user to sign:
const onToken = await client.readContract({
address: token, abi: erc20Abi, functionName: "allowance", args: [account, PERMIT2],
});
const [onPermit2, expiration] = await client.readContract({
address: PERMIT2, abi: permit2Abi, functionName: "allowance", args: [account, token, ROUTER],
});The Tartarus site grants the exact amount of each swap with a short expiry, so no allowance stays open afterwards. Granting more saves transactions and leaves a standing approval. That is your call to make.
Why not approve the factory#
Older launchpads pull fees with an allowance. Tartarus does not: the factory never moves your ERC-20s.
The only contract in the protocol you ever approve directly is TartarusLaunchAndBuy, and only for an
ERC-20 opening buy. It takes exactly what the pool charges and holds nothing between transactions.