arkiv-images 1.0.0 → 1.0.1

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,17 @@
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. Version 1.0.0 is a candidate until verification/publication evidence says otherwise.
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. 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.
package/CHANGELOG.md CHANGED
@@ -1,13 +1,17 @@
1
- # Changelog
2
-
3
- ## 1.0.0 — candidate
4
-
5
- - Breaking: SDK 1.0.0 peer requirement and Rabdi testnet, chain ID 7794095.
6
- - Breaking: named `arkiv-images/v2` schema with non-indexed binary data and typed columns. Older image formats are not read by this release.
7
- - Breaking: arkiv-chunking 1.1.0 with its public write policy and latest-state keyset pagination; no historical snapshot guarantee.
8
- - Breaking: maximum admitted lifetime 7,883,970 blocks with an inclusion margin; no unlimited lifetime.
9
- - Public per-write transaction policy, explicit latest estimates/gas/price/nonce, half-block admission ceiling, and recovery keys/hashes when confirmation is pending.
10
- - 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.
11
- - 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.
12
-
13
- This candidate is not published. Validation status is documented in docs/verification.md.
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.
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,126 @@
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
- **1.0.0 is a candidate, pending verification and publication. Intended for Rabdi testnet only.** The published release remains separate from this source candidate. See [verification](docs/verification.md) for actual results and blockers.
6
-
7
- ## Install and inspect
8
-
9
- ESM, Node 22.10+ with Web Crypto, or a browser on HTTPS/localhost. Candidate dependencies: SDK 1.0.0, viem 2.56.3, arkiv-chunking 1.1.0. Install the packed candidate until 1.0.0 is published:
10
-
11
- ```sh
12
- mkdir image-consumer
13
- cd image-consumer
14
- npm init -y
15
- npm pkg set type=module
16
- npm install /absolute/path/arkiv-images-1.0.0.tgz @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` | Package version 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 records 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, preview status 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. Candidate previews consume the packed candidate; registry releases must be verified separately.
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
+ **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,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,46 @@
1
- # Rabdi candidate verification
2
-
3
- Candidate: **arkiv-images 1.0.0**, SDK **1.0.0**, viem **2.56.3**, exact registry **arkiv-chunking 1.1.0**. This source candidate is not an npm publication or production deployment. Review approval is required before either.
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` requires live evidence, runs the library gates, packs the publication allowlist, installs that exact tarball in a clean consumer, runs the exact README inspection example and public TypeScript imports, refreshes the sample lock from the registry and builds the sample. Its artifact report records SHA-256/SHA-512 and distinguishes a packed candidate from a registry release. The sample pins chunking 1.1.0 directly and consumes the packed image candidate.
43
-
44
- The preview 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 preview validation and its deployment ID are recorded in the PR's validation report. Live browser retrieval, local input/decode failures and controlled wallet/operation tests are different checks; neither replaces the other. No npm publish, production deploy, tag, merge or release belongs to this candidate review.
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
+ # 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.
package/package.json CHANGED
@@ -1,57 +1,57 @@
1
- {
2
- "name": "arkiv-images",
3
- "version": "1.0.0",
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.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
+ }
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
- - Candidate previews consume the root packed candidate; after publication use the verified exact npm release. 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,33 +1,30 @@
1
- # Image sample — Rabdi candidate
2
-
3
- This source candidate keeps the existing 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; production publication is deferred.
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 this candidate
10
-
11
- From the package root, after dependencies are available:
12
-
13
- ```sh
14
- npm run build
15
- npm pack --pack-destination sample
16
- cd sample
17
- npm install --no-audit --no-fund
18
- npm run build
19
- ```
20
-
21
- The candidate sample dependency is `file:./arkiv-images-1.0.0.tgz`, produced by the root pack in the sample folder, rather than the older registry release. This is an artifact integration test, not registry verification. Deploy the sample folder, including this tarball, to the existing linked project; its publication allowlist includes the candidate and API function.
22
-
23
- 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.
24
-
25
- 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.
26
-
27
- 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.
28
-
29
- 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 candidate results are in [verification](../docs/verification.md); the PR records the hydrated preview and deployment ID. Browser-wallet signing remains a separate check from controlled wallet tests and live Node signatures.
30
-
31
- 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.
32
-
33
- 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). This preview 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, 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.