Costs and who pays
Where the money goes when Seed Protocol publishes: gas for EAS attestations on OP Sepolia in each wallet setup, and Arweave storage paid by the bundler behind the upload API or by the user through Turbo, with live Arweave prices, the 256 KiB minimum and what each party funds.
A publish costs money in two places: gas on the chain where the attestations go, and storage on Arweave for files, images and Html bodies.
For the estimate API (estimatePublishCost, watchPublishCost, usePublishCostEstimate), see Cost estimates and payment in the publish package reference.
Summary
| Cost | Paid in | Who pays by default |
|---|---|---|
| Attestation gas (EAS on OP Sepolia) | Testnet ETH | thirdweb's paymaster, if the thirdweb project behind your client ID sponsors gas. Otherwise the publishing account |
| Schema registration gas | Testnet ETH | The same, from the owner's account |
| Arweave storage, operator-paid (the default) | Real AR (Arweave mainnet) | The bundler's wallet, run by the upload API's operator |
Arweave storage, user-paid (payment: { storage: 'user' }) | Turbo credits | The user, from the signer address's Turbo credits (or an app's shared credits) |
The payment option in @seedprotocol/publish states who pays gas and storage, and makes initPublish throw on a config that contradicts it. See The payment option.
Arweave has no testnet, so storage is on Arweave mainnet even while the attestations are on OP Sepolia: every test upload costs real AR for whoever pays it, and stays public for good. See What "permanent" means today.
Gas
Attestations go to EAS on OP Sepolia, a testnet, so gas is paid in testnet ETH that has no market value. The public OP Sepolia RPC reported a gas price of about 0.001 gwei (eth_gasPrice = 1,000,250 wei) on 2026-10-18.
With the default thirdweb setup, the publishing account is an ERC-4337 smart account (the ManagedAccount), and its transactions are UserOperations that a bundler submits. @seedprotocol/publish asks thirdweb's paymaster to pay for them (thirdweb.sponsorGas, true by default), but asking isn't paying: whether the paymaster sponsors is set in the thirdweb project behind your thirdwebClientId, by whoever set the project up.
| Setup | Who pays gas |
|---|---|
ConnectButton, default config | The ManagedAccount's UserOps go through thirdweb's bundler with sponsorGas: true, which asks thirdweb's paymaster to pay. A new user's first publish sends one more UserOp first, which deploys the ManagedAccount, on the same terms. If the thirdweb project doesn't sponsor, turn sponsorship on there, or set sponsorGas: false and fund the ManagedAccount with testnet ETH. |
ConnectButton with useModularExecutor | As above for publishes. The in-app wallet's own transactions (deploying the ManagedAccount with autoDeployManagedAccount, installing the executor, bootstrapping its EIP-7702 account) also request sponsorship in 'EIP7702' mode. In modularWalletMode: 'EOA' the in-app wallet pays them. |
thirdweb.sponsorGas: false, or payment: { gas: 'user' } | Nothing is sponsored. The ManagedAccount, and the in-app wallet in 'EOA' mode, need ETH. This is the setup for a local chain with no paymaster. |
useSeedWallet or fromEip1193Provider, accountMode: 'eoa' | The user's wallet pays every transaction. It attests straight to EAS only when the wallet says it's an EOA (kind: 'eoa', which the adapters set); see EOA: no account abstraction. |
accountMode: 'eip7702' with bundlerUrl | UserOps go through bundlerUrl with paymasterUrl (or, when unset, bundlerUrl) as the paymaster. Whoever runs that paymaster pays, if it sponsors. With payment: { gas: 'user' } no paymaster is attached and the account pays its own gas. |
| Session-key automation | The sender you build decides. The guide's example sets sponsorGas: true on a thirdweb smart wallet. |
To state who pays instead of relying on these defaults, set payment.
What uses gas: one multiPublish per publish from a smart account (one per item when a relation or list names another item published with it). From a plain wallet, each item takes one EAS attest for a new Seed, one for a new Version, and one multiAttest for its properties. Add two transactions for each EAS schema an item's models and properties need that isn't registered on the chain yet (the registration and a naming attestation). Revoking costs one transaction per schema.
Account abstraction: bundler and paymaster explains the parts and how to run without sponsorship.
Arweave storage
Arweave charges once, up front, for permanent storage. You can ask any gateway for the current price of storing a number of bytes:
curl https://arweave.net/price/1024The answer is in winston: 1 AR = 10¹² winston.
Live prices
From https://arweave.net/price/{bytes} on 2026-10-18 at 16:36 UTC. Prices follow the network and the AR market and change all the time, so treat these as an illustration and fetch your own.
| Data size | Price (winston) | Price (AR) |
|---|---|---|
| 1 byte | 3,408,198,040 | 0.00341 |
| 1 KiB | 3,408,198,040 | 0.00341 |
| 256 KiB | 3,408,198,040 | 0.00341 |
| 256 KiB + 1 byte | 6,775,166,946 | 0.00678 |
| 512 KiB | 6,775,166,946 | 0.00678 |
| 1 MiB | 13,509,104,759 | 0.0135 |
| 10 MiB | 134,719,985,382 | 0.135 |
Two things stand out:
- There's a 256 KiB minimum. Arweave prices each transaction's data in 256 KiB chunks and rounds up, so a 1-byte file costs the same as a 256 KiB one.
- Above that, it's roughly linear. About 0.0135 AR per MiB at these prices.
estimatePublishCost prices a publish's uploads at the total size of their signed data items (each file plus its ANS-104 header and tags) or 256 KiB, whichever is larger; see arweave.chargedBytes in Cost estimates and payment.
The bundler path
Publishes upload signed ANS-104 data items. The upload API forwards them to a bundler, and the bundler's wallet pays to post them. The data item signer is an Ethereum key and needs no AR. Seed's own gateway is private (Seed's gateway).
A bundle is one L1 transaction holding many data items, priced by its total size like any other transaction. The 256 KiB minimum applies to the bundle, not to each item, so small files share it, and the operator pays the network roughly by the bytes it bundles. Whether the bundler accepts your items is up to its operator; see The bundler path for the request flow.
User-paid storage: Turbo
With payment: { storage: 'user' }, the publish package sends the signed data items straight to Turbo (ArDrive's bundling service, upload.ardrive.io by default) instead of the upload API. Turbo bundles them like any bundler, and charges the signer's address in Turbo credits (winc, priced like winston), which users buy with a card or crypto. Items up to Turbo's free size limit are free. An app can pay instead by sharing its own credits with users and listing its address in userStorage.paidBy.
Nobody running an upload API pays for these items. The publish checks the signer's balance first and fails with InsufficientStorageCreditsError rather than upload part of a publish. Both the publish and estimatePublishCost read Turbo's current free limit from its /v1/info, and the estimate falls back to 100 KiB when Turbo doesn't answer; Turbo reported 105 KiB (107,520 bytes) on 2026-10-27. See Uploads and ANS-104 data items.
What to fund
| You run | You fund |
|---|---|
An app with ConnectButton | The thirdweb project's gas sponsorship, if you enable it. Otherwise your users need testnet ETH. |
An app with an injected wallet in 'eoa' mode | Nothing. Users pay gas. |
| An upload API | Its bundler's wallet, in real AR |
An app with payment: { storage: 'user' } | Nothing for storage. Users buy Turbo credits, unless you share an app wallet's credits through userStorage.paidBy |
| A local twin | Both accounts, with twin:fund (see Local twin) |
Related
- Cost estimates and payment:
estimatePublishCostand thepaymentoption - Account abstraction: bundler and paymaster
- Uploads and ANS-104 data items
- Identity and signing: paying isn't signing
- Permanence
Gateway Hyper
How @seedprotocol/gateway-hyper exposes an upload API and Arweave gateway over the Hyper network, how to reach one through a local sidecar or an app-server proxy, how an operator restricts who connects, raw TCP mode, and the seed-gateway CLI and library reference.
EAS indexing
Where Seed reads attestations from (the easscan GraphQL indexer by default), how to point the SDK and @seedprotocol/query at another indexer, and the pinned EAS indexer in infra/eas-indexer that the local twin runs.