@parity/product-sdk-bulletin 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +445 -396
- package/dist/index.js +306 -1119
- package/dist/index.js.map +1 -1
- package/package.json +8 -7
- package/src/authorization.ts +300 -1
- package/src/cid.ts +115 -239
- package/src/client.ts +238 -179
- package/src/errors.ts +65 -138
- package/src/index.ts +85 -20
- package/src/lazy-signer.ts +113 -0
- package/src/networks.ts +49 -0
- package/src/query.ts +165 -31
- package/src/resolve-query.ts +9 -3
- package/src/types.ts +26 -117
- package/src/verify.ts +384 -0
- package/src/gateway.ts +0 -209
- package/src/resolve-signer.ts +0 -66
- package/src/upload.ts +0 -344
package/src/client.ts
CHANGED
|
@@ -1,59 +1,96 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
1
|
+
import {
|
|
2
|
+
AsyncBulletinClient,
|
|
3
|
+
type AuthCallBuilder,
|
|
4
|
+
type BulletinTypedApi,
|
|
5
|
+
type CallBuilder,
|
|
6
|
+
type ClientConfig,
|
|
7
|
+
type StoreBuilder,
|
|
8
|
+
type SubmitFn,
|
|
9
|
+
} from "@parity/bulletin-sdk";
|
|
10
|
+
import { createChainClient, getChainAPI } from "@parity/product-sdk-chain-client";
|
|
11
|
+
import { createLogger } from "@parity/product-sdk-logger";
|
|
12
|
+
import type { PolkadotClient, PolkadotSigner } from "polkadot-api";
|
|
3
13
|
|
|
4
14
|
import { checkAuthorization } from "./authorization.js";
|
|
5
|
-
import {
|
|
6
|
-
type CidCodec,
|
|
7
|
-
type HashAlgorithm,
|
|
8
|
-
cidToPreimageKey,
|
|
9
|
-
computeCid,
|
|
10
|
-
hashToCid,
|
|
11
|
-
} from "./cid.js";
|
|
12
|
-
import { cidExists, getGateway, gatewayUrl } from "./gateway.js";
|
|
15
|
+
import type { BulletinChain, BulletinEnvironment } from "./networks.js";
|
|
13
16
|
import { executeQuery } from "./query.js";
|
|
14
17
|
import { resolveQueryStrategy, type QueryStrategy } from "./resolve-query.js";
|
|
15
|
-
import {
|
|
16
|
-
import type
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
}
|
|
18
|
+
import type { AuthorizationStatus, BulletinApi, QueryOptions } from "./types.js";
|
|
19
|
+
import { verifyOnChain, type ChainStoredEntry, type VerifyOnChainOptions } from "./verify.js";
|
|
20
|
+
|
|
21
|
+
const log = createLogger("bulletin");
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Options for {@link BulletinClient.create}.
|
|
25
|
+
*
|
|
26
|
+
* One of two construction shapes is supported:
|
|
27
|
+
*
|
|
28
|
+
* - **Environment shorthand** — pass an `environment` string keyed by
|
|
29
|
+
* {@link BulletinChain}. Wires up the chain-client automatically.
|
|
30
|
+
* - **Explicit network** — pass `genesisHash` and `descriptor` directly
|
|
31
|
+
* (e.g., spread from a {@link BulletinChain} entry, or supply custom
|
|
32
|
+
* values for a private chain).
|
|
33
|
+
*/
|
|
34
|
+
export type CreateBulletinClientOptions =
|
|
35
|
+
| (CreateBulletinClientCommon & { environment: BulletinEnvironment })
|
|
36
|
+
| (CreateBulletinClientCommon & {
|
|
37
|
+
genesisHash: `0x${string}`;
|
|
38
|
+
descriptor: (typeof BulletinChain)[BulletinEnvironment]["descriptor"];
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
interface CreateBulletinClientCommon {
|
|
42
|
+
/** Signer for transaction submission. Required — every store needs a signer. */
|
|
43
|
+
signer: PolkadotSigner;
|
|
44
|
+
/** Optional config forwarded to {@link AsyncBulletinClient}. */
|
|
45
|
+
config?: Partial<ClientConfig>;
|
|
46
|
+
}
|
|
27
47
|
|
|
28
48
|
/**
|
|
29
49
|
* Ergonomic entry point for Bulletin Chain operations.
|
|
30
50
|
*
|
|
31
|
-
*
|
|
32
|
-
*
|
|
51
|
+
* Wraps {@link AsyncBulletinClient} from `@parity/bulletin-sdk` (which handles
|
|
52
|
+
* chunking, DAG-PB manifests, CID calculation, and progress events) and adds:
|
|
53
|
+
*
|
|
54
|
+
* - **Network presets** via {@link BulletinClient.create} and {@link BulletinChain}.
|
|
55
|
+
* - **Read helpers** ({@link fetchBytes}, {@link fetchJson}) routed through
|
|
56
|
+
* the host's preimage subscription — upstream is upload-only and the SDK
|
|
57
|
+
* is container-only by design (no public-gateway fetches).
|
|
58
|
+
* - **Pre-flight authorization check** ({@link checkAuthorization}) for
|
|
59
|
+
* friendlier UX before submitting a store.
|
|
60
|
+
*
|
|
61
|
+
* For uploads, mirror upstream's fluent builders:
|
|
62
|
+
*
|
|
63
|
+
* ```ts
|
|
64
|
+
* const client = await BulletinClient.create({ environment: "paseo", signer });
|
|
65
|
+
* const result = await client.store(data).send();
|
|
66
|
+
* ```
|
|
33
67
|
*
|
|
34
|
-
*
|
|
35
|
-
* - **Uploads** — the host preimage API signs and submits automatically.
|
|
36
|
-
* - **Queries** (`fetchBytes`/`fetchJson`) — uses host preimage lookup with caching.
|
|
68
|
+
* For chunked uploads with progress:
|
|
37
69
|
*
|
|
38
|
-
* @example
|
|
39
70
|
* ```ts
|
|
40
|
-
* const
|
|
41
|
-
*
|
|
42
|
-
*
|
|
71
|
+
* const result = await client
|
|
72
|
+
* .store(largeFile)
|
|
73
|
+
* .withChunkSize(1 << 20)
|
|
74
|
+
* .withCallback((evt) => console.log(evt))
|
|
75
|
+
* .send();
|
|
43
76
|
* ```
|
|
44
77
|
*/
|
|
45
78
|
export class BulletinClient {
|
|
79
|
+
/** Underlying upstream client — exposed for power users. */
|
|
80
|
+
readonly inner: AsyncBulletinClient;
|
|
81
|
+
/** Typed Bulletin Chain API. */
|
|
46
82
|
readonly api: BulletinApi;
|
|
47
|
-
readonly gateway: string;
|
|
48
83
|
|
|
84
|
+
/** Lazy-resolved host-preimage query strategy, cached for the client lifetime. */
|
|
49
85
|
private queryStrategyPromise: Promise<QueryStrategy> | null = null;
|
|
50
86
|
|
|
51
|
-
|
|
87
|
+
/** Constructed via {@link create} or {@link from}. */
|
|
88
|
+
private constructor(inner: AsyncBulletinClient, api: BulletinApi) {
|
|
89
|
+
this.inner = inner;
|
|
52
90
|
this.api = api;
|
|
53
|
-
this.gateway = gateway;
|
|
54
91
|
}
|
|
55
92
|
|
|
56
|
-
/**
|
|
93
|
+
/** Resolve and cache the host query strategy on first use. */
|
|
57
94
|
private resolveQuery(): Promise<QueryStrategy> {
|
|
58
95
|
if (!this.queryStrategyPromise) {
|
|
59
96
|
this.queryStrategyPromise = resolveQueryStrategy();
|
|
@@ -61,195 +98,217 @@ export class BulletinClient {
|
|
|
61
98
|
return this.queryStrategyPromise;
|
|
62
99
|
}
|
|
63
100
|
|
|
64
|
-
/** Create from an environment — resolves API via chain-client, gateway from known list. */
|
|
65
|
-
static async create(env: Environment): Promise<BulletinClient> {
|
|
66
|
-
const chain = await getChainAPI(env);
|
|
67
|
-
return new BulletinClient(chain.bulletin, getGateway(env));
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
/** Create from an explicit API and gateway (custom setups, testing). */
|
|
71
|
-
static from(api: BulletinApi, gateway: string): BulletinClient {
|
|
72
|
-
return new BulletinClient(api, gateway);
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
/** Compute CID without uploading. Static — no instance needed. */
|
|
76
|
-
static computeCid(data: Uint8Array): string {
|
|
77
|
-
return computeCid(data);
|
|
78
|
-
}
|
|
79
|
-
|
|
80
101
|
/**
|
|
81
|
-
*
|
|
102
|
+
* Create a client from an environment shorthand or an explicit network.
|
|
82
103
|
*
|
|
83
|
-
*
|
|
84
|
-
*
|
|
104
|
+
* Environment form uses our `getChainAPI(env)` to resolve the typed API.
|
|
105
|
+
* Explicit form skips the environment lookup and lets you pass any
|
|
106
|
+
* genesis/descriptor combo.
|
|
85
107
|
*
|
|
86
|
-
* @
|
|
108
|
+
* @example
|
|
109
|
+
* ```ts
|
|
110
|
+
* // Shorthand
|
|
111
|
+
* const client = await BulletinClient.create({ environment: "paseo", signer });
|
|
112
|
+
*
|
|
113
|
+
* // Explicit (custom network)
|
|
114
|
+
* const client = await BulletinClient.create({
|
|
115
|
+
* ...BulletinChain.paseo,
|
|
116
|
+
* signer,
|
|
117
|
+
* config: { defaultChunkSize: 1 << 20 },
|
|
118
|
+
* });
|
|
119
|
+
* ```
|
|
87
120
|
*/
|
|
88
|
-
static
|
|
89
|
-
|
|
121
|
+
static async create(options: CreateBulletinClientOptions): Promise<BulletinClient> {
|
|
122
|
+
if ("environment" in options) {
|
|
123
|
+
const chain = await getChainAPI(options.environment);
|
|
124
|
+
const inner = new AsyncBulletinClient(
|
|
125
|
+
chain.bulletin as BulletinTypedApi,
|
|
126
|
+
options.signer,
|
|
127
|
+
chain.raw.bulletin.submit as SubmitFn,
|
|
128
|
+
options.config,
|
|
129
|
+
() => chain.destroy(),
|
|
130
|
+
);
|
|
131
|
+
log.info("BulletinClient created (environment shorthand)", {
|
|
132
|
+
environment: options.environment,
|
|
133
|
+
});
|
|
134
|
+
return new BulletinClient(inner, chain.bulletin);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// Explicit form — caller owns the descriptor choice. We still need a
|
|
138
|
+
// PolkadotClient to feed AsyncBulletinClient. Going through
|
|
139
|
+
// chain-client keeps connection management consistent across the SDK.
|
|
140
|
+
const { genesisHash, descriptor, signer, config } = options;
|
|
141
|
+
// Catch the obvious foot-gun where caller mixes a genesis from one
|
|
142
|
+
// network with a descriptor from another — the connection would
|
|
143
|
+
// succeed but typed calls would silently target the wrong chain.
|
|
144
|
+
// The descriptor's own `.genesis` field is the on-chain truth; the
|
|
145
|
+
// user-supplied `genesisHash` is informational today (createChainClient
|
|
146
|
+
// doesn't use it because host routes connections) but kept on the
|
|
147
|
+
// option shape for future RPC-direct paths.
|
|
148
|
+
if (descriptor.genesis && genesisHash.toLowerCase() !== descriptor.genesis.toLowerCase()) {
|
|
149
|
+
throw new Error(
|
|
150
|
+
`BulletinClient.create: genesisHash (${genesisHash}) does not match descriptor.genesis (${descriptor.genesis}). These must refer to the same network — check that you're pairing the right descriptor with the right genesis hash.`,
|
|
151
|
+
);
|
|
152
|
+
}
|
|
153
|
+
const chain = await createChainClient({
|
|
154
|
+
chains: { bulletin: descriptor },
|
|
155
|
+
rpcs: { bulletin: [] },
|
|
156
|
+
});
|
|
157
|
+
const inner = new AsyncBulletinClient(
|
|
158
|
+
chain.bulletin as BulletinTypedApi,
|
|
159
|
+
signer,
|
|
160
|
+
chain.raw.bulletin.submit as SubmitFn,
|
|
161
|
+
config,
|
|
162
|
+
() => chain.destroy(),
|
|
163
|
+
);
|
|
164
|
+
log.info("BulletinClient created (explicit network)");
|
|
165
|
+
return new BulletinClient(inner, chain.bulletin);
|
|
90
166
|
}
|
|
91
167
|
|
|
92
168
|
/**
|
|
93
|
-
*
|
|
169
|
+
* Construct from a pre-built `AsyncBulletinClient` and PAPI typed API.
|
|
94
170
|
*
|
|
95
|
-
*
|
|
96
|
-
*
|
|
97
|
-
*
|
|
171
|
+
* Use this when you already own the connection lifecycle (BYOD setups,
|
|
172
|
+
* tests). The caller is responsible for calling `papiClient.destroy()`
|
|
173
|
+
* — this client's {@link destroy} only tears down the upstream's
|
|
174
|
+
* `onDestroy` hook.
|
|
98
175
|
*/
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
signer?: PolkadotSigner,
|
|
102
|
-
options?: Omit<UploadOptions, "gateway">,
|
|
103
|
-
): Promise<UploadResult> {
|
|
104
|
-
return upload(this.api, data, signer, { ...options, gateway: this.gateway });
|
|
176
|
+
static from(inner: AsyncBulletinClient, api: BulletinApi): BulletinClient {
|
|
177
|
+
return new BulletinClient(inner, api);
|
|
105
178
|
}
|
|
106
179
|
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
* @param options - Batch upload options (timeout, progress callback).
|
|
113
|
-
*/
|
|
114
|
-
async batchUpload(
|
|
115
|
-
items: BatchUploadItem[],
|
|
116
|
-
signer?: PolkadotSigner,
|
|
117
|
-
options?: Omit<BatchUploadOptions, "gateway">,
|
|
118
|
-
): Promise<BatchUploadResult[]> {
|
|
119
|
-
return batchUpload(this.api, items, signer, { ...options, gateway: this.gateway });
|
|
180
|
+
// ─── Upload + authorization (forwarded to upstream) ────────────────
|
|
181
|
+
|
|
182
|
+
/** Build a store transaction. See upstream `StoreBuilder` for chained options. */
|
|
183
|
+
store(data: Uint8Array): StoreBuilder {
|
|
184
|
+
return this.inner.store(data);
|
|
120
185
|
}
|
|
121
186
|
|
|
187
|
+
/** Authorize an account to store data on the chain (sudo required on most networks). */
|
|
188
|
+
authorizeAccount(who: string, transactions: number, bytes: bigint): AuthCallBuilder {
|
|
189
|
+
return this.inner.authorizeAccount(who, transactions, bytes);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/** Authorize content storage by hash (anyone can store; no fees). */
|
|
193
|
+
authorizePreimage(contentHash: Uint8Array, maxSize: bigint): AuthCallBuilder {
|
|
194
|
+
return this.inner.authorizePreimage(contentHash, maxSize);
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
/** Renew a stored transaction by block + index. */
|
|
198
|
+
renew(block: number, index: number): CallBuilder {
|
|
199
|
+
return this.inner.renew(block, index);
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
/** Estimate the authorization (transactions + bytes) needed for `dataSize`. */
|
|
203
|
+
estimateAuthorization(dataSize: number): { transactions: number; bytes: number } {
|
|
204
|
+
return this.inner.estimateAuthorization(dataSize);
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
// ─── Read side (our own helpers) ───────────────────────────────────
|
|
208
|
+
|
|
122
209
|
/**
|
|
123
|
-
* Fetch raw bytes
|
|
210
|
+
* Fetch raw bytes for a CID via the host's preimage lookup.
|
|
211
|
+
*
|
|
212
|
+
* Container-only — outside a Polkadot Browser / Desktop host this
|
|
213
|
+
* throws {@link BulletinHostUnavailableError}. The chain stores
|
|
214
|
+
* content metadata (`content_hash`, size, codec) but the bytes
|
|
215
|
+
* themselves are surfaced through the host's preimage subscription.
|
|
124
216
|
*
|
|
125
|
-
*
|
|
217
|
+
* Use {@link verifyOnChain} if you only need to confirm a CID was
|
|
218
|
+
* recorded on-chain (no byte fetch).
|
|
126
219
|
*/
|
|
127
220
|
async fetchBytes(cid: string, options?: QueryOptions): Promise<Uint8Array> {
|
|
128
221
|
const strategy = await this.resolveQuery();
|
|
129
222
|
return executeQuery(strategy, cid, options);
|
|
130
223
|
}
|
|
131
224
|
|
|
132
|
-
/**
|
|
133
|
-
* Fetch and parse JSON by CID.
|
|
134
|
-
*
|
|
135
|
-
* Auto-resolves query path (same as {@link fetchBytes}).
|
|
136
|
-
*/
|
|
225
|
+
/** Fetch and parse JSON for a CID. */
|
|
137
226
|
async fetchJson<T>(cid: string, options?: QueryOptions): Promise<T> {
|
|
138
227
|
const bytes = await this.fetchBytes(cid, options);
|
|
139
228
|
return JSON.parse(new TextDecoder().decode(bytes)) as T;
|
|
140
229
|
}
|
|
141
230
|
|
|
142
|
-
/**
|
|
143
|
-
async
|
|
144
|
-
return
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
/** Build the full gateway URL for a CID. */
|
|
148
|
-
gatewayUrl(cid: string): string {
|
|
149
|
-
return gatewayUrl(cid, this.gateway);
|
|
231
|
+
/** Pre-flight: check whether `address` can store on the bulletin chain. */
|
|
232
|
+
async checkAuthorization(address: string): Promise<AuthorizationStatus> {
|
|
233
|
+
return checkAuthorization(this.api, address);
|
|
150
234
|
}
|
|
151
235
|
|
|
152
236
|
/**
|
|
153
|
-
*
|
|
154
|
-
*
|
|
155
|
-
* Use as a pre-flight check before {@link upload} to provide clear UX
|
|
156
|
-
* instead of letting the transaction fail mid-execution.
|
|
157
|
-
*
|
|
158
|
-
* @param address - SS58-encoded account address to check.
|
|
159
|
-
* @returns Authorization status with remaining quota.
|
|
237
|
+
* Verify that a CID was recorded on-chain at the given block.
|
|
160
238
|
*
|
|
161
|
-
*
|
|
239
|
+
* Common pattern: pass `blockNumber` (and optionally `extrinsicIndex`)
|
|
240
|
+
* from a `store(...).send()` receipt to confirm the upload landed.
|
|
241
|
+
* See {@link verifyOnChain} for details.
|
|
162
242
|
*/
|
|
163
|
-
async
|
|
164
|
-
|
|
243
|
+
async verifyOnChain(
|
|
244
|
+
cid: string,
|
|
245
|
+
options: VerifyOnChainOptions,
|
|
246
|
+
): Promise<ChainStoredEntry | null> {
|
|
247
|
+
return verifyOnChain(this.api, cid, options);
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
/** Tear down the underlying connection. */
|
|
251
|
+
async destroy(): Promise<void> {
|
|
252
|
+
await this.inner.destroy();
|
|
165
253
|
}
|
|
166
254
|
}
|
|
167
255
|
|
|
168
256
|
if (import.meta.vitest) {
|
|
169
257
|
const { describe, test, expect, vi } = import.meta.vitest;
|
|
170
258
|
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
type: "txBestBlocksState",
|
|
181
|
-
txHash: "0x",
|
|
182
|
-
found: true,
|
|
183
|
-
ok: true,
|
|
184
|
-
block: { hash: "0xblock", number: 1, index: 0 },
|
|
185
|
-
events: [],
|
|
186
|
-
});
|
|
187
|
-
});
|
|
188
|
-
return { unsubscribe: vi.fn() };
|
|
189
|
-
},
|
|
190
|
-
}),
|
|
191
|
-
}),
|
|
192
|
-
},
|
|
193
|
-
},
|
|
194
|
-
} as unknown as BulletinApi;
|
|
195
|
-
|
|
196
|
-
const GATEWAY = "https://test-gw/ipfs/";
|
|
197
|
-
|
|
198
|
-
describe("BulletinClient", () => {
|
|
199
|
-
test("from() creates client with given API and gateway", () => {
|
|
200
|
-
const client = BulletinClient.from(mockApi, GATEWAY);
|
|
201
|
-
expect(client.api).toBe(mockApi);
|
|
202
|
-
expect(client.gateway).toBe(GATEWAY);
|
|
259
|
+
describe("BulletinClient.from", () => {
|
|
260
|
+
test("constructs with given inner and api", () => {
|
|
261
|
+
const inner = {
|
|
262
|
+
destroy: vi.fn().mockResolvedValue(undefined),
|
|
263
|
+
} as unknown as AsyncBulletinClient;
|
|
264
|
+
const api = {} as BulletinApi;
|
|
265
|
+
const client = BulletinClient.from(inner, api);
|
|
266
|
+
expect(client.inner).toBe(inner);
|
|
267
|
+
expect(client.api).toBe(api);
|
|
203
268
|
});
|
|
204
269
|
|
|
205
|
-
test("
|
|
206
|
-
const
|
|
207
|
-
const
|
|
208
|
-
|
|
270
|
+
test("destroy delegates to upstream", async () => {
|
|
271
|
+
const destroy = vi.fn().mockResolvedValue(undefined);
|
|
272
|
+
const inner = { destroy } as unknown as AsyncBulletinClient;
|
|
273
|
+
const client = BulletinClient.from(inner, {} as BulletinApi);
|
|
274
|
+
await client.destroy();
|
|
275
|
+
expect(destroy).toHaveBeenCalledOnce();
|
|
209
276
|
});
|
|
210
277
|
|
|
211
|
-
test("
|
|
212
|
-
const
|
|
213
|
-
const
|
|
214
|
-
|
|
215
|
-
|
|
278
|
+
test("store delegates to inner", () => {
|
|
279
|
+
const builder = {} as StoreBuilder;
|
|
280
|
+
const inner = {
|
|
281
|
+
store: vi.fn().mockReturnValue(builder),
|
|
282
|
+
} as unknown as AsyncBulletinClient;
|
|
283
|
+
const client = BulletinClient.from(inner, {} as BulletinApi);
|
|
284
|
+
const data = new Uint8Array([1, 2, 3]);
|
|
285
|
+
expect(client.store(data)).toBe(builder);
|
|
286
|
+
expect(inner.store).toHaveBeenCalledWith(data);
|
|
216
287
|
});
|
|
288
|
+
});
|
|
217
289
|
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
290
|
+
describe("BulletinClient.create (BYOD genesis assertion)", () => {
|
|
291
|
+
// Stand-in descriptor with a known genesis. The full PAPI descriptor
|
|
292
|
+
// type is a `ChainDefinition` with a deep type-level shape; the cast
|
|
293
|
+
// below is fine for the assertion test because we never actually
|
|
294
|
+
// reach createChainClient.
|
|
295
|
+
const stubDescriptor = (
|
|
296
|
+
genesis: `0x${string}`,
|
|
297
|
+
): (typeof BulletinChain)[BulletinEnvironment]["descriptor"] =>
|
|
298
|
+
({ genesis }) as unknown as (typeof BulletinChain)[BulletinEnvironment]["descriptor"];
|
|
222
299
|
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
const data = new TextEncoder().encode("test");
|
|
226
|
-
const result = await client.upload(data, {} as PolkadotSigner);
|
|
227
|
-
expect(result.gatewayUrl).toContain(GATEWAY);
|
|
228
|
-
expect(result.cid).toBeTruthy();
|
|
229
|
-
});
|
|
300
|
+
const realPaseo =
|
|
301
|
+
"0x744960c32e3a3df5440e1ecd4d34096f1ce2230d7016a5ada8a765d5a622b4ea" as `0x${string}`;
|
|
230
302
|
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
getValue: vi.fn().mockResolvedValue({
|
|
241
|
-
extent: { transactions: 5, bytes: 2000n },
|
|
242
|
-
expiration: 100,
|
|
243
|
-
}),
|
|
244
|
-
},
|
|
245
|
-
},
|
|
246
|
-
},
|
|
247
|
-
} as unknown as BulletinApi;
|
|
248
|
-
const client = BulletinClient.from(authMockApi, GATEWAY);
|
|
249
|
-
const status = await client.checkAuthorization("5GrwvaEF...");
|
|
250
|
-
expect(status.authorized).toBe(true);
|
|
251
|
-
expect(status.remainingTransactions).toBe(5);
|
|
252
|
-
expect(status.remainingBytes).toBe(2000n);
|
|
303
|
+
test("throws when genesisHash and descriptor.genesis disagree", async () => {
|
|
304
|
+
await expect(
|
|
305
|
+
BulletinClient.create({
|
|
306
|
+
genesisHash:
|
|
307
|
+
"0x0000000000000000000000000000000000000000000000000000000000000001",
|
|
308
|
+
descriptor: stubDescriptor(realPaseo),
|
|
309
|
+
signer: {} as PolkadotSigner,
|
|
310
|
+
}),
|
|
311
|
+
).rejects.toThrow(/does not match descriptor\.genesis/i);
|
|
253
312
|
});
|
|
254
313
|
});
|
|
255
314
|
}
|