Contracts
Three small contracts, with no owner, no admin functions and no upgrade path. Their code is the same for everyone; what makes a batcher or a delegate yours is the addresses built into it.
The factory
Section titled “The factory”SweepFactory is shared: one per chain, at the same address on every chain, deployed through the standard CREATE2
deployer with a zero salt.
| Chain | Factory | Deployed |
|---|---|---|
BSC testnet 97 Testnet | 0x66f63A7246dF0eD76098dD95ad0F53d8e7bed456 | transaction not recorded |
Sepolia 11155111 Testnet | 0x66f63A7246dF0eD76098dD95ad0F53d8e7bed456 | block 11876757 |
| Compiler | solc 0.8.33+commit.64118f21, EVM prague, optimizer 200 runs |
|---|---|
| CREATE2 | deployer 0x4e59b44847b379578588920cA78FbF26c0B4956C, salt 0x0000000000000000000000000000000000000000000000000000000000000000 |
| Code hashes | init code 0x8978addd4a3a80d6acb85369e765f8200edf5f2ae440241994bf3b2b11021320runtime code 0xa805b3e8e2915685aaa97e94e623be43255d68ab0ce7886c272242d082cc475d |
| commit | 97eb1efe5c30bb96de87da6e726601602d30d360 |
The factory deploys your contracts when you register a hot wallet, from your gas wallet: whoever calls it becomes the gas wallet of what it deploys, so nobody can deploy contracts in your name. Its functions are idempotent.
| Function | What it does |
|---|---|
deployBatcher() | Deploys the caller’s batcher, or returns it. |
deployDelegate(hotWallet) | Deploys the delegate of hotWallet for the caller’s batcher, or returns it. |
onboard(hotWallet) | Both at once. |
predictBatcher(gasWallet) | The address of a gas wallet’s batcher, deployed or not. |
predictDelegate(hotWallet, gasWallet) | The address of a hot wallet’s delegate, deployed or not. |
Your batcher
Section titled “Your batcher”SweepBatcher(gasWallet): one per gas wallet, and only that gas wallet may call it. sweepMany sweeps a list of
deposit addresses in one transaction, giving each at most 300,000 gas, so that one failing address cannot stop the
rest. It emits Swept(account, token, amount) for each success and SweepFailed(account, token, reason) with the
error selector for each failure.
Your delegates
Section titled “Your delegates”SweepDelegate(hotWallet, batcher): one per hot wallet, the code your deposit addresses run. Its single function,
sweep(token, amount), sends the address’s token, or its native coin for the zero address, to the hot wallet, and
only when your batcher calls; amount 0 means the whole balance. It supports tokens that return nothing from
transfer, such as USDT on Ethereum, and reads at most 32 bytes of a token’s answer. It keeps the address receiving
native coin and calls like a plain address.
| Error | Meaning |
|---|---|
Unauthorized() | The caller is not your batcher, or not your gas wallet. |
NothingToSweep() | The balance to sweep is 0. |
TokenTransferFailed() | The token refused the transfer. |
NativeTransferFailed() | The hot wallet refused the native coin. |
ZeroAddress() | A delegate cannot be built for the zero address. |
Verify what is on chain
Section titled “Verify what is on chain”Every address above follows from the contract code: compute them from the code and compare. The SDK’s
verify-delegate does it in one command; Verifying the delegate shows it and the
manual way with Foundry. The compiler settings and code hashes above are those of the deployed factory.
On mainnet, the source of the factory and of each batcher and delegate will also be verified on Etherscan and Sourcify. Testnet deployments are not verified in explorers: check them by their addresses. For the contract source, ask support@portuna.io.