{"_id":"@ardrive/turbo-upload","_rev":"4-29be2211c8b2f140a319e0f3c3cdbc4a","name":"@ardrive/turbo-upload","dist-tags":{"latest":"0.3.0"},"versions":{"0.1.0":{"name":"@ardrive/turbo-upload","version":"0.1.0","keywords":["arweave","ans-104","ans104","turbo","ardrive","data-item","bundle","zero-dependency","upload"],"author":{"name":"Permanent Data Solutions, Inc."},"license":"Apache-2.0","_id":"@ardrive/turbo-upload@0.1.0","maintainers":[{"name":"vilenarios","email":"vilenarios@gmail.com"},{"name":"ariel_at_ardrive","email":"ariel@ardrive.io"}],"homepage":"https://github.com/ardriveapp/turbo-upload#readme","bugs":{"url":"https://github.com/ardriveapp/turbo-upload/issues"},"dist":{"shasum":"f7205d2c8fee837b4d9240d4228363a28c86e4cc","tarball":"https://registry.npmjs.org/@ardrive/turbo-upload/-/turbo-upload-0.1.0.tgz","fileCount":19,"integrity":"sha512-AJXrCnbj4O8ZLflngDuyIlUD4mbMTuFXOcI3nvFud3Wr9yuszn9z0l01sbHq9sOkCxc0gAVqCy53eflPgu6Wiw==","signatures":[{"sig":"MEUCICThXO5VPU2idRlEvIWAT4GULW8UUPhUO7O9G18MuJkVAiEAtAFOodFHPlOqQ+cQc1EgyBiAe6fuS2P6cA59ihgOz0s=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":380525},"main":"index.js","types":"index.d.ts","engines":{"node":">=18.17.0"},"exports":{".":{"types":"./index.d.ts","default":"./index.js"},"./package.json":"./package.json"},"gitHead":"e186f92bdf87b154f95ec159b889ba49aa1bd29f","scripts":{"test":"node --test test/*.test.js","test:live":"node scripts/live-roundtrip.js","test:conformance":"node --test test/conformance.test.js"},"_npmUser":{"name":"vilenarios","email":"vilenarios@gmail.com"},"repository":{"url":"git+https://github.com/ardriveapp/turbo-upload.git","type":"git"},"_npmVersion":"10.9.2","description":"Zero-dependency ANS-104 data-item signing and Turbo upload for Arweave JWKs. Server-side Node, no wallet connectors, no CLI.","directories":{},"_nodeVersion":"23.9.0","dependencies":{},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{},"_npmOperationalInternal":{"tmp":"tmp/turbo-upload_0.1.0_1788289655952_0.9701963694554814","host":"s3://npm-registry-packages-npm-production"}},"0.2.0":{"name":"@ardrive/turbo-upload","version":"0.2.0","keywords":["arweave","ans-104","ans104","turbo","ardrive","data-item","bundle","zero-dependency","upload"],"author":{"name":"Permanent Data Solutions, Inc."},"license":"Apache-2.0","_id":"@ardrive/turbo-upload@0.2.0","maintainers":[{"name":"vilenarios","email":"vilenarios@gmail.com"},{"name":"ariel_at_ardrive","email":"ariel@ardrive.io"}],"homepage":"https://github.com/ardriveapp/turbo-upload#readme","bugs":{"url":"https://github.com/ardriveapp/turbo-upload/issues"},"dist":{"shasum":"5cfc131d1b72c3f0f685b4917a5f10b978c69ebe","tarball":"https://registry.npmjs.org/@ardrive/turbo-upload/-/turbo-upload-0.2.0.tgz","fileCount":20,"integrity":"sha512-kste8aUCHCak8VKyloyAeZWgHInIMNHLe8HLpgIegNTDBHK2/lsEr6MB7A+GSHvR5rdTm8YNu+WivgoV3qb4sw==","signatures":[{"sig":"MEQCIEh2c881mGe0qAF07JRVg2RezTHMWO2cG8FcXhfwdwMiAiAIe+V9vqfdFrMcs/l/Oik6lpUGK2+VGXpOUo4mAvnQpQ==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@ardrive%2fturbo-upload@0.2.0","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":398083},"main":"index.js","types":"index.d.ts","engines":{"node":">=18.17.0"},"exports":{".":{"types":"./index.d.ts","default":"./index.js"},"./package.json":"./package.json"},"gitHead":"faccec9555f969da1e903d14eeab5e71d9d9263e","scripts":{"test":"node --test test/*.test.js","test:live":"node scripts/live-roundtrip.js","test:conformance":"node --test test/conformance.test.js"},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:9d2d988d-f467-46c6-a749-fdf96216b87d"}},"repository":{"url":"git+https://github.com/ardriveapp/turbo-upload.git","type":"git"},"_npmVersion":"11.19.1","description":"Zero-dependency ANS-104 data-item signing and Turbo upload for Arweave JWKs. Server-side Node, no wallet connectors, no CLI.","directories":{},"_nodeVersion":"24.20.0","dependencies":{},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{},"_npmOperationalInternal":{"tmp":"tmp/turbo-upload_0.2.0_1789007763257_0.21703699949815292","host":"s3://npm-registry-packages-npm-production"}},"0.2.1":{"name":"@ardrive/turbo-upload","version":"0.2.1","keywords":["arweave","ans-104","ans104","turbo","ardrive","data-item","bundle","zero-dependency","upload"],"author":{"name":"Permanent Data Solutions, Inc."},"license":"Apache-2.0","_id":"@ardrive/turbo-upload@0.2.1","maintainers":[{"name":"vilenarios","email":"vilenarios@gmail.com"},{"name":"ariel_at_ardrive","email":"ariel@ardrive.io"}],"homepage":"https://github.com/ardriveapp/turbo-upload#readme","bugs":{"url":"https://github.com/ardriveapp/turbo-upload/issues"},"dist":{"shasum":"641c38ca087f35f0af514904225d96bd4e8f1972","tarball":"https://registry.npmjs.org/@ardrive/turbo-upload/-/turbo-upload-0.2.1.tgz","fileCount":20,"integrity":"sha512-KYrkzIYa2glhpG6a9wF2Re0Ud4MM5K/14bRYO2qI2vgPy9wU1R3nRAmeZKs7i4JBOhd9Mrjh3Z95I3h5JEN2Xw==","signatures":[{"sig":"MEYCIQDZiO0baNusxhHC5uzCDQY1qhAh9Om3QXU6p5TFCtK7WQIhANflQaNDyYvQhwcF+QFkePUwWSwDia/iso0+6p/tfim5","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@ardrive%2fturbo-upload@0.2.1","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":398063},"main":"index.js","types":"index.d.ts","engines":{"node":">=18.17.0"},"exports":{".":{"types":"./index.d.ts","default":"./index.js"},"./package.json":"./package.json"},"gitHead":"ba86fc2e94812142a82b59f6818e18dedbfbed93","scripts":{"test":"node --test test/*.test.js","test:live":"node scripts/live-roundtrip.js","test:conformance":"node --test test/conformance.test.js"},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:9d2d988d-f467-46c6-a749-fdf96216b87d"}},"repository":{"url":"git+https://github.com/ardriveapp/turbo-upload.git","type":"git"},"_npmVersion":"11.19.1","description":"Zero-dependency ANS-104 data-item signing and Turbo upload for Arweave JWKs. Server-side Node, no wallet connectors, no CLI.","directories":{},"_nodeVersion":"24.20.0","dependencies":{},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{},"_npmOperationalInternal":{"tmp":"tmp/turbo-upload_0.2.1_1789130471727_0.8618978331026514","host":"s3://npm-registry-packages-npm-production"}},"0.3.0":{"name":"@ardrive/turbo-upload","version":"0.3.0","description":"Zero-dependency ANS-104 data-item signing and Turbo upload, for Arweave JWKs and Solana keys. Server-side Node, no wallet connectors, no CLI.","keywords":["arweave","ed25519","solana","ans-104","ans104","turbo","ardrive","data-item","bundle","zero-dependency","upload"],"license":"Apache-2.0","main":"index.js","types":"index.d.ts","exports":{".":{"types":"./index.d.ts","default":"./index.js"},"./package.json":"./package.json"},"engines":{"node":">=18.17.0"},"scripts":{"test":"node --test test/*.test.js","test:conformance":"node --test test/conformance.test.js","test:live":"node scripts/live-roundtrip.js"},"dependencies":{},"devDependencies":{},"publishConfig":{"access":"public"},"author":{"name":"Permanent Data Solutions, Inc."},"repository":{"type":"git","url":"git+https://github.com/ardriveapp/turbo-upload.git"},"homepage":"https://github.com/ardriveapp/turbo-upload#readme","bugs":{"url":"https://github.com/ardriveapp/turbo-upload/issues"},"gitHead":"372c0f2122dcd477e9406ac88d6fd011d675233a","_id":"@ardrive/turbo-upload@0.3.0","_nodeVersion":"24.20.0","_npmVersion":"11.19.1","dist":{"integrity":"sha512-QzfAHhVOU6YXqMWLSVowooTS0Oa0brWzo6YQK3ZxicbqZkknkQJTgjqdf/X6tzYnynq3fRXeJURPwp6Hyle8HA==","shasum":"1a6e08df9b1e74aa52d350bb01e1c2af8033e459","tarball":"https://registry.npmjs.org/@ardrive/turbo-upload/-/turbo-upload-0.3.0.tgz","fileCount":23,"unpackedSize":420729,"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@ardrive%2fturbo-upload@0.3.0","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEUCIQC6rx731oDUenpdbURW/uNFEdijx+zZCATctbj5di42nAIgAVOAgBBLeRZ8dnXzBFYRDwDaNSUJ82e9LXvGTCTAd9Q="}]},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:9d2d988d-f467-46c6-a749-fdf96216b87d"}},"directories":{},"maintainers":[{"name":"vilenarios","email":"vilenarios@gmail.com"},{"name":"ariel_at_ardrive","email":"ariel@ardrive.io"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/turbo-upload_0.3.0_1789143853341_0.3192709384936099"},"_hasShrinkwrap":false}},"time":{"created":"2026-09-01T19:07:35.710Z","modified":"2026-09-11T16:24:13.854Z","0.1.0":"2026-09-01T19:07:36.206Z","0.2.0":"2026-09-10T02:36:03.403Z","0.2.1":"2026-09-11T12:41:11.882Z","0.3.0":"2026-09-11T16:24:13.478Z"},"bugs":{"url":"https://github.com/ardriveapp/turbo-upload/issues"},"author":{"name":"Permanent Data Solutions, Inc."},"license":"Apache-2.0","homepage":"https://github.com/ardriveapp/turbo-upload#readme","keywords":["arweave","ed25519","solana","ans-104","ans104","turbo","ardrive","data-item","bundle","zero-dependency","upload"],"repository":{"type":"git","url":"git+https://github.com/ardriveapp/turbo-upload.git"},"description":"Zero-dependency ANS-104 data-item signing and Turbo upload, for Arweave JWKs and Solana keys. Server-side Node, no wallet connectors, no CLI.","maintainers":[{"name":"vilenarios","email":"vilenarios@gmail.com"},{"name":"ariel_at_ardrive","email":"ariel@ardrive.io"}],"readme":"# @ardrive/turbo-upload\n\nSign [ANS-104](https://github.com/ArweaveTeam/arweave-standards/blob/master/ans/ANS-104.md)\ndata items with an Arweave JWK and upload them to a Turbo upload service.\n\n**Zero runtime dependencies.** Not \"few\". Zero. `dependencies`,\n`peerDependencies` and `optionalDependencies` are all empty, and a test in the\nshipped suite fails the build if that ever changes. The only things it imports\nare `node:crypto` and `node:buffer`.\n\n```bash\nnpm install @ardrive/turbo-upload\n```\n\nRunnable examples: [`examples/`](examples/).\n\n```js\nimport { TurboUpload, TESTNET } from \"@ardrive/turbo-upload\";\n\n// Testnet, so this costs nothing and nothing it writes is permanent.\n// Drop `uploadUrl` and `paymentUrl` to talk to production, where uploads are\n// permanent, public, and paid for out of the wallet you signed with.\nconst client = new TurboUpload({\n  jwk: process.env.ARWEAVE_JWK,\n  uploadUrl: TESTNET.uploadUrl,\n  paymentUrl: TESTNET.paymentUrl,\n});\n\nconst { id, winc } = await client.upload({\n  data: Buffer.from(\"hello permanence\"),\n  tags: [{ name: \"Content-Type\", value: \"text/plain\" }],\n});\n// -> https://ar-io.dev/<id>   (production reads from https://turbo-gateway.com/<id>)\n```\n\n---\n\n## Why this exists\n\n`@ardrive/turbo-sdk` is the full-featured client, and it is the right choice\nfor most things. It carries multi-chain signing, wallet connectors, payments\nand a CLI, and that costs a dependency tree:\n\n| installed on its own | lockfile entries | on disk | `npm audit` |\n|---|---|---|---|\n| `@ardrive/turbo-sdk@1.43.0` | 784 | 895 MB | 58 advisories, 3 critical, 9 high |\n| `@ardrive/turbo-upload@0.3.0` | 1 | 488 KB | none |\n\nMeasured 2026-09-11 into an empty project with `npm install` and `npm audit`.\nRe-run it rather than trusting this table: the numbers move as either tree\nchanges, and the point is the shape, not the digits.\n\nWhen you are adding Arweave storage to *someone else's* server, that tree is\nwhat a dependency review rejects, and those three criticals are what it asks\nabout first. This package does one thing completely, with nothing else in it.\n\n## What it does\n\n- Signs ANS-104 data items with an Arweave JWK (signature type 1, RSA-4096 / RSA-PSS-SHA256)\n- Uploads them to a Turbo upload service (`POST /v1/tx`)\n- Prices uploads, reads your credit balance, reads service info\n- Verifies data items, including a strict mode most implementations do not have\n\n## Use `@ardrive/turbo-sdk` instead if you need\n\nEthereum, KYVE or Polygon keys · a browser build or an\ninjected wallet · buying credits, promo codes, any payment flow · the CLI,\nfolder uploads, or ArDrive drive abstractions · packing your own bundles ·\nstreaming very large files.\n\nThis package signs one Arweave JWK and uploads bytes. If that is your case, it\nbrings nothing with it.\n\n---\n\n## Solana keys\n\n```js\nimport { TurboUpload } from \"@ardrive/turbo-upload\";\n\nconst client = TurboUpload.production({ jwk: process.env.SOLANA_SECRET_KEY, token: \"solana\" });\n```\n\nTakes any form a Solana user actually holds: a base58 secret key as Phantom exports it, the JSON array `solana-keygen` writes, raw 64 bytes, or a bare 32-byte seed. `client.address` is the base58 Solana address.\n\nA 64-byte key carries its own public key, and that half is checked rather than trusted. A key whose halves disagree is refused at construction, because signing with one produces items that verify nowhere and you find out after paying.\n\nThis is **ANS-104 signature type 4**, the same type `@ardrive/turbo-sdk` uses for `token: \"solana\"`, so the two produce identical ids for identical content. Type 4 signs the hex encoding of the signature data rather than the bytes; the library does that for you, and a test asserts it, because signing the raw bytes yields a valid signature over the wrong message.\n\n## Coming from `@ardrive/turbo-sdk`\n\nThe realistic reader already has turbo-sdk wired into a server and wants one\nupload path out of it. The call maps directly:\n\n```js\n// before\nimport { TurboFactory } from \"@ardrive/turbo-sdk\";\nconst turbo = TurboFactory.authenticated({ privateKey: jwk });\nconst { id } = await turbo.uploadFile({\n  fileStreamFactory: () => Readable.from(buffer),\n  fileSizeFactory: () => buffer.length,\n  dataItemOpts: { tags },\n});\n\n// after\nimport { TurboUpload } from \"@ardrive/turbo-upload\";\nconst client = new TurboUpload({ jwk });\nconst { id } = await client.upload({ data: buffer, tags });\n```\n\nWhat changes beyond the call:\n\n| turbo-sdk | here |\n|---|---|\n| `TurboFactory.authenticated({ privateKey })` | `new TurboUpload({ jwk })`, and a bad key throws at construction rather than at first upload |\n| stream factories | a `Buffer`, `Uint8Array` or string. There is no streaming |\n| `getBalance()` returns a signed-in account | `getBalance()` returns zeros for an unknown wallet, because the service answers `404` |\n| errors arrive as `fetch failed` | typed errors that name the endpoint, the status and the method |\n| any supported token | Arweave JWKs and Solana keys. Anything else throws at construction |\n\n**Keep turbo-sdk** for the cases in § Use `@ardrive/turbo-sdk` instead if you need. Nothing stops both being\ninstalled; they share no state.\n\n## API\n\n### `new TurboUpload(options)`\n\n| option | default | notes |\n|---|---|---|\n| `jwk` | *required* | Arweave JWK, **an object or a JSON string** |\n| `uploadUrl` | `https://upload.ardrive.io` | |\n| `paymentUrl` | `https://payment.ardrive.io` | |\n| `timeoutMs` | `60000` | **per request, not per call**. See § Bounding a call |\n| `retry` | `{retries:3, minDelayMs:500, maxDelayMs:8000, retryStatuses:[408,429,500,502,503,504]}` | **partial**: override one field, keep the rest |\n| `token` | `\"arweave\"` | or `\"solana\"`. Anything else throws immediately |\n| `fetch` | global `fetch` | injectable for tests and proxies |\n\nEverything is validated **in the constructor**, so a bad key is a startup error\nthat names the problem.\n\n**An option this package does not recognise is an error, not something it\nignores.** That holds for every method that takes an options object, and it\nexists because the two typos that hide are both expensive:\n\n```js\nnew TurboUpload({ jwk, uploadServiceUrl: TESTNET.uploadUrl });\n// TurboConfigError: new TurboUpload: unknown option `uploadServiceUrl`\n//   (did you mean `uploadUrl`?). Accepted: jwk, uploadUrl, paymentUrl, ...\n\nclient.sign({ data, tag: [{ name: \"Chain-Id\", value: \"1\" }] });\n// TurboValidationError: sign(): unknown option `tag` (did you mean `tags`?)\n```\n\nSilently dropped, the first leaves the client on production, so data meant for\na throwaway testnet is written permanently and billed for. The second uploads\nan item with no tags, which no tag query will find again. Neither shows up in\nthe return value.\n\n```js\nconst { TurboUpload } = require(\"@ardrive/turbo-upload\");\n\n// Use the helpers rather than spreading TESTNET or PRODUCTION: those records\n// also carry `name` and `gatewayUrl`, which are not constructor options.\nconst client = TurboUpload.testnet({ jwk, timeoutMs: 30_000, retry: { retries: 5 } });\n```\n\n#### Bounding a call\n\n`timeoutMs` applies to each HTTP attempt, and `retry` runs up to `retries` more\nof them. **They multiply.** With the defaults, one `upload()` can take:\n\n```\n(retries + 1) × timeoutMs + backoff\n(3 + 1)       × 60_000    + ~15s     ≈ 4 minutes 7 seconds\n```\n\nThere is deliberately no total-deadline option, because the right bound depends\non the caller. In a request handler or any path a user is waiting on, set one:\n\n```js\nconst bounded = AbortSignal.any([shutdownSignal, AbortSignal.timeout(20_000)]);\nawait client.upload({ data, tags, signal: bounded });\n```\n\nLower `timeoutMs` alone is not enough: it shortens each attempt, not the call.\n\n### `await client.upload({ data, tags?, target?, anchor?, signal?, timeoutMs? })`\n\nSigns and uploads. Returns the service response plus `id`, `owner` and\n`byteCount`. The returned `id` is **checked against the id computed locally from\nour own signature**, and a mismatch throws rather than handing back an id you did\nnot produce.\n\n### `client.sign({ data, tags?, target?, anchor? })` → `{ binary, id, idB64Url, signature }`\n\nSigns without uploading.\n\n### `await client.uploadSigned(item, { signal?, timeoutMs? })`\n\nUploads bytes already produced by `sign()`.\n\n> **Use this, not `sign()` + `upload()`.** RSA-PSS draws a fresh random salt per\n> signature, so signing the same payload twice yields **different bytes and a\n> different id**. `upload()` signs internally; calling it after `sign()` signs a\n> second time and the id you printed is not the id that landed.\n\n```js\nconst item = client.sign({ data, tags });\nawait recordInMyDatabase(item.idB64Url);   // the id, before it exists on-chain\nawait client.uploadSigned(item);           // the same bytes, same id\n```\n\n### `await client.getUploadCost(bytes)` → `{ winc, adjustments }`\n\nPrice in winston credits for a raw byte count. A signed item is ~1044 bytes\nlarger than its payload, so price `item.binary.length`, not your payload length.\n\n### `await client.getBalance()` → `{ winc, controlledWinc, effectiveBalance, address }`\n\nA wallet the payment service has never seen answers `404 User Not Found`. That\nis a zero balance, not an error, and is normalised to zeros.\n\n### `await client.getInfo()` / `await client.getFreeUploadLimitBytes()`\n\nService info, including the free-upload threshold, **107,520 bytes** when this\nwas written. Read it from `/v1/info` rather than from this page: it is service\npolicy and it changes. Items at or below it upload with no credit\nbalance at all.\n\n### `client.verify(binary, { strictSaltLength? })` → `boolean`\n\nStructural and cryptographic verification. § The PSS salt length, and why it is\n478 explains what `strictSaltLength` catches.\n\n### Low-level exports\n\n`signDataItem` · `verifyDataItem` · `createDataItem` · `parseDataItem` ·\n`getSignatureData` · `deepHash` · `serializeTags` · `deserializeTags` ·\n`signMessage` · `verifyMessage` · `idFromSignature` · `parseJwk` ·\n`ownerFromJwk` · `addressFromOwner` · `publicKeyFromOwner`\n\nConstants: `MAX_TAG_BYTES` (4096) · `MIN_ITEM_SIZE` (1044) ·\n`PSS_SALT_LENGTH_BYTES` (478) · `SIGNATURE_TYPE_ARWEAVE` (1) · `PRODUCTION` ·\n`TESTNET` · `DEFAULT_TIMEOUT_MS` · `DEFAULT_RETRY`.\n\n### Types\n\nHand-written `index.d.ts`, so no `typescript` dependency, no `@types/*`.\n\n```ts\nimport type { Tag } from \"@ardrive/turbo-upload\";\n// Tag is { name: string; value: string }\n```\n\n`Tag` is exported deliberately: in `@ardrive/turbo-sdk` you have to go read a\nthird-party package's `.d.ts` to learn the shape.\n\n### Errors\n\nEvery error extends `TurboError` and carries its context.\n\n| class | when | carries |\n|---|---|---|\n| `TurboKeyError` | bad/missing/non-RSA-4096 JWK | none |\n| `TurboConfigError` | bad option or unsupported token | none |\n| `TurboValidationError` | bad arguments to a call | none |\n| `TurboNetworkError` | no response at all (DNS, TLS, reset) | `endpoint`, `method`, `cause` |\n| `TurboTimeoutError` | exceeded `timeoutMs`, or caller aborted | `endpoint`, `timeoutMs`, `cause` |\n| `TurboHTTPError` | non-2xx | `status`, `endpoint`, `method`, `body` |\n| `TurboPaymentError` | `402`, the wallet cannot pay. A `TurboHTTPError`, so existing catches still work | `status`, `endpoint`, `method`, `body` |\n| `TurboVerificationError` | the service returned an id we did not sign | `expectedId`, `receivedId` |\n\n```\nTurboHTTPError: POST https://upload.ardrive.io/v1/tx failed: HTTP 402 Payment Required\n  {\"error\":\"Insufficient balance\"}\n```\n\nrather than a bare `fetch failed`.\n\n---\n\n## Testing against it\n\nThe constructor validates eagerly, so a placeholder key will not work. Generate\na throwaway one, and inject `fetch`:\n\n```js\nconst { generateKeyPairSync } = require(\"node:crypto\");\nconst { TurboUpload } = require(\"@ardrive/turbo-upload\");\n\nconst jwk = generateKeyPairSync(\"rsa\", { modulusLength: 4096 })\n  .privateKey.export({ format: \"jwk\" });\n\nconst fetchStub = async (url, init) =>\n  new Response(JSON.stringify({ id: \"…\", winc: \"0\" }), {\n    status: 200,\n    headers: { \"content-type\": \"application/json\" },\n  });\n\nconst client = new TurboUpload({ jwk, fetch: fetchStub });\n```\n\n**Two things worth knowing before you write the stub.** `uploadSigned` checks\nthat the id the service returned matches the one it computed locally, so a stub\nreturning a fixed id fails with `TurboVerificationError` rather than the thing\nyou were testing. Return the id of what was actually POSTed. And signing an\nRSA-4096 key takes tens of milliseconds, so generate it once per suite, not per\ntest.\n\nThe error classes take an options object and are declared with constructors, so\nyou can build one directly to test your own handling:\n\n```js\nconst { TurboPaymentError } = require(\"@ardrive/turbo-upload\");\nthrow new TurboPaymentError({ status: 402, endpoint: UPLOAD_URL, method: \"POST\" });\n```\n\n## Endpoints\n\nExported as named constants so nobody has to guess a hostname.\n\nEach `gatewayUrl` is the gateway its own upload service names in `/v1/info`.\n**Set it yourself if reads matter to you.** Any gateway serving Arweave returns\nthese items by id, they differ in what they have indexed, and a busy one will\nrate limit you: `arweave.net` answered `429` to ten consecutive reads of items\nthis package had just uploaded.\n\n| | upload | payment | gateway |\n|---|---|---|---|\n| `PRODUCTION` | `https://upload.ardrive.io` | `https://payment.ardrive.io` | `https://turbo-gateway.com` |\n| `TESTNET` | `https://upload.services.ar-io.dev` | `https://payment.services.ar-io.dev` | `https://ar-io.dev` |\n\n> **The testnet hostnames contain `.services.`**, which is why they are\n> constants rather than prose. `upload.ar-io.dev`, without `.services.`,\n> **resolves** and serves an HTML page on every path including `/v1/tx`, so a\n> wrong hostname gives you a `200` with an HTML body instead of an obvious\n> failure. Import the constant and the question never arises.\n\nUploads to `PRODUCTION` are permanent and cost real money.\n\n---\n\n## The PSS salt length\n\nANS-104 says RSA-PSS and stops. This package sets the salt length to **478\nbytes** explicitly rather than inheriting a default, because that is what the\nreference implementation produces and **getting it wrong does not fail loudly**:\nverification is salt-agnostic, so a 32-byte-salt signature passes round-trip\ntests, passes cross-verification, and is accepted by the live service. It would\nfail later, at a stricter verifier.\n\nWhat that means for you: nothing, unless you sign items yourself elsewhere. If\nyou do, pass `{ strictSaltLength: true }` to `client.verify()` to catch it.\n\nThe derivation, the empirical recovery of the salt off the wire, and the\nper-library instructions are in [the conformance spec](https://github.com/ardriveapp/turbo-upload/blob/main/conformance/spec.md).\n\n## Conformance\n\nThe package ships **22 conformance vectors** in `vectors/vectors.json`,\ngenerated from `@dha-team/arbundles@1.0.4`, the de-facto reference that the\ngateways and bundlers actually run. The test suite ships too, so you can\nre-prove all of it from your own `node_modules`:\n\n```bash\nnpm test --prefix node_modules/@ardrive/turbo-upload\n```\n\nThe vectors pin the unsigned item bytes, the deep-hash transcript, every field\noffset, the serialized tag region and a reference-produced signature per vector.\nNo private key ships: the corpus carries only the public modulus, and the\nsigning tests generate an ephemeral key at runtime.\n\nReimplementing the format rather than consuming it? [The conformance\nspec](https://github.com/ardriveapp/turbo-upload/blob/main/conformance/spec.md) has the byte-level rules, including the two adjacent 32-byte\nfields with opposite string conventions and the encoder quirks reproduced\nbug-for-bug.\n\n## Requirements\n\nNode **>= 18.17.0** (global `fetch`, `AbortSignal.any`). Server-side only:\nthere is no browser build, and a JWK does not belong in a browser anyway.\n\n## License\n\nApache-2.0\n","readmeFilename":"README.md"}