@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.
- package/CHANGELOG.md +154 -0
- package/dist/arweave.d.ts +34 -47
- package/dist/arweave.d.ts.map +1 -1
- package/dist/arweave.js +64 -160
- package/dist/arweave.js.map +1 -1
- package/dist/backend.d.ts +1 -4
- package/dist/backend.d.ts.map +1 -1
- package/dist/backend.js +4 -33
- package/dist/backend.js.map +1 -1
- package/dist/content-index.d.ts.map +1 -1
- package/dist/content-index.js +3 -1
- package/dist/content-index.js.map +1 -1
- package/dist/content-plan.d.ts +78 -0
- package/dist/content-plan.d.ts.map +1 -0
- package/dist/content-plan.js +68 -0
- package/dist/content-plan.js.map +1 -0
- package/dist/fs.d.ts.map +1 -1
- package/dist/fs.js +3 -1
- package/dist/fs.js.map +1 -1
- package/dist/index.d.ts +7 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +7 -1
- package/dist/index.js.map +1 -1
- package/dist/ipfs.d.ts +17 -1
- package/dist/ipfs.d.ts.map +1 -1
- package/dist/ipfs.js +60 -2
- package/dist/ipfs.js.map +1 -1
- package/dist/migrate.d.ts +37 -0
- package/dist/migrate.d.ts.map +1 -0
- package/dist/migrate.js +55 -0
- package/dist/migrate.js.map +1 -0
- package/dist/probe.d.ts +66 -0
- package/dist/probe.d.ts.map +1 -0
- package/dist/probe.js +155 -0
- package/dist/probe.js.map +1 -0
- package/dist/readiness.d.ts +158 -0
- package/dist/readiness.d.ts.map +1 -0
- package/dist/readiness.js +183 -0
- package/dist/readiness.js.map +1 -0
- package/dist/upload-readiness.d.ts +89 -0
- package/dist/upload-readiness.d.ts.map +1 -0
- package/dist/upload-readiness.js +85 -0
- package/dist/upload-readiness.js.map +1 -0
- package/dist/upload.d.ts +28 -0
- package/dist/upload.d.ts.map +1 -0
- package/dist/upload.js +41 -0
- package/dist/upload.js.map +1 -0
- 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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"}
|
package/dist/upload.d.ts
ADDED
|
@@ -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
|
|
3
|
+
"version": "0.1.0",
|
|
4
4
|
"license": "MIT",
|
|
5
|
-
"description": "
|
|
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.
|
|
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://
|
|
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.
|
|
42
|
-
"@artblocks/abx-sdk": "0.1.0
|
|
42
|
+
"viem": "^2.56.5",
|
|
43
|
+
"@artblocks/abx-sdk": "0.1.0"
|
|
43
44
|
},
|
|
44
|
-
"
|
|
45
|
-
"@
|
|
46
|
-
|
|
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"
|