Tool attestations
Write and revoke PublishedBy and PublishAuthorization attestations with @seedprotocol/publish: attestPublishedBy and the onPublished batch, attestPublishAuthorization, assessPublishAuthorizationLive, the schema helpers, and the revoke functions for every tool attestation.
A tool attestation is one a tool or app makes from its own wallet next to items. It isn't part of any item, and item.unpublish() doesn't revoke it. This page covers two kinds: PublishedBy, where a tool records that it published a batch of attestations, and PublishAuthorization, where a ManagedAccount records a grant to an automation session key. The third, DomainOwnership, has its own page, Domain ownership. Readers query all three with the helpers in @seedprotocol/eas.
import {
attestPublishedBy,
collectPublishedBatch,
hashPublishedByBatch,
attestPublishAuthorization,
assessPublishAuthorizationLive,
revokePublishedBy,
revokePublishAuthorization,
} from '@seedprotocol/publish'Every function here that sends a transaction needs initPublish to have run: it sends to the publish chain and reads the attestation's UID from the receipt using getPublishConfig().easContractAddress. wallet is a PublishWallet or SeedTxSender from one of the wallet adapters, and becomes the attester; anything else throws … require a PublishWallet or SeedTxSender. A reverted transaction throws PublishTransactionRevertedError.
UID arguments are normalized to lowercase 32-byte hex before they're checked: a shorter value is left-padded with zeros and characters that aren't hex become 0. So only an empty or all-zero value fails a "valid UID" check, and a truncated UID is attested as given.
attestPublishedBy
Makes a PublishedBy attestation from the tool's wallet for one publish: its Seed UID, Version UID, the attestation UIDs it made (or only their hash), and the tool's name and version. The attestation references the Seed (refUID), has no recipient and no expiration, and is revocable.
function attestPublishedBy(
params: {
wallet: PublishWallet | SeedTxSender
seedUid: string
versionUid?: string
toolName: string
toolVersion: string
ensureSchema?: boolean
} & (
| { mode: 'uids'; attestationUids: readonly string[]; batchHash?: string }
| { mode: 'hash'; batchHash: string }
),
): Promise<AttestPublishedByResult>Parameters
| Name | Type | Default | Description |
|---|---|---|---|
wallet | PublishWallet | SeedTxSender | required | The tool's wallet; the attester. It pays the gas. |
seedUid | string | required | The item's Seed UID. Throws attestPublishedBy requires a valid seedUid when empty or zero. |
versionUid | string | zero bytes32 | The Version the publish made |
toolName, toolVersion | string | required | Recorded as given |
mode | 'uids' | 'hash' | required | 'uids' writes the UID list on-chain. 'hash' writes an empty list and only batchHash; keep the list off-chain so readers can check it. |
attestationUids | readonly string[] | required with 'uids' | Empty and zero values are dropped |
batchHash | string | with 'uids', hashPublishedByBatch of the list | Required and non-zero with 'hash'; otherwise it throws attestPublishedBy hash mode requires batchHash or … requires a non-zero batchHash |
ensureSchema | boolean | true | Register and name the schema first if it's missing (two extra transactions) |
The inputs are checked before the schema step, so a call that throws on a bad seedUid sends no transaction.
Returns
{ uid, schemaUid, batchHash, attestationUids }. attestationUids is what went on-chain: the normalized list with 'uids', empty with 'hash'. It throws attestPublishedBy failed — could not parse attestation UID when the receipt has no attestation from the configured EAS contract.
Example
A publish can hand its batch to a callback: onPublished in the initPublish options, or in the createPublish options, which replaces the config one. It runs in the app that publishes, after the publish succeeds, with the item's seedLocalId, seedUid, versionUid and every attestation UID the publish made, including related items' Seeds and Versions. The publish doesn't wait for it, and an error it throws is logged with console.warn. A publish that resumes after a page reload doesn't call it: the saved publish can't hold a function.
The tool's key belongs on a server, so the callback sends the batch there:
import type { OnPublishedCallback } from '@seedprotocol/publish'
export const onPublished: OnPublishedCallback = async ({ seedLocalId, ...batch }) => {
await fetch('/api/published-by', {
method: 'POST',
headers: { 'content-type': 'application/json' },
body: JSON.stringify(batch),
})
}import { ethers } from 'ethers'
import { attestPublishedBy, fromEthersWallet, type PublishedBatchResult } from '@seedprotocol/publish'
const provider = new ethers.JsonRpcProvider(process.env.RPC_URL)
const toolWallet = fromEthersWallet(new ethers.Wallet(process.env.TOOL_PRIVATE_KEY!, provider))
export async function recordPublish(batch: PublishedBatchResult) {
const { uid } = await attestPublishedBy({
wallet: toolWallet,
mode: 'uids',
seedUid: batch.seedUid,
versionUid: batch.versionUid,
attestationUids: batch.attestationUids,
toolName: 'example-publisher',
toolVersion: '1.4.0',
})
return uid
}The server attests whatever UIDs it's sent, so authenticate the request. Run it in Node with initPublish configured and the loader hook, and fund the tool wallet with testnet ETH.
collectPublishedBatch and uidsFromAttestationPairs
collectPublishedBatch({ seedUid, versionUid?, extraUids? }) builds the batch the publish passes to onPublished, for code that publishes some other way. It returns { seedUid, versionUid?, attestationUids }, where attestationUids is seedUid, then versionUid, then extraUids, with duplicates (ignoring case), empty and zero values removed and the order kept. It returns null when seedUid is empty or zero.
uidsFromAttestationPairs(pairs) takes { attestationUid?, uid? }[] and returns each entry's attestationUid ?? uid, dropping empty and zero values.
hashPublishedByBatch
hashPublishedByBatch(uids) is the keccak-256 of the UIDs, lowercased, sorted and concatenated as 32-byte values. Order and case don't change it; empty and zero values are dropped, and an empty list hashes the empty input. Readers check a hash with verifyPublishedByBatch from @seedprotocol/eas.
attestPublishAuthorization
Makes a PublishAuthorization attestation from a ManagedAccount, recording that it granted a session key. enrollPublishAutomation calls it after adding the key; call it yourself only when you added the key another way. What each field means is in Security and session keys. The attestation's recipient is the session key, and it's revocable.
function attestPublishAuthorization(params: AttestPublishAuthorizationParams): Promise<AttestPublishAuthorizationResult>Parameters
| Name | Type | Default | Description |
|---|---|---|---|
wallet | PublishWallet | SeedTxSender | required | The attester: the ManagedAccount's wallet. Throws attestPublishAuthorization must be sent by the identity (<identity>), not <sender> when its address isn't identity. |
identity | string | required | The ManagedAccount address. Throws identity must be a valid address unless it's a 20-byte hex address. |
sessionKey | string | required | The session key's address; checked the same way |
app | string | zero address | An address that labels your app; checked when not blank |
scopes | string | 'publish,revoke' (PUBLISH_AUTHORIZATION_SCOPES) | |
grantedAt | number | now | Unix seconds |
expiresAt | number | 0 | When the grant ends, Unix seconds. 0 or less means no end. |
permissionsHash | string | see below | Throws attestPublishAuthorization requires a non-zero permissionsHash when zero |
ensureSchema | boolean | true | Register and name the schema first if it's missing (two extra transactions) |
expirationTime | number | expiresAt when set, else grantedAt plus 90 days (PUBLISH_AUTHORIZATION_DEFAULT_EXPIRATION_SECS) | EAS expiration, Unix seconds |
The default permissionsHash is the hash of the permissions enrollPublishAutomation gives a key (the Seed executor module as its only target, no native token, expiresAt) and scopes. Computing it needs modularAccountModuleContract in the config; without it, the call throws session keys require PublishConfig.modularAccountModuleContract …. As with attestPublishedBy, the inputs are checked before the schema step, so a call that throws sends no transaction.
Returns
{ uid, schemaUid, permissionsHash, grantedAt, expiresAt, expirationTime }, with the values that were attested.
assessPublishAuthorizationLive
Finds a PublishAuthorization attestation by UID among those the given accounts made, and assesses it.
function assessPublishAuthorizationLive(params: {
uid: string
identities: string[]
}): Promise<AssessPublishAuthorizationLiveResult | null>It queries getPublishAuthorizationFromEas({ identities, excludeRevoked: false }) through the EAS client the SDK registers, and returns null when the UID isn't among the results. Otherwise it returns { uid, decoded, assess, expirationTime }, where assess is assessPublishAuthorization's { status, warnings }: 'valid', 'revoked', 'expired' (past the grant's expiresAt or its EAS expiration), 'scope_mismatch' (scopes isn't publish,revoke) or 'unknown' (no identity or session key), checked in that order. expirationTime is the attestation's EAS expiration in Unix seconds.
Schema helpers
ensurePublishedBySchema(wallet) and ensurePublishAuthorizationSchema(wallet) register the schema when the chain doesn't have it, with no resolver and revocable, and name it (seedprotocol.publishedBy, seedprotocol.publishAuthorization) with a second transaction so EAS Scan shows it. They do nothing when it's registered, and return its UID. getPublishedBySchemaUid() and getPublishAuthorizationSchemaUid() compute the UID locally; it's the same on every chain. Schema strings, UIDs and registration status are on Base schemas.
encodePublishedByAttestationData(fields) and encodePublishAuthorizationAttestationData(fields) ABI-encode an attestation's fields. Constants: PUBLISHED_BY_SCHEMA_DEF, PUBLISHED_BY_SCHEMA_NAME, PUBLISH_AUTHORIZATION_SCHEMA_DEF, PUBLISH_AUTHORIZATION_SCHEMA_NAME and PUBLISH_AUTHORIZATION_SCOPES. Types: PublishedBatchResult, OnPublishedCallback, AttestPublishedByParams, AttestPublishedByResult, AttestPublishAuthorizationParams, AttestPublishAuthorizationResult and AssessPublishAuthorizationLiveResult.
Revoking tool attestations
Only the attester can revoke a tool attestation; EAS rejects anyone else.
| Function | Entry point | Revokes |
|---|---|---|
revokePublishedBy({ wallet, uid, schemaUid? }) | root | A PublishedBy attestation |
revokePublishAuthorization({ wallet, uid, schemaUid? }) | root | A PublishAuthorization attestation. Leaves the session key on the account. |
revokeDomainOwnership({ wallet, uid, schemaUid? }) | root | A DomainOwnership attestation. See Domain ownership. |
revokePublishAutomation({ managedAddress, sessionKeyAddress, authorizationUid, userWallet }) | root, /thirdweb | Removes the session key from the ManagedAccount, then revokes its PublishAuthorization attestation. userWallet must send as managedAddress, or the call fails with MANAGED_ACCOUNT_UNAVAILABLE before sending. It's required on the root entry; on /thirdweb it defaults to the connected ManagedAccount. See Publish unattended with a session key. |
The three root functions send one EAS multiRevoke from wallet and wait for the receipt. schemaUid defaults to the schema's UID. They throw … requires a valid attestation uid when uid is empty or zero.
Related
- Security and session keys: what a PublishAuthorization attestation records
@seedprotocol/eas: reading and checking tool attestations- Domain ownership
- Revocation:
item.unpublish()
Domain ownership
Prove that a user controls a domain with a DNS TXT challenge, record it as a seedprotocol.domainOwnership EAS attestation from a tool wallet, recheck it against the domain registry, and revoke it.
Overview
What @seedprotocol/feed and @seedprotocol/feed-hyper do, how to install them, the site config, the environment variables they read, and how feed caching works.