arkiv-images 1.0.1 → 1.0.3

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/AGENTS.md CHANGED
@@ -1,17 +1,18 @@
1
- Edit instructions only in AGENTS.md; CLAUDE.md only imports this file.
2
-
3
- # Integrating arkiv-images
4
-
5
- Read [README.md](README.md) explicitly for the public API, runnable local example, data model, limits, signing and recovery. After this patch is published, install it with `npm install arkiv-images@1.0.1 @arkiv-network/sdk@1.0.0 viem@2.56.3`. Before publication, use the reviewed candidate tarball instead.
6
-
7
- - Ask whether the task needs local inspection, retrieval or a new public upload. Inspection requires no network. Retrieval needs an image key, Rabdi and optionally an independently trusted SHA-256. Writes additionally require a locally configured signing wallet and test funds.
8
- - Ask whether original bytes, filename and metadata are safe to publish. EXIF/GPS is preserved. Stop if confidentiality is required; encryption is a different tool.
9
- - Use Rabdi chain 7794095 with SDK 1.0.0. Ask for an explicit Entity Expiration within the admitted block range. Access keys and test funds come from the Hub links in README. Never request a private key in chat.
10
- - Import only public package APIs. Reuse arkiv-chunking's publicly exported write policy; no private dependency imports or copied storage/chunking/gas logic. Bytes use non-indexed bytes columns; query only indexed typed metadata with SDK predicates.
11
- - Preserve MIME/size/network/integrity/decode checks and public TypeScript types. A browser must decode before upload and after retrieval, render only PNG/JPEG, treat filenames as text and revoke Blob URLs.
12
- - Every write uses unsigned latest-state estimation, explicit gas/price/latest nonce and at least twice estimate, below half the head block gas limit. Do not use unlimited lifetime or historical state reads. Never automatically retry successful or uncertain writes.
13
- - Serialize account writes. Recheck browser wallet identity/network before each signature, including the final root. Confirm completion only after storeImage resolves. Preserve error imageKey/manifestKey/transactionHashes, including CONFIRMATION_PENDING. Never display raw provider errors or causes.
14
- - No independent part mutation, transfer or extension. Root existence does not prove chunk availability; expiration does not erase historical copies.
15
- - Sample reads keep the access key server-side in its own endpoint. Browser writes use the visitor's configured wallet RPC; no shared server signer.
16
- - Verify the exact local example, public types, meaningful controlled failure cases, requested live network flow and original/retrieved byte equality. Distinguish controlled RPC, packed candidate and registry release. Inspect sample empty/loading/error/recovered states at narrow/medium/wide widths.
17
- - Keep AGENTS.md, CLAUDE.md and verification in the npm publication allowlist. Explicitly provide consumers the sample guides. No new skill, MCP, generator, encryption or media-management features belong here.
1
+ Edit instructions only in AGENTS.md; CLAUDE.md only imports this file.
2
+
3
+ # Integrating arkiv-images
4
+
5
+ Read [README.md](README.md) explicitly for the public API, runnable local example, data model, limits, signing and recovery. Install version 1.0.3 with `npm install arkiv-images@1.0.3 @arkiv-network/sdk@1.0.0 viem@2.56.3`.
6
+
7
+ - Ask whether the task needs local inspection, retrieval or a new public upload. Inspection requires no network. Retrieval needs an image key, Rabdi and optionally an independently trusted SHA-256. Writes additionally require a locally configured signing wallet and test funds.
8
+ - Ask whether original bytes, filename and metadata are safe to publish. EXIF/GPS is preserved. Stop if confidentiality is required; encryption is a different tool.
9
+ - Use Rabdi chain 7794095 with SDK 1.0.0. Ask for an explicit Entity Expiration within the admitted block range. Access keys and test funds come from the Hub links in README. Never request a private key in chat.
10
+ - Import only public package APIs. Reuse arkiv-chunking's publicly exported write policy; no private dependency imports or copied storage/chunking/gas logic. Bytes use non-indexed bytes columns; query only indexed typed metadata with SDK predicates.
11
+ - Preserve MIME/size/network/integrity/decode checks and public TypeScript types. A browser must decode before upload and after retrieval, render only PNG/JPEG, treat filenames as text and revoke Blob URLs.
12
+ - Every write uses unsigned latest-state estimation, explicit gas/price/latest nonce and at least twice estimate, below half the head block gas limit. Do not use unlimited lifetime or historical state reads. Never automatically retry successful or uncertain writes.
13
+ - Serialize account writes. Recheck browser wallet identity/network before each signature, including the final root. Confirm completion only after storeImage resolves. Preserve error imageKey/manifestKey/transactionHashes, including CONFIRMATION_PENDING. Never display raw provider errors or causes.
14
+ - No independent part mutation, transfer or extension. Root existence does not prove chunk availability; expiration does not erase historical copies.
15
+ - Sample reads keep the access key server-side in its own endpoint. Browser writes use the visitor's configured wallet RPC; no shared server signer.
16
+ - Verify the exact local example, public types, meaningful controlled failure cases, requested live network flow and original/retrieved byte equality. Distinguish controlled RPC, packed archive and registry release. Inspect sample empty/loading/error/recovered states at narrow/medium/wide widths.
17
+ - Keep AGENTS.md, CLAUDE.md and verification in the npm publication allowlist. Explicitly provide consumers the sample guides. No new skill, MCP, generator, encryption or media-management features belong here.
18
+
package/CHANGELOG.md CHANGED
@@ -1,17 +1,27 @@
1
- # Changelog
2
-
3
- ## 1.0.1 — 2026-10-31
4
-
5
- - Correct README vocabulary and package installation guidance for MCP importer compatibility. No runtime changes; the exported implementation `VERSION` remains 1.0.0.
6
-
7
- ## 1.0.0 — 2026-10-31
8
-
9
- - Breaking: SDK 1.0.0 peer requirement and Rabdi testnet, chain ID 7794095.
10
- - Breaking: named `arkiv-images/v2` schema with non-indexed binary data and typed columns. Older image formats are not read by this release.
11
- - Breaking: arkiv-chunking 1.1.0 with its public write policy and latest-state keyset pagination; no historical snapshot guarantee.
12
- - Breaking: maximum admitted lifetime 7,883,970 blocks with an inclusion margin; no unlimited lifetime.
13
- - Public per-write transaction policy, explicit latest estimates/gas/price/nonce, half-block admission ceiling, and recovery keys/hashes when confirmation is pending.
14
- - Persistent createImageWriteOperation state across UI renders/clicks. Submitted hashes are recorded before receipt rechecks; uncertain, successful and terminal results cannot trigger another send for the same operation.
15
- - Sample read proxy keeps its access key server-side; the visitor signs through their own configured wallet RPC. Idempotent synthetic PNG/JPEG seed with a persistent unresolved-write journal.
16
-
17
- Published from the separately reviewed tarball. Registry and source validation are documented in docs/verification.md.
1
+ # Changelog
2
+
3
+ ## 1.0.3
4
+
5
+ - Depends on arkiv-chunking 1.1.2 for the corrected shared write policy, which preserves an uncertain submission hash without resending.
6
+
7
+ ## 1.0.2
8
+
9
+ - Use the long-lived JPEG example, filter expired examples by current block, align VERSION guidance and verify archives using package.json.
10
+
11
+ > Historical version entries below are preserved as recorded; use README.md for the current release.
12
+
13
+ ## 1.0.1 — 2026-10-31
14
+
15
+ - Correct README vocabulary and package installation guidance for MCP importer compatibility. No runtime changes; the exported implementation `VERSION` remains 1.0.0.
16
+
17
+ ## 1.0.0 — 2026-10-31
18
+
19
+ - Breaking: SDK 1.0.0 peer requirement and Rabdi testnet, chain ID 7794095.
20
+ - Breaking: named `arkiv-images/v2` schema with non-indexed binary data and typed columns. Older image formats are not read by this release.
21
+ - Breaking: arkiv-chunking 1.1.0 with its public write policy and latest-state keyset pagination; no historical snapshot guarantee.
22
+ - Breaking: maximum admitted lifetime 7,883,970 blocks with an inclusion margin; no unlimited lifetime.
23
+ - Public per-write transaction policy, explicit latest estimates/gas/price/nonce, half-block admission ceiling, and recovery keys/hashes when confirmation is pending.
24
+ - Persistent createImageWriteOperation state across UI renders/clicks. Submitted hashes are recorded before receipt rechecks; uncertain, successful and terminal results cannot trigger another send for the same operation.
25
+ - Sample read proxy keeps its access key server-side; the visitor signs through their own configured wallet RPC. Idempotent synthetic PNG/JPEG seed with a persistent unresolved-write journal.
26
+
27
+ Published from the separately reviewed tarball. Registry and source validation are documented in docs/verification.md.
package/CLAUDE.md CHANGED
@@ -1 +1 @@
1
- @AGENTS.md
1
+ @AGENTS.md
package/LICENSE CHANGED
@@ -1,21 +1,21 @@
1
- MIT License
2
-
3
- Copyright (c) 2026 Santiago Trujillo Zuluaga
4
-
5
- Permission is hereby granted, free of charge, to any person obtaining a copy
6
- of this software and associated documentation files (the "Software"), to deal
7
- in the Software without restriction, including without limitation the rights
8
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
- copies of the Software, and to permit persons to whom the Software is
10
- furnished to do so, subject to the following conditions:
11
-
12
- The above copyright notice and this permission notice shall be included in all
13
- copies or substantial portions of the Software.
14
-
15
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
- SOFTWARE.
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Santiago Trujillo Zuluaga
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,126 +1,127 @@
1
- # arkiv-images
2
-
3
- Store a public PNG or JPEG as queryable Arkiv entities, then retrieve its original bytes with size, MIME, dimensions and SHA-256 checks. Small images use one entity with a non-indexed `bytes()` column. Larger images use `arkiv-chunking`: an image entity references a file manifest and ordered immutable chunks.
4
-
5
- **Version 1.0.1 targets Rabdi testnet only.** This documentation-only patch preserves the 1.0.0 implementation. See [verification](docs/verification.md) for that published artifact's actual results and limitations.
6
-
7
- ## Install and inspect
8
-
9
- ESM, Node 22.10+ with Web Crypto, or a browser on HTTPS/localhost. Tested dependencies: SDK 1.0.0, viem 2.56.3, arkiv-chunking 1.1.0.
10
-
11
- ```sh
12
- mkdir image-consumer
13
- cd image-consumer
14
- npm init -y
15
- npm pkg set type=module
16
- npm install arkiv-images@1.0.1 @arkiv-network/sdk@1.0.0 viem@2.56.3
17
- ```
18
-
19
- Save `inspect.mjs`:
20
-
21
- ```js
22
- import { inspectImage, planImage } from 'arkiv-images';
23
- const bytes = new Uint8Array(Buffer.from(
24
- 'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR4nGP4V+L2HwAG5AK4X5zdSAAAAABJRU5ErkJggg==', 'base64'));
25
- console.log(inspectImage(bytes));
26
- console.log(planImage(bytes.length));
27
- ```
28
-
29
- `node inspect.mjs` returns PNG, 1 × 1, 70 bytes, inline mode, one entity and one transaction. Inspection is local and needs no wallet, access key or network. Base64 embeds this example only; stored content is binary.
30
-
31
- ## Rabdi round trip
32
-
33
- Rabdi is a DB-Chain testnet, chain ID **7794095**. Obtain your own [access key](https://hub.arkiv.network/access-keys) and [test funds](https://hub.arkiv.network/faucet). Configure a dedicated test wallet locally, outside source control; never send its private key in chat. Missing access keys fail closed with `MissingAccessKeyError`.
34
-
35
- Save `roundtrip.mjs` in the consumer:
36
-
37
- ```js
38
- import assert from 'node:assert/strict';
39
- import { readFile } from 'node:fs/promises';
40
- import { createPublicClient, createWalletClient, MissingAccessKeyError } from '@arkiv-network/sdk';
41
- import { rabdi } from '@arkiv-network/sdk/chains';
42
- import { http } from 'viem';
43
- import { privateKeyToAccount } from 'viem/accounts';
44
- import { inspectImage, planImage, storeImage, retrieveImage } from 'arkiv-images';
45
-
46
- if (!process.env.ARKIV_ACCESS_KEY) throw new MissingAccessKeyError('Rabdi');
47
- if (!process.env.ARKIV_PRIVATE_KEY) throw Error('Configure a dedicated test wallet locally.');
48
- const chain = rabdi({ accessKey: process.env.ARKIV_ACCESS_KEY });
49
- const transport = () => http(chain.rpcUrls.default.http[0], {
50
- retryCount: 0, timeout: 30_000, fetchOptions: { cache: 'no-store' },
51
- });
52
- const publicClient = createPublicClient({ chain, transport: transport() });
53
- const walletClient = createWalletClient({ chain, transport: transport(),
54
- account: privateKeyToAccount(process.env.ARKIV_PRIVATE_KEY) });
55
- const bytes = new Uint8Array(await readFile('example.png'));
56
- console.log(inspectImage(bytes), planImage(bytes.length));
57
- const stored = await storeImage({ publicClient, walletClient, bytes,
58
- filename: 'example.png', expirationBlocks: 3600 });
59
- const recovered = await retrieveImage({ publicClient, imageKey: stored.imageKey,
60
- expectedSha256: stored.sha256 });
61
- assert.deepEqual(recovered.bytes, bytes);
62
- console.log({ imageKey: stored.imageKey, byteExact: true });
63
- ```
64
-
65
- Run `node roundtrip.mjs` after configuring those environment variables locally. All original bytes, filename, metadata and wallet addresses are public, including EXIF/GPS. Use synthetic or non-sensitive images. There is no encryption, metadata stripping, resizing, recompression or read authorization. Entity Expiration removes data from current queries; it does not erase history or copies.
66
-
67
- ## Public API
68
-
69
- | Export | Contract |
70
- |---|---|
71
- | `inspectImage(bytes, expectedContentType?)` | Size, static PNG/JPEG container, MIME and dimensions; no network or complete pixel decode. |
72
- | `planImage(byteLength)` | Inline/chunked mode and measured entity/transaction count. No network. |
73
- | `storeImage(options)` | Requires matching Rabdi public/wallet clients, bytes, basename, explicit `expirationBlocks`; optional content type, progress and per-write `txParams` policy. Returns imageKey, optional manifestKey, sha256, mode, dimensions and observed transaction hashes. |
74
- | `createImageWriteOperation(optionsOrAsyncSetup)` | Create once per intentional upload and keep across UI renders/clicks. `run()` sets sending before setup/network calls and returns the same promise forever, including failed, uncertain or pending outcomes. Read-only state, imageKey and transactionHashes support recovery. |
75
- | `retrieveImage(options)` | Public client, **image** key, optional independently trusted SHA-256 and progress. Returns original bytes, metadata, mode and earliest observed expiration. |
76
- | `ImageSchema`, `IMAGE_TYPE` | SDK 1.0 named schema `arkiv-images/v2`. Binary `data` is stored without an index. Indexed type/mode/mime/sha256/manifest support discovery. Filename, size and dimensions are stored typed columns. |
77
- | `ImageStorageError`, `ChunkingError` | Safe code/message; preserve imageKey/manifestKey and transactionHashes on failure. Raw causes can include provider details: never display or log them. |
78
- | `TransactionParameterPolicy`, `Progress` | Public TypeScript integration types re-exported from chunking. |
79
- | `VERSION`, `INLINE_MAX_BYTES`, `MAX_IMAGE_BYTES`, `MAX_IMAGE_PIXELS`, `MAX_EXPIRATION_BLOCKS`, `RABDI_MAX_LIFETIME_BLOCKS`, `EXPIRATION_INCLUSION_MARGIN_BLOCKS` | Implementation version (`1.0.0` in this documentation-only patch) and enforced limits. |
80
-
81
- Import types such as `ImageInfo`, `ImagePlan`, `StoreImageOptions`, `StoredImage`, `RetrieveImageOptions`, `RetrievedImage`, `ImageContentType` and `ImageErrorCode` directly from `arkiv-images`.
82
-
83
- Limits: **120,000 bytes** inline; **25 MiB** per image; **40,000,000 pixels**. Chunking uses 100,000-byte slices. Larger files require N+3 transactions for N chunks. `planImage` computes this count. These are package admission limits, not a promise of every node's transaction budget.
84
-
85
- Entity Expiration is whole blocks, at least `max(64, transactionCount + 32)` and at most **7,883,970**, leaving 30 blocks below Rabdi's 7,884,000-block maximum. There is no unlimited lifetime. Longer storage requires coordinated Lifetime Extension outside this API. The image root and manifest use the same absolute deadline; retrieval rejects independent deadline changes. Root existence alone does not guarantee availability of every chunk.
86
-
87
- ## Signing and recovery
88
-
89
- Every part and the final root reuse the public `arkiv-chunking` write policy: an unsigned SDK request is estimated at `latest`; gas is at least `max(1_000_000, 2 × estimate)`, with explicit network gasPrice and a fresh latest nonce. The image API additionally stops before signing if that budget exceeds half the latest block gas limit. Longer expiration can substantially increase gas, so shorten Entity Expiration or choose a smaller image if the estimate exceeds that budget. Admitted byte/lifetime limits do not guarantee every combination fits the network's gas budget. `txParams` can increase gas or change fees; it cannot remove required headroom or override the latest nonce. Wallet account/network are checked before every signature.
90
-
91
- Serialize writes from the same account. A retry is permitted only once after an authoritative gas-exhausted reverted receipt and only within the half-block gas ceiling. Successful or uncertain writes are never automatically resent. `CONFIRMATION_PENDING` preserves SDK-success hashes and root/manifest keys if a later receipt lookup is unresolved or disagrees. The operation enters `submitted` before that lookup. Browser consumers must keep a `createImageWriteOperation` object across renders and later clicks; `storeImage` is a one-call convenience for an intentional new upload. Inspect preserved receipts before explicitly creating another operation. The sample resets the operation only when you choose a file again. Other failures can also leave confirmed entities. There is no multi-transaction atomicity, rollback, resumable upload, independent part mutation/transfer/extension or automatic cleanup.
92
-
93
- Retrieval uses latest-state keyset pages and verifies immutable chunks, owner/creator, sequence, lengths, metadata and digest; the manifest is rechecked before return. It does not claim a historical snapshot. An independently trusted digest pins bytes, not the publisher's identity. Missing/corrupt chunks never return a partial image.
94
-
95
- The package checks containers, not full PNG CRC/JPEG entropy decoding or malware. Browser consumers must fully decode before upload and after retrieval, render only PNG/JPEG as images, treat filenames as text, handle decode failure and revoke Blob URLs. JPEG must end at its end-of-image marker; trailing padding is rejected.
96
-
97
- ## Troubleshooting
98
-
99
- | Symptom | Action |
100
- |---|---|
101
- | `MissingAccessKeyError`, HTTP 401/403/429 | Configure your own network access key; check provider quota. The sample keeps its read key server-side. |
102
- | `arkiv serves state at the chain head only` | Use `latest` for state reads, nonce and unsigned estimates; historical block hashes, safe/finalized state are unsupported. |
103
- | Revert with gasUsed equal to gas limit | Use explicit gas with at least twice the latest estimate. The public policy owns the bounded retry; do not resend an uncertain write. |
104
- | `INVALID_INPUT`, `NETWORK_MISMATCH` | Check Rabdi, whole-block lifetime, filename and 32-byte key/digest. |
105
- | `INVALID_IMAGE`, `TOO_LARGE`, browser decode error | Choose a valid static PNG/JPEG within limits. Changing an extension does not convert it. |
106
- | `NOT_FOUND`, `MISSING_CHUNK`, `INCOMPLETE_UPLOAD` | Check the image key, network, referenced parts and expiration; a manifest key is a different key type. |
107
- | `INVALID_ENTITY`, `INVALID_MANIFEST`, `INVALID_CHUNK`, `HASH_MISMATCH` | Do not bypass integrity checks or display partial content. |
108
- | `CONFIRMATION_PENDING`, `UPLOAD_FAILED` | Preserve recovery keys/hashes. Inspect receipts and the wallet before a new upload. |
109
-
110
- ## Sample and development
111
-
112
- The [sample README](sample/README.md) explains the existing `arkiv-images-example` project and visitor-wallet setup. Reads use its own bounded `/api/rpc` endpoint and a server-side `ARKIV_ACCESS_KEY`; the visitor configures their own wallet RPC. No server signer. The sample pins the released registry package `arkiv-images@1.0.0`.
113
-
114
- Give consumer agents [AGENTS.md](AGENTS.md) and [sample/AGENTS.md](sample/AGENTS.md) explicitly. The corresponding CLAUDE.md files only import those guides.
115
-
116
- ```sh
117
- npm ci
118
- npm run typecheck
119
- npm run lint
120
- npm test
121
- npm pack
122
- ```
123
-
124
- Tests distinguish controlled RPC, live Rabdi, packed installs and npm publication in [verification](docs/verification.md). Seed synthetic data with `node scripts/seed-rabdi.mjs` after build and local key configuration; it discovers matching live entities and never retries unresolved journal entries. Keep `.seed-rabdi-state.json` private and persistent between runs.
125
-
126
- Breaking format changes are in [CHANGELOG.md](CHANGELOG.md). Historical evidence under docs stays dated; it is not validation of this candidate.
1
+ # arkiv-images
2
+
3
+ Store a public PNG or JPEG as queryable Arkiv entities, then retrieve its original bytes with size, MIME, dimensions and SHA-256 checks. Small images use one entity with a non-indexed `bytes()` column. Larger images use `arkiv-chunking`: an image entity references a file manifest and ordered immutable chunks.
4
+
5
+ **arkiv-images 1.0.3 targets Rabdi testnet.** It stores PNG/JPEG binary data in `bytes` columns. See [verification](docs/verification.md) for reproducible checks and separately labelled historical observations.
6
+
7
+ ## Install and inspect
8
+
9
+ ESM, Node 22.10+ with Web Crypto, or a browser on HTTPS/localhost. Tested dependencies: SDK 1.0.0, viem 2.56.3, arkiv-chunking 1.1.2.
10
+
11
+ ```sh
12
+ mkdir image-consumer
13
+ cd image-consumer
14
+ npm init -y
15
+ npm pkg set type=module
16
+ npm install arkiv-images@1.0.3 @arkiv-network/sdk@1.0.0 viem@2.56.3
17
+ ```
18
+
19
+ Save `inspect.mjs`:
20
+
21
+ ```js
22
+ import { inspectImage, planImage } from 'arkiv-images';
23
+ const bytes = new Uint8Array(Buffer.from(
24
+ 'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR4nGP4V+L2HwAG5AK4X5zdSAAAAABJRU5ErkJggg==', 'base64'));
25
+ console.log(inspectImage(bytes));
26
+ console.log(planImage(bytes.length));
27
+ ```
28
+
29
+ `node inspect.mjs` returns PNG, 1 × 1, 70 bytes, inline mode, one entity and one transaction. Inspection is local and needs no wallet, access key or network. Base64 embeds this example only; stored content is binary.
30
+
31
+ ## Rabdi round trip
32
+
33
+ Rabdi is a DB-Chain testnet, chain ID **7794095**. Obtain your own [access key](https://hub.arkiv.network/access-keys) and [test funds](https://hub.arkiv.network/faucet). Configure a dedicated test wallet locally, outside source control; never send its private key in chat. Missing access keys fail closed with `MissingAccessKeyError`.
34
+
35
+ Save `roundtrip.mjs` in the consumer:
36
+
37
+ ```js
38
+ import assert from 'node:assert/strict';
39
+ import { readFile } from 'node:fs/promises';
40
+ import { createPublicClient, createWalletClient, MissingAccessKeyError } from '@arkiv-network/sdk';
41
+ import { rabdi } from '@arkiv-network/sdk/chains';
42
+ import { http } from 'viem';
43
+ import { privateKeyToAccount } from 'viem/accounts';
44
+ import { inspectImage, planImage, storeImage, retrieveImage } from 'arkiv-images';
45
+
46
+ if (!process.env.ARKIV_ACCESS_KEY) throw new MissingAccessKeyError('Rabdi');
47
+ if (!process.env.ARKIV_PRIVATE_KEY) throw Error('Configure a dedicated test wallet locally.');
48
+ const chain = rabdi({ accessKey: process.env.ARKIV_ACCESS_KEY });
49
+ const transport = () => http(chain.rpcUrls.default.http[0], {
50
+ retryCount: 0, timeout: 30_000, fetchOptions: { cache: 'no-store' },
51
+ });
52
+ const publicClient = createPublicClient({ chain, transport: transport() });
53
+ const walletClient = createWalletClient({ chain, transport: transport(),
54
+ account: privateKeyToAccount(process.env.ARKIV_PRIVATE_KEY) });
55
+ const bytes = new Uint8Array(await readFile('example.png'));
56
+ console.log(inspectImage(bytes), planImage(bytes.length));
57
+ const stored = await storeImage({ publicClient, walletClient, bytes,
58
+ filename: 'example.png', expirationBlocks: 3600 });
59
+ const recovered = await retrieveImage({ publicClient, imageKey: stored.imageKey,
60
+ expectedSha256: stored.sha256 });
61
+ assert.deepEqual(recovered.bytes, bytes);
62
+ console.log({ imageKey: stored.imageKey, byteExact: true });
63
+ ```
64
+
65
+ Run `node roundtrip.mjs` after configuring those environment variables locally. All original bytes, filename, metadata and wallet addresses are public, including EXIF/GPS. Use synthetic or non-sensitive images. There is no encryption, metadata stripping, resizing, recompression or read authorization. Entity Expiration removes data from current queries; it does not erase history or copies.
66
+
67
+ ## Public API
68
+
69
+ | Export | Contract |
70
+ |---|---|
71
+ | `inspectImage(bytes, expectedContentType?)` | Size, static PNG/JPEG container, MIME and dimensions; no network or complete pixel decode. |
72
+ | `planImage(byteLength)` | Inline/chunked mode and measured entity/transaction count. No network. |
73
+ | `storeImage(options)` | Requires matching Rabdi public/wallet clients, bytes, basename, explicit `expirationBlocks`; optional content type, progress and per-write `txParams` policy. Returns imageKey, optional manifestKey, sha256, mode, dimensions and observed transaction hashes. |
74
+ | `createImageWriteOperation(optionsOrAsyncSetup)` | Create once per intentional upload and keep across UI renders/clicks. `run()` sets sending before setup/network calls and returns the same promise forever, including failed, uncertain or pending outcomes. Read-only state, imageKey and transactionHashes support recovery. |
75
+ | `retrieveImage(options)` | Public client, **image** key, optional independently trusted SHA-256 and progress. Returns original bytes, metadata, mode and earliest observed expiration. |
76
+ | `ImageSchema`, `IMAGE_TYPE` | SDK 1.0 named schema `arkiv-images/v2`. Binary `data` is stored without an index. Indexed type/mode/mime/sha256/manifest support discovery. Filename, size and dimensions are stored typed columns. |
77
+ | `ImageStorageError`, `ChunkingError` | Safe code/message; preserve imageKey/manifestKey and transactionHashes on failure. Raw causes can include provider details: never display or log them. |
78
+ | `TransactionParameterPolicy`, `Progress` | Public TypeScript integration types re-exported from chunking. |
79
+ | `VERSION`, `INLINE_MAX_BYTES`, `MAX_IMAGE_BYTES`, `MAX_IMAGE_PIXELS`, `MAX_EXPIRATION_BLOCKS`, `RABDI_MAX_LIFETIME_BLOCKS`, `EXPIRATION_INCLUSION_MARGIN_BLOCKS` | Implementation version (`1.0.3`) and enforced limits. |
80
+
81
+ Import types such as `ImageInfo`, `ImagePlan`, `StoreImageOptions`, `StoredImage`, `RetrieveImageOptions`, `RetrievedImage`, `ImageContentType` and `ImageErrorCode` directly from `arkiv-images`.
82
+
83
+ Limits: **120,000 bytes** inline; **25 MiB** per image; **40,000,000 pixels**. Chunking uses 100,000-byte slices. Larger files require N+3 transactions for N chunks. `planImage` computes this count. These are package admission limits, not a promise of every node's transaction budget.
84
+
85
+ Entity Expiration is whole blocks, at least `max(64, transactionCount + 32)` and at most **7,883,970**, leaving 30 blocks below Rabdi's 7,884,000-block maximum. There is no unlimited lifetime. Longer storage requires coordinated Lifetime Extension outside this API. The image root and manifest use the same absolute deadline; retrieval rejects independent deadline changes. Root existence alone does not guarantee availability of every chunk.
86
+
87
+ ## Signing and recovery
88
+
89
+ Every part and the final root reuse the public `arkiv-chunking` write policy: an unsigned SDK request is estimated at `latest`; gas is at least `max(1_000_000, 2 × estimate)`, with explicit network gasPrice and a fresh latest nonce. The image API additionally stops before signing if that budget exceeds half the latest block gas limit. Longer expiration can substantially increase gas, so shorten Entity Expiration or choose a smaller image if the estimate exceeds that budget. Admitted byte/lifetime limits do not guarantee every combination fits the network's gas budget. `txParams` can increase gas or change fees; it cannot remove required headroom or override the latest nonce. Wallet account/network are checked before every signature.
90
+
91
+ Serialize writes from the same account. A retry is permitted only once after an authoritative gas-exhausted reverted receipt and only within the half-block gas ceiling. Successful or uncertain writes are never automatically resent. `CONFIRMATION_PENDING` preserves a known submission hash, including one recovered by the shared policy from a wrapped SDK error, plus any available root/manifest keys. SDK-success results enter `submitted` before the receipt recheck; a known-hash SDK error enters `submitted` when the shared policy returns its unresolved confirmation. Observed hashes are recovery evidence, not proof of successful receipts. Browser consumers must keep a `createImageWriteOperation` object across renders and later clicks; `storeImage` is a one-call convenience for an intentional new upload. Inspect preserved receipts before explicitly creating another operation. The sample resets the operation only when you choose a file again. Other failures can also leave confirmed entities. There is no multi-transaction atomicity, rollback, resumable upload, independent part mutation/transfer/extension or automatic cleanup.
92
+
93
+ Retrieval uses latest-state keyset pages and verifies immutable chunks, owner/creator, sequence, lengths, metadata and digest; the manifest is rechecked before return. It does not claim a historical snapshot. An independently trusted digest pins bytes, not the publisher's identity. Missing/corrupt chunks never return a partial image.
94
+
95
+ The package checks containers, not full PNG CRC/JPEG entropy decoding or malware. Browser consumers must fully decode before upload and after retrieval, render only PNG/JPEG as images, treat filenames as text, handle decode failure and revoke Blob URLs. JPEG must end at its end-of-image marker; trailing padding is rejected.
96
+
97
+ ## Troubleshooting
98
+
99
+ | Symptom | Action |
100
+ |---|---|
101
+ | `MissingAccessKeyError`, HTTP 401/403/429 | Configure your own network access key; check provider quota. The sample keeps its read key server-side. |
102
+ | `arkiv serves state at the chain head only` | Use `latest` for state reads, nonce and unsigned estimates; historical block hashes, safe/finalized state are unsupported. |
103
+ | Revert with gasUsed equal to gas limit | Use explicit gas with at least twice the latest estimate. The public policy owns the bounded retry; do not resend an uncertain write. |
104
+ | `INVALID_INPUT`, `NETWORK_MISMATCH` | Check Rabdi, whole-block lifetime, filename and 32-byte key/digest. |
105
+ | `INVALID_IMAGE`, `TOO_LARGE`, browser decode error | Choose a valid static PNG/JPEG within limits. Changing an extension does not convert it. |
106
+ | `NOT_FOUND`, `MISSING_CHUNK`, `INCOMPLETE_UPLOAD` | Check the image key, network, referenced parts and expiration; a manifest key is a different key type. |
107
+ | `INVALID_ENTITY`, `INVALID_MANIFEST`, `INVALID_CHUNK`, `HASH_MISMATCH` | Do not bypass integrity checks or display partial content. |
108
+ | `CONFIRMATION_PENDING`, `UPLOAD_FAILED` | Preserve recovery keys/hashes. Inspect receipts and the wallet before a new upload. |
109
+
110
+ ## Sample and development
111
+
112
+ The [sample README](sample/README.md) explains the existing `arkiv-images-example` project and visitor-wallet setup. Reads use its own bounded `/api/rpc` endpoint and a server-side `ARKIV_ACCESS_KEY`; the visitor configures their own wallet RPC. No server signer. The sample pins the released registry package `arkiv-images@1.0.0`.
113
+
114
+ Give consumer agents [AGENTS.md](AGENTS.md) and [sample/AGENTS.md](sample/AGENTS.md) explicitly. The corresponding CLAUDE.md files only import those guides.
115
+
116
+ ```sh
117
+ npm ci
118
+ npm run typecheck
119
+ npm run lint
120
+ npm test
121
+ npm pack
122
+ ```
123
+
124
+ Tests distinguish controlled RPC, live Rabdi, packed installs and npm publication in [verification](docs/verification.md). Seed synthetic data with `node scripts/seed-rabdi.mjs` after build and local key configuration; it discovers matching live entities and never retries unresolved journal entries. Keep `.seed-rabdi-state.json` private and persistent between runs.
125
+
126
+ Breaking format changes are in [CHANGELOG.md](CHANGELOG.md). Historical evidence under docs stays dated; current checks are specified separately.
127
+
package/dist/index.d.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  import { type Column, type Schema, type TypeTag } from '@arkiv-network/sdk/schema';
2
2
  import { type TransactionParameterPolicy, type ChunkingPublicClient, type ChunkingWalletClient, type Progress } from 'arkiv-chunking';
3
3
  import { type Hex } from 'viem';
4
- export declare const VERSION = "1.0.0";
4
+ export declare const VERSION = "1.0.3";
5
5
  export declare const IMAGE_TYPE = "arkiv-images/v2";
6
6
  type Stored<T extends TypeTag> = Column<T, false, false, false>;
7
7
  type Indexed<T extends TypeTag> = Column<T, true, false, false>;
package/dist/index.js CHANGED
@@ -3,7 +3,7 @@ import { bytes as binary, defineSchema, key, str, u64 } from '@arkiv-network/sdk
3
3
  import { eq } from '@arkiv-network/sdk/query';
4
4
  import { uploadFile, downloadFile, hashFile, ChunkingError, DEFAULT_CHUNK_BYTES, MAX_EXPIRATION_BLOCKS, writeWithPolicy, WriteConfirmationPendingError } from 'arkiv-chunking';
5
5
  import { hexToBytes } from 'viem';
6
- export const VERSION = '1.0.0';
6
+ export const VERSION = '1.0.3';
7
7
  export const IMAGE_TYPE = 'arkiv-images/v2';
8
8
  export const ImageSchema = defineSchema(IMAGE_TYPE, {
9
9
  type: str().indexed(), mode: str().indexed(), mime: str().indexed(), sha256: str().indexed(),
@@ -234,6 +234,12 @@ async function executeStoreImage(options, submitted, observed) {
234
234
  return { ...info, imageKey: stored.entityKey, manifestKey, mode: plan.mode, sha256, transactionHashes };
235
235
  }
236
236
  catch (cause) {
237
+ // The shared policy can recover a submission hash from a wrapped SDK error.
238
+ // Retain that typed pending evidence without treating it as a successful receipt.
239
+ if (cause instanceof WriteConfirmationPendingError || (cause instanceof ChunkingError && cause.code === 'CONFIRMATION_PENDING')) {
240
+ if (hex32(cause.txHash))
241
+ rememberHash(cause.txHash);
242
+ }
237
243
  if (cause instanceof ChunkingError) {
238
244
  manifestKey = cause.manifestKey ?? manifestKey;
239
245
  cause.transactionHashes.forEach(rememberHash);
@@ -241,7 +247,7 @@ async function executeStoreImage(options, submitted, observed) {
241
247
  if (cause instanceof ImageStorageError && !transactionHashes.length)
242
248
  throw cause;
243
249
  const pending = cause instanceof WriteConfirmationPendingError || (cause instanceof ChunkingError && cause.code === 'CONFIRMATION_PENDING');
244
- throw new ImageStorageError(pending ? 'CONFIRMATION_PENDING' : 'UPLOAD_FAILED', pending ? 'The SDK reported a successful write, but receipt confirmation is pending. Preserve the image/manifest keys and hashes; inspect them using reads and never resend automatically.' : 'Upload did not complete. Inspect the wallet and available transaction receipts before retrying; any previously written entities remain.', { cause, manifestKey, imageKey, transactionHashes });
250
+ throw new ImageStorageError(pending ? 'CONFIRMATION_PENDING' : 'UPLOAD_FAILED', pending ? 'A submitted write has unresolved receipt confirmation. Preserve the image/manifest keys and hashes; inspect them using reads and never resend automatically.' : 'Upload did not complete. Inspect the wallet and available transaction receipts before retrying; any previously written entities remain.', { cause, manifestKey, imageKey, transactionHashes });
245
251
  }
246
252
  }
247
253
  function attr(attributes, name, type) {
@@ -1,162 +1,162 @@
1
- {
2
- "network": "Rabdi",
3
- "chainId": 7794095,
4
- "sdk": "1.0.0",
5
- "package": "arkiv-images@1.0.0 candidate",
6
- "verifiedAt": "2026-10-31T03:40:22.639Z",
7
- "queriesKeyA": 24,
8
- "cases": [
9
- {
10
- "filename": "rabdi-public-inline.png",
11
- "imageKey": "0xc4edc02a7bb871a311fe301003a187d49f167ba781ee78993673d60f57e1bc95",
12
- "sha256": "0x31c2782dc1ad3ce9a983c9743ed67337125b433dc08e532da315ed58e63203a4",
13
- "mode": "inline",
14
- "format": "image/png",
15
- "bytes": 120000,
16
- "width": 320,
17
- "height": 200,
18
- "expiresAt": "19707",
19
- "byteExact": true,
20
- "transactions": [
21
- {
22
- "hash": "0x7a653d54754df44815981e22d5ee08d9b95686f09b0594b1f809f40295cec464",
23
- "status": "success",
24
- "gasUsed": "4851921",
25
- "gasLimit": "9783200",
26
- "gasPrice": "1000000000",
27
- "blockNumber": "16110"
28
- }
29
- ]
30
- },
31
- {
32
- "filename": "rabdi-public-chunked.png",
33
- "imageKey": "0x3e1eda66d2cf07867a01fd87a3c78c940e0b9c24bc97f2630aff3fd484ef1996",
34
- "sha256": "0xc99a39198cd5767d1c5443044e09d554cbf6eddea0fb4f3d89c9bee346cfd0e6",
35
- "mode": "chunked",
36
- "format": "image/png",
37
- "bytes": 120001,
38
- "width": 320,
39
- "height": 200,
40
- "expiresAt": "19713",
41
- "byteExact": true,
42
- "transactions": [
43
- {
44
- "hash": "0x7664141c3ebf658fa2edd3366ca7756b551ae16e06a08d78bf343694c831c435",
45
- "status": "success",
46
- "gasUsed": "144496",
47
- "gasLimit": "1000000",
48
- "gasPrice": "1000000000",
49
- "blockNumber": "16115"
50
- },
51
- {
52
- "hash": "0xf2eef3ca59b029c9c133e5c7f28679d52569533dae5ff784d50bc6a7688ab4cd",
53
- "status": "success",
54
- "gasUsed": "4037190",
55
- "gasLimit": "8140802",
56
- "gasPrice": "1000000000",
57
- "blockNumber": "16119"
58
- },
59
- {
60
- "hash": "0x9258c904e6d71f081aa5bbdc7090f3b02ceccb3aaef1a611f647b38f4a2e2f0d",
61
- "status": "success",
62
- "gasUsed": "840804",
63
- "gasLimit": "1697290",
64
- "gasPrice": "1000000000",
65
- "blockNumber": "16122"
66
- },
67
- {
68
- "hash": "0xdd691739009a9ce7d8f3cc62c5e3cfdc3dfdfc64294fb6319aaaff77f329bde4",
69
- "status": "success",
70
- "gasUsed": "47091",
71
- "gasLimit": "1000000",
72
- "gasPrice": "1000000000",
73
- "blockNumber": "16126"
74
- },
75
- {
76
- "hash": "0x17345e1d64a6664d67900b22b3c4e9f58a738b007f96644198e93e0f5eb95f72",
77
- "status": "success",
78
- "gasUsed": "160684",
79
- "gasLimit": "1000000",
80
- "gasPrice": "1000000000",
81
- "blockNumber": "16129"
82
- }
83
- ]
84
- },
85
- {
86
- "filename": "rabdi-public-inline.jpg",
87
- "imageKey": "0x2989bda8bbadfe7e22a0b1e3a435aa3b266106adbbca3f97c162ee4eccca8488",
88
- "sha256": "0x6505b34edf4866c89d73791c5b775ee8588ba62ba54249051891d7833d3b1684",
89
- "mode": "inline",
90
- "format": "image/jpeg",
91
- "bytes": 2977,
92
- "width": 320,
93
- "height": 200,
94
- "expiresAt": "7900102",
95
- "byteExact": true,
96
- "transactions": [
97
- {
98
- "hash": "0xce4076bcb59b789493b5224d99cc4824fbf59b1538771d14b933600c56c201ed",
99
- "status": "success",
100
- "gasUsed": "861622",
101
- "gasLimit": "1739256",
102
- "gasPrice": "1000000000",
103
- "blockNumber": "16135"
104
- }
105
- ]
106
- },
107
- {
108
- "filename": "rabdi-public-chunked.jpg",
109
- "imageKey": "0xa8d93ed220c49a0872f288b35da9494d45b06fe21ec13edb747d38196aaeaf53",
110
- "sha256": "0x54053f8b9fa8fbf575fc21b68502276e8a4ca249312cd34d292827518fee07a0",
111
- "mode": "chunked",
112
- "format": "image/jpeg",
113
- "bytes": 130001,
114
- "width": 320,
115
- "height": 200,
116
- "expiresAt": "19737",
117
- "byteExact": true,
118
- "transactions": [
119
- {
120
- "hash": "0x7773e2e9657abd063cb91799077002f910bda08ac210ba97b5585bfcf16fd819",
121
- "status": "success",
122
- "gasUsed": "144519",
123
- "gasLimit": "1000000",
124
- "gasPrice": "1000000000",
125
- "blockNumber": "16140"
126
- },
127
- {
128
- "hash": "0xba0514e0365dd6c5e43ba39286fb7b1d50a96f7ef6dc541b45eaa46d26cf49a8",
129
- "status": "success",
130
- "gasUsed": "4044719",
131
- "gasLimit": "8155982",
132
- "gasPrice": "1000000000",
133
- "blockNumber": "16144"
134
- },
135
- {
136
- "hash": "0x8167c8d43bff578f54e97c4f5f3cadc341270e7d6d96fec750133615f8409268",
137
- "status": "success",
138
- "gasUsed": "1230616",
139
- "gasLimit": "2483102",
140
- "gasPrice": "1000000000",
141
- "blockNumber": "16147"
142
- },
143
- {
144
- "hash": "0x9a89cef71177ecf549e2e9cb1fceb1bae80c33b8b4d4e9bca016ccc74232adf2",
145
- "status": "success",
146
- "gasUsed": "47096",
147
- "gasLimit": "1000000",
148
- "gasPrice": "1000000000",
149
- "blockNumber": "16150"
150
- },
151
- {
152
- "hash": "0x5a5ffc1a691d0f362d2d3100a880ec05e59022265aaf41453523f89877767ebf",
153
- "status": "success",
154
- "gasUsed": "158710",
155
- "gasLimit": "1000000",
156
- "gasPrice": "1000000000",
157
- "blockNumber": "16152"
158
- }
159
- ]
160
- }
161
- ]
162
- }
1
+ {
2
+ "network": "Rabdi",
3
+ "chainId": 7794095,
4
+ "sdk": "1.0.0",
5
+ "package": "arkiv-images@1.0.0 candidate",
6
+ "verifiedAt": "2026-10-31T03:40:22.639Z",
7
+ "queriesKeyA": 24,
8
+ "cases": [
9
+ {
10
+ "filename": "rabdi-public-inline.png",
11
+ "imageKey": "0xc4edc02a7bb871a311fe301003a187d49f167ba781ee78993673d60f57e1bc95",
12
+ "sha256": "0x31c2782dc1ad3ce9a983c9743ed67337125b433dc08e532da315ed58e63203a4",
13
+ "mode": "inline",
14
+ "format": "image/png",
15
+ "bytes": 120000,
16
+ "width": 320,
17
+ "height": 200,
18
+ "expiresAt": "19707",
19
+ "byteExact": true,
20
+ "transactions": [
21
+ {
22
+ "hash": "0x7a653d54754df44815981e22d5ee08d9b95686f09b0594b1f809f40295cec464",
23
+ "status": "success",
24
+ "gasUsed": "4851921",
25
+ "gasLimit": "9783200",
26
+ "gasPrice": "1000000000",
27
+ "blockNumber": "16110"
28
+ }
29
+ ]
30
+ },
31
+ {
32
+ "filename": "rabdi-public-chunked.png",
33
+ "imageKey": "0x3e1eda66d2cf07867a01fd87a3c78c940e0b9c24bc97f2630aff3fd484ef1996",
34
+ "sha256": "0xc99a39198cd5767d1c5443044e09d554cbf6eddea0fb4f3d89c9bee346cfd0e6",
35
+ "mode": "chunked",
36
+ "format": "image/png",
37
+ "bytes": 120001,
38
+ "width": 320,
39
+ "height": 200,
40
+ "expiresAt": "19713",
41
+ "byteExact": true,
42
+ "transactions": [
43
+ {
44
+ "hash": "0x7664141c3ebf658fa2edd3366ca7756b551ae16e06a08d78bf343694c831c435",
45
+ "status": "success",
46
+ "gasUsed": "144496",
47
+ "gasLimit": "1000000",
48
+ "gasPrice": "1000000000",
49
+ "blockNumber": "16115"
50
+ },
51
+ {
52
+ "hash": "0xf2eef3ca59b029c9c133e5c7f28679d52569533dae5ff784d50bc6a7688ab4cd",
53
+ "status": "success",
54
+ "gasUsed": "4037190",
55
+ "gasLimit": "8140802",
56
+ "gasPrice": "1000000000",
57
+ "blockNumber": "16119"
58
+ },
59
+ {
60
+ "hash": "0x9258c904e6d71f081aa5bbdc7090f3b02ceccb3aaef1a611f647b38f4a2e2f0d",
61
+ "status": "success",
62
+ "gasUsed": "840804",
63
+ "gasLimit": "1697290",
64
+ "gasPrice": "1000000000",
65
+ "blockNumber": "16122"
66
+ },
67
+ {
68
+ "hash": "0xdd691739009a9ce7d8f3cc62c5e3cfdc3dfdfc64294fb6319aaaff77f329bde4",
69
+ "status": "success",
70
+ "gasUsed": "47091",
71
+ "gasLimit": "1000000",
72
+ "gasPrice": "1000000000",
73
+ "blockNumber": "16126"
74
+ },
75
+ {
76
+ "hash": "0x17345e1d64a6664d67900b22b3c4e9f58a738b007f96644198e93e0f5eb95f72",
77
+ "status": "success",
78
+ "gasUsed": "160684",
79
+ "gasLimit": "1000000",
80
+ "gasPrice": "1000000000",
81
+ "blockNumber": "16129"
82
+ }
83
+ ]
84
+ },
85
+ {
86
+ "filename": "rabdi-public-inline.jpg",
87
+ "imageKey": "0x2989bda8bbadfe7e22a0b1e3a435aa3b266106adbbca3f97c162ee4eccca8488",
88
+ "sha256": "0x6505b34edf4866c89d73791c5b775ee8588ba62ba54249051891d7833d3b1684",
89
+ "mode": "inline",
90
+ "format": "image/jpeg",
91
+ "bytes": 2977,
92
+ "width": 320,
93
+ "height": 200,
94
+ "expiresAt": "7900102",
95
+ "byteExact": true,
96
+ "transactions": [
97
+ {
98
+ "hash": "0xce4076bcb59b789493b5224d99cc4824fbf59b1538771d14b933600c56c201ed",
99
+ "status": "success",
100
+ "gasUsed": "861622",
101
+ "gasLimit": "1739256",
102
+ "gasPrice": "1000000000",
103
+ "blockNumber": "16135"
104
+ }
105
+ ]
106
+ },
107
+ {
108
+ "filename": "rabdi-public-chunked.jpg",
109
+ "imageKey": "0xa8d93ed220c49a0872f288b35da9494d45b06fe21ec13edb747d38196aaeaf53",
110
+ "sha256": "0x54053f8b9fa8fbf575fc21b68502276e8a4ca249312cd34d292827518fee07a0",
111
+ "mode": "chunked",
112
+ "format": "image/jpeg",
113
+ "bytes": 130001,
114
+ "width": 320,
115
+ "height": 200,
116
+ "expiresAt": "19737",
117
+ "byteExact": true,
118
+ "transactions": [
119
+ {
120
+ "hash": "0x7773e2e9657abd063cb91799077002f910bda08ac210ba97b5585bfcf16fd819",
121
+ "status": "success",
122
+ "gasUsed": "144519",
123
+ "gasLimit": "1000000",
124
+ "gasPrice": "1000000000",
125
+ "blockNumber": "16140"
126
+ },
127
+ {
128
+ "hash": "0xba0514e0365dd6c5e43ba39286fb7b1d50a96f7ef6dc541b45eaa46d26cf49a8",
129
+ "status": "success",
130
+ "gasUsed": "4044719",
131
+ "gasLimit": "8155982",
132
+ "gasPrice": "1000000000",
133
+ "blockNumber": "16144"
134
+ },
135
+ {
136
+ "hash": "0x8167c8d43bff578f54e97c4f5f3cadc341270e7d6d96fec750133615f8409268",
137
+ "status": "success",
138
+ "gasUsed": "1230616",
139
+ "gasLimit": "2483102",
140
+ "gasPrice": "1000000000",
141
+ "blockNumber": "16147"
142
+ },
143
+ {
144
+ "hash": "0x9a89cef71177ecf549e2e9cb1fceb1bae80c33b8b4d4e9bca016ccc74232adf2",
145
+ "status": "success",
146
+ "gasUsed": "47096",
147
+ "gasLimit": "1000000",
148
+ "gasPrice": "1000000000",
149
+ "blockNumber": "16150"
150
+ },
151
+ {
152
+ "hash": "0x5a5ffc1a691d0f362d2d3100a880ec05e59022265aaf41453523f89877767ebf",
153
+ "status": "success",
154
+ "gasUsed": "158710",
155
+ "gasLimit": "1000000",
156
+ "gasPrice": "1000000000",
157
+ "blockNumber": "16152"
158
+ }
159
+ ]
160
+ }
161
+ ]
162
+ }
@@ -1,46 +1,56 @@
1
- # Rabdi release verification
2
-
3
- Release: **arkiv-images 1.0.0**, SDK **1.0.0**, viem **2.56.3**, exact registry **arkiv-chunking 1.1.0**. The separately approved tarball was published without rebuilding: SHA-256 `c4c5417edb32179f7cc2f39b2e959711103925f019a0bd0ba62e84593826c110`, npm shasum `ff85d022ad0d3e074f002fec06a52cb2914b1d34`. Registry installation is checked independently from the earlier candidate installation.
4
-
5
- ## Library and operation tests
6
-
7
- Linux ARM64, Node 24.22.1: typecheck, build, syntax/current-guidance lint and **51 tests passed**. Tests exercise actual SDK encoding against a controlled in-memory RPC. They cover PNG/JPEG validation, size/MIME/dimension limits, inline/chunked exact-byte reconstruction, the 25 MiB ceiling, keyset pagination, wrong network, malformed chain IDs, missing/corrupt chunks, root/manifest changes, safe errors and browser-wallet guards.
8
-
9
- The persistent operation is the same public API used by the sample. Binding send-count tests passed:
10
-
11
- | Outcome | Sends for the same operation |
12
- |---|---|
13
- | Authoritative non-gas revert | 1 |
14
- | Authoritative gas-exhausted revert | 2, with doubled/re-estimated gas inside the half-block ceiling |
15
- | SDK success then disagreeing receipt | 1; submitted hash preserved before lookup |
16
- | SDK success then null receipt, followed by another click | 1; the same rejected promise and recovery keys remain |
17
- | Uncertain transport outcome | 1; another click cannot resend |
18
-
19
- An additional test proves asynchronous wallet/date setup starts only after the persistent sending guard is stored. Full decoding and rendered behavior require the separate browser gate; container/unit tests do not certify a browser decoder.
20
-
21
- Six regression cases cover SDK errors carrying an uncertain transaction hash at the inline root, second chunk and manifest finalization, with receipt lookups returning null or throwing. The operation retains every observed hash before the lookup, the final error exposes it exactly once, prior manifest recovery remains available, and later clicks reuse the same promise without resending. Images observes only the public wallet `createEntity`/`patchEntity` boundary; chunking 1.1.0 still owns estimates, gas, receipts and retries. The new regressions failed before this correction.
22
-
23
- ## Actual Rabdi evidence
24
-
25
- [rabdi-evidence.json](rabdi-evidence.json) contains four live cases and authoritative successful receipts:
26
-
27
- | Format | Storage | Original bytes | Expiration used |
28
- |---|---|---:|---|
29
- | PNG | Inline | 120,000 | 3,600 blocks |
30
- | PNG | Chunked | 120,001 | 3,600 blocks |
31
- | JPEG | Inline | 2,977 | Maximum admitted 7,883,970 blocks, less inclusion delay |
32
- | JPEG | Chunked | 130,001 | 3,600 blocks |
33
-
34
- Each original/retrieved byte array was compared exactly and its digest verified. Every receipt status was successful; every transaction had gas at least twice measured usage and no more than half the 30,000,000 head gas limit. Across 12 confirmed writes, maximum gasUsed was **4,851,921** and maximum submitted gas was **9,783,200**. A second complete seed run returned the same image keys and transaction hashes without another write. Both runs used the dedicated image test wallet; no funder signing.
35
-
36
- The largest image tested live is 130,001 bytes. The 25 MiB maximum was tested against controlled RPC, not submitted to Rabdi. The large synthetic samples expire soon; rerun the idempotent seed after expiration or a network reset. A root key alone is not a promise of availability.
37
-
38
- Long expiration materially affects estimates: unsigned 120,000/100,000/70,000-byte maximum-lifetime probes estimated 18,539,745 / 15,508,335 / 10,961,427 gas. Their doubled budgets exceeded the admission ceiling and were rejected before signing. The gate was preserved; large live cases used a shorter lifetime, while the small JPEG verified a maximum-lifetime create.
39
-
40
- ## Artifacts and sample gate
41
-
42
- `node scripts/verify-candidate.mjs --registry` requires the existing live evidence, runs library gates, verifies the registry archive against the approved SHA-256/SHA-512/shasum, installs exact registry 1.0.0 in a clean consumer and runs the exact README inspection example, public TypeScript imports and linked-guide presence checks. It compares installed JavaScript with the built source, performs a clean sample install and builds it. Its registry artifact report distinguishes these checks from the earlier packed-candidate tests. The sample pins registry images 1.0.0 and chunking 1.1.0 directly; no local tarball is used.
43
-
44
- The sample uses the existing arkiv-images-example project. Read access is through its bounded server-side endpoint with a server-only access key; browser writes use the visitor's configured Rabdi wallet. Hydrated deployment validation, production and rollback IDs are recorded separately in the release handoff. Live browser retrieval, local input/decode failures and controlled wallet/operation tests are different checks; neither replaces the other. Browser-wallet signing was not exercised in the migration checks.
45
-
46
- Historical dated evidence remains under docs and is not validation of this release. The previous chunking 1.0.0 public-export failure was resolved by the separately published 1.1.0; no private dependency import, copied gas policy or relaxed acceptance rule was used.
1
+ # Verify arkiv-images 1.0.3
2
+
3
+ Use Node 22.10+ on the DGX. From this checkout run `npm ci --no-audit --no-fund`, then `node scripts/verify-candidate.mjs`. The script reads package.json, runs library gates, packs that exact version, installs it in a private clean consumer, checks runtime VERSION and public TypeScript imports, and builds an isolated copy of the sample using the same archive. It preserves the sample registry pin and lockfile.
4
+
5
+ To check a registry installation of the same version, run `node scripts/verify-candidate.mjs --registry`. It verifies registry shasum/integrity and byte equality with this source build. Both modes require the registered DGX job private TMPDIR and perform no chain writes. Run `node scripts/vocabulary-guard.mjs` and `node scripts/vocabulary-build-mutation.mjs` for complete-tree and source-to-bundle checks.
6
+
7
+ The public example selects the longer-lived inline JPEG `0x2989bda8bbadfe7e22a0b1e3a435aa3b266106adbbca3f97c162ee4eccca8488` and filters entries using the current block. The short-lived PNG and chunked examples in the old measurements have expired; those measurements are not an availability promise.
8
+
9
+ > Historical 1.0.0 measurements and procedure below are preserved from 51f4905; use the current procedure above for 1.0.3.
10
+
11
+ # Rabdi release verification
12
+
13
+ Release: **arkiv-images 1.0.0**, SDK **1.0.0**, viem **2.56.3**, exact registry **arkiv-chunking 1.1.0**. The separately approved tarball was published without rebuilding: SHA-256 `c4c5417edb32179f7cc2f39b2e959711103925f019a0bd0ba62e84593826c110`, npm shasum `ff85d022ad0d3e074f002fec06a52cb2914b1d34`. Registry installation is checked independently from the earlier candidate installation.
14
+
15
+ ## Library and operation tests
16
+
17
+ Linux ARM64, Node 24.22.1: typecheck, build, syntax/current-guidance lint and **51 tests passed**. Tests exercise actual SDK encoding against a controlled in-memory RPC. They cover PNG/JPEG validation, size/MIME/dimension limits, inline/chunked exact-byte reconstruction, the 25 MiB ceiling, keyset pagination, wrong network, malformed chain IDs, missing/corrupt chunks, root/manifest changes, safe errors and browser-wallet guards.
18
+
19
+ The persistent operation is the same public API used by the sample. Binding send-count tests passed:
20
+
21
+ | Outcome | Sends for the same operation |
22
+ |---|---|
23
+ | Authoritative non-gas revert | 1 |
24
+ | Authoritative gas-exhausted revert | 2, with doubled/re-estimated gas inside the half-block ceiling |
25
+ | SDK success then disagreeing receipt | 1; submitted hash preserved before lookup |
26
+ | SDK success then null receipt, followed by another click | 1; the same rejected promise and recovery keys remain |
27
+ | Uncertain transport outcome | 1; another click cannot resend |
28
+
29
+ An additional test proves asynchronous wallet/date setup starts only after the persistent sending guard is stored. Full decoding and rendered behavior require the separate browser gate; container/unit tests do not certify a browser decoder.
30
+
31
+ Six regression cases cover SDK errors carrying an uncertain transaction hash at the inline root, second chunk and manifest finalization, with receipt lookups returning null or throwing. The operation retains every observed hash before the lookup, the final error exposes it exactly once, prior manifest recovery remains available, and later clicks reuse the same promise without resending. Images observes only the public wallet `createEntity`/`patchEntity` boundary; chunking 1.1.0 still owns estimates, gas, receipts and retries. The new regressions failed before this correction.
32
+
33
+ ## Actual Rabdi evidence
34
+
35
+ [rabdi-evidence.json](rabdi-evidence.json) contains four live cases and authoritative successful receipts:
36
+
37
+ | Format | Storage | Original bytes | Expiration used |
38
+ |---|---|---:|---|
39
+ | PNG | Inline | 120,000 | 3,600 blocks |
40
+ | PNG | Chunked | 120,001 | 3,600 blocks |
41
+ | JPEG | Inline | 2,977 | Maximum admitted 7,883,970 blocks, less inclusion delay |
42
+ | JPEG | Chunked | 130,001 | 3,600 blocks |
43
+
44
+ Each original/retrieved byte array was compared exactly and its digest verified. Every receipt status was successful; every transaction had gas at least twice measured usage and no more than half the 30,000,000 head gas limit. Across 12 confirmed writes, maximum gasUsed was **4,851,921** and maximum submitted gas was **9,783,200**. A second complete seed run returned the same image keys and transaction hashes without another write. Both runs used the dedicated image test wallet; no funder signing.
45
+
46
+ The largest image tested live is 130,001 bytes. The 25 MiB maximum was tested against controlled RPC, not submitted to Rabdi. The large synthetic samples expire soon; rerun the idempotent seed after expiration or a network reset. A root key alone is not a promise of availability.
47
+
48
+ Long expiration materially affects estimates: unsigned 120,000/100,000/70,000-byte maximum-lifetime probes estimated 18,539,745 / 15,508,335 / 10,961,427 gas. Their doubled budgets exceeded the admission ceiling and were rejected before signing. The gate was preserved; large live cases used a shorter lifetime, while the small JPEG verified a maximum-lifetime create.
49
+
50
+ ## Artifacts and sample gate
51
+
52
+ `node scripts/verify-candidate.mjs --registry` requires the existing live evidence, runs library gates, verifies the registry archive against the approved SHA-256/SHA-512/shasum, installs exact registry 1.0.0 in a clean consumer and runs the exact README inspection example, public TypeScript imports and linked-guide presence checks. It compares installed JavaScript with the built source, performs a clean sample install and builds it. Its registry artifact report distinguishes these checks from the earlier packed-candidate tests. The sample pins registry images 1.0.0 and chunking 1.1.0 directly; no local tarball is used.
53
+
54
+ The sample uses the existing arkiv-images-example project. Read access is through its bounded server-side endpoint with a server-only access key; browser writes use the visitor's configured Rabdi wallet. Hydrated deployment validation, production and rollback IDs are recorded separately in the release handoff. Live browser retrieval, local input/decode failures and controlled wallet/operation tests are different checks; neither replaces the other. Browser-wallet signing was not exercised in the migration checks.
55
+
56
+ Historical dated evidence remains under docs and is not validation of this release. The previous chunking 1.0.0 public-export failure was resolved by the separately published 1.1.0; no private dependency import, copied gas policy or relaxed acceptance rule was used.
package/package.json CHANGED
@@ -1,57 +1,57 @@
1
- {
2
- "name": "arkiv-images",
3
- "version": "1.0.1",
4
- "description": "Store public PNG and JPEG images as Arkiv entities and retrieve verified original bytes.",
5
- "type": "module",
6
- "exports": {
7
- ".": {
8
- "types": "./dist/index.d.ts",
9
- "import": "./dist/index.js"
10
- },
11
- "./package.json": "./package.json"
12
- },
13
- "files": [
14
- "dist",
15
- "README.md",
16
- "AGENTS.md",
17
- "CLAUDE.md",
18
- "sample/README.md",
19
- "sample/AGENTS.md",
20
- "sample/CLAUDE.md",
21
- "LICENSE",
22
- "docs/verification.md",
23
- "docs/rabdi-evidence.json",
24
- "CHANGELOG.md"
25
- ],
26
- "sideEffects": false,
27
- "engines": {
28
- "node": ">=22.10.0"
29
- },
30
- "license": "MIT",
31
- "repository": {
32
- "type": "git",
33
- "url": "git+https://github.com/SantiagoDevRel/arkiv-images.git"
34
- },
35
- "scripts": {
36
- "build": "tsc",
37
- "typecheck": "tsc --noEmit",
38
- "lint": "node scripts/lint.mjs",
39
- "test": "npm run build && node --test test/*.test.mjs",
40
- "test:browser": "node test/browser-smoke.mjs",
41
- "prepack": "npm run build"
42
- },
43
- "dependencies": {
44
- "arkiv-chunking": "1.1.0"
45
- },
46
- "peerDependencies": {
47
- "@arkiv-network/sdk": "^1.0.0",
48
- "viem": "^2.56.3"
49
- },
50
- "devDependencies": {
51
- "@arkiv-network/sdk": "1.0.0",
52
- "@types/node": "22.19.13",
53
- "playwright": "1.62.1",
54
- "typescript": "5.9.3",
55
- "viem": "2.56.3"
56
- }
57
- }
1
+ {
2
+ "name": "arkiv-images",
3
+ "version": "1.0.3",
4
+ "description": "Store public PNG and JPEG images as Arkiv entities and retrieve verified original bytes.",
5
+ "type": "module",
6
+ "exports": {
7
+ ".": {
8
+ "types": "./dist/index.d.ts",
9
+ "import": "./dist/index.js"
10
+ },
11
+ "./package.json": "./package.json"
12
+ },
13
+ "files": [
14
+ "dist",
15
+ "README.md",
16
+ "AGENTS.md",
17
+ "CLAUDE.md",
18
+ "sample/README.md",
19
+ "sample/AGENTS.md",
20
+ "sample/CLAUDE.md",
21
+ "LICENSE",
22
+ "docs/verification.md",
23
+ "docs/rabdi-evidence.json",
24
+ "CHANGELOG.md"
25
+ ],
26
+ "sideEffects": false,
27
+ "engines": {
28
+ "node": ">=22.10.0"
29
+ },
30
+ "license": "MIT",
31
+ "repository": {
32
+ "type": "git",
33
+ "url": "git+https://github.com/SantiagoDevRel/arkiv-images.git"
34
+ },
35
+ "scripts": {
36
+ "build": "tsc",
37
+ "typecheck": "tsc --noEmit",
38
+ "lint": "node scripts/lint.mjs",
39
+ "test": "npm run build && node --test test/*.test.mjs",
40
+ "test:browser": "node test/browser-smoke.mjs",
41
+ "prepack": "npm run build"
42
+ },
43
+ "dependencies": {
44
+ "arkiv-chunking": "1.1.2"
45
+ },
46
+ "peerDependencies": {
47
+ "@arkiv-network/sdk": "^1.0.0",
48
+ "viem": "^2.56.3"
49
+ },
50
+ "devDependencies": {
51
+ "@arkiv-network/sdk": "1.0.0",
52
+ "@types/node": "22.19.13",
53
+ "playwright": "1.62.1",
54
+ "typescript": "5.9.3",
55
+ "viem": "2.56.3"
56
+ }
57
+ }
package/sample/AGENTS.md CHANGED
@@ -1,15 +1,15 @@
1
- Edit instructions only in AGENTS.md; CLAUDE.md only imports this file.
2
-
3
- # Integrating the image sample
4
-
5
- Read README.md here and the package README/AGENTS.md before changing the sample. Preserve its existing visual design and English Arkiv copy.
6
-
7
- - Run `npm ci` in sample; it pins the registry release arkiv-images 1.0.0, SDK 1.0.0 and chunking 1.1.0. Never import sibling source or duplicate package storage, chunking or transaction policy.
8
- - Retrieval is read-only through /api/rpc. Set ARKIV_ACCESS_KEY server-side only; no browser access key, arbitrary RPC destination, signing method, batch request, historical-state query or raw provider error may pass the proxy.
9
- - Browser writes use MetaMask/Rabby and the visitor's account/Rabdi RPC. Ask the visitor to configure their own key in their wallet if Rabdi is missing. Never request a private key or add a server signer. Guard account/network before each signature; never switch halfway through a write.
10
- - Keep the selected file's createImageWriteOperation across clicks/re-renders, including failed or uncertain results. Setup runs after its sending guard is stored. Only an explicit new file choice creates another upload operation. Preserve submitted hashes before receipt rechecks; only an authoritative gas-exhausted revert permits the shared policy's one bounded retry.
11
- - Preserve the safe public-image picker, full browser decode on both sides, measured transaction count, local-date expiration adapter, checks against the package lifetime limits, visible recovery keys/hashes and object URL cleanup.
12
- - Inspection reads actual latest-state entities after verified package retrieval. Display stored bytes as text, expose typed metadata accurately, and keep inspection errors separate from verified image status. Do not claim a pinned snapshot or mark all attributes indexed.
13
- - Fixed explorer origin, validated 32-byte keys, safe link rel values; no filename HTML or uploaded SVG/HTML rendering.
14
- - Run the documented checks, then inspect 390/768/1440 widths, dark/light, keyboard and 200% zoom, loading/empty/error/recovered states. State what was controlled RPC and what was live. Preserve real failures.
15
- - scripts/seed-rabdi.mjs at package root is idempotent through on-chain discovery and a private persistent unresolved-write journal. Never erase the journal to repeat a write.
1
+ Edit instructions only in AGENTS.md; CLAUDE.md only imports this file.
2
+
3
+ # Integrating the image sample
4
+
5
+ Read README.md here and the package README/AGENTS.md before changing the sample. Preserve its existing visual design and English Arkiv copy.
6
+
7
+ - Run `npm ci` in sample; it pins the registry release arkiv-images 1.0.0, SDK 1.0.0 and chunking 1.1.0. Never import sibling source or duplicate package storage, chunking or transaction policy.
8
+ - Retrieval is read-only through /api/rpc. Set ARKIV_ACCESS_KEY server-side only; no browser access key, arbitrary RPC destination, signing method, batch request, historical-state query or raw provider error may pass the proxy.
9
+ - Browser writes use MetaMask/Rabby and the visitor's account/Rabdi RPC. Ask the visitor to configure their own key in their wallet if Rabdi is missing. Never request a private key or add a server signer. Guard account/network before each signature; never switch halfway through a write.
10
+ - Keep the selected file's createImageWriteOperation across clicks/re-renders, including failed or uncertain results. Setup runs after its sending guard is stored. Only an explicit new file choice creates another upload operation. Preserve submitted hashes before receipt rechecks; only an authoritative gas-exhausted revert permits the shared policy's one bounded retry.
11
+ - Preserve the safe public-image picker, full browser decode on both sides, measured transaction count, local-date expiration adapter, checks against the package lifetime limits, visible recovery keys/hashes and object URL cleanup.
12
+ - Inspection reads actual latest-state entities after verified package retrieval. Display stored bytes as text, expose typed metadata accurately, and keep inspection errors separate from verified image status. Do not claim a pinned snapshot or mark all attributes indexed.
13
+ - Fixed explorer origin, validated 32-byte keys, safe link rel values; no filename HTML or uploaded SVG/HTML rendering.
14
+ - Run the documented checks, then inspect 390/768/1440 widths, dark/light, keyboard and 200% zoom, loading/empty/error/recovered states. State what was controlled RPC and what was live. Preserve real failures.
15
+ - scripts/seed-rabdi.mjs at package root is idempotent through on-chain discovery and a private persistent unresolved-write journal. Never erase the journal to repeat a write.
package/sample/CLAUDE.md CHANGED
@@ -1 +1 @@
1
- @AGENTS.md
1
+ @AGENTS.md
package/sample/README.md CHANGED
@@ -1,30 +1,32 @@
1
- # Image sample — Rabdi
2
-
3
- This sample uses the registry release **arkiv-images 1.0.0** and keeps the image picker, local-date Entity Expiration, byte verification, download, entity inspector, dark/light themes and wallet selection. All copy is English. It targets the existing **arkiv-images-example** Vercel project at https://arkiv-images-example.vercel.app.
4
-
5
- The UI uses the package public API. Reads go through its own bounded `/api/rpc` function with server-only `ARKIV_ACCESS_KEY`. Only chain ID, block timing and bounded image/chunk queries are accepted; no signer, access key, upstream URL or raw provider error is sent to the browser. Root and manifest reads are latest state; chunks use keyset pages. Set a free [Hub access key](https://hub.arkiv.network/access-keys) in the Vercel preview environment.
6
-
7
- Writes use the visitor's MetaMask/Rabby account and configured Rabdi RPC, chain 7794095. If Rabdi is missing, configure it in the wallet using your own access key, then connect again. The app switches to an already configured Rabdi network; it never installs a server key in a wallet. Obtain [test funds](https://hub.arkiv.network/faucet). Every actual signature is guarded for account/network changes and uses the package's explicit gas policy.
8
-
9
- ## Build the sample
10
-
11
- From the sample directory:
12
-
13
- ```sh
14
- npm ci
15
- npm run build
16
- ```
17
-
18
- The sample pins `arkiv-images` to exact registry version `1.0.0`; its lock verifies the published archive integrity. No local tarball is used or uploaded. Deploy the sample folder to the existing linked project; its publication allowlist includes the application source and API function.
19
-
20
- The Vite development server alone does not serve Vercel functions. Use a Vercel preview for the complete real-network read flow; controlled browser tests route `/api/rpc` to the in-memory harness. Never put an access key in Vite environment variables.
21
-
22
- Seed non-sensitive PNG/JPEG examples with `node scripts/seed-rabdi.mjs` from the package root after configuring local test credentials. Keep `.seed-rabdi-state.json` between runs; unresolved writes block further seed writes. Successful seeding writes `sample/public/samples.json` and `docs/rabdi-evidence.json`; the Retrieve tab offers a public sample only when that manifest exists. A Rabdi reset requires rerunning the command, not hardcoded assumptions that old entity keys still exist.
23
-
24
- The sample fully decodes images before writes and after retrieval, displays only allowed raster MIME, treats filenames as text and revokes Blob URLs. All original image bytes and metadata, including EXIF/GPS, remain public. Use synthetic images. The entity inspector performs separate latest-state read checks and can fail without hiding an already verified image.
25
-
26
- For controlled browser checks, run the project browser script against an isolated app context and save evidence separately. Verify 390/768/1440 widths, both themes, keyboard and 200% zoom plus empty/loading/error/recovered states. Actual results are in [verification](../docs/verification.md); the release handoff records hydrated deployment checks and IDs. Browser-wallet signing remains a separate check from controlled wallet tests and live Node signatures.
27
-
28
- The existing layout mirrors the project's Arkiv token/type contract. This migration changes network/data behavior and preserves the design; historical design evidence remains dated under docs.
29
-
30
- The locked build tools have two high-severity npm audit findings: `vite` and `source-map-js`, both development dependencies. See the [Windows editor middleware advisory](https://github.com/advisories/GHSA-587h-7wmj-h5pm) and [source-map offset advisory](https://github.com/advisories/GHSA-68fv-2mgg-jv7q). The deployment serves built assets and a Node read function, not a Vite development server. These existing tool versions were not upgraded in the migration; the full dependency audit is not clean. Update the affected build tools before exposing a development server or accepting untrusted source maps.
1
+ # Image sample — Rabdi
2
+
3
+ This sample uses the registry release **arkiv-images 1.0.0** and keeps the image picker, local-date Entity Expiration, byte verification, download, entity inspector, dark/light themes and wallet selection. All copy is English. It targets the existing **arkiv-images-example** Vercel project at https://arkiv-images-example.vercel.app.
4
+
5
+ The UI uses the package public API. Reads go through its own bounded `/api/rpc` function with server-only `ARKIV_ACCESS_KEY`. Only chain ID, current block number, block timing and bounded image/chunk queries are accepted; no signer, access key, upstream URL or raw provider error is sent to the browser. Root and manifest reads are latest state; chunks use keyset pages. Set a free [Hub access key](https://hub.arkiv.network/access-keys) in the Vercel preview environment.
6
+
7
+ Writes use the visitor's MetaMask/Rabby account and configured Rabdi RPC, chain 7794095. If Rabdi is missing, configure it in the wallet using your own access key, then connect again. The app switches to an already configured Rabdi network; it never installs a server key in a wallet. Obtain [test funds](https://hub.arkiv.network/faucet). Every actual signature is guarded for account/network changes and uses the package's explicit gas policy.
8
+
9
+ ## Build the sample
10
+
11
+ From the sample directory:
12
+
13
+ ```sh
14
+ npm ci
15
+ npm run build
16
+ ```
17
+
18
+ The sample pins `arkiv-images` to exact registry version `1.0.0`; its lock verifies the published archive integrity. No local tarball is used or uploaded. Deploy the sample folder to the existing linked project; its publication allowlist includes the application source and API function.
19
+
20
+ The Vite development server alone does not serve Vercel functions. Use a Vercel preview for the complete real-network read flow; controlled browser tests route `/api/rpc` to the in-memory harness. Never put an access key in Vite environment variables.
21
+
22
+ Seed non-sensitive PNG/JPEG examples with `node scripts/seed-rabdi.mjs` from the package root after configuring local test credentials. Keep `.seed-rabdi-state.json` between runs; unresolved writes block further seed writes. Successful seeding writes `sample/public/samples.json` and `docs/rabdi-evidence.json`; the Retrieve tab offers a public sample only when that manifest exists. A Rabdi reset requires rerunning the command, not hardcoded assumptions that old entity keys still exist.
23
+
24
+ The sample fully decodes images before writes and after retrieval, displays only allowed raster MIME, treats filenames as text and revokes Blob URLs. All original image bytes and metadata, including EXIF/GPS, remain public. Use synthetic images. The entity inspector performs separate latest-state read checks and can fail without hiding an already verified image.
25
+
26
+ For controlled browser checks, run the project browser script against an isolated app context and save evidence separately. Verify 390/768/1440 widths, both themes, keyboard and 200% zoom plus empty/loading/error/recovered states. Actual results are in [verification](../docs/verification.md); the release handoff records hydrated deployment checks and IDs. Browser-wallet signing remains a separate check from controlled wallet tests and live Node signatures.
27
+
28
+ The existing layout mirrors the project's Arkiv token/type contract. This migration changes network/data behavior and preserves the design; historical design evidence remains dated under docs.
29
+
30
+ The locked build tools have two high-severity npm audit findings: `vite` and `source-map-js`, both development dependencies. See the [Windows editor middleware advisory](https://github.com/advisories/GHSA-587h-7wmj-h5pm) and [source-map offset advisory](https://github.com/advisories/GHSA-68fv-2mgg-jv7q). The deployment serves built assets and a Node read function, not a Vite development server. These existing tool versions were not upgraded in the migration; the full dependency audit is not clean. Update the affected build tools before exposing a development server or accepting untrusted source maps.
31
+
32
+ The public example defaults to the longer-lived JPEG, entity `0x2989bda8bbadfe7e22a0b1e3a435aa3b266106adbbca3f97c162ee4eccca8488`. The example list reads the current Rabdi block and skips expired entries. Its button stays unavailable if timing/configuration fails or no active example exists. A second head read rejects expiry before retrieval. No seed or write is performed.