Seed Protocol

Changelog

What changed in each @seedprotocol release from 0.5.1 through 0.8.0, by package: breaking, changed, added and fixed.

All @seedprotocol/* packages are released together and share one version number, so @seedprotocol/react 0.8.0 goes with @seedprotocol/sdk 0.8.0. Keep them on the same version. @seedprotocol/next starts at 0.7.7 and @seedprotocol/archive at 0.7.10. Dates are the npm publish dates (UTC). A package that isn't listed under a version had no user-facing changes in it. This page lists user-facing changes only; each package's CHANGELOG.md in the SDK repository lists its commits per release.

For upgrade steps, see Upgrading.

0.8.0

Released 2026-10-30. Upgrade steps: Upgrading from 0.7.13 to 0.8.0.

This release goes with seed-protocol contracts fb7ba92, which OP Sepolia has routed since 2026-10-30. The two can't be mixed: 0.8.0 sends the three-phase multiPublish (selector 0xa1263421), and the contracts no longer route the multiPublish that releases from 0.6.9 to 0.7.13 send. Deployments has the addresses. @seedprotocol/next and @seedprotocol/archive have no user-facing changes; the fix listed in next's own changelog touched a test fixture.

@seedprotocol/sdk

  • Breaking: a repeat client.init never changes the stored addresses, whatever addresses holds. Only the first init, or the first after client.stop(), applies them; once the client is running, use client.setAddresses. Overlapping inits share one run, and the first call's addresses win. INIT_CONFIG_CONFLICT_MESSAGE no longer suggests init for new addresses. See Addresses.
  • Breaking: getUnpublishedRelatedItems is removed. summarizePublishUploads(item, { publishMode }) returns the same list as unpublishedRelatedItems, with uploadCount, uploadBytes and uploadSizes, from one read of the publish graph and file sizes only. PublishUploadSummary is exported. See Other read helpers.
  • Changed: Model.create refuses a model name, or a name in properties, that can't publish, before it makes a model: it rejects, or throws with waitForReady: false, with Model "Event" publishes as "event", … or Property "view" publishes as "view", …. The Model "X" could not create its properties: prefix is gone for this case, and nothing stays in schema.models or Model.getByName. See Reserved Solidity words.
  • Changed: renaming a ModelProperty to a name that can't publish fails validation: validate() returns an error on name with code reserved_name, the row keeps the old name, and the instance shows the new one until a valid name replaces it. It returned no error. See Reserved Solidity words.
  • Changed: getPublishModels throws UnknownModelError, with modelName and schemaName, and the message is Model "Nope" not found., or Model "Post" not found in schema "other".. It threw a plain Error (No model named Nope). See Other read helpers.
  • Changed: client.resetLocalData() in Node throws ResetLocalDataBrowserOnlyError (code: 'RESET_LOCAL_DATA_BROWSER_ONLY', exported from both entries), with the same message. It threw a plain Error. See Errors.
  • Changed: EAS sync skips a relation holding an unfilled reference, a non-zero bytes32 below 2^32, with a console.warn naming the property, drops such members from a list, and doesn't fetch either as a related seed. Only 0x…20 was skipped. See Reading in: how EAS sync works.
  • Changed: a schema with no $schema gets https://seedprotocol.io/schemas/data-model/v1 in its file (it was .org), and $schema no longer counts toward the content hash. A source that sets $schema keeps it, and the value isn't validated. After the upgrade each schema's stored hash changes once, with no log and no file rewritten. See Top-level fields.
  • Changed: a payload's reference into a list names the element it fills, with listElement: true and elementIndex, one propertiesToUpdate entry per position, so a draft listed twice fills both. Attestations carry _easType.
  • Added: backReferences (PublishBackReferences: 'link' | 'defer', default 'defer') on item.getPublishPayload's options, validateItemForPublish and getPublishGraph. 'link' attests a relation back to an item the publish is still creating. See Other read helpers.
  • Added: ItemPublishOptions declares confirmUpload and maxUploadBytes. The type UpdateVersionUidProps is exported from the main entry.
  • Fixed: in the browser, a directory listing (Schema.load, listCompleteSchemaFiles, fs.promises.readdir) skips a file removed between the listing and its stat. It failed with TypeError: Cannot set property message of … which has only a getter.

@seedprotocol/react

  • Breaking: SeedProvider and SeedClientGate apply a changed addresses with client.setAddresses, after their init settles and in order. A change to [] clears the stored addresses, which it didn't in 0.7.13, and leaving the prop out changes nothing. A remount changes no addresses, whatever the prop holds; in 0.7.13 a remount with a different non-empty set replaced them. A failed setAddresses is logged with console.error. See Changing addresses.

@seedprotocol/publish

  • Breaking: a contract publish calls the three-phase multiPublish, selector 0xa1263421, and needs the contracts deployed with this release. Against contracts that don't route it, a publish fails with ManagedAccountPublishError code MULTI_PUBLISH_NOT_ROUTED before simulating, naming both selectors. MULTI_PUBLISH_SELECTOR and assertMultiPublishRouted(target, { executorModule }) are exported for deploy checks. See Other exports.
  • Breaking: removed from /thirdweb: ensureManagedSignerSessionKey, defaultApprovedTargetsForModularPublish and toThirdwebSessionKeyPermissions. See Session keys without thirdweb.
  • Breaking: ensureAutomationSessionKey requires expiresAt and refuses a time that has passed, with no ten-year default. @seedprotocol/publish now exports it, removeAutomationSessionKey, isAutomationSessionActive, enrollPublishAutomation, revokePublishAutomation and ensureExecutorModuleInstalled, none of which needs thirdweb. There admin or userWallet is required; /thirdweb's versions default them to the connected in-app wallets. Both entries' enroll and revoke refuse a userWallet that doesn't send as the account, with MANAGED_ACCOUNT_UNAVAILABLE, before sending. See Session keys without thirdweb.
  • Breaking: ensureExecutorModuleInstalled(account, getAdmin, config) takes a function that returns the admin EOA as a PublishWallet, called only when an install has to be sent; on /thirdweb it may be undefined. The second argument was a thirdweb Account it ignored. See ensureExecutorModuleInstalled.
  • Breaking: enrollPublishAutomation defaults to a 90-day key and PublishAuthorization, as does attestPublishAuthorization's expirationTime. Both were 365 days. Keys enrolled before keep their end. See Enroll the key.
  • Breaking: enrollPublishAutomation fails with AUTOMATION_ACCOUNT_FUNDED, before sending, when the account's balance plus EntryPoint deposit is above the new maxAutomationExposureWei (default 0.001 ETH). A failed read of those fails with MODULAR_SIGNER_ACTIVATION_FAILED. An automation publish over the limit logs a warning and goes ahead. See Enroll the key.
  • Breaking: an automation publish refuses a session key approved for anything besides the configured executor, the zero address included, with MODULAR_SIGNER_ACTIVATION_FAILED, before sending. It passed the simulation and failed at the bundler. Enrolling again replaces the key's targets.
  • Breaking: a publish whose uploads add up to more than confirmUploadAboveBytes (default 25 MiB) fails with UploadNeedsConfirmError unless given confirmUpload: true, and so does one whose memory peak would pass a quarter of navigator.deviceMemory. Over maxUploadBytes (default 100 MiB) it fails with UploadTooLargeError. Both come in checking, from file sizes, on Node and with Turbo too, and item.publish() rejects with the errors themselves. PublishConfig gains maxUploadBytes, confirmUploadAboveBytes and uplinkBitsPerSecond, and CreatePublishOptions gains maxUploadBytes and confirmUpload. See Upload limits.
  • Changed: a contract publish goes out as one transaction, whatever relations, storage seeds or list members it holds, so a failed publish leaves no related draft attested. It attests both sides of a cycle between drafts in that transaction. Direct EAS still goes request by request and leaves the last side of a cycle for the item's next publish. See Publish related items together.
  • Changed: a contract publish is sent with its gas estimate plus 30%, falling back to 5,000,000 when the estimate can't be made. It was always 5,000,000. One that needs more gas than a block holds fails with PUBLISH_TOO_LARGE before sending. See Publish related items together.
  • Changed: encodeMultiPublish takes listElement and elementIndex on each propertiesToUpdate entry, writes the contract's placeholder into each slot a reference fills, sets value to the sum of the attestations' value, and refuses before sending what the contract would revert on. An automation publish with a non-zero total fails with AUTOMATION_PREFLIGHT_FAILED. A reverted publish names the new contract errors, and PublishTargetAlreadyAttested and UnknownPublishLocalId are gone. See How the SDK builds a batch.
  • Changed: a publish reads what it created from the publishing account's Attested events on every route, including attestPublishedBy, attestDomainOwnership and attestPublishAuthorization. CreatedAttestation and SeedPublished aren't read, so another UserOp's logs in the same bundle can't be recorded as this publish's.
  • Changed: enrolling an active key sends a grant whenever its end or targets differ from the request, so renewing moves the key's end. It attested the new end and left the key on its old one. A signed grant can be sent for one hour; it was ten years.
  • Changed: with useModularExecutor, a wallet that sends as its publisherAddress while its signer is another address, with a kind other than 'eoa', publishes as it is, with no thirdweb prep. See Who signs and who sends.
  • Changed: a publish with no wallet takes the registered wallet in checking, so the schema check runs. With none at all, a PublishManager.createPublish run fails there with PublishWalletMissingError (code: 'PUBLISH_WALLET_MISSING'), before any upload. item.publish() stops earlier, with a plain Error with the same text. See item.publish() and publishItem.
  • Changed: the direct EAS error for a backward reference reads Direct EAS publish from <address> can't fill a reference backwards: …; the code is still PUBLISH_PREFLIGHT_FAILED.
  • Added: @seedprotocol/publish loads without thirdweb installed. It needs it only on thirdweb paths, which import it when they run. See Entry points.
  • Added: fromViemWalletClient(walletClient, { chain, kind, signAuthorization }), for wagmi and the kits built on it, and fromPrivyWallet(wallet, { chain, kind, signAuthorization }). SeedSigner gains optional signTypedData and signAuthorization. See Adapters.
  • Added: createManagedAccountTxSender(options) sends UserOps from a ManagedAccount through any EntryPoint v0.6 ERC-4337 bundler without thirdweb's SDK. It has run only against a local Alto bundler. CALL_GAS_HEADROOM_BPS is exported from the root entry too. See ManagedAccount without thirdweb.
  • Added: estimatePublishCost returns upload, and estimateUpload(work, limits), DEFAULT_MAX_UPLOAD_BYTES, DEFAULT_CONFIRM_UPLOAD_ABOVE_BYTES, DEFAULT_UPLINK_BITS_PER_SECOND and PUBLISH_MEMORY_MULTIPLIER are exported. See estimateUpload.
  • Fixed: the EIP-7702 sender works on an EOA's first UserOp: it signs a real authorization once, with SeedSigner.signAuthorization. No undelegated EOA could send through it. Browser wallets don't expose that call, so the browser adapters take a signAuthorization option, and without one the first send fails with an error that says so. It also takes fees from the ERC-4337 bundler only when the bundler answers pimlico_getUserOperationGasPrice. See The EIP-7702 sender.
  • Fixed: disconnectPublishWallet() works before publish config exists, and ConnectButton waits for the disconnect before it stops publishes; a failed disconnect is logged and still stops them. See Signing out.

@seedprotocol/query

  • Breaking: relations set to anything but 'expand' or 'uids', such as 'urls' from JavaScript, rejects with TypeError: relations must be 'expand' or 'uids', got "urls" before any request. See queryBySchema.
  • Breaking (types): QueryBySchemaResult requires listed, the number of Seeds the listing returned for the page before Seeds with no Version are left out. listed < limit ends the listing; listed === limit doesn't promise more. See Returns.
  • Changed: the item cache codes are e6 ('expand') and e7 ('uids'), so records cached by 0.7.13, on disk included, aren't read. See Caching.
  • Changed: a record leaves out a relation holding an unfilled reference and drops such members from a list; neither is fetched as a related seed. See SeedRecord.
  • Added: initializeQueryPlatform({ models }) and setQueryModels(models) take a schema file's models, so List relations come back under their schema keys (co_author_identity_ids as co_authors and coAuthors). Without models the key is still the first word of the EAS name. See setQueryModels.
  • Added: getSeedAttestations(seedUids, { source, pins }) returns each seed's record with the property attestations it's assembled from. See getSeedAttestations.
  • Fixed: only bytes32 and bytes32[] properties are relations. A string external_id is back under external_id and externalId, as in 0.7.12, keeps its value under 'expand', and isn't fetched as a related seed. 0.7.13 returned it as external. The _id and _ids name rule applies only to an attestation with no type. See SeedRecord.

@seedprotocol/eas

  • Breaking: PUBLISH_AUTHORIZATION_DEFAULT_EXPIRATION_SECS is 90 days; it was 365. See Tool attestations.
  • Added: isUnfilledReference(value) says whether a relation value is a bytes32 slot a publish never filled, non-zero and below 2^32. See Reading Versions and properties.

@seedprotocol/arweave

  • Breaking: probeGateway(url, signal) from JavaScript rejects with a TypeError (probeGateway takes options: pass { signal }, not an AbortSignal) before any request. The signal was ignored. See Other read helpers.
  • Breaking (types): ArweaveTransaction is the arweave package's Transaction, and createTransaction returns Promise<ArweaveTransaction> on BaseArweaveClient, IArweaveClient and the default client. Both were any. Nothing changes at run time. See Transaction methods.

@seedprotocol/feed

  • Fixed: in Atom, a present null in an item's updated or links keeps the default. updated: null gave an entry with no <updated>. See Field mapping.

@seedprotocol/mapping

  • Breaking: the document lookup table is removed: MappingDocument.lookups, MappingLookups, the lookups option of applyMapping and applyMappingAsync, resolveLookup's third parameter, and lookups on previewForEdge's options and buildPropertyModeRows's argument. Lookups read lookup.entries on the edge. A saved table is ignored with no warning, and its values count as misses. See Saving and loading a mapping.
  • Breaking: lookups and onLookupsChange are removed from FieldMapper and useFieldMapper. Without renderLookup, the built-in text editor shows on lookup rows; slots.lookups: false hides it. It needed onLookupsChange. See Built-in editor.
  • Breaking: connectSourceToProperty returns FieldMapping[]. It returned { mappings, clearedLookupProps }.
  • Fixed: applyResolveToEdge(edge, 'lookup') keeps split, so useFieldMapper().setTransform(rowId, 'lookup') on a lookup edge no longer clears it. See Edge helpers.

0.7.13

Released 2026-10-29. Upgrade steps: Upgrading from 0.7.12 to 0.7.13.

@seedprotocol/sdk

  • Breaking: a model or property whose EAS name is a word the Solidity ABI reserves (view, event, error, storage, struct, function, indexed, returns and the other keywords) is refused. A schema file with one makes client.init reject with Could not load config.schema: Schema <name>: <Model>.<property>: Property "view" publishes as "view", …, and ModelProperty.create, and Model.create with properties, reject. EAS couldn't register or attest such a schema, so publish failed at its first encode. A List of Relation is exempt. See Reserved Solidity words.
  • Breaking: an Image, File or Html property whose file isn't found reads as undefined. Its value was the storage seed's ID (Html), the file name (Image) or 'No file found' (File). The new valueStatus ('resolved' | 'fetching' | 'missing', also on IItemProperty) says which case applies, and missingFile gives the path looked for. A missing property reads its file again when a save of that file is announced, by a download or another tab. See valueStatus and missingFile.
  • Changed: Item.find rejects with the new ClientNotInitializedError (code: 'CLIENT_NOT_INITIALIZED') once readyTimeout passes without an open database, and client methods that need init throw it before init has finished and after client.stop() or client.unload(). Both threw a plain Error; the message is unchanged. Exported from both entries. See Errors.
  • Changed: a repeat client.init with the same config and empty addresses ([], or { owned: [], watched: [] }) keeps the stored addresses, so a SeedClientGate remount before a wallet connects doesn't drop them. A different non-empty set still replaces them, client.setAddresses([]) clears them, and the first init, or the first after stop(), with [] still clears them. See Addresses.
  • Changed: an edit that puts a property back to its latest attested value leaves no draft: when the write is applied, the property's drafts are deleted, so getPublishPendingDiff, cost estimates and publish leave it out. It was listed with the same current and published value and attested again. Relations compare by the seed they name, lists member by member in order. Going back to an old value after sync stored a newer one is still an edit, and drafts written by 0.7.12 that equal the attested value stay until the next publish. getPublishPendingDiff also leaves out a pending edit back to the published value before it's applied. See Writing.
  • Changed: recording a publish gives the seed, version and property rows that were unsealed drafts the attesting address as their publisher. A draft stamped with the EOA and published by the smart account named the EOA. A sealed row's publisher never changes.
  • Added: client.resetLocalData({ otherTabsTimeoutMs, filesDir }), browser only, stops the client from any state, has the database's other tabs stop theirs, deletes the database and every file under filesDir from OPFS, and resolves with { deleted, failed }; a later init starts empty. Each tab holds the Web Lock seed:open:<db> while its database is open. The client snapshot's context.stopReason (ClientStopReason: 'stop' | 'reset') says why a stopped client stopped. localStorage isn't touched. In Node it throws. ResetLocalDataOptions, ResetLocalDataResult, ClientStopReason and the static BaseFileManager.unmountFileSystem() are exported. See Deleting local data and When another tab resets.
  • Added: getPublishModels(modelNames, { schemaName, followRefs }) lists the models a publish of items of those models attests, with each property's publish kind, from the loaded definitions. Main entry only. See Other read helpers.
  • Added: IItemProperty.save declares { timeoutMs }, so a property from item.properties or useItemProperty takes it without a cast. See save.
  • Added: @seedprotocol/sdk/node exports entityLoadError. See Node entry point.
  • Added: lower-level publish helpers, used by @seedprotocol/publish: recordPublishRequest, which records a request's version and property uids in one transaction, and publishRemovesValue. applyPropertyAttestationUidsFromPublish takes publisher, and its parameters type is exported as ApplyPropertyAttestationUidsParams.
  • Fixed: client.init after client.stop() with another filesDir uses the new directory. It applied the first directory's schema files to the new database, and read and wrote files there.
  • Fixed: a Schema loaded after a ModelProperty edit or rename, or after a model was created at runtime, starts edited with those changes, so saveNewVersion() writes them to the next version file. It loaded unedited and returned ''. See saveNewVersion.
  • Fixed: the slow-save and removed-event warnings stay quiet in a browser production build, including one with no process global.

@seedprotocol/react

  • Changed: useGlobalServiceStatus() types status as ClientManagerState | 'stopped' and internalStatus as ClientManagerState | 'ready' | 'stopped' (both were string), and returns stopReason: 'reset' when client.resetLocalData(), in this tab or another, stopped the client, 'stop' after client.stop() or client.unload(). See useGlobalServiceStatus.
  • Changed: useCreateModelProperty().create rejects with ClientNotInitializedError while the database isn't open. It rejected with No stored schema has the name or schema file id "<id>". See useCreateModelProperty.
  • Changed: useItem re-renders when an idle item's seedUid, latestVersionUid, latestVersionLocalId, lastVersionPublishedAt, attestationCreatedAt, revokedAt or publisher changes, from sync, another tab, a late publish record or an unpublish. It re-rendered only on busy, idle and error changes and value changes. See useItem.
  • Changed: useCreateItem compares arrays and plain objects in a call's values and options by contents when it decides whether a call repeats one in flight, so a double click whose values hold an array built in the handler creates one item. A File, Blob or class instance still has to be the same object. See useCreateItem.
  • Added: useItemProperty returns valueStatus, and re-renders when the property's file is found or missed. See useItemProperty.

@seedprotocol/publish

  • Changed: ensureEasSchemasForItem asks only for the schemas a publish of the item attests: each seed's own model, properties with a value or with an empty value that removes a live attestation, and string storage_transaction_id for a seed that uploads. It asked for every property of every model in the publish graph, so an automation run failed on a schema nothing would attest under, and an owner's publish registered schemas no attestation used. It checks every schema before registering any, and returns { missing, registered }. Attestation steps no longer check schemas a second time. See Model and property schemas.
  • Changed: an automation key that meets missing schemas throws the new EasSchemasNotRegisteredError (code: 'EAS_SCHEMAS_NOT_REGISTERED'), whose missing lists every missing definition, before anything is sent. The plain Error it replaces named the first one and pointed to ensureEasSchemasForItem; the message now points to ensureEasSchemasForModels. A publish run's checkingFailed error is this instance. See Other exports.
  • Changed: ConnectButton's disconnect removes the in-app wallet's sign-in token from localStorage, so a reload no longer signs back in a user who chose Disconnect. See Signing out.
  • Changed: useItemPublishStatus().isActive is false once the run's value is success, failure or stopping, even while its final save is written. A run waiting for retry stays active. See useItemPublishStatus.
  • Changed: a contract publish records each request's version uid and property uids in one transaction, so getPublishPendingDiff read right after a first publish no longer lists every property as pending.
  • Added: ensureEasSchemasForModels(modelNames, account, { schemaName, followRefs, dryRun, managedAddress }) registers, with no item, every EAS schema a publish of those models can attest under. Run it with the owner's wallet before automation publishes, and again after a schema version adds a property; with dryRun it returns what's missing and sends nothing. See Other exports.
  • Added: describePublishRun(value) gives a publish machine value's display phase, whether the run waits for retry (awaitingRetry) and whether it has ended (terminal), with the types PublishRunDescription and PublishRunPhase. See Display helpers.
  • Added: @seedprotocol/publish/thirdweb exports disconnectPublishWallet and markPublishWalletConnected, which ConnectButton calls on disconnect and connect, and clearInAppPublishSession, which removes the stored in-app wallet session for a "delete local data" action beside client.resetLocalData(). disconnectPublishWallet throws before publish config exists. See Signing out.
  • Fixed: a Node script that published no longer stays alive up to 15 seconds after the publish succeeds.
  • Fixed: the package no longer ships declarations for its tests.

@seedprotocol/query

  • Breaking: relations: 'urls' is removed, and RelationsMode is 'expand' | 'uids'. It fetched the same related seeds as 'expand'. Untyped code that still passes it gets relations as UIDs, with the related seeds still fetched. See queryBySchema.
  • Breaking: relations come back under their schema key in both relations modes and in changelog entries: author_id as author, a list's author_identity_ids as authors, with a camelCase copy only when it differs. 'uids' records, and 'expand' ones whose related seed was missing, kept the EAS name and its camelCase copy (authorId). The key comes from the EAS name: a property whose EAS name ends in _id or _ids is read as a relation and loses the suffix, whatever its type, and a list's key keeps only the first word of its EAS name (co_author_identity_ids comes back as cos). The cache keys changed with the shape (the item codes are e3 and e5, were e1, e0 and e2), so records cached by 0.7.12 aren't read. See SeedRecord.
  • Breaking: queryBySchema, queryBySchemaForMonth and assembleSeeds leave out a Seed with no Version from its attester, as getSeed and getSeeds have since 0.7.12. They returned it with versionUid: '' and no properties. limit and skip count the Seeds listed before that, so a page can hold fewer items than limit while later pages have more. See queryBySchema.
  • Changed: hydrateStorage treats a gateway's 4xx other than 429, such as a 404 for a body it doesn't have, as a miss: it tries the next read gateway and leaves the first one's circuit closed. A 404 opened the circuit for 60 seconds. It fetches a /raw/<id> link as {gateway}/raw/<id> on each gateway, where it fetched {gateway}/<id>, and caches bodies by link form and ID.
  • Changed: engines declares Node 20.18.1 or later, as @seedprotocol/eas does.

@seedprotocol/eas

  • Breaking: createEasNodeDispatcher is async and returns Promise<Dispatcher | undefined>, and NodeEasClient's protected getDispatcher() returns a promise. It loads undici's Agent with a dynamic import that bundlers resolve from @seedprotocol/eas, so an app that bundles eas (Astro with ssr.noExternal) no longer fails its first EAS request with Cannot find module 'undici/lib/dispatcher/agent.js'. NodeEasClient creates the dispatcher once and awaits it before each request. See registerNodeEasPlatform.
  • Added: createEasGraphQLClient's dispatcher option takes a promise, awaited before each request; a request aborted meanwhile isn't sent. See Client classes.

@seedprotocol/arweave

  • Breaking: probeGateway(baseUrl, { signal, timeoutMs, recordCircuit }) takes an options object; the signal was the second argument. It times out after timeoutMs (default 5,000) with or without a signal. A probe the caller aborts resolves false without counting as a failure in the circuit breaker, where it opened the host for 60 seconds. recordCircuit: false probes a host whose circuit is open and records nothing. See Other read helpers.
  • Breaking: DEFAULT_ARWEAVE_GATEWAYS is arweave.net, zigza.xyz, ar.seedprotocol.io, so reads, probes, status checks and failover (nextArweaveGatewayUrl, SeedHtml) no longer go to permagate.io. Its /<id> links still parse and get re-hosted: it's in the new KNOWN_ARWEAVE_GATEWAYS, which isKnownArweaveGatewayHostname and link parsing check. See The default gateway and its fallbacks.
  • Changed: resolveSeedGatewayEndpoints takes timeoutMs, one budget for all of a resolve's probes. A probe cut short by it or by signal isn't cached; it was cached as unreachable for 3 minutes. A failed probe is cached for 60 seconds, a healthy one for 3 minutes. See resolveSeedGatewayEndpoints.
  • Changed: ensureReadGatewaySelected(signal) caches nothing when signal aborts before a gateway passes, and returns the last healthy host, else the current one. It cached the aborted probe as no healthy gateway for 60 seconds. See ensureReadGatewaySelected.
  • Changed: before endpoints resolve, getPublicArweaveBaseUrl returns the read host only when it's a gateway's root. A read host with a path prefix, such as an app proxy, gives the env host or https://arweave.net, as a loopback address does; it was returned, and a publish at that point wrote it into Html.
  • Changed: parseArweaveTransactionLink reads {base}/<id> as a link when base is a read base URL with a path prefix, such as an app proxy at https://app.example/api/seed-gateway, so rehostArweaveLinksInHtml, publishedArweaveLinksInHtml and SeedHtml's failover handle such links. A base with a path prefix no longer makes /<id> on its host's root a link. nextArweaveGatewayUrl matches the longest base a URL is on. See Links in Html.
  • Added: resolveSidecarOrigin(config) gives the local Hyper sidecar's { host, port, baseUrl }. cancelGatewayHostHalfOpenProbe, the ProbeGatewayOptions type and ARWEAVE_GRAPHQL_TIMEOUT_MS (30000) are exported. See resolveSeedGatewayEndpoints and Constants.
  • Fixed: fetchTransactionStatusAcrossGateways has its doc comment in the published declarations again.

@seedprotocol/feed

  • Breaking: an item with a storage transaction and no link of its own has the RSS guid ar://<txId>, so its Atom entry ID and JSON Feed item ID are that too. Its link is still https://<public host>/raw/<txId>. The guid was that link, so it changed with arweaveDomain; feed readers show these items once more, and a later gateway change doesn't repeat them. A guid is no longer moved onto the public gateway with an item's other Arweave links. A cached document keeps the old guids until it's rebuilt. See Field mapping.
  • Changed: engines declares Node 20.18.1 or later.
  • Fixed: an Atom entry keeps an item's own title, updated and links; 0.7.12 replaced them with the defaults. A field that's present but null replaces the default too, so updated: null gives an entry with no <updated>.

@seedprotocol/feed-hyper

  • Changed: engines declares Node 20.18.1 or later (it was 20).

@seedprotocol/gateway-hyper

  • Fixed: a client refused by address gets the 403 for every request; sequential requests alternated between the 403 and a 502 (Unexpected end of stream). The operator's 403 carries connection: close, and the client opens a new stream after it. Both the operator and the client need 0.7.13. See Restricting who can connect.

@seedprotocol/mapping

  • Breaking: LookupEntry is { value: string; refs: string[] }, an ordered list of refs, and ref is removed from the type. A stored { value, ref } still applies as refs: [ref], and lookupEntriesFromMapping returns it in the new shape. A List target gets every ref, a Relation target the first. lookupRefFromEntries is replaced by lookupRefsFromEntries, which returns the list, and resolveLookupMapped by resolveLookup, which returns { refs, unmatched, split }. FieldMapperLookupRow has a required onSplitChange. See LookupEntry.
  • Changed: lookup entries match on the new lookupKey (NFC, trimmed, whitespace collapsed, case-folded), so 'mira chen' finds the 'Mira Chen' entry. normalizeLookupKey adds NFC and keeps case. The deprecated document-level lookups table still matches exactly. The fallback LookupEditor shows one row per key and takes refs separated by commas or spaces. See Relation lookups.
  • Changed: an empty source value on a lookup edge is no lookup: applyMappingAsync doesn't list it as an error.
  • Changed: autoMap(sources, targets, previous) keeps the entries and split of a property that stays a lookup in previous, and useFieldMapper's Auto-map passes its current mappings, so Auto-map no longer drops every assignment. withPreservedExtras keeps a lookup's entries and split when it moves to another source. See autoMap.
  • Added: a lookup edge takes split: { separators } ('and', '&', ','; DEFAULT_LOOKUP_SEPARATORS is ['and', '&']): a value with no entry of its own is split and each part looked up, with the known parts' refs applied in order. New splitLookupValue, withLookupSplit, useFieldMapper().setLookupSplit and the types LookupSeparator, LookupResolution and UnmatchedLookup. See Relation lookups.
  • Added: applyMapping takes onUnmatched, called with { sourceId, propertyName, value, splitFrom? } for each lookup value with no entry; only applyMappingAsync reported misses. collectLookupValues(items, mapping) lists each distinct value a lookup's source holds across items. See applyMapping.
  • Fixed: an extract or file edge on an empty source passes '' as the resolve callback's url, and lookup samples, previews and LookupEditor use the source's value only. They fell back to the source's label, so the field's name went to the callback as a URL, and an entry written in the editor for an empty source never matched.

@seedprotocol/vite

  • Fixed: seedVitePlugin's doc comment is on the function in the published declarations again, so editors describe it.

0.7.12

Released 2026-10-27. Upgrade steps: Upgrading from 0.7.11 to 0.7.12.

Every package's .d.ts files resolve under moduleResolution: node16 and nodenext: relative imports carry .js or /index.js. Under those settings, names re-exported with export * were missing (TS2305) and named re-exports were typed any. drizzle-orm's own declarations still fail without skipLibCheck, so keep it on.

@seedprotocol/sdk

  • Breaking: validation errors carry named codes, in ItemCreateValidationError.problems[].code, ItemPropertySaveValidationError.validationErrors[].code and validate() results: type for a wrong type, pattern, min_length, max_length_exceeded, enum_violation, format for a Date that isn't one and unknown_data_type, and in a property definition also required, unknown_field, minimum, maximum and rule_not_applicable. Type and pattern failures were TypeBox's numbers as strings ('41', '53', '62'), and a minLength failure was coded max_length_exceeded with a "characters or less" message. An enum miss is one problem whose message lists the allowed values; it was two, and every other problem on a property with an enum was coded enum_violation too. The 255-character Text message suggests validation.maxLength or an Html property instead of a File property. See ItemPropertySaveValidationError.
  • Breaking: pattern, minLength and maxLength on a property that isn't Text or Html (or a List of those) fail with rule_not_applicable, and an unknown type name such as Integer fails with unknown_data_type; blob is no longer read as Blob. A schema file with either is rejected when it loads, before anything is stored, so client.init rejects. The rules were ignored and unknown names kept. validation.custom is ignored with a warning: the schema loads, and the property's validate() lists it under warnings. See Validation.
  • Breaking: Item.getById(id) takes no cache hold unless called with { hold: true }; without one the item stays cached for the grace period, as after a find. Item.createById still holds. See Cache helpers.
  • Breaking: Item.find called before client.init or after client.stop() rejects once readyTimeout passes (15 seconds by default), with a plain Error whose message is ClientManager is not initialized. Please call init() first. and which has no code. It resolved undefined, as for an item that doesn't exist. A find called while init runs waits for the database. See Item.find.
  • Breaking: Item.create checks its initial Relation and List of Relation values when its write is applied, as an assignment's are. A seedLocalId that names no item rejects the create with OutboxOpFailedError (Relation target not found: <id>, errorName: 'RelationTargetError'), and a target whose own create is still queued is waited for. The create used to store the dangling ID. seedUids and the new item's own ID pass. See Validation.
  • Breaking: getArweaveUrlForTransaction is @seedprotocol/arweave's, which returns {first read gateway}/raw/{id}. The SDK's deprecated copy is removed. See Media references.
  • Breaking: DEFAULT_ARWEAVE_HOST, and so the default SeedConfig.arweaveDomain, is arweave.net (it was ar.seedprotocol.io), from @seedprotocol/arweave. Reads try it first, and publish writes it into Html links. An app that wants Seed's gateway sets arweaveDomain: 'ar.seedprotocol.io'. See SeedConfig.
  • Changed: client.removeLocalCopiesForAddresses(addresses, options?) keeps a copy whose publisher is still an owned address and that has an unpublished edit or a pending outbox write, and lists it in the result's new keptSeedLocalIds. It deleted those copies, and their edits, with the rest; { includeUnpublished: true } still does. See client.
  • Changed: an Html property's value shows its Arweave links (ar://<id>, /raw/<id> on any host, /<id> on a known gateway) on this reader's first read gateway. The stored file keeps the links as written, and exportArchive exports it unchanged. See How readers re-host links.
  • Changed: publish uploads an unpublished Html value with every Arweave link on the public gateway (arweaveDomain) in its path form, or as ar://<id> with publish's htmlArweaveRefs: 'ar', and saves the file that way. A link that a reader had re-hosted onto a sidecar or app proxy, and that an editor saved back, was uploaded as that local URL. prepareHtmlEmbeddedImagesForPublish and rewriteHtmlEmbeddedImagesOnDisk take { htmlArweaveRefs }. See What publish writes.
  • Changed: a publish waits only for the pending outbox writes on the items in its graph, and reads the graph again if any were pending. It waited for every pending write in the database, so one stuck write held up every publish. getPublishGraph and what's built on it (exportArchive, validateItemForPublish, getPublishPayload, the upload and cost summaries) wait the same way. See Other read helpers.
  • Changed: sync records no conflict for a saved Image, File or Html draft, and the draft stays the value. Only edits still in the outbox were exempt. See Conflicts.
  • Changed: on Node, client.stop() and BaseDb.close() complete open BaseDb.liveQuery subscriptions, which got a Failed query: select … error instead. Node also deletes entity_changes rows older than 10 minutes when the database opens and every minute after, on a timer that doesn't keep the process running; the table grew without limit. See Browser and Node.
  • Changed: outside production, emitting syncDbWithEas or fs.downloadAll.request with no listener logs a warning once per event, naming client.syncFromEas(). See Events.
  • Changed: an automatic sync (at startup, on an address change) merged into a run that a client.syncFromEas() call started is retried when that run fails. See Offline retries.
  • Changed: ItemProperty.storagePropertyName is the name new rows are stored under: authorId for a single Relation author (it was author), and <singular name><Model>Ids for a List of Relation. See Identity fields.
  • Changed: ItemPublishOptions.dataItemSigner is typed as an object with address and signMessage, such as an ethers Wallet or a SeedSigner. Any value type-checked, a string included.
  • Added: ItemProperty.save({ timeoutMs }) rejects with the new OutboxSaveTimeoutError (code: 'OUTBOX_SAVE_TIMEOUT', the queued write's opId) once that many milliseconds pass, and cancels nothing: the write stays queued. Without timeoutMs a save still waits with no limit, and outside production one still waiting after 10 seconds logs a warning naming the property. The IItemProperty type doesn't declare the option. OutboxSaveTimeoutError is exported from both entries. See save.
  • Added: when save(), Item.create or a delete rejects with OutboxOpFailedError because of a missing relation target or a file whose bytes were never stored, cause is a RelationTargetError (with targets) or a StagedValueMissingError (with path), rebuilt from the stored failure. No caller got an instance of either class before.
  • Added: entityLoadError(instance) returns the error an entity's last load failed with while its status is 'error'. Exported from the main entry only. See entityLoadError.
  • Added: @seedprotocol/sdk/node exports TabLockTimeoutError. See Node entry point.
  • Added: the main entry re-exports rehostArweaveLinksInHtml, nextArweaveGatewayUrl, mapUrlAttribute, parseArweaveTransactionLink and the types ArweaveTransactionLink and HtmlArweaveRefs from @seedprotocol/arweave. See Links in Html.
  • Fixed: a property assigned right after Item.create(…, { waitForReady: false }) is saved. In a process's first creates, the assignment was lost once the properties loaded.
  • Fixed: Item.create({ modelName: 'Post', title: 'Hello' }) type-checks as an object literal; the property values failed the excess-property check. The Property builders from @seedprotocol/sdk/node keep their own parameter types, so Property.Html({ htmlEmbeddedDataUriPolicy: 'preserve' }) type-checks, and Property.List() without an element type and a third argument to Property.Relation that isn't a boolean don't. See Property builders.

@seedprotocol/react

  • Breaking: useModelProperty(schemaId, …) and useCreateModelProperty().create(schemaId, …) no longer cut a trailing -<n> off a schema ID that no stored schema has as its name or file ID, so blog-2 doesn't resolve to blog. useModelProperty finds nothing for such an ID, and create rejects with No stored schema has the name or schema file id "<id>", also when called before the app database is open. See useCreateModelProperty.
  • Changed: useGlobalServiceStatus() reports status and internalStatus as 'stopped' after client.stop() until the next init starts. Both read 'idle', so a stopped client looked ready. status is typed string (it was ClientManagerState). See useGlobalServiceStatus.
  • Changed: useCreateItem hands a running create to a second call only when it's the same call (same model, values and options equal key by key), as from a double click; another call creates its own item. A value that's a new array or object counts as different. isLoading stays true until every create finishes. See useCreateItem.
  • Changed: SeedHtml re-hosts the Html's Arweave links onto this reader's first read gateway before calling sanitize, so ar:// links reach the sanitizer as https URLs. When an img, video, audio or source in it fails to load, it moves to the same transaction on the next read gateway. sanitize runs when html or sanitize changes, not on every render. See SeedHtml.
  • Added: attachArweaveLoadFailover(container) adds that failover to markup an app renders itself with SeedHtml's render prop. See attachArweaveLoadFailover.
  • Added: isClientReadySnapshot, the SDK's, is re-exported. See useIsClientReady.
  • Fixed: useCreateModel, useCreateModelProperty and useCreateItemProperty set error to the entity's own load error when there is one, as useCreateSchema does. They always set a generic "Failed to create …" message.
  • Fixed: useItemProperty re-renders when only the property's conflict changes, from sync or another tab. useItemProperties re-renders after another tab's save. See useItemProperty.

@seedprotocol/publish

  • Changed: every publish holds its item until the publish stops (it succeeds, fails or is stopped). createPublish takes the hold, so PublishManager.createPublish, ensureWalletThenPublish and direct callers get it, not only item.publish(). See PublishManager.createPublish.
  • Changed: createPublish picks the data item signer for every entry point: the option's dataItemSigner, else the config's, else the registered session's when that session publishes as the address, else the wallet's own dataItemSigner, else its signer unless it's kind: 'smart'. PublishManager.createPublish called directly set none, so a bundler or Turbo publish failed at signing, and item.publish() let the session's signer override a configured dataItemSigner. See CreatePublishOptions.
  • Changed: PublishWalletKind adds 'modular', the modular executor's in-app EOA, which publishes as the user's ManagedAccount. ConnectButton with useModularExecutor and ensureSmartWalletThenPublish's modular path register it as 'modular'; they said 'smart', which tells publish the signer can't sign data items. Direct EAS refuses 'modular' as it does 'smart'. See Wallets.
  • Changed: ensureSmartWalletThenPublish signs data items with the registered session's dataItemSigner under useModularExecutor, not the in-app account's signer. Without useModularExecutor and with no registered session, it signs with the connected account when that's the smart wallet's admin EOA; it had no signer, and bundler publishes failed at signing.
  • Changed: useSeedWallet().connect registers the wallet's kind, so a direct-EAS publish from an EIP-1193 or ethers wallet works without useDirectEas. See useSeedWallet.
  • Changed: a publish whose revocation of retracted properties fails rejects with the new PublishRetractionError (code: 'PUBLISH_RETRACTION_FAILED'), which says the new attestations are in place and that PublishManager.retryAttestations(seedLocalId) sends the revocation again. It rejected with the attestation-failure message. See item.publish() and publishItem.
  • Changed: checking refuses a publish with no publisher address, and a wallet that can't revoke a pending retraction, before it registers EAS schemas, so a refused publish no longer pays for them. Validation still runs after registration. See What a publish does.
  • Changed: cost estimates count charged bytes on the signed data items: each upload plus its ANS-104 header and tags. With user-paid storage, estimatePublishCost reads Turbo's free data item size from its /v1/info (cached 10 minutes per upload URL) and falls back to 102,400 bytes; it always used 102,400. Estimates report it as arweave.freeBytes, and where uploads go as arweave.uploadTo. See estimatePublishCost.
  • Changed: the gas heuristic adds 35,000 per revocation (PUBLISH_GAS_HEURISTICS.REVOCATION, not measured), and PublishWorkSummary declares revocationCount.
  • Fixed: watchPublishCost's price refresh keeps the estimate's gas, charged bytes, payers, uploadTo, freeBytes and testnet and applies the new rates, through the new repricePublishCostEstimate. It repriced a Turbo or testnet estimate as an upload-API mainnet one. See watchPublishCost.
  • Fixed: usePublishCostEstimate shows a previousEstimate passed in its options until its first estimate arrives. See usePublishCostEstimate.
  • Fixed: a tab that opens while another tab is uploading no longer marks that publish interrupted.
  • Added: PublishProvider takes seedConfig, addresses and onInitError and passes them to the SeedProvider it renders, which starts the client when seedConfig is set. See PublishProvider.
  • Added: htmlArweaveRefs, in the initPublish config and the createPublish and item.publish() options. 'gateway' (the default) writes https://<public gateway>/raw/<id> into published Html; 'ar' writes ar://<id>, which loads only in a Seed reader. See Options.
  • Added: MissingSignedDataItemsError is exported. uploadApiBaseUrl is optional in the PublishConfig type; initPublish still requires it unless payment.storage is 'user', and the resolved config's is '' when unset.

@seedprotocol/vite

  • Breaking: seedVitePlugin throws on an option it doesn't take. autoInit says it was removed, and any other unknown option names the ones it takes (fsModules, debug, includeNodePolyfills, isolationHeaders). Both were ignored. See Options.
  • Changed: includeNodePolyfills: true also polyfills timers (timers-browserify), so @seedprotocol/mapping's rssXmlToSources builds for the browser. See With includeNodePolyfills.
  • Changed: debug: true skips chunks made only of Seed packages' modules when it reports Node globals. A chunk that mixes app and Seed code is still scanned. See Warnings.
  • Fixed: @zenfs/core (^2.7.7) and @zenfs/dom (^1.2.14) are declared as optional peers.

@seedprotocol/next

  • Added: withSeed's dir option, the directory it resolves the SDK and next from and makes Turbopack alias paths relative to (default process.cwd()). Pass the config file's directory when Next.js runs from somewhere else, such as a monorepo root, where the aliases were skipped. See Options.

@seedprotocol/mapping

  • Breaking: the --fm-* and --map-* CSS custom properties are removed. Theme with --sfm-*; wire strokes default to var(--sfm-map-N, currentColor). See Classes and CSS variables.
  • Changed: applyMapping and applyMappingAsync copy a source's value only, and lookups match its value only. An empty frontmatter field or Markdown body copies as ''; the field's name, or "Full document", was copied. See applyMapping.
  • Fixed: coerceValue(raw, 'Boolean') reads true, 1 and yes in any case, trimmed; "TRUE" was false. See coerceValue.
  • Fixed: useFieldMapper keeps each row's ID when another row is added, removed or edited, so React keys no longer shift.

@seedprotocol/arweave

  • Breaking: DEFAULT_ARWEAVE_HOST is arweave.net: the default read host, resolveSeedGatewayEndpoints's default arweaveDomain, and the host publish writes into Html links. DEFAULT_ARWEAVE_GATEWAYS is arweave.net, permagate.io, zigza.xyz, ar.seedprotocol.io, and DEFAULT_ARWEAVE_GRAPHQL_URL is https://arweave.net/graphql. They were ar.seedprotocol.io, a list that started with it, and https://ar.seedprotocol.io/graphql. An app that wants Seed's gateway sets arweaveDomain: 'ar.seedprotocol.io'. See The default gateway and its fallbacks.
  • Breaking: getArweaveUrlForTransaction(id) returns {first read gateway}/raw/{id}, the URL this reader loads from; it returned {read host}/{id}. The SDK and query re-export this one. For URLs that leave the reader, use getPublicArweaveRawUrl. See Media references.
  • Breaking: in http-gateway mode, and hybrid's HTTP fallback, with no uploadApiBaseUrl, resolveSeedGatewayEndpoints resolves it to ''. It fell back to the gateway's base URL. Set it when your gateway also serves the upload API. See resolveSeedGatewayEndpoints.
  • Changed: in a browser, hybrid tries the local sidecar only from a loopback page origin or with the new hyper.probeSidecarFromBrowser: true; transport: 'hyper' is unchanged. The new hyper.probeProxy turns the proxy's health check on or off by itself (it defaults to probeSidecar, which turned both). See SeedGatewayConfig.
  • Changed: with a sidecar or app proxy active, the read gateways continue after it with the public gateway and the defaults, so /raw reads fall back to them. fetchTransactionStatusAcrossGateways still asks only the sidecar or proxy and the preferred gateways then.
  • Changed: getArweaveReadGatewayHostsForPrimary keeps every default after a custom primary; it dropped ar.seedprotocol.io.
  • Changed: before endpoints resolve, getPublicArweaveBaseUrl returns the read host unless it's a loopback address, else the env host or https://arweave.net. An empty arweaveDomain means the default host; it produced https:///raw/<id>.
  • Changed: DefaultArweaveClient and NodeArweaveClient GraphQL requests abort after 30 seconds (Arweave GraphQL request timed out after 30000 ms) or when the caller's signal does. They had no limit. See Transaction methods.
  • Added: helpers for Arweave links in Html: rehostArweaveLinksInHtml, publishedArweaveLinksInHtml, parseArweaveTransactionLink, buildArweaveTransactionLink, nextArweaveGatewayUrl, rewriteHtmlAttributes and mapUrlAttribute, and the types ArweaveTransactionLink, ArweaveLinkOptions and HtmlArweaveRefs. The package depends on parse5. See Links in Html.

@seedprotocol/query

  • Breaking: getSeed returns null for a Seed with no Version from its attester, and getSeeds leaves it out. Both returned a record with versionUid: '' and no properties. With source: 'auto', such a Seed in the local database is read from EAS. queryBySchema still returns it. See getSeed.
  • Breaking: QueryDataSource.listSeedsByUidPrefix is removed. listSeedsBySchemaName(schemaName, opts) takes uidPrefix and attesters in opts (the SeedListFilter type) and applies them before limit and skip. A custom source that reads only limit and skip still type-checks but ignores both filters. A custom getSeedByUid gets the UID in lowercase. See QueryDataSource.
  • Breaking: image arweaveUrl fields and storage URLs in assembled Seeds are {first read gateway}/raw/{id}, from arweave's getArweaveUrlForTransaction; they were https://{read host}/{id}. isArweaveTransactionGatewayUrl accepts /raw/{id} on any host and /{id} on a known gateway. See SeedRecord.
  • Changed: getSeed lowercases the UID, so a mixed-case UID finds the Seed, and the record's seedUid is lowercase.
  • Changed: hydrateStorage fetches each body from the read gateways in order, skipping gateways whose circuit is open, instead of only the URL in the field, and re-hosts the Arweave links in each body onto the first read gateway.
  • Changed: reads with source: 'local' don't throw DuplicateEasModuleError or DuplicateArweaveModuleError, since they use neither client; initializeQueryPlatform takes source. See DuplicateEasModuleError or DuplicateArweaveModuleError.
  • Added: queryBySchema(schemaName, { attesters }) keeps Seeds attested by one of the addresses, in any case. On the remote source it's part of the EAS request, before limit and skip. Entries that aren't addresses are dropped, and with none left there are no items. Like uidPrefix, it skips the collection cache. normalizeAttesters is exported. See One publisher's items.

@seedprotocol/eas

  • Breaking: normalizeBytes32Hex returns '' unless the value is 64 hex digits, with or without 0x. It padded short values and turned non-hex characters into 0. The new bytes32FromNumberHex pads a decoded BigNumber's hex. See UID and address helpers.
  • Breaking: toSnakeCase is removed. Use easName.
  • Changed: the 30-second indexer timeout holds when a caller passes a signal, and where AbortSignal.any or AbortSignal.timeout is missing. A timeout rejects with TimeoutError: EAS indexer request timed out after <n> ms (it was The operation was aborted due to timeout), and the caller's abort with AbortError. See Client classes.
  • Changed: engines declares Node 20.18.1 or later.
  • Added: getSeedsBySchemaName(schemaName, limit, skip, { attesters }) keeps Seeds from those attesters; with no valid address in the list it returns [] without a request. checksumAddresses is exported, and getSeedsFromSchemaUids, getPublishedByFromEas, getDomainOwnershipFromEas and getPublishAuthorizationFromEas use it, so a non-address in their address lists is left out of the request. See getSeedsBySchemaName.

@seedprotocol/feed

  • Breaking: Atom entry IDs and JSON Feed item IDs are the item's RSS guid, so the three formats identify an item the same way. They were the Seed UID, so subscribers to an existing Atom or JSON feed see each item once more as new. An item's own updated and links no longer override Atom's. See Field mapping.
  • Changed: every Arweave link in a feed item, in link fields and inside Html, is on the public gateway (arweaveDomain, default arweave.net). An item with its own storage transaction links to https://<host>/raw/<id> (it was https://<host>/<id>), and the handlers use that link as its guid, so an Image feed's RSS guids change too and RSS readers show those items again.
  • Fixed: the stale fallback serves only a document built for the same site config; an entry cached before the upgrade isn't served stale until it's rebuilt. A weak If-None-Match (W/"…") for the current ETag gets a 304. An archive request whose year or month isn't a number gets a 400, not a 500. See Caching.

@seedprotocol/feed-hyper

  • Fixed: the declarations include module declarations for corestore, hyperdrive, hyperswarm, hypercore-id-encoding and serve-drive, so they compile without skipLibCheck.

@seedprotocol/gateway-hyper

  • Breaking: the default upstreamTimeoutMs is 130,000 (it was 30,000) and requestTimeoutMs 150,000 (it was 60,000), in the CLI too. Both are idle limits, set above the 120 seconds publish waits for an upload batch. See serveTunnel.
  • Breaking: storePath is removed from the serveTunnel, connectTunnel, createGatewayProxy and createTunnelSession options. Nothing read it.
  • Breaking: serveTunnel throws for an https:// upstream, so seed-gateway serve --upstream https://… stops. The framed tunnel speaks plain HTTP to its upstream; serveRawTunnel carries TLS end to end. See seed-gateway serve.
  • Breaking: serveRawTunnel throws Pass keyFile or keyPair, not both when given both. keyPair won. See serveRawTunnel.
  • Changed: an operator that refuses a client by address answers its request with a 403 that says so, instead of resetting the stream. A framed reader that gets plain HTTP says the other side runs in raw mode, instead of Frame too large: 1213486160 bytes. See Restricting who can connect.
  • Added: serveTunnel takes keyPair, as serveRawTunnel does. seed-gateway serve and connect take --bootstrap host:port,…, warn about each flag the mode ignores, and print their own usage with --help. formatTunnelEvent is exported. See seed-gateway CLI.

@seedprotocol/archive

  • Changed: engines declares Node 20.12 or later.

0.7.11

Released 2026-10-24. Upgrade steps: Upgrading from 0.7.10 to 0.7.11.

@seedprotocol/sdk

  • Breaking: @seedprotocol/sdk/node exports NodeDb and NodeFileManager in place of the deprecated Db and FileManager, which are removed. The classes are the same.
  • Breaking: migration 0030 drops the property_uids table, and propertyUids is no longer exported. Nothing wrote to it.
  • Changed: a local database that looks older than 0.7.7 is copied to seed.before-recreate-<ms>.db beside seed.db (in OPFS in the browser) before it's recreated, and the warning names the copy. If the copy fails, or the database is a remote libsql URL, it's opened as it is. A 0.7.7 database whose background clean-up never finished looks older, so 0.7.8 to 0.7.10 recreated it without a copy. See Storage.
  • Changed: with NODE_ENV=test or IS_SEED_DEV set, client.init rejects when storing its config or app state, adding the schema's models or running Node migrations fails, as it does without them. It used to carry on.
  • Added: OutboxOpFailedError, RelationTargetError and StagedValueMissingError are exported from both entries; save() rejects with the first. @seedprotocol/sdk/node also exports EntityUnloadedError, ItemCreateError, PublishValidationFailedError, RelatedItemUnpublishedError, HtmlEmbeddedDataUriLimitError, HtmlSeedPropertyRefError, exportArchive and ArchiveExportError. See Node entry point.
  • Fixed: clear() on an unloaded ItemProperty rejects with EntityUnloadedError before it writes. It cleared the value and then rejected.
  • Fixed: a property saved after a publish registered its EAS schema stores that schema's UID, so property.schemaUid has it. It stayed empty until the indexer had the schema. Saving a draft value again fills in a missing UID.

@seedprotocol/publish

  • Breaking: configurePublish (an alias of initPublish) and SEED_PROTOCOL_CONTRACT_ADDRESS_OP_SEPOLIA (the old name of MULTI_PUBLISH_ABI_REFERENCE_ADDRESS_OP_SEPOLIA) are removed.
  • Changed: enrollPublishAutomation defaults expiresAt to 365 days from now and gives the session key and its PublishAuthorization the same value. Without expiresAt, the key used to get thirdweb's ten-year window.
  • Changed: attestPublishAuthorization throws when wallet isn't the identity account.
  • Fixed: assessPublishAuthorizationLive reports a revoked grant as 'revoked' (it returned 'valid') and sees the grant's EAS expiration, so a grant past it is 'expired'. assertStorageBoundToIdentity (from /thirdweb) no longer accepts a revoked or expired grant. See Tool attestations.
  • Fixed: attestPublishAuthorization, attestPublishedBy and attestDomainOwnership check their inputs before registering or naming a schema, so a call that throws sends no transaction.

@seedprotocol/eas

  • Breaking: the deprecated EasClient and QueryClient aliases are removed from @seedprotocol/eas/node. Use NodeEasClient and NodeQueryClient.
  • Changed: assessPublishAuthorization takes revoked and returns the new status 'revoked' for a revoked grant. A switch over every PublishAuthorizationAssessStatus needs the case.
  • Changed: getEasSchemaUidForExactDefinition, getEasSchemaUidForSchemaDefinition and getSchemaUidForSchemaDefinitionFromCache share one cache, keyed by EAS endpoint and exact definition (string title), which setSchemaUidForSchemaDefinition writes. The keys were snake-cased and shared across endpoints. See getEasSchemaUidForExactDefinition.
  • Added: GET_PUBLISH_AUTHORIZATIONS, which selects expirationTime and revoked. getPublishAuthorizationFromEas uses it, so the grants it returns have both.

@seedprotocol/arweave

  • Breaking: the deprecated ArweaveClient alias is removed from @seedprotocol/arweave/node. Use NodeArweaveClient.
  • Changed: DEFAULT_ARWEAVE_GATEWAYS no longer lists arweave.dev and g8way.io, which don't resolve: it's ar.seedprotocol.io, arweave.net, permagate.io, zigza.xyz. See How the SDK reads.
  • Fixed: fetchTransactionStatusAcrossGateways doesn't start another gateway attempt when less than MIN_TRANSACTION_STATUS_ATTEMPT_MS (1000, new) or timeoutMs of the budget is left, whichever is smaller, so a near-zero attempt no longer times out and counts against the gateway. The first gateway is always asked.

0.7.10

Released 2026-10-24. Upgrade steps: Upgrading from 0.7.9 to 0.7.10.

@seedprotocol/sdk

  • Breaking: Item.find, Item.all, ItemProperty.find and ItemProperty.all take no cache hold unless called with hold: true. Item.create and ItemProperty.create still hold by default, and Item.getById still takes a hold. unload() on an instance no one holds does nothing; it used to stop it. Code that called unload() after a lookup without hold: true must drop the call, because it now releases a hold someone else took, such as a React hook's. See unload.
  • Breaking: an Item or ItemProperty no one holds stays cached for the grace period, SeedConfig.entityCache.unloadGraceMs (default 60,000 ms), after its last release or its last lookup without a hold, and then stops. A hold taken meanwhile keeps the same instance. Its last unload() used to stop it at once. destroy(), client.stop() and removals by other tabs still stop it at once, and the timers don't keep a Node process running. With unloadGraceMs: 0 the last unload() stops an instance at once, but one that was never held stays cached until it's evicted or the client stops. See SeedConfig.
  • Breaking: no lookup or create on Schema, Model or ModelProperty takes a hold unless called with hold: true (create, also when it returns a cached instance, getById, createById, find, all, Model.getByName, Model.getByNameAsync and Model.createBySchemaId). A definition stays cached until it's destroyed or evicted, or the client stops, whether or not anything holds it: its last unload() no longer stops it. See unload and destroy.
  • Breaking: writes on a stopped instance throw EntityUnloadedError, with a message naming the instance: assigning a property, setValue(), save(), publish() and unpublish() on an unloaded Item or ItemProperty, and assigning a field, saveNewVersion(), reload(), model.create(values) and modelProperty.save() on a destroyed or evicted Schema, Model or ModelProperty. Assignments used to do nothing, and publish() never settled. Reads still return the last values. EntityUnloadedError is exported from the main entry, not from @seedprotocol/sdk/node.
  • Breaking: client.stop() and client.unload() return a promise, which resolves once the client has released everything: the outbox stops applying writes (pending ones stay in the database and are applied after the next init), every cached Item, ItemProperty, Schema, Model and ModelProperty is stopped and dropped, this tab hands tab leadership to another tab, and the database closes. An instance kept across stop() is stopped; look it up again after init. An init called while a stop is still releasing waits for it. See Stopping the client.
  • Changed: @zenfs/core 2.7.7 and @zenfs/dom 1.2.14 (were 2.5.6 and 1.2.9). An app that installs them beside the SDK needs the same versions.
  • Changed: item.publish() holds the item until the publish settles, so an item looked up without a hold doesn't unload mid-publish (through @seedprotocol/publish, below).
  • Changed: on Node, an open BaseDb.liveQuery subscription no longer outlives client.stop(): its next poll fails with Failed query: select … and the subscription errors. Unsubscribe before stopping to avoid the error. See Browser and Node.
  • Added: hold?: boolean on Item.find, Item.all, ItemProperty.find, ItemProperty.all (default false), on ItemCreateOptions (default true; Item.create(values, { hold: false }) creates without one), and on every Schema, Model and ModelProperty lookup and create (default false).
  • Added: Item.retain(item), Schema.retain, Model.retain and ModelProperty.retain take one more hold on a cached, running instance, released with unload(), and return false when it has stopped.
  • Added: SeedConfig.entityCache ({ unloadGraceMs?: number }), the grace period above.
  • Added: supported browsers are Chrome and Edge 102, Firefox 114 and Safari 26 or later. In a browser without OPFS, FileSystemFileHandle.createWritable or Web Locks, client.init rejects with UnsupportedBrowserError (code: 'BROWSER_UNSUPPORTED', the absent features in missing) before it loads or stores anything. checkBrowserSupport() returns { supported, missing } to check first; it can't see worker-only features, so Chrome 86 to 101 and Firefox 111 to 113 pass it and fail later in init. Also the BrowserFeature and BrowserSupport types. See UnsupportedBrowserError.
  • Added: BaseDb.close() closes the app database; client.stop() calls it. See Other BaseDb methods.
  • Added: exportArchive(item, options?) writes a published item as a BagIt bag in memory, and throws ArchiveExportError (code 'not_published' or 'unpublished_changes'). Both are exported from the main entry, not from @seedprotocol/sdk/node. The SDK depends on the new @seedprotocol/archive. See Export a published item.
  • Fixed: client.stop() works before init, while it runs and after it fails; it threw ClientManager is not initialized. Please call init() first. An init after a failed one closes the database the failed one left open instead of opening it a second time.
  • Fixed: browser init no longer fails with journalOperations.values(...).map is not a function in browsers without Iterator helpers (Chrome before 122, Firefox before 131, Safari before 18.4), through the ZenFS update.
  • Fixed: in the browser, creating a file in an OPFS folder that the SQLite worker or another tab made after this tab started no longer fails with ENOENT … (Out of sync!). Firefox and Safari could hit it.
  • Fixed: a schema file's single Relation keeps its refValueType, so { "type": "Relation", "refValueType": "Image", "model": "Image" } saves a data: or blob: URL as an Image; it failed with Relation target not found. A File or Blob is still rejected; declare { "type": "Image" } for uploads. See Define the relations.
  • Fixed: an item property that's already loaded takes its property's new definition as soon as updateModelProperties or a new schema file version rewrites the property's row, so the next value assigned is checked against the new rules. It kept the definition it loaded with: on Node always, in the browser for a moment. See Editing a property.
  • Fixed: a property never shows the synced value beside its sync conflict. A read between sync's two writes could show it for a moment.

@seedprotocol/react

  • Breaking: useItem holds the item it shows while mounted and releases it a tick after it unmounts or shows another item, so a StrictMode remount keeps it. useItems holds each listed item while mounted and releases it when the item leaves the list or the hook unmounts; its list query is dropped once no hook uses it, so a remount fetches again. An item no hook or caller holds unloads after the SDK's grace period. Before, both hooks took holds they never released, so their items never unloaded. Code that keeps an item from them after unmount must hold it (Item.find({ hold: true }) or Item.retain, then unload()). See useItem and useItems.
  • Breaking: useSchemas, useModels, useModel (by ID), useModelProperties and useModelProperty hold what they show while mounted and release it on unmount or change; useSchema and useAllSchemaVersions pass hold: true. A list that includes a stopped instance, or whose instance stops while shown, is fetched again. useModelProperty took a hold on every lookup and never released it.
  • Breaking: useCreateItem, useCreateItemProperty, useCreateModel and useCreateModelProperty create without a hold. They held everything they created and never released it. Pass hold: true in createItem's options to hold the new item, and unload() it when you're done; otherwise a created item that no hook shows unloads after the grace period. See useCreateItem.
  • Fixed: useItem looks the item up again when the instance it shows stops (another tab dropped it, a local purge, destroy()). It kept the stopped instance until the next sync.

@seedprotocol/publish

  • Changed: publishItem, behind item.publish() and usePublishItem, holds the item until the publish settles. PublishManager.createPublish and ensureWalletThenPublish don't: hold the item yourself and unload() it once the publish settles. See PublishManager.createPublish.
  • Changed: in the browser, watchPublishCost and usePublishCostEstimate re-estimate after any write to the item's seed, version or property rows, from this tab or another, through the SDK's change feed. They compared the loaded property values on every item update, which they still do on Node. The new watchRows option replaces the row watch. See watchPublishCost.
  • Changed: a failed first estimate is retried once after firstEstimateRetryMs (default 5,000; 0 turns it off), and isEstimating stays true until the retry settles. It left estimate null until the item changed.
  • Changed: usePublishCostEstimate keeps showing its last estimate, with isStale: true, when it restarts for the same seedLocalId and publishMode with a new Item instance, until the new estimate arrives. It showed null in between. watchPublishCost takes the estimate to start from as previousEstimate. See usePublishCostEstimate.
  • Fixed: a publish restored from its saved record releases the item it loads to create attestations on the multiPublish path too, so the item can unload afterwards.

@seedprotocol/gateway-hyper

  • Added: client allowlists. serveTunnel({ allowlist: { keys, ips } }) and seed-gateway serve --allow-keys and --allow-ips let in only clients whose public key or address (an IP or a CIDR range) is listed; with neither list set, every client gets in, as before. An invalid entry stops the operator at startup. See Restricting who can connect.
  • Added: client keys. connectTunnel, createGatewayProxy and createTunnelSession take keyFile or keyPair for the key the client presents, and connectTunnel's result and TunnelSession have clientKey. seed-gateway connect takes --key-file, and --print-key --key-file <path> prints the client's key. Without one, each run uses a new key, as before.
  • Added: raw TCP mode. serveRawTunnel, connectRawTunnel, seed-gateway serve --raw and seed-gateway connect --raw pipe each TCP connection byte for byte over its own HyperDHT stream, so HTTP runs end to end with no 64 MiB limit. A raw operator needs its own key, and the SDK's sidecar and createGatewayProxy use the framed tunnel only. See Raw TCP mode.
  • Added: tracing. serveTunnel({ onEvent }) gets a TunnelEvent for each stream and each step of a request, and seed-gateway serve --trace prints them. requestIdHeader (--request-id-header) sends each request's ID upstream, replacing any value the client sent. upstreamRequestHeaders is exported. See Tracing an operator.
  • Added: bootstrap on every library entry point, to join a private or local HyperDHT network. The CLI has no flag for it and always uses the public network. See Private or local DHT networks.
  • Added: seed-gateway connect --connect-timeout-ms (default 30,000; 0 disables).
  • Changed: seed-gateway connect --port 0 picks a free port, and a value that isn't a whole number from 0 to 65535 stops the command with --port must be 0–65535. Both fell back to 1984. connect also prints Client key (z32): <key>. See seed-gateway connect.

@seedprotocol/archive (new)

  • Added: first release, a BagIt library that the SDK's exportArchive uses. These docs cover it only through exportArchive.

0.7.9

Released 2026-10-22. Upgrade steps: Upgrading from 0.7.7 to 0.7.9.

@seedprotocol/sdk

  • Breaking: a local database last opened by an SDK older than 0.7.7 is recreated at init. Its tables are dropped before the migrations run, and a console.warn says so. Unpublished items in it are lost, published items come back through EAS sync, and files under filesDir stay. To keep drafts, publish them first or start the app once on 0.7.8. The data repairs that 0.7.1 to 0.7.6 ran at start-up are gone with it.
  • Breaking: status on Schema, Model, ModelProperty, Item and ItemProperty is the load status, 'loading', 'ready' or 'error', not the actor's state value. Item gains status, and every entity has waitForReady({ timeout }). Entity actors start in idle, so waiting for idle no longer means loaded; a property with a pending save is 'ready'. A schema no longer reports { loading: <stage> } or 'validating'. The client manager's 'idle' is unchanged.
  • Breaking: a value that breaks its property's rules stays shown, with the reasons in saveValidationErrors, until the next assignment, and isn't written. save() still rejects with ItemPropertySaveValidationError. This reverses 0.7.7's rollback; a fresh load (a restart, another process) reads the stored value.
  • Breaking: a refused or failed write shows the property's current value again: its newest pending value from any tab, else its stored row. Ownership, a Relation's targets and the EAS schema UID are checked when the write is applied, so an assignment on an item the client doesn't own queues a write that fails with OwnershipError. save() still checks ownership before it waits.
  • Breaking: setValue(value, { before }) loses its option: setValue(value). showValue no longer remembers a value to roll back to, so if the property is re-read before the next setValue, the stored value shows again. The types PropertyValueSnapshot and SaveValueToDbEvent are removed. See showValue.
  • Breaking: schema, model and property IDs that an import assigns come from the names: the first 10 hex characters of SHA3-256 of blog, blog/Post and blog/Post/title, written into the stored version file. They used to be a hash of the whole schema, so adding a property changed every ID. A database that already has the schema keeps the IDs it stored, matched by name; a fresh database gets the derived ones; IDs set in the file are used as they are, with or without $schema. A model or property renamed without a set id is still a new one.
  • Breaking: schema files read the same way on every path (config.schema, importJsonSchema, Schema.create). importJsonSchema(input, version) uses version only when the JSON has none. Minimal <name>-v<n>.json files in the working directory are no longer imported at init; pass them as config.schema or import them. loadAllSchemasFromDb() only reads, and LoadedSchema.dbMatchesFile is removed. Renaming a published schema writes the next version file. A missing name fails with Could not load config.schema: Schema name is required (either at top level or in metadata.name), without the Failed to parse JSON import content: prefix.
  • Breaking: a property's stored rows are matched to it by its ID, not its name. A property renamed in code keeps its values after a restart. New rows of an Image, File, Html or Relation property are stored under <name>Id (a List of Relation under <singular name><Model>Ids), and getPublishPendingDiff reports each property under that storage name (authorId). pickLatestMetadataRowPerProperty groups rows by propertyId; code that filters metadata rows should match on propertyId.
  • Breaking: publishing a property with no stored definition throws. It used to attest the value as string <name>.
  • Breaking: getPublishUploadData(item, options) and getPublishUploads(item, options) take the options second, accept any { seedLocalId }, and lose the uploads and relatedItemProperty parameters. applyPropertyAttestationUidsFromPublish records a UID only on the row its metadataLocalId names. item.getPublishUploads(options?) is unchanged.
  • Breaking: watchEntityRows loses its since option, and window.__SEED_INVALIDATE_ITEM_PROPERTIES__ is no longer set or read.
  • Breaking: the SDK registers the EAS and Arweave clients from @seedprotocol/eas and @seedprotocol/arweave instead of its own copies. On Node that's NodeEasClient, with its own connection pool (see eas below); EasClient.configure(new NodeEasClient({ dispatcher: false })) after client.init goes back to fetch's defaults. EasClient from the main entry is BaseEasClient. Node's getTransactionData tries the preferred read gateways first, as the browser did.
  • Breaking: eventEmitter.emit('syncDbWithEas') does nothing: the SDK no longer listens for it. Call client.syncFromEas(). requestEasSyncFromEventBus, the EasSyncRequestSource type and the 'event_bus' sync source are removed.
  • Breaking: @seedprotocol/sdk/node loses the Markdown import (parseMarkdownFrontmatter, processSeedConfig, saveModelsFromMarkdown), withSeed (use @seedprotocol/next), getRefValueType, and the CLI helpers PathResolver, BasePathResolver, commandExists, ensureNodeDbSchema, runSeedInit, findSeedBinary, INIT_SCRIPT_SUCCESS_MESSAGE and SCHEMA_TS.
  • Breaking: removed from the main entry: the schema file API (createSchema, readSchema, deleteModelFromSchema, deletePropertyFromModel; listSchemas and getSchemaNameFromId stay), waitForEntityIdle (use waitForReady()), getSegmentedItemProperties, getRelatedItemsForPublish, getPublishDraftGraph and its types (use getPublishGraph), shouldAttestProperty, getEasSchemaForItemProperty, getEasSchemaUidBySchemaName (use getEasSchemaUidForExactDefinition), restoreDataItemIdsOverwrittenByL1, applyArweaveL1TransactionIdLocal, the statics Model.updateNameIndex, Model.trackPendingWrite, Model.getPendingModelIds and ModelProperty.adoptForModel, ClientManagerState.ADD_MODELS_TO_STORE, and the types GetItem, GetItemParams, GetPropertyDataOptions, SeedInitBrowserProps, SeedInitBrowser, DbQueryResult, ResultObject, SqliteWasmResult, SqliteWasmCallback, ReturnObj, PublishRequestData, PropertyToUpdateWithSeed, GlobalMachineContext, PublishMachineContext, GetSchemaForModelEvent, HydrateExistingItemEvent, WebpackConfigContext, Environment and ClientCallback.
  • Changed: save() has no time limit; the 10-second Timeout of 10000 ms exceeded is gone. A failed Image, File or Html write rejects with OutboxOpFailedError (code: 'OUTBOX_OP_FAILED', the original class in errorName, such as 'TypeError' for a failed fetch or 'Error' for No file data found), because the fetch or file read now runs when the write is applied, and the property shows its current value again. See save.
  • Changed: a File, Image or Html value is written when its write is applied: a string (a URL, a file name, Html text) travels in the outbox, and a Blob's or data: URL's bytes go to a staged file. A write whose staged bytes never landed fails with StagedValueMissingError. OutboxOpRetryableError is removed.
  • Changed: what an Image string becomes: any data: image URL is decoded (image/svg+xml and parameters included); any http(s): or blob: URL is fetched, localhost and IP addresses included; the name of a file already in the images folder links it; host.tld/path is fetched over https; raw base64 with an image signature is decoded; anything else fails with No file data found. A bare name such as photo.png is no longer fetched relative to the page. A new file without a source name takes its MIME type's extension (.svg).
  • Changed: an empty value on an Image, File or Html property ('' or null) clears it. '' used to save a storage seed pointing at the folder, and null failed while the old file stayed.
  • Changed: Html values no longer store a session URL, and reads ignore one an older SDK stored; an Html property's value is still the file's text.
  • Changed: a property's value is its newest row (or the newest pending write over it), revoked or not. A property retracted on the current Version reads as empty instead of showing an earlier Version's value, and a cleared Image no longer falls back to the image it replaced. getPublishPendingDiff reports a pending value as currentValue.
  • Changed: a property set to '' or null after it was attested publishes the removal: the next publish attests the EAS type's empty value instead of skipping it, which left the old value on chain. A published Boolean or Date with an empty draft fails publish validation with publish_clear_unsupported. See Remove a published value.
  • Changed: EAS sync stores an empty-value attestation, or a revoked property attestation on a live Seed, as a row with no value. A revoked Seed keeps its last values.
  • Changed: in the browser, loaded items learn about sync's writes from the change feed only, and a loaded item also watches its Seed's metadata rows, so a property sync adds shows on it. Sync no longer re-reads every cached item afterwards; Node still does. A loaded property's change-feed read no longer skips rows while one of its writes is pending; it shows the pending value.
  • Changed: a schema is applied at init only when its ID, version or content changed since it was last applied (the first start after upgrading applies each one again). Its rows are written in one transaction.
  • Changed: a property's whole definition is stored on its row, so validation rules set in code (Model.create, ModelProperty.create) apply after a restart. getPropertySchema returns the stored definition.
  • Changed: init's steps run back to back with no fixed waits. Model.all for a schema with no models returns at once, and reading the stored addresses before the database opens waits up to 30 seconds, then returns none. The order is processSchemaFiles, then addModelsToDb.
  • Changed: the SDK no longer handles the events fs.downloadAll.request, fs.downloadAllBinary.request, service.save and service.saveState.request, and nothing emits fs.downloadAll.success. The download after each sync still runs.
  • Changed: six migrations run at init (0024 to 0029). The last adds unique indexes, so a second seed or version row under an attested UID fails with a UNIQUE constraint error.
  • Changed: the SDK no longer depends on js-yaml or @sqlite.org/sqlite-wasm (SQLite WASM still installs through sqlocal).
  • Added: property.clear() and property.clear({ retract: true }). Both show the property empty at once and resolve when the cleared row is stored. The next publish attests the type's empty value, or with retract revokes the property's attestations. Boolean and Date have no empty value, so their clear() rejects with ClearNotSupportedError (exported from both entries) unless it retracts. See clear.
  • Added: getPublishPendingDiff entries have change ('set', 'removed' or 'retracted'), and an empty value with nothing on chain to remove isn't listed. summarizePublishWork counts removals in attestationCount and adds revocationCount. New exports getPendingRetractions (type PendingRetraction) and markPropertyAttestationsRetracted.
  • Added: isClientReady(), isClientReadySnapshot(snapshot) and whenDbReady(timeoutMs?). See isClientReady and whenDbReady.
  • Added: Schema.find({ schemaName }) returns the schema with that name, or undefined, and never creates one.
  • Added: getPublishGraph and its types (PublishGraph, PublishGraphNode, PublishGraphProperty, PublishGraphRef, PublishPropertyKind, GetPublishGraphOptions). The publish reads (getPublishPayload, getPublishUploadData, summarizePublishWork, getUnpublishedRelatedItems) are built from the stored rows, load no item, and wait for every pending write first. A property attests its newest row even when a loaded instance shows an older one, a Relation to a saved Image, File or Html uploads once, and a published storage seed's file isn't uploaded again.
  • Fixed: an edit at the same version no longer duplicates models. The SDK replaces the stored schema file and logs a warning asking you to raise version.
  • Fixed: a write to a new model or property made before its first write started is no longer dropped (Model.create waited out its timeout). A ModelProperty save without required keeps the stored value, Model.lastSaveError reports a failed rename, a second rename during the first is saved after it, and Model.find({ modelName, schemaName }) reads the database when the model isn't cached.
  • Fixed: a renamed schema is cached under its new name only, so Schema.create(oldName) no longer returns it; a schema created again after an unload is found by its file ID; Schema.clearCache() stops every cached schema; Model.destroy() stops its properties; ModelProperty.destroy() deletes its row by file ID.
  • Fixed: in the browser, a conflict that sync records over a loaded property's draft shows on property.conflict once the change feed reports the write. It used to appear only after a reload. No React hook re-renders for it.
  • Fixed: sync no longer makes a second seed for a republished item (migration 0024 merges existing ones) or a second row for a UID publish recorded meanwhile. An item synced without its model gets the model by EAS name at the model's nameMatch level, and Item.create throws AmbiguousModelError when several schemas match.
  • Fixed: a loaded item takes its publish times from its rows; the local query source labels seeds with their EAS schema name and bounds months in UTC; revoking skips placeholder UIDs.
  • Fixed: the download after each sync skips files already on disk and no longer runs a 480 px resize pass. The resize worker recognizes WebP without a MIME type, untyped HEIF as image/heic and ICO as image/x-icon.
  • Fixed: in the browser, opening the database while another tab holds it retries after 1, 2 and 4 seconds before failing with DatabaseNotPersistentError; a second tab could fail init while the first was still migrating.
  • Fixed: browser init works in Chromium builds that refuse workers started by URL from inside a worker: SQLocal's worker and SQLite's OPFS proxy start from blobs. On a cross-origin isolated page, DatabaseNotPersistentError says SQLite's OPFS VFS failed to install and names SQLite's console lines, instead of asking for the COOP and COEP headers.

@seedprotocol/react

  • Changed: useDebouncedItemProperty keeps a value that breaks a rule in the input, with error an ItemPropertySaveValidationError. A refused write (OwnershipError) or a failed one shows the stored value again, with error set. 0.7.8 rolled both back to what showed before the edit.
  • Changed: a property save refetches useItemProperties once, when the change feed reports the write. SeedProvider no longer refetches on itemProperty.saved, and invalidateItemPropertiesForItem invalidates once and resolves when the refetch is done.
  • Changed: useSchema, useItem, useItemProperty, useCreateItemProperty and the create hooks follow the entity's load status instead of its actor reaching idle. useCreateItemProperty's error for a failed load is Error('Failed to create item property').
  • Changed: useServices().percentComplete has no 80% step: 10, 30, 50, 60, 70, 90, 100.
  • Changed: isClientReadySnapshot is the SDK's; import it from @seedprotocol/sdk. The React package's entry doesn't export it.
  • Fixed: useItems returns a new, re-sorted array when a listed item is published or republished. It kept its order and identity.
  • Fixed: SeedImage stops showing an image once its property is cleared or points at another file.

@seedprotocol/publish

  • Breaking: publish uploads only signed ANS-104 data items, through POST /api/upload/arweave/batch on the upload API or to Turbo with payment: { storage: 'user' }. The L1 path is removed: no /start request, reimbursement or chunk upload. Apps that left useArweaveBundler unset took the L1 path and now need a data item signer (ConnectButton registers one, or set signDataItems).
  • Breaking: the signArweaveTransactions and arweaveJwk options, ResolvedPublishConfig.useArweaveBundler, the types SerializedPublishUpload and ArweaveTransactionInfoResult, and the L1 context fields are removed.
  • Breaking: the L1 states (creatingArweaveTransactions, sendingReimbursementRequest, pollingForConfirmation, uploadingData and their Phase2 versions) are removed. A publish saved in one of them is marked interrupted when it's restored; publish the item again.
  • Breaking: cost estimates are always bundler-priced. storage.path and an estimate's arweave.path are 'bundler', and combinePublishCostEstimate ignores arweavePath. Code comparing path with 'l1' no longer type-checks.
  • Breaking: the L1-finalize worker runs for every publish, and no longer restores data item IDs that publish 0.7.4 and earlier overwrote with L1 IDs (the SDK recreates those databases).
  • Deprecated: useArweaveBundler. true changes nothing; false makes initPublish throw PublishPaymentConfigError.
  • Changed: item.unpublish() revokes the Seed attestation first, then the property attestations (one transaction per schema), then the Versions. The Seed used to go last. A sync between the transactions sees the Seed revoked and keeps the item's last values; a failure partway leaves the Seed revoked and some properties live.
  • Changed: ensureEasSchemasForItem registers the schemas of every Seed the publish attests (the item and the drafts published with it), each once, and looks a property schema up by its exact definition before registering it. Its item parameter takes { seedLocalId, modelName }.
  • Added: a publish revokes retracted properties after attesting, one multiRevoke per schema; an EOA publisher signs a second transaction. A failure waits in retractionFailureRecoverable, where PublishManager.retryAttestations sends the revocations again, and item.publish() rejects with the attestation-failure message. checking refuses the publish when the wallet can't revoke (an automation session key, or a Seed the executor attested). See What a publish does.
  • Fixed: published Html links its images to the public gateway (https://<arweaveDomain>/raw/<tx>), not the read host. Under Gateway Hyper or a proxy, 0.7.8 and earlier wrote loopback or app-only URLs into the uploaded Html.
  • Fixed: each attestation UID is recorded on the row whose value was attested, so a value another tab changed after the payload was built stays a draft. A restored publish no longer loads the item first.

@seedprotocol/eas

  • Breaking: getEasSchemaUidBySchemaName and fetchEasSchemaUidBySchemaName are removed. Look a property schema up by its definition with getEasSchemaUidForExactDefinition, and a model's with the model-schema lookup.
  • Changed: NodeEasClient sends requests through its own undici Agent: TLS key exchange limited to X25519 and P-256 (EAS_NODE_ECDH_CURVE), idle connections kept for 30 seconds (EAS_NODE_KEEP_ALIVE_TIMEOUT_MS). Per the package's changelog, Node 24.21 and later offer a post-quantum key exchange that easscan doesn't support, and each new TLS handshake took about 1.45 seconds. No Agent is made under Bun or Deno, or when the host set its own global dispatcher. new NodeEasClient({ dispatcher: false }) uses fetch's defaults, and registerNodeEasPlatform({ easClient }) passes options. The 30-second request limit stays. eas now depends on undici, loaded only by @seedprotocol/eas/node.
  • Changed: getEasSchemaUidForSchemaDefinition looks up the exact definition after its cache; it used to take the first schema whose definition ended with the text. Registering a schema forgets only that definition's cached miss.
  • Added: createEasGraphQLClient(url, timeoutMs?, { dispatcher? }) and createEasNodeDispatcher (from /node).
  • Added: the model-schema lookup: fetchEasModelSchemas, matchEasModelSchemas, getEasModelSchemasForNames and modelSchemaDefinition.
  • Added: a per-request timing log under DEBUG=seedEas:timing.
  • Added: VERSION_SCHEMA_UID and VERSION_SCHEMA_DEF (moved from the SDK, which re-exports them) and isEasEmptyValue(easType, value).
  • Fixed: getItemVersionsFromEas returns only Version attestations. A newer tool attestation from the Seed's own attester was taken as the latest Version, and @seedprotocol/query returned the record with no properties.

@seedprotocol/query

  • Breaking: expandRelations is replaced by relations. true was 'expand' and false was 'urls'. An untyped expandRelations: false is ignored, so relations come back expanded.
  • Breaking: the deprecated Feed aliases (FEED_RICH_BODY_STORAGE_SCHEMAS, enrichImageSeedCloneForFeed, feedRichBodyModelPriority, getFeedFieldStorageModels, getFeedListElementStorageModels, hydrateArweaveRichTextInFeedItems, isFeedRichBodyStorageSchema, parseEasPropertyMetadataForFeed, setFeedFieldStorageModel, setFeedListElementStorageModels and their types) are removed; the names without Feed stay. QueryCacheConfig.backgroundRefresh and refreshInterval are removed, and CACHE_BACKGROUND_REFRESH and CACHE_REFRESH_INTERVAL aren't read.
  • Changed: a warm queryBySchema is one round of requests: the change check goes out with the Seed list and covers every cached Seed.
  • Changed: a property whose attestation is the empty value is left out of the record, and a zero bytes32 isn't read as a related Seed. Changelog entries show a removal as a change to undefined.
  • Added: relations: 'expand' | 'urls' | 'uids' (default 'expand') on queryBySchema, getSeed, getSeeds and queryBySchemaForMonth. 'uids' leaves each relation a Seed UID and reads no related Seeds. Type RelationsMode. See queryBySchema options.
  • Added: queryBySchema(name, { maxAge }) and CACHE_MAX_AGE (default 0, off): a collection checked less than maxAge seconds ago in this process is returned without a request, so changes show up to maxAge seconds late.
  • Fixed: on Node, initializeQueryPlatform registers NodeEasClient when no EAS client is registered.
  • Fixed: queryBySchemaForMonth bounds the month in UTC. It used the process's time zone.
  • Fixed: records no longer come back empty when a tool attestation from the Seed's attester references the Seed (through eas, above).

@seedprotocol/arweave

  • Added: DefaultArweaveClient, a fetch-based client for Node and browsers, which NodeArweaveClient extends. getTransactionData first tries /raw/<id> on the preferred read gateways, unless decode: false, then the configured host's raw URL, then arweave-js's chunk API, which loads only when it's needed.
  • Added: getPublicArweaveBaseUrl() and getPublicArweaveRawUrl(txId), the public gateway for links written into published content, from the new required publicArweaveBaseUrl field of the resolved gateway endpoints. Code that builds that type itself has to set it.

@seedprotocol/feed

  • Breaking: FEED_SITE_URL is removed (it had no effect): loadFeedConfig() has no siteUrl, and TransformOptions requires itemUrlBase and itemUrlPath. The site config's siteUrl is unchanged.
  • Breaking: FEED_EXPAND_RELATIONS is removed; feeds always expand relations. false left Image and author relations as UIDs, so items lost image enclosures and author names.
  • Breaking: initializeFeedPlatform keeps EAS and Arweave clients a host app or the SDK registered, and registers the Node ones otherwise; it used to replace them. initializeSeedClient, getClient and teardownSeedClient are removed; teardownFeedPlatform is added.
  • Breaking: GraphQLItem is now FeedItem, with the same shape. enrichImageSeedCloneForFeed, FEED_RICH_BODY_STORAGE_SCHEMAS and isFeedRichBodyStorageSchema are removed; use query's enrichImageSeedClone, RICH_BODY_STORAGE_SCHEMAS and isRichBodyStorageSchema.
  • Changed: page 1 asks query on every request and is rebuilt only when query's collection changed, so new posts appear on the next request instead of up to CACHE_TTL later. Its Cache-Control is public, max-age=<CACHE_MAX_AGE>, s-maxage=<CACHE_MAX_AGE>, must-revalidate (default 0, was 3600); later pages stay at 3600 and archives at 86400, all with must-revalidate. Set CACHE_MAX_AGE to let a CDN keep page 1.
  • Changed: ETags stay the same across rebuilds of an unchanged feed, and responses carry ETag and Last-Modified with the cache off too, though only a cached response answers 304.
  • Changed: per-request logs print only under DEBUG=seedFeed, and Cache is DISABLED prints once. The package depends on debug and no longer on express or graphql-request.
  • Added: fetchFeedItemsBySchemaName(schemaName, { limit?, skip? }) returns { items, etag? }.
  • Fixed: a cached body is served only to requests with the site config overrides it was built with; another site used to get the first site's body.
  • Fixed: a failed build falls back to an unexpired cached body with X-Cache: STALE and Warning: 110 - "Response is stale", archives included. Without one, both handlers answer 500 with Failed to generate feed.
  • Fixed: a body read from the disk cache keeps its ETag and expiry.
  • Fixed: image metadata probes the image the enclosure uses, not the item's own storage transaction; a probe no gateway answered isn't cached; archives get image metadata too.
  • Fixed: archive months and the links between them are in UTC.
  • Fixed: a page whose last item fills it exactly no longer links an empty next page, and a FEED_PAGE_SIZE that isn't a positive integer gives 25.

0.7.8

Released 2026-10-20. Upgrade steps: Upgrading from 0.7.7 to 0.7.9.

@seedprotocol/sdk

  • Changed: item edits go through an outbox. An assignment queues a write, and the leader tab applies it in one transaction; await property.save() resolves when it has. Only the newest of a property's pending values is written, and writes left pending by a reload or a closed tab are applied when a tab next leads. Item.create, item.destroy() and property.destroy() go through it too, and publish waits for every pending write first. Raw SQL or a live query on metadata sees a value only once its write is applied, so await save() before reading metadata yourself. A pending value shows after a reload, before it's applied. The local query data source (source: 'local' and 'auto' reads in @seedprotocol/query) reads published rows only. A save that a newer save in the same tab replaced queued nothing (changed in 0.7.9: each assignment queues a write before the setter returns).
  • Changed: a write is checked again when it's applied. A write on an item the client no longer owns fails with OwnershipError, and a Relation value naming a local item that doesn't exist fails with Relation target not found: <ids> (RelationTargetError, not exported). The assignment also checked ownership (changed in 0.7.9: only the applied write is checked).
  • Changed: a write that fails when applied makes save() reject with OutboxOpFailedError (code: 'OUTBOX_OP_FAILED', the original class in errorName, not exported), except an ownership refusal, which rejects with OwnershipError. A write held up by a busy database is retried first. A rule-breaking value was still rolled back (changed in 0.7.9: it stays with its errors, and failed Image, File and Html writes reject this way too).
  • Changed: EAS sync keeps an unpublished local edit over a newer synced value. When the synced value differs from both the edit and the value the edit replaced, the SDK records a conflict. A synced value attested after a local edit used to replace it.
  • Changed: an item holds its properties. Its last unload() or destroy() releases them, so a property nobody else holds stops with it. Each ItemProperty.find() or ItemProperty.create() is one hold, released by one property.unload(). Code that called property.unload() in a loop should call item.unload() once and property.unload() once per find. A property read through item.properties after its item is unloaded is stopped.
  • Changed: the SDK revokes the blob: URLs it makes for Image and File values when the value moves to another file and when the property stops. A URL from property.value works while it's the current value and the property is loaded. URLs the app passes in are left alone.
  • Changed: in the browser, loaded items and properties watch the change feed instead of holding live queries, and re-read only when their own Seed's rows are written. Other tabs' writes still reach them. The SDK's measurement: 2 statements per create instead of 400, and 3 per save instead of 320, with 40 items of 8 properties loaded. Node has no change feed.
  • Changed: addresses.persisted fires only when the owned or watched addresses differ, ignoring case and order, from the ones this tab last announced. Every setAddresses() and every init that saved addresses used to fire it, and useItems reloaded each time.
  • Changed: in the browser, identical SQL-tag queries passed to BaseDb.liveQuery share one SQLocal subscription, and BaseDb.liveQuery logs SQLite errors through debug (seedSdk:browser:db:Db) instead of console.error. Subscribers still get the error.
  • Changed: saves read EAS schema UIDs from the cache item creation uses (found UIDs kept, misses for 5 minutes) instead of asking the indexer each time, and createNewItem looks up a model's property schemas in parallel. Indexer requests abort after 30 seconds (see eas below).
  • Changed: seeds, versions and metadata rows always get created_at on insert and updated_at on every write.
  • Changed: sqlocal 0.18 (SQLite WASM 3.51). Four migrations run at init: the outbox, read indexes and the change feed's table and triggers.
  • Added: property.conflict, property.resolveConflict('local' | 'remote'), item.conflicts(), the itemProperty.conflict event and the PropertyConflict type. See Conflicts.
  • Added: property.showValue(value) shows a value without saving it, and the next setValue or assignment rolled back to what showed before it when refused (changed in 0.7.9: there's no rollback).
  • Added: BaseDb.transaction(fn): the Drizzle queries fn runs on its tx commit together or not at all. A busy transaction is retried as a whole. On Node the SDK keeps one connection and uses WAL for a file database.
  • Added: watchEntityChanges, watchEntityRows, isChangeFeedRunning and the types ChangeWatch and EntityChangeTable, browser only. watchEntityRows took a since option (removed in 0.7.9).
  • Added: Item.all takes a signal; once it aborts, Item.all stops building items and rejects with the signal's reason.
  • Added: client.subscribeService(listener) is called with the client manager actor when client.init() after client.stop() replaces it and when stop() ends it.
  • Added: DEBUG=seedSdk:timing logs per-phase times for creates, finds, loads and saves, and when a ready wait timed out.
  • Added: the leader tab deletes applied outbox entries after a day and failed ones after a week.
  • Fixed: Item.all honors waitForReady: by default it returns items without waiting for them to load, and with true waits for all of them together. It waited for each item in turn either way.
  • Fixed: an item's live queries close when it stops, on destroy() and on Item.dropCachedInstancesForSeedIds, and a schema's last unload releases the models it loaded.
  • Fixed: an Image or File property with no display URL no longer makes a new blob: URL about 60 times a second, and a metadata write no longer shows the storage seed ID as the value for a moment.
  • Fixed: on Node, a write retried after SQLITE_BUSY is no longer lost: the SDK replaces the connection before retrying.
  • Fixed: client.claimUnpublishedDrafts runs in one transaction and also claims drafts whose create is still queued; its result adds pendingCreates.
  • Fixed: assigning item.properties throws Cannot set item.properties directly…, a seed UID assigned to a new draft is stored, and an item keeps revokedAt, versionsCount and createdAt sent before it finished loading.

@seedprotocol/react

  • Breaking: useCreateModelProperty's create returns Promise<ModelProperty>, and a missing modelName, name or dataType rejects instead of throwing. schemaId is looked up as a stored name or file ID first; a -<n> suffix is dropped only when nothing matches.
  • Changed: useModels, useModel, useSchemas, useModelProperties, useFiles and useImageFiles work without SeedProvider, on a shared fallback query client with Seed's defaults. In development builds, any hook that uses the fallback logs one warning that these hooks will throw without SeedProvider from 0.8. useItems and useItemProperties still throw without it.
  • Changed: useItemProperties re-reads when the change feed reports a write to its item's rows, instead of on every metadata write in every tab (changed in 0.7.9: once per save). On Node it polls.
  • Changed: useItemProperty and useItemProperties hold the properties they return while mounted and release them on unmount.
  • Changed: useCreateItem's createItem(modelName, itemData, options) takes ItemCreateOptions. A call made while another is running returns that call's promise and item instead of undefined.
  • Changed: react-dom 18 or 19 is a peer dependency.
  • Added: useClientService() and useClientSelector(selector). See useClientService and useClientSelector.
  • Fixed: useIsClientReady, useDbsAreReady, useIsDbReady, useServices and SeedClientGate follow the client after client.stop() and a new client.init(), so the gate shows its loading state again without a key remount.
  • Fixed: a text input bound to property.value from useItemProperty keeps every keystroke.
  • Fixed: useDebouncedItemProperty works on a property without a loaded definition. A refused save or a broken rule rolled the input back to what showed before the edit, and error reported the failure (changed in 0.7.9: a rule-breaking value stays).
  • Fixed: useLiveQuery and useLiveQueryState resubscribe after an error, after 0.5, 1, 2, 5 and 10 seconds and then every 30, keeping the last rows; an error while setting the query up lands in error.
  • Fixed: useItems lists an item created during its first load, and a superseded load stops building items.
  • Fixed: SeedClientGate compares initConfig and schema by value, so an inline object doesn't start init on every render.
  • Fixed: useService(...).percentComplete is 100 once the service is idle and 0 to 100 while the client starts, and useService re-renders on state changes (changed in 0.7.9: no 80% step).
  • Fixed: useCreateSchema, useCreateModel and useCreateModelProperty clear isLoading when the SDK returns an already-loaded instance and when the create throws; a throw also sets error. The isLoading check followed the actor's idle state (changed in 0.7.9: the load status).
  • Fixed: useSchema releases its hold on unmount and when schemaIdentifier changes. useModelProperty accepts null and undefined arguments and waits instead of looking up the wrong form.
  • Fixed: useEasSyncProcesses, useEasSyncProcessesState and useEasSyncProcessById return undefined until the client is ready instead of throwing Db not configured.
  • Fixed: useResolvedMediaRef reports status: 'loading' and no href after value changes, until the new value resolves. SeedImage forwards ref to its <img> on React 18.

@seedprotocol/eas

  • Changed: EAS indexer requests abort after 30 seconds (EAS_REQUEST_TIMEOUT_MS) through the default clients; they used to wait indefinitely. So with a stalled indexer, @seedprotocol/query reads reject (see TimeoutError), the feed handlers answer 500 (changed in 0.7.9: an unexpired cached body can answer instead), and publish's attestation check gives up after about 3.4 minutes.
  • Added: createEasGraphQLClient(url, timeoutMs?), a GraphQLClient with that limit, for apps that implement IEasClient themselves.
  • Fixed: registering a schema forgets only the cached lookup misses it answers, not all of them (changed in 0.7.9: only that definition's miss).

0.7.7

Released 2026-10-18. Upgrade steps: Upgrading from 0.7.5 to 0.7.7.

@seedprotocol/sdk

  • Breaking: after client.stop() or client.unload(), client methods such as getAddresses, setAddresses and syncFromEas throw ClientManager is not initialized. Please call init() first. until client.init() runs again. They used to keep working on the open database. A second stop() or unload() logs a console.warn and returns instead of stopping again.
  • Changed: ItemProperty.save() and item.publish() reject with the error that was thrown, so instanceof, code and cause work (an OwnershipError, or @seedprotocol/publish's ManagedAccountPublishError). They used to reject with a new plain Error carrying the message. save()'s message loses its Failed to save property <name>: prefix (No file data found, not Failed to save property cover: No file data found), so code that matches the old text has to change.
  • Changed: a value that breaks its property's rules rolls back. property.value, Item.find and Item.all return the value from before the assignment, as they already did after an ownership refusal. The rejected value used to stay in memory for the rest of the session.
  • Changed: after a publish, Relation and List values stored on this device hold the attested Seed UIDs (a UID, or a JSON array of UIDs), as an EAS sync of the same attestation stores them. They used to keep the related drafts' local IDs. A List of Relation also records its attestation UID, so a patch publish no longer attests it again every time.
  • Added: property.setValue(value, { before }) shows and saves a value like assigning property.value, but saves even when the value is already in context. before is what a refused or rejected save restores.
  • Added: ItemCreateError, thrown by Item.create when a File, Image, Html or ItemStorage value fails to save for a reason other than its rules (an ownership refusal, for example) or its property doesn't load. It has code ('save_failed' or 'property_not_loaded'), modelName, property, the removed draft's seedLocalId, and the save's error as cause. It used to be a plain Error with the same message. Exported from the main entry, not from @seedprotocol/sdk/node.
  • Fixed: a File value works after a reload. Each load makes a fresh blob: (browser) or file:// (Node) URL from the file instead of reading a stored one, and a one-time step after init clears the URLs that 0.7.5 and 0.7.6 stored.
  • Fixed: a non-list Relation whose schema sets a refValueType other than Image saves its value. The save resolved without writing.
  • Fixed: item.destroy() marks only its own Seed row. It matched by local ID or UID, so other local rows with the same UID were marked deleted too.
  • Fixed: Item.all(modelName, true) lists only deleted items. It also listed items that weren't deleted.
  • Fixed: an item claimed by client.claimUnpublishedDrafts stops when its owner calls unload(). The claim no longer keeps a hold on it.
  • Fixed: a complete-format schema file ($schema set) with no metadata.name fails init with a Schema name is required error instead of a raw SQL error.
  • Fixed: nameMatch levels are loaded before client.init resolves. For a few seconds after init, lookups by EAS type treated every model as 'fuzzy'.
  • Fixed: on Node, BaseDb.isAppDbReady() is false until the database opens. It returned true before init.

@seedprotocol/publish

  • Fixed: a direct-to-EAS publish refuses a batch in which a property update targets a request attested earlier in the batch. It throws ManagedAccountPublishError with code PUBLISH_PREFLIGHT_FAILED (the message names PublishTargetAlreadyAttested(i, t)) before anything is sent. The update used to be dropped without an error. The contract path already refused it, and batches the SDK builds don't contain such updates.
  • Fixed: both publish paths rewrite local Relation and List values to the Seed UIDs they attested (see the SDK above).

@seedprotocol/react

  • Fixed: useItems({ deleted: true }) matches only deleted items.
  • Fixed: usePublishItem's error is the publish's own error class (see the SDK above).

@seedprotocol/next (new)

  • Added: withSeed(nextConfig, options?), the Next.js config helper. It points the SDK and @seedprotocol/query at their browser builds in every graph, server rendering included, and adds cross-origin isolation headers through headers(). fsModules (default []) aliases fs modules to ZenFS in browser builds. It supports Next.js 16, and Next.js 15.5 with webpack; under Next.js 15 with Turbopack it logs a warning, because SQLite WASM can't load there. See @seedprotocol/next.

0.7.6

Released 2026-10-18. Upgrade steps: Upgrading from 0.7.5 to 0.7.7.

@seedprotocol/sdk

  • Breaking: browser client.init fails with DatabaseNotPersistentError (code 'DB_NOT_PERSISTENT') when SQLite can't keep its database in OPFS: when SharedArrayBuffer or Atomics is missing because the page isn't cross-origin isolated, or when SQLite reports another storage type. The message names the COOP and COEP headers. Before, the page ran on an in-memory database and lost everything on reload. @seedprotocol/vite sets the headers on the dev and preview servers; production hosting has to send them too. onInitError on SeedClientGate and SeedProvider receives the error.
  • Breaking: @seedprotocol/vite is an optional peer of the SDK instead of a dependency, so installing the SDK no longer installs Vite, Rolldown and the polyfills. Importing @seedprotocol/sdk/vite needs @seedprotocol/vite installed; without it the import fails with ERR_MODULE_NOT_FOUND.
  • Changed: the SDK has no required peers. xstate, drizzle-orm, @zenfs/core 2.5.6 and @zenfs/dom 1.2.9 are dependencies. The SDK no longer depends on @tanstack/react-query (its query cache uses @tanstack/query-core); React apps get React Query as a peer of @seedprotocol/react. fsevents is gone.
  • Changed: browser bundles no longer contain the Node platform. It loads through a #seed-node-platform import that maps to the browser platform under the browser condition, and the browser entry no longer has a bare import 'fs', which webpack and Turbopack failed to resolve.
  • Changed: models a schema drops are unlinked from it when the schema loads, with or without a new version. From the next start, Model.all(schemaName) no longer lists them; their rows and items stay. Schemas with draft edits are skipped.
  • Deprecated: withSeed from @seedprotocol/sdk/node. Use withSeed from @seedprotocol/next.
  • Added: @seedprotocol/sdk/drizzle re-exports drizzle-orm (eq, and, like, sql and the rest) from the SDK's own copy, so apps that query the SDK's tables don't install drizzle-orm or keep it on 0.44.
  • Added: client.init({ config: { schema } }) is enough. filesDir defaults to endpoints.files, then '.seed', and both endpoints fields default to filesDir, in the browser too. The Config must include endpoints with filePaths and files error is gone. On Node the default puts everything in <cwd>/.seed.
  • Added: SeedConfig.schema takes a SchemaFileInput (new type export): only metadata.name and models are required, and version defaults to 1. A schema object without $schema used to fail with Schema name is required.
  • Fixed: a changed schema whose complete-format file sets an id loads at init even when its version is unchanged. The stored copy is replaced and a warning says to raise version; it used to load the stored copy and drop the changes without an error. importJsonSchema throws Schema … already exists with different content in that case. Without an id, an edit at the same version still left duplicate models (fixed in 0.7.9).
  • Fixed: properties publish under the EAS schema for their exact definition (<type> <name>), not any schema whose name matches. On OP Sepolia a legacy bytes32 storage_transaction_id schema exists, and File, Image and Html IDs could be attested under it. A bytes32-shaped value is no longer read as an Arweave ID.
  • Fixed: item.publish() works on the direct-EAS (EOA) path. It failed every time with Cannot set property seedUid of #<Item> which has only a getter. publishItem(item) wasn't affected.
  • Fixed: a property edited after its Seed is published attests on the next patch publish. The edit went into a new draft row that publish skipped, and each further edit added another row. Publish also reconciles properties that still point at a published row, such as after another tab's edit.
  • Fixed: loading a schema keeps a model's or property's nameMatch, validation and description from the file, fresh browser inits no longer log SQLITE_CONSTRAINT_UNIQUE for model and property rows, and a model no longer takes a same-named model's row from another schema.
  • Fixed: in a browser without Node polyfills, init errors report themselves instead of process is not defined.

@seedprotocol/vite

  • Breaking: includeNodePolyfills defaults to false. It also controls the global and process defines, the index.html shims and the stream stub, which used to apply with polyfills off too.
  • Breaking: fsModules defaults to [], so no fs modules are aliased to ZenFS unless you list them.
  • Breaking: autoInit is removed from the options type. At run time it's ignored without a warning.
  • Breaking: the plugin no longer sets optimizeDeps.include or exclude, the interop aliases (debug, arweave, eventemitter3, nanoid-dictionary, the ZenFS entry files), build.commonjsOptions or a better-sqlite3 external, and no longer rewrites the SDK's path-browserify import.
  • Breaking: the Node-globals warning on production chunks runs only with debug: true.
  • Added: optimizeDeps.rolldownOptions.output.strictExecutionOrder defaults to true unless you set it. Without it, vite dev failed with Cannot read properties of undefined (reading 'Symbol') when an app imported both @seedprotocol/sdk and @seedprotocol/sdk/drizzle.
  • Changed: resolve.dedupe lists only the packages the app root resolves, and server.fs.allow finds the SDK, sqlocal and SQLite WASM in pnpm and bun stores.
  • Changed: no longer depends on debug.
  • Fixed: vite dev works under pnpm's default isolated layout. The old optimizeDeps.exclude list broke it.

See @seedprotocol/vite for what the plugin sets now.

@seedprotocol/react

  • Added: SeedProvider takes config, addresses and onInitError and starts the client itself, so <SeedProvider config={config}> works without SeedClientGate. Children render at once and the hooks report loading until the client is ready. Init runs again when config changes identity or addresses holds different values. SeedClientGate still works for a loading screen.
  • Added: the bundle is marked 'use client', so a React Server Components app (the Next.js App Router) can import SeedProvider and the components into a server component such as app/layout.tsx. That used to fail with createContext is not a function.
  • Changed: SeedClientGate's schema prop takes string | SchemaFileInput.

@seedprotocol/publish

  • Fixed: bundler and Turbo uploads no longer use the Buffer global, so a browser publish doesn't need a Buffer polyfill.
  • Fixed: after a direct-to-EAS publish, attestation verification waits for the EAS indexer. A partial or empty result is retried up to 6 times, 5 seconds apart, before AttestationVerificationError (METADATA_PROPERTIES_MISSING); it used to fail on the first incomplete result.
  • Fixed: File, Image and Html storage IDs are attested under string storage_transaction_id, never the legacy bytes32 schema (through the SDK, above).

@seedprotocol/query

  • Added: getSeeds(seedUids, options?) reads many Seeds, of any schemas, in one set of indexer requests and returns a Map keyed by each UID as given. It takes getSeed's options except the changelog, plus pins, and shares the item cache with getSeed. Also the GetSeedsOptions and VersionPin types.
  • Added: pinned reads. getSeed(seedUid, { versionUid, asOf }) reads a Seed at one of its Versions, with relations expanded and storage hydrated as for the latest, and returns null when the Seed has no such Version. asOf (Unix seconds) leaves out attestations made after it. A patch publish adds properties to the current Version, so versionUid alone also shows later patches.
  • Fixed: queryBySchema, queryBySchemaForMonth and getSeed return only the Seeds asked for. A related Seed with the same schema (a post linking to another post) used to come back as an extra record, past limit, and went into the item cache. Feeds from @seedprotocol/feed lose those extra items too.

@seedprotocol/arweave

  • Changed: getTransactionStatus tries the read gateways in order (getArweaveReadBaseUrls()), 10 seconds each and 20 seconds in all, skipping gateways whose circuit breaker is open. A 2xx to 4xx answer is returned, and a 404 isn't retried elsewhere; a timeout, network error, 5xx or 429 moves on to the next gateway. If none answers, it returns 504 when the last attempt timed out, otherwise the last failure's status. It used to make one request to the configured host with no timeout. The SDK's browser client uses it too.
  • Added: fetchTransactionStatusAcrossGateways, DEFAULT_TRANSACTION_STATUS_TIMEOUT_MS, DEFAULT_TRANSACTION_STATUS_TOTAL_TIMEOUT_MS and the TransactionStatusFetchOptions type.
  • Changed: DuplicateArweaveModuleError's Vite advice: keep @seedprotocol/* packages out of optimizeDeps.exclude, and dedupe them when they're linked into the app.

@seedprotocol/eas

  • Added: getSchemaUidForSchemaDefinitionFromCache(text) returns the UID cached for a schema definition, without a request.
  • Changed: DuplicateEasModuleError's Vite advice, as for arweave.

@seedprotocol/gateway-hyper

  • Changed: the operator no longer forwards hop-by-hop request headers or content-length, and moves the client's Host to X-Forwarded-Host, so the upstream sees its own host. Check any upstream rule that matched on the forwarded Host.
  • Changed: a tunnel session sends requests over up to maxStreams HyperDHT streams to the operator (default 4), one request per stream at a time, so a slow request holds up only its own stream. Operators see up to that many connections and concurrent upstream requests from each client. The wire protocol is unchanged.
  • Added: timeouts. The client answers 504 once a request's stream has sent and received nothing for requestTimeoutMs (default 60,000), and 502 when it can't reach the operator within connectTimeoutMs (default 30,000). The operator answers 504 when its upstream has been idle for upstreamTimeoutMs (default 30,000), and 502 for other upstream failures, keeping the stream open. 0 turns a timeout off. A request the upstream never answered used to block the session until restart.
  • Added: forwardHttp and runOnTunnel take { signal, timeoutMs }. handleFetch passes request.signal, and handleNode and the sidecar abort when the caller disconnects.
  • Added: the CLI flags seed-gateway serve --upstream-timeout-ms and seed-gateway connect --request-timeout-ms and --max-streams; the new options on createGatewayProxy, connectTunnel and serveTunnel; and the exports DEFAULT_CONNECT_TIMEOUT_MS, DEFAULT_MAX_STREAMS, DEFAULT_REQUEST_TIMEOUT_MS, DEFAULT_UPSTREAM_TIMEOUT_MS, TunnelConnectTimeoutError, TunnelTimeoutError, UpstreamTimeoutError and the TunnelRequestOptions, TunnelSession and TunnelSessionOptions types.
  • Fixed: bodies between 16 and 64 MiB pass through the tunnel, written in 1 MiB chunks. Bodies over 64 MiB are refused in both directions.

0.7.5

Released 2026-10-17. Upgrade steps: Upgrading from 0.7.4 to 0.7.5.

@seedprotocol/sdk

  • Breaking: Number properties publish as string <name> instead of uint8 <name>, so values above 255, negatives and decimals publish; they used to fail with Failed to encode property. The value is attested as its decimal string, and a stored value that isn't a finite number fails publish validation with publish_invalid_number. The first publish after upgrading registers the new schema, so published Number values move to a new EAS schema UID. Items published before keep their uint8 attestations until the property changes.
  • Breaking: EAS sync skips, with a warning, a property attestation whose EAS type doesn't fit the local property. A Number accepts integer types and numeric strings; other types accept only the type publish writes for them. Any attestation with a matching name used to be stored.
  • Breaking: a File property's value is its content URL (blob: in browsers, file:// in Node) after a save and after a reload. It used to turn into the file's text a moment after a save or load. Read the contents through the URL (fetch in browsers, fs in Node). Html and ItemStorage values still read as their text.
  • Breaking: a draft that has never had a publisher is owned whatever the owned addresses are: it's editable, addressFilter: 'owned' lists it, and the next wallet to connect becomes its publisher. A draft with a publisher is owned only while that address is. A draft with no publisher used to be read-only, and left out of owned lists, whenever any owned address was set.
  • Changed: a new draft gets the owned address as its publisher when exactly one is set and no publisher getter supplies one. With several, it has none until the next wallet connects; register setGetPublisherForNewSeeds to give it one at creation.
  • Changed: EAS sync stores numeric values decoded: a Number as a decimal string, a Date attested as an integer as an ISO string. It used to store {"type":"BigNumber","hex":…} text. Rows synced before stay as they are.
  • Changed: item.unload() and modelProperty.unload() release one holder. A shared instance stops when its last holder unloads it, and a model's own properties run until the model is evicted. The first unload() used to stop it for everyone.
  • Added: nameMatch ('exact', 'normalized' or 'fuzzy') on a property, a model or a schema's top level sets how loosely names from EAS match it. The setting nearest the property wins, and the default is 'fuzzy'. Also the SchemaNameMatch type.
  • Added: summarizePublishWork returns uploadSizes, the size of each upload. restoreDataItemIdsOverwrittenByL1() is exported.
  • Deprecated: applyArweaveL1TransactionIdLocal.
  • Fixed: Item.create leaves nothing behind when it rejects: the draft, the storage items it points at and their files are removed. A File, Image, Html or ItemStorage value that breaks its property's rules rejects with ItemCreateValidationError like the other values; Html rule failures used to be a plain Error.
  • Fixed: Node processes exit on their own after client.stop(), so scripts don't need process.exit().
  • Fixed: client.init after client.stop() initializes again with the new config, and client.isInitialized() is false after stop(). It used to resolve without doing anything.
  • Fixed: client.init no longer creates <cwd>/.seed, so an absolute filesDir works from a read-only working directory such as a packaged Electron app's /. A relative filesDir still resolves under <cwd>/.seed.
  • Fixed: a dbConfig.dbUrl in a folder that doesn't exist yet works; the folder is created.
  • Fixed: publishing Html with an embedded data:image/… image works again. Since 0.7.2 it failed with ItemPropertySaveValidationError before anything was uploaded.
  • Fixed: version and property rows take their Seed's publisher, and claimUnpublishedDrafts claims them only together with their Seed, so connecting another wallet no longer claims rows of someone else's Seed. A one-time step after init fixes existing rows.
  • Fixed: 0 and false are attested; only a missing or empty value is skipped.
  • Fixed: assigning a property the value it already has no longer adds a draft row next to a published value.
  • Fixed: getEasSchemaUidForModel looks up the model's EAS name (blog_post), not its lowercased name (blogpost).

@seedprotocol/publish

  • Breaking: Number properties register string <name> EAS schemas (see the SDK above).
  • Breaking: without useDirectEas, a publisher with no contract code attests straight to EAS only when its PublishWallet has kind: 'eoa' and signs as the publisher. fromEip1193Provider and fromEthersWallet set kind: 'eoa', fromThirdwebAccount(account, { kind }) takes it, and ConnectButton, ensureSmartWalletThenPublish and enrollPublishAutomation set it. A wallet with no kind takes the registered session's kind when the signer matches, and is refused otherwise. Add kind to a hand-built { signer, txSender } wallet, or set useDirectEas. New type PublishWalletKind.
  • Breaking: initPublish throws when uploadApiBaseUrl is missing, unless payment.storage is 'user', and when it isn't an absolute http(s) URL or an origin path such as /seed-api. A missing value used to fail only at upload time.
  • Breaking: PublishManager.query(seedLocalId) only reads: it returns the publish's current snapshot, or undefined. It used to stop the publish.
  • Breaking: ensureWalletThenPublish and ensureSmartWalletThenPublish resolve once the publish has started, with { outcome: 'started', publish } (the publish actor) or the new { outcome: 'already_publishing' }. started is deprecated and already resolved.
  • Changed: @seedprotocol/sdk and @seedprotocol/react are peer dependencies at the exact release version, no longer dependencies. npm installs one top-level SDK even when the app lists only @seedprotocol/publish; publish used to bring its own.
  • Changed: data items are signed by an EOA. PublishWalletSession.dataItemSigner holds that signer, and ConnectButton sets it to a smart wallet's admin EOA (with useModularExecutor, the connected EOA). item.publish() and ensureWalletThenPublish default to it, or to the wallet's own signer when the wallet isn't kind: 'smart'. PublishManager.createPublish still has no default. Signing checks that the signature recovers to the signer's address.
  • Changed: ConnectButton stops running publishes only when another wallet takes over, not on every wallet sync.
  • Changed: useCanPublishItem is false until its check finishes, and checks again when owned addresses change.
  • Changed: pollForConfirmation gives up after 30 minutes, or after 12 failed status requests in a row, so item.publish() on the L1 path settles.
  • Changed: cost estimates charge an L1 upload for at least 256 KiB, a bundle once, and nothing for Turbo uploads up to 100 KiB (arweave.chargedBytes; arweave.bytes stays the raw size). Testnet gas is priced at $0, with evm.testnet: true.
  • Deprecated: useLocalWalletAccount (always null), and the gatewayTransport and modularAccountModuleData config fields, which nothing reads.
  • Fixed: a new user's smart wallet is deployed on its first publish. PublishWalletSession.deploy deploys it, and ConnectButton sets it for a smart wallet without useModularExecutor. In 0.7.4 that first publish was refused with MANAGED_ACCOUNT_NOT_DEPLOYED, which a wallet without deploy still gets.
  • Fixed: a bundler or Turbo publish restored after a reload no longer skips its upload. Its signed data items aren't saved, so restore marks it interrupted, to publish again, and an upload with nothing to send fails with code SIGNED_DATA_ITEMS_MISSING.
  • Fixed: local storage IDs stay the attested data item IDs. The L1-finalize worker records the L1 ID in its own table instead of rewriting them, and once per session restores IDs it rewrote before.
  • Fixed: Html with embedded images gets its second upload pass on the direct-EAS route, and on the L1 path that pass is reimbursed, so the user's Arweave key no longer needs AR.
  • Fixed: EIP-7702 wallets keep attesting directly after their first publish. A delegated EOA's code no longer counts as a deployed contract.
  • Fixed: PublishProvider calls initPublish during render, once per config object, so ConnectButton no longer throws on first render under a config-only provider.

@seedprotocol/react

  • Changed: the @seedprotocol/sdk peer is the exact release version instead of ^0.6.0. npm refuses a mismatched React and SDK pair with ERESOLVE instead of letting it fail at import, and no overrides block is needed. The source is unchanged.

@seedprotocol/eas

  • Added: decodeEasNumber(value) turns an indexer value (a number, a decimal or hex string, a bigint, or { type: 'BigNumber', hex }) into a decimal string, and isNumericEasType(easType) says whether a type is uint* or int*.
  • Added: NAME_MATCH_LEVELS, isNameMatchLevel and easNamesToFetch(name, level). localNamesInBestMatchTier's level can be a function of the local name.
  • Changed: the exact name-match tier means the EAS name is exactly easName(local), so Title and html-page are now normalized matches. At the default fuzzy level only the tier ranks change.
  • Changed: getCanonicalItemPropertiesFromEas checks attesters by default: a Version counts only if its Seed's attester made it, and a property only if its Version's attester did. It makes two more requests; pass checkAttesters: false to skip them.

@seedprotocol/query

  • Fixed: values from uint* and int* property attestations come back as decimal strings, not the indexer's {"type":"BigNumber","hex":…} JSON. A Date published as an integer reads as Unix seconds.
  • Fixed: main points at the Node build, with a top-level types and a browser field, for resolvers that ignore exports (TypeScript's node10 resolution, older bundlers).

All packages

  • Internal @seedprotocol/* dependencies and peers are the exact release version, and every package has a top-level types field. vite, arweave, feed, feed-hyper, gateway-hyper and mapping changed only their manifest and README.

0.7.4

Released 2026-10-16. Upgrade steps: Upgrading from 0.7.3 to 0.7.4.

React and the SDK had to be upgraded together. React 0.7.4 imports easName, which first shipped in SDK 0.7.4, so React 0.7.4 with SDK 0.7.3 failed at import (does not provide an export named 'easName'). Its peer range still said ^0.6.0, which no 0.7 SDK matches, so npm needed an overrides block. Since 0.7.5 React's peer is the exact SDK version and no overrides block is needed.

@seedprotocol/sdk

  • Breaking: Item.create checks its values before writing anything. An unknown modelName (in schemaName, if given) throws UnknownModelError. Unknown keys, and values that fail their property's type or validation rules, throw ItemCreateValidationError, whose problems lists each one as { property, message, code }. Pass { validate: false } as the second argument to skip the value checks when importing data that's already stored or attested. Model#create(values) and React's useCreateItem always validate. required isn't checked, and File, Image, Html and ItemStorage values are still checked by their own save.
  • Breaking: validation rules that were skipped are enforced. pattern applies to Text; on Html it's kept alongside a length rule; enum combines with the other rules instead of replacing them (and no longer gets Text's default 255-character limit); rules on properties defined in code (Model.create(…, { properties })) are enforced. Values that used to save may now be rejected, on create and on assignment.
  • Breaking: EAS names come from one function, easName (ADR 0008). Model and property names with acronyms, a digit before a capital, spaces or hyphens publish to new EAS schemas (HTMLPage was bytes32 htmlpage and is now bytes32 html_page; ID3Tag becomes id3_tag). A single Relation, Image, File or Html property always publishes under its schema key (bytes32 author), never author_id. Names like Post, BlogPost and publishedAt are unchanged. EAS sync still finds items published under the old names.
  • Breaking: a File property given a string stores it as follows: a data: URL is decoded to bytes, an http(s): or blob: URL is fetched, the name of a file already in the files folder links that file, and anything else is stored as the file's text. A raw base64 string used to be decoded; it's now stored as text, so pass a data: URL instead.
  • Changed: after init, the SDK rewrites the model types it stored (seeds.type, and the matching columns of versions and metadata) to the easName form (image_v2, not image_v_2; html_page, not htmlpage) once per type, in the leader tab. Raw SQL that filters on the old forms stops matching.
  • Changed: importing a schema treats a property as renamed only when its id matches a stored property's. The metadata rows then move to the new name. Without an id match, a changed name is an added property plus a removed one, and the old values stay under the old name.
  • Changed: unload() releases one reference, and the instance stops only when no reference is left. This covers Schema, Model and the other entities, but not Item: Item.unload() still stops the shared item at once.
  • Changed: Schema.destroy() deletes the schema's version files in the files folder, so the next Schema.all() doesn't import it again. It still doesn't delete items or anything published.
  • Changed: validation.custom is reported as unsupported by schema validation (custom_validation_unsupported). Loading a schema that uses it still doesn't fail, so it still has no effect.
  • Deprecated: DbConfig.schemaDir, which nothing reads.
  • Added: exports ItemCreateValidationError, UnknownModelError and the ItemCreateProblem and ItemCreateOptions types (also from @seedprotocol/sdk/node); easName, matchEasName and the NameMatchLevel type, re-exported from @seedprotocol/eas; easPropertyNameForPublish; and setRetriesWhenOnlineEnabled.
  • Added: offline retries. The start-up EAS schema lookup and failed automatic syncs are retried when the browser comes back online, or otherwise after 15 seconds, 30 seconds, 1, 2 and 5 minutes (also in Node). client.stop() cancels them. They're off when NODE_ENV or Vite's mode is test, unless you call setRetriesWhenOnlineEnabled(true).
  • Added: schema.lastSaveError and model.lastSaveError. A rename or other tracked edit starts a background draft save; when it fails (a ConflictError, for example) the error is kept there and logged with console.warn, instead of only reaching the debug log.
  • Added: importing a schema warns when two models, or two properties of one model, have the same easName and so would publish to the same EAS schema. The schema still loads.
  • Fixed (security): EAS sync ignores Version attestations not made by the Seed's attester, and property attestations not made by the Version's attester. Rows like that from earlier syncs are deleted once after init, without the network. Drafts are kept.
  • Fixed: config.dbConfig works in Node: dbUrl sets the database file (default <filesDir>/db/seed.db) and outDir where migrations are copied and run from (default <filesDir>/db). The browser ignores it.
  • Fixed: Date values validate, so assigning a Date property on an existing item saves.
  • Fixed: Item.create, and Image, File and Html saves, work offline. The model's EAS schema UID is read from the local database first, and a failed EAS request no longer fails the create.
  • Fixed: a List of Relation reads back as an array, after create and after a reload.
  • Fixed: new Image and File values are stored as <seedLocalId>-<name>, so two items whose files share a name no longer overwrite each other.
  • Fixed: writes to a property go through a queue, one at a time, and a stored value no longer replaces the in-memory one while a save is pending. A text input bound to item.title (via useItem) no longer drops keystrokes (one bound to property.value from useItemProperty still can, see the React notes), and another tab's write no longer replaces a value mid-edit.
  • Fixed: editing a draft moves its timestamp to the time of the edit, so the edit wins over synced values attested before it.
  • Fixed: a sync cut short by a closed tab no longer stays in_progress. The next sync marks it failed with Interrupted: the sync stopped before it finished (tab closed?).
  • Fixed: client.init({ addresses }) emits addresses.persisted after saving them, as setAddresses does.
  • Fixed: saveNewVersion() writes a version file that loads again: every model, each property in file format, and the new version. A schema created only in code gets a version and metadata, so it can be saved. If the app keeps passing the older file as config.schema, the next start reports the older version and saveNewVersion() throws ConflictError.
  • Fixed: a version bump through config.schema updates the schema row to the new file's id and version, and an older file no longer rolls it back. renameModelProperty and the other schema edits work after a bump. In the session that applies the bump, Schema.create(name).version and model.properties still show the old version until the next start.
  • Fixed: schema ids keep hyphenated names (my-app stays my-app); only a numeric -<n> suffix is dropped.
  • Fixed: a destroyed model is no longer cached again by a lookup that runs while its rows are deleted.
  • Fixed: the 10-second start-up EAS timeout is cleared when the lookup settles. Node processes still don't exit on their own.
  • Fixed: an unrelated error whose stack happens to contain 2067 is no longer taken for a SQLite UNIQUE violation.
  • Fixed: Schema.name and ModelProperty.id are declared in the types.

@seedprotocol/publish

  • Breaking: a publish from a smart account that isn't deployed yet is refused with ManagedAccountPublishError code MANAGED_ACCOUNT_NOT_DEPLOYED, instead of attesting straight to EAS one transaction per Seed. A wallet that sends as a different address than the publisher is refused too. Plain wallets (EOAs) and useDirectEas: true still attest directly. ConnectButton records the admin address so publish can tell the two apart.
  • Breaking: clearPublishWallet() returns a Promise and also clears the SDK's owned addresses, keeping watched ones. ConnectButton's disconnect and useSeedWallet().disconnect call it. Drafts created after a disconnect are signed-out drafts that the next connect claims.
  • Changed: a publish starts in a new authorizing state, before checking. It only does something when authorizePublish is set.
  • Changed: EAS schemas are registered under easName names (see the SDK above).
  • Changed: the per-publish arweaveJwk and dataItemSigner are no longer saved in publish_processes. A publish restored after a reload signs with PublishConfig.arweaveJwk or dataItemSigner, and fails at creatingArweaveTransactions without them. Rows written by 0.7.3 and earlier keep their copy.
  • Changed: createPermissionlessTxSender takes sponsored (default true); false sends without a paymaster.
  • Added: PublishConfig.payment ({ gas?: 'sponsored' | 'user', storage?: 'operator' | 'user', userStorage? }) says who pays. It fills in the defaults of the lower-level settings, and initPublish throws PublishPaymentConfigError when one contradicts it. Also resolvePublishPayment, resolvePublishSender and their types.
  • Added: user-paid storage through Turbo, with payment: { storage: 'user' }. Data items signed by the user's key go straight to Turbo and are charged to that address's Turbo credits (or userStorage.paidBy first). An unfunded signer gets InsufficientStorageCreditsError. useArweaveBundler defaults to true with this setting. Also getTurboBalanceWinc and getTurboPriceWinc.
  • Added: authorizePublish, in the config and per publish. It's called before anything is spent, with the item, the payment and a cost estimate. Return false or throw to refuse (PublishNotAuthorizedError), or { token } to send a token as X-Seed-Publish-Authorization (PUBLISH_AUTHORIZATION_HEADER) on the upload API requests.
  • Added: DOMAIN_OWNERSHIP_SCOPE_HOSTNAME. A challenge made with normalizeRegistrable: false defaults to it, and the attestation records the hostname.
  • Fixed: estimatePublishCost reports who pays from the sender the publish will use: evm.userPays is false when Thirdweb or a paymaster sponsors, and arweave.userPays is true only with payment.storage: 'user'. Prices are unchanged.
  • Fixed: connecting a wallet (setPublishWallet) keeps the SDK's watched addresses.
  • Fixed: a direct-to-EAS publish records each Seed as soon as it's attested, so a retry after a failure part-way no longer attests a second Seed.
  • Fixed: unpublish() revokes from the attesting account when the registered wallet isn't it (for example with useModularExecutor), leaves out UIDs EAS already shows as revoked, and recognizes AlreadyRevoked by its selector too.
  • Fixed: assessDomainOwnershipLive honors the attestation's expirationTime, and live checks of easscan data no longer report a false likely_transferred.
  • Fixed: the package's test script runs every test file.

@seedprotocol/react

  • Breaking: needs @seedprotocol/sdk 0.7.4 (see the note above). useItems matches items by the easName of the model.
  • Breaking: UseCreateModelOptions no longer has registerWithSchema, which did nothing.
  • Deprecated: usePersistedSnapshots and useHasSavedSnapshots are no-ops: the first does nothing and the second always returns false.
  • Added: exports useIsClientReady, useLiveQueryState ({ data, error }), useStorageEstimate, useThumbnail, and the StorageEstimate, ThumbnailStatus, FileEntry, UseDeleteItemReturn, UseCreateModelOptions, UseCreateModelReturn and UseDestroyModelReturn types.
  • Fixed: useLiveQuery keys a Drizzle builder by its SQL and parameters, so an inline builder no longer resubscribes on every render; rows reset to undefined when the query changes. A SQL tag function is still keyed by reference.
  • Fixed: useItems reports isLoading: true until the client is ready, and items keeps its identity while the data is unchanged. Deleting the last item still leaves it on screen.
  • Fixed: useCreateItem no longer creates twice when called twice before a re-render.
  • Fixed: useDebouncedItemProperty saves the pending edit on unmount and returns flush().
  • Fixed: useItemProperty with itemId tries it as a local ID, then as a seedUid.
  • Fixed: useModel always calls its hooks in the same order; useModels and useSchemas refresh when models or schemas are deleted; useSchema(name) returns schema: null for an unknown name instead of creating a draft, and reports machine errors with isLoading: false.
  • Fixed: useDbsAreReady, useIsDbReady and useGlobalServiceStatus().internalStatus turn ready once the database is open, and back to not ready if init fails. useServices().percentComplete starts from the current state.
  • Fixed: useCreateModelProperty and three-argument useModelProperty keep hyphenated schema names.
  • Fixed: useEasSyncProcessById(undefined) reports isLoading: false.
  • Fixed: useResolvedMediaRef revokes the blob URLs it creates; formatSeedJson prints [Circular] only for real cycles; SeedImage re-renders when any prop changes, sets width on the <img>, and takes an IItemProperty.

@seedprotocol/vite

  • Changed: @seedprotocol/eas, @seedprotocol/arweave and @seedprotocol/query are pre-bundled together and deduped, so a later optimizer pass can't load a second copy.
  • Changed: the plugin adds its own aliases, resolve.dedupe entries and build options without copying yours back, so nothing is duplicated and your aliases match first.
  • Changed: new dependency @rollup/pluginutils, for the bare-globals scan.
  • Fixed: COOP, COEP and CORP headers you set in server.headers or preview.headers are kept; the plugin adds only the ones you didn't set.
  • Fixed: builds no longer warn "Both rollupOptions and rolldownOptions were specified", and your build options pass through.
  • Fixed: the bare Node globals warning reports only free references to global, process, Buffer, __dirname and __filename, so a default app builds without it.

@seedprotocol/eas

  • Added: easName and the name-matching helpers ADR 0008 uses (matchEasName, easNameVariants, normalizedNameKey, fuzzyNameKey and others).
  • Added: partitionByRefAttester and keepAttestationsFromRefAttester, to keep only Versions made by their Seed's attester and properties made by their Version's attester. The raw reads don't filter.
  • Added: assertSingleEasModule and DuplicateEasModuleError, thrown when another copy of the package registered the EAS clients.
  • Added: GET_DOMAIN_OWNERSHIPS (with expirationTime) and DOMAIN_OWNERSHIP_SCOPE_HOSTNAME. Nested bytes32 values from easscan now decode.

@seedprotocol/query

  • Breaking: every read throws DuplicateEasModuleError or DuplicateArweaveModuleError when the clients were registered on another copy of @seedprotocol/eas or @seedprotocol/arweave (a second install, or a separate Vite pre-bundle). It used to read quietly through other clients and possibly another chain.
  • Fixed (security): a Seed's Versions count only when its attester made them, and a Version's properties only when the Version's attester made them, in queryBySchema, getSeed and changelogs.

@seedprotocol/arweave

  • Added: assertSingleArweaveModule and DuplicateArweaveModuleError.

All packages

  • Internal @seedprotocol/* dependencies are pinned to 0.7.4. @seedprotocol/feed changed only its README; feed-hyper, gateway-hyper and mapping changed only their version.

0.7.3

Released 2026-10-14. Upgrade steps: Upgrading from 0.7.2 to 0.7.3.

React and the SDK had to be upgraded together. React 0.7.3 imports SDK exports that first shipped in SDK 0.7.3, so React 0.7.3 with SDK 0.7.2 failed at import (does not provide an export named 'placeholderUidSql'). Its peer range still said ^0.6.0, so npm needed an overrides block, and pnpm and bun installed a mismatched pair with only a warning. Since 0.7.5 React's peer is the exact SDK version and no overrides block is needed.

@seedprotocol/sdk

  • Breaking: the property decorators are removed from @seedprotocol/sdk/node: Text, Number, Boolean, Date, Json, Image, List and Relation. Nothing read the metadata they recorded. Use the Property builders, which ./node keeps along with Model.
  • Breaking: engines.node is ^20.19.0 || >=22.12.0 <=24 (it was >= 20 <= 24). Node 20.0–20.18, 21 and 22.0–22.11 are outside the range.
  • Breaking: a second client.init with a different config throws INIT_CONFIG_CONFLICT_MESSAGE instead of resolving and keeping the first config. Call client.stop() before initializing with a new config. A repeat init with the same config resolves, and applies new addresses through setAddresses.
  • Breaking: client.init rejects when config.schema or config.schemaFile can't be read or applied, with Could not load config.schema from "<path>": <reason>. It used to succeed and log the error. With React, the error reaches SeedClientGate's onInitError.
  • Breaking: assigning a property on an item the client's addresses don't own (post.title = 'x') is refused. Nothing is written, the value rolls back, and the property's _saveError is an OwnershipError. A side effect: once owned addresses are set, a draft created without a publisher isn't owned, so assignments to it are refused until it's claimed. @seedprotocol/publish supplies the publisher while a wallet is connected; Node scripts have to supply it themselves. See the upgrade steps.
  • Breaking: destroy() on Item, ItemProperty, Model, ModelProperty and Schema rejects when the delete fails. It used to resolve with the rows still there.
  • Changed: item.publish(options?) hands the item to the executor that @seedprotocol/publish registers in initPublish() (or <PublishProvider config>) and resolves when the publish has finished. Without the publish package it throws "Publishing is not configured…" at once. The 5-minute timeout and the "payload only" behavior are gone. Item.publish(item, options) takes the options too.
  • Changed: the default readyTimeout for create, find and all on Schema, Model, ModelProperty, Item and ItemProperty is 15 seconds. Most were 5 seconds.
  • Changed: client.init without addresses keeps the stored addresses and warns once. addresses: [] still clears them in Node and in the first tab.
  • Changed: destroy() on a ModelProperty, Model or Schema works when items have values for it. The items' stored values are kept and unlinked from the property.
  • Changed: the Content-SHA-256 upload tag is SHA-256 on both upload paths (the L1 path wrote SHA3-256). Downloaded files are checked against it: a gateway that serves different bytes is skipped, and if none matches, nothing is saved. Uploads from 0.7.2 and earlier are still accepted.
  • Changed: every file write is announced to other tabs as fs.files.changed, coalesced over 25 ms, not only Image, File and Html saves.
  • Changed: publish creates L1 transactions four at a time.
  • Changed: reflect-metadata is no longer a dependency.
  • Deprecated: setUploadExecutor, getUploadExecutor and UploadExecutor (nothing calls them), and ownedPublisherSql (use ownedSeedsSql).
  • Added: exports from the main entry: OwnershipError, assertItemOwned, ConflictError and the ConflictResult, ValidationError and ValidationResult types; setPublishExecutor, getPublishExecutor, PUBLISH_NOT_CONFIGURED_MESSAGE, INIT_CONFIG_CONFLICT_MESSAGE and the PublishExecutor and ItemPublishOptions types; getPublishUploadData, contentSha256TagMatches and the other Content-SHA-256 helpers; ownedSeedsSql, localUnsealedDraftSql and placeholderUidSql; and the experimental Arweave content read transform from @seedprotocol/arweave.
  • Added: @seedprotocol/sdk/node exports AmbiguousModelError, ConflictError, OwnershipError, assertItemOwned, isItemOwned, and the IItem, IItemProperty, MultiTabMode, SyncFromEasOptions and HtmlEmbeddedDataUriPolicy types.
  • Added: after each init, the leader tab moves ItemStorage content that older versions stored in the database row into its file, once.
  • Fixed: require('@seedprotocol/sdk') and require('@seedprotocol/sdk/node') work on Node 20.19+ and 22.12+. They load the same ESM build as import, so mixed code shares one client.
  • Fixed: Node no longer needs --import @seedprotocol/sdk/node-eas-lodash for the SDK. Processes that load @seedprotocol/publish still do.
  • Fixed: in Node the SDK finds its database migrations from any working directory. It still creates .seed in the working directory at every init.
  • Fixed: @seedprotocol/sdk/platform exports configurePlatform.
  • Fixed: PropertyDataType is a union of string literals, so { dataType: 'Text' } type-checks. Schema.create, Model.create, ModelProperty.create and ItemProperty.create are typed as returning a Promise unless you pass waitForReady: false. client.init is typed with SeedConstructorOptions.
  • Fixed: client.onReady(callback) registered after the client is ready fires, and returns a function that cancels it.
  • Fixed: client.claimUnpublishedDrafts updates publisher on items already loaded, in every tab, and returns the claimed seedLocalIds.
  • Fixed: addressFilter: 'owned' lists exactly what isItemOwned calls owned: local drafts when no owned addresses are set, otherwise items whose publisher (or, when it's blank, attester) is owned.
  • Fixed: Model.create with properties waits until the properties exist and rejects if they can't be created. ModelProperty.create rejects when its row isn't written. Model.all({ waitForReady }) no longer drops ready models.
  • Fixed: schema reloads (renameModelProperty, updateModelProperties, a changed config.schema) update the property row in place and refresh cached ModelProperty instances.
  • Fixed: ItemProperty#destroy() checks ownership and deletes only its own item's rows (every version of them).
  • Fixed: an ItemStorage value passed to Item.create is written to its file before create resolves, and is included in publishes. Each ItemStorage file is locked across saves.
  • Fixed: a listener that throws no longer breaks the SDK code that emitted the event. The error is logged and the other listeners still run.
  • Fixed: upload Content-Type comes from the file's own type, then its extension (documents, audio, video, AVIF and HEIC are now recognized), then its bytes. Downloads are filed by Content-Type, so non-images no longer land in images/ and plain text is kept. Binary files inside multi-file ItemStorage uploads stay intact.
  • Fixed: file downloads settle when the worker fails or a gateway stalls (60-second timeout), instead of hanging.
  • Fixed: browser OPFS writes no longer leave the old tail when a file gets shorter, or cut off bytes another tab wrote.
  • Fixed: filesDir: '.seed-alt/x' resolves under .seed; only a first segment of exactly .seed counts as already inside it.
  • Fixed: in Node before 24.5, which has no navigator.locks, concurrent inits and syncs in one process take turns instead of racing.

@seedprotocol/publish

  • Added: initPublish() registers publishItem as the executor behind the SDK's item.publish(), so item.publish(options) runs a full publish (uploads and attestations) with the wallet from ConnectButton or setPublishWallet. options are CreatePublishOptions. On the bundler path, pass your own dataItemSigner: with ConnectButton, the default signer is the smart account, which can't sign data items.
  • Added: publishItem(item, options), waitForPublishActor(actor, seedLocalId) and the PublishStarted type.
  • Changed: ensureWalletThenPublish and ensureSmartWalletThenPublish return { outcome: 'started', started }. started resolves to the publish actor, or undefined when the item was already publishing. Neither waits for the publish to finish.
  • Fixed: the bundler path builds its uploads with the SDK's getPublishUploadData, so both paths upload the same bytes and tags. Hashing no longer needs WebCrypto, so it works on plain-HTTP pages other than localhost.

@seedprotocol/react

  • Breaking: needs @seedprotocol/sdk 0.7.3 (see the note above).
  • Fixed: usePublishItem publishes once @seedprotocol/publish is initialized, and its error reports the rejection of item.publish(): not configured, not owned, already publishing, or the publish's own failure. publishItem(item, options?) takes the publish options, and isLoading covers the whole publish.
  • Fixed: useItems({ addressFilter: 'owned' }) uses the same rule as isItemOwned.
  • Added: useEnsureLocalImage returns gatewayHrefs and onGatewayError(), and SeedImage moves to the next read gateway when an image fails to load, so a just-published image no longer 404s while it propagates.

@seedprotocol/arweave

  • Added: getTransactionTagsByIds(ids, options?) reads many transactions' tags in batched GraphQL requests (up to 100 per request), with GET_TRANSACTIONS_TAGS_BY_IDS and TRANSACTION_TAGS_BATCH_SIZE.
  • Added: BaseArweaveClient.isConfigured().
  • Added (experimental): a read-side content transform: setArweaveContentReadTransform, hasArweaveContentReadTransform, transformArweaveContent, readArweaveContentResponse and getResponseMediaType. Nothing registers one yet.
  • Changed: BaseArweaveClient.getTransactionData is async and runs decoded data through the transform when one is registered.

@seedprotocol/eas

  • Added: DefaultEasClient, DefaultQueryClient and ensureEasPlatform() from the main entry: fetch-based clients for browsers and Node. NodeEasClient and NodeQueryClient now extend them.
  • Added: isConfigured() on BaseEasClient and BaseQueryClient.

@seedprotocol/query

  • Fixed: initializeQueryPlatform keeps EAS clients and an Arweave gateway that are already registered, so query next to a running SeedClient, or after @seedprotocol/feed's init, no longer replaces them. The browser build no longer loads the Node clients. An arweaveDomain you pass still sets the host.

All packages

  • Internal @seedprotocol/* dependencies are pinned to 0.7.3. @seedprotocol/feed, feed-hyper, gateway-hyper, mapping and vite changed only their version.

0.7.2

Released 2026-10-11.

@seedprotocol/eas

  • Breaking: version and property attestations returned by GET_VERSIONS, GET_PROPERTIES, GET_ALL_PROPERTIES_FOR_ALL_VERSIONS, GET_FILES_METADATA and GET_IMAGE_VERSIONS no longer include schema { schemaNames }, which easscan resolved slowly. Read schemaId instead.
  • Added: GET_SEEDS_LEAN, getSeedsByUidsFromEas, getAttestationChangesSince with GET_ATTESTATION_CHANGES, and getSchemaNamesBySchemaUids.
  • Changed: getSeedsBySchemaName skips the schema join and takes an optional 4th argument, { uidPrefix }.
  • Changed: in Node, NodeQueryClient is now a real TanStack Query client (@tanstack/query-core is a new dependency). Identical requests in flight at the same time share one fetch. Nothing is kept once a request settles unless you pass staleTime, there are no retries, and cache keys are scoped to the EAS endpoint.

@seedprotocol/query

  • Added: queryBySchema(schema, { uidPrefix, limit }) finds a schema's seeds by UID prefix without listing the schema, and normalizeUidPrefix is exported. See Query overview.
  • Fixed: a cached collection now asks EAS what changed since the last check and re-assembles only those seeds. Before, edits to existing seeds were missed, and under steady traffic the cache never expired.
  • Changed: Arweave bodies are fetched in parallel and cached by transaction ID.
  • Changed: cached records are deep-frozen outside production and typed read-only (SeedRecord fields are readonly). Mutating a returned record now throws in development. QueryCacheConfig.freezeRecords overrides the default.
  • Changed: EAS requests that don't depend on each other run in parallel, so reads are faster.

@seedprotocol/feed

  • Fixed: feed defaults no longer modify records in the query cache.

@seedprotocol/vite

  • Changed: viem, isows, kerium, utilium, memium and readable-stream are prebundled only when your app can resolve them, which removes "Failed to resolve dependency" warnings.

@seedprotocol/sdk

  • Fixed: a local ID that happened to start with 0x was taken for an EAS UID, and publishing a list that contained it failed with "invalid BytesLike value". UIDs are now recognized by their full shape, and new local IDs never start with 0x.
  • Fixed: Item.unpublish could stall for several seconds loading a module lazily.

All packages

  • Releases are published from CI with npm trusted publishing. Builds of the main branch are published as prereleases under the next dist-tag (versions like 0.7.3-next.613.g79a8dfd). latest only moves on a tagged release.

0.7.1

Released 2026-10-09.

@seedprotocol/sdk

  • Added: seed:property/<name> placeholders in Html. Write <img src="seed:property/featureImage"> and the publish rewrites it to the gateway URL of that property's upload, instead of embedding the image as a data: URI. A placeholder naming a missing or empty property fails the publish before anything is uploaded.
  • Added: Item.peekReady and ItemProperty.peekReady return a cached instance only if it can be read right now, without taking a cache hold.
  • Changed: a publish now carries every draft reachable through relation and list properties, each with all its properties. Before, related drafts were published one level deep with only some of their properties, so a published relation could point at a draft that was never published.
  • Fixed: images embedded in Html no longer overwrite the Html seed's reference, and their src is rewritten in place without re-serializing the rest of the Html.
  • Changed: the optional @tanstack/react-query peer range is now ^5.102.0.

@seedprotocol/react

  • Added: OPFSFilesManager can delete folders.
  • Changed: useItem and useItemProperty load through React Query and return a ready cached instance on the first render, so a row that useItems just returned renders filled in instead of empty for a tick.
  • Changed: useItem re-renders when one of the item's property values changes.
  • Fixed: changing the ID passed to useItem or useItemProperty no longer shows the previous item while the new one loads.

@seedprotocol/publish

  • Fixed: a direct-to-EAS publish attests every list member's seed UID.

@seedprotocol/vite

  • Fixed: the dev server on Vite 8.2 and later with includeNodePolyfills. Every prebundled dependency declared the polyfill banner twice and failed to parse. Your own optimizeDeps include and exclude entries are no longer duplicated either.
  • Changed: with node polyfills on, global resolves to globalThis.

@seedprotocol/feed-hyper and @seedprotocol/gateway-hyper

  • Added: the seed-feed command (publish, seed, serve) ships with @seedprotocol/feed-hyper, and seed-gateway (serve, connect) with @seedprotocol/gateway-hyper. See Hyper distribution and Gateway Hyper.

0.7.0

Released 2026-10-08.

@seedprotocol/sdk

  • Added: tabs of the same app now coordinate (multiTab: 'coordinate', the default, set on client.init). One leader tab runs automatic EAS sync and the Arweave finalize worker. Sync results, address changes and file changes are relayed to the other tabs. Migrations and init writes take turns, and a tab can open the database while another tab is using it. Pass multiTab: 'off' for the old behavior. See Multi-tab behavior.
  • Added: each item records its model, and models are resolved by schema and name. Item.all and getItemsData accept schemaName or modelFileId.
  • Breaking: model names are only unique within a schema. Name-only lookups (Model.getByName, Item.create without a schema, and others) throw AmbiguousModelError when two loaded schemas define the same model name. Pass the schema name.
  • Breaking: on first start after upgrading, if items saved before this release have no recorded model and their model name exists in more than one schema, the local item data is cleared once. Schemas and models are kept, and published items come back on the next EAS sync. Unpublished drafts in that situation are lost.
  • Added: version revocations are recorded locally. Revoked versions are skipped when picking a seed's latest version, and property revocations and replacements reach stored data.
  • Changed: republishing an unpublished (revoked) item creates a new seed instead of adding a version to the revoked one.
  • Changed: publishing an item that refers to an unpublished item fails before anything is sent, with RelatedItemUnpublishedError (code related_item_unpublished).
  • Changed: unchanged schema files aren't re-applied on each client.init.
  • Fixed: ModelProperty.save() looped until it ran out of memory.
  • Fixed: many schema and model fixes: importing a schema no longer takes over another schema's model with the same name, Schema.destroy waits for in-flight writes, property required and storage settings persist, and Image properties accept Blob values.
  • Fixed: EAS sync now syncs versions of seeds that were already stored, and batches its reads and inserts under SQLite's variable limit.
  • Fixed: OPFS file locks are released when a worker write fails.

@seedprotocol/react

  • Breaking: OPFSFilesManager was redesigned as a folder browser with thumbnails, search, sort, grid and list views and in-component dialogs. Its theme now defaults to 'system' (it was 'dark'), and onBeforeDelete runs after the user confirms instead of before. See Files and OPFS.
  • Added: onNotify and deleteAction on OPFSFilesManager.
  • Added: SeedClientGate reports init failures through onInitError (for example FileSystemLockedError, code OPFS_LOCKED). Before, a failed init left the loading UI up.
  • Changed: useItems accepts schemaName.
  • Fixed: the destroy hooks report isLoading and error correctly, useModelProperties follows the model, and useModelProperty retries a lookup that found nothing.
  • Fixed: the @seedprotocol/sdk peer range is ^0.6.0. It was ^0.4.0, which reported a conflict with SDK 0.6.

@seedprotocol/publish

  • Fixed: each multiPublish request is matched to its own receipt events, so a publish that sends several requests in one transaction records all of them. Direct-to-EAS publishes record each request's own seed.
  • Fixed: images embedded in a related draft's Html are published with it.
  • Fixed: an in-progress publish resumes in one tab only.

@seedprotocol/eas

  • Fixed: revoked attestations are skipped when picking a property's canonical value, and version and property attestations include revocationTime.
  • Fixed: query cache keys include the query's inputs.

@seedprotocol/query

  • Fixed: seeds whose versions are all revoked are left out, from both the local and the remote source.

0.6.10

Released 2026-10-05.

@seedprotocol/publish

  • Breaking: waitForPublishReceipt throws PublishTransactionRevertedError (with transactionHash and receipt) on a reverted transaction. It used to return the receipt, so callers treated a revert as success. Functions that return its receipt, such as attestPublishAuthorization, now throw too.
  • Breaking: ensureExecutorModuleInstalled no longer calls installModule on thirdweb ModularCore accounts (that path could never work). It returns { status: 'skipped' | 'already-installed' } or { status: 'installed', eas, transactionHash }. PublishConfig.modularAccountModuleData is no longer used.
  • Added: readFactorySeedExecutor(factory?) reads the executor the factory's router extension trusts, so you can compare it with modularAccountModuleContract at startup.
  • Added: seedPaymaster(client, chain, bundlerUrl?) in @seedprotocol/publish/thirdweb, for smart wallets the SDK doesn't build.
  • Fixed: sponsored ManagedAccount UserOps ran out of gas on large publishes. The call gas limit now gets 20% headroom before the paymaster signs.
  • Fixed: turning on automation could fail with "The Seed executor is not installed" right after a successful install, because of a lagging RPC read. The install is now confirmed from the receipt's events, and a failed install reports the revert reason.

0.6.9

Released 2026-10-03. This is the minimum version for the contracts switched on 2026-10-03 (Deployments, upgrade steps).

@seedprotocol/publish

  • Breaking: encodeMultiPublish encodes the new SeedProtocolExtension struct (selector 0x2a29fadc), in which a property names its target by publishIndex, its position in the batch. encodeExecutorMultiPublish uses the same field order and works only with the new executor.
  • Breaking: removed encodeMultiPublishInteger, transformPayloadToIntegerIds, the RequestWithStringIds and RequestWithIntegerIds types, and the useIntegerLocalIds option. No deployed contract accepts them.
  • Changed: both encoders throw on a duplicate local ID or a missing or unknown publishLocalId, instead of dropping the reference or pointing it at the first request.

0.6.8

Released 2026-10-03.

@seedprotocol/publish

  • Added: the publish chain is configurable. PublishConfig accepts easContractAddress, schemaRegistryAddress and managedAccountFactoryAddress, and known chains fill them in from config.chain. See Deploying to a new chain.
  • Added: verifyPublishChain checks before the first publish or revoke that the RPC reports the configured chain and that EAS, the SchemaRegistry and any configured factory or executor have code there. It throws PublishChainConfigError.
  • Added: PublishConfig.thirdweb (bundlerUrl, sponsorGas, modularWalletMode) configures the in-app wallets, and seedTwinConfig reads a local twin's twin.json.
  • Added: every publish route simulates multiPublish before sending. A call that would revert throws PUBLISH_PREFLIGHT_FAILED with the decoded reason, and a failed UserOp reports USEROP_FAILED_NO_REASON or USEROP_REVERTED.
  • Changed: client changes for the new contracts. An interactive publish is a UserOp from the ManagedAccount calling multiPublish on itself. setEas is sent only for older accounts that report no EAS. Session keys never get EAS as an approved target.
  • Changed: revocation is owner-only, through EAS. Automation session keys can't revoke, and can't register missing schemas: register them once with the owner wallet through ensureEasSchemasForItem.
  • Fixed: list relations are always attested under one EAS name ending in _ids (for example bytes32[] staff_identity_ids). Already-published data is unchanged.
  • Fixed: a property whose EAS schema can't be found fails validation with publish_schema_not_found instead of being attested under the Version schema.
  • Fixed: turning on automation works on older ManagedAccounts that use the Router design, and failed UserOps on the permissionless (EIP-7702) sender throw instead of reporting success.
  • Fixed: a Node server can publish without a browser thirdweb client.

@seedprotocol/sdk

  • Added: SeedConfig.eas (chainId, indexerUrl) and the EAS_CHAIN_ID environment variable choose which chain's EAS the SDK reads.
  • Added: the local database records its EAS chain and refuses to sync, publish or revoke against a different one. Databases from earlier versions are treated as OP Sepolia.
  • Changed: EAS sync waits up to 30 seconds for initPublish to set the chain, unless the chain is already known from config, the environment or the database.
  • Changed: VERSION_SCHEMA_UID_OPTIMISM_SEPOLIA is renamed VERSION_SCHEMA_UID. The old name still works and is deprecated.
  • Fixed: property data types are case-insensitive, and failed saves in Node are reported instead of hanging.

@seedprotocol/eas

  • Added: EAS_CHAIN_DEPLOYMENTS lists EAS and SchemaRegistry addresses and easscan URLs for known chains.

@seedprotocol/query

  • Fixed: parseEasRelationPropertyName turns staff_identity_ids into staff (was staffs).

@seedprotocol/feed

  • Changed: item links point to the configured chain's EAS explorer.

0.6.7

Released 2026-09-29.

@seedprotocol/sdk

  • Added: ensureImageLocal downloads a missing image and creates only the size variants that are needed.
  • Added: an Arweave gateway circuit breaker. Gateways that fail a probe or a media request are skipped for a cooldown.
  • Fixed: Node built-ins no longer end up in browser bundles, and Rollup builds of the SDK work.

@seedprotocol/react

  • Added: useEnsureLocalImage. SeedImage shows the gateway URL until the image is stored locally.

@seedprotocol/publish

  • Added: PublishedBy sidecar attestations over a publish batch: attestPublishedBy, revokePublishedBy, ensurePublishedBySchema and helpers to read them.

@seedprotocol/vite

  • Fixed: plugin bugs.

0.6.4 to 0.6.6

Released 2026-09-25 (three releases the same day).

@seedprotocol/publish (0.6.4)

  • Fixed: the first publish failed when the ManagedAccount wasn't deployed yet. It's now deployed as part of that publish.

@seedprotocol/sdk (0.6.5)

  • Fixed: a failed client.init was treated as ready, so the app ran against a missing database. Init now stays failed, overlapping init calls share one run, and save failures reject immediately.
  • Changed: @zenfs/core 2.5.6 and @zenfs/dom 1.2.9 are optional peer dependencies.

@seedprotocol/sdk (0.6.6)

  • Fixed: database init hung silently in Chrome profiles that kill the SQLite module worker. The worker now starts in a way those profiles accept, and a worker that dies fails init with an error instead of hanging.

0.6.3

Released 2026-09-25.

@seedprotocol/mapping

  • Added: derive and assemble resolve jobs, useFieldMapper.setDerive and setAssembleBlocks, and the transformOptions and renderRowExpansion props on FieldMapper. applyMapping skips derive and assemble edges; your app applies them.
  • Fixed: a later edit could bring back a mapping your app had removed.

@seedprotocol/sdk

  • Fixed: Item proxies made React 19 abort some updates.

0.6.2

Released 2026-09-24.

@seedprotocol/publish

  • Added: publish cost estimates: estimatePublishCost and watchPublishCost. See Costs and who pays.

@seedprotocol/sdk

  • Added: summarizePublishWork, and ownership helpers such as isLocalUnsealedDraft and claimUnpublishedDrafts.
  • Fixed: item ownership checks.

0.6.1

Released 2026-09-23.

@seedprotocol/mapping

  • Changed: relation lookups are stored on the mapping edge as FieldMapping.lookup.entries ({ value, ref }[]). MappingDocument.lookups still works and is deprecated.
  • Added: renderLookup on FieldMapper for your own picker, and useFieldMapper.setLookupEntries.

@seedprotocol/publish

  • Fixed: seed UIDs after a publish are read from the transaction receipt and don't depend on a separate created-attestation lookup.

0.6.0

Released 2026-09-22. The other packages only changed version.

@seedprotocol/mapping

  • Breaking: FieldMapper and the other UI exports moved to @seedprotocol/mapping/react. The main entry no longer imports React, so it loads in Node without it.
  • Breaking: FieldMapper's layout defaults to 'rows' (was 'wires'). layout="wires", theme="unstyled", showPreview and connectionColors still work and are deprecated.
  • Added: the headless useFieldMapper hook, the rows layout, the theming contract (--sfm-* tokens, theme, classNames, slots), renderRowAccessory and SourceNode.subtitle.
  • Added: resolve: 'extract' | 'file' | 'lookup' jobs, applyMappingAsync, classifyUrl, and one source mapped to several properties.

See FieldMapper UI.

0.5.3

Released 2026-09-17.

@seedprotocol/mapping (first release on npm)

  • Added: the headless mapping core, Markdown and RSS adapters, and the FieldMapper UI. See Mapping overview.

@seedprotocol/publish

  • Added: publish automation: session keys hosted on the ManagedAccount, enrollPublishAutomation and revokePublishAutomation, and the PublishAuthorization sidecar. See Publish unattended with a session key.
  • Added: domain ownership tooling: DNS TXT challenges, RDAP snapshots, and attestDomainOwnership / revokeDomainOwnership. See Domain ownership.

@seedprotocol/eas

  • Added: helpers to read and assess PublishAuthorization and DomainOwnership attestations.

@seedprotocol/sdk

  • Added: client.removeLocalCopiesForAddresses deletes the local copies of a publisher's on-chain items (for example on wallet disconnect). Drafts stay, and EAS sync brings the rest back on reconnect.

0.5.2

Released 2026-09-06.

@seedprotocol/query (first release on npm)

  • Added: reads of published Seed data as canonical JSON, with local, remote and auto sources, collection and item caching, and a Version changelog on getSeed. See Query overview.

@seedprotocol/feed

  • Changed: reads through @seedprotocol/query, and emits media URLs for Image relations instead of EAS links.

@seedprotocol/arweave

  • Added: a configured proxyBaseUrl is preferred as the gateway path. EAS explorer URLs are recognized, and RSS image references resolve.

@seedprotocol/gateway-hyper

  • Added: createGatewayProxy, to proxy the gateway from your own app server.

@seedprotocol/feed-hyper (first release on npm)

  • Added: publish Seed feeds into a Hyperdrive, seed them over Hyperswarm and serve them over local HTTP.

0.5.1

Released 2026-08-29. The package CHANGELOGs list most of these changes under 0.5.0, which was never published to npm.

New packages: @seedprotocol/eas (EAS GraphQL client), @seedprotocol/arweave (Arweave gateway client and gateway resolution), @seedprotocol/vite (the Vite plugin, extracted from @seedprotocol/sdk/vite) and @seedprotocol/gateway-hyper.

@seedprotocol/sdk

  • Breaking: drizzle-orm and xstate are peer dependencies. Install them in your app.
  • Changed: EAS and Arweave code moved to @seedprotocol/eas and @seedprotocol/arweave. The SDK re-exports them, so its public API is unchanged. @seedprotocol/sdk/vite re-exports the plugin from @seedprotocol/vite.
  • Changed: ethers, globals and better-sqlite3 are no longer runtime dependencies.
  • Added: Seed gateway endpoint resolution for HTTPS and sidecar paths, VITE_ARWEAVE_HOST, and DEFAULT_SEED_FEED_HYPER_KEY.

@seedprotocol/publish

  • Breaking: thirdweb is optional. The main entry no longer exports thirdweb helpers or ConnectButton; import them from @seedprotocol/publish/thirdweb. The main entry's PublishProvider no longer wraps ThirdwebProvider.
  • Breaking: thirdwebClientId is optional, and rpcUrl is required when it's unset.
  • Breaking: SeedSigner no longer has sendTransaction. Use SeedTxSender or a PublishWallet ({ signer, txSender }), and wrap thirdweb accounts with fromThirdwebAccount.
  • Added: PublishWallet, fromEthersWallet, fromEip1193Provider, useSeedWallet, ensureWalletThenPublish, and createPermissionlessTxSender for gasless EIP-7702 publishing.
  • Fixed: in-app auth tokens sync when a managed wallet connects.

@seedprotocol/feed

  • Changed: depends on @seedprotocol/eas and @seedprotocol/arweave instead of the full SDK. Documents use feedUrl for their self-links, and the Arweave gateway comes from the environment.

On this page