Docs · DAO

A treasury that signs for itself.

SwapsPro runs inside a Safe as a Safe App. A multisig can swap, place gasless limit orders its owners sign with ERC-1271, pay a whole team in one batched transaction, ladder a treasury sale over days, and deploy a Splits contract that shares out every payment it receives. This page covers the parts that behave differently because the account is a contract rather than a key.

Adding SwapsPro to your Safe#

  1. In your Safe, open Apps and choose Add custom Safe App.
  2. Paste this exact URL:

    Safe App URL

    https://www.swaps.pro/swap
  3. Confirm, then open it. There is nothing to connect — the app detects the Safe by itself.

Use /swap, not the apex

https://www.swaps.pro on its own does not work as a Safe App. Safe fetches <appUrl>/manifest.json cross-origin before it will load anything in its iframe, and it then loads exactly the URL you gave it. The apex serves the marketing landing page — no swap card, no wallet surface — so a Safe pointed at it opens a page with nothing to sign. /swap has its own manifest and icon served CORS-open beside it, and it is the app itself.

the manifest Safe reads

$ curl -s https://www.swaps.pro/manifest.json
{
  "name": "SwapsPro",
  "description": "Swap across chains, send or sweep every token in one confirmation, and place gasless limit orders from your Safe.",
  "iconPath": "icon.svg",
  "safe_apps_permissions": []
}

safe_apps_permissions is empty: SwapsPro asks your Safe for no special permissions. The site's content-security policy allows framing by app.safe.global and its subdomains, and nothing else.

How the connection actually works#

A Safe App gets no injected window.ethereum. The only channel to the Safe is postMessage to the parent frame, which the Safe Apps SDK speaks and which is wrapped into a plain EIP-1193 provider. Two things follow:

  • There is nothing to approve. Being open inside the Safe is the authorisation, so connecting is silent and identical on a fresh visit and a session restore.
  • Transactions queue, they do not execute. Every eth_sendTransaction SwapsPro sends becomes a proposed Safe transaction that still needs your usual owner threshold. That is the desired behaviour, and it is why a treasury can use these tools at all.
  • A Safe lives on one chain. The connector reads the Safe's chain from the handshake and narrows everything to it — the supported set is derived from SwapsPro's own EVM chain list (Ethereum, Base, BNB Chain, Avalanche, Arbitrum and Robinhood Chain), and a Safe on anything else is refused with a message rather than half-working.
  • Detection is cheap and silent. The Safe SDK is only loaded when the page is framed and the parent answers a handshake within about a second, so a normal visitor never downloads it and an unrelated embedder simply never sees a Safe.

You can also pair a Safe over WalletConnect rather than opening SwapsPro inside it. Everything below works the same way.

How a multisig signs an order#

A contract account has no private key, so it cannot produce the ECDSA signature an order book verifies with ecrecover. SwapsPro detects a contract wallet by code lookup and switches signing schemes.

SchemeUsed forCosts gas?What happens
eip712Regular accountsNoAn ECDSA signature over the order's EIP-712 digest.
eip1271Safes and other contract walletsNoThe order book calls isValidSignature(digest, signature) on the account and requires the 0x1626ba7e magic value.
presignAccounts that cannot sign off-chain at allYesThe order is posted with an empty signature and flagged on-chain with setPreSignature(orderUid, true).

SwapsPro prefers eip1271 for contract wallets, because it stays gasless and because a pre-signed order's validity clock runs while owners are still signing — which is how a pre-signed order expires unfilled.

The waiting part#

When a dApp sends eth_signTypedData_v4 to a Safe, the Safe does not answer with a signature. It wraps the payload in a SafeMessage and asks its owners to confirm — the request you see as “SafeMessage · Confirmations (0 of 2)”. Only once the threshold is met does a signature exist: the owners' 65-byte signatures, sorted by signer address and concatenated, which is exactly what the Safe's fallback handler verifies and exactly what CoW's eip1271 scheme wants.

The order book runs that verification at POST time, so an eip1271 order can only be posted once a complete signature exists. For a 3-of-5 that can be minutes or days. SwapsPro handles this by computing the SafeMessage hash itself and polling the Safe Transaction Service for the finished signature independently of the connection that proposed it — a WalletConnect request would otherwise time out long before your owners finish.

What the UI tells your signers, verbatim

“Your wallet is a smart-contract wallet, so the order is signed by the wallet contract itself (ERC-1271) — still gasless. On a multisig the signature only exists once enough owners have confirmed it, so placing the order waits for them. Cancelling it later is an on-chain transaction.”

Paying a team, or moving a treasury#

Batch Send puts every ERC-20 you select into a single EIP-5792 batch, so your wallet asks for one confirmation instead of one per token. From a Safe, that means one proposed Safe transaction for your owners to review — one signature round instead of twelve.

Batch SendHow it behaves
ChainsBase, Ethereum, Optimism, Arbitrum, Robinhood Chain — all ETH-gas, which is what lets the fee be one flat amount in ETH everywhere.
Per batchUp to 50 ERC-20 tokens.
DestinationOne address for the whole batch. An ENS name is accepted and resolved on Ethereum mainnet regardless of which chain you are sending on.
AssetsERC-20 only. There is no native-ETH send path; the only native value in the batch is the flat fee.
CostA flat fee appended as the last call — see Fees.

One confirmation is not the same as atomic

EIP-5792 batching means one wallet confirmation, not a guarantee that the calls succeed or fail together. Some hosts execute a batch sequentially. Wallets with no wallet_sendCalls support fall back automatically to one transaction per token.

Every transfer is simulated on a public RPC before submission, and any token whose transfer would revert is dropped and named. That matters more for a treasury than it sounds: a wallet that validates the whole batch will reject all of it because of one transfer-locked scam airdrop, and a treasury address collects a lot of those. Full mechanics are on Tools.

Paying many different recipients is a different job — Batch Send moves many tokens to one address. For recurring many-recipient payouts, deploy a Splits contract and pay it once.

TWAP — selling a position over time#

A TWAP is n equal sell orders, one every t seconds. CoW implements it as a conditional order: your Safe authorises a single on-chain record describing the whole ladder, and CoW's watch-tower polls it every block, cuts the part that is valid right now, and posts that part to the order book under the eip1271 scheme. SwapsPro runs none of that infrastructure.

TWAPWhat it allows
ChainsEthereum, Gnosis, Arbitrum, Base.
PartsBetween 2 and 100.
IntervalFrom 5 minutes to 1 year.
Part size warningCoW's own thresholds — $1,000 on Ethereum, $1 elsewhere. Below that a part is often not worth a solver settling. It is a warning, not a block: nothing on-chain enforces it.

Wallet requirement#

A regular account cannot do this at all

The verifier proves each part is authorised by calling the owner's own ERC-1271 isValidSignature. A regular account has no code to call, so there is nothing to ask. Connect a Safe — or open SwapsPro as a Safe App — and the TWAP tab appears.

The two setup transactions#

The first time a Safe creates a TWAP, it must be taught to delegate signature verification for CoW's domain. That is two calls, batched into one Safe transaction, and it changes your Safe's configuration — so it is worth understanding before your owners sign it.

twapSetupCalls(safe, chainId)

// Both setup calls are addressed to the SAFE ITSELF, in this order:
//
// 1. setFallbackHandler(0x2f55e8b20D0B9FEFA187AA7d00B6Cbe563605bF5)
//    -> installs Safe's ExtensibleFallbackHandler.
//       Guarded by the Safe's own "authorized" modifier, so only the Safe can
//       call it on itself.
//
// 2. setDomainVerifier(<CoW's GPv2Settlement domain separator>,
//                      0xfdaFc9d1902f4e0b84f65F49f244b32b31013b74)
//    -> delegates ERC-1271 verification for CoW's EIP-712 domain to ComposableCoW.
//       Lives on the fallback handler and is "onlySelf", so the Safe reaches it
//       by calling itself and falling through — which is why step 1 must land first.
//
// Both are batched into ONE Safe transaction, so the ordering inside the batch
// guarantees the handler exists before the delegation is attempted.

What they change, concretely#

  • Your Safe's fallback handler is replaced. It moves from the default compatibility handler to ExtensibleFallbackHandler, which can delegate ERC-1271 verification per EIP-712 domain. This is a real change to how your Safe answers signature checks — the same contract CoW Swap's own frontend installs.
  • One domain is delegated. Only CoW's settlement domain is pointed at ComposableCoW. Every other domain keeps behaving exactly as before.
  • It happens once. Both halves are checked independently before anything is proposed — whether the handler is installed, and whether the domain is already delegated — so a Safe that is already set up creates its TWAP with a single transaction and is never asked to repoint its handler for nothing.
  • A failed check is not guessed. If the RPC read fails, SwapsPro says it could not check rather than assuming. Guessing “already set up” would produce a TWAP the watch-tower can never sign; guessing the opposite would ask you to change your Safe for no reason.

Timing, for a slow multisig#

The ladder's start time is not baked in when the transaction is built — it is written at execution time from the block timestamp. For a 3-of-5 that may take days to collect signatures, this is the difference between a working TWAP and a broken one: a hard-coded start in the past would mean every part whose window elapsed while owners were signing is simply gone, and the ladder would start mid-way and sell less than you asked for.

The three sentences your owners should read first#

Cost

“This is not gasless. Creating the TWAP is an on-chain Safe transaction, and the first time also installs a new fallback handler on your Safe. Once it is on chain the individual parts are relayed and settled for free, like a limit order.”

Who executes the parts

“CoW's watch-tower executes the parts: it polls your order on every block, cuts the part that is valid right now and posts it to the order book. We run none of that. If the watch-tower is down, the parts it misses are not queued — each one is only valid inside its own window, so a missed window is simply skipped and the rest of the ladder continues.”

Cancelling

“Cancelling stops every future part. It is an on-chain Safe transaction, and it cannot recall a part that is already on the order book — that one either fills or expires at the end of its window.”

One practical consequence for a multisig: cancelling needs your threshold too. If you may want to stop a ladder quickly, size the interval so a cancellation round has time to complete between parts.

Your ladders are remembered in your browser

There is no server and no indexer behind the TWAP progress view. Recovering a Safe's ladders from chain logs needs an archive-capable eth_getLogs over an unbounded range, which public RPCs refuse — so the ladders you create are stored locally. Everything stored is public or derivable, but a different browser will not show the progress view for a ladder created elsewhere. The order itself is entirely on-chain and unaffected.

Splits — paying a group automatically#

/create deploys audited 0xSplits contracts: an address that shares out every payment it receives, by the percentages you set. For a DAO that is the clean answer to recurring payouts — publish one address, and contributors, a grants program or a revenue share are handled by the contract rather than by a monthly batch of transfers.

SplitsWhat you choose
Types shippedSplit, Waterfall and Liquid Split. Swapper and Diversifier are visible but not enabled yet.
ChainsBase, Ethereum, Optimism, Arbitrum, Gnosis, Polygon, BNB Chain, Avalanche.
RecipientsAt least 2, at most 100. Shares are entered as percentages, down to 0.0001%, and must total exactly 100%.
OwnerOptional. Leave it empty for a permanent, immutable split; set an address — your Safe, for instance — to keep the ability to change it later.
VariantPull (recipients withdraw) or push (funds are sent straight to wallets on distribution).
Distribution incentiveA percentage of each distribution paid to whoever triggers it and pays its gas. Pre-filled at 1%, editable and removable, hard-capped by the contract at 6.5535%.

What actually happens when you deploy#

  1. The chain is switched if your wallet is elsewhere.
  2. The deploy is simulated first with eth_call. If it would revert you are told why, in plain language, and never asked to sign it.
  3. The factory call — plus the optional support transfer, if you left it non-zero — is sent as one EIP-5792 batch. Wallets without batching send the deploy first and the support transfer separately, and a declined support transfer does not affect the deploy.
  4. The new contract's address is read back from the factory's creation event, and the configuration is saved in your browser.

Nothing sits between you and the factory

The transaction goes straight to the audited 0xSplits factory — the same contracts splits.org deploys. SwapsPro is not in the path, holds no admin role over what you deploy, and takes no fee for deploying. The review step shows the exact factory address you are calling.

Keep the recipient list

Splits v2 stores only a hash of the recipient list on chain, so triggering a distribution requires supplying the full list again. SwapsPro keeps it in your browser's local storage for the “My contracts” view — that is convenient, not durable. Record your recipients and percentages somewhere your DAO controls, or manage the contract on splits.org, which indexes them.

Deploying the same split across several chains in one flow is planned, not built — today it is one deploy per chain, at separate addresses. See Free and Pro.

Everything else#

  • Tools — Batch Send and Batch Swap in full, limit orders, the Danger Zone.
  • Fees — one table, every path.
  • SDK — if your DAO's own front end wants SwapsPro's routing behind it.