Seed Protocol

@seedprotocol/arweave

Reference for @seedprotocol/arweave, the low-level Arweave gateway client: gateway selection and fallback, environment overrides, proxy and Hyper transports, raw reads, upload status checks, media reference helpers, Html link helpers and constants.

@seedprotocol/arweave is how Seed packages reach Arweave, where published files and long-form content live. It picks a gateway (an HTTP server that serves Arweave data), falls back to others when one is down, reads transaction data and tags, and checks whether an upload has landed. The SDK, @seedprotocol/query and @seedprotocol/feed use it, and the SDK re-exports most of it. Use it directly in a script or server that reads Arweave content without the SDK, or to inspect what the SDK resolved.

import { BaseArweaveClient, fetchArweaveRawTextAcrossGateways } from '@seedprotocol/arweave'
import { registerNodeArweavePlatform } from '@seedprotocol/arweave/node'

It doesn't upload: building, signing and sending uploads is @seedprotocol/publish's job. Permanence explains what's stored on Arweave and why.

Install

npm install @seedprotocol/arweave

It has no Seed dependencies and no peer dependencies. The SDK and @seedprotocol/query install it for you.

Entry points

ImportWhat it has
@seedprotocol/arweaveBaseArweaveClient, the fetch-based client DefaultArweaveClient, gateway resolution and fallback, raw-read and upload-status helpers, public gateway URLs, media reference helpers, Html link helpers, constants and types
@seedprotocol/arweave/noderegisterNodeArweavePlatform and the Node client NodeArweaveClient, which extends DefaultArweaveClient

There is no /react entry. DefaultArweaveClient works in browsers and Node; register it with BaseArweaveClient.configure(new DefaultArweaveClient()). The SDK registers it during client.init. It loads the arweave npm client only on first use, for the data fallback and createTransaction. The URL and gateway helpers that don't go through the registered client (getRawUrl, fetchArweaveRawTextAcrossGateways, resolveSeedGatewayEndpoints, …) work anywhere fetch does.

The default gateway and its fallbacks

Reads go to a primary gateway, arweave.net by default, and fall back through a list of public gateways:

const DEFAULT_ARWEAVE_HOST = 'arweave.net'
const DEFAULT_ARWEAVE_GATEWAYS = ['arweave.net', 'zigza.xyz', 'ar.seedprotocol.io']

A primary you set goes first, followed by each default not already in the list: with example.org as the primary, the order is example.org, arweave.net, zigza.xyz, ar.seedprotocol.io. Seed's own gateway is last; an app that wants it first sets arweaveDomain: 'ar.seedprotocol.io' (here through registerNodeArweavePlatform, or in the SDK's config, see Client and configuration). Under the SDK, arweaveDomain is also the host publish writes into Html links (Links in published content).

Environment variables

VariableEffect
ARWEAVE_READ_GATEWAYS, NEXT_PUBLIC_ARWEAVE_READ_GATEWAYSComma-separated hosts that replace DEFAULT_ARWEAVE_GATEWAYS as the fallback list
NEXT_PUBLIC_ARWEAVE_HOST, ARWEAVE_HOST, VITE_ARWEAVE_HOSTPrimary gateway host (first one set wins; https:// is stripped, http:// keeps plain HTTP). It overrides registerNodeArweavePlatform's arweaveDomain, but not BaseArweaveClient.setHost, and stops applying once a probe has picked a host

They're read from process.env only. In a browser build, VITE_ARWEAVE_HOST has no effect unless your bundler defines process.env. Under the SDK, client.init sets the host from arweaveDomain with setHost, so the host variables don't apply after init.

registerNodeArweavePlatform

Registers the Node client and sets the primary gateway.

function registerNodeArweavePlatform(options?: { arweaveDomain?: string }): void
NameTypeDefaultDescription
options.arweaveDomainstring'arweave.net'Primary gateway host. Plain hosts use HTTPS; prefix http:// for a plain HTTP gateway such as http://localhost:1984

Importing @seedprotocol/arweave/node registers the client as a side effect; calling the function also sets the primary. It doesn't lock the host, so the environment variables and the read probe can still change it. Call it before setHost, because it resets a host set earlier. Without a registered client, the transaction methods throw ArweaveClient not configured.

import { registerNodeArweavePlatform } from '@seedprotocol/arweave/node'
import { BaseArweaveClient, fetchArweaveRawTextAcrossGateways } from '@seedprotocol/arweave'

registerNodeArweavePlatform({ arweaveDomain: 'arweave.net' })

const txId = process.argv[2]!
const tags = await BaseArweaveClient.getTransactionTags(txId)
console.log(tags.find((t) => t.name === 'Content-Type')?.value)

const text = await fetchArweaveRawTextAcrossGateways(txId)
console.log(text?.slice(0, 200))

BaseArweaveClient

The process-wide gateway setting and the registered client, as static methods. ArweaveClient is the same class.

Gateway settings

MethodReturnsDescription
setHost(host)voidSets the gateway and locks it: the read probe won't change it and the host environment variables no longer apply. 'arweave.net', 'https://arweave.net' or 'http://localhost:1984'; a path prefix such as 'app.example.com/api/seed-gateway' is allowed
setPreferredReadGateway(host)voidSets the gateway without locking it
isReadGatewayLocked()booleanTrue after setHost
getHost()stringHost without scheme, possibly with port and path, e.g. arweave.net
getProtocol()'http' | 'https'
getBaseUrl()stringe.g. https://arweave.net
getRawUrl(txId)string{base}/raw/{txId}: the data as uploaded
getStatusUrl(txId)string{base}/{txId}
getEndpoint()string{base}/graphql
getGatewayPath()stringThe path prefix, or ''
getArweaveSdkHost(), getArweaveJsApiConfig()The host and { host, port, protocol } to pass to the arweave npm client's init(). Path prefixes are dropped
resolveGateway(){ protocol, host }The effective gateway, including the environment override
isConfigured()booleanTrue once something (the SDK, registerNodeArweavePlatform or your app) has registered a client. The transaction methods below throw until then

Transaction methods

These go through the registered client.

MethodSignatureDescription
getTransactionTags(txId: string) => Promise<TransactionTag[]>Tags through the gateway's GraphQL API. [] on error, after 30 seconds without an answer, or when not found
getTransactionStatus(txId: string) => Promise<TransactionStatus>Whether a gateway has the transaction: fetchTransactionStatusAcrossGateways with its defaults. confirmed is always null
getTransactionData(txId: string, options?: { decode?: boolean; string?: boolean }) => Promise<Uint8Array | string>The data. It first tries /raw/{txId} on each preferred read gateway in turn (10 seconds each), unless decode is false. When none serves it, it fetches getRawUrl with a path-prefixed gateway, throwing on a non-2xx status, and otherwise downloads through the arweave npm client. Decoded data goes through the content read transform when one is registered
downloadFiles(params: { transactionIds: string[]; excludedTransactions?: Set<string> }) => Promise<DownloadResult[]>Fetches each /raw/{id} in turn. Each result has transactionId, success, and contentType and data or error
createTransaction(data: string | Uint8Array, options?: { tags?: TransactionTag[] }) => Promise<ArweaveTransaction>An unsigned arweave transaction with the tags added. It doesn't sign or post it
getArweaveClient() => GraphQLClientA graphql-request client for getEndpoint(). With DefaultArweaveClient and NodeArweaveClient, each request aborts after 30 seconds (ARWEAVE_GRAPHQL_TIMEOUT_MS; Arweave GraphQL request timed out after 30000 ms), or when the caller's signal aborts

getTransactionTagsByIds

The tags of many transactions, in batched GraphQL requests, keyed by transaction ID. The SDK uses it to check downloads against their Content-SHA-256 tag.

function getTransactionTagsByIds(
  transactionIds: readonly string[],
  options?: { client?: GraphQLClient; batchSize?: number; onError?: (error: unknown) => void },
): Promise<Map<string, TransactionTag[]>>
NameTypeDefaultDescription
transactionIdsstring[]IDs to look up. Duplicates and blanks are dropped
options.clientGraphQLClientBaseArweaveClient.getArweaveClient()The gateway's GraphQL client
options.batchSizenumber100 (TRANSACTION_TAGS_BATCH_SIZE, also the maximum)IDs per request
options.onError(error: unknown) => voidCalled for a batch whose request failed

An ID the gateway doesn't know yet, such as a fresh upload that isn't indexed, is missing from the map. A failed batch is skipped, not thrown. The query is exported as GET_TRANSACTIONS_TAGS_BY_IDS.

When no preferred read gateway serves the data and the gateway has no path prefix, getTransactionData downloads through the arweave client's chunk API, which fails for bundled data items (how most uploads are stored today). It then logs [chunk] Failed to fetch chunk … and can return an empty result. Set preferred read gateways, or fetch getRawUrl(txId) or use fetchArweaveRawTextAcrossGateways instead.

One copy of the package

The registered client and the gateway settings live in module state, and assertSingleArweaveModule() throws DuplicateArweaveModuleError when the client was registered through another copy of this package; @seedprotocol/query calls it before every read. When two copies happen and how to fix it is in query's troubleshooting.

Gateway probing and fallback

A probe is GET {base}/info; a gateway is healthy when that returns a 2xx JSON object within 5 seconds, whether or not the caller passed a signal. A host that fails a probe is skipped for 60 seconds (a circuit breaker), then tried again. A probe the caller aborts resolves false and doesn't count as a failure.

ensureReadGatewaySelected

Probes the primary and then each fallback in order, and switches the gateway to the first healthy one.

function ensureReadGatewaySelected(signal?: AbortSignal): Promise<string>

It returns the selected host. The result is cached for 3 minutes (1 minute when nothing was healthy, in which case the last healthy host is kept). When signal aborts before a gateway passes, nothing is cached and it returns the last healthy host, else the current one. It does nothing while the host is locked with setHost.

fetchArweaveRawTextAcrossGateways

Reads a transaction's data as text, trying gateways until one returns a non-empty body.

function fetchArweaveRawTextAcrossGateways(
  transactionId: string,
  options?: { fallbackHosts?: readonly string[]; timeoutMs?: number },
): Promise<string | undefined>
NameTypeDefaultDescription
transactionIdstringThe Arweave transaction or data item ID
options.fallbackHostsstring[]the primary and fallback listHosts to try when no endpoints were resolved
options.timeoutMsnumber10000 (DEFAULT_ARWEAVE_RAW_READ_TIMEOUT_MS)Per-gateway timeout

It tries the preferred read gateways first, then runs ensureReadGatewaySelected (unless a sidecar or proxy is active) and tries the resolved and fallback gateways. Each body goes through the content read transform when one is registered; a gateway whose body can't be read, or whose transform throws, is skipped. The bytes are decoded as UTF-8, so a binary body comes back with U+FFFD in place of invalid sequences. It returns undefined when none had it. It never throws.

fetchArweaveRawBytesAcrossGateways

The same read as fetchArweaveRawTextAcrossGateways, with the same options, gateway order and errors, returning the body as bytes without decoding it.

function fetchArweaveRawBytesAcrossGateways(
  transactionId: string,
  options?: { fallbackHosts?: readonly string[]; timeoutMs?: number },
): Promise<Uint8Array | undefined>

The bytes are the transaction's data only while no content read transform is registered. With one registered it returns the transform's output, and there's no option to skip the transform, as getTransactionData's decode: false does. @seedprotocol/archive's exportArchive downloads Arweave content through it.

fetchTransactionStatusAcrossGateways

Checks whether a gateway has a transaction, trying gateways in order. BaseArweaveClient.getTransactionStatus calls it with the defaults.

function fetchTransactionStatusAcrossGateways(
  transactionId: string,
  options?: { timeoutMs?: number; totalTimeoutMs?: number; baseUrls?: readonly string[] },
): Promise<TransactionStatus>
NameTypeDefaultDescription
transactionIdstringThe Arweave transaction or data item ID
options.timeoutMsnumber10000 (DEFAULT_TRANSACTION_STATUS_TIMEOUT_MS)Per-gateway timeout
options.totalTimeoutMsnumber20000 (DEFAULT_TRANSACTION_STATUS_TOTAL_TIMEOUT_MS)Budget across all gateways. The first gateway is always asked; a later one only while the budget left is at least timeoutMs or MIN_TRANSACTION_STATUS_ATTEMPT_MS (1000), whichever is smaller. The last one gets only what's left
options.baseUrlsstring[]getArweaveReadBaseUrls(); while a sidecar or proxy is active, only the preferred read gateways and the sidecar or proxyBase URLs to try, in order. A data item the sidecar or proxy just accepted isn't on the public gateways yet, and their 404 would read as missing

Each attempt is GET {base}/{txId}, without downloading the body. Gateways whose circuit is open are skipped, unless every one is open, in which case all are tried. The first gateway to answer with a 2xx to 4xx status decides the result: any 2xx comes back as 200 (present), and a 404 is returned without asking the other gateways. A timeout, network error, 5xx or 429 counts as a failure against that gateway's circuit and moves on to the next. When no gateway answers, status is 504 if the last attempt timed out and otherwise that attempt's status (500 for a network error, or when there was nothing to try). confirmed is always null. It never throws.

Other read helpers

FunctionSignatureDescription
fetchArweaveRawFromBaseUrl(baseUrl: string, txId: string, options?: { timeoutMs?: number }) => Promise<Response | undefined>GET {baseUrl}/raw/{txId} with a timeout; undefined on error or non-2xx
getArweaveReadBaseUrls(options?: { fallbackHosts? }) => string[]The ordered base URLs reads try: preferred gateways, then the resolved ones. Doesn't probe
getResolvedArweaveReadBaseUrls(options?: { fallbackHosts? }) => string[]The same without the preferred gateways
ensureArweaveReadGatewayForFallback() => Promise<void>Runs the probe unless a sidecar or proxy is active. Never throws
probeGateway(baseUrl: string, options?: ProbeGatewayOptions) => Promise<boolean>One /info probe. After timeoutMs (default 5000) it fails. With recordCircuit (default true) it returns false without a request while the host's circuit is open, and records the result; with false it probes an open host and records nothing. A probe that signal aborts resolves false and records nothing. An AbortSignal passed as the second argument, which TypeScript refuses, rejects with a TypeError before any request; pass { signal }
selectFirstHealthyReadGateway(hosts: string[], protocol?, signal?) => Promise<string | null>The first host whose probe passes
isReadGatewayKnownHealthy, getLastHealthyReadGatewayHost, invalidateReadGatewayCacheInspect or reset the probe result
getArweaveReadGatewayHostsForPrimary(primary: string) => string[]Primary first, then each host of the env list, or of DEFAULT_ARWEAVE_GATEWAYS, not already in it
getDefaultArweaveReadGatewayHostsOrdered() => string[]The env list, or DEFAULT_ARWEAVE_GATEWAYS
mergePrimaryHostWithDefaults(primary: string, defaults: readonly string[]) => string[]Deduplicated, primary first
isKnownArweaveGatewayHostname(hostname: string) => booleanTrue for a host in KNOWN_ARWEAVE_GATEWAYS or a subdomain of one, and for *.arweave.net and *.ar-io.net
resolveArweaveHostFromEnv() => string | undefinedThe host from the environment variables

The circuit breaker is exported too (isGatewayHostCircuitOpen, getGatewayHostCircuitState, listOpenGatewayCircuitHosts, recordGatewayHostSuccess, recordGatewayHostFailure, beginGatewayHostHalfOpenProbe, cancelGatewayHostHalfOpenProbe (ends a half-open probe without a result, so the next caller can probe the host), normalizeGatewayHostKey, configureGatewayCircuitBreaker({ failureThreshold?, openDurationMs? })). The reset…ForTests functions are for tests only.

Experimental: content read transform

Groundwork for private publishing. A transform registered with setArweaveContentReadTransform sees content after it's fetched and before it's used, for example to decrypt it. Nothing in the Seed packages registers one, and the signature may change.

type ArweaveContent = { bytes: Uint8Array; contentType?: string }
type ArweaveContentReadContext = { transactionId: string }
type ArweaveContentReadTransform = (
  content: ArweaveContent,
  context: ArweaveContentReadContext,
) => ArweaveContent | Promise<ArweaveContent>

function setArweaveContentReadTransform(transform: ArweaveContentReadTransform | undefined): void
function hasArweaveContentReadTransform(): boolean
function transformArweaveContent(content: ArweaveContent, context: ArweaveContentReadContext): Promise<ArweaveContent>
function readArweaveContentResponse(response: Response, context: ArweaveContentReadContext): Promise<ArweaveContent>
function getResponseMediaType(response: Response): string | undefined

Reads that go through it: fetchArweaveRawTextAcrossGateways, fetchArweaveRawBytesAcrossGateways, BaseArweaveClient.getTransactionData (not with decode: false) and @seedprotocol/query's rich-text hydration (which then re-hosts the links in each body). Gateway URLs handed straight to the browser, such as SeedImage's progressive gatewayHref, media links and feed image URLs, don't, and neither does the SDK's browser download worker.

setPreferredArweaveReadBaseUrls

Gateways tried before all others for /raw reads, typically the one your app uploads through, which has a new upload before the public gateways do.

function setPreferredArweaveReadBaseUrls(urls: readonly string[]): void
function getPreferredArweaveReadBaseUrls(): string[]

Each entry is a base URL with scheme and any path, e.g. https://app.example.com/api/seed-gateway. A relative path such as /api/seed-gateway resolves against the page origin and is dropped where there is none (Node). Pass [] to clear. It doesn't change uploads, GraphQL or BaseArweaveClient's host. BaseArweaveClient.getTransactionData tries these gateways too. The SDK sets it from config.gateway.preferredReadBaseUrls.

Proxy and Hyper transports

Besides public HTTP gateways, an upload API and gateway can be reached over the Hyper network, either through a local sidecar (seed-gateway connect <operator key>, listening on http://127.0.0.1:1984) or through your app server (a proxy route built with createGatewayProxy from @seedprotocol/gateway-hyper). Gateway Hyper explains the setup.

resolveSeedGatewayEndpoints

Works out which gateway and upload API to use for a transport setting, probing the proxy or sidecar first.

function resolveSeedGatewayEndpoints(
  config: SeedGatewayConfig,
  options?: { signal?: AbortSignal; timeoutMs?: number; forceSidecarProbe?: boolean; origin?: string },
): Promise<ResolvedSeedGatewayEndpoints>
transportWhat it picks
'http-gateway' (default)The proxyBaseUrl if set (throws Gateway HTTP proxy not reachable if its probe fails), otherwise arweaveDomain (default arweave.net; an empty string means the default)
'hyper'A healthy proxyBaseUrl, otherwise the local sidecar. Throws Gateway Hyper sidecar not reachable at http://127.0.0.1:1984 if the sidecar doesn't answer
'hybrid'A healthy proxy, then a healthy sidecar, then arweaveDomain over HTTP (activePath: 'hybrid-fallback-http')

'hyper' probes the sidecar from wherever the code runs, so a web app on a public origin sends every visitor's browser a request to 127.0.0.1:1984, where most visitors run no sidecar. In a browser, 'hybrid' tries the sidecar only when the page's origin is loopback (localhost, 127.0.0.1, [::1] or *.localhost), or with hyper.probeSidecarFromBrowser: true, for a page that is meant to reach it, such as an Electron or desktop shell. hyper.probeSidecar: false skips the sidecar probe, and hyper.probeProxy turns the proxy's /info check on or off on its own (default: probeSidecar's value, else true); without that check, a set proxyBaseUrl is used even when it's down. A healthy probe result is cached for 3 minutes and a failed one for 60 seconds; forceSidecarProbe: true or invalidateSidecarProbeCache() clears them. timeoutMs is one budget for all of the resolve's probes (each probe still times out after 5 seconds on its own). A probe cut short by it or by signal counts as unreachable for that resolve only: it isn't cached or recorded in the circuit breaker. client.init passes no timeoutMs. A relative proxyBaseUrl needs options.origin outside a browser.

With a sidecar or proxy active, reads try it first and then the public gateway and the default list, so /raw reads and re-hosted links still work when it's down. Status checks ask only it and the preferred read gateways.

ResolvedSeedGatewayEndpoints:

FieldDescription
modeThe transport asked for
activePath'http', 'http-proxy', 'hyper-sidecar' or 'hybrid-fallback-http'
arweaveHost, arweaveProtocol, arweaveBaseUrl, arweaveGraphqlUrlThe gateway to use
uploadApiBaseUrlThe upload API. On the 'http' and 'hybrid-fallback-http' paths, the config's uploadApiBaseUrl, or '' when it's unset: a public gateway doesn't serve the upload API. On the sidecar and proxy paths, the sidecar's or proxy's base URL
publicArweaveBaseUrlThe public HTTP gateway for links written into published content, scheme://host from arweaveDomain (default https://arweave.net). The same as arweaveBaseUrl on the 'http' path; on the sidecar and proxy paths, whose hosts only this machine or app can reach, it differs
gatewayHyperKey, proxyBaseUrlEchoed when set

setResolvedSeedGatewayEndpoints and getResolvedSeedGatewayEndpoints store the result for the read helpers; the SDK calls the setter during init. getReadGatewayHostsForConfig(resolved) gives the read host list for a result (with a sidecar or proxy active, its base URL followed by the public gateway and the defaults as https:// URLs), resolveProxyBaseUrl(url, { origin? }) turns a relative proxy URL into an absolute one, and resolveSidecarOrigin(config) gives the sidecar's address as { host, port, baseUrl } from hyper.localSidecarHost and localSidecarPort, else 127.0.0.1:1984 (host leaves out port 80 and 443; baseUrl always has the port).

SeedGatewayConfig

interface SeedGatewayConfig {
  transport?: 'http-gateway' | 'hyper' | 'hybrid'
  arweaveDomain?: string
  uploadApiBaseUrl?: string
  proxyBaseUrl?: string
  preferredReadBaseUrls?: string[]
  hyper?: {
    gatewayHyperKey?: string
    localSidecarHost?: string // default '127.0.0.1'
    localSidecarPort?: number // default 1984
    probeSidecar?: boolean // default true
    probeProxy?: boolean // default probeSidecar, else true
    probeSidecarFromBrowser?: boolean // default false
  }
  // Shorthands for the hyper fields: gatewayHyperKey, localSidecarHost, localSidecarPort, probeSidecar
}

seedGatewayConfigFromSeedConfig(seedConfig) builds one from an SDK-style config (top-level arweaveDomain and uploadApiBaseUrl win over the gateway ones; probeProxy and probeSidecarFromBrowser are read from gateway.hyper only), and seedGatewayConfigFromClientContext(context) from the SDK client's context. Note that resolveSeedGatewayEndpoints reads only the nested hyper object; the shorthands are merged into it by seedGatewayConfigFromSeedConfig. The SDK's gateway options are described in Client and configuration.

Upload status helpers

Read-only checks on an upload made through an upload API (uploadApiBaseUrl) or a gateway. There's no Arweave testnet, so uploads go to mainnet and cost real AR even when its attestations are on OP Sepolia.

FunctionSignatureDescription
getUploadPipelineTransactionStatus(uploadApiBaseUrl: string, txOrDataItemId: string, options?: { timeoutMs?: number }) => Promise<TransactionStatus>GET {api}/api/upload/arweave/data/{id}: 200 when the upload API has it, 404 when not, 504 after the timeout (default 45 s), 500 on other errors
getArweaveUploadStatus(uploadApiBaseUrl: string, dataItemId: string) => Promise<ArweaveUploadStatusResponse | null>GET {api}/api/upload/arweave/status/{id}: the upload's phase, bundle and L1 confirmation state, or null
isArweaveL1AnchoringComplete(status: ArweaveUploadStatusResponse) => booleanTrue when l1.confirmed or l1.meetsMinConfirmations
queryArweaveGatewayTransaction(graphqlUrl: string, dataItemId: string, init?: { signal? }) => Promise<ArweaveGatewayTransactionQueryResult | null>The transaction's transactionId, bundledInId, blockHeight and blockTimestamp from a gateway's GraphQL API, or null on error
queryArweaveGatewayTransactionWithFallback(graphqlUrl: string | undefined, dataItemId: string, init?) => Promise<… | null>The same, trying the given gateway's host and then the fallbacks, skipping unhealthy ones
getUploadApiArweaveDataUrl, getUploadApiArweaveStatusUrl, normalizeUploadApiBaseUrlBuild the URLs above

The GraphQL query behind queryArweaveGatewayTransaction declares its ID as String!, and arweave.net rejects that with HTTP 400 (it expects ID!), so the function returns null there. Query the gateway's GraphQL API yourself if you need this from arweave.net.

Media references

Helpers for turning a stored value (an Arweave transaction ID, a Seed UID, a URL) into something you can display. The SDK re-exports them, with its own resolveMediaRef that also loads local files.

ExportSignatureDescription
classifyMediaRef(raw: string, options?: { treatAs?: 'arweaveTx' | 'seedUid' | 'url' }) => MediaRefClassification{ kind: 'url' } for http(s), blob: and data: URLs, 'nonMediaUrl' for EAS explorer pages, 'seedUid' for a 32-byte hex UID, 'seedLocalId' for a 10–21 character ID, 'arweaveTxId' for a 43-character ID, else 'unknown' or 'empty'
getArweaveUrlForTransaction(txId: string) => string{first read gateway}/raw/{txId}, the URL this reader loads from: the first of getArweaveReadBaseUrls(), so a preferred read gateway, sidecar or proxy comes first. The SDK and @seedprotocol/query re-export this function. Don't write its result into published content
getPublicArweaveBaseUrl, getPublicArweaveRawUrl() => string, (txId: string) => stringThe public gateway's base URL and {public base}/raw/{txId}, for URLs that other people will open, such as links in published content. The read gateway can be a local sidecar or your app's proxy. Before endpoints are resolved, they use the read gateway unless it's a loopback address or has a path prefix, such as an app proxy, else the host environment variable or https://arweave.net
normalizeFeedItemFields(item: Record<string, unknown>, manifest: FeedFieldManifest) => Record<string, NormalizedFeedFieldValue | undefined>Classifies a feed item's fields by role (image, audio, video, file, html, text)
getFeedItemStringField(item, key) => stringReads a field by its name, its snake_case or its camelCase form
resolveSeedRssImageRelationRef, resolveSeedRssImageRelationFromItemFind an image's storage transaction ID, URL or Seed UID in a Seed RSS item. A URL built from a transaction ID is getPublicArweaveRawUrl's. SEED_RSS_IMAGE_RELATION_FIELD_KEYS lists the fields tried
normalizeRelationPropertyValue, resolveSeedIdsFromRefStringRead a relation value as a seedLocalId or seedUid
isEasAttestationExplorerUrl(url: string) => booleanTrue for easscan attestation pages, which aren't media

In a React app, useNormalizedFeedItemFields classifies a feed item's fields and the SeedMedia* components resolve and render raw media strings.

Links into Arweave inside an Html value name a gateway. Seed readers re-host them onto their own read gateways before showing the Html, and publish puts them back on the public gateway before upload; Links in published content says which reader does what. These are the helpers that do it. The SDK's main entry re-exports rehostArweaveLinksInHtml, nextArweaveGatewayUrl, mapUrlAttribute, parseArweaveTransactionLink and the types ArweaveTransactionLink and HtmlArweaveRefs.

An Arweave link is the URL in a src, href or poster attribute, or a candidate in a srcset, when it is one of:

  • ar://<id>
  • /raw/<id> on any host, after any path prefix, so an app proxy's URL counts
  • /<id> on a known gateway: a host of a read base URL at a host's root, a host in KNOWN_ARWEAVE_GATEWAYS, a subdomain of either, *.arweave.net or *.ar-io.net
  • {base}/<id> under a read base URL with a path prefix, such as https://app.example.com/api/seed-gateway/<id>. Such a base doesn't make /<id> on its host's root a link

<id> is a 43-character transaction ID, and whatever follows it (path, query, hash) is kept. A bare /<id> on any other host isn't treated as a link, because the path shape is common elsewhere. URLs in other attributes or in text are left alone.

FunctionSignatureDescription
rehostArweaveLinksInHtml(html: string, options?: ArweaveLinkOptions) => stringMoves each Arweave link onto the first of options.baseUrls (default getArweaveReadBaseUrls()), keeping its form: /raw/<id> stays /raw/<id>, /<id> stays /<id>, and ar://<id> becomes /raw/<id>. The first base URL can be a sidecar or proxy that only this reader reaches, so don't store or upload the result
publishedArweaveLinksInHtml(html: string, options: { to?: HtmlArweaveRefs; publicBaseUrl: string; baseUrls?: readonly string[] }) => stringThe reverse, for upload: each Arweave link on publicBaseUrl with its form kept, or ar://<id> with to: 'ar' (default 'gateway'). publicBaseUrl and baseUrls (default the read list) count as known gateways. Publish runs it on each Html file it uploads
nextArweaveGatewayUrl(url: string, options?: ArweaveLinkOptions) => string | undefinedThe same link on the base URL after the one it's on, for retrying a failed load. A URL is on the base with the longest matching path, so a link under a proxy on https://app.example.com/api/seed-gateway moves past a root base on the same host. A link on none of the base URLs, such as ar://<id>, moves to the first; after the last, or for a URL that isn't an Arweave link, it's undefined. SeedHtml's load failover uses it
parseArweaveTransactionLink(url: string, options?: ArweaveLinkOptions) => ArweaveTransactionLink | undefinedSplits an Arweave link into its ID, form and rest; undefined for any other URL. options.baseUrls (default the read list) adds known gateways: a root base adds its host, a base with a path prefix adds that prefix
buildArweaveTransactionLink(link: ArweaveTransactionLink, baseUrl: string) => stringlink on baseUrl (scheme, host and any path prefix)
rewriteHtmlAttributes(html: string, rewrite: (element, attrName: string, value: string) => string | undefined) => stringCalls rewrite with each attribute's decoded value, including inside <template>, and writes each value it returns in place as name="value". Every other byte of html stays as written. element is a parse5 DefaultTreeAdapterTypes.Element
mapUrlAttribute(attrName: string, value: string, map: (url: string) => string | undefined) => string | undefinedRuns map on the URL in a src, href or poster value, or on each candidate of a srcset (descriptors kept). undefined for other attributes or when nothing changes
type ArweaveTransactionLink = {
  transactionId: string
  form: 'raw' | 'bare' // `/raw/<id>` (and `ar://<id>`), or `/<id>`
  rest: string // path, query and hash after the ID, e.g. '/index.html' or '?v=2'
}
type ArweaveLinkOptions = { baseUrls?: readonly string[] }
type HtmlArweaveRefs = 'gateway' | 'ar'
import { publishedArweaveLinksInHtml, rehostArweaveLinksInHtml } from '@seedprotocol/arweave'

const id = 'Xb1kQ3t9mZp4Rr8sVw2yLc6nHf0jDg5aEu7iOq1xYzM'
const html = `<img src="ar://${id}"><a href="https://ar.seedprotocol.io/${id}">file</a>`

console.log(rehostArweaveLinksInHtml(html, { baseUrls: ['https://zigza.xyz'] }))
// <img src="https://zigza.xyz/raw/Xb1k…"><a href="https://zigza.xyz/Xb1k…">file</a>

console.log(publishedArweaveLinksInHtml(html, { to: 'ar', publicBaseUrl: 'https://arweave.net' }))
// <img src="ar://Xb1k…"><a href="ar://Xb1k…">file</a>

Constants

ConstantValue
DEFAULT_ARWEAVE_HOST'arweave.net'
DEFAULT_ARWEAVE_GATEWAYS['arweave.net', 'zigza.xyz', 'ar.seedprotocol.io']
KNOWN_ARWEAVE_GATEWAYS['arweave.net', 'zigza.xyz', 'ar.seedprotocol.io', 'permagate.io']: the defaults plus hosts whose /<id> links still parse and get re-hosted. Nothing reads from a host that is only in this list
DEFAULT_ARWEAVE_GRAPHQL_URL'https://arweave.net/graphql'
ARWEAVE_HOST_ENV_KEYS['NEXT_PUBLIC_ARWEAVE_HOST', 'ARWEAVE_HOST', 'VITE_ARWEAVE_HOST']
DEFAULT_GATEWAY_SIDECAR_HOST, DEFAULT_GATEWAY_SIDECAR_PORT'127.0.0.1', 1984
DEFAULT_ARWEAVE_RAW_READ_TIMEOUT_MS10000
ARWEAVE_GRAPHQL_TIMEOUT_MS30000
DEFAULT_TRANSACTION_STATUS_TIMEOUT_MS, DEFAULT_TRANSACTION_STATUS_TOTAL_TIMEOUT_MS, MIN_TRANSACTION_STATUS_ATTEMPT_MS10000, 20000, 1000
DEFAULT_SEED_GATEWAY_HYPER_KEY, DEFAULT_SEED_FEED_HYPER_KEY''
GET_TRANSACTION_TAGS, GET_ARWEAVE_TRANSACTIONS, GET_TRANSACTIONS_TAGS_BY_IDSGraphQL documents for a gateway's API
TRANSACTION_TAGS_BATCH_SIZE100

DEFAULT_SEED_GATEWAY_HYPER_KEY and DEFAULT_SEED_FEED_HYPER_KEY are empty strings, so there's no built-in key to fall back on: to reach a gateway over Hyper, pass an operator key to a sidecar or proxy. See Gateway Hyper.

Types

TypeDescription
TransactionStatus{ status: number; confirmed: { block_height; block_indep_hash; number_of_confirmations } | null }
TransactionTag{ name: string; value: string }
GetDataOptions{ decode?: boolean; string?: boolean }
DownloadFilesParams, DownloadResultArguments and results of downloadFiles
CreateTransactionOptions{ tags?: TransactionTag[] }, for createTransaction
ArweaveTransactionThe arweave package's Transaction, which createTransaction returns. Declared members are checked, such as id and addTag. Its base class has an index signature, so reading an undeclared property compiles and is any
SeedGatewayConfig, SeedGatewayHyperConfig, SeedGatewayTransportMode, SeedGatewayActivePath, ResolvedSeedGatewayEndpoints, ResolveSeedGatewayEndpointsOptionsGateway resolution
ProbeGatewayOptions{ signal?: AbortSignal; timeoutMs?: number; recordCircuit?: boolean }, for probeGateway
SeedConfigGatewayInput, ClientGatewayContextInputInputs to the two seedGatewayConfigFrom… functions
ArweaveReadBaseUrlOptions{ fallbackHosts?: readonly string[] }
TransactionStatusFetchOptionsThe options of fetchTransactionStatusAcrossGateways
ArweaveUploadStatusResponse, ArweaveGatewayTransactionQueryResult, GetUploadPipelineTransactionStatusOptionsUpload status
GatewayCircuitState'closed' | 'open' | 'half-open'
MediaRefClassification, ClassifyMediaRefOptions, ResolveMediaRefResult, ResolveMediaRefOptions, FeedFieldManifest, FeedFieldDescriptor, FeedFieldRole, NormalizedFeedFieldValue, NormalizedMediaField, NormalizedHtmlField, NormalizedTextField, SeedRssImageRelationRefMedia references
ArweaveTransactionLink, ArweaveLinkOptions, HtmlArweaveRefsHtml links
IArweaveClientThe interface a platform client implements, for BaseArweaveClient.configure
ArweaveContent, ArweaveContentReadContext, ArweaveContentReadTransformThe content read transform (experimental)

On this page