@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/errors.ts
CHANGED
|
@@ -1,33 +1,42 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* Bulletin error types.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
4
|
+
* Two error families coexist:
|
|
5
5
|
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
6
|
+
* 1. **Upstream SDK errors** — `BulletinError` and `ErrorCode` from
|
|
7
|
+
* `@parity/bulletin-sdk` cover upload/store/authorization failures
|
|
8
|
+
* surfaced by `AsyncBulletinClient`. Each carries `code`, `retryable`,
|
|
9
|
+
* and `recoveryHint`.
|
|
10
|
+
* 2. **Read-side errors** declared here — host preimage availability /
|
|
11
|
+
* lookup timeouts / interrupts, plus CID format problems, surfaced by
|
|
12
|
+
* our retrieval helpers (`fetchBytes`, `fetchJson`, `verifyOnChain`).
|
|
13
|
+
*
|
|
14
|
+
* Catch upstream errors with `instanceof BulletinError`. Catch our read-side
|
|
15
|
+
* errors with `instanceof ProductBulletinError` (or the specific subclass).
|
|
16
|
+
*/
|
|
17
|
+
export { BulletinError, ErrorCode } from "@parity/bulletin-sdk";
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Base class for read-side errors raised by `@parity/product-sdk-bulletin`.
|
|
21
|
+
*
|
|
22
|
+
* Distinct from upstream `BulletinError` which covers upload/store failures.
|
|
16
23
|
*/
|
|
17
|
-
export class
|
|
24
|
+
export class ProductBulletinError extends Error {
|
|
18
25
|
constructor(message: string, options?: ErrorOptions) {
|
|
19
26
|
super(message, options);
|
|
20
|
-
this.name = "
|
|
27
|
+
this.name = "ProductBulletinError";
|
|
21
28
|
}
|
|
22
29
|
}
|
|
23
30
|
|
|
24
31
|
/**
|
|
25
32
|
* The host preimage API is unavailable.
|
|
26
33
|
*
|
|
27
|
-
* Thrown when bulletin operations require the host container but it's not
|
|
28
|
-
* This typically means the SDK is running outside
|
|
34
|
+
* Thrown when bulletin operations require the host container but it's not
|
|
35
|
+
* available. This typically means the SDK is running outside Polkadot
|
|
36
|
+
* Browser / Desktop. The bulletin SDK is container-only by design — see
|
|
37
|
+
* the README for the rationale.
|
|
29
38
|
*/
|
|
30
|
-
export class BulletinHostUnavailableError extends
|
|
39
|
+
export class BulletinHostUnavailableError extends ProductBulletinError {
|
|
31
40
|
constructor(operation: "upload" | "query") {
|
|
32
41
|
super(
|
|
33
42
|
`Host preimage API unavailable for ${operation}. Ensure you are running inside a host container (Polkadot Browser / Desktop).`,
|
|
@@ -39,10 +48,10 @@ export class BulletinHostUnavailableError extends BulletinError {
|
|
|
39
48
|
/**
|
|
40
49
|
* The host preimage lookup timed out.
|
|
41
50
|
*
|
|
42
|
-
* The host was unable to retrieve the requested content within the timeout
|
|
43
|
-
* The content may still become available later.
|
|
51
|
+
* The host was unable to retrieve the requested content within the timeout
|
|
52
|
+
* period. The content may still become available later.
|
|
44
53
|
*/
|
|
45
|
-
export class BulletinLookupTimeoutError extends
|
|
54
|
+
export class BulletinLookupTimeoutError extends ProductBulletinError {
|
|
46
55
|
/** The CID that was being looked up. */
|
|
47
56
|
readonly cid: string;
|
|
48
57
|
/** The timeout duration in milliseconds. */
|
|
@@ -59,10 +68,10 @@ export class BulletinLookupTimeoutError extends BulletinError {
|
|
|
59
68
|
/**
|
|
60
69
|
* The host interrupted the preimage lookup.
|
|
61
70
|
*
|
|
62
|
-
* The host terminated the lookup subscription, typically after repeated
|
|
63
|
-
* or when the host determines the content is unavailable.
|
|
71
|
+
* The host terminated the lookup subscription, typically after repeated
|
|
72
|
+
* failures or when the host determines the content is unavailable.
|
|
64
73
|
*/
|
|
65
|
-
export class BulletinLookupInterruptedError extends
|
|
74
|
+
export class BulletinLookupInterruptedError extends ProductBulletinError {
|
|
66
75
|
/** The CID that was being looked up. */
|
|
67
76
|
readonly cid: string;
|
|
68
77
|
|
|
@@ -73,13 +82,27 @@ export class BulletinLookupInterruptedError extends BulletinError {
|
|
|
73
82
|
}
|
|
74
83
|
}
|
|
75
84
|
|
|
85
|
+
/**
|
|
86
|
+
* Invalid CID format or version.
|
|
87
|
+
*/
|
|
88
|
+
export class BulletinCidError extends ProductBulletinError {
|
|
89
|
+
/** The invalid CID string, if available. */
|
|
90
|
+
readonly cid?: string;
|
|
91
|
+
|
|
92
|
+
constructor(message: string, cid?: string) {
|
|
93
|
+
super(message);
|
|
94
|
+
this.name = "BulletinCidError";
|
|
95
|
+
this.cid = cid;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
76
99
|
/**
|
|
77
100
|
* Failed to check authorization status for an account.
|
|
78
101
|
*
|
|
79
102
|
* Wraps RPC or query errors that occur when checking if an account
|
|
80
103
|
* is authorized to store data on the Bulletin Chain.
|
|
81
104
|
*/
|
|
82
|
-
export class BulletinAuthorizationError extends
|
|
105
|
+
export class BulletinAuthorizationError extends ProductBulletinError {
|
|
83
106
|
/** The address that was being checked. */
|
|
84
107
|
readonly address: string;
|
|
85
108
|
|
|
@@ -90,146 +113,50 @@ export class BulletinAuthorizationError extends BulletinError {
|
|
|
90
113
|
}
|
|
91
114
|
}
|
|
92
115
|
|
|
93
|
-
/**
|
|
94
|
-
* The IPFS gateway for the specified environment is not available.
|
|
95
|
-
*
|
|
96
|
-
* Thrown when attempting to use gateway features for a network that
|
|
97
|
-
* doesn't have a live bulletin gateway yet.
|
|
98
|
-
*/
|
|
99
|
-
export class BulletinGatewayUnavailableError extends BulletinError {
|
|
100
|
-
/** The environment that was requested. */
|
|
101
|
-
readonly environment: string;
|
|
102
|
-
|
|
103
|
-
constructor(environment: string) {
|
|
104
|
-
super(`Bulletin gateway for "${environment}" is not yet available`);
|
|
105
|
-
this.name = "BulletinGatewayUnavailableError";
|
|
106
|
-
this.environment = environment;
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
/**
|
|
111
|
-
* An IPFS gateway request failed.
|
|
112
|
-
*
|
|
113
|
-
* Thrown when a fetch to the IPFS gateway returns a non-OK response.
|
|
114
|
-
*/
|
|
115
|
-
export class BulletinGatewayFetchError extends BulletinError {
|
|
116
|
-
/** The CID that was being fetched. */
|
|
117
|
-
readonly cid: string;
|
|
118
|
-
/** The HTTP status code returned by the gateway. */
|
|
119
|
-
readonly status: number;
|
|
120
|
-
/** The HTTP status text returned by the gateway. */
|
|
121
|
-
readonly statusText: string;
|
|
122
|
-
|
|
123
|
-
constructor(cid: string, status: number, statusText: string) {
|
|
124
|
-
super(`Gateway fetch failed for ${cid}: ${status} ${statusText}`);
|
|
125
|
-
this.name = "BulletinGatewayFetchError";
|
|
126
|
-
this.cid = cid;
|
|
127
|
-
this.status = status;
|
|
128
|
-
this.statusText = statusText;
|
|
129
|
-
}
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
/**
|
|
133
|
-
* Invalid CID format or version.
|
|
134
|
-
*
|
|
135
|
-
* Thrown when a CID string cannot be parsed or has an unexpected version/codec.
|
|
136
|
-
*/
|
|
137
|
-
export class BulletinCidError extends BulletinError {
|
|
138
|
-
/** The invalid CID string, if available. */
|
|
139
|
-
readonly cid?: string;
|
|
140
|
-
|
|
141
|
-
constructor(message: string, cid?: string) {
|
|
142
|
-
super(message);
|
|
143
|
-
this.name = "BulletinCidError";
|
|
144
|
-
this.cid = cid;
|
|
145
|
-
}
|
|
146
|
-
}
|
|
147
|
-
|
|
148
116
|
if (import.meta.vitest) {
|
|
149
117
|
const { describe, test, expect } = import.meta.vitest;
|
|
150
118
|
|
|
151
|
-
describe("
|
|
152
|
-
test("
|
|
153
|
-
const err = new
|
|
119
|
+
describe("ProductBulletinError hierarchy", () => {
|
|
120
|
+
test("ProductBulletinError extends Error", () => {
|
|
121
|
+
const err = new ProductBulletinError("test");
|
|
154
122
|
expect(err).toBeInstanceOf(Error);
|
|
155
|
-
expect(err).
|
|
156
|
-
|
|
123
|
+
expect(err.name).toBe("ProductBulletinError");
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
test("BulletinCidError", () => {
|
|
127
|
+
const err = new BulletinCidError("bad", "Qmabc");
|
|
128
|
+
expect(err).toBeInstanceOf(ProductBulletinError);
|
|
129
|
+
expect(err.cid).toBe("Qmabc");
|
|
157
130
|
});
|
|
158
131
|
|
|
159
132
|
test("BulletinHostUnavailableError", () => {
|
|
160
|
-
const err = new BulletinHostUnavailableError("
|
|
161
|
-
expect(err).toBeInstanceOf(
|
|
162
|
-
expect(err.
|
|
163
|
-
expect(err.message).toContain("upload");
|
|
133
|
+
const err = new BulletinHostUnavailableError("query");
|
|
134
|
+
expect(err).toBeInstanceOf(ProductBulletinError);
|
|
135
|
+
expect(err.message).toContain("query");
|
|
164
136
|
expect(err.message).toContain("Host preimage API unavailable");
|
|
165
137
|
});
|
|
166
138
|
|
|
167
139
|
test("BulletinLookupTimeoutError", () => {
|
|
168
140
|
const err = new BulletinLookupTimeoutError("bafyabc123", 30000);
|
|
169
|
-
expect(err).toBeInstanceOf(
|
|
170
|
-
expect(err.name).toBe("BulletinLookupTimeoutError");
|
|
141
|
+
expect(err).toBeInstanceOf(ProductBulletinError);
|
|
171
142
|
expect(err.cid).toBe("bafyabc123");
|
|
172
143
|
expect(err.timeoutMs).toBe(30000);
|
|
173
144
|
expect(err.message).toContain("30000ms");
|
|
174
|
-
expect(err.message).toContain("bafyabc123");
|
|
175
145
|
});
|
|
176
146
|
|
|
177
147
|
test("BulletinLookupInterruptedError", () => {
|
|
178
148
|
const err = new BulletinLookupInterruptedError("bafyabc123");
|
|
179
|
-
expect(err).toBeInstanceOf(
|
|
180
|
-
expect(err.name).toBe("BulletinLookupInterruptedError");
|
|
149
|
+
expect(err).toBeInstanceOf(ProductBulletinError);
|
|
181
150
|
expect(err.cid).toBe("bafyabc123");
|
|
182
151
|
expect(err.message).toContain("interrupted");
|
|
183
152
|
});
|
|
184
153
|
|
|
185
|
-
test("BulletinAuthorizationError
|
|
186
|
-
const cause = new Error("RPC
|
|
154
|
+
test("BulletinAuthorizationError carries cause", () => {
|
|
155
|
+
const cause = new Error("RPC down");
|
|
187
156
|
const err = new BulletinAuthorizationError("5GrwvaEF...", cause);
|
|
188
|
-
expect(err).toBeInstanceOf(
|
|
189
|
-
expect(err.name).toBe("BulletinAuthorizationError");
|
|
157
|
+
expect(err).toBeInstanceOf(ProductBulletinError);
|
|
190
158
|
expect(err.address).toBe("5GrwvaEF...");
|
|
191
159
|
expect(err.cause).toBe(cause);
|
|
192
160
|
});
|
|
193
|
-
|
|
194
|
-
test("BulletinGatewayUnavailableError", () => {
|
|
195
|
-
const err = new BulletinGatewayUnavailableError("polkadot");
|
|
196
|
-
expect(err).toBeInstanceOf(BulletinError);
|
|
197
|
-
expect(err.name).toBe("BulletinGatewayUnavailableError");
|
|
198
|
-
expect(err.environment).toBe("polkadot");
|
|
199
|
-
expect(err.message).toContain("polkadot");
|
|
200
|
-
});
|
|
201
|
-
|
|
202
|
-
test("BulletinGatewayFetchError", () => {
|
|
203
|
-
const err = new BulletinGatewayFetchError("bafyabc", 404, "Not Found");
|
|
204
|
-
expect(err).toBeInstanceOf(BulletinError);
|
|
205
|
-
expect(err.name).toBe("BulletinGatewayFetchError");
|
|
206
|
-
expect(err.cid).toBe("bafyabc");
|
|
207
|
-
expect(err.status).toBe(404);
|
|
208
|
-
expect(err.statusText).toBe("Not Found");
|
|
209
|
-
expect(err.message).toContain("404");
|
|
210
|
-
});
|
|
211
|
-
|
|
212
|
-
test("BulletinCidError", () => {
|
|
213
|
-
const err = new BulletinCidError("Expected CIDv1, got CIDv0", "Qmabc");
|
|
214
|
-
expect(err).toBeInstanceOf(BulletinError);
|
|
215
|
-
expect(err.name).toBe("BulletinCidError");
|
|
216
|
-
expect(err.cid).toBe("Qmabc");
|
|
217
|
-
});
|
|
218
|
-
|
|
219
|
-
test("all errors can be caught with BulletinError", () => {
|
|
220
|
-
const errors = [
|
|
221
|
-
new BulletinHostUnavailableError("query"),
|
|
222
|
-
new BulletinLookupTimeoutError("cid", 1000),
|
|
223
|
-
new BulletinLookupInterruptedError("cid"),
|
|
224
|
-
new BulletinAuthorizationError("addr"),
|
|
225
|
-
new BulletinGatewayUnavailableError("env"),
|
|
226
|
-
new BulletinGatewayFetchError("cid", 500, "Error"),
|
|
227
|
-
new BulletinCidError("bad cid"),
|
|
228
|
-
];
|
|
229
|
-
|
|
230
|
-
for (const err of errors) {
|
|
231
|
-
expect(err).toBeInstanceOf(BulletinError);
|
|
232
|
-
}
|
|
233
|
-
});
|
|
234
161
|
});
|
|
235
162
|
}
|
package/src/index.ts
CHANGED
|
@@ -1,38 +1,103 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @parity/product-sdk-bulletin — Upload and retrieve content on the Polkadot Bulletin Chain.
|
|
3
|
+
*
|
|
4
|
+
* Wraps `@parity/bulletin-sdk` (chunking, DAG-PB manifests, CID calculation,
|
|
5
|
+
* progress events) and adds:
|
|
6
|
+
*
|
|
7
|
+
* - **Network presets** via {@link BulletinChain}
|
|
8
|
+
* - **Read helpers** ({@link BulletinClient.fetchBytes} / {@link BulletinClient.fetchJson})
|
|
9
|
+
* - **Authorization pre-flight** via {@link checkAuthorization}
|
|
10
|
+
*
|
|
11
|
+
* @packageDocumentation
|
|
12
|
+
*/
|
|
13
|
+
|
|
1
14
|
export { BulletinClient } from "./client.js";
|
|
2
|
-
export {
|
|
15
|
+
export type { CreateBulletinClientOptions } from "./client.js";
|
|
16
|
+
export { BulletinChain } from "./networks.js";
|
|
17
|
+
export type { BulletinEnvironment, BulletinNetwork } from "./networks.js";
|
|
18
|
+
export { authorizeAccount, checkAuthorization } from "./authorization.js";
|
|
19
|
+
export type { AuthorizeAccountOptions } from "./authorization.js";
|
|
20
|
+
export { createLazySigner } from "./lazy-signer.js";
|
|
21
|
+
export { executeQuery, queryBytes, queryJson } from "./query.js";
|
|
22
|
+
export { resolveQueryStrategy } from "./resolve-query.js";
|
|
23
|
+
export type { QueryStrategy } from "./resolve-query.js";
|
|
24
|
+
export { verifyOnChain } from "./verify.js";
|
|
25
|
+
export type { ChainStoredEntry, VerifyOnChainOptions } from "./verify.js";
|
|
3
26
|
export {
|
|
4
|
-
computeCid,
|
|
5
27
|
cidToPreimageKey,
|
|
6
28
|
hashToCid,
|
|
7
29
|
HashAlgorithm,
|
|
8
30
|
CidCodec,
|
|
9
31
|
} from "./cid.js";
|
|
32
|
+
|
|
33
|
+
// Errors — both upstream `BulletinError` and our read-side errors
|
|
10
34
|
export {
|
|
11
35
|
BulletinError,
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
BulletinLookupInterruptedError,
|
|
36
|
+
ErrorCode,
|
|
37
|
+
ProductBulletinError,
|
|
15
38
|
BulletinAuthorizationError,
|
|
16
|
-
BulletinGatewayUnavailableError,
|
|
17
|
-
BulletinGatewayFetchError,
|
|
18
39
|
BulletinCidError,
|
|
40
|
+
BulletinHostUnavailableError,
|
|
41
|
+
BulletinLookupInterruptedError,
|
|
42
|
+
BulletinLookupTimeoutError,
|
|
19
43
|
} from "./errors.js";
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
export { queryBytes, queryJson } from "./query.js";
|
|
23
|
-
export { resolveUploadStrategy } from "./resolve-signer.js";
|
|
24
|
-
export { upload, batchUpload } from "./upload.js";
|
|
44
|
+
|
|
45
|
+
// Types
|
|
25
46
|
export type {
|
|
26
47
|
AuthorizationStatus,
|
|
27
48
|
BulletinApi,
|
|
28
49
|
Environment,
|
|
29
|
-
UploadOptions,
|
|
30
|
-
UploadResult,
|
|
31
|
-
BatchUploadItem,
|
|
32
|
-
BatchUploadResult,
|
|
33
|
-
BatchUploadOptions,
|
|
34
|
-
FetchOptions,
|
|
35
50
|
QueryOptions,
|
|
36
51
|
} from "./types.js";
|
|
37
|
-
|
|
38
|
-
|
|
52
|
+
|
|
53
|
+
// Re-exports from upstream SDK — surface for power users + so consumers
|
|
54
|
+
// don't need a separate `@parity/bulletin-sdk` import.
|
|
55
|
+
export {
|
|
56
|
+
AsyncBulletinClient,
|
|
57
|
+
BulletinPreparer,
|
|
58
|
+
AuthCallBuilder,
|
|
59
|
+
CallBuilder,
|
|
60
|
+
StoreBuilder,
|
|
61
|
+
MockBulletinClient,
|
|
62
|
+
AuthorizationScope,
|
|
63
|
+
ChunkStatus,
|
|
64
|
+
TxStatus,
|
|
65
|
+
WaitFor,
|
|
66
|
+
MAX_CHUNK_SIZE,
|
|
67
|
+
MAX_FILE_SIZE,
|
|
68
|
+
DEFAULT_CHUNKER_CONFIG,
|
|
69
|
+
DEFAULT_CLIENT_CONFIG,
|
|
70
|
+
DEFAULT_STORE_OPTIONS,
|
|
71
|
+
calculateCid,
|
|
72
|
+
cidFromBytes,
|
|
73
|
+
cidToBytes,
|
|
74
|
+
convertCid,
|
|
75
|
+
estimateAuthorization,
|
|
76
|
+
getContentHash,
|
|
77
|
+
parseCid,
|
|
78
|
+
reassembleChunks,
|
|
79
|
+
resolveClientConfig,
|
|
80
|
+
validateChunkSize,
|
|
81
|
+
CID,
|
|
82
|
+
} from "@parity/bulletin-sdk";
|
|
83
|
+
|
|
84
|
+
export type {
|
|
85
|
+
BulletinClientInterface,
|
|
86
|
+
BulletinTypedApi,
|
|
87
|
+
Chunk,
|
|
88
|
+
ChunkDetails,
|
|
89
|
+
ChunkProgressEvent,
|
|
90
|
+
ChunkedStoreResult,
|
|
91
|
+
ChunkerConfig,
|
|
92
|
+
ClientConfig,
|
|
93
|
+
DagManifest,
|
|
94
|
+
MockClientConfig,
|
|
95
|
+
MockOperation,
|
|
96
|
+
ProgressCallback,
|
|
97
|
+
ProgressEvent,
|
|
98
|
+
StoreOptions,
|
|
99
|
+
StoreResult,
|
|
100
|
+
SubmitFn,
|
|
101
|
+
TransactionReceipt,
|
|
102
|
+
TransactionStatusEvent,
|
|
103
|
+
} from "@parity/bulletin-sdk";
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import type { PolkadotSigner } from "polkadot-api";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Build a `PolkadotSigner` whose underlying signer is resolved on every call.
|
|
5
|
+
*
|
|
6
|
+
* `AsyncBulletinClient` takes a fixed `PolkadotSigner` at construction, but
|
|
7
|
+
* apps often build the bulletin client before any account is selected. This
|
|
8
|
+
* wrapper defers signer resolution: each call to `signTx` / `signBytes`
|
|
9
|
+
* invokes `getSigner()` and forwards to the result. If the getter returns
|
|
10
|
+
* `null`, calls throw with a clear message.
|
|
11
|
+
*
|
|
12
|
+
* The `publicKey` field is *also* resolved lazily — accessing it before a
|
|
13
|
+
* signer is available throws. This means callers that read `publicKey`
|
|
14
|
+
* eagerly will fail fast with the same error rather than seeing a stale
|
|
15
|
+
* key from a previously-selected account.
|
|
16
|
+
*
|
|
17
|
+
* Account changes between calls are picked up automatically: each sign
|
|
18
|
+
* resolves the current signer.
|
|
19
|
+
*/
|
|
20
|
+
export function createLazySigner(
|
|
21
|
+
getSigner: () => PolkadotSigner | null,
|
|
22
|
+
onMissing = "No signer available — connect a wallet and select an account first.",
|
|
23
|
+
): PolkadotSigner {
|
|
24
|
+
const resolve = (): PolkadotSigner => {
|
|
25
|
+
const inner = getSigner();
|
|
26
|
+
if (!inner) throw new Error(onMissing);
|
|
27
|
+
return inner;
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
// `async` on the methods is deliberate: it converts a "no signer" throw
|
|
31
|
+
// from `resolve()` into a rejected Promise. PolkadotSigner.signTx /
|
|
32
|
+
// signBytes are typed as returning Promises, and consumers expect a
|
|
33
|
+
// rejection rather than a synchronous escape on the failure path.
|
|
34
|
+
const lazy: PolkadotSigner = {
|
|
35
|
+
get publicKey() {
|
|
36
|
+
return resolve().publicKey;
|
|
37
|
+
},
|
|
38
|
+
signTx: async (...args: Parameters<PolkadotSigner["signTx"]>) => resolve().signTx(...args),
|
|
39
|
+
signBytes: async (...args: Parameters<PolkadotSigner["signBytes"]>) =>
|
|
40
|
+
resolve().signBytes(...args),
|
|
41
|
+
};
|
|
42
|
+
return lazy;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
if (import.meta.vitest) {
|
|
46
|
+
const { describe, test, expect, vi } = import.meta.vitest;
|
|
47
|
+
|
|
48
|
+
function makeMockSigner(label: string): PolkadotSigner {
|
|
49
|
+
return {
|
|
50
|
+
publicKey: new TextEncoder().encode(label),
|
|
51
|
+
signTx: vi.fn().mockResolvedValue(new Uint8Array([1])),
|
|
52
|
+
signBytes: vi.fn().mockResolvedValue(new Uint8Array([2])),
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
describe("createLazySigner", () => {
|
|
57
|
+
test("publicKey throws when getter returns null", () => {
|
|
58
|
+
const lazy = createLazySigner(() => null);
|
|
59
|
+
expect(() => lazy.publicKey).toThrow("No signer available");
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
test("publicKey resolves through getter when signer is available", () => {
|
|
63
|
+
const inner = makeMockSigner("alice");
|
|
64
|
+
const lazy = createLazySigner(() => inner);
|
|
65
|
+
expect(lazy.publicKey).toBe(inner.publicKey);
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
test("signTx throws when getter returns null", async () => {
|
|
69
|
+
const lazy = createLazySigner(() => null);
|
|
70
|
+
await expect(lazy.signTx(new Uint8Array(), {}, new Uint8Array(), 0)).rejects.toThrow(
|
|
71
|
+
"No signer available",
|
|
72
|
+
);
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
test("signTx forwards to current signer", async () => {
|
|
76
|
+
const inner = makeMockSigner("alice");
|
|
77
|
+
const lazy = createLazySigner(() => inner);
|
|
78
|
+
const callData = new Uint8Array([0xaa, 0xbb]);
|
|
79
|
+
const signedExtensions = {};
|
|
80
|
+
const metadata = new Uint8Array([0xcc]);
|
|
81
|
+
const atBlock = 42;
|
|
82
|
+
const result = await lazy.signTx(callData, signedExtensions, metadata, atBlock);
|
|
83
|
+
expect(inner.signTx).toHaveBeenCalledWith(
|
|
84
|
+
callData,
|
|
85
|
+
signedExtensions,
|
|
86
|
+
metadata,
|
|
87
|
+
atBlock,
|
|
88
|
+
);
|
|
89
|
+
expect(result).toEqual(new Uint8Array([1]));
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
test("signBytes forwards to current signer", async () => {
|
|
93
|
+
const inner = makeMockSigner("bob");
|
|
94
|
+
const lazy = createLazySigner(() => inner);
|
|
95
|
+
const result = await lazy.signBytes(new Uint8Array([9]));
|
|
96
|
+
expect(inner.signBytes).toHaveBeenCalledWith(new Uint8Array([9]));
|
|
97
|
+
expect(result).toEqual(new Uint8Array([2]));
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
test("picks up account changes between calls", () => {
|
|
101
|
+
let active: PolkadotSigner | null = makeMockSigner("first");
|
|
102
|
+
const lazy = createLazySigner(() => active);
|
|
103
|
+
expect(lazy.publicKey).toEqual(new TextEncoder().encode("first"));
|
|
104
|
+
active = makeMockSigner("second");
|
|
105
|
+
expect(lazy.publicKey).toEqual(new TextEncoder().encode("second"));
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
test("custom error message", () => {
|
|
109
|
+
const lazy = createLazySigner(() => null, "select an account first");
|
|
110
|
+
expect(() => lazy.publicKey).toThrow("select an account first");
|
|
111
|
+
});
|
|
112
|
+
});
|
|
113
|
+
}
|
package/src/networks.ts
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Known Bulletin Chain networks.
|
|
3
|
+
*
|
|
4
|
+
* Pairs each environment with the genesis hash and the PAPI descriptor needed
|
|
5
|
+
* to construct an `AsyncBulletinClient`. Re-uses the descriptor exported by
|
|
6
|
+
* `@parity/product-sdk-descriptors/bulletin` — the bulletin descriptor is the
|
|
7
|
+
* same across all environments today, so the difference between entries is
|
|
8
|
+
* the genesis hash (and, downstream, the chain RPC URL).
|
|
9
|
+
*/
|
|
10
|
+
import { bulletin as bulletinDescriptor } from "@parity/product-sdk-descriptors/bulletin";
|
|
11
|
+
|
|
12
|
+
export interface BulletinNetwork {
|
|
13
|
+
/** Genesis hash of the bulletin chain on this environment. */
|
|
14
|
+
genesisHash: `0x${string}`;
|
|
15
|
+
/** PAPI descriptor for typed API access. */
|
|
16
|
+
descriptor: typeof bulletinDescriptor;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Bulletin Chain network presets.
|
|
21
|
+
*
|
|
22
|
+
* Use these with {@link BulletinClient.create} when you want to be explicit
|
|
23
|
+
* about the network rather than passing an environment string. Reads go
|
|
24
|
+
* through the host's preimage subscription (container-only); no gateway
|
|
25
|
+
* URL is configured per network.
|
|
26
|
+
*/
|
|
27
|
+
export const BulletinChain = {
|
|
28
|
+
paseo: {
|
|
29
|
+
genesisHash: "0x744960c32e3a3df5440e1ecd4d34096f1ce2230d7016a5ada8a765d5a622b4ea",
|
|
30
|
+
descriptor: bulletinDescriptor,
|
|
31
|
+
},
|
|
32
|
+
} as const satisfies Record<string, BulletinNetwork>;
|
|
33
|
+
|
|
34
|
+
/** Network keys with built-in presets in {@link BulletinChain}. */
|
|
35
|
+
export type BulletinEnvironment = keyof typeof BulletinChain;
|
|
36
|
+
|
|
37
|
+
if (import.meta.vitest) {
|
|
38
|
+
const { describe, test, expect } = import.meta.vitest;
|
|
39
|
+
|
|
40
|
+
describe("BulletinChain", () => {
|
|
41
|
+
test("paseo has a valid genesis hash", () => {
|
|
42
|
+
expect(BulletinChain.paseo.genesisHash).toMatch(/^0x[a-f0-9]{64}$/);
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
test("paseo descriptor has matching genesis", () => {
|
|
46
|
+
expect(BulletinChain.paseo.descriptor.genesis).toBe(BulletinChain.paseo.genesisHash);
|
|
47
|
+
});
|
|
48
|
+
});
|
|
49
|
+
}
|