Identity and signing
Whose account a published item belongs to, who signs the attestations and the Arweave uploads, who pays, and how delegated keys fit in.
In Seed Protocol, an author's identity is an account address: the account that made an item's EAS attestations is, by definition, the item's author. Everything else (who paid the gas, which key signed the Arweave upload, which app or session key sent the transaction) is plumbing around that one fact.
Everything here runs on OP Sepolia, an Optimism testnet, today; there's no mainnet deployment yet. The default setup also relies on thirdweb's in-app wallets, bundler and paymaster. See Deployments and Bundler and paymaster.
The attester is the author
Every EAS attestation records an attester: the address that called EAS to create it. Seed Protocol uses that field as authorship. A Seed, its Versions and its property attestations all carry the same attester, and every reader (the SDK's EAS sync, @seedprotocol/query, @seedprotocol/feed, or a client you've never seen) attributes the item to that address.
Two things follow from this:
- Only the attester can revoke. EAS lets an attestation's attester revoke it, and no one else. So control of the account is control of the published record.
- Identity can't be transferred. An attestation's attester is fixed when it's created. If a user moves to a different account, content they publish from it has a different author, and their earlier content stays attributed to the old one.
The default account: a smart account the user controls
With @seedprotocol/publish and its thirdweb integration, a user signs in with email, phone, a passkey or Farcaster. That gives them two linked accounts:
- A ManagedAccount: a thirdweb ERC-4337 smart account that publishes on the user's behalf. It's the attester, so it's the author.
- An in-app wallet that controls it: a signing key that thirdweb's in-app wallet service holds for the user and unlocks when they log in. It's the smart account's admin.
Users don't manage that key themselves, so using the account depends on thirdweb's service. Apps that want users to hold their own keys can connect an external wallet instead, such as MetaMask through useSeedWallet; with a plain external wallet, the user pays their own gas. Wallets covers the adapters, who sends each transaction, and a ManagedAccount without thirdweb.
When the user publishes, the in-app wallet signs a UserOperation for the ManagedAccount, and a bundler submits it. The account calls multiPublish, which is handled by the Seed Protocol extension running inside the account, and the extension calls EAS. To EAS, the caller is the ManagedAccount, so the ManagedAccount is the attester.
The in-app wallet sends a few admin-only transactions of its own, such as installing the Seed executor module (below). By default it sends them through thirdweb's EIP-7702 service.
The contracts check callers themselves: a publish must come from an admin of the account, from the EntryPoint (a UserOp an admin signed), or from the account itself, which is how a session key's calls arrive. Contracts overview and Seed Protocol extension have the details.
Other wallet setups
The managed smart account is the default, not the only option. Wallets other than thirdweb's can send through an EIP-7702 sender when you configure a bundler. An undelegated EOA's first UserOp needs a signAuthorization call, which browser wallets don't expose over EIP-1193, so a plain injected wallet can't send it unless you pass the wallet vendor's own call to the adapter. A plain wallet attests straight to EAS, with no Seed contract in between, when the publish wallet says it's an EOA (kind: 'eoa', the default of the viem, Privy, injected wallet and ethers adapters); an EIP-7702-delegated EOA counts as one.
A new user's ManagedAccount has no contract code until it's deployed, and it can't attest straight to EAS in the meantime. ConnectButton's default setup deploys it on the user's first publish, and so does the configuration these docs recommend. In a setup that can't deploy it, that publish fails with MANAGED_ACCOUNT_NOT_DEPLOYED. Attestation routing in Publish overview has the options that change the route and the deploy step.
Whichever setup you choose, the rule is the same: the address that ends up calling EAS is the author.
Paying isn't signing
By default, the publish package asks for gas to be sponsored (thirdweb.sponsorGas: true): thirdweb's paymaster pays for the ManagedAccount's UserOps and the in-app wallet's EIP-7702 transactions, so users don't need ETH. Whether the paymaster actually pays is set in the thirdweb project behind your client ID, by whoever set that project up. Without sponsorship, or where there's no paymaster, such as a local chain, set payment: { gas: 'user' } (or thirdweb.sponsorGas: false) and the accounts pay their own gas.
Sponsorship changes who pays, not who signs. The paymaster can't publish anything on its own, and its address appears nowhere in the attestations. If the sponsor stops paying, already-published content is unaffected; new publishes need another way to pay. Costs and who pays has who pays gas in each wallet setup.
Arweave uploads are signed separately
File uploads are signed separately from the attestations, and the key that signs them doesn't have to be the publishing account.
Publish uploads each file as an ANS-104 data item, signed with an Ethereum key: the data item signer. It has to be a plain key, because a data item needs a signature that recovers to its owner and a smart account's signatures are checked by its contract. So with the default thirdweb setup, the data item signer is the in-app wallet that controls the ManagedAccount, not the ManagedAccount. ConnectButton registers it with the publish wallet and item.publish() uses it. PublishManager.createPublish doesn't use the publish wallet's, so pass one there or set one in the config. The bundler behind the upload API pays for the storage, so the signer needs no AR and there's no Arweave key to keep.
With payment: { storage: 'user' }, the same signed data items go straight to Turbo instead, paid from the signer's Turbo credits.
The upload's signer isn't the author. What ties a file to its author is the attestation: the account's storage_transaction_id attestation records the data item ID. A reader should trust that link, not the key that signed the upload. Uploads and ANS-104 data items covers the upload flow and who signs and pays for each part.
Delegated publishing with session keys
Sometimes an app needs to publish when the user isn't there: scheduled posts, imports, a server-side pipeline. For that, the user's account can grant a session key, a key the app holds that may publish as the account until the grant expires or the user removes it.
The grant is a thirdweb signer permission on the ManagedAccount, limited by a time window, a native-token limit and a list of approved targets. In the setup @seedprotocol/publish provides, the native-token limit is 0, so the key can send no ETH, and the only approved target is the Seed executor module, an ERC-7579 module the account's admin installs on the account. The executor can call nothing but EAS attest and multiAttest, and it calls them through the account, so the attester is still the user's ManagedAccount. Content an app publishes this way is the user's content.
The publish package also records each grant as a revocable PublishAuthorization attestation made by the account, naming the session key, so the grant is discoverable on EAS.
A session key's reach is narrow on purpose:
- It can publish. Including edits, which change what readers see as the item's current values.
- It can't revoke. No path a session key can reach calls EAS's revoke. A publish that would revoke a retracted property (
clear({ retract: true })) fails before it attests anything. - It can't register schemas. Before unattended publishing works, the owner runs
ensureEasSchemasForModelswith their own wallet for the models it will publish (Publish overview). A schema version that adds a property needs another call. - It can't change its own permissions or add other signers.
Security and session keys and Seed Protocol executor describe the on-chain rules.
Revocation stays with the owner
Every Seed, Version and property attestation the Seed contracts create is revocable, whatever the request asks for. That guarantees the owner can always revoke what a delegate published.
Revoking is something only the owner does. The SDK sends EAS multiRevoke from the publishing account itself, which for a ManagedAccount means a UserOp signed by its admin. If the connected wallet is the account's admin key rather than the ManagedAccount (as with useModularExecutor), the publish package switches to the ManagedAccount before sending. If the connected wallet is an automation session key, it refuses before sending anything; if it's some other account, EAS rejects the revoke. Permanence explains what revoking does and doesn't remove.
Addresses in the SDK
The core SDK doesn't sign anything. Its owned addresses decide what the user may edit and publish in your app, but they're a local statement: the SDK doesn't check that the user holds a key for any of them, and an item's publisher on a synced item is the attester EAS reports. The real check happens on-chain, where EAS accepts attestations and revocations only from the account itself. Ownership and drafts explains the owned and watched lists and how ConnectButton sets them.
What this means for your code
- Decide which address is the author. With the default thirdweb setup it's the ManagedAccount, not the address the user sees at sign-in. Show and store that address.
- Register the author as owned. If you don't use
ConnectButton, callclient.setAddresses()with the author's address (see Client and configuration). While it's the only owned address, new drafts get it as their publisher;client.claimUnpublishedDrafts()stamps the drafts made before. - Give a smart account an EOA data item signer.
ConnectButtonregisters one. If you build the publish wallet for a smart account yourself, set itsdataItemSigner; withPublishManager.createPublish, pass one or set it in the config. Treat it as an upload credential, not an identity. - Delegate with session keys, not shared keys. Never ask for the user's admin key to publish for them. Give the session key an expiry, register schemas with the owner's wallet first, and route every revoke through the owner.
- Don't over-trust the local checks. A user can only publish or revoke as an account they control, whatever's in the owned list.
Related
- How Seed Protocol works: the architecture in brief
- Ownership and drafts: owned and watched addresses in the SDK
- Permanence: what publishing and revoking leave behind
- Publish overview: wallets, routes and configuration
- Wallets: adapters, who sends, and a ManagedAccount without thirdweb
- Revocation
- Contracts overview, Security and session keys and Working with EAS
- Bundler and paymaster
Multi-tab behavior
How browser tabs of one app share a single SQLite database and OPFS tree: the locks every tab takes, the leader tab that runs background work, how other tabs stay current, the multiTab modes, and the errors users can see.
Ownership and drafts
How the SDK decides which items the user may edit, publish and delete: owned and watched addresses, each item's publisher, drafts, and claiming.