@parity/product-sdk-host 0.11.0 → 0.12.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 +528 -534
- package/dist/index.js +853 -285
- package/dist/index.js.map +1 -1
- package/package.json +5 -3
- package/src/accounts.ts +544 -0
- package/src/chain-spec.ts +126 -84
- package/src/chain-transaction.ts +107 -78
- package/src/chat.ts +81 -85
- package/src/container.ts +211 -246
- package/src/entropy.ts +63 -25
- package/src/errors.ts +198 -0
- package/src/features.ts +66 -55
- package/src/index.ts +33 -22
- package/src/navigation.ts +50 -49
- package/src/notifications.ts +59 -69
- package/src/papi-provider.ts +673 -0
- package/src/payments.ts +77 -61
- package/src/permissions.ts +107 -105
- package/src/result.ts +56 -0
- package/src/theme.ts +35 -63
- package/src/transport.ts +71 -0
- package/src/truapi.ts +166 -409
- package/src/types.ts +69 -61
package/src/chain-spec.ts
CHANGED
|
@@ -3,19 +3,21 @@
|
|
|
3
3
|
/**
|
|
4
4
|
* Higher-level wrapper for the host's chain-spec lookups.
|
|
5
5
|
*
|
|
6
|
-
* The host exposes three separate chain-spec calls — `
|
|
7
|
-
* `
|
|
8
|
-
* {@link getTruApi}
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
6
|
+
* The host exposes three separate chain-spec calls — `chain.getSpecGenesisHash`,
|
|
7
|
+
* `chain.getSpecChainName`, and `chain.getSpecProperties` — each reachable via
|
|
8
|
+
* {@link getTruApi} and each returning a neverthrow `ResultAsync`.
|
|
9
|
+
* {@link getChainSpec} fetches all three in one call and returns a single
|
|
10
|
+
* struct so callers read whichever field they need, matching the JSON-RPC
|
|
11
|
+
* `chainSpec_v1_*` family they mirror.
|
|
12
12
|
*
|
|
13
13
|
* @module
|
|
14
14
|
*/
|
|
15
15
|
|
|
16
16
|
import { createLogger } from "@parity/product-sdk-logger";
|
|
17
17
|
|
|
18
|
-
import {
|
|
18
|
+
import type { HostError } from "./errors.js";
|
|
19
|
+
import { type Result, ok } from "./result.js";
|
|
20
|
+
import { getTruApi, type HexString, mapHostResult } from "./truapi.js";
|
|
19
21
|
|
|
20
22
|
const log = createLogger("host:chain-spec");
|
|
21
23
|
|
|
@@ -25,8 +27,8 @@ const log = createLogger("host:chain-spec");
|
|
|
25
27
|
*
|
|
26
28
|
* The host returns this as a JSON string (mirroring the substrate
|
|
27
29
|
* `chainSpec_v1_properties` JSON-RPC, whose payload is an open-ended object).
|
|
28
|
-
* {@link getChainSpec} parses it into {@link properties} and also
|
|
29
|
-
* untouched JSON as {@link propertiesRaw}. The well-known substrate fields are
|
|
30
|
+
* {@link getChainSpec} parses it into {@link ChainSpec.properties} and also
|
|
31
|
+
* surfaces the untouched JSON as {@link ChainSpec.propertiesRaw}. The well-known substrate fields are
|
|
30
32
|
* typed for convenience; the index signature keeps any chain-specific extras
|
|
31
33
|
* reachable without `any` at the call site.
|
|
32
34
|
*/
|
|
@@ -62,71 +64,78 @@ export interface ChainSpec {
|
|
|
62
64
|
* Fetch a chain's full spec (genesis hash, name, and properties) from the host
|
|
63
65
|
* in one call.
|
|
64
66
|
*
|
|
65
|
-
* Issues the three underlying `
|
|
66
|
-
*
|
|
67
|
-
* result is the value the host echoes back from `
|
|
67
|
+
* Issues the three underlying `chain.getSpec*` requests concurrently, unwraps
|
|
68
|
+
* each response, and parses the properties JSON. Note the `genesisHash` in the
|
|
69
|
+
* result is the value the host echoes back from `getSpecGenesisHash` for the
|
|
68
70
|
* looked-up chain — pass the chain's known genesis hash as the lookup key.
|
|
69
71
|
*
|
|
72
|
+
* `null` (outside a container) is preserved as an `ok` value — it is an
|
|
73
|
+
* expected state, not a failure — so callers branch on `r.ok && r.value`. A
|
|
74
|
+
* real host-call failure surfaces on the `err` channel.
|
|
75
|
+
*
|
|
70
76
|
* @param genesisHash - The `0x`-prefixed genesis hash identifying the chain.
|
|
71
|
-
* @returns
|
|
72
|
-
* unavailable (running outside a container)
|
|
73
|
-
*
|
|
77
|
+
* @returns `ok(spec)` with the combined {@link ChainSpec}, `ok(null)` if the
|
|
78
|
+
* host is unavailable (running outside a container), or
|
|
79
|
+
* `err(HostCallFailedError)` if any underlying host call fails.
|
|
74
80
|
*
|
|
75
81
|
* @example
|
|
76
82
|
* ```ts
|
|
77
83
|
* import { getChainSpec } from "@parity/product-sdk-host";
|
|
78
84
|
*
|
|
79
|
-
* const
|
|
80
|
-
* if (
|
|
81
|
-
* console.log(
|
|
85
|
+
* const r = await getChainSpec(genesisHash);
|
|
86
|
+
* if (r.ok && r.value) {
|
|
87
|
+
* console.log(r.value.name, r.value.properties?.tokenSymbol);
|
|
82
88
|
* }
|
|
83
89
|
* ```
|
|
84
90
|
*/
|
|
85
|
-
export async function getChainSpec(
|
|
91
|
+
export async function getChainSpec(
|
|
92
|
+
genesisHash: HexString,
|
|
93
|
+
): Promise<Result<ChainSpec | null, HostError>> {
|
|
86
94
|
const truApi = await getTruApi();
|
|
87
95
|
if (!truApi) {
|
|
88
96
|
log.debug("getChainSpec: TruAPI unavailable");
|
|
89
|
-
return null;
|
|
97
|
+
return ok(null);
|
|
90
98
|
}
|
|
91
99
|
log.debug("getChainSpec", { genesisHash });
|
|
92
100
|
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
(
|
|
97
|
-
(
|
|
98
|
-
throw new Error(`getChainSpec (genesisHash) failed: ${formatHostError(err)}`, {
|
|
99
|
-
cause: err,
|
|
100
|
-
});
|
|
101
|
-
},
|
|
101
|
+
const [genesisHashResult, nameResult, propertiesResult] = await Promise.all([
|
|
102
|
+
mapHostResult(
|
|
103
|
+
truApi.chain.getSpecGenesisHash({ genesisHash }),
|
|
104
|
+
(response) => response.genesisHash,
|
|
105
|
+
"getChainSpec (genesisHash) failed",
|
|
102
106
|
),
|
|
103
|
-
|
|
104
|
-
(
|
|
105
|
-
(
|
|
106
|
-
|
|
107
|
-
cause: err,
|
|
108
|
-
});
|
|
109
|
-
},
|
|
107
|
+
mapHostResult(
|
|
108
|
+
truApi.chain.getSpecChainName({ genesisHash }),
|
|
109
|
+
(response) => response.chainName,
|
|
110
|
+
"getChainSpec (chainName) failed",
|
|
110
111
|
),
|
|
111
|
-
|
|
112
|
-
(
|
|
113
|
-
(
|
|
114
|
-
|
|
115
|
-
cause: err,
|
|
116
|
-
});
|
|
117
|
-
},
|
|
112
|
+
mapHostResult(
|
|
113
|
+
truApi.chain.getSpecProperties({ genesisHash }),
|
|
114
|
+
(response) => response.properties,
|
|
115
|
+
"getChainSpec (properties) failed",
|
|
118
116
|
),
|
|
119
117
|
]);
|
|
120
118
|
|
|
119
|
+
// Short-circuit on the first failing call.
|
|
120
|
+
if (!genesisHashResult.ok) return genesisHashResult;
|
|
121
|
+
if (!nameResult.ok) return nameResult;
|
|
122
|
+
if (!propertiesResult.ok) return propertiesResult;
|
|
123
|
+
|
|
124
|
+
const propertiesRaw = propertiesResult.value;
|
|
121
125
|
let properties: ChainProperties | null;
|
|
122
126
|
try {
|
|
123
127
|
properties = JSON.parse(propertiesRaw) as ChainProperties;
|
|
124
|
-
} catch (
|
|
125
|
-
log.debug("getChainSpec: properties JSON parse failed",
|
|
128
|
+
} catch (parseError) {
|
|
129
|
+
log.debug("getChainSpec: properties JSON parse failed", parseError);
|
|
126
130
|
properties = null;
|
|
127
131
|
}
|
|
128
132
|
|
|
129
|
-
return {
|
|
133
|
+
return ok({
|
|
134
|
+
genesisHash: genesisHashResult.value,
|
|
135
|
+
name: nameResult.value,
|
|
136
|
+
properties,
|
|
137
|
+
propertiesRaw,
|
|
138
|
+
});
|
|
130
139
|
}
|
|
131
140
|
|
|
132
141
|
if (import.meta.vitest) {
|
|
@@ -134,9 +143,11 @@ if (import.meta.vitest) {
|
|
|
134
143
|
|
|
135
144
|
async function withMockedTruApi<T>(
|
|
136
145
|
bridge: {
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
146
|
+
chain?: {
|
|
147
|
+
getSpecGenesisHash?: (req: unknown) => unknown;
|
|
148
|
+
getSpecChainName?: (req: unknown) => unknown;
|
|
149
|
+
getSpecProperties?: (req: unknown) => unknown;
|
|
150
|
+
};
|
|
140
151
|
} | null,
|
|
141
152
|
fn: (mod: typeof import("./chain-spec.js")) => Promise<T>,
|
|
142
153
|
): Promise<T> {
|
|
@@ -146,7 +157,6 @@ if (import.meta.vitest) {
|
|
|
146
157
|
return {
|
|
147
158
|
...original,
|
|
148
159
|
getTruApi: async () => bridge,
|
|
149
|
-
enumValue: (version: string, value: unknown) => ({ tag: version, value }),
|
|
150
160
|
};
|
|
151
161
|
});
|
|
152
162
|
try {
|
|
@@ -158,35 +168,47 @@ if (import.meta.vitest) {
|
|
|
158
168
|
}
|
|
159
169
|
}
|
|
160
170
|
|
|
161
|
-
|
|
162
|
-
|
|
171
|
+
/** A resolved ResultAsync stub yielding the given response object. */
|
|
172
|
+
const okAsync = (response: unknown) => ({
|
|
173
|
+
match: async (onOk: (v: unknown) => unknown) => onOk(response),
|
|
163
174
|
});
|
|
164
175
|
|
|
165
176
|
describe("getChainSpec", () => {
|
|
166
|
-
test("returns null when TruAPI is unavailable", async () => {
|
|
177
|
+
test("returns ok(null) when TruAPI is unavailable", async () => {
|
|
167
178
|
await withMockedTruApi(null, async (mod) => {
|
|
168
|
-
expect(await mod.getChainSpec("0x00")).
|
|
179
|
+
expect(await mod.getChainSpec("0x00")).toEqual({ ok: true, value: null });
|
|
169
180
|
});
|
|
170
181
|
});
|
|
171
182
|
|
|
172
183
|
test("combines the three calls and parses properties JSON", async () => {
|
|
173
184
|
await withMockedTruApi(
|
|
174
185
|
{
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
186
|
+
chain: {
|
|
187
|
+
getSpecGenesisHash: vi
|
|
188
|
+
.fn()
|
|
189
|
+
.mockReturnValue(okAsync({ genesisHash: "0xabcd" })),
|
|
190
|
+
getSpecChainName: vi
|
|
191
|
+
.fn()
|
|
192
|
+
.mockReturnValue(okAsync({ chainName: "Polkadot" })),
|
|
193
|
+
getSpecProperties: vi.fn().mockReturnValue(
|
|
194
|
+
okAsync({
|
|
195
|
+
properties:
|
|
196
|
+
'{"ss58Format":0,"tokenDecimals":10,"tokenSymbol":"DOT"}',
|
|
197
|
+
}),
|
|
181
198
|
),
|
|
199
|
+
},
|
|
182
200
|
},
|
|
183
201
|
async (mod) => {
|
|
184
|
-
const
|
|
185
|
-
expect(
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
202
|
+
const result = await mod.getChainSpec("0xabcd");
|
|
203
|
+
expect(result).toEqual({
|
|
204
|
+
ok: true,
|
|
205
|
+
value: {
|
|
206
|
+
genesisHash: "0xabcd",
|
|
207
|
+
name: "Polkadot",
|
|
208
|
+
properties: { ss58Format: 0, tokenDecimals: 10, tokenSymbol: "DOT" },
|
|
209
|
+
propertiesRaw:
|
|
210
|
+
'{"ss58Format":0,"tokenDecimals":10,"tokenSymbol":"DOT"}',
|
|
211
|
+
},
|
|
190
212
|
});
|
|
191
213
|
},
|
|
192
214
|
);
|
|
@@ -195,34 +217,54 @@ if (import.meta.vitest) {
|
|
|
195
217
|
test("leaves properties null when the JSON is malformed", async () => {
|
|
196
218
|
await withMockedTruApi(
|
|
197
219
|
{
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
220
|
+
chain: {
|
|
221
|
+
getSpecGenesisHash: vi
|
|
222
|
+
.fn()
|
|
223
|
+
.mockReturnValue(okAsync({ genesisHash: "0xabcd" })),
|
|
224
|
+
getSpecChainName: vi
|
|
225
|
+
.fn()
|
|
226
|
+
.mockReturnValue(okAsync({ chainName: "Polkadot" })),
|
|
227
|
+
getSpecProperties: vi
|
|
228
|
+
.fn()
|
|
229
|
+
.mockReturnValue(okAsync({ properties: "not json" })),
|
|
230
|
+
},
|
|
201
231
|
},
|
|
202
232
|
async (mod) => {
|
|
203
|
-
const
|
|
204
|
-
expect(
|
|
205
|
-
|
|
233
|
+
const result = await mod.getChainSpec("0xabcd");
|
|
234
|
+
expect(result.ok).toBe(true);
|
|
235
|
+
if (result.ok) {
|
|
236
|
+
expect(result.value?.properties).toBeNull();
|
|
237
|
+
expect(result.value?.propertiesRaw).toBe("not json");
|
|
238
|
+
}
|
|
206
239
|
},
|
|
207
240
|
);
|
|
208
241
|
});
|
|
209
242
|
|
|
210
|
-
test("
|
|
243
|
+
test("returns err(HostCallFailedError) when a host call fails", async () => {
|
|
211
244
|
await withMockedTruApi(
|
|
212
245
|
{
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
246
|
+
chain: {
|
|
247
|
+
getSpecGenesisHash: vi.fn().mockReturnValue({
|
|
248
|
+
match: async (
|
|
249
|
+
_onOk: (v: unknown) => unknown,
|
|
250
|
+
onErr: (e: unknown) => unknown,
|
|
251
|
+
) => onErr({ reason: "boom" }),
|
|
252
|
+
}),
|
|
253
|
+
getSpecChainName: vi
|
|
254
|
+
.fn()
|
|
255
|
+
.mockReturnValue(okAsync({ chainName: "Polkadot" })),
|
|
256
|
+
getSpecProperties: vi.fn().mockReturnValue(okAsync({ properties: "{}" })),
|
|
257
|
+
},
|
|
221
258
|
},
|
|
222
259
|
async (mod) => {
|
|
223
|
-
await
|
|
224
|
-
|
|
225
|
-
)
|
|
260
|
+
const result = await mod.getChainSpec("0xabcd");
|
|
261
|
+
expect(result.ok).toBe(false);
|
|
262
|
+
if (!result.ok) {
|
|
263
|
+
expect(result.error.name).toBe("HostCallFailedError");
|
|
264
|
+
expect(result.error.message).toMatch(
|
|
265
|
+
/getChainSpec \(genesisHash\) failed: boom/,
|
|
266
|
+
);
|
|
267
|
+
}
|
|
226
268
|
},
|
|
227
269
|
);
|
|
228
270
|
});
|
package/src/chain-transaction.ts
CHANGED
|
@@ -3,97 +3,92 @@
|
|
|
3
3
|
/**
|
|
4
4
|
* Higher-level wrappers for the host's transaction broadcast lifecycle.
|
|
5
5
|
*
|
|
6
|
-
* `
|
|
7
|
-
* reachable via {@link getTruApi}, but consumers have to
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
6
|
+
* `truApi.chain.broadcastTransaction` / `truApi.chain.stopTransaction` are
|
|
7
|
+
* reachable via {@link getTruApi}, but consumers have to unwrap the neverthrow
|
|
8
|
+
* `ResultAsync` themselves. {@link broadcastTransaction} and
|
|
9
|
+
* {@link stopTransaction} collapse that to `Result`-returning Promises, mirroring
|
|
10
|
+
* the JSON-RPC `transaction_v1_broadcast` / `transaction_v1_stop` pair they
|
|
11
|
+
* wrap.
|
|
12
12
|
*
|
|
13
13
|
* @module
|
|
14
14
|
*/
|
|
15
15
|
|
|
16
16
|
import { createLogger } from "@parity/product-sdk-logger";
|
|
17
17
|
|
|
18
|
-
import {
|
|
18
|
+
import { type HostError, HostUnavailableError } from "./errors.js";
|
|
19
|
+
import { type Result, err } from "./result.js";
|
|
20
|
+
import { getTruApi, type HexString, mapHostResult } from "./truapi.js";
|
|
19
21
|
|
|
20
22
|
const log = createLogger("host:chain-transaction");
|
|
21
23
|
|
|
22
24
|
/**
|
|
23
25
|
* Broadcast a signed transaction to the network via the host.
|
|
24
26
|
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
* operation id.
|
|
27
|
+
* Calls `truApi.chain.broadcastTransaction` and unwraps the response. The host
|
|
28
|
+
* keeps re-broadcasting until the transaction is finalized/dropped or
|
|
29
|
+
* {@link stopTransaction} is called with the returned operation id.
|
|
29
30
|
*
|
|
30
31
|
* @param genesisHash - The `0x`-prefixed genesis hash of the target chain.
|
|
31
32
|
* @param transaction - The `0x`-prefixed SCALE-encoded signed transaction.
|
|
32
|
-
* @returns
|
|
33
|
-
* the host accepted the broadcast without issuing one
|
|
34
|
-
*
|
|
33
|
+
* @returns `ok` with the operation id to pass to {@link stopTransaction} (or
|
|
34
|
+
* `null` if the host accepted the broadcast without issuing one), or
|
|
35
|
+
* `err(HostUnavailableError | HostCallFailedError)`.
|
|
35
36
|
*
|
|
36
37
|
* @example
|
|
37
38
|
* ```ts
|
|
38
39
|
* import { broadcastTransaction, stopTransaction } from "@parity/product-sdk-host";
|
|
39
40
|
*
|
|
40
|
-
* const
|
|
41
|
+
* const r = await broadcastTransaction(genesisHash, signedTx);
|
|
41
42
|
* // later, to stop re-broadcasting:
|
|
42
|
-
* if (
|
|
43
|
+
* if (r.ok && r.value) await stopTransaction(genesisHash, r.value);
|
|
43
44
|
* ```
|
|
44
45
|
*/
|
|
45
46
|
export async function broadcastTransaction(
|
|
46
47
|
genesisHash: HexString,
|
|
47
48
|
transaction: HexString,
|
|
48
|
-
): Promise<string | null
|
|
49
|
+
): Promise<Result<string | null, HostError>> {
|
|
49
50
|
const truApi = await getTruApi();
|
|
50
51
|
if (!truApi) {
|
|
51
|
-
|
|
52
|
+
return err(new HostUnavailableError("broadcastTransaction: TruAPI unavailable"));
|
|
52
53
|
}
|
|
53
54
|
log.debug("broadcastTransaction", { genesisHash });
|
|
54
55
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
(err: unknown) => {
|
|
61
|
-
throw new Error(`broadcastTransaction failed: ${formatHostError(err)}`, {
|
|
62
|
-
cause: err,
|
|
63
|
-
});
|
|
64
|
-
},
|
|
65
|
-
);
|
|
56
|
+
return mapHostResult(
|
|
57
|
+
truApi.chain.broadcastTransaction({ genesisHash, transaction }),
|
|
58
|
+
(response) => response.operationId ?? null,
|
|
59
|
+
"broadcastTransaction failed",
|
|
60
|
+
);
|
|
66
61
|
}
|
|
67
62
|
|
|
68
63
|
/**
|
|
69
64
|
* Stop an in-flight broadcast started by {@link broadcastTransaction}.
|
|
70
65
|
*
|
|
71
|
-
*
|
|
72
|
-
* the response.
|
|
66
|
+
* Calls `truApi.chain.stopTransaction` and unwraps the response.
|
|
73
67
|
*
|
|
74
68
|
* @param genesisHash - The `0x`-prefixed genesis hash of the target chain.
|
|
75
69
|
* @param operationId - The operation id returned by
|
|
76
70
|
* {@link broadcastTransaction}.
|
|
77
|
-
* @
|
|
71
|
+
* @returns `ok` on success, or `err(HostUnavailableError | HostCallFailedError)`.
|
|
78
72
|
*
|
|
79
73
|
* @example
|
|
80
74
|
* ```ts
|
|
81
75
|
* await stopTransaction(genesisHash, operationId);
|
|
82
76
|
* ```
|
|
83
77
|
*/
|
|
84
|
-
export async function stopTransaction(
|
|
78
|
+
export async function stopTransaction(
|
|
79
|
+
genesisHash: HexString,
|
|
80
|
+
operationId: string,
|
|
81
|
+
): Promise<Result<void, HostError>> {
|
|
85
82
|
const truApi = await getTruApi();
|
|
86
83
|
if (!truApi) {
|
|
87
|
-
|
|
84
|
+
return err(new HostUnavailableError("stopTransaction: TruAPI unavailable"));
|
|
88
85
|
}
|
|
89
86
|
log.debug("stopTransaction", { genesisHash, operationId });
|
|
90
87
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
(
|
|
94
|
-
|
|
95
|
-
throw new Error(`stopTransaction failed: ${formatHostError(err)}`, { cause: err });
|
|
96
|
-
},
|
|
88
|
+
return mapHostResult(
|
|
89
|
+
truApi.chain.stopTransaction({ genesisHash, operationId }),
|
|
90
|
+
() => undefined,
|
|
91
|
+
"stopTransaction failed",
|
|
97
92
|
);
|
|
98
93
|
}
|
|
99
94
|
|
|
@@ -102,8 +97,10 @@ if (import.meta.vitest) {
|
|
|
102
97
|
|
|
103
98
|
async function withMockedTruApi<T>(
|
|
104
99
|
bridge: {
|
|
105
|
-
|
|
106
|
-
|
|
100
|
+
chain?: {
|
|
101
|
+
broadcastTransaction?: (req: unknown) => unknown;
|
|
102
|
+
stopTransaction?: (req: unknown) => unknown;
|
|
103
|
+
};
|
|
107
104
|
} | null,
|
|
108
105
|
fn: (mod: typeof import("./chain-transaction.js")) => Promise<T>,
|
|
109
106
|
): Promise<T> {
|
|
@@ -113,7 +110,6 @@ if (import.meta.vitest) {
|
|
|
113
110
|
return {
|
|
114
111
|
...original,
|
|
115
112
|
getTruApi: async () => bridge,
|
|
116
|
-
enumValue: (version: string, value: unknown) => ({ tag: version, value }),
|
|
117
113
|
};
|
|
118
114
|
});
|
|
119
115
|
try {
|
|
@@ -125,86 +121,119 @@ if (import.meta.vitest) {
|
|
|
125
121
|
}
|
|
126
122
|
}
|
|
127
123
|
|
|
128
|
-
|
|
129
|
-
|
|
124
|
+
/** A resolved ResultAsync stub yielding the given response object. */
|
|
125
|
+
const ok = (response: unknown) => ({
|
|
126
|
+
match: async (onOk: (v: unknown) => unknown) => onOk(response),
|
|
130
127
|
});
|
|
131
|
-
|
|
128
|
+
/** A rejected ResultAsync stub yielding a truapi `GenericError` (`{ reason }`). */
|
|
129
|
+
const errResult = (reason: string) => ({
|
|
132
130
|
match: async (_onOk: (v: unknown) => unknown, onErr: (e: unknown) => unknown) =>
|
|
133
|
-
onErr({
|
|
131
|
+
onErr({ reason }),
|
|
134
132
|
});
|
|
135
133
|
|
|
136
134
|
describe("broadcastTransaction", () => {
|
|
137
|
-
test("
|
|
135
|
+
test("returns err(HostUnavailableError) when TruAPI is unavailable", async () => {
|
|
138
136
|
await withMockedTruApi(null, async (mod) => {
|
|
139
|
-
await
|
|
140
|
-
|
|
141
|
-
)
|
|
137
|
+
const result = await mod.broadcastTransaction("0x00", "0x01");
|
|
138
|
+
expect(result.ok).toBe(false);
|
|
139
|
+
if (!result.ok) {
|
|
140
|
+
expect(result.error.name).toBe("HostUnavailableError");
|
|
141
|
+
}
|
|
142
142
|
});
|
|
143
143
|
});
|
|
144
144
|
|
|
145
|
-
test("
|
|
145
|
+
test("returns ok with the operation id", async () => {
|
|
146
146
|
await withMockedTruApi(
|
|
147
|
-
{
|
|
147
|
+
{
|
|
148
|
+
chain: {
|
|
149
|
+
broadcastTransaction: vi.fn().mockReturnValue(ok({ operationId: "op-1" })),
|
|
150
|
+
},
|
|
151
|
+
},
|
|
148
152
|
async (mod) => {
|
|
149
|
-
expect(await mod.broadcastTransaction("0x00", "0x01")).
|
|
153
|
+
expect(await mod.broadcastTransaction("0x00", "0x01")).toEqual({
|
|
154
|
+
ok: true,
|
|
155
|
+
value: "op-1",
|
|
156
|
+
});
|
|
150
157
|
},
|
|
151
158
|
);
|
|
152
159
|
});
|
|
153
160
|
|
|
154
|
-
test("passes through a
|
|
161
|
+
test("passes through a missing operation id as ok(null)", async () => {
|
|
155
162
|
await withMockedTruApi(
|
|
156
|
-
{
|
|
163
|
+
{
|
|
164
|
+
chain: {
|
|
165
|
+
broadcastTransaction: vi.fn().mockReturnValue(ok({})),
|
|
166
|
+
},
|
|
167
|
+
},
|
|
157
168
|
async (mod) => {
|
|
158
|
-
expect(await mod.broadcastTransaction("0x00", "0x01")).
|
|
169
|
+
expect(await mod.broadcastTransaction("0x00", "0x01")).toEqual({
|
|
170
|
+
ok: true,
|
|
171
|
+
value: null,
|
|
172
|
+
});
|
|
159
173
|
},
|
|
160
174
|
);
|
|
161
175
|
});
|
|
162
176
|
|
|
163
|
-
test("wraps host errors with a diagnostic message", async () => {
|
|
177
|
+
test("wraps host errors in err(HostCallFailedError) with a diagnostic message", async () => {
|
|
164
178
|
await withMockedTruApi(
|
|
165
179
|
{
|
|
166
|
-
|
|
167
|
-
.fn()
|
|
168
|
-
|
|
180
|
+
chain: {
|
|
181
|
+
broadcastTransaction: vi.fn().mockReturnValue(errResult("boom")),
|
|
182
|
+
},
|
|
169
183
|
},
|
|
170
184
|
async (mod) => {
|
|
171
|
-
await
|
|
172
|
-
|
|
173
|
-
)
|
|
185
|
+
const result = await mod.broadcastTransaction("0x00", "0x01");
|
|
186
|
+
expect(result.ok).toBe(false);
|
|
187
|
+
if (!result.ok) {
|
|
188
|
+
expect(result.error.name).toBe("HostCallFailedError");
|
|
189
|
+
expect(result.error.message).toMatch(/broadcastTransaction failed: boom/);
|
|
190
|
+
}
|
|
174
191
|
},
|
|
175
192
|
);
|
|
176
193
|
});
|
|
177
194
|
});
|
|
178
195
|
|
|
179
196
|
describe("stopTransaction", () => {
|
|
180
|
-
test("
|
|
197
|
+
test("returns err(HostUnavailableError) when TruAPI is unavailable", async () => {
|
|
181
198
|
await withMockedTruApi(null, async (mod) => {
|
|
182
|
-
await
|
|
183
|
-
|
|
184
|
-
)
|
|
199
|
+
const result = await mod.stopTransaction("0x00", "op-1");
|
|
200
|
+
expect(result.ok).toBe(false);
|
|
201
|
+
if (!result.ok) {
|
|
202
|
+
expect(result.error.name).toBe("HostUnavailableError");
|
|
203
|
+
}
|
|
185
204
|
});
|
|
186
205
|
});
|
|
187
206
|
|
|
188
|
-
test("
|
|
207
|
+
test("returns ok on success", async () => {
|
|
189
208
|
await withMockedTruApi(
|
|
190
|
-
{
|
|
209
|
+
{
|
|
210
|
+
chain: {
|
|
211
|
+
stopTransaction: vi.fn().mockReturnValue(ok(undefined)),
|
|
212
|
+
},
|
|
213
|
+
},
|
|
191
214
|
async (mod) => {
|
|
192
|
-
await
|
|
215
|
+
expect(await mod.stopTransaction("0x00", "op-1")).toEqual({
|
|
216
|
+
ok: true,
|
|
217
|
+
value: undefined,
|
|
218
|
+
});
|
|
193
219
|
},
|
|
194
220
|
);
|
|
195
221
|
});
|
|
196
222
|
|
|
197
|
-
test("wraps host errors with a diagnostic message", async () => {
|
|
223
|
+
test("wraps host errors in err(HostCallFailedError) with a diagnostic message", async () => {
|
|
198
224
|
await withMockedTruApi(
|
|
199
225
|
{
|
|
200
|
-
|
|
201
|
-
.fn()
|
|
202
|
-
|
|
226
|
+
chain: {
|
|
227
|
+
stopTransaction: vi.fn().mockReturnValue(errResult("boom")),
|
|
228
|
+
},
|
|
203
229
|
},
|
|
204
230
|
async (mod) => {
|
|
205
|
-
await
|
|
206
|
-
|
|
207
|
-
)
|
|
231
|
+
const result = await mod.stopTransaction("0x00", "op-1");
|
|
232
|
+
expect(result.ok).toBe(false);
|
|
233
|
+
if (!result.ok) {
|
|
234
|
+
expect(result.error.name).toBe("HostCallFailedError");
|
|
235
|
+
expect(result.error.message).toMatch(/stopTransaction failed: boom/);
|
|
236
|
+
}
|
|
208
237
|
},
|
|
209
238
|
);
|
|
210
239
|
});
|