@artblocks/abx-storage 0.1.0-alpha.8 → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (48) hide show
  1. package/CHANGELOG.md +154 -0
  2. package/dist/arweave.d.ts +34 -47
  3. package/dist/arweave.d.ts.map +1 -1
  4. package/dist/arweave.js +64 -160
  5. package/dist/arweave.js.map +1 -1
  6. package/dist/backend.d.ts +1 -4
  7. package/dist/backend.d.ts.map +1 -1
  8. package/dist/backend.js +4 -33
  9. package/dist/backend.js.map +1 -1
  10. package/dist/content-index.d.ts.map +1 -1
  11. package/dist/content-index.js +3 -1
  12. package/dist/content-index.js.map +1 -1
  13. package/dist/content-plan.d.ts +78 -0
  14. package/dist/content-plan.d.ts.map +1 -0
  15. package/dist/content-plan.js +68 -0
  16. package/dist/content-plan.js.map +1 -0
  17. package/dist/fs.d.ts.map +1 -1
  18. package/dist/fs.js +3 -1
  19. package/dist/fs.js.map +1 -1
  20. package/dist/index.d.ts +7 -1
  21. package/dist/index.d.ts.map +1 -1
  22. package/dist/index.js +7 -1
  23. package/dist/index.js.map +1 -1
  24. package/dist/ipfs.d.ts +17 -1
  25. package/dist/ipfs.d.ts.map +1 -1
  26. package/dist/ipfs.js +60 -2
  27. package/dist/ipfs.js.map +1 -1
  28. package/dist/migrate.d.ts +37 -0
  29. package/dist/migrate.d.ts.map +1 -0
  30. package/dist/migrate.js +55 -0
  31. package/dist/migrate.js.map +1 -0
  32. package/dist/probe.d.ts +66 -0
  33. package/dist/probe.d.ts.map +1 -0
  34. package/dist/probe.js +155 -0
  35. package/dist/probe.js.map +1 -0
  36. package/dist/readiness.d.ts +158 -0
  37. package/dist/readiness.d.ts.map +1 -0
  38. package/dist/readiness.js +183 -0
  39. package/dist/readiness.js.map +1 -0
  40. package/dist/upload-readiness.d.ts +89 -0
  41. package/dist/upload-readiness.d.ts.map +1 -0
  42. package/dist/upload-readiness.js +85 -0
  43. package/dist/upload-readiness.js.map +1 -0
  44. package/dist/upload.d.ts +28 -0
  45. package/dist/upload.d.ts.map +1 -0
  46. package/dist/upload.js +41 -0
  47. package/dist/upload.js.map +1 -0
  48. package/package.json +17 -9
@@ -0,0 +1,183 @@
1
+ import { gatewayConfigFromEnv, gatewayUrlFor, linearBackoffDelay, resolveGatewayBase as sdkResolveGatewayBase, sleep, } from '@artblocks/abx-sdk';
2
+ // Re-export the SDK gateway types and helpers unchanged so this package's public API remains stable.
3
+ export { gatewayUrlFor } from '@artblocks/abx-sdk';
4
+ /**
5
+ * Gateways probed as alternates, per network. Kept short on purpose — this is a diagnosis, not a
6
+ * survey, and each entry is a request someone waits on. The Arweave pair is the one the second
7
+ * report actually measured serving ahead of `arweave.net`, so they are evidence rather than taste.
8
+ */
9
+ const ALTERNATE_GATEWAYS = {
10
+ arweave: ['https://permagate.io', 'https://vilenarios.com'],
11
+ ipfs: ['https://dweb.link', 'https://w3s.link'],
12
+ };
13
+ const DEFAULT_TIMEOUT_MS = 6000;
14
+ /** Parse any accepted locator form into its network + id. */
15
+ export function parseLocator(locator) {
16
+ const raw = locator.trim();
17
+ if (/^ar:\/\//i.test(raw))
18
+ return { network: 'arweave', id: raw.slice(5).replace(/^\/+/, '') };
19
+ if (/^ipfs:\/\//i.test(raw))
20
+ return { network: 'ipfs', id: raw.slice(7).replace(/^\/+/, '') };
21
+ if (/^https?:\/\//i.test(raw)) {
22
+ // A gateway URL already names its own gateway; keep it whole and ask exactly it. Detecting the
23
+ // network from the shape lets the alternates still apply (that is the point of the whole probe).
24
+ if (/\/ipfs\//.test(raw) || /\.ipfs\./.test(raw))
25
+ return { network: 'ipfs', id: raw };
26
+ if (/arweave\.net|permagate\.io|vilenarios\.com|ar-io\.dev/.test(raw))
27
+ return { network: 'arweave', id: raw };
28
+ return { network: 'http', id: raw };
29
+ }
30
+ // Bare ids: an Arweave txid is 43 chars of base64url; a CIDv0 starts Qm…, a CIDv1 b…/f…
31
+ if (/^[A-Za-z0-9_-]{43}$/.test(raw))
32
+ return { network: 'arweave', id: raw };
33
+ if (/^(Qm[1-9A-HJ-NP-Za-km-z]{44}|b[a-z2-7]{20,}|f[0-9a-f]{20,})/.test(raw))
34
+ return { network: 'ipfs', id: raw };
35
+ throw new Error(`'${locator}' isn't a recognisable locator. Expected ar://<txid>, ipfs://<cid>, an https:// gateway URL, or a bare txid/CID.`);
36
+ }
37
+ /**
38
+ * The gateway BASE for a locator network — **override → env → generic public default**
39
+ * (`ABX_IPFS_GATEWAY`/`ABX_ARWEAVE_GATEWAY`, then `ipfs.io`/`arweave.net`). This is the READ-TIME
40
+ * default for resolving an arbitrary on-chain locator whose upload backend is unknown to the
41
+ * resolver — distinct from {@link ipfsConfigFromEnv}/{@link arweaveConfigFromEnv}'s MODE-aware
42
+ * defaults for a backend this toolkit itself configured to write through (e.g. a `kubo` IPFS
43
+ * gateway defaults to `127.0.0.1:8080`, a locator resolver has no such context and must default
44
+ * to something that resolves for anyone). `http` locators carry their own base (they are already
45
+ * an absolute URL) and never reach here — callers return them verbatim before consulting a gateway.
46
+ *
47
+ * A thin compose over the SDK's split: {@link resolveGatewayBase} used to own the
48
+ * `override ?? env ?? default` line itself (`process.env` read inline); now the pure decision
49
+ * lives in `@artblocks/abx-sdk`'s `resolveGatewayBase` (src/gateways.ts) and the env read in its
50
+ * `gatewayConfigFromEnv`, so this function's whole body is just wiring override → (env or
51
+ * nothing) → the SDK's pure default. Kept here, under this same name and signature, because
52
+ * every existing caller (this package's own `locatorStatus`, token-api's `resolveLocatorUrl`)
53
+ * still wants exactly this three-rung precedence with one call.
54
+ */
55
+ export function resolveGatewayBase(network, override) {
56
+ if (network === 'http')
57
+ return '';
58
+ if (override)
59
+ return sdkResolveGatewayBase(network, network === 'ipfs' ? { ipfs: override } : { arweave: override });
60
+ return sdkResolveGatewayBase(network, gatewayConfigFromEnv());
61
+ }
62
+ /**
63
+ * Ask one gateway whether it serves these bytes, reading **headers only**.
64
+ *
65
+ * A ranged request (`Range: bytes=0-0`) is what keeps this cheap: the content in question can be tens of
66
+ * megabytes and the question is only whether it is there. Gateways that ignore `Range` answer 200
67
+ * with the whole body, so the body is cancelled rather than read — without that, checking a large
68
+ * locator would download it.
69
+ */
70
+ export async function probeGateway(url, opts = {}) {
71
+ const doFetch = opts.fetchFn ?? fetch;
72
+ const gateway = safeHost(url);
73
+ const started = Date.now();
74
+ const ctrl = new AbortController();
75
+ const timer = setTimeout(() => ctrl.abort(), opts.timeoutMs ?? DEFAULT_TIMEOUT_MS);
76
+ try {
77
+ const res = await doFetch(url, { headers: { range: 'bytes=0-0' }, signal: ctrl.signal, redirect: 'follow' });
78
+ // Never read the body: a Range-ignoring gateway would otherwise stream the whole asset.
79
+ try {
80
+ await res.body?.cancel();
81
+ }
82
+ catch {
83
+ /* already consumed or unsupported — nothing to release */
84
+ }
85
+ return {
86
+ url,
87
+ gateway,
88
+ serving: res.status >= 200 && res.status < 300,
89
+ status: res.status,
90
+ contentType: res.headers.get('content-type'),
91
+ bytes: totalBytesFrom(res.headers.get('content-range'), res.headers.get('content-length'), res.status),
92
+ ms: Date.now() - started,
93
+ };
94
+ }
95
+ catch (err) {
96
+ const aborted = err.name === 'AbortError' || ctrl.signal.aborted;
97
+ return {
98
+ url,
99
+ gateway,
100
+ serving: false,
101
+ status: null,
102
+ contentType: null,
103
+ bytes: null,
104
+ ms: Date.now() - started,
105
+ error: aborted ? `no response in ${opts.timeoutMs ?? DEFAULT_TIMEOUT_MS}ms` : err.message,
106
+ };
107
+ }
108
+ finally {
109
+ clearTimeout(timer);
110
+ }
111
+ }
112
+ /** Accepted-vs-retrievable for one locator, across the gateway that matters and a couple of others. */
113
+ export async function locatorStatus(locator, opts = {}) {
114
+ const { network, id } = parseLocator(locator);
115
+ const primaryGateway = resolveGatewayBase(network, opts.gateway);
116
+ const primaryUrl = gatewayUrlFor(network, id, primaryGateway);
117
+ // `http` locators have no notion of an alternate — the URL IS the address, so a second host would
118
+ // be answering about different bytes.
119
+ const alternateBases = opts.primaryOnly || network === 'http' ? [] : ALTERNATE_GATEWAYS[network];
120
+ const alternateUrls = alternateBases
121
+ .map((base) => gatewayUrlFor(network, id, base))
122
+ .filter((u) => u !== primaryUrl);
123
+ const probe = (u) => probeGateway(u, { timeoutMs: opts.timeoutMs, fetchFn: opts.fetchFn });
124
+ const [primary, ...alternates] = await Promise.all([probe(primaryUrl), ...alternateUrls.map(probe)]);
125
+ const readiness = primary.serving
126
+ ? 'ready'
127
+ : alternates.some((a) => a.serving)
128
+ ? 'propagating'
129
+ : 'unreachable';
130
+ return { locator, network, id, readiness, primary, alternates };
131
+ }
132
+ const DEFAULT_AWAIT_TIMEOUT_MS = 5 * 60_000;
133
+ const DEFAULT_POLL_BASE_MS = 3000;
134
+ /**
135
+ * Poll {@link locatorStatus} until it reports `'ready'` or the deadline passes.
136
+ *
137
+ * `locatorStatus` alone answers "right now" — the natural next question, for a caller that would
138
+ * otherwise upload during a mint and write the locator into a token that renders broken for its
139
+ * first minutes of life (see the module doc), is "wait until it's actually there." This is that,
140
+ * factored out once so a caller doesn't hand-roll its own poll/backoff loop around a one-shot check.
141
+ *
142
+ * `'propagating'` is treated the same as `'unreachable'` here — both mean "not yet what THIS caller
143
+ * can rely on" — but every intermediate status still reaches `onEvent`, so a caller that wants to
144
+ * distinguish "provably on the network, just behind" from "no evidence yet" can from the events.
145
+ */
146
+ export async function awaitLocatorReady(locator, opts = {}) {
147
+ const deadlineMs = opts.timeoutMs ?? DEFAULT_AWAIT_TIMEOUT_MS;
148
+ const pollBaseMs = opts.pollBaseMs ?? DEFAULT_POLL_BASE_MS;
149
+ const started = Date.now();
150
+ const probeOpts = { gateway: opts.gateway, primaryOnly: opts.primaryOnly, timeoutMs: opts.probeTimeoutMs, fetchFn: opts.fetchFn };
151
+ let attempt = 0;
152
+ for (;;) {
153
+ attempt++;
154
+ const status = await locatorStatus(locator, probeOpts);
155
+ const elapsedMs = Date.now() - started;
156
+ opts.onEvent?.({ attempt, elapsedMs, status });
157
+ if (status.readiness === 'ready')
158
+ return { status, attempts: attempt, elapsedMs, ready: true };
159
+ const delay = linearBackoffDelay(attempt, pollBaseMs);
160
+ if (elapsedMs + delay >= deadlineMs)
161
+ return { status, attempts: attempt, elapsedMs, ready: false };
162
+ await sleep(delay);
163
+ }
164
+ }
165
+ /** Total size from a ranged response's `content-range` (`bytes 0-0/12345`), else `content-length`
166
+ * when the gateway ignored the range and answered 200 with the whole thing. */
167
+ function totalBytesFrom(contentRange, contentLength, status) {
168
+ const m = contentRange?.match(/\/\s*(\d+)\s*$/);
169
+ if (m)
170
+ return Number(m[1]);
171
+ if (status === 200 && contentLength && /^\d+$/.test(contentLength))
172
+ return Number(contentLength);
173
+ return null;
174
+ }
175
+ function safeHost(url) {
176
+ try {
177
+ return new URL(url).host;
178
+ }
179
+ catch {
180
+ return url;
181
+ }
182
+ }
183
+ //# sourceMappingURL=readiness.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"readiness.js","sourceRoot":"","sources":["../src/readiness.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,89 @@
1
+ import type { ResolveStorageOptions } from './resolve.js';
2
+ import { type ArweaveConfig } from './arweave.js';
3
+ /**
4
+ * Deploy-time storage readiness — what a REAL upload needs, decided from local facts (and, for the
5
+ * best-effort USD estimate, one price-API call) so "dry-run passes, then the real deploy fails at
6
+ * upload" can't happen. Three questions, each pure aside from the one noted network call:
7
+ *
8
+ * - {@link planTurboUpload} — is THIS ONE file free (Turbo's 100 KiB tier) or chargeable?
9
+ * - {@link assessStorageReadiness} — across every backend, what does a real upload of `sizes` need
10
+ * (Arweave credits, a Pinata JWT, a cloud public base)?
11
+ * - {@link assessTurboFunds} — does the resolved Turbo identity have ENOUGH prepaid credits for
12
+ * `sizes`, and (when it's short) does the creator's own wallet already hold some?
13
+ *
14
+ * None of this mints or spends anything — it only INTERROGATES the resolved provider. Identity
15
+ * creation is the caller's job (the CLI's managed-key flow owns the key file location; see
16
+ * `arweave-identity.ts` / `ArweaveConfig.ensureJwk`), so `assessTurboFunds` expects a config that
17
+ * already has one.
18
+ */
19
+ /** Whether `opts` resolves to Arweave via the Turbo (prepaid-credits) provider — the only provider
20
+ * with a free tier and a funds balance to check; `http-bundler` skips every readiness/funds
21
+ * question below (it has its own auth, not a balance this package knows how to read). */
22
+ export declare function isTurboArweave(opts: ResolveStorageOptions): boolean;
23
+ export interface TurboUploadPlan {
24
+ /** Whether this upload is over Turbo's 100 KiB free tier (so it draws on prepaid credits). */
25
+ chargeable: boolean;
26
+ /** `size` in KB, formatted to 1 decimal — for the free-vs-credit readout. */
27
+ sizeKb: string;
28
+ }
29
+ /** The free-vs-credit decision for ONE Turbo upload of `size` bytes — from the local byte size
30
+ * alone, so it works under `--dry-run` too. Null when `opts` doesn't resolve to Turbo-Arweave
31
+ * (nothing to decide). */
32
+ export declare function planTurboUpload(opts: ResolveStorageOptions, size: number): TurboUploadPlan | null;
33
+ /** Per-backend readiness facts for a real upload of `sizes` (per-file byte counts). Each variant
34
+ * carries only what its backend needs checked; a backend with nothing to check (`fs`, or anything
35
+ * this package doesn't know about) reports `'other'` — a literal, not the resolved id, so the
36
+ * union stays a real discriminated union (a plain `string` member would swallow the other
37
+ * branches on narrowing) — callers that branch on `backend` never needed the id anyway. */
38
+ export type StorageReadinessReport = {
39
+ backend: 'arweave';
40
+ overCount: number;
41
+ totalCount: number;
42
+ totalKb: number;
43
+ usd: number | null;
44
+ } | {
45
+ backend: 'ipfs';
46
+ pinataMissingJwt: boolean;
47
+ } | {
48
+ backend: 'cloud';
49
+ missingPublicBase: boolean;
50
+ } | {
51
+ backend: 'other';
52
+ };
53
+ /**
54
+ * What a REAL deploy against `opts` needs, from `sizes` (per-file byte sizes) alone — Arweave
55
+ * credit needs (plus a best-effort USD estimate for the chargeable bytes), a missing Pinata JWT, or
56
+ * a missing cloud public base. The one network call (Turbo's price API) is best-effort: a failure
57
+ * there degrades `usd` to `null` rather than failing the whole check.
58
+ */
59
+ export declare function assessStorageReadiness(opts: ResolveStorageOptions, sizes: number[]): Promise<StorageReadinessReport>;
60
+ export interface TurboFundsCheck {
61
+ /** True when the resolved identity does NOT have enough prepaid credits for `sizes`. */
62
+ short: boolean;
63
+ address: string;
64
+ /** Whether the identity is an EVM one (`.env` key or a remote wallet) rather than the CLI-managed
65
+ * Arweave key — decides which recovery message applies. */
66
+ isEth: boolean;
67
+ haveWinc: number;
68
+ needWinc: number;
69
+ /** How many of `sizes` are over the free tier (what this shortfall is actually for). */
70
+ chargeableCount: number;
71
+ /** Set only when checked (short, a non-eth identity, and a wallet address was given): the
72
+ * creator's OWN wallet's Turbo balance, queryable by address with no key — so "spend those
73
+ * instead of topping up again" can be recommended before ever proposing a top-up. Null if that
74
+ * wallet has never had a Turbo balance. */
75
+ walletCredits?: {
76
+ winc: number;
77
+ credits: string;
78
+ } | null;
79
+ }
80
+ /**
81
+ * The pre-upload funds decision for a Turbo (Arweave) upload of `sizes` (per-file byte sizes):
82
+ * chargeable bytes → the Winston cost → compare against the identity's prepaid balance. `cfg` must
83
+ * already have a resolved identity (`jwk`/`ethSignerKey`/`remoteEth`) — this module only
84
+ * INTERROGATES the resolved provider, never mints one (see the module doc). Returns `null` when
85
+ * nothing is chargeable (every file is free) or the provider can't be reached right now — a
86
+ * best-effort check never false-blocks; the upload itself will surface any real error.
87
+ */
88
+ export declare function assessTurboFunds(cfg: ArweaveConfig, sizes: number[], walletAddr?: string): Promise<TurboFundsCheck | null>;
89
+ //# sourceMappingURL=upload-readiness.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"upload-readiness.d.ts","sourceRoot":"","sources":["../src/upload-readiness.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAC,qBAAqB,EAAC,MAAM,cAAc,CAAC;AACxD,OAAO,EAAyG,KAAK,aAAa,EAAC,MAAM,cAAc,CAAC;AAExJ;;;;;;;;;;;;;;;GAeG;AAEH;;0FAE0F;AAC1F,wBAAgB,cAAc,CAAC,IAAI,EAAE,qBAAqB,GAAG,OAAO,CAEnE;AAED,MAAM,WAAW,eAAe;IAC9B,8FAA8F;IAC9F,UAAU,EAAE,OAAO,CAAC;IACpB,6EAA6E;IAC7E,MAAM,EAAE,MAAM,CAAC;CAChB;AAED;;2BAE2B;AAC3B,wBAAgB,eAAe,CAAC,IAAI,EAAE,qBAAqB,EAAE,IAAI,EAAE,MAAM,GAAG,eAAe,GAAG,IAAI,CAGjG;AAED;;;;4FAI4F;AAC5F,MAAM,MAAM,sBAAsB,GAC9B;IAAC,OAAO,EAAE,SAAS,CAAC;IAAC,SAAS,EAAE,MAAM,CAAC;IAAC,UAAU,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAAA;CAAC,GAChG;IAAC,OAAO,EAAE,MAAM,CAAC;IAAC,gBAAgB,EAAE,OAAO,CAAA;CAAC,GAC5C;IAAC,OAAO,EAAE,OAAO,CAAC;IAAC,iBAAiB,EAAE,OAAO,CAAA;CAAC,GAC9C;IAAC,OAAO,EAAE,OAAO,CAAA;CAAC,CAAC;AAEvB;;;;;GAKG;AACH,wBAAsB,sBAAsB,CAAC,IAAI,EAAE,qBAAqB,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,sBAAsB,CAAC,CAc1H;AAED,MAAM,WAAW,eAAe;IAC9B,wFAAwF;IACxF,KAAK,EAAE,OAAO,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;IAChB;gEAC4D;IAC5D,KAAK,EAAE,OAAO,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,wFAAwF;IACxF,eAAe,EAAE,MAAM,CAAC;IACxB;;;gDAG4C;IAC5C,aAAa,CAAC,EAAE;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAC,GAAG,IAAI,CAAC;CACxD;AAED;;;;;;;GAOG;AACH,wBAAsB,gBAAgB,CAAC,GAAG,EAAE,aAAa,EAAE,KAAK,EAAE,MAAM,EAAE,EAAE,UAAU,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,eAAe,GAAG,IAAI,CAAC,CAuBhI"}
@@ -0,0 +1,85 @@
1
+ import { ARWEAVE_FREE_UPLOAD_LIMIT, arweaveFunding, turboBalanceForAddress, turboUploadCostUsd, turboUploadWinc } from './arweave.js';
2
+ /**
3
+ * Deploy-time storage readiness — what a REAL upload needs, decided from local facts (and, for the
4
+ * best-effort USD estimate, one price-API call) so "dry-run passes, then the real deploy fails at
5
+ * upload" can't happen. Three questions, each pure aside from the one noted network call:
6
+ *
7
+ * - {@link planTurboUpload} — is THIS ONE file free (Turbo's 100 KiB tier) or chargeable?
8
+ * - {@link assessStorageReadiness} — across every backend, what does a real upload of `sizes` need
9
+ * (Arweave credits, a Pinata JWT, a cloud public base)?
10
+ * - {@link assessTurboFunds} — does the resolved Turbo identity have ENOUGH prepaid credits for
11
+ * `sizes`, and (when it's short) does the creator's own wallet already hold some?
12
+ *
13
+ * None of this mints or spends anything — it only INTERROGATES the resolved provider. Identity
14
+ * creation is the caller's job (the CLI's managed-key flow owns the key file location; see
15
+ * `arweave-identity.ts` / `ArweaveConfig.ensureJwk`), so `assessTurboFunds` expects a config that
16
+ * already has one.
17
+ */
18
+ /** Whether `opts` resolves to Arweave via the Turbo (prepaid-credits) provider — the only provider
19
+ * with a free tier and a funds balance to check; `http-bundler` skips every readiness/funds
20
+ * question below (it has its own auth, not a balance this package knows how to read). */
21
+ export function isTurboArweave(opts) {
22
+ return opts.backend === 'arweave' && (opts.arweave?.provider ?? 'turbo') === 'turbo';
23
+ }
24
+ /** The free-vs-credit decision for ONE Turbo upload of `size` bytes — from the local byte size
25
+ * alone, so it works under `--dry-run` too. Null when `opts` doesn't resolve to Turbo-Arweave
26
+ * (nothing to decide). */
27
+ export function planTurboUpload(opts, size) {
28
+ if (!isTurboArweave(opts))
29
+ return null;
30
+ return { chargeable: size >= ARWEAVE_FREE_UPLOAD_LIMIT, sizeKb: (size / 1024).toFixed(1) };
31
+ }
32
+ /**
33
+ * What a REAL deploy against `opts` needs, from `sizes` (per-file byte sizes) alone — Arweave
34
+ * credit needs (plus a best-effort USD estimate for the chargeable bytes), a missing Pinata JWT, or
35
+ * a missing cloud public base. The one network call (Turbo's price API) is best-effort: a failure
36
+ * there degrades `usd` to `null` rather than failing the whole check.
37
+ */
38
+ export async function assessStorageReadiness(opts, sizes) {
39
+ if (opts.backend === 'arweave') {
40
+ const chargeable = sizes.filter((s) => s >= ARWEAVE_FREE_UPLOAD_LIMIT);
41
+ const usd = chargeable.length ? await turboUploadCostUsd(chargeable.reduce((a, b) => a + b, 0)) : null;
42
+ const totalKb = Math.round(sizes.reduce((a, b) => a + b, 0) / 1024);
43
+ return { backend: 'arweave', overCount: chargeable.length, totalCount: sizes.length, totalKb, usd };
44
+ }
45
+ if (opts.backend === 'ipfs') {
46
+ return { backend: 'ipfs', pinataMissingJwt: (opts.ipfs?.mode ?? 'pinata') === 'pinata' && !opts.ipfs?.pinataJwt };
47
+ }
48
+ if (opts.backend === 'cloud') {
49
+ return { backend: 'cloud', missingPublicBase: !opts.cloud?.publicBase };
50
+ }
51
+ return { backend: 'other' };
52
+ }
53
+ /**
54
+ * The pre-upload funds decision for a Turbo (Arweave) upload of `sizes` (per-file byte sizes):
55
+ * chargeable bytes → the Winston cost → compare against the identity's prepaid balance. `cfg` must
56
+ * already have a resolved identity (`jwk`/`ethSignerKey`/`remoteEth`) — this module only
57
+ * INTERROGATES the resolved provider, never mints one (see the module doc). Returns `null` when
58
+ * nothing is chargeable (every file is free) or the provider can't be reached right now — a
59
+ * best-effort check never false-blocks; the upload itself will surface any real error.
60
+ */
61
+ export async function assessTurboFunds(cfg, sizes, walletAddr) {
62
+ const chargeable = sizes.filter((s) => s >= ARWEAVE_FREE_UPLOAD_LIMIT);
63
+ if (chargeable.length === 0)
64
+ return null; // all free
65
+ const funding = await arweaveFunding(cfg);
66
+ let address;
67
+ let haveWinc;
68
+ try {
69
+ address = await funding.address();
70
+ haveWinc = Number((await funding.balance()).winc) || 0;
71
+ }
72
+ catch {
73
+ return null; // can't reach Turbo to check — don't false-block; the upload will surface any real error
74
+ }
75
+ const needWinc = (await turboUploadWinc(chargeable.reduce((a, b) => a + b, 0))) ?? 0;
76
+ const short = needWinc > 0 ? haveWinc < needWinc : haveWinc <= 0;
77
+ const isEth = !!(cfg.ethSignerKey || cfg.remoteEth);
78
+ let walletCredits;
79
+ if (short && !isEth && walletAddr) {
80
+ const wb = await turboBalanceForAddress(walletAddr, 'ethereum');
81
+ walletCredits = wb ? { winc: Number(wb.winc) || 0, credits: wb.credits } : null;
82
+ }
83
+ return { short, address, isEth, haveWinc, needWinc, chargeableCount: chargeable.length, walletCredits };
84
+ }
85
+ //# sourceMappingURL=upload-readiness.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"upload-readiness.js","sourceRoot":"","sources":["../src/upload-readiness.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,28 @@
1
+ /**
2
+ * `abx storage upload` core — upload ONE file and return a public locator that PRESERVES the
3
+ * filename, so the declared mimeType survives when the locator is later attached (`abx attach`).
4
+ * The on-chain metadata field has no MIME slot: the URL extension IS the type declaration, so a
5
+ * bare content-addressed locator (`ar://<txid>`, `<gw>/ipfs/<cid>`) — which has no extension —
6
+ * would make the attached artifact `application/octet-stream`. Extracted from main.ts so the
7
+ * capability-based branch (the exact bug this guards) is unit-testable with fake backends.
8
+ */
9
+ import type { StorageBackend, StoredContent } from './backend.js';
10
+ export interface UploadResult {
11
+ /** A fetchable public URL for the file. */
12
+ locator: string;
13
+ /** True when `locator` carries the filename (extension → correct declared type on attach). */
14
+ filenamePreserved: boolean;
15
+ /** Set when we had to fall back to a bare (extension-less) locator — the caller warns. */
16
+ fallbackReason?: string;
17
+ }
18
+ /**
19
+ * Store `content` under `name` and return the best available public locator. Preference order:
20
+ * 1. path-addressed host (cloud/S3/R2): the object key IS the filename → `<publicBase>/<name>`.
21
+ * 2. content-addressed host with directory support (ipfs/arweave): wrap the single file in a
22
+ * one-entry directory → `<base>/<name>` (filename preserved).
23
+ * 3. bare content-addressed locator (e.g. kubo IPFS with no dir-add): `<gw>/ipfs/<cid>` — no
24
+ * extension; `filenamePreserved: false` so the caller can warn about the octet-stream type.
25
+ * Throws for a backend that can't produce any public URL (e.g. `fs`).
26
+ */
27
+ export declare function uploadAndLocate(backend: StorageBackend, name: string, content: StoredContent): Promise<UploadResult>;
28
+ //# sourceMappingURL=upload.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"upload.d.ts","sourceRoot":"","sources":["../src/upload.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,OAAO,KAAK,EAAC,cAAc,EAAE,aAAa,EAAC,MAAM,cAAc,CAAC;AAGhE,MAAM,WAAW,YAAY;IAC3B,2CAA2C;IAC3C,OAAO,EAAE,MAAM,CAAC;IAChB,8FAA8F;IAC9F,iBAAiB,EAAE,OAAO,CAAC;IAC3B,0FAA0F;IAC1F,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB;AAID;;;;;;;;GAQG;AACH,wBAAsB,eAAe,CAAC,OAAO,EAAE,cAAc,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,aAAa,GAAG,OAAO,CAAC,YAAY,CAAC,CAyB1H"}
package/dist/upload.js ADDED
@@ -0,0 +1,41 @@
1
+ import { hashContent } from './backend.js';
2
+ const trimSlash = (s) => s.replace(/\/+$/, '');
3
+ /**
4
+ * Store `content` under `name` and return the best available public locator. Preference order:
5
+ * 1. path-addressed host (cloud/S3/R2): the object key IS the filename → `<publicBase>/<name>`.
6
+ * 2. content-addressed host with directory support (ipfs/arweave): wrap the single file in a
7
+ * one-entry directory → `<base>/<name>` (filename preserved).
8
+ * 3. bare content-addressed locator (e.g. kubo IPFS with no dir-add): `<gw>/ipfs/<cid>` — no
9
+ * extension; `filenamePreserved: false` so the caller can warn about the octet-stream type.
10
+ * Throws for a backend that can't produce any public URL (e.g. `fs`).
11
+ */
12
+ export async function uploadAndLocate(backend, name, content) {
13
+ if (backend.publicBase && backend.putObject) {
14
+ await backend.putObject(name, content);
15
+ return { locator: `${trimSlash(backend.publicBase)}/${name}`, filenamePreserved: true };
16
+ }
17
+ if (backend.putDirectory && backend.locator) {
18
+ try {
19
+ const { base } = await backend.putDirectory([{ name, bytes: content.bytes, contentType: content.contentType }]);
20
+ return { locator: `${trimSlash(base)}/${name}`, filenamePreserved: true };
21
+ }
22
+ catch (e) {
23
+ const hash = hashContent(content.bytes);
24
+ await backend.put(hash, content);
25
+ const loc = await backend.locator(hash);
26
+ if (!loc)
27
+ throw e;
28
+ return { locator: loc, filenamePreserved: false, fallbackReason: e.message };
29
+ }
30
+ }
31
+ if (backend.locator) {
32
+ const hash = hashContent(content.bytes);
33
+ await backend.put(hash, content);
34
+ const loc = await backend.locator(hash);
35
+ if (!loc)
36
+ throw new Error(`backend '${backend.id}' stored the file but returned no durable locator`);
37
+ return { locator: loc, filenamePreserved: false };
38
+ }
39
+ throw new Error(`backend '${backend.id}' can't produce a public URL for an attached file — use ipfs, arweave, or cloud (with a public base).`);
40
+ }
41
+ //# sourceMappingURL=upload.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"upload.js","sourceRoot":"","sources":["../src/upload.ts"],"names":[],"mappings":"AASA,OAAO,EAAC,WAAW,EAAC,MAAM,cAAc,CAAC;AAWzC,MAAM,SAAS,GAAG,CAAC,CAAS,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;AAEvD;;;;;;;;GAQG;AACH,MAAM,CAAC,KAAK,UAAU,eAAe,CAAC,OAAuB,EAAE,IAAY,EAAE,OAAsB;IACjG,IAAI,OAAO,CAAC,UAAU,IAAI,OAAO,CAAC,SAAS,EAAE,CAAC;QAC5C,MAAM,OAAO,CAAC,SAAS,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;QACvC,OAAO,EAAC,OAAO,EAAE,GAAG,SAAS,CAAC,OAAO,CAAC,UAAU,CAAC,IAAI,IAAI,EAAE,EAAE,iBAAiB,EAAE,IAAI,EAAC,CAAC;IACxF,CAAC;IACD,IAAI,OAAO,CAAC,YAAY,IAAI,OAAO,CAAC,OAAO,EAAE,CAAC;QAC5C,IAAI,CAAC;YACH,MAAM,EAAC,IAAI,EAAC,GAAG,MAAM,OAAO,CAAC,YAAY,CAAC,CAAC,EAAC,IAAI,EAAE,KAAK,EAAE,OAAO,CAAC,KAAK,EAAE,WAAW,EAAE,OAAO,CAAC,WAAW,EAAC,CAAC,CAAC,CAAC;YAC5G,OAAO,EAAC,OAAO,EAAE,GAAG,SAAS,CAAC,IAAI,CAAC,IAAI,IAAI,EAAE,EAAE,iBAAiB,EAAE,IAAI,EAAC,CAAC;QAC1E,CAAC;QAAC,OAAO,CAAC,EAAE,CAAC;YACX,MAAM,IAAI,GAAG,WAAW,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;YACxC,MAAM,OAAO,CAAC,GAAG,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;YACjC,MAAM,GAAG,GAAG,MAAM,OAAO,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;YACxC,IAAI,CAAC,GAAG;gBAAE,MAAM,CAAC,CAAC;YAClB,OAAO,EAAC,OAAO,EAAE,GAAG,EAAE,iBAAiB,EAAE,KAAK,EAAE,cAAc,EAAG,CAAW,CAAC,OAAO,EAAC,CAAC;QACxF,CAAC;IACH,CAAC;IACD,IAAI,OAAO,CAAC,OAAO,EAAE,CAAC;QACpB,MAAM,IAAI,GAAG,WAAW,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;QACxC,MAAM,OAAO,CAAC,GAAG,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;QACjC,MAAM,GAAG,GAAG,MAAM,OAAO,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;QACxC,IAAI,CAAC,GAAG;YAAE,MAAM,IAAI,KAAK,CAAC,YAAY,OAAO,CAAC,EAAE,mDAAmD,CAAC,CAAC;QACrG,OAAO,EAAC,OAAO,EAAE,GAAG,EAAE,iBAAiB,EAAE,KAAK,EAAC,CAAC;IAClD,CAAC;IACD,MAAM,IAAI,KAAK,CAAC,YAAY,OAAO,CAAC,EAAE,uGAAuG,CAAC,CAAC;AACjJ,CAAC"}
package/package.json CHANGED
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "name": "@artblocks/abx-storage",
3
- "version": "0.1.0-alpha.8",
3
+ "version": "0.1.0",
4
4
  "license": "MIT",
5
- "description": "ABX Self-Host Toolkit (Layer 3) — the reference byte-custody / storage service. Holds the source-of-truth bytes a token's content commitment points at, content-addressed by hash, behind a pluggable backend interface.",
5
+ "description": "Storage adapters for ABX content using filesystem, S3-compatible, IPFS, or Arweave backends.",
6
6
  "type": "module",
7
7
  "main": "./dist/index.js",
8
8
  "exports": {
@@ -13,18 +13,19 @@
13
13
  },
14
14
  "files": [
15
15
  "dist",
16
+ "CHANGELOG.md",
16
17
  "LICENSE"
17
18
  ],
18
19
  "sideEffects": false,
19
20
  "engines": {
20
- "node": ">=22.5.0"
21
+ "node": ">=22.13.0"
21
22
  },
22
23
  "repository": {
23
24
  "type": "git",
24
25
  "url": "git+https://github.com/ArtBlocks/abx.git",
25
26
  "directory": "packages/storage"
26
27
  },
27
- "homepage": "https://github.com/ArtBlocks/abx#readme",
28
+ "homepage": "https://docs.abx.io",
28
29
  "bugs": "https://github.com/ArtBlocks/abx/issues",
29
30
  "keywords": [
30
31
  "abx",
@@ -38,12 +39,19 @@
38
39
  "access": "public"
39
40
  },
40
41
  "dependencies": {
41
- "viem": "^2.21.0",
42
- "@artblocks/abx-sdk": "0.1.0-alpha.8"
42
+ "viem": "^2.56.5",
43
+ "@artblocks/abx-sdk": "0.1.0"
43
44
  },
44
- "optionalDependencies": {
45
- "@ardrive/turbo-sdk": "^1.23.0",
46
- "@dha-team/arbundles": "^1.0.4"
45
+ "peerDependencies": {
46
+ "@artblocks/abx-storage-arweave": ">=0.1.0-alpha.0"
47
+ },
48
+ "peerDependenciesMeta": {
49
+ "@artblocks/abx-storage-arweave": {
50
+ "optional": true
51
+ }
52
+ },
53
+ "devDependencies": {
54
+ "@artblocks/abx-storage-arweave": "0.1.0"
47
55
  },
48
56
  "scripts": {
49
57
  "build": "tsc -b tsconfig.build.json"