@ckb-ccc/core 1.21.0 → 1.22.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/CHANGELOG.md +50 -0
- package/README.md +3 -3
- package/dist/advanced.d.mts +1 -1
- package/dist/advanced.mjs +1 -1
- package/dist/{advancedBarrel-B_9PyL2D.d.mts → advancedBarrel-Daj_LJkz.d.mts} +139 -19
- package/dist/advancedBarrel-Daj_LJkz.d.mts.map +1 -0
- package/dist/advancedBarrel.d.mts +1 -1
- package/dist/advancedBarrel.mjs +1 -1
- package/dist/barrel-pRBtHOAW.mjs +7 -0
- package/dist/barrel-pRBtHOAW.mjs.map +1 -0
- package/dist/barrel.d.mts +2 -2
- package/dist/barrel.mjs +1 -1
- package/dist/index.d.mts +2 -2
- package/dist/index.mjs +1 -1
- package/dist/transformers-lavDTtAW.mjs +2 -0
- package/dist/transformers-lavDTtAW.mjs.map +1 -0
- package/dist.commonjs/advanced.d.ts +1 -1
- package/dist.commonjs/advanced.js +1 -1
- package/dist.commonjs/{advancedBarrel-Cll2j9KP.d.ts → advancedBarrel-CJL3rfuW.d.ts} +139 -19
- package/dist.commonjs/advancedBarrel-CJL3rfuW.d.ts.map +1 -0
- package/dist.commonjs/advancedBarrel.d.ts +1 -1
- package/dist.commonjs/advancedBarrel.js +1 -1
- package/dist.commonjs/barrel--PaV2IZx.js +23 -0
- package/dist.commonjs/barrel--PaV2IZx.js.map +1 -0
- package/dist.commonjs/barrel.d.ts +2 -2
- package/dist.commonjs/barrel.js +1 -1
- package/dist.commonjs/index.d.ts +2 -2
- package/dist.commonjs/index.js +1 -1
- package/dist.commonjs/transformers-VBXQGQKe.js +2 -0
- package/dist.commonjs/transformers-VBXQGQKe.js.map +1 -0
- package/package.json +1 -1
- package/src/ckb/transaction.ts +20 -4
- package/src/client/client.ts +8 -2
- package/src/client/clientPublicMainnet.advanced.ts +9 -0
- package/src/client/clientPublicTestnet.advanced.ts +9 -0
- package/src/client/jsonRpc/client.ts +1 -1
- package/src/fixedPoint/index.ts +17 -8
- package/src/jsonRpc/requestor.ts +32 -8
- package/src/jsonRpc/transports/fallback.ts +6 -2
- package/src/jsonRpc/transports/http.ts +20 -17
- package/src/jsonRpc/transports/transport.ts +11 -1
- package/src/jsonRpc/transports/webSocket.ts +87 -27
- package/src/signer/btc/psbt.ts +4 -2
- package/src/signer/jsonRpc/handler.ts +394 -133
- package/src/signer/jsonRpc/signer.ts +178 -34
- package/src/signer/jsonRpc/transformers.ts +4 -0
- package/src/utils/abortSignal.ts +14 -0
- package/src/utils/index.ts +150 -0
- package/dist/advancedBarrel-B_9PyL2D.d.mts.map +0 -1
- package/dist/barrel-CrN8Ibc4.mjs +0 -7
- package/dist/barrel-CrN8Ibc4.mjs.map +0 -1
- package/dist/transformers-7aume1sD.mjs +0 -2
- package/dist/transformers-7aume1sD.mjs.map +0 -1
- package/dist.commonjs/advancedBarrel-Cll2j9KP.d.ts.map +0 -1
- package/dist.commonjs/barrel-BL6-tNI3.js +0 -23
- package/dist.commonjs/barrel-BL6-tNI3.js.map +0 -1
- package/dist.commonjs/transformers-1XBitiyh.js +0 -2
- package/dist.commonjs/transformers-1XBitiyh.js.map +0 -1
package/package.json
CHANGED
package/src/ckb/transaction.ts
CHANGED
|
@@ -2497,13 +2497,22 @@ export class Transaction extends Entity.Base<TransactionLike, Transaction>() {
|
|
|
2497
2497
|
const needed = numFrom(
|
|
2498
2498
|
await Promise.resolve(change(changedTx, fee - leastFee)),
|
|
2499
2499
|
);
|
|
2500
|
+
|
|
2500
2501
|
if (needed > Zero) {
|
|
2501
2502
|
// No enough extra capacity to create new cells for change, collect inputs again
|
|
2502
2503
|
leastExtraCapacity = needed;
|
|
2503
2504
|
continue;
|
|
2504
2505
|
}
|
|
2505
2506
|
|
|
2506
|
-
|
|
2507
|
+
const changedActualFee = await changedTx.getFee(from.client);
|
|
2508
|
+
if (changedActualFee < leastFee && fee < leastFee) {
|
|
2509
|
+
// Fee is not fully paid yet because the initial transaction had insufficient inputs.
|
|
2510
|
+
// Collect inputs again to pay leastFee.
|
|
2511
|
+
leastExtraCapacity = Zero;
|
|
2512
|
+
continue;
|
|
2513
|
+
}
|
|
2514
|
+
|
|
2515
|
+
if (changedActualFee !== leastFee) {
|
|
2507
2516
|
throw new Error(
|
|
2508
2517
|
"The change function doesn't use all available capacity",
|
|
2509
2518
|
);
|
|
@@ -2671,14 +2680,21 @@ export class Transaction extends Entity.Base<TransactionLike, Transaction>() {
|
|
|
2671
2680
|
shouldAddInputs?: boolean;
|
|
2672
2681
|
},
|
|
2673
2682
|
): Promise<[number, boolean]> {
|
|
2674
|
-
const change =
|
|
2675
|
-
if (!this.outputs[change]) {
|
|
2683
|
+
const change = transactionIndexFrom(index);
|
|
2684
|
+
if (change === undefined || !this.outputs[change]) {
|
|
2676
2685
|
throw new Error("Non-existed output to change");
|
|
2677
2686
|
}
|
|
2678
2687
|
return this.completeFee(
|
|
2679
2688
|
from,
|
|
2680
2689
|
(tx, capacity) => {
|
|
2681
|
-
tx.
|
|
2690
|
+
tx.setOutput(
|
|
2691
|
+
change,
|
|
2692
|
+
{
|
|
2693
|
+
...tx.outputs[change],
|
|
2694
|
+
capacity: tx.outputs[change].capacity + capacity,
|
|
2695
|
+
},
|
|
2696
|
+
tx.outputsData[change],
|
|
2697
|
+
);
|
|
2682
2698
|
return 0;
|
|
2683
2699
|
},
|
|
2684
2700
|
feeRate,
|
package/src/client/client.ts
CHANGED
|
@@ -450,7 +450,9 @@ export abstract class Client {
|
|
|
450
450
|
after?: string,
|
|
451
451
|
): Promise<ClientFindCellsResponse> {
|
|
452
452
|
const res = await this.findCellsPagedNoCache(key, order, limit, after);
|
|
453
|
-
|
|
453
|
+
if (key.withData !== false) {
|
|
454
|
+
await this.cache.recordCells(res.cells);
|
|
455
|
+
}
|
|
454
456
|
return res;
|
|
455
457
|
}
|
|
456
458
|
|
|
@@ -645,7 +647,11 @@ export abstract class Client {
|
|
|
645
647
|
|
|
646
648
|
/**
|
|
647
649
|
* Resolve cell dependency info into concrete CellDep objects.
|
|
648
|
-
*
|
|
650
|
+
*
|
|
651
|
+
* If a CellDepInfo specifies a type script, the live cell carrying it is looked up
|
|
652
|
+
* on-chain and, when found, its outpoint is used instead of the configured one. When
|
|
653
|
+
* the lookup finds nothing, the configured outpoint is used as written. A CellDepInfo
|
|
654
|
+
* without a type script is always used as written.
|
|
649
655
|
*
|
|
650
656
|
* @param cellDepsInfoLike - One or more CellDepInfo or arrays of CellDepInfo.
|
|
651
657
|
* @returns Resolved CellDep array ready to be added to a transaction.
|
|
@@ -1,6 +1,15 @@
|
|
|
1
1
|
import { ScriptInfoLike } from "./clientTypes.js";
|
|
2
2
|
import { KnownScript } from "./knownScript.js";
|
|
3
3
|
|
|
4
|
+
/**
|
|
5
|
+
* The known scripts, with a cell dep outpoint recorded for each.
|
|
6
|
+
*
|
|
7
|
+
* An outpoint here is a starting point. A dep with a `type` is resolved on-chain by
|
|
8
|
+
* {@link Client.getCellDeps} before use, so its outpoint keeps working after the code
|
|
9
|
+
* cell moves and may look spent when checked directly. Only a dep without a `type`
|
|
10
|
+
* needs updating when its cell moves. See #531.
|
|
11
|
+
*/
|
|
12
|
+
|
|
4
13
|
export const MAINNET_SCRIPTS: Record<KnownScript, ScriptInfoLike | undefined> =
|
|
5
14
|
Object.freeze({
|
|
6
15
|
[KnownScript.NervosDao]: {
|
|
@@ -1,6 +1,15 @@
|
|
|
1
1
|
import { ScriptInfoLike } from "./clientTypes.js";
|
|
2
2
|
import { KnownScript } from "./knownScript.js";
|
|
3
3
|
|
|
4
|
+
/**
|
|
5
|
+
* The known scripts, with a cell dep outpoint recorded for each.
|
|
6
|
+
*
|
|
7
|
+
* An outpoint here is a starting point. A dep with a `type` is resolved on-chain by
|
|
8
|
+
* {@link Client.getCellDeps} before use, so its outpoint keeps working after the code
|
|
9
|
+
* cell moves and may look spent when checked directly. Only a dep without a `type`
|
|
10
|
+
* needs updating when its cell moves. See #531.
|
|
11
|
+
*/
|
|
12
|
+
|
|
4
13
|
export const TESTNET_SCRIPTS: Record<KnownScript, ScriptInfoLike> =
|
|
5
14
|
Object.freeze({
|
|
6
15
|
[KnownScript.NervosDao]: {
|
|
@@ -178,7 +178,7 @@ export abstract class ClientJsonRpc extends Client {
|
|
|
178
178
|
|
|
179
179
|
getFeeRateStatistics = this.buildSender(
|
|
180
180
|
"get_fee_rate_statistics",
|
|
181
|
-
[(n
|
|
181
|
+
[(n?: NumLike) => apply(numToHex, n)],
|
|
182
182
|
// TODO: This is a temporary workaround for the fact that the node may return null for fee rate statistics.
|
|
183
183
|
// We should change the method signature to return null instead of falling back to the default value,
|
|
184
184
|
// but this would be a breaking change.
|
package/src/fixedPoint/index.ts
CHANGED
|
@@ -30,7 +30,11 @@ export type FixedPointLike = bigint | string | number;
|
|
|
30
30
|
*/
|
|
31
31
|
|
|
32
32
|
export function fixedPointToString(val: FixedPointLike, decimals = 8): string {
|
|
33
|
-
const
|
|
33
|
+
const fp = fixedPointFrom(val, decimals);
|
|
34
|
+
if (fp < 0n) {
|
|
35
|
+
return `-${fixedPointToString(-fp, decimals)}`;
|
|
36
|
+
}
|
|
37
|
+
const str = fp.toString();
|
|
34
38
|
if (decimals === 0) {
|
|
35
39
|
return str;
|
|
36
40
|
}
|
|
@@ -65,15 +69,20 @@ export function fixedPointFrom(val: FixedPointLike, decimals = 8): FixedPoint {
|
|
|
65
69
|
return val;
|
|
66
70
|
}
|
|
67
71
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
72
|
+
let str = typeof val === "number" ? val.toFixed(decimals) : val.toString();
|
|
73
|
+
const isNegative = str.startsWith("-");
|
|
74
|
+
if (isNegative || str.startsWith("+")) {
|
|
75
|
+
str = str.slice(1);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const [l, r] = str.split(".");
|
|
79
|
+
const lVal = l === "" ? 0n : BigInt(l.padEnd(l.length + decimals, "0"));
|
|
80
|
+
if (r === undefined || decimals === 0) {
|
|
81
|
+
return isNegative ? -lVal : lVal;
|
|
74
82
|
}
|
|
75
83
|
|
|
76
|
-
|
|
84
|
+
const unsignedVal = lVal + BigInt(r.slice(0, decimals).padEnd(decimals, "0"));
|
|
85
|
+
return isNegative ? -unsignedVal : unsignedVal;
|
|
77
86
|
}
|
|
78
87
|
|
|
79
88
|
/**
|
package/src/jsonRpc/requestor.ts
CHANGED
|
@@ -1,11 +1,14 @@
|
|
|
1
|
+
import { abortSignalToPromise } from "../utils/abortSignal.js";
|
|
1
2
|
import { OwnerAggregated } from "../utils/owner/aggregated.js";
|
|
2
3
|
import { Owner } from "../utils/owner/owner.js";
|
|
3
4
|
import { jsonRpcTransportFromUri } from "./transports/factory.js";
|
|
4
5
|
import { JsonRpcTransportFallback } from "./transports/fallback.js";
|
|
5
6
|
import {
|
|
7
|
+
JsonRpcError,
|
|
6
8
|
JsonRpcPayload,
|
|
7
9
|
JsonRpcResponse,
|
|
8
10
|
JsonRpcTransport,
|
|
11
|
+
JsonRpcTransportRequestOptions,
|
|
9
12
|
} from "./transports/index.js";
|
|
10
13
|
|
|
11
14
|
function openTransports(
|
|
@@ -146,6 +149,7 @@ export class RequestorJsonRpc {
|
|
|
146
149
|
inTransformers?: (((_: any) => unknown) | undefined)[],
|
|
147
150
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
148
151
|
outTransformer?: (_: any) => unknown,
|
|
152
|
+
options?: JsonRpcTransportRequestOptions,
|
|
149
153
|
): Promise<unknown> {
|
|
150
154
|
const payload = this.buildPayload(
|
|
151
155
|
rpcMethod,
|
|
@@ -164,7 +168,7 @@ export class RequestorJsonRpc {
|
|
|
164
168
|
|
|
165
169
|
try {
|
|
166
170
|
return await transform(
|
|
167
|
-
await this.requestPayload(payload),
|
|
171
|
+
await this.requestPayload(payload, options),
|
|
168
172
|
outTransformer,
|
|
169
173
|
);
|
|
170
174
|
} catch (err: unknown) {
|
|
@@ -175,21 +179,41 @@ export class RequestorJsonRpc {
|
|
|
175
179
|
}
|
|
176
180
|
}
|
|
177
181
|
|
|
178
|
-
async requestPayload(
|
|
182
|
+
async requestPayload(
|
|
183
|
+
payload: JsonRpcPayload,
|
|
184
|
+
options?: JsonRpcTransportRequestOptions,
|
|
185
|
+
): Promise<unknown> {
|
|
179
186
|
if (
|
|
180
187
|
this.maxConcurrent !== undefined &&
|
|
181
188
|
this.concurrent >= this.maxConcurrent
|
|
182
189
|
) {
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
)
|
|
186
|
-
|
|
190
|
+
options?.signal?.throwIfAborted();
|
|
191
|
+
let resolvePending: (() => void) | undefined;
|
|
192
|
+
const pending = new Promise<void>((resolve) => {
|
|
193
|
+
resolvePending = resolve;
|
|
194
|
+
this.pending.push(resolve);
|
|
195
|
+
});
|
|
196
|
+
try {
|
|
197
|
+
await (options?.signal
|
|
198
|
+
? Promise.race([pending, abortSignalToPromise(options.signal)])
|
|
199
|
+
: pending);
|
|
200
|
+
} catch (cause) {
|
|
201
|
+
const index = resolvePending
|
|
202
|
+
? this.pending.indexOf(resolvePending)
|
|
203
|
+
: -1;
|
|
204
|
+
if (index !== -1) {
|
|
205
|
+
this.pending.splice(index, 1);
|
|
206
|
+
} else {
|
|
207
|
+
this.pending.shift()?.();
|
|
208
|
+
}
|
|
209
|
+
throw cause;
|
|
210
|
+
}
|
|
187
211
|
}
|
|
188
212
|
|
|
189
213
|
const res: JsonRpcResponse = await (async () => {
|
|
190
214
|
this.concurrent += 1;
|
|
191
215
|
try {
|
|
192
|
-
return await this.transport.request(payload);
|
|
216
|
+
return await this.transport.request(payload, options);
|
|
193
217
|
} finally {
|
|
194
218
|
this.concurrent -= 1;
|
|
195
219
|
this.pending.shift()?.();
|
|
@@ -200,7 +224,7 @@ export class RequestorJsonRpc {
|
|
|
200
224
|
throw new Error(`Id mismatched, got ${res.id}, expected ${payload.id}`);
|
|
201
225
|
}
|
|
202
226
|
if (res.error != null) {
|
|
203
|
-
throw res.error
|
|
227
|
+
throw new JsonRpcError(res.error);
|
|
204
228
|
}
|
|
205
229
|
return res.result;
|
|
206
230
|
}
|
|
@@ -2,6 +2,7 @@ import {
|
|
|
2
2
|
JsonRpcPayload,
|
|
3
3
|
JsonRpcResponse,
|
|
4
4
|
JsonRpcTransport,
|
|
5
|
+
JsonRpcTransportRequestOptions,
|
|
5
6
|
} from "./transport.js";
|
|
6
7
|
|
|
7
8
|
export class JsonRpcTransportFallback implements JsonRpcTransport {
|
|
@@ -10,7 +11,10 @@ export class JsonRpcTransportFallback implements JsonRpcTransport {
|
|
|
10
11
|
|
|
11
12
|
constructor(private readonly transports: JsonRpcTransport[]) {}
|
|
12
13
|
|
|
13
|
-
async request(
|
|
14
|
+
async request(
|
|
15
|
+
data: JsonRpcPayload,
|
|
16
|
+
options?: JsonRpcTransportRequestOptions,
|
|
17
|
+
): Promise<JsonRpcResponse> {
|
|
14
18
|
const startI = this.i;
|
|
15
19
|
let lastErr: unknown = new Error(
|
|
16
20
|
"JsonRpcTransportFallback requires at least one transport",
|
|
@@ -20,7 +24,7 @@ export class JsonRpcTransportFallback implements JsonRpcTransport {
|
|
|
20
24
|
const i = (startI + tried) % this.transports.length;
|
|
21
25
|
|
|
22
26
|
try {
|
|
23
|
-
const res = await this.transports[i].request(data);
|
|
27
|
+
const res = await this.transports[i].request(data, options);
|
|
24
28
|
|
|
25
29
|
this.i = i;
|
|
26
30
|
return res;
|
|
@@ -1,7 +1,9 @@
|
|
|
1
|
+
import { abortSignalAny } from "../../utils/abortSignal.js";
|
|
1
2
|
import {
|
|
2
3
|
JsonRpcPayload,
|
|
3
4
|
JsonRpcResponse,
|
|
4
5
|
JsonRpcTransport,
|
|
6
|
+
JsonRpcTransportRequestOptions,
|
|
5
7
|
} from "./transport.js";
|
|
6
8
|
|
|
7
9
|
export class JsonRpcTransportHttp implements JsonRpcTransport {
|
|
@@ -10,23 +12,24 @@ export class JsonRpcTransportHttp implements JsonRpcTransport {
|
|
|
10
12
|
private readonly timeout = 30000,
|
|
11
13
|
) {}
|
|
12
14
|
|
|
13
|
-
async request(
|
|
14
|
-
|
|
15
|
-
|
|
15
|
+
async request(
|
|
16
|
+
payload: JsonRpcPayload,
|
|
17
|
+
options?: JsonRpcTransportRequestOptions,
|
|
18
|
+
): Promise<JsonRpcResponse> {
|
|
19
|
+
const timeoutSignal = AbortSignal.timeout(options?.timeout ?? this.timeout);
|
|
20
|
+
const signal = options?.signal
|
|
21
|
+
? abortSignalAny([options.signal, timeoutSignal])
|
|
22
|
+
: timeoutSignal;
|
|
16
23
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
).json()) as JsonRpcResponse;
|
|
28
|
-
} finally {
|
|
29
|
-
clearTimeout(abortTimer);
|
|
30
|
-
}
|
|
24
|
+
return (await (
|
|
25
|
+
await fetch(this.url, {
|
|
26
|
+
method: "POST",
|
|
27
|
+
headers: {
|
|
28
|
+
"content-type": "application/json",
|
|
29
|
+
},
|
|
30
|
+
body: JSON.stringify(payload),
|
|
31
|
+
signal,
|
|
32
|
+
})
|
|
33
|
+
).json()) as JsonRpcResponse;
|
|
31
34
|
}
|
|
32
35
|
}
|
|
@@ -33,6 +33,13 @@ export type JsonRpcResponse<Result = unknown, ErrorData = unknown> = {
|
|
|
33
33
|
| { result?: never; error: JsonRpcErrorLike<ErrorData> }
|
|
34
34
|
);
|
|
35
35
|
|
|
36
|
+
export type JsonRpcTransportRequestOptions = {
|
|
37
|
+
/** Cancels this individual request without disposing the transport. */
|
|
38
|
+
signal?: AbortSignal;
|
|
39
|
+
/** Overrides the transport's default timeout for this request. */
|
|
40
|
+
timeout?: number;
|
|
41
|
+
};
|
|
42
|
+
|
|
36
43
|
export interface JsonRpcTransport {
|
|
37
44
|
/**
|
|
38
45
|
* Sends a JSON-RPC request to the server.
|
|
@@ -40,5 +47,8 @@ export interface JsonRpcTransport {
|
|
|
40
47
|
* @param payload - The JSON-RPC payload to send.
|
|
41
48
|
* @returns The JSON-RPC response.
|
|
42
49
|
*/
|
|
43
|
-
request(
|
|
50
|
+
request(
|
|
51
|
+
payload: JsonRpcPayload,
|
|
52
|
+
options?: JsonRpcTransportRequestOptions,
|
|
53
|
+
): Promise<JsonRpcResponse>;
|
|
44
54
|
}
|
|
@@ -5,17 +5,21 @@ import {
|
|
|
5
5
|
JsonRpcPayload,
|
|
6
6
|
JsonRpcResponse,
|
|
7
7
|
JsonRpcTransport,
|
|
8
|
+
JsonRpcTransportRequestOptions,
|
|
8
9
|
} from "./transport.js";
|
|
9
10
|
|
|
11
|
+
const SOCKET_TIMEOUT_GRACE_PERIOD = 10_000;
|
|
12
|
+
|
|
10
13
|
export class JsonRpcTransportWebSocket implements JsonRpcTransport {
|
|
11
14
|
private ongoing: Map<
|
|
12
15
|
JsonRpcId,
|
|
13
16
|
[
|
|
14
17
|
(response: JsonRpcResponse) => unknown,
|
|
15
18
|
(error: unknown) => unknown,
|
|
16
|
-
|
|
19
|
+
WebSocket,
|
|
17
20
|
]
|
|
18
21
|
> = new Map();
|
|
22
|
+
private firstTimeout?: { socket: WebSocket; at: number };
|
|
19
23
|
private disposed = false;
|
|
20
24
|
private socket?: WebSocket;
|
|
21
25
|
private openSocket?: Promise<WebSocket>;
|
|
@@ -39,12 +43,20 @@ export class JsonRpcTransportWebSocket implements JsonRpcTransport {
|
|
|
39
43
|
return new OwnerUnique(transport, (transport) => transport.dispose());
|
|
40
44
|
}
|
|
41
45
|
|
|
42
|
-
request(
|
|
46
|
+
request(
|
|
47
|
+
data: JsonRpcPayload,
|
|
48
|
+
options?: JsonRpcTransportRequestOptions,
|
|
49
|
+
): Promise<JsonRpcResponse> {
|
|
43
50
|
if (this.disposed) {
|
|
44
51
|
return Promise.reject(
|
|
45
52
|
new Error("Cannot use a disposed JsonRpcTransportWebSocket"),
|
|
46
53
|
);
|
|
47
54
|
}
|
|
55
|
+
if (options?.signal?.aborted) {
|
|
56
|
+
return Promise.reject(
|
|
57
|
+
options.signal.reason ?? new Error("Request aborted"),
|
|
58
|
+
);
|
|
59
|
+
}
|
|
48
60
|
|
|
49
61
|
const [socketUnsafe, socket] = (() => {
|
|
50
62
|
if (
|
|
@@ -71,23 +83,26 @@ export class JsonRpcTransportWebSocket implements JsonRpcTransport {
|
|
|
71
83
|
return;
|
|
72
84
|
}
|
|
73
85
|
const id = res.id;
|
|
86
|
+
if (this.firstTimeout?.socket === socket) {
|
|
87
|
+
this.firstTimeout = undefined;
|
|
88
|
+
}
|
|
74
89
|
|
|
75
90
|
const req = this.ongoing.get(id);
|
|
76
|
-
if (!req) {
|
|
91
|
+
if (!req || req[2] !== socket) {
|
|
77
92
|
return;
|
|
78
93
|
}
|
|
79
|
-
const [resolve
|
|
80
|
-
clearTimeout(timeout);
|
|
81
|
-
this.ongoing.delete(id);
|
|
82
|
-
|
|
94
|
+
const [resolve] = req;
|
|
83
95
|
resolve(res);
|
|
84
96
|
};
|
|
85
97
|
const onClose = () => {
|
|
86
|
-
this.
|
|
87
|
-
|
|
88
|
-
|
|
98
|
+
if (this.firstTimeout?.socket === socket) {
|
|
99
|
+
this.firstTimeout = undefined;
|
|
100
|
+
}
|
|
101
|
+
this.ongoing.forEach(([_, reject, requestSocket]) => {
|
|
102
|
+
if (requestSocket === socket) {
|
|
103
|
+
reject(new Error("Connection closed"));
|
|
104
|
+
}
|
|
89
105
|
});
|
|
90
|
-
this.ongoing.clear();
|
|
91
106
|
};
|
|
92
107
|
|
|
93
108
|
socket.onclose = onClose;
|
|
@@ -108,41 +123,84 @@ export class JsonRpcTransportWebSocket implements JsonRpcTransport {
|
|
|
108
123
|
})();
|
|
109
124
|
|
|
110
125
|
return new Promise<JsonRpcResponse>((resolve, reject) => {
|
|
126
|
+
const abortSignal = options?.signal;
|
|
127
|
+
let timeout: ReturnType<typeof setTimeout>;
|
|
111
128
|
const req: [
|
|
112
129
|
(res: JsonRpcResponse) => unknown,
|
|
113
130
|
(err: unknown) => unknown,
|
|
114
|
-
|
|
131
|
+
WebSocket,
|
|
115
132
|
] = [
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
133
|
+
(response) => {
|
|
134
|
+
if (cleanup()) {
|
|
135
|
+
resolve(response);
|
|
136
|
+
}
|
|
137
|
+
},
|
|
138
|
+
(error) => {
|
|
139
|
+
if (cleanup()) {
|
|
140
|
+
reject(error);
|
|
141
|
+
}
|
|
142
|
+
},
|
|
143
|
+
socketUnsafe,
|
|
123
144
|
];
|
|
145
|
+
const cleanup = () => {
|
|
146
|
+
if (this.ongoing.get(data.id) !== req) {
|
|
147
|
+
return false;
|
|
148
|
+
}
|
|
149
|
+
this.ongoing.delete(data.id);
|
|
150
|
+
clearTimeout(timeout);
|
|
151
|
+
abortSignal?.removeEventListener("abort", onAbort);
|
|
152
|
+
return true;
|
|
153
|
+
};
|
|
154
|
+
const onAbort = () => {
|
|
155
|
+
req[1](abortSignal?.reason ?? new Error("Request aborted"));
|
|
156
|
+
};
|
|
157
|
+
|
|
158
|
+
timeout = setTimeout(() => {
|
|
159
|
+
if (this.ongoing.get(data.id) === req) {
|
|
160
|
+
req[1](new Error("Request timeout"));
|
|
161
|
+
const hasPending = [...this.ongoing.values()].some(
|
|
162
|
+
([, , requestSocket]) => requestSocket === socketUnsafe,
|
|
163
|
+
);
|
|
164
|
+
if (!hasPending) {
|
|
165
|
+
socketUnsafe.close();
|
|
166
|
+
return;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
const firstTimeout = this.firstTimeout;
|
|
170
|
+
if (firstTimeout?.socket !== socketUnsafe) {
|
|
171
|
+
this.firstTimeout = { socket: socketUnsafe, at: Date.now() };
|
|
172
|
+
} else if (
|
|
173
|
+
Date.now() - firstTimeout.at >=
|
|
174
|
+
SOCKET_TIMEOUT_GRACE_PERIOD
|
|
175
|
+
) {
|
|
176
|
+
socketUnsafe.close();
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
}, options?.timeout ?? this.timeout);
|
|
124
180
|
this.ongoing.set(data.id, req);
|
|
125
181
|
|
|
182
|
+
abortSignal?.addEventListener("abort", onAbort, { once: true });
|
|
183
|
+
if (abortSignal?.aborted) {
|
|
184
|
+
onAbort();
|
|
185
|
+
return;
|
|
186
|
+
}
|
|
187
|
+
|
|
126
188
|
void socket
|
|
127
189
|
.then((socket) => {
|
|
128
|
-
if (
|
|
190
|
+
if (this.ongoing.get(data.id) !== req) {
|
|
129
191
|
return;
|
|
130
192
|
}
|
|
131
193
|
if (
|
|
132
194
|
socket.readyState === socket.CLOSED ||
|
|
133
195
|
socket.readyState === socket.CLOSING
|
|
134
196
|
) {
|
|
135
|
-
|
|
136
|
-
this.ongoing.delete(data.id);
|
|
137
|
-
reject(new Error("Connection closed"));
|
|
197
|
+
req[1](new Error("Connection closed"));
|
|
138
198
|
} else {
|
|
139
199
|
socket.send(JSON.stringify(data));
|
|
140
200
|
}
|
|
141
201
|
})
|
|
142
202
|
.catch((err) => {
|
|
143
|
-
|
|
144
|
-
this.ongoing.delete(data.id);
|
|
145
|
-
reject(err);
|
|
203
|
+
req[1](err);
|
|
146
204
|
});
|
|
147
205
|
});
|
|
148
206
|
}
|
|
@@ -151,7 +209,9 @@ export class JsonRpcTransportWebSocket implements JsonRpcTransport {
|
|
|
151
209
|
this.disposed = true;
|
|
152
210
|
const socket = this.socket;
|
|
153
211
|
|
|
154
|
-
if (!socket || socket.readyState === socket.CLOSED)
|
|
212
|
+
if (!socket || socket.readyState === socket.CLOSED) {
|
|
213
|
+
return;
|
|
214
|
+
}
|
|
155
215
|
|
|
156
216
|
await new Promise<void>((resolve) => {
|
|
157
217
|
socket.addEventListener("close", () => resolve(), { once: true });
|
package/src/signer/btc/psbt.ts
CHANGED
|
@@ -5,7 +5,7 @@ import { Hex, HexLike, hexFrom } from "../../hex/index.js";
|
|
|
5
5
|
*/
|
|
6
6
|
export type SignPsbtOptionsLike = {
|
|
7
7
|
/**
|
|
8
|
-
* Whether to finalize the
|
|
8
|
+
* Whether to attempt to finalize the signed inputs after signing.
|
|
9
9
|
* Default is true.
|
|
10
10
|
*/
|
|
11
11
|
autoFinalized?: boolean;
|
|
@@ -21,7 +21,9 @@ export class SignPsbtOptions {
|
|
|
21
21
|
public inputsToSign: InputToSign[],
|
|
22
22
|
) {}
|
|
23
23
|
|
|
24
|
-
static from(
|
|
24
|
+
static from(
|
|
25
|
+
options?: SignPsbtOptionsLike | SignPsbtOptions,
|
|
26
|
+
): SignPsbtOptions {
|
|
25
27
|
if (options instanceof SignPsbtOptions) {
|
|
26
28
|
return options;
|
|
27
29
|
}
|